blob: 123165e3ba3c309aa10569afbeb2504c31c53e46 [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;
24import android.app.ITaskStackListener;
25import android.content.BroadcastReceiver;
Jaewan Kim62338192016-02-25 10:00:05 -080026import android.content.ComponentName;
Youngsang Chof1647922015-12-17 13:39:39 -080027import android.content.Context;
28import android.content.Intent;
29import android.content.IntentFilter;
30import android.content.res.Resources;
31import android.graphics.Rect;
Jaewan Kim62338192016-02-25 10:00:05 -080032import android.media.session.MediaController;
33import android.media.session.MediaSessionManager;
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;
37import android.util.Log;
38
Jaewan Kim977dcdc2016-01-20 19:21:08 +090039import com.android.systemui.Prefs;
40
Youngsang Chof1647922015-12-17 13:39:39 -080041import java.util.ArrayList;
42import java.util.List;
43
44import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
45import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
Jaewan Kim977dcdc2016-01-20 19:21:08 +090046import static com.android.systemui.Prefs.Key.TV_PICTURE_IN_PICTURE_ONBOARDING_SHOWN;
Youngsang Chof1647922015-12-17 13:39:39 -080047
48/**
49 * Manages the picture-in-picture (PIP) UI and states.
50 */
51public class PipManager {
52 private static final String TAG = "PipManager";
53 private static final boolean DEBUG = false;
Jaewan Kim977dcdc2016-01-20 19:21:08 +090054 private static final boolean DEBUG_FORCE_ONBOARDING = false;
Youngsang Chof1647922015-12-17 13:39:39 -080055
56 private static PipManager sPipManager;
57
Youngsang Choad8ceb02016-01-15 16:59:27 -080058 private static final int MAX_RUNNING_TASKS_COUNT = 10;
59
Wale Ogunwale480dca02016-02-06 13:58:29 -080060 public static final int STATE_NO_PIP = 0;
61 public static final int STATE_PIP_OVERLAY = 1;
62 public static final int STATE_PIP_MENU = 2;
Youngsang Chof1647922015-12-17 13:39:39 -080063
Youngsang Choad8ceb02016-01-15 16:59:27 -080064 private static final int TASK_ID_NO_PIP = -1;
65 private static final int INVALID_RESOURCE_TYPE = -1;
66
Wale Ogunwale480dca02016-02-06 13:58:29 -080067 public static final int SUSPEND_PIP_RESIZE_REASON_WAITING_FOR_MENU_ACTIVITY_FINISH = 0x1;
68 public static final int SUSPEND_PIP_RESIZE_REASON_WAITING_FOR_OVERLAY_ACTIVITY_FINISH = 0x2;
69 private int mSuspendPipResizingReason;
70
Jaewan Kimc92a7d12016-02-15 17:33:25 -080071 private static final float SCALE_FACTOR = 1.1f;
72
Youngsang Chof1647922015-12-17 13:39:39 -080073 private Context mContext;
74 private IActivityManager mActivityManager;
Jaewan Kim62338192016-02-25 10:00:05 -080075 private MediaSessionManager mMediaSessionManager;
Youngsang Chof1647922015-12-17 13:39:39 -080076 private int mState = STATE_NO_PIP;
77 private final Handler mHandler = new Handler();
78 private List<Listener> mListeners = new ArrayList<>();
Jaewan Kimc92a7d12016-02-15 17:33:25 -080079 private Rect mCurrentPipBounds;
80 private Rect mPipBounds;
81 private Rect mMenuModePipBounds;
82 private Rect mRecentsPipBounds;
83 private Rect mRecentsFocusedPipBounds;
Youngsang Chof1647922015-12-17 13:39:39 -080084 private boolean mInitialized;
Youngsang Choad8ceb02016-01-15 16:59:27 -080085 private int mPipTaskId = TASK_ID_NO_PIP;
Jaewan Kim62338192016-02-25 10:00:05 -080086 private ComponentName mPipComponentName;
87 private MediaController mPipMediaController;
Jaewan Kim977dcdc2016-01-20 19:21:08 +090088 private boolean mOnboardingShown;
Youngsang Choad8ceb02016-01-15 16:59:27 -080089
Jaewan Kimc92a7d12016-02-15 17:33:25 -080090 private boolean mIsRecentsShown;
91 private boolean mIsPipFocusedInRecent;
92
Youngsang Chof1647922015-12-17 13:39:39 -080093 private final Runnable mOnActivityPinnedRunnable = new Runnable() {
94 @Override
95 public void run() {
96 StackInfo stackInfo = null;
97 try {
98 stackInfo = mActivityManager.getStackInfo(PINNED_STACK_ID);
99 if (stackInfo == null) {
Jaewan Kimc92a7d12016-02-15 17:33:25 -0800100 Log.w(TAG, "Cannot find pinned stack");
Youngsang Chof1647922015-12-17 13:39:39 -0800101 return;
102 }
103 } catch (RemoteException e) {
104 Log.e(TAG, "getStackInfo failed", e);
105 return;
106 }
107 if (DEBUG) Log.d(TAG, "PINNED_STACK:" + stackInfo);
Youngsang Choad8ceb02016-01-15 16:59:27 -0800108 mPipTaskId = stackInfo.taskIds[stackInfo.taskIds.length - 1];
Jaewan Kim62338192016-02-25 10:00:05 -0800109 mPipComponentName = ComponentName.unflattenFromString(
110 stackInfo.taskNames[stackInfo.taskNames.length - 1]);
Wale Ogunwale480dca02016-02-06 13:58:29 -0800111 // Set state to overlay so we show it when the pinned stack animation ends.
112 mState = STATE_PIP_OVERLAY;
Jaewan Kimc92a7d12016-02-15 17:33:25 -0800113 mCurrentPipBounds = mPipBounds;
Jaewan Kim977dcdc2016-01-20 19:21:08 +0900114 launchPipOnboardingActivityIfNeeded();
Jaewan Kim62338192016-02-25 10:00:05 -0800115 mMediaSessionManager.addOnActiveSessionsChangedListener(
116 mActiveMediaSessionListener, null);
117 updateMediaController(mMediaSessionManager.getActiveSessions(null));
Youngsang Chof1647922015-12-17 13:39:39 -0800118 }
119 };
120 private final Runnable mOnTaskStackChanged = new Runnable() {
121 @Override
122 public void run() {
123 if (mState != STATE_NO_PIP) {
Youngsang Cho23df6992016-01-26 17:51:33 -0800124 StackInfo stackInfo = null;
125 try {
126 stackInfo = mActivityManager.getStackInfo(PINNED_STACK_ID);
127 if (stackInfo == null) {
128 Log.w(TAG, "There is no pinned stack");
Youngsang Cho336007b2016-02-22 11:17:29 -0800129 closePipInternal(false);
Youngsang Cho23df6992016-01-26 17:51:33 -0800130 return;
131 }
132 } catch (RemoteException e) {
133 Log.e(TAG, "getStackInfo failed", e);
134 return;
135 }
136 for (int i = stackInfo.taskIds.length - 1; i >= 0; --i) {
137 if (stackInfo.taskIds[i] == mPipTaskId) {
138 // PIP task is still alive.
139 return;
140 }
141 }
142 // PIP task doesn't exist anymore in PINNED_STACK.
Youngsang Cho336007b2016-02-22 11:17:29 -0800143 closePipInternal(true);
Youngsang Chof1647922015-12-17 13:39:39 -0800144 }
145 }
146 };
Youngsang Cho6a00b702016-01-25 15:48:41 -0800147 private final Runnable mOnPinnedActivityRestartAttempt = new Runnable() {
148 @Override
149 public void run() {
150 movePipToFullscreen();
151 }
152 };
Wale Ogunwale480dca02016-02-06 13:58:29 -0800153 private final Runnable mOnPinnedStackAnimationEnded = new Runnable() {
154 @Override
155 public void run() {
Jaewan Kimc92a7d12016-02-15 17:33:25 -0800156 switch (mState) {
157 case STATE_PIP_OVERLAY:
158 showPipOverlay();
159 break;
160 case STATE_PIP_MENU:
161 showPipMenu();
162 break;
Wale Ogunwale480dca02016-02-06 13:58:29 -0800163 }
164 }
165 };
166
167 private final Runnable mResizePinnedStackRunnable = new Runnable() {
168 @Override
169 public void run() {
170 resizePinnedStack(mState);
171 }
172 };
Youngsang Chof1647922015-12-17 13:39:39 -0800173
Youngsang Choad8ceb02016-01-15 16:59:27 -0800174 private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
Youngsang Chof1647922015-12-17 13:39:39 -0800175 @Override
176 public void onReceive(Context context, Intent intent) {
Youngsang Choad8ceb02016-01-15 16:59:27 -0800177 String action = intent.getAction();
Jaewan Kimc552b042016-01-18 16:08:45 +0900178 if (Intent.ACTION_MEDIA_RESOURCE_GRANTED.equals(action)) {
Youngsang Choad8ceb02016-01-15 16:59:27 -0800179 String[] packageNames = intent.getStringArrayExtra(Intent.EXTRA_PACKAGES);
180 int resourceType = intent.getIntExtra(Intent.EXTRA_MEDIA_RESOURCE_TYPE,
181 INVALID_RESOURCE_TYPE);
182 if (mState != STATE_NO_PIP && packageNames != null && packageNames.length > 0
183 && resourceType == Intent.EXTRA_MEDIA_RESOURCE_TYPE_VIDEO_CODEC) {
184 handleMediaResourceGranted(packageNames);
185 }
Youngsang Chof1647922015-12-17 13:39:39 -0800186 }
Youngsang Choad8ceb02016-01-15 16:59:27 -0800187
Youngsang Chof1647922015-12-17 13:39:39 -0800188 }
189 };
Jaewan Kim62338192016-02-25 10:00:05 -0800190 private final MediaSessionManager.OnActiveSessionsChangedListener mActiveMediaSessionListener =
191 new MediaSessionManager.OnActiveSessionsChangedListener() {
192 @Override
193 public void onActiveSessionsChanged(List<MediaController> controllers) {
194 updateMediaController(controllers);
195 }
196 };
Youngsang Chof1647922015-12-17 13:39:39 -0800197
198 private PipManager() { }
199
200 /**
201 * Initializes {@link PipManager}.
202 */
203 public void initialize(Context context) {
204 if (mInitialized) {
205 return;
206 }
207 mInitialized = true;
208 mContext = context;
209 Resources res = context.getResources();
Jaewan Kimc92a7d12016-02-15 17:33:25 -0800210 mPipBounds = Rect.unflattenFromString(res.getString(
Youngsang Chof1647922015-12-17 13:39:39 -0800211 com.android.internal.R.string.config_defaultPictureInPictureBounds));
Jaewan Kimc92a7d12016-02-15 17:33:25 -0800212 mMenuModePipBounds = Rect.unflattenFromString(res.getString(
Youngsang Chof1647922015-12-17 13:39:39 -0800213 com.android.internal.R.string.config_centeredPictureInPictureBounds));
Jaewan Kimc92a7d12016-02-15 17:33:25 -0800214 mRecentsPipBounds = Rect.unflattenFromString(res.getString(
215 com.android.internal.R.string.config_pictureInPictureBoundsInRecents));
216 float scaleBy = (SCALE_FACTOR - 1.0f) / 2;
217 mRecentsFocusedPipBounds = new Rect(
218 (int) (mRecentsPipBounds.left - scaleBy * mRecentsPipBounds.width()),
219 (int) (mRecentsPipBounds.top - scaleBy * mRecentsPipBounds.height()),
220 (int) (mRecentsPipBounds.right + scaleBy * mRecentsPipBounds.width()),
221 (int) (mRecentsPipBounds.bottom + scaleBy * mRecentsPipBounds.height()));
Youngsang Chof1647922015-12-17 13:39:39 -0800222
223 mActivityManager = ActivityManagerNative.getDefault();
224 TaskStackListener taskStackListener = new TaskStackListener();
225 IActivityManager iam = ActivityManagerNative.getDefault();
226 try {
227 iam.registerTaskStackListener(taskStackListener);
228 } catch (RemoteException e) {
229 Log.e(TAG, "registerTaskStackListener failed", e);
230 }
231 IntentFilter intentFilter = new IntentFilter();
Youngsang Choad8ceb02016-01-15 16:59:27 -0800232 intentFilter.addAction(Intent.ACTION_MEDIA_RESOURCE_GRANTED);
233 mContext.registerReceiver(mBroadcastReceiver, intentFilter);
Jaewan Kim977dcdc2016-01-20 19:21:08 +0900234 mOnboardingShown = Prefs.getBoolean(
235 mContext, TV_PICTURE_IN_PICTURE_ONBOARDING_SHOWN, false);
Jaewan Kim62338192016-02-25 10:00:05 -0800236
237 mMediaSessionManager =
238 (MediaSessionManager) mContext.getSystemService(Context.MEDIA_SESSION_SERVICE);
Youngsang Chof1647922015-12-17 13:39:39 -0800239 }
240
Jaewan Kimc552b042016-01-18 16:08:45 +0900241 /**
242 * Request PIP.
243 * It could either start PIP if there's none, and show PIP menu otherwise.
244 */
245 public void requestTvPictureInPicture() {
246 if (DEBUG) Log.d(TAG, "requestTvPictureInPicture()");
Jaewan Kimc92a7d12016-02-15 17:33:25 -0800247 if (!isPipShown()) {
Jaewan Kimc552b042016-01-18 16:08:45 +0900248 startPip();
249 } else if (mState == STATE_PIP_OVERLAY) {
Wale Ogunwale480dca02016-02-06 13:58:29 -0800250 resizePinnedStack(STATE_PIP_MENU);
Jaewan Kimc552b042016-01-18 16:08:45 +0900251 }
252 }
253
Youngsang Chof1647922015-12-17 13:39:39 -0800254 private void startPip() {
255 try {
Jaewan Kimc92a7d12016-02-15 17:33:25 -0800256 mActivityManager.moveTopActivityToPinnedStack(FULLSCREEN_WORKSPACE_STACK_ID, mPipBounds);
Youngsang Chof1647922015-12-17 13:39:39 -0800257 } catch (RemoteException|IllegalArgumentException e) {
258 Log.e(TAG, "moveTopActivityToPinnedStack failed", e);
259 }
Youngsang Chof1647922015-12-17 13:39:39 -0800260 }
261
262 /**
Jaewan Kim977dcdc2016-01-20 19:21:08 +0900263 * Closes PIP (PIPed activity and PIP system UI).
Youngsang Chof1647922015-12-17 13:39:39 -0800264 */
265 public void closePip() {
Youngsang Cho336007b2016-02-22 11:17:29 -0800266 closePipInternal(true);
Youngsang Cho23df6992016-01-26 17:51:33 -0800267 }
268
Youngsang Cho336007b2016-02-22 11:17:29 -0800269 private void closePipInternal(boolean removePipStack) {
Youngsang Chof1647922015-12-17 13:39:39 -0800270 mState = STATE_NO_PIP;
Youngsang Choad8ceb02016-01-15 16:59:27 -0800271 mPipTaskId = TASK_ID_NO_PIP;
Jaewan Kim62338192016-02-25 10:00:05 -0800272 mPipMediaController = null;
273 mMediaSessionManager.removeOnActiveSessionsChangedListener(mActiveMediaSessionListener);
Youngsang Cho23df6992016-01-26 17:51:33 -0800274 if (removePipStack) {
275 try {
276 mActivityManager.removeStack(PINNED_STACK_ID);
277 } catch (RemoteException e) {
278 Log.e(TAG, "removeStack failed", e);
279 }
Youngsang Chof1647922015-12-17 13:39:39 -0800280 }
Jaewan Kimc92a7d12016-02-15 17:33:25 -0800281 for (int i = mListeners.size() - 1; i >= 0; --i) {
282 mListeners.get(i).onPipActivityClosed();
283 }
Youngsang Chof1647922015-12-17 13:39:39 -0800284 }
285
286 /**
Jaewan Kim977dcdc2016-01-20 19:21:08 +0900287 * Moves the PIPed activity to the fullscreen and closes PIP system UI.
Youngsang Chof1647922015-12-17 13:39:39 -0800288 */
289 public void movePipToFullscreen() {
290 mState = STATE_NO_PIP;
Youngsang Choad8ceb02016-01-15 16:59:27 -0800291 mPipTaskId = TASK_ID_NO_PIP;
Youngsang Chof1647922015-12-17 13:39:39 -0800292 for (int i = mListeners.size() - 1; i >= 0; --i) {
293 mListeners.get(i).onMoveToFullscreen();
294 }
Wale Ogunwale480dca02016-02-06 13:58:29 -0800295 resizePinnedStack(mState);
Youngsang Chof1647922015-12-17 13:39:39 -0800296 }
297
298 /**
299 * Shows PIP overlay UI by launching {@link PipOverlayActivity}. It also locates the pinned
300 * stack to the default PIP bound {@link com.android.internal.R.string
301 * .config_defaultPictureInPictureBounds}.
302 */
Wale Ogunwale480dca02016-02-06 13:58:29 -0800303 private void showPipOverlay() {
Youngsang Chof1647922015-12-17 13:39:39 -0800304 if (DEBUG) Log.d(TAG, "showPipOverlay()");
Youngsang Chof1647922015-12-17 13:39:39 -0800305 mState = STATE_PIP_OVERLAY;
Youngsang Choefbbd492016-01-21 14:30:31 -0800306 Intent intent = new Intent(mContext, PipOverlayActivity.class);
307 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
308 final ActivityOptions options = ActivityOptions.makeBasic();
309 options.setLaunchStackId(PINNED_STACK_ID);
Youngsang Choefbbd492016-01-21 14:30:31 -0800310 mContext.startActivity(intent, options.toBundle());
Youngsang Chof1647922015-12-17 13:39:39 -0800311 }
312
313 /**
Wale Ogunwale480dca02016-02-06 13:58:29 -0800314 * Suspends resizing operation on the Pip until {@link #resumePipResizing} is called
315 * @param reason The reason for suspending resizing operations on the Pip.
316 */
317 public void suspendPipResizing(int reason) {
318 if (DEBUG) Log.d(TAG,
319 "suspendPipResizing() reason=" + reason + " callers=" + Debug.getCallers(2));
320 mSuspendPipResizingReason |= reason;
321 }
322
323 /**
324 * Resumes resizing operation on the Pip that was previously suspended.
325 * @param reason The reason resizing operations on the Pip was suspended.
326 */
327 public void resumePipResizing(int reason) {
328 if ((mSuspendPipResizingReason & reason) == 0) {
329 return;
330 }
331 if (DEBUG) Log.d(TAG,
332 "resumePipResizing() reason=" + reason + " callers=" + Debug.getCallers(2));
333 mSuspendPipResizingReason &= ~reason;
334 mHandler.post(mResizePinnedStackRunnable);
335 }
336
337 /**
338 * Resize the Pip to the appropriate size for the input state.
339 * @param state In Pip state also used to determine the new size for the Pip.
340 */
341 public void resizePinnedStack(int state) {
342 if (DEBUG) Log.d(TAG, "resizePinnedStack() state=" + state);
343 mState = state;
Wale Ogunwale480dca02016-02-06 13:58:29 -0800344 for (int i = mListeners.size() - 1; i >= 0; --i) {
345 mListeners.get(i).onPipResizeAboutToStart();
346 }
Wale Ogunwale480dca02016-02-06 13:58:29 -0800347 if (mSuspendPipResizingReason != 0) {
348 if (DEBUG) Log.d(TAG,
349 "resizePinnedStack() deferring mSuspendPipResizingReason=" +
350 mSuspendPipResizingReason);
351 return;
352 }
Jaewan Kimc92a7d12016-02-15 17:33:25 -0800353 switch (mState) {
354 case STATE_NO_PIP:
355 mCurrentPipBounds = null;
356 break;
357 case STATE_PIP_MENU:
358 mCurrentPipBounds = mMenuModePipBounds;
359 break;
360 case STATE_PIP_OVERLAY:
361 if (mIsRecentsShown) {
362 if (mIsPipFocusedInRecent) {
363 mCurrentPipBounds = mRecentsFocusedPipBounds;
364 } else {
365 mCurrentPipBounds = mRecentsPipBounds;
366 }
367 } else {
368 mCurrentPipBounds = mPipBounds;
369 }
370 break;
371 default:
372 mCurrentPipBounds = mPipBounds;
373 break;
374 }
Wale Ogunwale480dca02016-02-06 13:58:29 -0800375 try {
Jaewan Kimc92a7d12016-02-15 17:33:25 -0800376 mActivityManager.resizeStack(PINNED_STACK_ID, mCurrentPipBounds, true, true, true);
Wale Ogunwale480dca02016-02-06 13:58:29 -0800377 } catch (RemoteException e) {
378 Log.e(TAG, "showPipMenu failed", e);
379 }
380 }
381
382 /**
Jaewan Kimc92a7d12016-02-15 17:33:25 -0800383 * Returns the current PIP bound for activities to sync their UI with PIP.
384 */
385 public Rect getPipBounds() {
386 return mCurrentPipBounds;
387 }
388
389 /**
390 * Called when Recents is started.
391 * PIPed activity will be resized accordingly and overlay will show available buttons.
392 */
393 public void onRecentsStarted() {
394 mIsRecentsShown = true;
395 mIsPipFocusedInRecent = false;
396 if (mState == STATE_NO_PIP) {
397 return;
398 }
399 resizePinnedStack(STATE_PIP_OVERLAY);
400 }
401
402 /**
403 * Called when Recents is stopped.
404 * PIPed activity will be resized accordingly and overlay will hide available buttons.
405 */
406 public void onRecentsStopped() {
407 mIsRecentsShown = false;
408 mIsPipFocusedInRecent = false;
409 if (mState == STATE_NO_PIP) {
410 return;
411 }
412 resizePinnedStack(STATE_PIP_OVERLAY);
413 }
414
415 /**
416 * Returns {@code true} if recents is shown.
417 */
418 boolean isRecentsShown() {
419 return mIsRecentsShown;
420 }
421
422 /**
423 * Called when the PIP view in {@link com.android.systemui.recents.tv.RecentsTvActivity}
424 * is focused.
425 * This only resizes pinned stack so it looks like it's in Recents.
426 * This should be called only by {@link com.android.systemui.recents.tv.RecentsTvActivity}.
427 */
428 public void onPipViewFocusChangedInRecents(boolean hasFocus) {
429 mIsPipFocusedInRecent = hasFocus;
430 if (mState != STATE_PIP_OVERLAY) {
431 Log.w(TAG, "There is no pinned stack to handle focus change.");
432 return;
433 }
434 resizePinnedStack(STATE_PIP_OVERLAY);
435 }
436
437 /**
Youngsang Chof1647922015-12-17 13:39:39 -0800438 * Shows PIP menu UI by launching {@link PipMenuActivity}. It also locates the pinned
439 * stack to the centered PIP bound {@link com.android.internal.R.string
440 * .config_centeredPictureInPictureBounds}.
441 */
Wale Ogunwale480dca02016-02-06 13:58:29 -0800442 private void showPipMenu() {
Youngsang Chof1647922015-12-17 13:39:39 -0800443 if (DEBUG) Log.d(TAG, "showPipMenu()");
Youngsang Chof1647922015-12-17 13:39:39 -0800444 mState = STATE_PIP_MENU;
445 for (int i = mListeners.size() - 1; i >= 0; --i) {
446 mListeners.get(i).onShowPipMenu();
447 }
448 Intent intent = new Intent(mContext, PipMenuActivity.class);
449 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Jaewan Kim1a9dc562016-02-17 13:41:51 -0800450 mContext.startActivity(intent);
Youngsang Chof1647922015-12-17 13:39:39 -0800451 }
452
Youngsang Chof1647922015-12-17 13:39:39 -0800453 public void addListener(Listener listener) {
454 mListeners.add(listener);
455 }
456
Youngsang Chof1647922015-12-17 13:39:39 -0800457 public void removeListener(Listener listener) {
458 mListeners.remove(listener);
459 }
460
Jaewan Kim977dcdc2016-01-20 19:21:08 +0900461 private void launchPipOnboardingActivityIfNeeded() {
462 if (DEBUG_FORCE_ONBOARDING || !mOnboardingShown) {
463 mOnboardingShown = true;
464 Prefs.putBoolean(mContext, TV_PICTURE_IN_PICTURE_ONBOARDING_SHOWN, true);
465
466 Intent intent = new Intent(mContext, PipOnboardingActivity.class);
467 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
468 mContext.startActivity(intent);
469 }
470 }
471
Jaewan Kimc92a7d12016-02-15 17:33:25 -0800472 /**
473 * Returns {@code true} if PIP is shown.
474 */
475 public boolean isPipShown() {
476 return hasPipTasks();
477 }
478
Youngsang Chof1647922015-12-17 13:39:39 -0800479 private boolean hasPipTasks() {
480 try {
481 StackInfo stackInfo = mActivityManager.getStackInfo(PINNED_STACK_ID);
482 return stackInfo != null;
483 } catch (RemoteException e) {
484 Log.e(TAG, "getStackInfo failed", e);
485 return false;
486 }
487 }
488
Youngsang Choad8ceb02016-01-15 16:59:27 -0800489 private void handleMediaResourceGranted(String[] packageNames) {
490 StackInfo fullscreenStack = null;
491 try {
492 fullscreenStack = mActivityManager.getStackInfo(FULLSCREEN_WORKSPACE_STACK_ID);
493 } catch (RemoteException e) {
494 Log.e(TAG, "getStackInfo failed", e);
495 }
496 if (fullscreenStack == null) {
497 return;
498 }
499 int fullscreenTopTaskId = fullscreenStack.taskIds[fullscreenStack.taskIds.length - 1];
500 List<RunningTaskInfo> tasks = null;
501 try {
502 tasks = mActivityManager.getTasks(MAX_RUNNING_TASKS_COUNT, 0);
503 } catch (RemoteException e) {
504 Log.e(TAG, "getTasks failed", e);
505 }
506 if (tasks == null) {
507 return;
508 }
509 boolean wasGrantedInFullscreen = false;
510 boolean wasGrantedInPip = false;
511 for (int i = tasks.size() - 1; i >= 0; --i) {
512 RunningTaskInfo task = tasks.get(i);
513 for (int j = packageNames.length - 1; j >= 0; --j) {
514 if (task.topActivity.getPackageName().equals(packageNames[j])) {
515 if (task.id == fullscreenTopTaskId) {
516 wasGrantedInFullscreen = true;
517 } else if (task.id == mPipTaskId) {
518 wasGrantedInPip= true;
519 }
520 }
521 }
522 }
523 if (wasGrantedInFullscreen && !wasGrantedInPip) {
524 closePip();
525 }
526 }
527
Jaewan Kim62338192016-02-25 10:00:05 -0800528 private void updateMediaController(List<MediaController> controllers) {
529 MediaController mediaController = null;
530 if (controllers != null && mState != STATE_NO_PIP && mPipComponentName != null) {
531 for (int i = controllers.size() - 1; i >= 0; i--) {
532 MediaController controller = controllers.get(i);
533 // We assumes that an app with PIPable activity
534 // keeps the single instance of media controller especially when PIP is on.
535 if (controller.getPackageName().equals(mPipComponentName.getPackageName())) {
536 mediaController = controller;
537 break;
538 }
539 }
540 }
541 if (mPipMediaController != mediaController) {
542 mPipMediaController = mediaController;
543 for (int i = mListeners.size() - 1; i >= 0; i--) {
544 mListeners.get(i).onMediaControllerChanged();
545 }
546 }
547 }
548
549 /**
550 * Gets the {@link android.media.session.MediaController} for the PIPed activity.
551 */
552 MediaController getMediaController() {
553 return mPipMediaController;
554 }
555
Youngsang Chof1647922015-12-17 13:39:39 -0800556 private class TaskStackListener extends ITaskStackListener.Stub {
557 @Override
558 public void onTaskStackChanged() throws RemoteException {
559 // Post the message back to the UI thread.
560 mHandler.post(mOnTaskStackChanged);
561 }
562
563 @Override
564 public void onActivityPinned() throws RemoteException {
565 // Post the message back to the UI thread.
Wale Ogunwale480dca02016-02-06 13:58:29 -0800566 if (DEBUG) Log.d(TAG, "onActivityPinned()");
Youngsang Chof1647922015-12-17 13:39:39 -0800567 mHandler.post(mOnActivityPinnedRunnable);
568 }
Wale Ogunwalecc25a8a2016-01-23 14:31:37 -0800569
570 @Override
571 public void onPinnedActivityRestartAttempt() {
Youngsang Cho6a00b702016-01-25 15:48:41 -0800572 // Post the message back to the UI thread.
Wale Ogunwale480dca02016-02-06 13:58:29 -0800573 if (DEBUG) Log.d(TAG, "onPinnedActivityRestartAttempt()");
Youngsang Cho6a00b702016-01-25 15:48:41 -0800574 mHandler.post(mOnPinnedActivityRestartAttempt);
Wale Ogunwalecc25a8a2016-01-23 14:31:37 -0800575 }
Wale Ogunwale480dca02016-02-06 13:58:29 -0800576
577 @Override
578 public void onPinnedStackAnimationEnded() {
579 if (DEBUG) Log.d(TAG, "onPinnedStackAnimationEnded()");
580 mHandler.post(mOnPinnedStackAnimationEnded);
581 }
Youngsang Chof1647922015-12-17 13:39:39 -0800582 }
583
584 /**
585 * A listener interface to receive notification on changes in PIP.
586 */
587 public interface Listener {
Wale Ogunwale480dca02016-02-06 13:58:29 -0800588 /** Invoked when a PIPed activity is closed. */
Youngsang Chof1647922015-12-17 13:39:39 -0800589 void onPipActivityClosed();
Wale Ogunwale480dca02016-02-06 13:58:29 -0800590 /** Invoked when the PIP menu gets shown. */
Youngsang Chof1647922015-12-17 13:39:39 -0800591 void onShowPipMenu();
Wale Ogunwale480dca02016-02-06 13:58:29 -0800592 /** Invoked when the PIPed activity is returned back to the fullscreen. */
Youngsang Chof1647922015-12-17 13:39:39 -0800593 void onMoveToFullscreen();
Wale Ogunwale480dca02016-02-06 13:58:29 -0800594 /** Invoked when we are above to start resizing the Pip. */
595 void onPipResizeAboutToStart();
Jaewan Kim62338192016-02-25 10:00:05 -0800596 /** Invoked when the MediaController on PIPed activity is changed. */
597 void onMediaControllerChanged();
Youngsang Chof1647922015-12-17 13:39:39 -0800598 }
599
600 /**
601 * Gets an instance of {@link PipManager}.
602 */
603 public static PipManager getInstance() {
604 if (sPipManager == null) {
605 sPipManager = new PipManager();
606 }
607 return sPipManager;
608 }
609}