blob: 186de5c4e1210fa0eaa88b376cb85b2a39b38118 [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
Winsonbf8c2c02016-10-18 18:56:24 -070017package com.android.systemui.pip.tv;
Youngsang Chof1647922015-12-17 13:39:39 -080018
Sudheer Shankadc589ac2016-11-10 15:30:17 -080019import android.app.ActivityManager;
Winsonc0d70582016-01-29 10:24:39 -080020import android.app.ActivityManager.RunningTaskInfo;
Youngsang Chof1647922015-12-17 13:39:39 -080021import android.app.ActivityManager.StackInfo;
Youngsang Chof1647922015-12-17 13:39:39 -080022import android.app.IActivityManager;
Winson Chungb6de8722017-06-02 12:45:51 -070023import android.app.RemoteAction;
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;
Winson Chung2a82fe52017-02-02 14:43:34 -080029import android.content.pm.ParceledListSlice;
Winson Chung0c5a5922017-05-22 17:41:06 -070030import android.content.res.Configuration;
Youngsang Chof1647922015-12-17 13:39:39 -080031import android.content.res.Resources;
32import android.graphics.Rect;
Jaewan Kim62338192016-02-25 10:00:05 -080033import android.media.session.MediaController;
34import android.media.session.MediaSessionManager;
Jaewan Kim8f584b82016-03-22 22:16:59 +090035import android.media.session.PlaybackState;
Wale Ogunwale480dca02016-02-06 13:58:29 -080036import android.os.Debug;
Youngsang Chof1647922015-12-17 13:39:39 -080037import android.os.Handler;
38import android.os.RemoteException;
Dongwon Kang353d8d72016-04-13 21:55:22 -070039import android.text.TextUtils;
Youngsang Chof1647922015-12-17 13:39:39 -080040import android.util.Log;
Jaewan Kimd89a6942016-04-08 11:41:45 +090041import android.util.Pair;
Winson Chung2a82fe52017-02-02 14:43:34 -080042import android.view.IPinnedStackController;
43import android.view.IPinnedStackListener;
Winson41275482016-10-10 15:17:45 -070044import android.view.IWindowManager;
Winson Chung655332c2016-10-31 13:14:28 -070045import android.view.WindowManagerGlobal;
46
Jaewan Kim8f584b82016-03-22 22:16:59 +090047import com.android.systemui.R;
Winson Chung29a78652017-02-09 18:35:26 -080048import com.android.systemui.pip.BasePipManager;
Jaewan Kimf0fd2182016-04-20 21:17:58 +090049import com.android.systemui.recents.misc.SystemServicesProxy;
Winson41275482016-10-10 15:17:45 -070050import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener;
Jaewan Kim977dcdc2016-01-20 19:21:08 +090051
Winson Chung29a78652017-02-09 18:35:26 -080052import java.io.PrintWriter;
Youngsang Chof1647922015-12-17 13:39:39 -080053import java.util.ArrayList;
54import java.util.List;
55
Youngsang Chof1647922015-12-17 13:39:39 -080056import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
Winson Chung2a82fe52017-02-02 14:43:34 -080057import static android.view.Display.DEFAULT_DISPLAY;
58
Youngsang Chof1647922015-12-17 13:39:39 -080059/**
60 * Manages the picture-in-picture (PIP) UI and states.
61 */
Winson Chung29a78652017-02-09 18:35:26 -080062public class PipManager implements BasePipManager {
Youngsang Chof1647922015-12-17 13:39:39 -080063 private static final String TAG = "PipManager";
Jaewan Kim26c63562017-04-26 15:41:43 +090064 static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
65
Jaewan Kim8af250e2017-02-14 19:12:01 +090066 private static final String SETTINGS_PACKAGE_AND_CLASS_DELIMITER = "/";
Youngsang Chof1647922015-12-17 13:39:39 -080067
68 private static PipManager sPipManager;
Jaewan Kim8af250e2017-02-14 19:12:01 +090069 private static List<Pair<String, String>> sSettingsPackageAndClassNamePairList;
Jaewan Kimd89a6942016-04-08 11:41:45 +090070
71 /**
Jaewan Kima0d4d252016-03-31 13:37:10 +090072 * State when there's no PIP.
73 */
Wale Ogunwale480dca02016-02-06 13:58:29 -080074 public static final int STATE_NO_PIP = 0;
Jaewan Kima0d4d252016-03-31 13:37:10 +090075 /**
Jaewan Kime6309232017-04-14 15:26:20 +090076 * State when PIP is shown. This is used as default PIP state.
Jaewan Kima0d4d252016-03-31 13:37:10 +090077 */
Jaewan Kime6309232017-04-14 15:26:20 +090078 public static final int STATE_PIP = 1;
Jaewan Kima0d4d252016-03-31 13:37:10 +090079 /**
80 * State when PIP menu dialog is shown.
81 */
Wale Ogunwale480dca02016-02-06 13:58:29 -080082 public static final int STATE_PIP_MENU = 2;
Youngsang Chof1647922015-12-17 13:39:39 -080083
Youngsang Choad8ceb02016-01-15 16:59:27 -080084 private static final int TASK_ID_NO_PIP = -1;
85 private static final int INVALID_RESOURCE_TYPE = -1;
86
Wale Ogunwale480dca02016-02-06 13:58:29 -080087 public static final int SUSPEND_PIP_RESIZE_REASON_WAITING_FOR_MENU_ACTIVITY_FINISH = 0x1;
88 public static final int SUSPEND_PIP_RESIZE_REASON_WAITING_FOR_OVERLAY_ACTIVITY_FINISH = 0x2;
Jaewan Kimf40fcdc2016-03-04 17:58:22 +090089
Jaewan Kim8f584b82016-03-22 22:16:59 +090090 /**
91 * PIPed activity is playing a media and it can be paused.
92 */
93 static final int PLAYBACK_STATE_PLAYING = 0;
94 /**
95 * PIPed activity has a paused media and it can be played.
96 */
97 static final int PLAYBACK_STATE_PAUSED = 1;
98 /**
99 * Users are unable to control PIPed activity's media playback.
100 */
101 static final int PLAYBACK_STATE_UNAVAILABLE = 2;
102
Jaewan Kimf40fcdc2016-03-04 17:58:22 +0900103 private static final int CLOSE_PIP_WHEN_MEDIA_SESSION_GONE_TIMEOUT_MS = 3000;
104
Wale Ogunwale480dca02016-02-06 13:58:29 -0800105 private int mSuspendPipResizingReason;
106
Youngsang Chof1647922015-12-17 13:39:39 -0800107 private Context mContext;
108 private IActivityManager mActivityManager;
Winson Chung655332c2016-10-31 13:14:28 -0700109 private IWindowManager mWindowManager;
Jaewan Kim62338192016-02-25 10:00:05 -0800110 private MediaSessionManager mMediaSessionManager;
Youngsang Chof1647922015-12-17 13:39:39 -0800111 private int mState = STATE_NO_PIP;
Winson Chung0a657372017-05-12 15:04:54 -0700112 private int mResumeResizePinnedStackRunnableState = STATE_NO_PIP;
Youngsang Chof1647922015-12-17 13:39:39 -0800113 private final Handler mHandler = new Handler();
114 private List<Listener> mListeners = new ArrayList<>();
Jaewan Kima0d4d252016-03-31 13:37:10 +0900115 private List<MediaListener> mMediaListeners = new ArrayList<>();
Jaewan Kimc92a7d12016-02-15 17:33:25 -0800116 private Rect mCurrentPipBounds;
117 private Rect mPipBounds;
Winson Chungdb2ad5d2017-02-07 17:18:01 -0800118 private Rect mDefaultPipBounds = new Rect();
Jaewan Kimd89a6942016-04-08 11:41:45 +0900119 private Rect mSettingsPipBounds;
Jaewan Kimc92a7d12016-02-15 17:33:25 -0800120 private Rect mMenuModePipBounds;
Winson Chung0c5a5922017-05-22 17:41:06 -0700121 private int mLastOrientation = Configuration.ORIENTATION_UNDEFINED;
Youngsang Chof1647922015-12-17 13:39:39 -0800122 private boolean mInitialized;
Youngsang Choad8ceb02016-01-15 16:59:27 -0800123 private int mPipTaskId = TASK_ID_NO_PIP;
Jaewan Kim62338192016-02-25 10:00:05 -0800124 private ComponentName mPipComponentName;
125 private MediaController mPipMediaController;
Dongwon Kang353d8d72016-04-13 21:55:22 -0700126 private String[] mLastPackagesResourceGranted;
Jaewan Kim26c63562017-04-26 15:41:43 +0900127 private PipNotification mPipNotification;
Winson Chungb6de8722017-06-02 12:45:51 -0700128 private ParceledListSlice mCustomActions;
Youngsang Choad8ceb02016-01-15 16:59:27 -0800129
Winson Chung44315c62017-08-24 13:35:24 -0700130 // Keeps track of the IME visibility to adjust the PiP when the IME is visible
131 private boolean mImeVisible;
132 private int mImeHeightAdjustment;
133
Winson Chung2a82fe52017-02-02 14:43:34 -0800134 private final PinnedStackListener mPinnedStackListener = new PinnedStackListener();
135
Wale Ogunwale480dca02016-02-06 13:58:29 -0800136 private final Runnable mResizePinnedStackRunnable = new Runnable() {
137 @Override
138 public void run() {
Winson Chung0a657372017-05-12 15:04:54 -0700139 resizePinnedStack(mResumeResizePinnedStackRunnableState);
Wale Ogunwale480dca02016-02-06 13:58:29 -0800140 }
141 };
Jaewan Kimf40fcdc2016-03-04 17:58:22 +0900142 private final Runnable mClosePipRunnable = new Runnable() {
143 @Override
144 public void run() {
145 closePip();
146 }
147 };
Youngsang Chof1647922015-12-17 13:39:39 -0800148
Youngsang Choad8ceb02016-01-15 16:59:27 -0800149 private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
Youngsang Chof1647922015-12-17 13:39:39 -0800150 @Override
151 public void onReceive(Context context, Intent intent) {
Youngsang Choad8ceb02016-01-15 16:59:27 -0800152 String action = intent.getAction();
Jaewan Kimc552b042016-01-18 16:08:45 +0900153 if (Intent.ACTION_MEDIA_RESOURCE_GRANTED.equals(action)) {
Youngsang Choad8ceb02016-01-15 16:59:27 -0800154 String[] packageNames = intent.getStringArrayExtra(Intent.EXTRA_PACKAGES);
155 int resourceType = intent.getIntExtra(Intent.EXTRA_MEDIA_RESOURCE_TYPE,
156 INVALID_RESOURCE_TYPE);
Dongwon Kang353d8d72016-04-13 21:55:22 -0700157 if (packageNames != null && packageNames.length > 0
Youngsang Choad8ceb02016-01-15 16:59:27 -0800158 && resourceType == Intent.EXTRA_MEDIA_RESOURCE_TYPE_VIDEO_CODEC) {
159 handleMediaResourceGranted(packageNames);
160 }
Youngsang Chof1647922015-12-17 13:39:39 -0800161 }
Youngsang Choad8ceb02016-01-15 16:59:27 -0800162
Youngsang Chof1647922015-12-17 13:39:39 -0800163 }
164 };
Jaewan Kim62338192016-02-25 10:00:05 -0800165 private final MediaSessionManager.OnActiveSessionsChangedListener mActiveMediaSessionListener =
166 new MediaSessionManager.OnActiveSessionsChangedListener() {
167 @Override
168 public void onActiveSessionsChanged(List<MediaController> controllers) {
169 updateMediaController(controllers);
170 }
171 };
Youngsang Chof1647922015-12-17 13:39:39 -0800172
Winson Chung2a82fe52017-02-02 14:43:34 -0800173 /**
174 * Handler for messages from the PIP controller.
175 */
176 private class PinnedStackListener extends IPinnedStackListener.Stub {
177
178 @Override
179 public void onListenerRegistered(IPinnedStackController controller) {}
180
181 @Override
Winson Chung44315c62017-08-24 13:35:24 -0700182 public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
183 if (mState == STATE_PIP) {
184 if (mImeVisible != imeVisible) {
185 if (imeVisible) {
186 // Save the IME height adjustment, and offset to not occlude the IME
187 mPipBounds.offset(0, -imeHeight);
188 mImeHeightAdjustment = imeHeight;
189 } else {
190 // Apply the inverse adjustment when the IME is hidden
191 mPipBounds.offset(0, mImeHeightAdjustment);
192 }
193 mImeVisible = imeVisible;
194 resizePinnedStack(STATE_PIP);
195 }
196 }
197 }
Winson Chung2a82fe52017-02-02 14:43:34 -0800198
199 @Override
200 public void onMinimizedStateChanged(boolean isMinimized) {}
201
202 @Override
203 public void onMovementBoundsChanged(Rect insetBounds, Rect normalBounds,
Winson Chungef4dc812017-04-11 13:31:44 -0700204 Rect animatingBounds, boolean fromImeAdjustement, int displayRotation) {
Winson Chung2a82fe52017-02-02 14:43:34 -0800205 mHandler.post(() -> {
206 mDefaultPipBounds.set(normalBounds);
207 });
208 }
209
210 @Override
Winson Chungb6de8722017-06-02 12:45:51 -0700211 public void onActionsChanged(ParceledListSlice actions) {
212 mCustomActions = actions;
213 mHandler.post(() -> {
214 for (int i = mListeners.size() - 1; i >= 0; --i) {
215 mListeners.get(i).onPipMenuActionsChanged(mCustomActions);
216 }
217 });
218 }
Winson Chung2a82fe52017-02-02 14:43:34 -0800219 }
220
Youngsang Chof1647922015-12-17 13:39:39 -0800221 private PipManager() { }
222
223 /**
224 * Initializes {@link PipManager}.
225 */
226 public void initialize(Context context) {
227 if (mInitialized) {
228 return;
229 }
230 mInitialized = true;
231 mContext = context;
Jaewan Kim73ef3512016-07-18 13:50:33 +0900232
Sudheer Shankadc589ac2016-11-10 15:30:17 -0800233 mActivityManager = ActivityManager.getService();
Winson Chung655332c2016-10-31 13:14:28 -0700234 mWindowManager = WindowManagerGlobal.getWindowManagerService();
Jaewan Kim73ef3512016-07-18 13:50:33 +0900235 SystemServicesProxy.getInstance(context).registerTaskStackListener(mTaskStackListener);
236 IntentFilter intentFilter = new IntentFilter();
237 intentFilter.addAction(Intent.ACTION_MEDIA_RESOURCE_GRANTED);
238 mContext.registerReceiver(mBroadcastReceiver, intentFilter);
Jaewan Kim73ef3512016-07-18 13:50:33 +0900239
Jaewan Kim8af250e2017-02-14 19:12:01 +0900240 if (sSettingsPackageAndClassNamePairList == null) {
241 String[] settings = mContext.getResources().getStringArray(
242 R.array.tv_pip_settings_class_name);
243 sSettingsPackageAndClassNamePairList = new ArrayList<>();
244 if (settings != null) {
245 for (int i = 0; i < settings.length; i++) {
246 Pair<String, String> entry = null;
247 String[] packageAndClassName =
248 settings[i].split(SETTINGS_PACKAGE_AND_CLASS_DELIMITER);
249 switch (packageAndClassName.length) {
250 case 1:
251 entry = Pair.<String, String>create(packageAndClassName[0], null);
252 break;
253 case 2:
Jaewan Kimdc298752017-02-24 10:45:12 +0900254 if (packageAndClassName[1] != null
255 && packageAndClassName[1].startsWith(".")) {
256 entry = Pair.<String, String>create(
257 packageAndClassName[0],
258 packageAndClassName[0] + packageAndClassName[1]);
259 }
Jaewan Kim8af250e2017-02-14 19:12:01 +0900260 }
261 if (entry != null) {
262 sSettingsPackageAndClassNamePairList.add(entry);
Jaewan Kimdc298752017-02-24 10:45:12 +0900263 } else {
264 Log.w(TAG, "Ignoring malformed settings name " + settings[i]);
Jaewan Kim8af250e2017-02-14 19:12:01 +0900265 }
266 }
267 }
268 }
269
Winson Chung8068ec22017-06-02 09:58:00 -0700270 // Initialize the last orientation and apply the current configuration
271 Configuration initialConfig = mContext.getResources().getConfiguration();
272 mLastOrientation = initialConfig.orientation;
273 loadConfigurationsAndApply(initialConfig);
274
Jaewan Kim73ef3512016-07-18 13:50:33 +0900275 mMediaSessionManager =
276 (MediaSessionManager) mContext.getSystemService(Context.MEDIA_SESSION_SERVICE);
Winson Chung2a82fe52017-02-02 14:43:34 -0800277
278 try {
279 mWindowManager.registerPinnedStackListener(DEFAULT_DISPLAY, mPinnedStackListener);
280 } catch (RemoteException e) {
281 Log.e(TAG, "Failed to register pinned stack listener", e);
282 }
Jaewan Kim26c63562017-04-26 15:41:43 +0900283
284 mPipNotification = new PipNotification(context);
Jaewan Kim73ef3512016-07-18 13:50:33 +0900285 }
286
Winson Chung0c5a5922017-05-22 17:41:06 -0700287 private void loadConfigurationsAndApply(Configuration newConfig) {
288 if (mLastOrientation != newConfig.orientation) {
289 // Don't resize the pinned stack on orientation change. TV does not care about this case
290 // and this could clobber the existing animation to the new bounds calculated by WM.
291 mLastOrientation = newConfig.orientation;
292 return;
293 }
294
Jaewan Kim73ef3512016-07-18 13:50:33 +0900295 Resources res = mContext.getResources();
Jaewan Kimd89a6942016-04-08 11:41:45 +0900296 mSettingsPipBounds = Rect.unflattenFromString(res.getString(
297 R.string.pip_settings_bounds));
Jaewan Kimc92a7d12016-02-15 17:33:25 -0800298 mMenuModePipBounds = Rect.unflattenFromString(res.getString(
Jaewan Kim8f584b82016-03-22 22:16:59 +0900299 R.string.pip_menu_bounds));
Youngsang Chof1647922015-12-17 13:39:39 -0800300
Jaewan Kim73ef3512016-07-18 13:50:33 +0900301 // Reset the PIP bounds and apply. PIP bounds can be changed by two reasons.
302 // 1. Configuration changed due to the language change (RTL <-> RTL)
303 // 2. SystemUI restarts after the crash
304 mPipBounds = isSettingsShown() ? mSettingsPipBounds : mDefaultPipBounds;
Jaewan Kime6309232017-04-14 15:26:20 +0900305 resizePinnedStack(getPinnedStackInfo() == null ? STATE_NO_PIP : STATE_PIP);
Youngsang Chof1647922015-12-17 13:39:39 -0800306 }
307
Jaewan Kimc552b042016-01-18 16:08:45 +0900308 /**
Jaewan Kim2d4d07c2016-05-20 10:28:17 +0900309 * Updates the PIP per configuration changed.
310 */
Winson Chung0c5a5922017-05-22 17:41:06 -0700311 public void onConfigurationChanged(Configuration newConfig) {
312 loadConfigurationsAndApply(newConfig);
Jaewan Kim26c63562017-04-26 15:41:43 +0900313 mPipNotification.onConfigurationChanged(mContext);
Jaewan Kim2d4d07c2016-05-20 10:28:17 +0900314 }
315
316 /**
Jaewan Kimf0fd2182016-04-20 21:17:58 +0900317 * Shows the picture-in-picture menu if an activity is in picture-in-picture mode.
Jaewan Kimc552b042016-01-18 16:08:45 +0900318 */
Winson Chungac52f282017-03-30 14:44:52 -0700319 public void showPictureInPictureMenu() {
Winson Chung0a657372017-05-12 15:04:54 -0700320 if (getState() == STATE_PIP) {
Wale Ogunwale480dca02016-02-06 13:58:29 -0800321 resizePinnedStack(STATE_PIP_MENU);
Jaewan Kimc552b042016-01-18 16:08:45 +0900322 }
323 }
324
Youngsang Chof1647922015-12-17 13:39:39 -0800325 /**
Jaewan Kim977dcdc2016-01-20 19:21:08 +0900326 * Closes PIP (PIPed activity and PIP system UI).
Youngsang Chof1647922015-12-17 13:39:39 -0800327 */
328 public void closePip() {
Youngsang Cho336007b2016-02-22 11:17:29 -0800329 closePipInternal(true);
Youngsang Cho23df6992016-01-26 17:51:33 -0800330 }
331
Youngsang Cho336007b2016-02-22 11:17:29 -0800332 private void closePipInternal(boolean removePipStack) {
Youngsang Chof1647922015-12-17 13:39:39 -0800333 mState = STATE_NO_PIP;
Youngsang Choad8ceb02016-01-15 16:59:27 -0800334 mPipTaskId = TASK_ID_NO_PIP;
Jaewan Kim62338192016-02-25 10:00:05 -0800335 mPipMediaController = null;
336 mMediaSessionManager.removeOnActiveSessionsChangedListener(mActiveMediaSessionListener);
Youngsang Cho23df6992016-01-26 17:51:33 -0800337 if (removePipStack) {
338 try {
339 mActivityManager.removeStack(PINNED_STACK_ID);
340 } catch (RemoteException e) {
341 Log.e(TAG, "removeStack failed", e);
342 }
Youngsang Chof1647922015-12-17 13:39:39 -0800343 }
Jaewan Kimc92a7d12016-02-15 17:33:25 -0800344 for (int i = mListeners.size() - 1; i >= 0; --i) {
345 mListeners.get(i).onPipActivityClosed();
346 }
Jaewan Kimf40fcdc2016-03-04 17:58:22 +0900347 mHandler.removeCallbacks(mClosePipRunnable);
Jaewan Kimf0fd2182016-04-20 21:17:58 +0900348 updatePipVisibility(false);
Youngsang Chof1647922015-12-17 13:39:39 -0800349 }
350
351 /**
Jaewan Kim977dcdc2016-01-20 19:21:08 +0900352 * Moves the PIPed activity to the fullscreen and closes PIP system UI.
Youngsang Chof1647922015-12-17 13:39:39 -0800353 */
Jaewan Kima0d4d252016-03-31 13:37:10 +0900354 void movePipToFullscreen() {
Youngsang Choad8ceb02016-01-15 16:59:27 -0800355 mPipTaskId = TASK_ID_NO_PIP;
Jaewan Kimb0033642016-04-22 18:41:37 +0900356 for (int i = mListeners.size() - 1; i >= 0; --i) {
357 mListeners.get(i).onMoveToFullscreen();
358 }
Winson Chungf04c1dd2017-02-24 16:47:37 -0800359 resizePinnedStack(STATE_NO_PIP);
Jaewan Kim47fe2862016-10-10 16:23:28 +0900360 updatePipVisibility(false);
Youngsang Chof1647922015-12-17 13:39:39 -0800361 }
362
363 /**
Wale Ogunwale480dca02016-02-06 13:58:29 -0800364 * Suspends resizing operation on the Pip until {@link #resumePipResizing} is called
365 * @param reason The reason for suspending resizing operations on the Pip.
366 */
367 public void suspendPipResizing(int reason) {
368 if (DEBUG) Log.d(TAG,
369 "suspendPipResizing() reason=" + reason + " callers=" + Debug.getCallers(2));
370 mSuspendPipResizingReason |= reason;
371 }
372
373 /**
374 * Resumes resizing operation on the Pip that was previously suspended.
375 * @param reason The reason resizing operations on the Pip was suspended.
376 */
377 public void resumePipResizing(int reason) {
378 if ((mSuspendPipResizingReason & reason) == 0) {
379 return;
380 }
381 if (DEBUG) Log.d(TAG,
382 "resumePipResizing() reason=" + reason + " callers=" + Debug.getCallers(2));
383 mSuspendPipResizingReason &= ~reason;
384 mHandler.post(mResizePinnedStackRunnable);
385 }
386
387 /**
388 * Resize the Pip to the appropriate size for the input state.
389 * @param state In Pip state also used to determine the new size for the Pip.
390 */
Jaewan Kima0d4d252016-03-31 13:37:10 +0900391 void resizePinnedStack(int state) {
Jaewan Kim26c63562017-04-26 15:41:43 +0900392 if (DEBUG) Log.d(TAG, "resizePinnedStack() state=" + state, new Exception());
Winson Chungf04c1dd2017-02-24 16:47:37 -0800393 boolean wasStateNoPip = (mState == STATE_NO_PIP);
Wale Ogunwale480dca02016-02-06 13:58:29 -0800394 for (int i = mListeners.size() - 1; i >= 0; --i) {
395 mListeners.get(i).onPipResizeAboutToStart();
396 }
Wale Ogunwale480dca02016-02-06 13:58:29 -0800397 if (mSuspendPipResizingReason != 0) {
Winson Chung0a657372017-05-12 15:04:54 -0700398 mResumeResizePinnedStackRunnableState = state;
Winson Chungd62acec2017-03-31 19:42:32 -0700399 if (DEBUG) Log.d(TAG, "resizePinnedStack() deferring"
400 + " mSuspendPipResizingReason=" + mSuspendPipResizingReason
Winson Chung0a657372017-05-12 15:04:54 -0700401 + " mResumeResizePinnedStackRunnableState="
402 + mResumeResizePinnedStackRunnableState);
Wale Ogunwale480dca02016-02-06 13:58:29 -0800403 return;
404 }
Winson Chungd62acec2017-03-31 19:42:32 -0700405 mState = state;
Jaewan Kimc92a7d12016-02-15 17:33:25 -0800406 switch (mState) {
407 case STATE_NO_PIP:
408 mCurrentPipBounds = null;
Winson Chungf04c1dd2017-02-24 16:47:37 -0800409 // If the state was already STATE_NO_PIP, then do not resize the stack below as it
410 // will not exist
411 if (wasStateNoPip) {
412 return;
413 }
Jaewan Kimc92a7d12016-02-15 17:33:25 -0800414 break;
415 case STATE_PIP_MENU:
416 mCurrentPipBounds = mMenuModePipBounds;
417 break;
Jaewan Kime6309232017-04-14 15:26:20 +0900418 case STATE_PIP:
Jaewan Kima0d4d252016-03-31 13:37:10 +0900419 mCurrentPipBounds = mPipBounds;
420 break;
Jaewan Kimc92a7d12016-02-15 17:33:25 -0800421 default:
422 mCurrentPipBounds = mPipBounds;
423 break;
424 }
Winson Chungf04c1dd2017-02-24 16:47:37 -0800425 try {
426 int animationDurationMs = -1;
Winson Chungf04c1dd2017-02-24 16:47:37 -0800427 mActivityManager.resizeStack(PINNED_STACK_ID, mCurrentPipBounds,
428 true, true, true, animationDurationMs);
429 } catch (RemoteException e) {
430 Log.e(TAG, "resizeStack failed", e);
Wale Ogunwale480dca02016-02-06 13:58:29 -0800431 }
432 }
433
434 /**
Winson Chung0a657372017-05-12 15:04:54 -0700435 * @return the current state, or the pending state if the state change was previously suspended.
436 */
437 private int getState() {
438 if (mSuspendPipResizingReason != 0) {
439 return mResumeResizePinnedStackRunnableState;
440 }
441 return mState;
442 }
443
444 /**
Jaewan Kima0d4d252016-03-31 13:37:10 +0900445 * Returns the default PIP bound.
Jaewan Kimc92a7d12016-02-15 17:33:25 -0800446 */
447 public Rect getPipBounds() {
Jaewan Kima0d4d252016-03-31 13:37:10 +0900448 return mPipBounds;
Jaewan Kimc92a7d12016-02-15 17:33:25 -0800449 }
450
451 /**
Youngsang Chof1647922015-12-17 13:39:39 -0800452 * Shows PIP menu UI by launching {@link PipMenuActivity}. It also locates the pinned
Jaewan Kim8f584b82016-03-22 22:16:59 +0900453 * stack to the centered PIP bound {@link R.config_centeredPictureInPictureBounds}.
Youngsang Chof1647922015-12-17 13:39:39 -0800454 */
Wale Ogunwale480dca02016-02-06 13:58:29 -0800455 private void showPipMenu() {
Youngsang Chof1647922015-12-17 13:39:39 -0800456 if (DEBUG) Log.d(TAG, "showPipMenu()");
Youngsang Chof1647922015-12-17 13:39:39 -0800457 mState = STATE_PIP_MENU;
458 for (int i = mListeners.size() - 1; i >= 0; --i) {
459 mListeners.get(i).onShowPipMenu();
460 }
461 Intent intent = new Intent(mContext, PipMenuActivity.class);
462 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Winson Chungb6de8722017-06-02 12:45:51 -0700463 intent.putExtra(PipMenuActivity.EXTRA_CUSTOM_ACTIONS, mCustomActions);
Jaewan Kim1a9dc562016-02-17 13:41:51 -0800464 mContext.startActivity(intent);
Youngsang Chof1647922015-12-17 13:39:39 -0800465 }
466
Jaewan Kima0d4d252016-03-31 13:37:10 +0900467 /**
468 * Adds a {@link Listener} to PipManager.
469 */
Youngsang Chof1647922015-12-17 13:39:39 -0800470 public void addListener(Listener listener) {
471 mListeners.add(listener);
472 }
473
Jaewan Kima0d4d252016-03-31 13:37:10 +0900474 /**
475 * Removes a {@link Listener} from PipManager.
476 */
Youngsang Chof1647922015-12-17 13:39:39 -0800477 public void removeListener(Listener listener) {
478 mListeners.remove(listener);
479 }
480
Jaewan Kima0d4d252016-03-31 13:37:10 +0900481 /**
482 * Adds a {@link MediaListener} to PipManager.
483 */
484 public void addMediaListener(MediaListener listener) {
485 mMediaListeners.add(listener);
486 }
487
488 /**
489 * Removes a {@link MediaListener} from PipManager.
490 */
491 public void removeMediaListener(MediaListener listener) {
492 mMediaListeners.remove(listener);
493 }
494
Jaewan Kimc92a7d12016-02-15 17:33:25 -0800495 /**
496 * Returns {@code true} if PIP is shown.
497 */
498 public boolean isPipShown() {
Jaewan Kim82ac50d2016-03-21 17:34:28 +0900499 return mState != STATE_NO_PIP;
Youngsang Chof1647922015-12-17 13:39:39 -0800500 }
501
Jaewan Kim73ef3512016-07-18 13:50:33 +0900502 private StackInfo getPinnedStackInfo() {
503 StackInfo stackInfo = null;
504 try {
505 stackInfo = mActivityManager.getStackInfo(PINNED_STACK_ID);
506 } catch (RemoteException e) {
507 Log.e(TAG, "getStackInfo failed", e);
508 }
509 return stackInfo;
510 }
511
Youngsang Choad8ceb02016-01-15 16:59:27 -0800512 private void handleMediaResourceGranted(String[] packageNames) {
Winson Chung0a657372017-05-12 15:04:54 -0700513 if (getState() == STATE_NO_PIP) {
Dongwon Kang353d8d72016-04-13 21:55:22 -0700514 mLastPackagesResourceGranted = packageNames;
515 } else {
516 boolean requestedFromLastPackages = false;
517 if (mLastPackagesResourceGranted != null) {
518 for (String packageName : mLastPackagesResourceGranted) {
519 for (String newPackageName : packageNames) {
520 if (TextUtils.equals(newPackageName, packageName)) {
521 requestedFromLastPackages = true;
522 break;
523 }
Youngsang Choad8ceb02016-01-15 16:59:27 -0800524 }
525 }
526 }
Dongwon Kang353d8d72016-04-13 21:55:22 -0700527 mLastPackagesResourceGranted = packageNames;
528 if (!requestedFromLastPackages) {
529 closePip();
530 }
Youngsang Choad8ceb02016-01-15 16:59:27 -0800531 }
532 }
533
Jaewan Kim62338192016-02-25 10:00:05 -0800534 private void updateMediaController(List<MediaController> controllers) {
535 MediaController mediaController = null;
Winson Chung0a657372017-05-12 15:04:54 -0700536 if (controllers != null && getState() != STATE_NO_PIP && mPipComponentName != null) {
Jaewan Kim62338192016-02-25 10:00:05 -0800537 for (int i = controllers.size() - 1; i >= 0; i--) {
538 MediaController controller = controllers.get(i);
539 // We assumes that an app with PIPable activity
540 // keeps the single instance of media controller especially when PIP is on.
541 if (controller.getPackageName().equals(mPipComponentName.getPackageName())) {
542 mediaController = controller;
543 break;
544 }
545 }
546 }
547 if (mPipMediaController != mediaController) {
548 mPipMediaController = mediaController;
Jaewan Kima0d4d252016-03-31 13:37:10 +0900549 for (int i = mMediaListeners.size() - 1; i >= 0; i--) {
550 mMediaListeners.get(i).onMediaControllerChanged();
Jaewan Kim62338192016-02-25 10:00:05 -0800551 }
Jaewan Kimf40fcdc2016-03-04 17:58:22 +0900552 if (mPipMediaController == null) {
553 mHandler.postDelayed(mClosePipRunnable,
554 CLOSE_PIP_WHEN_MEDIA_SESSION_GONE_TIMEOUT_MS);
555 } else {
556 mHandler.removeCallbacks(mClosePipRunnable);
557 }
Jaewan Kim62338192016-02-25 10:00:05 -0800558 }
559 }
560
561 /**
562 * Gets the {@link android.media.session.MediaController} for the PIPed activity.
563 */
564 MediaController getMediaController() {
565 return mPipMediaController;
566 }
567
Jaewan Kim8f584b82016-03-22 22:16:59 +0900568 /**
569 * Returns the PIPed activity's playback state.
Jaewan Kim26c63562017-04-26 15:41:43 +0900570 * This returns one of {@link #PLAYBACK_STATE_PLAYING}, {@link #PLAYBACK_STATE_PAUSED},
571 * or {@link #PLAYBACK_STATE_UNAVAILABLE}.
Jaewan Kim8f584b82016-03-22 22:16:59 +0900572 */
573 int getPlaybackState() {
574 if (mPipMediaController == null || mPipMediaController.getPlaybackState() == null) {
575 return PLAYBACK_STATE_UNAVAILABLE;
576 }
577 int state = mPipMediaController.getPlaybackState().getState();
578 boolean isPlaying = (state == PlaybackState.STATE_BUFFERING
579 || state == PlaybackState.STATE_CONNECTING
580 || state == PlaybackState.STATE_PLAYING
581 || state == PlaybackState.STATE_FAST_FORWARDING
582 || state == PlaybackState.STATE_REWINDING
583 || state == PlaybackState.STATE_SKIPPING_TO_PREVIOUS
584 || state == PlaybackState.STATE_SKIPPING_TO_NEXT);
585 long actions = mPipMediaController.getPlaybackState().getActions();
586 if (!isPlaying && ((actions & PlaybackState.ACTION_PLAY) != 0)) {
587 return PLAYBACK_STATE_PAUSED;
588 } else if (isPlaying && ((actions & PlaybackState.ACTION_PAUSE) != 0)) {
589 return PLAYBACK_STATE_PLAYING;
590 }
591 return PLAYBACK_STATE_UNAVAILABLE;
592 }
593
Jaewan Kim73ef3512016-07-18 13:50:33 +0900594 private boolean isSettingsShown() {
595 List<RunningTaskInfo> runningTasks;
596 try {
597 runningTasks = mActivityManager.getTasks(1, 0);
598 if (runningTasks == null || runningTasks.size() == 0) {
599 return false;
600 }
601 } catch (RemoteException e) {
602 Log.d(TAG, "Failed to detect top activity", e);
603 return false;
604 }
605 ComponentName topActivity = runningTasks.get(0).topActivity;
Jaewan Kimd89a6942016-04-08 11:41:45 +0900606 for (Pair<String, String> componentName : sSettingsPackageAndClassNamePairList) {
607 String packageName = componentName.first;
Andrii Kulian8290f8f2016-06-30 17:51:32 -0700608 if (topActivity.getPackageName().equals(packageName)) {
Jaewan Kimd89a6942016-04-08 11:41:45 +0900609 String className = componentName.second;
610 if (className == null || topActivity.getClassName().equals(className)) {
611 return true;
612 }
613 }
614 }
615 return false;
616 }
617
Jaewan Kima0d4d252016-03-31 13:37:10 +0900618 private TaskStackListener mTaskStackListener = new TaskStackListener() {
Youngsang Chof1647922015-12-17 13:39:39 -0800619 @Override
Jaewan Kim938a50b2016-03-14 17:35:43 +0900620 public void onTaskStackChanged() {
Jaewan Kim17ca4e32016-10-31 13:55:43 +0900621 if (DEBUG) Log.d(TAG, "onTaskStackChanged()");
Winson Chungee697562017-05-18 14:29:43 -0700622 if (!checkCurrentUserId(mContext, DEBUG)) {
Jaewan Kim17ca4e32016-10-31 13:55:43 +0900623 return;
624 }
Winson Chung0a657372017-05-12 15:04:54 -0700625 if (getState() != STATE_NO_PIP) {
Jaewan Kimd89a6942016-04-08 11:41:45 +0900626 boolean hasPip = false;
627
Jaewan Kim73ef3512016-07-18 13:50:33 +0900628 StackInfo stackInfo = getPinnedStackInfo();
629 if (stackInfo == null || stackInfo.taskIds == null) {
630 Log.w(TAG, "There is nothing in pinned stack");
631 closePipInternal(false);
Jaewan Kim938a50b2016-03-14 17:35:43 +0900632 return;
633 }
634 for (int i = stackInfo.taskIds.length - 1; i >= 0; --i) {
635 if (stackInfo.taskIds[i] == mPipTaskId) {
636 // PIP task is still alive.
Jaewan Kimd89a6942016-04-08 11:41:45 +0900637 hasPip = true;
638 break;
Jaewan Kim938a50b2016-03-14 17:35:43 +0900639 }
640 }
Jaewan Kimd89a6942016-04-08 11:41:45 +0900641 if (!hasPip) {
642 // PIP task doesn't exist anymore in PINNED_STACK.
643 closePipInternal(true);
644 return;
645 }
646 }
Winson Chung0a657372017-05-12 15:04:54 -0700647 if (getState() == STATE_PIP) {
Jaewan Kim73ef3512016-07-18 13:50:33 +0900648 Rect bounds = isSettingsShown() ? mSettingsPipBounds : mDefaultPipBounds;
649 if (mPipBounds != bounds) {
650 mPipBounds = bounds;
Jaewan Kime6309232017-04-14 15:26:20 +0900651 resizePinnedStack(STATE_PIP);
Jaewan Kimd89a6942016-04-08 11:41:45 +0900652 }
Jaewan Kim938a50b2016-03-14 17:35:43 +0900653 }
Youngsang Chof1647922015-12-17 13:39:39 -0800654 }
655
656 @Override
Winson Chung85d3c8a2017-09-15 15:41:00 -0700657 public void onActivityPinned(String packageName, int userId, int taskId) {
Wale Ogunwale480dca02016-02-06 13:58:29 -0800658 if (DEBUG) Log.d(TAG, "onActivityPinned()");
Winson Chungee697562017-05-18 14:29:43 -0700659 if (!checkCurrentUserId(mContext, DEBUG)) {
Jaewan Kim17ca4e32016-10-31 13:55:43 +0900660 return;
661 }
Jaewan Kim73ef3512016-07-18 13:50:33 +0900662 StackInfo stackInfo = getPinnedStackInfo();
663 if (stackInfo == null) {
664 Log.w(TAG, "Cannot find pinned stack");
Jaewan Kim938a50b2016-03-14 17:35:43 +0900665 return;
666 }
667 if (DEBUG) Log.d(TAG, "PINNED_STACK:" + stackInfo);
668 mPipTaskId = stackInfo.taskIds[stackInfo.taskIds.length - 1];
669 mPipComponentName = ComponentName.unflattenFromString(
670 stackInfo.taskNames[stackInfo.taskNames.length - 1]);
Jaewan Kime6309232017-04-14 15:26:20 +0900671 // Set state to STATE_PIP so we show it when the pinned stack animation ends.
672 mState = STATE_PIP;
Jaewan Kim938a50b2016-03-14 17:35:43 +0900673 mCurrentPipBounds = mPipBounds;
Jaewan Kim938a50b2016-03-14 17:35:43 +0900674 mMediaSessionManager.addOnActiveSessionsChangedListener(
675 mActiveMediaSessionListener, null);
676 updateMediaController(mMediaSessionManager.getActiveSessions(null));
Jaewan Kim938a50b2016-03-14 17:35:43 +0900677 for (int i = mListeners.size() - 1; i >= 0; i--) {
678 mListeners.get(i).onPipEntered();
679 }
Jaewan Kimf0fd2182016-04-20 21:17:58 +0900680 updatePipVisibility(true);
Youngsang Chof1647922015-12-17 13:39:39 -0800681 }
Wale Ogunwalecc25a8a2016-01-23 14:31:37 -0800682
683 @Override
Winson Chunge6385a22017-05-02 18:15:16 -0700684 public void onPinnedActivityRestartAttempt(boolean clearedTask) {
Wale Ogunwale480dca02016-02-06 13:58:29 -0800685 if (DEBUG) Log.d(TAG, "onPinnedActivityRestartAttempt()");
Winson Chungee697562017-05-18 14:29:43 -0700686 if (!checkCurrentUserId(mContext, DEBUG)) {
Jaewan Kim17ca4e32016-10-31 13:55:43 +0900687 return;
688 }
Jaewan Kim938a50b2016-03-14 17:35:43 +0900689 // If PIPed activity is launched again by Launcher or intent, make it fullscreen.
690 movePipToFullscreen();
Wale Ogunwalecc25a8a2016-01-23 14:31:37 -0800691 }
Wale Ogunwale480dca02016-02-06 13:58:29 -0800692
693 @Override
694 public void onPinnedStackAnimationEnded() {
695 if (DEBUG) Log.d(TAG, "onPinnedStackAnimationEnded()");
Winson Chungee697562017-05-18 14:29:43 -0700696 if (!checkCurrentUserId(mContext, DEBUG)) {
Jaewan Kim17ca4e32016-10-31 13:55:43 +0900697 return;
698 }
Winson Chung0a657372017-05-12 15:04:54 -0700699 switch (getState()) {
Jaewan Kim938a50b2016-03-14 17:35:43 +0900700 case STATE_PIP_MENU:
701 showPipMenu();
702 break;
703 }
Wale Ogunwale480dca02016-02-06 13:58:29 -0800704 }
Jaewan Kim938a50b2016-03-14 17:35:43 +0900705 };
Youngsang Chof1647922015-12-17 13:39:39 -0800706
707 /**
708 * A listener interface to receive notification on changes in PIP.
709 */
710 public interface Listener {
Jaewan Kim82ac50d2016-03-21 17:34:28 +0900711 /**
712 * Invoked when an activity is pinned and PIP manager is set corresponding information.
713 * Classes must use this instead of {@link android.app.ITaskStackListener.onActivityPinned}
714 * because there's no guarantee for the PIP manager be return relavent information
Jaewan Kima0d4d252016-03-31 13:37:10 +0900715 * correctly. (e.g. {@link isPipShown}).
Jaewan Kim82ac50d2016-03-21 17:34:28 +0900716 */
717 void onPipEntered();
Wale Ogunwale480dca02016-02-06 13:58:29 -0800718 /** Invoked when a PIPed activity is closed. */
Youngsang Chof1647922015-12-17 13:39:39 -0800719 void onPipActivityClosed();
Wale Ogunwale480dca02016-02-06 13:58:29 -0800720 /** Invoked when the PIP menu gets shown. */
Youngsang Chof1647922015-12-17 13:39:39 -0800721 void onShowPipMenu();
Winson Chungb6de8722017-06-02 12:45:51 -0700722 /** Invoked when the PIP menu actions change. */
723 void onPipMenuActionsChanged(ParceledListSlice actions);
Jaewan Kim10a86912016-04-04 16:01:51 +0900724 /** Invoked when the PIPed activity is about to return back to the fullscreen. */
Youngsang Chof1647922015-12-17 13:39:39 -0800725 void onMoveToFullscreen();
Wale Ogunwale480dca02016-02-06 13:58:29 -0800726 /** Invoked when we are above to start resizing the Pip. */
727 void onPipResizeAboutToStart();
Jaewan Kima0d4d252016-03-31 13:37:10 +0900728 }
729
730 /**
731 * A listener interface to receive change in PIP's media controller
732 */
733 public interface MediaListener {
Jaewan Kim62338192016-02-25 10:00:05 -0800734 /** Invoked when the MediaController on PIPed activity is changed. */
735 void onMediaControllerChanged();
Youngsang Chof1647922015-12-17 13:39:39 -0800736 }
737
738 /**
739 * Gets an instance of {@link PipManager}.
740 */
741 public static PipManager getInstance() {
742 if (sPipManager == null) {
743 sPipManager = new PipManager();
744 }
745 return sPipManager;
746 }
Jaewan Kima0d4d252016-03-31 13:37:10 +0900747
Winsonab216602016-08-09 14:05:20 -0700748 private void updatePipVisibility(final boolean visible) {
Winson Chungac52f282017-03-30 14:44:52 -0700749 SystemServicesProxy.getInstance(mContext).setPipVisibility(visible);
Jaewan Kimf0fd2182016-04-20 21:17:58 +0900750 }
Winson Chung29a78652017-02-09 18:35:26 -0800751
752 @Override
753 public void dump(PrintWriter pw) {
754 // Do nothing
755 }
Youngsang Chof1647922015-12-17 13:39:39 -0800756}