blob: 68e0883fbc88e67785dc4ec59ccff311754a4184 [file] [log] [blame]
Youngsang Chof1647922015-12-17 13:39:39 -08001/*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.systemui.tv.pip;
18
Winsonc0d70582016-01-29 10:24:39 -080019import android.app.ActivityManager.RunningTaskInfo;
Youngsang Chof1647922015-12-17 13:39:39 -080020import android.app.ActivityManager.StackInfo;
21import android.app.ActivityManagerNative;
22import android.app.ActivityOptions;
23import android.app.IActivityManager;
Youngsang Chof1647922015-12-17 13:39:39 -080024import android.content.BroadcastReceiver;
Jaewan Kim62338192016-02-25 10:00:05 -080025import android.content.ComponentName;
Youngsang Chof1647922015-12-17 13:39:39 -080026import android.content.Context;
27import android.content.Intent;
28import android.content.IntentFilter;
29import android.content.res.Resources;
30import android.graphics.Rect;
Jaewan Kim62338192016-02-25 10:00:05 -080031import android.media.session.MediaController;
32import android.media.session.MediaSessionManager;
Jaewan Kim8f584b82016-03-22 22:16:59 +090033import android.media.session.PlaybackState;
Wale Ogunwale480dca02016-02-06 13:58:29 -080034import android.os.Debug;
Youngsang Chof1647922015-12-17 13:39:39 -080035import android.os.Handler;
36import android.os.RemoteException;
Jaewan Kimc64b0ee2016-03-04 18:20:53 +090037import android.os.SystemProperties;
Youngsang Chof1647922015-12-17 13:39:39 -080038import android.util.Log;
39
Jaewan Kim977dcdc2016-01-20 19:21:08 +090040import com.android.systemui.Prefs;
Jaewan Kim8f584b82016-03-22 22:16:59 +090041import com.android.systemui.R;
Jaewan Kim938a50b2016-03-14 17:35:43 +090042import com.android.systemui.recents.misc.SystemServicesProxy;
43import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener;
Jaewan Kim977dcdc2016-01-20 19:21:08 +090044
Youngsang Chof1647922015-12-17 13:39:39 -080045import java.util.ArrayList;
46import java.util.List;
47
48import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
49import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
Jaewan Kim977dcdc2016-01-20 19:21:08 +090050import static com.android.systemui.Prefs.Key.TV_PICTURE_IN_PICTURE_ONBOARDING_SHOWN;
Youngsang Chof1647922015-12-17 13:39:39 -080051
52/**
53 * Manages the picture-in-picture (PIP) UI and states.
54 */
55public class PipManager {
56 private static final String TAG = "PipManager";
57 private static final boolean DEBUG = false;
Jaewan Kimc64b0ee2016-03-04 18:20:53 +090058 private static final boolean DEBUG_FORCE_ONBOARDING =
59 SystemProperties.getBoolean("debug.tv.pip_force_onboarding", false);
Youngsang Chof1647922015-12-17 13:39:39 -080060
61 private static PipManager sPipManager;
62
Youngsang Choad8ceb02016-01-15 16:59:27 -080063 private static final int MAX_RUNNING_TASKS_COUNT = 10;
64
Wale Ogunwale480dca02016-02-06 13:58:29 -080065 public static final int STATE_NO_PIP = 0;
66 public static final int STATE_PIP_OVERLAY = 1;
67 public static final int STATE_PIP_MENU = 2;
Youngsang Chof1647922015-12-17 13:39:39 -080068
Youngsang Choad8ceb02016-01-15 16:59:27 -080069 private static final int TASK_ID_NO_PIP = -1;
70 private static final int INVALID_RESOURCE_TYPE = -1;
71
Wale Ogunwale480dca02016-02-06 13:58:29 -080072 public static final int SUSPEND_PIP_RESIZE_REASON_WAITING_FOR_MENU_ACTIVITY_FINISH = 0x1;
73 public static final int SUSPEND_PIP_RESIZE_REASON_WAITING_FOR_OVERLAY_ACTIVITY_FINISH = 0x2;
Jaewan Kimf40fcdc2016-03-04 17:58:22 +090074
Jaewan Kim8f584b82016-03-22 22:16:59 +090075 /**
76 * PIPed activity is playing a media and it can be paused.
77 */
78 static final int PLAYBACK_STATE_PLAYING = 0;
79 /**
80 * PIPed activity has a paused media and it can be played.
81 */
82 static final int PLAYBACK_STATE_PAUSED = 1;
83 /**
84 * Users are unable to control PIPed activity's media playback.
85 */
86 static final int PLAYBACK_STATE_UNAVAILABLE = 2;
87
Jaewan Kimf40fcdc2016-03-04 17:58:22 +090088 private static final int CLOSE_PIP_WHEN_MEDIA_SESSION_GONE_TIMEOUT_MS = 3000;
89
Wale Ogunwale480dca02016-02-06 13:58:29 -080090 private int mSuspendPipResizingReason;
91
Youngsang Chof1647922015-12-17 13:39:39 -080092 private Context mContext;
93 private IActivityManager mActivityManager;
Jaewan Kim62338192016-02-25 10:00:05 -080094 private MediaSessionManager mMediaSessionManager;
Youngsang Chof1647922015-12-17 13:39:39 -080095 private int mState = STATE_NO_PIP;
96 private final Handler mHandler = new Handler();
97 private List<Listener> mListeners = new ArrayList<>();
Jaewan Kimc92a7d12016-02-15 17:33:25 -080098 private Rect mCurrentPipBounds;
99 private Rect mPipBounds;
100 private Rect mMenuModePipBounds;
101 private Rect mRecentsPipBounds;
102 private Rect mRecentsFocusedPipBounds;
Jaewan Kim8f584b82016-03-22 22:16:59 +0900103 private int mRecentsFocusChangedAnimationDurationMs;
Youngsang Chof1647922015-12-17 13:39:39 -0800104 private boolean mInitialized;
Youngsang Choad8ceb02016-01-15 16:59:27 -0800105 private int mPipTaskId = TASK_ID_NO_PIP;
Jaewan Kim62338192016-02-25 10:00:05 -0800106 private ComponentName mPipComponentName;
107 private MediaController mPipMediaController;
Jaewan Kim977dcdc2016-01-20 19:21:08 +0900108 private boolean mOnboardingShown;
Youngsang Choad8ceb02016-01-15 16:59:27 -0800109
Jaewan Kimc92a7d12016-02-15 17:33:25 -0800110 private boolean mIsRecentsShown;
111 private boolean mIsPipFocusedInRecent;
112
Wale Ogunwale480dca02016-02-06 13:58:29 -0800113 private final Runnable mResizePinnedStackRunnable = new Runnable() {
114 @Override
115 public void run() {
116 resizePinnedStack(mState);
117 }
118 };
Jaewan Kimf40fcdc2016-03-04 17:58:22 +0900119 private final Runnable mClosePipRunnable = new Runnable() {
120 @Override
121 public void run() {
122 closePip();
123 }
124 };
Youngsang Chof1647922015-12-17 13:39:39 -0800125
Youngsang Choad8ceb02016-01-15 16:59:27 -0800126 private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
Youngsang Chof1647922015-12-17 13:39:39 -0800127 @Override
128 public void onReceive(Context context, Intent intent) {
Youngsang Choad8ceb02016-01-15 16:59:27 -0800129 String action = intent.getAction();
Jaewan Kimc552b042016-01-18 16:08:45 +0900130 if (Intent.ACTION_MEDIA_RESOURCE_GRANTED.equals(action)) {
Youngsang Choad8ceb02016-01-15 16:59:27 -0800131 String[] packageNames = intent.getStringArrayExtra(Intent.EXTRA_PACKAGES);
132 int resourceType = intent.getIntExtra(Intent.EXTRA_MEDIA_RESOURCE_TYPE,
133 INVALID_RESOURCE_TYPE);
134 if (mState != STATE_NO_PIP && packageNames != null && packageNames.length > 0
135 && resourceType == Intent.EXTRA_MEDIA_RESOURCE_TYPE_VIDEO_CODEC) {
136 handleMediaResourceGranted(packageNames);
137 }
Youngsang Chof1647922015-12-17 13:39:39 -0800138 }
Youngsang Choad8ceb02016-01-15 16:59:27 -0800139
Youngsang Chof1647922015-12-17 13:39:39 -0800140 }
141 };
Jaewan Kim62338192016-02-25 10:00:05 -0800142 private final MediaSessionManager.OnActiveSessionsChangedListener mActiveMediaSessionListener =
143 new MediaSessionManager.OnActiveSessionsChangedListener() {
144 @Override
145 public void onActiveSessionsChanged(List<MediaController> controllers) {
146 updateMediaController(controllers);
147 }
148 };
Youngsang Chof1647922015-12-17 13:39:39 -0800149
150 private PipManager() { }
151
152 /**
153 * Initializes {@link PipManager}.
154 */
155 public void initialize(Context context) {
156 if (mInitialized) {
157 return;
158 }
159 mInitialized = true;
160 mContext = context;
161 Resources res = context.getResources();
Jaewan Kimc92a7d12016-02-15 17:33:25 -0800162 mPipBounds = Rect.unflattenFromString(res.getString(
Youngsang Chof1647922015-12-17 13:39:39 -0800163 com.android.internal.R.string.config_defaultPictureInPictureBounds));
Jaewan Kimc92a7d12016-02-15 17:33:25 -0800164 mMenuModePipBounds = Rect.unflattenFromString(res.getString(
Jaewan Kim8f584b82016-03-22 22:16:59 +0900165 R.string.pip_menu_bounds));
Jaewan Kimc92a7d12016-02-15 17:33:25 -0800166 mRecentsPipBounds = Rect.unflattenFromString(res.getString(
Jaewan Kim8f584b82016-03-22 22:16:59 +0900167 R.string.pip_recents_bounds));
168 mRecentsFocusedPipBounds = Rect.unflattenFromString(res.getString(
169 R.string.pip_recents_focused_bounds));
170 mRecentsFocusChangedAnimationDurationMs = res.getInteger(
171 R.integer.recents_tv_pip_focus_anim_duration);
Youngsang Chof1647922015-12-17 13:39:39 -0800172
173 mActivityManager = ActivityManagerNative.getDefault();
Jaewan Kim938a50b2016-03-14 17:35:43 +0900174 SystemServicesProxy.getInstance(context).registerTaskStackListener(mTaskStackListener);
Youngsang Chof1647922015-12-17 13:39:39 -0800175 IntentFilter intentFilter = new IntentFilter();
Youngsang Choad8ceb02016-01-15 16:59:27 -0800176 intentFilter.addAction(Intent.ACTION_MEDIA_RESOURCE_GRANTED);
177 mContext.registerReceiver(mBroadcastReceiver, intentFilter);
Jaewan Kim977dcdc2016-01-20 19:21:08 +0900178 mOnboardingShown = Prefs.getBoolean(
179 mContext, TV_PICTURE_IN_PICTURE_ONBOARDING_SHOWN, false);
Jaewan Kim62338192016-02-25 10:00:05 -0800180
181 mMediaSessionManager =
182 (MediaSessionManager) mContext.getSystemService(Context.MEDIA_SESSION_SERVICE);
Youngsang Chof1647922015-12-17 13:39:39 -0800183 }
184
Jaewan Kimc552b042016-01-18 16:08:45 +0900185 /**
186 * Request PIP.
187 * It could either start PIP if there's none, and show PIP menu otherwise.
188 */
189 public void requestTvPictureInPicture() {
190 if (DEBUG) Log.d(TAG, "requestTvPictureInPicture()");
Jaewan Kimc92a7d12016-02-15 17:33:25 -0800191 if (!isPipShown()) {
Jaewan Kimc552b042016-01-18 16:08:45 +0900192 startPip();
193 } else if (mState == STATE_PIP_OVERLAY) {
Wale Ogunwale480dca02016-02-06 13:58:29 -0800194 resizePinnedStack(STATE_PIP_MENU);
Jaewan Kimc552b042016-01-18 16:08:45 +0900195 }
196 }
197
Youngsang Chof1647922015-12-17 13:39:39 -0800198 private void startPip() {
199 try {
Jaewan Kimc92a7d12016-02-15 17:33:25 -0800200 mActivityManager.moveTopActivityToPinnedStack(FULLSCREEN_WORKSPACE_STACK_ID, mPipBounds);
Youngsang Chof1647922015-12-17 13:39:39 -0800201 } catch (RemoteException|IllegalArgumentException e) {
202 Log.e(TAG, "moveTopActivityToPinnedStack failed", e);
203 }
Youngsang Chof1647922015-12-17 13:39:39 -0800204 }
205
206 /**
Jaewan Kim977dcdc2016-01-20 19:21:08 +0900207 * Closes PIP (PIPed activity and PIP system UI).
Youngsang Chof1647922015-12-17 13:39:39 -0800208 */
209 public void closePip() {
Youngsang Cho336007b2016-02-22 11:17:29 -0800210 closePipInternal(true);
Youngsang Cho23df6992016-01-26 17:51:33 -0800211 }
212
Youngsang Cho336007b2016-02-22 11:17:29 -0800213 private void closePipInternal(boolean removePipStack) {
Youngsang Chof1647922015-12-17 13:39:39 -0800214 mState = STATE_NO_PIP;
Youngsang Choad8ceb02016-01-15 16:59:27 -0800215 mPipTaskId = TASK_ID_NO_PIP;
Jaewan Kim62338192016-02-25 10:00:05 -0800216 mPipMediaController = null;
217 mMediaSessionManager.removeOnActiveSessionsChangedListener(mActiveMediaSessionListener);
Youngsang Cho23df6992016-01-26 17:51:33 -0800218 if (removePipStack) {
219 try {
220 mActivityManager.removeStack(PINNED_STACK_ID);
221 } catch (RemoteException e) {
222 Log.e(TAG, "removeStack failed", e);
223 }
Youngsang Chof1647922015-12-17 13:39:39 -0800224 }
Jaewan Kimc92a7d12016-02-15 17:33:25 -0800225 for (int i = mListeners.size() - 1; i >= 0; --i) {
226 mListeners.get(i).onPipActivityClosed();
227 }
Jaewan Kimf40fcdc2016-03-04 17:58:22 +0900228 mHandler.removeCallbacks(mClosePipRunnable);
Youngsang Chof1647922015-12-17 13:39:39 -0800229 }
230
231 /**
Jaewan Kim977dcdc2016-01-20 19:21:08 +0900232 * Moves the PIPed activity to the fullscreen and closes PIP system UI.
Youngsang Chof1647922015-12-17 13:39:39 -0800233 */
234 public void movePipToFullscreen() {
235 mState = STATE_NO_PIP;
Youngsang Choad8ceb02016-01-15 16:59:27 -0800236 mPipTaskId = TASK_ID_NO_PIP;
Youngsang Chof1647922015-12-17 13:39:39 -0800237 for (int i = mListeners.size() - 1; i >= 0; --i) {
238 mListeners.get(i).onMoveToFullscreen();
239 }
Wale Ogunwale480dca02016-02-06 13:58:29 -0800240 resizePinnedStack(mState);
Youngsang Chof1647922015-12-17 13:39:39 -0800241 }
242
243 /**
244 * Shows PIP overlay UI by launching {@link PipOverlayActivity}. It also locates the pinned
245 * stack to the default PIP bound {@link com.android.internal.R.string
246 * .config_defaultPictureInPictureBounds}.
247 */
Wale Ogunwale480dca02016-02-06 13:58:29 -0800248 private void showPipOverlay() {
Youngsang Chof1647922015-12-17 13:39:39 -0800249 if (DEBUG) Log.d(TAG, "showPipOverlay()");
Youngsang Chof1647922015-12-17 13:39:39 -0800250 mState = STATE_PIP_OVERLAY;
Jaewan Kim21e04212016-03-21 14:22:33 +0900251 PipOverlayActivity.showPipOverlay(mContext);
Youngsang Chof1647922015-12-17 13:39:39 -0800252 }
253
254 /**
Wale Ogunwale480dca02016-02-06 13:58:29 -0800255 * Suspends resizing operation on the Pip until {@link #resumePipResizing} is called
256 * @param reason The reason for suspending resizing operations on the Pip.
257 */
258 public void suspendPipResizing(int reason) {
259 if (DEBUG) Log.d(TAG,
260 "suspendPipResizing() reason=" + reason + " callers=" + Debug.getCallers(2));
261 mSuspendPipResizingReason |= reason;
262 }
263
264 /**
265 * Resumes resizing operation on the Pip that was previously suspended.
266 * @param reason The reason resizing operations on the Pip was suspended.
267 */
268 public void resumePipResizing(int reason) {
269 if ((mSuspendPipResizingReason & reason) == 0) {
270 return;
271 }
272 if (DEBUG) Log.d(TAG,
273 "resumePipResizing() reason=" + reason + " callers=" + Debug.getCallers(2));
274 mSuspendPipResizingReason &= ~reason;
275 mHandler.post(mResizePinnedStackRunnable);
276 }
277
278 /**
279 * Resize the Pip to the appropriate size for the input state.
280 * @param state In Pip state also used to determine the new size for the Pip.
281 */
282 public void resizePinnedStack(int state) {
283 if (DEBUG) Log.d(TAG, "resizePinnedStack() state=" + state);
284 mState = state;
Wale Ogunwale480dca02016-02-06 13:58:29 -0800285 for (int i = mListeners.size() - 1; i >= 0; --i) {
286 mListeners.get(i).onPipResizeAboutToStart();
287 }
Wale Ogunwale480dca02016-02-06 13:58:29 -0800288 if (mSuspendPipResizingReason != 0) {
289 if (DEBUG) Log.d(TAG,
290 "resizePinnedStack() deferring mSuspendPipResizingReason=" +
291 mSuspendPipResizingReason);
292 return;
293 }
Jaewan Kim8f584b82016-03-22 22:16:59 +0900294 int animationDurationMs = -1;
Jaewan Kimc92a7d12016-02-15 17:33:25 -0800295 switch (mState) {
296 case STATE_NO_PIP:
297 mCurrentPipBounds = null;
298 break;
299 case STATE_PIP_MENU:
300 mCurrentPipBounds = mMenuModePipBounds;
301 break;
302 case STATE_PIP_OVERLAY:
303 if (mIsRecentsShown) {
Jaewan Kim8f584b82016-03-22 22:16:59 +0900304 if (mCurrentPipBounds == mRecentsFocusedPipBounds
305 || mCurrentPipBounds == mRecentsFocusedPipBounds) {
306 animationDurationMs = mRecentsFocusChangedAnimationDurationMs;
307 }
Jaewan Kimc92a7d12016-02-15 17:33:25 -0800308 if (mIsPipFocusedInRecent) {
309 mCurrentPipBounds = mRecentsFocusedPipBounds;
310 } else {
311 mCurrentPipBounds = mRecentsPipBounds;
312 }
313 } else {
314 mCurrentPipBounds = mPipBounds;
315 }
316 break;
317 default:
318 mCurrentPipBounds = mPipBounds;
319 break;
320 }
Wale Ogunwale480dca02016-02-06 13:58:29 -0800321 try {
Jaewan Kim8f584b82016-03-22 22:16:59 +0900322 mActivityManager.resizeStack(PINNED_STACK_ID, mCurrentPipBounds,
323 true, true, true, animationDurationMs);
Wale Ogunwale480dca02016-02-06 13:58:29 -0800324 } catch (RemoteException e) {
Jaewan Kim82ac50d2016-03-21 17:34:28 +0900325 Log.e(TAG, "resizeStack failed", e);
Wale Ogunwale480dca02016-02-06 13:58:29 -0800326 }
327 }
328
329 /**
Jaewan Kimc92a7d12016-02-15 17:33:25 -0800330 * Returns the current PIP bound for activities to sync their UI with PIP.
331 */
332 public Rect getPipBounds() {
333 return mCurrentPipBounds;
334 }
335
336 /**
337 * Called when Recents is started.
338 * PIPed activity will be resized accordingly and overlay will show available buttons.
339 */
340 public void onRecentsStarted() {
341 mIsRecentsShown = true;
342 mIsPipFocusedInRecent = false;
343 if (mState == STATE_NO_PIP) {
344 return;
345 }
346 resizePinnedStack(STATE_PIP_OVERLAY);
347 }
348
349 /**
350 * Called when Recents is stopped.
351 * PIPed activity will be resized accordingly and overlay will hide available buttons.
352 */
353 public void onRecentsStopped() {
354 mIsRecentsShown = false;
355 mIsPipFocusedInRecent = false;
356 if (mState == STATE_NO_PIP) {
357 return;
358 }
359 resizePinnedStack(STATE_PIP_OVERLAY);
360 }
361
362 /**
363 * Returns {@code true} if recents is shown.
364 */
365 boolean isRecentsShown() {
366 return mIsRecentsShown;
367 }
368
369 /**
370 * Called when the PIP view in {@link com.android.systemui.recents.tv.RecentsTvActivity}
371 * is focused.
372 * This only resizes pinned stack so it looks like it's in Recents.
373 * This should be called only by {@link com.android.systemui.recents.tv.RecentsTvActivity}.
374 */
375 public void onPipViewFocusChangedInRecents(boolean hasFocus) {
376 mIsPipFocusedInRecent = hasFocus;
377 if (mState != STATE_PIP_OVERLAY) {
378 Log.w(TAG, "There is no pinned stack to handle focus change.");
379 return;
380 }
381 resizePinnedStack(STATE_PIP_OVERLAY);
382 }
383
384 /**
Jaewan Kim8f584b82016-03-22 22:16:59 +0900385 * Returns {@code true} if the PIP view in
386 * {@link com.android.systemui.recents.tv.RecentsTvActivity} is focused in Recents.
387 * This API is valid only when {@link isRecentsShown()} returns {@code true}.
388 */
389 boolean isPipViewFocusdInRecents() {
390 return mIsPipFocusedInRecent;
391 }
392
393 /**
Youngsang Chof1647922015-12-17 13:39:39 -0800394 * Shows PIP menu UI by launching {@link PipMenuActivity}. It also locates the pinned
Jaewan Kim8f584b82016-03-22 22:16:59 +0900395 * stack to the centered PIP bound {@link R.config_centeredPictureInPictureBounds}.
Youngsang Chof1647922015-12-17 13:39:39 -0800396 */
Wale Ogunwale480dca02016-02-06 13:58:29 -0800397 private void showPipMenu() {
Youngsang Chof1647922015-12-17 13:39:39 -0800398 if (DEBUG) Log.d(TAG, "showPipMenu()");
Youngsang Chof1647922015-12-17 13:39:39 -0800399 mState = STATE_PIP_MENU;
400 for (int i = mListeners.size() - 1; i >= 0; --i) {
401 mListeners.get(i).onShowPipMenu();
402 }
403 Intent intent = new Intent(mContext, PipMenuActivity.class);
404 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Jaewan Kim1a9dc562016-02-17 13:41:51 -0800405 mContext.startActivity(intent);
Youngsang Chof1647922015-12-17 13:39:39 -0800406 }
407
Youngsang Chof1647922015-12-17 13:39:39 -0800408 public void addListener(Listener listener) {
409 mListeners.add(listener);
410 }
411
Youngsang Chof1647922015-12-17 13:39:39 -0800412 public void removeListener(Listener listener) {
413 mListeners.remove(listener);
414 }
415
Jaewan Kim977dcdc2016-01-20 19:21:08 +0900416 private void launchPipOnboardingActivityIfNeeded() {
417 if (DEBUG_FORCE_ONBOARDING || !mOnboardingShown) {
418 mOnboardingShown = true;
419 Prefs.putBoolean(mContext, TV_PICTURE_IN_PICTURE_ONBOARDING_SHOWN, true);
420
421 Intent intent = new Intent(mContext, PipOnboardingActivity.class);
422 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
423 mContext.startActivity(intent);
424 }
425 }
426
Jaewan Kimc92a7d12016-02-15 17:33:25 -0800427 /**
428 * Returns {@code true} if PIP is shown.
429 */
430 public boolean isPipShown() {
Jaewan Kim82ac50d2016-03-21 17:34:28 +0900431 return mState != STATE_NO_PIP;
Youngsang Chof1647922015-12-17 13:39:39 -0800432 }
433
Youngsang Choad8ceb02016-01-15 16:59:27 -0800434 private void handleMediaResourceGranted(String[] packageNames) {
435 StackInfo fullscreenStack = null;
436 try {
437 fullscreenStack = mActivityManager.getStackInfo(FULLSCREEN_WORKSPACE_STACK_ID);
438 } catch (RemoteException e) {
439 Log.e(TAG, "getStackInfo failed", e);
440 }
441 if (fullscreenStack == null) {
442 return;
443 }
444 int fullscreenTopTaskId = fullscreenStack.taskIds[fullscreenStack.taskIds.length - 1];
445 List<RunningTaskInfo> tasks = null;
446 try {
447 tasks = mActivityManager.getTasks(MAX_RUNNING_TASKS_COUNT, 0);
448 } catch (RemoteException e) {
449 Log.e(TAG, "getTasks failed", e);
450 }
451 if (tasks == null) {
452 return;
453 }
454 boolean wasGrantedInFullscreen = false;
455 boolean wasGrantedInPip = false;
456 for (int i = tasks.size() - 1; i >= 0; --i) {
457 RunningTaskInfo task = tasks.get(i);
458 for (int j = packageNames.length - 1; j >= 0; --j) {
459 if (task.topActivity.getPackageName().equals(packageNames[j])) {
460 if (task.id == fullscreenTopTaskId) {
461 wasGrantedInFullscreen = true;
462 } else if (task.id == mPipTaskId) {
463 wasGrantedInPip= true;
464 }
465 }
466 }
467 }
468 if (wasGrantedInFullscreen && !wasGrantedInPip) {
469 closePip();
470 }
471 }
472
Jaewan Kim62338192016-02-25 10:00:05 -0800473 private void updateMediaController(List<MediaController> controllers) {
474 MediaController mediaController = null;
475 if (controllers != null && mState != STATE_NO_PIP && mPipComponentName != null) {
476 for (int i = controllers.size() - 1; i >= 0; i--) {
477 MediaController controller = controllers.get(i);
478 // We assumes that an app with PIPable activity
479 // keeps the single instance of media controller especially when PIP is on.
480 if (controller.getPackageName().equals(mPipComponentName.getPackageName())) {
481 mediaController = controller;
482 break;
483 }
484 }
485 }
486 if (mPipMediaController != mediaController) {
487 mPipMediaController = mediaController;
488 for (int i = mListeners.size() - 1; i >= 0; i--) {
489 mListeners.get(i).onMediaControllerChanged();
490 }
Jaewan Kimf40fcdc2016-03-04 17:58:22 +0900491 if (mPipMediaController == null) {
492 mHandler.postDelayed(mClosePipRunnable,
493 CLOSE_PIP_WHEN_MEDIA_SESSION_GONE_TIMEOUT_MS);
494 } else {
495 mHandler.removeCallbacks(mClosePipRunnable);
496 }
Jaewan Kim62338192016-02-25 10:00:05 -0800497 }
498 }
499
500 /**
501 * Gets the {@link android.media.session.MediaController} for the PIPed activity.
502 */
503 MediaController getMediaController() {
504 return mPipMediaController;
505 }
506
Jaewan Kim8f584b82016-03-22 22:16:59 +0900507 /**
508 * Returns the PIPed activity's playback state.
509 * This returns one of {@link PLAYBACK_STATE_PLAYING}, {@link PLAYBACK_STATE_PAUSED},
510 * or {@link PLAYBACK_STATE_UNAVAILABLE}.
511 */
512 int getPlaybackState() {
513 if (mPipMediaController == null || mPipMediaController.getPlaybackState() == null) {
514 return PLAYBACK_STATE_UNAVAILABLE;
515 }
516 int state = mPipMediaController.getPlaybackState().getState();
517 boolean isPlaying = (state == PlaybackState.STATE_BUFFERING
518 || state == PlaybackState.STATE_CONNECTING
519 || state == PlaybackState.STATE_PLAYING
520 || state == PlaybackState.STATE_FAST_FORWARDING
521 || state == PlaybackState.STATE_REWINDING
522 || state == PlaybackState.STATE_SKIPPING_TO_PREVIOUS
523 || state == PlaybackState.STATE_SKIPPING_TO_NEXT);
524 long actions = mPipMediaController.getPlaybackState().getActions();
525 if (!isPlaying && ((actions & PlaybackState.ACTION_PLAY) != 0)) {
526 return PLAYBACK_STATE_PAUSED;
527 } else if (isPlaying && ((actions & PlaybackState.ACTION_PAUSE) != 0)) {
528 return PLAYBACK_STATE_PLAYING;
529 }
530 return PLAYBACK_STATE_UNAVAILABLE;
531 }
532
Jaewan Kim938a50b2016-03-14 17:35:43 +0900533 TaskStackListener mTaskStackListener = new TaskStackListener() {
Youngsang Chof1647922015-12-17 13:39:39 -0800534 @Override
Jaewan Kim938a50b2016-03-14 17:35:43 +0900535 public void onTaskStackChanged() {
536 if (mState != STATE_NO_PIP) {
537 StackInfo stackInfo = null;
538 try {
539 stackInfo = mActivityManager.getStackInfo(PINNED_STACK_ID);
540 if (stackInfo == null) {
541 Log.w(TAG, "There is no pinned stack");
542 closePipInternal(false);
543 return;
544 }
545 } catch (RemoteException e) {
546 Log.e(TAG, "getStackInfo failed", e);
547 return;
548 }
549 for (int i = stackInfo.taskIds.length - 1; i >= 0; --i) {
550 if (stackInfo.taskIds[i] == mPipTaskId) {
551 // PIP task is still alive.
552 return;
553 }
554 }
555 // PIP task doesn't exist anymore in PINNED_STACK.
556 closePipInternal(true);
557 }
Youngsang Chof1647922015-12-17 13:39:39 -0800558 }
559
560 @Override
Jaewan Kim938a50b2016-03-14 17:35:43 +0900561 public void onActivityPinned() {
Wale Ogunwale480dca02016-02-06 13:58:29 -0800562 if (DEBUG) Log.d(TAG, "onActivityPinned()");
Jaewan Kim938a50b2016-03-14 17:35:43 +0900563 StackInfo stackInfo = null;
564 try {
565 stackInfo = mActivityManager.getStackInfo(PINNED_STACK_ID);
566 if (stackInfo == null) {
567 Log.w(TAG, "Cannot find pinned stack");
568 return;
569 }
570 } catch (RemoteException e) {
571 Log.e(TAG, "getStackInfo failed", e);
572 return;
573 }
574 if (DEBUG) Log.d(TAG, "PINNED_STACK:" + stackInfo);
575 mPipTaskId = stackInfo.taskIds[stackInfo.taskIds.length - 1];
576 mPipComponentName = ComponentName.unflattenFromString(
577 stackInfo.taskNames[stackInfo.taskNames.length - 1]);
578 // Set state to overlay so we show it when the pinned stack animation ends.
579 mState = STATE_PIP_OVERLAY;
580 mCurrentPipBounds = mPipBounds;
581 launchPipOnboardingActivityIfNeeded();
582 mMediaSessionManager.addOnActiveSessionsChangedListener(
583 mActiveMediaSessionListener, null);
584 updateMediaController(mMediaSessionManager.getActiveSessions(null));
585 if (mIsRecentsShown) {
586 // If an activity becomes PIPed again after the fullscreen, the Recents is shown
587 // behind so we need to resize the pinned stack and show the correct overlay.
588 resizePinnedStack(STATE_PIP_OVERLAY);
589 }
590 for (int i = mListeners.size() - 1; i >= 0; i--) {
591 mListeners.get(i).onPipEntered();
592 }
Youngsang Chof1647922015-12-17 13:39:39 -0800593 }
Wale Ogunwalecc25a8a2016-01-23 14:31:37 -0800594
595 @Override
596 public void onPinnedActivityRestartAttempt() {
Wale Ogunwale480dca02016-02-06 13:58:29 -0800597 if (DEBUG) Log.d(TAG, "onPinnedActivityRestartAttempt()");
Jaewan Kim938a50b2016-03-14 17:35:43 +0900598 // If PIPed activity is launched again by Launcher or intent, make it fullscreen.
599 movePipToFullscreen();
Wale Ogunwalecc25a8a2016-01-23 14:31:37 -0800600 }
Wale Ogunwale480dca02016-02-06 13:58:29 -0800601
602 @Override
603 public void onPinnedStackAnimationEnded() {
604 if (DEBUG) Log.d(TAG, "onPinnedStackAnimationEnded()");
Jaewan Kim938a50b2016-03-14 17:35:43 +0900605 switch (mState) {
606 case STATE_PIP_OVERLAY:
607 showPipOverlay();
608 break;
609 case STATE_PIP_MENU:
610 showPipMenu();
611 break;
612 }
Wale Ogunwale480dca02016-02-06 13:58:29 -0800613 }
Jaewan Kim938a50b2016-03-14 17:35:43 +0900614 };
Youngsang Chof1647922015-12-17 13:39:39 -0800615
616 /**
617 * A listener interface to receive notification on changes in PIP.
618 */
619 public interface Listener {
Jaewan Kim82ac50d2016-03-21 17:34:28 +0900620 /**
621 * Invoked when an activity is pinned and PIP manager is set corresponding information.
622 * Classes must use this instead of {@link android.app.ITaskStackListener.onActivityPinned}
623 * because there's no guarantee for the PIP manager be return relavent information
624 * correctly. (e.g. {@link isPipShown}, {@link getPipBounds})
625 */
626 void onPipEntered();
Wale Ogunwale480dca02016-02-06 13:58:29 -0800627 /** Invoked when a PIPed activity is closed. */
Youngsang Chof1647922015-12-17 13:39:39 -0800628 void onPipActivityClosed();
Wale Ogunwale480dca02016-02-06 13:58:29 -0800629 /** Invoked when the PIP menu gets shown. */
Youngsang Chof1647922015-12-17 13:39:39 -0800630 void onShowPipMenu();
Wale Ogunwale480dca02016-02-06 13:58:29 -0800631 /** Invoked when the PIPed activity is returned back to the fullscreen. */
Youngsang Chof1647922015-12-17 13:39:39 -0800632 void onMoveToFullscreen();
Wale Ogunwale480dca02016-02-06 13:58:29 -0800633 /** Invoked when we are above to start resizing the Pip. */
634 void onPipResizeAboutToStart();
Jaewan Kim62338192016-02-25 10:00:05 -0800635 /** Invoked when the MediaController on PIPed activity is changed. */
636 void onMediaControllerChanged();
Youngsang Chof1647922015-12-17 13:39:39 -0800637 }
638
639 /**
640 * Gets an instance of {@link PipManager}.
641 */
642 public static PipManager getInstance() {
643 if (sPipManager == null) {
644 sPipManager = new PipManager();
645 }
646 return sPipManager;
647 }
648}