blob: b5c1f5739e657da90be28dc3820614b84d83b00f [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
Jaewan Kima0d4d252016-03-31 13:37:10 +090065 /**
66 * State when there's no PIP.
67 */
Wale Ogunwale480dca02016-02-06 13:58:29 -080068 public static final int STATE_NO_PIP = 0;
Jaewan Kima0d4d252016-03-31 13:37:10 +090069 /**
70 * State when PIP is shown with an overlay message on top of it.
71 * This is used as default PIP state.
72 */
Wale Ogunwale480dca02016-02-06 13:58:29 -080073 public static final int STATE_PIP_OVERLAY = 1;
Jaewan Kima0d4d252016-03-31 13:37:10 +090074 /**
75 * State when PIP menu dialog is shown.
76 */
Wale Ogunwale480dca02016-02-06 13:58:29 -080077 public static final int STATE_PIP_MENU = 2;
Jaewan Kima0d4d252016-03-31 13:37:10 +090078 /**
79 * State when PIP is shown in Recents.
80 */
81 public static final int STATE_PIP_RECENTS = 3;
82 /**
83 * State when PIP is shown in Recents and it's focused to allow an user to control.
84 */
85 public static final int STATE_PIP_RECENTS_FOCUSED = 4;
Youngsang Chof1647922015-12-17 13:39:39 -080086
Youngsang Choad8ceb02016-01-15 16:59:27 -080087 private static final int TASK_ID_NO_PIP = -1;
88 private static final int INVALID_RESOURCE_TYPE = -1;
89
Wale Ogunwale480dca02016-02-06 13:58:29 -080090 public static final int SUSPEND_PIP_RESIZE_REASON_WAITING_FOR_MENU_ACTIVITY_FINISH = 0x1;
91 public static final int SUSPEND_PIP_RESIZE_REASON_WAITING_FOR_OVERLAY_ACTIVITY_FINISH = 0x2;
Jaewan Kimf40fcdc2016-03-04 17:58:22 +090092
Jaewan Kim8f584b82016-03-22 22:16:59 +090093 /**
94 * PIPed activity is playing a media and it can be paused.
95 */
96 static final int PLAYBACK_STATE_PLAYING = 0;
97 /**
98 * PIPed activity has a paused media and it can be played.
99 */
100 static final int PLAYBACK_STATE_PAUSED = 1;
101 /**
102 * Users are unable to control PIPed activity's media playback.
103 */
104 static final int PLAYBACK_STATE_UNAVAILABLE = 2;
105
Jaewan Kimf40fcdc2016-03-04 17:58:22 +0900106 private static final int CLOSE_PIP_WHEN_MEDIA_SESSION_GONE_TIMEOUT_MS = 3000;
107
Wale Ogunwale480dca02016-02-06 13:58:29 -0800108 private int mSuspendPipResizingReason;
109
Youngsang Chof1647922015-12-17 13:39:39 -0800110 private Context mContext;
Jaewan Kima0d4d252016-03-31 13:37:10 +0900111 private PipRecentsOverlayManager mPipRecentsOverlayManager;
Youngsang Chof1647922015-12-17 13:39:39 -0800112 private IActivityManager mActivityManager;
Jaewan Kim62338192016-02-25 10:00:05 -0800113 private MediaSessionManager mMediaSessionManager;
Youngsang Chof1647922015-12-17 13:39:39 -0800114 private int mState = STATE_NO_PIP;
115 private final Handler mHandler = new Handler();
116 private List<Listener> mListeners = new ArrayList<>();
Jaewan Kima0d4d252016-03-31 13:37:10 +0900117 private List<MediaListener> mMediaListeners = new ArrayList<>();
Jaewan Kimc92a7d12016-02-15 17:33:25 -0800118 private Rect mCurrentPipBounds;
119 private Rect mPipBounds;
120 private Rect mMenuModePipBounds;
121 private Rect mRecentsPipBounds;
122 private Rect mRecentsFocusedPipBounds;
Jaewan Kim8f584b82016-03-22 22:16:59 +0900123 private int mRecentsFocusChangedAnimationDurationMs;
Youngsang Chof1647922015-12-17 13:39:39 -0800124 private boolean mInitialized;
Youngsang Choad8ceb02016-01-15 16:59:27 -0800125 private int mPipTaskId = TASK_ID_NO_PIP;
Jaewan Kim62338192016-02-25 10:00:05 -0800126 private ComponentName mPipComponentName;
127 private MediaController mPipMediaController;
Jaewan Kim977dcdc2016-01-20 19:21:08 +0900128 private boolean mOnboardingShown;
Youngsang Choad8ceb02016-01-15 16:59:27 -0800129
Wale Ogunwale480dca02016-02-06 13:58:29 -0800130 private final Runnable mResizePinnedStackRunnable = new Runnable() {
131 @Override
132 public void run() {
133 resizePinnedStack(mState);
134 }
135 };
Jaewan Kimf40fcdc2016-03-04 17:58:22 +0900136 private final Runnable mClosePipRunnable = new Runnable() {
137 @Override
138 public void run() {
139 closePip();
140 }
141 };
Youngsang Chof1647922015-12-17 13:39:39 -0800142
Youngsang Choad8ceb02016-01-15 16:59:27 -0800143 private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
Youngsang Chof1647922015-12-17 13:39:39 -0800144 @Override
145 public void onReceive(Context context, Intent intent) {
Youngsang Choad8ceb02016-01-15 16:59:27 -0800146 String action = intent.getAction();
Jaewan Kimc552b042016-01-18 16:08:45 +0900147 if (Intent.ACTION_MEDIA_RESOURCE_GRANTED.equals(action)) {
Youngsang Choad8ceb02016-01-15 16:59:27 -0800148 String[] packageNames = intent.getStringArrayExtra(Intent.EXTRA_PACKAGES);
149 int resourceType = intent.getIntExtra(Intent.EXTRA_MEDIA_RESOURCE_TYPE,
150 INVALID_RESOURCE_TYPE);
151 if (mState != STATE_NO_PIP && packageNames != null && packageNames.length > 0
152 && resourceType == Intent.EXTRA_MEDIA_RESOURCE_TYPE_VIDEO_CODEC) {
153 handleMediaResourceGranted(packageNames);
154 }
Youngsang Chof1647922015-12-17 13:39:39 -0800155 }
Youngsang Choad8ceb02016-01-15 16:59:27 -0800156
Youngsang Chof1647922015-12-17 13:39:39 -0800157 }
158 };
Jaewan Kim62338192016-02-25 10:00:05 -0800159 private final MediaSessionManager.OnActiveSessionsChangedListener mActiveMediaSessionListener =
160 new MediaSessionManager.OnActiveSessionsChangedListener() {
161 @Override
162 public void onActiveSessionsChanged(List<MediaController> controllers) {
163 updateMediaController(controllers);
164 }
165 };
Youngsang Chof1647922015-12-17 13:39:39 -0800166
167 private PipManager() { }
168
169 /**
170 * Initializes {@link PipManager}.
171 */
172 public void initialize(Context context) {
173 if (mInitialized) {
174 return;
175 }
176 mInitialized = true;
177 mContext = context;
178 Resources res = context.getResources();
Jaewan Kimc92a7d12016-02-15 17:33:25 -0800179 mPipBounds = Rect.unflattenFromString(res.getString(
Youngsang Chof1647922015-12-17 13:39:39 -0800180 com.android.internal.R.string.config_defaultPictureInPictureBounds));
Jaewan Kimc92a7d12016-02-15 17:33:25 -0800181 mMenuModePipBounds = Rect.unflattenFromString(res.getString(
Jaewan Kim8f584b82016-03-22 22:16:59 +0900182 R.string.pip_menu_bounds));
Jaewan Kimc92a7d12016-02-15 17:33:25 -0800183 mRecentsPipBounds = Rect.unflattenFromString(res.getString(
Jaewan Kim8f584b82016-03-22 22:16:59 +0900184 R.string.pip_recents_bounds));
185 mRecentsFocusedPipBounds = Rect.unflattenFromString(res.getString(
186 R.string.pip_recents_focused_bounds));
187 mRecentsFocusChangedAnimationDurationMs = res.getInteger(
188 R.integer.recents_tv_pip_focus_anim_duration);
Youngsang Chof1647922015-12-17 13:39:39 -0800189
190 mActivityManager = ActivityManagerNative.getDefault();
Jaewan Kim938a50b2016-03-14 17:35:43 +0900191 SystemServicesProxy.getInstance(context).registerTaskStackListener(mTaskStackListener);
Youngsang Chof1647922015-12-17 13:39:39 -0800192 IntentFilter intentFilter = new IntentFilter();
Youngsang Choad8ceb02016-01-15 16:59:27 -0800193 intentFilter.addAction(Intent.ACTION_MEDIA_RESOURCE_GRANTED);
194 mContext.registerReceiver(mBroadcastReceiver, intentFilter);
Jaewan Kim977dcdc2016-01-20 19:21:08 +0900195 mOnboardingShown = Prefs.getBoolean(
196 mContext, TV_PICTURE_IN_PICTURE_ONBOARDING_SHOWN, false);
Jaewan Kim62338192016-02-25 10:00:05 -0800197
Jaewan Kima0d4d252016-03-31 13:37:10 +0900198 mPipRecentsOverlayManager = new PipRecentsOverlayManager(context);
Jaewan Kim62338192016-02-25 10:00:05 -0800199 mMediaSessionManager =
200 (MediaSessionManager) mContext.getSystemService(Context.MEDIA_SESSION_SERVICE);
Youngsang Chof1647922015-12-17 13:39:39 -0800201 }
202
Jaewan Kimc552b042016-01-18 16:08:45 +0900203 /**
204 * Request PIP.
205 * It could either start PIP if there's none, and show PIP menu otherwise.
206 */
207 public void requestTvPictureInPicture() {
208 if (DEBUG) Log.d(TAG, "requestTvPictureInPicture()");
Jaewan Kimc92a7d12016-02-15 17:33:25 -0800209 if (!isPipShown()) {
Jaewan Kimc552b042016-01-18 16:08:45 +0900210 startPip();
211 } else if (mState == STATE_PIP_OVERLAY) {
Wale Ogunwale480dca02016-02-06 13:58:29 -0800212 resizePinnedStack(STATE_PIP_MENU);
Jaewan Kimc552b042016-01-18 16:08:45 +0900213 }
214 }
215
Youngsang Chof1647922015-12-17 13:39:39 -0800216 private void startPip() {
217 try {
Jaewan Kimc92a7d12016-02-15 17:33:25 -0800218 mActivityManager.moveTopActivityToPinnedStack(FULLSCREEN_WORKSPACE_STACK_ID, mPipBounds);
Youngsang Chof1647922015-12-17 13:39:39 -0800219 } catch (RemoteException|IllegalArgumentException e) {
220 Log.e(TAG, "moveTopActivityToPinnedStack failed", e);
221 }
Youngsang Chof1647922015-12-17 13:39:39 -0800222 }
223
224 /**
Jaewan Kim977dcdc2016-01-20 19:21:08 +0900225 * Closes PIP (PIPed activity and PIP system UI).
Youngsang Chof1647922015-12-17 13:39:39 -0800226 */
227 public void closePip() {
Youngsang Cho336007b2016-02-22 11:17:29 -0800228 closePipInternal(true);
Youngsang Cho23df6992016-01-26 17:51:33 -0800229 }
230
Youngsang Cho336007b2016-02-22 11:17:29 -0800231 private void closePipInternal(boolean removePipStack) {
Youngsang Chof1647922015-12-17 13:39:39 -0800232 mState = STATE_NO_PIP;
Youngsang Choad8ceb02016-01-15 16:59:27 -0800233 mPipTaskId = TASK_ID_NO_PIP;
Jaewan Kim62338192016-02-25 10:00:05 -0800234 mPipMediaController = null;
235 mMediaSessionManager.removeOnActiveSessionsChangedListener(mActiveMediaSessionListener);
Youngsang Cho23df6992016-01-26 17:51:33 -0800236 if (removePipStack) {
237 try {
238 mActivityManager.removeStack(PINNED_STACK_ID);
239 } catch (RemoteException e) {
240 Log.e(TAG, "removeStack failed", e);
241 }
Youngsang Chof1647922015-12-17 13:39:39 -0800242 }
Jaewan Kimc92a7d12016-02-15 17:33:25 -0800243 for (int i = mListeners.size() - 1; i >= 0; --i) {
244 mListeners.get(i).onPipActivityClosed();
245 }
Jaewan Kimf40fcdc2016-03-04 17:58:22 +0900246 mHandler.removeCallbacks(mClosePipRunnable);
Youngsang Chof1647922015-12-17 13:39:39 -0800247 }
248
249 /**
Jaewan Kim977dcdc2016-01-20 19:21:08 +0900250 * Moves the PIPed activity to the fullscreen and closes PIP system UI.
Youngsang Chof1647922015-12-17 13:39:39 -0800251 */
Jaewan Kima0d4d252016-03-31 13:37:10 +0900252 void movePipToFullscreen() {
Youngsang Chof1647922015-12-17 13:39:39 -0800253 mState = STATE_NO_PIP;
Youngsang Choad8ceb02016-01-15 16:59:27 -0800254 mPipTaskId = TASK_ID_NO_PIP;
Youngsang Chof1647922015-12-17 13:39:39 -0800255 for (int i = mListeners.size() - 1; i >= 0; --i) {
256 mListeners.get(i).onMoveToFullscreen();
257 }
Wale Ogunwale480dca02016-02-06 13:58:29 -0800258 resizePinnedStack(mState);
Youngsang Chof1647922015-12-17 13:39:39 -0800259 }
260
261 /**
262 * Shows PIP overlay UI by launching {@link PipOverlayActivity}. It also locates the pinned
263 * stack to the default PIP bound {@link com.android.internal.R.string
264 * .config_defaultPictureInPictureBounds}.
265 */
Wale Ogunwale480dca02016-02-06 13:58:29 -0800266 private void showPipOverlay() {
Youngsang Chof1647922015-12-17 13:39:39 -0800267 if (DEBUG) Log.d(TAG, "showPipOverlay()");
Jaewan Kima0d4d252016-03-31 13:37:10 +0900268 Intent intent = new Intent(mContext, PipOverlayActivity.class);
269 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
270 final ActivityOptions options = ActivityOptions.makeBasic();
271 options.setLaunchStackId(PINNED_STACK_ID);
272 mContext.startActivity(intent, options.toBundle());
Youngsang Chof1647922015-12-17 13:39:39 -0800273 }
274
275 /**
Wale Ogunwale480dca02016-02-06 13:58:29 -0800276 * Suspends resizing operation on the Pip until {@link #resumePipResizing} is called
277 * @param reason The reason for suspending resizing operations on the Pip.
278 */
279 public void suspendPipResizing(int reason) {
280 if (DEBUG) Log.d(TAG,
281 "suspendPipResizing() reason=" + reason + " callers=" + Debug.getCallers(2));
282 mSuspendPipResizingReason |= reason;
283 }
284
285 /**
286 * Resumes resizing operation on the Pip that was previously suspended.
287 * @param reason The reason resizing operations on the Pip was suspended.
288 */
289 public void resumePipResizing(int reason) {
290 if ((mSuspendPipResizingReason & reason) == 0) {
291 return;
292 }
293 if (DEBUG) Log.d(TAG,
294 "resumePipResizing() reason=" + reason + " callers=" + Debug.getCallers(2));
295 mSuspendPipResizingReason &= ~reason;
296 mHandler.post(mResizePinnedStackRunnable);
297 }
298
299 /**
300 * Resize the Pip to the appropriate size for the input state.
301 * @param state In Pip state also used to determine the new size for the Pip.
302 */
Jaewan Kima0d4d252016-03-31 13:37:10 +0900303 void resizePinnedStack(int state) {
Wale Ogunwale480dca02016-02-06 13:58:29 -0800304 if (DEBUG) Log.d(TAG, "resizePinnedStack() state=" + state);
Jaewan Kima0d4d252016-03-31 13:37:10 +0900305 boolean wasRecentsShown =
306 (mState == STATE_PIP_RECENTS || mState == STATE_PIP_RECENTS_FOCUSED);
Wale Ogunwale480dca02016-02-06 13:58:29 -0800307 mState = state;
Wale Ogunwale480dca02016-02-06 13:58:29 -0800308 for (int i = mListeners.size() - 1; i >= 0; --i) {
309 mListeners.get(i).onPipResizeAboutToStart();
310 }
Wale Ogunwale480dca02016-02-06 13:58:29 -0800311 if (mSuspendPipResizingReason != 0) {
312 if (DEBUG) Log.d(TAG,
313 "resizePinnedStack() deferring mSuspendPipResizingReason=" +
314 mSuspendPipResizingReason);
315 return;
316 }
Jaewan Kimc92a7d12016-02-15 17:33:25 -0800317 switch (mState) {
318 case STATE_NO_PIP:
319 mCurrentPipBounds = null;
320 break;
321 case STATE_PIP_MENU:
322 mCurrentPipBounds = mMenuModePipBounds;
323 break;
324 case STATE_PIP_OVERLAY:
Jaewan Kima0d4d252016-03-31 13:37:10 +0900325 mCurrentPipBounds = mPipBounds;
326 break;
327 case STATE_PIP_RECENTS:
328 mCurrentPipBounds = mRecentsPipBounds;
329 break;
330 case STATE_PIP_RECENTS_FOCUSED:
331 mCurrentPipBounds = mRecentsFocusedPipBounds;
Jaewan Kimc92a7d12016-02-15 17:33:25 -0800332 break;
333 default:
334 mCurrentPipBounds = mPipBounds;
335 break;
336 }
Wale Ogunwale480dca02016-02-06 13:58:29 -0800337 try {
Jaewan Kima0d4d252016-03-31 13:37:10 +0900338 int animationDurationMs = -1;
339 if (wasRecentsShown
340 && (mState == STATE_PIP_RECENTS || mState == STATE_PIP_RECENTS_FOCUSED)) {
341 animationDurationMs = mRecentsFocusChangedAnimationDurationMs;
342 }
Jaewan Kim8f584b82016-03-22 22:16:59 +0900343 mActivityManager.resizeStack(PINNED_STACK_ID, mCurrentPipBounds,
344 true, true, true, animationDurationMs);
Wale Ogunwale480dca02016-02-06 13:58:29 -0800345 } catch (RemoteException e) {
Jaewan Kim82ac50d2016-03-21 17:34:28 +0900346 Log.e(TAG, "resizeStack failed", e);
Wale Ogunwale480dca02016-02-06 13:58:29 -0800347 }
348 }
349
350 /**
Jaewan Kima0d4d252016-03-31 13:37:10 +0900351 * Returns the default PIP bound.
Jaewan Kimc92a7d12016-02-15 17:33:25 -0800352 */
353 public Rect getPipBounds() {
Jaewan Kima0d4d252016-03-31 13:37:10 +0900354 return mPipBounds;
Jaewan Kimc92a7d12016-02-15 17:33:25 -0800355 }
356
357 /**
Jaewan Kima0d4d252016-03-31 13:37:10 +0900358 * Returns the focused PIP bound while Recents is shown.
359 * This is used to place PIP controls in Recents.
Jaewan Kimc92a7d12016-02-15 17:33:25 -0800360 */
Jaewan Kima0d4d252016-03-31 13:37:10 +0900361 public Rect getRecentsFocusedPipBounds() {
362 return mRecentsFocusedPipBounds;
Jaewan Kim8f584b82016-03-22 22:16:59 +0900363 }
364
365 /**
Youngsang Chof1647922015-12-17 13:39:39 -0800366 * Shows PIP menu UI by launching {@link PipMenuActivity}. It also locates the pinned
Jaewan Kim8f584b82016-03-22 22:16:59 +0900367 * stack to the centered PIP bound {@link R.config_centeredPictureInPictureBounds}.
Youngsang Chof1647922015-12-17 13:39:39 -0800368 */
Wale Ogunwale480dca02016-02-06 13:58:29 -0800369 private void showPipMenu() {
Youngsang Chof1647922015-12-17 13:39:39 -0800370 if (DEBUG) Log.d(TAG, "showPipMenu()");
Jaewan Kima0d4d252016-03-31 13:37:10 +0900371 if (mPipRecentsOverlayManager.isRecentsShown()) {
372 if (DEBUG) Log.d(TAG, "Ignore showing PIP menu");
373 return;
374 }
Youngsang Chof1647922015-12-17 13:39:39 -0800375 mState = STATE_PIP_MENU;
376 for (int i = mListeners.size() - 1; i >= 0; --i) {
377 mListeners.get(i).onShowPipMenu();
378 }
379 Intent intent = new Intent(mContext, PipMenuActivity.class);
380 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Jaewan Kim1a9dc562016-02-17 13:41:51 -0800381 mContext.startActivity(intent);
Youngsang Chof1647922015-12-17 13:39:39 -0800382 }
383
Jaewan Kima0d4d252016-03-31 13:37:10 +0900384 /**
385 * Adds a {@link Listener} to PipManager.
386 */
Youngsang Chof1647922015-12-17 13:39:39 -0800387 public void addListener(Listener listener) {
388 mListeners.add(listener);
389 }
390
Jaewan Kima0d4d252016-03-31 13:37:10 +0900391 /**
392 * Removes a {@link Listener} from PipManager.
393 */
Youngsang Chof1647922015-12-17 13:39:39 -0800394 public void removeListener(Listener listener) {
395 mListeners.remove(listener);
396 }
397
Jaewan Kima0d4d252016-03-31 13:37:10 +0900398 /**
399 * Adds a {@link MediaListener} to PipManager.
400 */
401 public void addMediaListener(MediaListener listener) {
402 mMediaListeners.add(listener);
403 }
404
405 /**
406 * Removes a {@link MediaListener} from PipManager.
407 */
408 public void removeMediaListener(MediaListener listener) {
409 mMediaListeners.remove(listener);
410 }
411
Jaewan Kim977dcdc2016-01-20 19:21:08 +0900412 private void launchPipOnboardingActivityIfNeeded() {
413 if (DEBUG_FORCE_ONBOARDING || !mOnboardingShown) {
414 mOnboardingShown = true;
415 Prefs.putBoolean(mContext, TV_PICTURE_IN_PICTURE_ONBOARDING_SHOWN, true);
416
417 Intent intent = new Intent(mContext, PipOnboardingActivity.class);
418 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
419 mContext.startActivity(intent);
420 }
421 }
422
Jaewan Kimc92a7d12016-02-15 17:33:25 -0800423 /**
424 * Returns {@code true} if PIP is shown.
425 */
426 public boolean isPipShown() {
Jaewan Kim82ac50d2016-03-21 17:34:28 +0900427 return mState != STATE_NO_PIP;
Youngsang Chof1647922015-12-17 13:39:39 -0800428 }
429
Youngsang Choad8ceb02016-01-15 16:59:27 -0800430 private void handleMediaResourceGranted(String[] packageNames) {
431 StackInfo fullscreenStack = null;
432 try {
433 fullscreenStack = mActivityManager.getStackInfo(FULLSCREEN_WORKSPACE_STACK_ID);
434 } catch (RemoteException e) {
435 Log.e(TAG, "getStackInfo failed", e);
436 }
437 if (fullscreenStack == null) {
438 return;
439 }
440 int fullscreenTopTaskId = fullscreenStack.taskIds[fullscreenStack.taskIds.length - 1];
441 List<RunningTaskInfo> tasks = null;
442 try {
443 tasks = mActivityManager.getTasks(MAX_RUNNING_TASKS_COUNT, 0);
444 } catch (RemoteException e) {
445 Log.e(TAG, "getTasks failed", e);
446 }
447 if (tasks == null) {
448 return;
449 }
450 boolean wasGrantedInFullscreen = false;
451 boolean wasGrantedInPip = false;
452 for (int i = tasks.size() - 1; i >= 0; --i) {
453 RunningTaskInfo task = tasks.get(i);
454 for (int j = packageNames.length - 1; j >= 0; --j) {
455 if (task.topActivity.getPackageName().equals(packageNames[j])) {
456 if (task.id == fullscreenTopTaskId) {
457 wasGrantedInFullscreen = true;
458 } else if (task.id == mPipTaskId) {
459 wasGrantedInPip= true;
460 }
461 }
462 }
463 }
464 if (wasGrantedInFullscreen && !wasGrantedInPip) {
465 closePip();
466 }
467 }
468
Jaewan Kim62338192016-02-25 10:00:05 -0800469 private void updateMediaController(List<MediaController> controllers) {
470 MediaController mediaController = null;
471 if (controllers != null && mState != STATE_NO_PIP && mPipComponentName != null) {
472 for (int i = controllers.size() - 1; i >= 0; i--) {
473 MediaController controller = controllers.get(i);
474 // We assumes that an app with PIPable activity
475 // keeps the single instance of media controller especially when PIP is on.
476 if (controller.getPackageName().equals(mPipComponentName.getPackageName())) {
477 mediaController = controller;
478 break;
479 }
480 }
481 }
482 if (mPipMediaController != mediaController) {
483 mPipMediaController = mediaController;
Jaewan Kima0d4d252016-03-31 13:37:10 +0900484 for (int i = mMediaListeners.size() - 1; i >= 0; i--) {
485 mMediaListeners.get(i).onMediaControllerChanged();
Jaewan Kim62338192016-02-25 10:00:05 -0800486 }
Jaewan Kimf40fcdc2016-03-04 17:58:22 +0900487 if (mPipMediaController == null) {
488 mHandler.postDelayed(mClosePipRunnable,
489 CLOSE_PIP_WHEN_MEDIA_SESSION_GONE_TIMEOUT_MS);
490 } else {
491 mHandler.removeCallbacks(mClosePipRunnable);
492 }
Jaewan Kim62338192016-02-25 10:00:05 -0800493 }
494 }
495
496 /**
497 * Gets the {@link android.media.session.MediaController} for the PIPed activity.
498 */
499 MediaController getMediaController() {
500 return mPipMediaController;
501 }
502
Jaewan Kim8f584b82016-03-22 22:16:59 +0900503 /**
504 * Returns the PIPed activity's playback state.
505 * This returns one of {@link PLAYBACK_STATE_PLAYING}, {@link PLAYBACK_STATE_PAUSED},
506 * or {@link PLAYBACK_STATE_UNAVAILABLE}.
507 */
508 int getPlaybackState() {
509 if (mPipMediaController == null || mPipMediaController.getPlaybackState() == null) {
510 return PLAYBACK_STATE_UNAVAILABLE;
511 }
512 int state = mPipMediaController.getPlaybackState().getState();
513 boolean isPlaying = (state == PlaybackState.STATE_BUFFERING
514 || state == PlaybackState.STATE_CONNECTING
515 || state == PlaybackState.STATE_PLAYING
516 || state == PlaybackState.STATE_FAST_FORWARDING
517 || state == PlaybackState.STATE_REWINDING
518 || state == PlaybackState.STATE_SKIPPING_TO_PREVIOUS
519 || state == PlaybackState.STATE_SKIPPING_TO_NEXT);
520 long actions = mPipMediaController.getPlaybackState().getActions();
521 if (!isPlaying && ((actions & PlaybackState.ACTION_PLAY) != 0)) {
522 return PLAYBACK_STATE_PAUSED;
523 } else if (isPlaying && ((actions & PlaybackState.ACTION_PAUSE) != 0)) {
524 return PLAYBACK_STATE_PLAYING;
525 }
526 return PLAYBACK_STATE_UNAVAILABLE;
527 }
528
Jaewan Kima0d4d252016-03-31 13:37:10 +0900529 private TaskStackListener mTaskStackListener = new TaskStackListener() {
Youngsang Chof1647922015-12-17 13:39:39 -0800530 @Override
Jaewan Kim938a50b2016-03-14 17:35:43 +0900531 public void onTaskStackChanged() {
532 if (mState != STATE_NO_PIP) {
533 StackInfo stackInfo = null;
534 try {
535 stackInfo = mActivityManager.getStackInfo(PINNED_STACK_ID);
536 if (stackInfo == null) {
537 Log.w(TAG, "There is no pinned stack");
538 closePipInternal(false);
539 return;
540 }
541 } catch (RemoteException e) {
542 Log.e(TAG, "getStackInfo failed", e);
543 return;
544 }
545 for (int i = stackInfo.taskIds.length - 1; i >= 0; --i) {
546 if (stackInfo.taskIds[i] == mPipTaskId) {
547 // PIP task is still alive.
548 return;
549 }
550 }
551 // PIP task doesn't exist anymore in PINNED_STACK.
552 closePipInternal(true);
553 }
Youngsang Chof1647922015-12-17 13:39:39 -0800554 }
555
556 @Override
Jaewan Kim938a50b2016-03-14 17:35:43 +0900557 public void onActivityPinned() {
Wale Ogunwale480dca02016-02-06 13:58:29 -0800558 if (DEBUG) Log.d(TAG, "onActivityPinned()");
Jaewan Kim938a50b2016-03-14 17:35:43 +0900559 StackInfo stackInfo = null;
560 try {
561 stackInfo = mActivityManager.getStackInfo(PINNED_STACK_ID);
562 if (stackInfo == null) {
563 Log.w(TAG, "Cannot find pinned stack");
564 return;
565 }
566 } catch (RemoteException e) {
567 Log.e(TAG, "getStackInfo failed", e);
568 return;
569 }
570 if (DEBUG) Log.d(TAG, "PINNED_STACK:" + stackInfo);
571 mPipTaskId = stackInfo.taskIds[stackInfo.taskIds.length - 1];
572 mPipComponentName = ComponentName.unflattenFromString(
573 stackInfo.taskNames[stackInfo.taskNames.length - 1]);
574 // Set state to overlay so we show it when the pinned stack animation ends.
575 mState = STATE_PIP_OVERLAY;
576 mCurrentPipBounds = mPipBounds;
577 launchPipOnboardingActivityIfNeeded();
578 mMediaSessionManager.addOnActiveSessionsChangedListener(
579 mActiveMediaSessionListener, null);
580 updateMediaController(mMediaSessionManager.getActiveSessions(null));
Jaewan Kima0d4d252016-03-31 13:37:10 +0900581 if (mPipRecentsOverlayManager.isRecentsShown()) {
Jaewan Kim938a50b2016-03-14 17:35:43 +0900582 // If an activity becomes PIPed again after the fullscreen, the Recents is shown
583 // behind so we need to resize the pinned stack and show the correct overlay.
Jaewan Kima0d4d252016-03-31 13:37:10 +0900584 resizePinnedStack(STATE_PIP_RECENTS);
Jaewan Kim938a50b2016-03-14 17:35:43 +0900585 }
586 for (int i = mListeners.size() - 1; i >= 0; i--) {
587 mListeners.get(i).onPipEntered();
588 }
Youngsang Chof1647922015-12-17 13:39:39 -0800589 }
Wale Ogunwalecc25a8a2016-01-23 14:31:37 -0800590
591 @Override
592 public void onPinnedActivityRestartAttempt() {
Wale Ogunwale480dca02016-02-06 13:58:29 -0800593 if (DEBUG) Log.d(TAG, "onPinnedActivityRestartAttempt()");
Jaewan Kim938a50b2016-03-14 17:35:43 +0900594 // If PIPed activity is launched again by Launcher or intent, make it fullscreen.
595 movePipToFullscreen();
Wale Ogunwalecc25a8a2016-01-23 14:31:37 -0800596 }
Wale Ogunwale480dca02016-02-06 13:58:29 -0800597
598 @Override
599 public void onPinnedStackAnimationEnded() {
600 if (DEBUG) Log.d(TAG, "onPinnedStackAnimationEnded()");
Jaewan Kim938a50b2016-03-14 17:35:43 +0900601 switch (mState) {
602 case STATE_PIP_OVERLAY:
Jaewan Kima0d4d252016-03-31 13:37:10 +0900603 if (!mPipRecentsOverlayManager.isRecentsShown()) {
604 showPipOverlay();
605 break;
606 } else {
607 // This happens only if an activity is PIPed after the Recents is shown.
608 // See {@link PipRecentsOverlayManager.requestFocus} for more details.
609 resizePinnedStack(mState);
610 break;
611 }
612 case STATE_PIP_RECENTS:
613 case STATE_PIP_RECENTS_FOCUSED:
614 mPipRecentsOverlayManager.addPipRecentsOverlayView();
Jaewan Kim938a50b2016-03-14 17:35:43 +0900615 break;
616 case STATE_PIP_MENU:
617 showPipMenu();
618 break;
619 }
Wale Ogunwale480dca02016-02-06 13:58:29 -0800620 }
Jaewan Kim938a50b2016-03-14 17:35:43 +0900621 };
Youngsang Chof1647922015-12-17 13:39:39 -0800622
623 /**
624 * A listener interface to receive notification on changes in PIP.
625 */
626 public interface Listener {
Jaewan Kim82ac50d2016-03-21 17:34:28 +0900627 /**
628 * Invoked when an activity is pinned and PIP manager is set corresponding information.
629 * Classes must use this instead of {@link android.app.ITaskStackListener.onActivityPinned}
630 * because there's no guarantee for the PIP manager be return relavent information
Jaewan Kima0d4d252016-03-31 13:37:10 +0900631 * correctly. (e.g. {@link isPipShown}).
Jaewan Kim82ac50d2016-03-21 17:34:28 +0900632 */
633 void onPipEntered();
Wale Ogunwale480dca02016-02-06 13:58:29 -0800634 /** Invoked when a PIPed activity is closed. */
Youngsang Chof1647922015-12-17 13:39:39 -0800635 void onPipActivityClosed();
Wale Ogunwale480dca02016-02-06 13:58:29 -0800636 /** Invoked when the PIP menu gets shown. */
Youngsang Chof1647922015-12-17 13:39:39 -0800637 void onShowPipMenu();
Wale Ogunwale480dca02016-02-06 13:58:29 -0800638 /** Invoked when the PIPed activity is returned back to the fullscreen. */
Youngsang Chof1647922015-12-17 13:39:39 -0800639 void onMoveToFullscreen();
Wale Ogunwale480dca02016-02-06 13:58:29 -0800640 /** Invoked when we are above to start resizing the Pip. */
641 void onPipResizeAboutToStart();
Jaewan Kima0d4d252016-03-31 13:37:10 +0900642 }
643
644 /**
645 * A listener interface to receive change in PIP's media controller
646 */
647 public interface MediaListener {
Jaewan Kim62338192016-02-25 10:00:05 -0800648 /** Invoked when the MediaController on PIPed activity is changed. */
649 void onMediaControllerChanged();
Youngsang Chof1647922015-12-17 13:39:39 -0800650 }
651
652 /**
653 * Gets an instance of {@link PipManager}.
654 */
655 public static PipManager getInstance() {
656 if (sPipManager == null) {
657 sPipManager = new PipManager();
658 }
659 return sPipManager;
660 }
Jaewan Kima0d4d252016-03-31 13:37:10 +0900661
662 /**
663 * Gets an instance of {@link PipRecentsOverlayManager}.
664 */
665 public PipRecentsOverlayManager getPipRecentsOverlayManager() {
666 return mPipRecentsOverlayManager;
667 }
Youngsang Chof1647922015-12-17 13:39:39 -0800668}