blob: 81d6973efb2a96a41f064850bb37433c8156224b [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
Winson Chung67f5c8b2018-09-24 12:09:19 -070019import static android.app.ActivityTaskManager.INVALID_STACK_ID;
20import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
21import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
Winson Chung67f5c8b2018-09-24 12:09:19 -070022
Winsonc0d70582016-01-29 10:24:39 -080023import android.app.ActivityManager.RunningTaskInfo;
Youngsang Chof1647922015-12-17 13:39:39 -080024import android.app.ActivityManager.StackInfo;
Wale Ogunwale65ebd952018-04-25 15:41:44 -070025import android.app.ActivityTaskManager;
Wale Ogunwale65ebd952018-04-25 15:41:44 -070026import android.app.IActivityTaskManager;
Youngsang Chof1647922015-12-17 13:39:39 -080027import android.content.BroadcastReceiver;
Jaewan Kim62338192016-02-25 10:00:05 -080028import android.content.ComponentName;
Youngsang Chof1647922015-12-17 13:39:39 -080029import android.content.Context;
30import android.content.Intent;
31import android.content.IntentFilter;
Winson Chung2a82fe52017-02-02 14:43:34 -080032import android.content.pm.ParceledListSlice;
Winson Chung0c5a5922017-05-22 17:41:06 -070033import android.content.res.Configuration;
Youngsang Chof1647922015-12-17 13:39:39 -080034import android.content.res.Resources;
35import android.graphics.Rect;
Jaewan Kim62338192016-02-25 10:00:05 -080036import android.media.session.MediaController;
37import android.media.session.MediaSessionManager;
Jaewan Kim8f584b82016-03-22 22:16:59 +090038import android.media.session.PlaybackState;
Wale Ogunwale480dca02016-02-06 13:58:29 -080039import android.os.Debug;
Youngsang Chof1647922015-12-17 13:39:39 -080040import android.os.Handler;
41import android.os.RemoteException;
Kyeongkab.Namd61de152018-10-09 14:55:42 +090042import android.os.UserHandle;
Dongwon Kang353d8d72016-04-13 21:55:22 -070043import android.text.TextUtils;
Youngsang Chof1647922015-12-17 13:39:39 -080044import android.util.Log;
Jaewan Kimd89a6942016-04-08 11:41:45 +090045import android.util.Pair;
Hongwei Wang43a752b2019-09-17 20:20:30 +000046import android.view.DisplayInfo;
Gus Prevasab336792018-11-14 13:52:20 -050047
Winson Chung67f5c8b2018-09-24 12:09:19 -070048import com.android.systemui.Dependency;
Jaewan Kim8f584b82016-03-22 22:16:59 +090049import com.android.systemui.R;
Winson Chung67f5c8b2018-09-24 12:09:19 -070050import com.android.systemui.UiOffloadThread;
Winson Chung29a78652017-02-09 18:35:26 -080051import com.android.systemui.pip.BasePipManager;
Hongwei Wang43a752b2019-09-17 20:20:30 +000052import com.android.systemui.pip.PipBoundsHandler;
Winson Chung2cf6ad82017-11-09 17:36:59 -080053import com.android.systemui.shared.system.ActivityManagerWrapper;
Hongwei Wang43a752b2019-09-17 20:20:30 +000054import com.android.systemui.shared.system.PinnedStackListenerForwarder.PinnedStackListener;
Winson Chung67f5c8b2018-09-24 12:09:19 -070055import com.android.systemui.shared.system.TaskStackChangeListener;
56import com.android.systemui.shared.system.WindowManagerWrapper;
Gus Prevasab336792018-11-14 13:52:20 -050057
Youngsang Chof1647922015-12-17 13:39:39 -080058import java.util.ArrayList;
59import java.util.List;
60
Youngsang Chof1647922015-12-17 13:39:39 -080061/**
62 * Manages the picture-in-picture (PIP) UI and states.
63 */
Winson Chung29a78652017-02-09 18:35:26 -080064public class PipManager implements BasePipManager {
Youngsang Chof1647922015-12-17 13:39:39 -080065 private static final String TAG = "PipManager";
Jaewan Kim26c63562017-04-26 15:41:43 +090066 static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
67
Jaewan Kim8af250e2017-02-14 19:12:01 +090068 private static final String SETTINGS_PACKAGE_AND_CLASS_DELIMITER = "/";
Youngsang Chof1647922015-12-17 13:39:39 -080069
70 private static PipManager sPipManager;
Jaewan Kim8af250e2017-02-14 19:12:01 +090071 private static List<Pair<String, String>> sSettingsPackageAndClassNamePairList;
Jaewan Kimd89a6942016-04-08 11:41:45 +090072
73 /**
Jaewan Kima0d4d252016-03-31 13:37:10 +090074 * State when there's no PIP.
75 */
Wale Ogunwale480dca02016-02-06 13:58:29 -080076 public static final int STATE_NO_PIP = 0;
Jaewan Kima0d4d252016-03-31 13:37:10 +090077 /**
Jaewan Kime6309232017-04-14 15:26:20 +090078 * State when PIP is shown. This is used as default PIP state.
Jaewan Kima0d4d252016-03-31 13:37:10 +090079 */
Jaewan Kime6309232017-04-14 15:26:20 +090080 public static final int STATE_PIP = 1;
Jaewan Kima0d4d252016-03-31 13:37:10 +090081 /**
82 * State when PIP menu dialog is shown.
83 */
Wale Ogunwale480dca02016-02-06 13:58:29 -080084 public static final int STATE_PIP_MENU = 2;
Youngsang Chof1647922015-12-17 13:39:39 -080085
Youngsang Choad8ceb02016-01-15 16:59:27 -080086 private static final int TASK_ID_NO_PIP = -1;
87 private static final int INVALID_RESOURCE_TYPE = -1;
88
Wale Ogunwale480dca02016-02-06 13:58:29 -080089 public static final int SUSPEND_PIP_RESIZE_REASON_WAITING_FOR_MENU_ACTIVITY_FINISH = 0x1;
90 public static final int SUSPEND_PIP_RESIZE_REASON_WAITING_FOR_OVERLAY_ACTIVITY_FINISH = 0x2;
Jaewan Kimf40fcdc2016-03-04 17:58:22 +090091
Jaewan Kim8f584b82016-03-22 22:16:59 +090092 /**
93 * PIPed activity is playing a media and it can be paused.
94 */
95 static final int PLAYBACK_STATE_PLAYING = 0;
96 /**
97 * PIPed activity has a paused media and it can be played.
98 */
99 static final int PLAYBACK_STATE_PAUSED = 1;
100 /**
101 * Users are unable to control PIPed activity's media playback.
102 */
103 static final int PLAYBACK_STATE_UNAVAILABLE = 2;
104
Jaewan Kimf40fcdc2016-03-04 17:58:22 +0900105 private static final int CLOSE_PIP_WHEN_MEDIA_SESSION_GONE_TIMEOUT_MS = 3000;
106
Wale Ogunwale480dca02016-02-06 13:58:29 -0800107 private int mSuspendPipResizingReason;
108
Youngsang Chof1647922015-12-17 13:39:39 -0800109 private Context mContext;
Hongwei Wang43a752b2019-09-17 20:20:30 +0000110 private PipBoundsHandler mPipBoundsHandler;
Wale Ogunwale65ebd952018-04-25 15:41:44 -0700111 private IActivityTaskManager mActivityTaskManager;
Jaewan Kim62338192016-02-25 10:00:05 -0800112 private MediaSessionManager mMediaSessionManager;
Youngsang Chof1647922015-12-17 13:39:39 -0800113 private int mState = STATE_NO_PIP;
Winson Chung0a657372017-05-12 15:04:54 -0700114 private int mResumeResizePinnedStackRunnableState = STATE_NO_PIP;
Youngsang Chof1647922015-12-17 13:39:39 -0800115 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;
Winson Chungdb2ad5d2017-02-07 17:18:01 -0800120 private Rect mDefaultPipBounds = new Rect();
Jaewan Kimd89a6942016-04-08 11:41:45 +0900121 private Rect mSettingsPipBounds;
Jaewan Kimc92a7d12016-02-15 17:33:25 -0800122 private Rect mMenuModePipBounds;
Winson Chung0c5a5922017-05-22 17:41:06 -0700123 private int mLastOrientation = Configuration.ORIENTATION_UNDEFINED;
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;
Wale Ogunwale89be5762017-10-04 13:27:49 -0700126 private int mPinnedStackId = INVALID_STACK_ID;
Jaewan Kim62338192016-02-25 10:00:05 -0800127 private ComponentName mPipComponentName;
128 private MediaController mPipMediaController;
Dongwon Kang353d8d72016-04-13 21:55:22 -0700129 private String[] mLastPackagesResourceGranted;
Jaewan Kim26c63562017-04-26 15:41:43 +0900130 private PipNotification mPipNotification;
Winson Chungb6de8722017-06-02 12:45:51 -0700131 private ParceledListSlice mCustomActions;
Youngsang Choad8ceb02016-01-15 16:59:27 -0800132
Hongwei Wang43a752b2019-09-17 20:20:30 +0000133 // Used to calculate the movement bounds
134 private final DisplayInfo mTmpDisplayInfo = new DisplayInfo();
135 private final Rect mTmpInsetBounds = new Rect();
136 private final Rect mTmpNormalBounds = new Rect();
137
Winson Chung44315c62017-08-24 13:35:24 -0700138 // Keeps track of the IME visibility to adjust the PiP when the IME is visible
139 private boolean mImeVisible;
140 private int mImeHeightAdjustment;
141
Hongwei Wang43a752b2019-09-17 20:20:30 +0000142 private final PinnedStackListener mPinnedStackListener = new PipManagerPinnedStackListener();
Winson Chung2a82fe52017-02-02 14:43:34 -0800143
Wale Ogunwale480dca02016-02-06 13:58:29 -0800144 private final Runnable mResizePinnedStackRunnable = new Runnable() {
145 @Override
146 public void run() {
Winson Chung0a657372017-05-12 15:04:54 -0700147 resizePinnedStack(mResumeResizePinnedStackRunnableState);
Wale Ogunwale480dca02016-02-06 13:58:29 -0800148 }
149 };
Jaewan Kimf40fcdc2016-03-04 17:58:22 +0900150 private final Runnable mClosePipRunnable = new Runnable() {
151 @Override
152 public void run() {
153 closePip();
154 }
155 };
Youngsang Chof1647922015-12-17 13:39:39 -0800156
Youngsang Choad8ceb02016-01-15 16:59:27 -0800157 private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
Youngsang Chof1647922015-12-17 13:39:39 -0800158 @Override
159 public void onReceive(Context context, Intent intent) {
Youngsang Choad8ceb02016-01-15 16:59:27 -0800160 String action = intent.getAction();
Jaewan Kimc552b042016-01-18 16:08:45 +0900161 if (Intent.ACTION_MEDIA_RESOURCE_GRANTED.equals(action)) {
Youngsang Choad8ceb02016-01-15 16:59:27 -0800162 String[] packageNames = intent.getStringArrayExtra(Intent.EXTRA_PACKAGES);
163 int resourceType = intent.getIntExtra(Intent.EXTRA_MEDIA_RESOURCE_TYPE,
164 INVALID_RESOURCE_TYPE);
Dongwon Kang353d8d72016-04-13 21:55:22 -0700165 if (packageNames != null && packageNames.length > 0
Youngsang Choad8ceb02016-01-15 16:59:27 -0800166 && resourceType == Intent.EXTRA_MEDIA_RESOURCE_TYPE_VIDEO_CODEC) {
167 handleMediaResourceGranted(packageNames);
168 }
Youngsang Chof1647922015-12-17 13:39:39 -0800169 }
Youngsang Choad8ceb02016-01-15 16:59:27 -0800170
Youngsang Chof1647922015-12-17 13:39:39 -0800171 }
172 };
Jaewan Kim62338192016-02-25 10:00:05 -0800173 private final MediaSessionManager.OnActiveSessionsChangedListener mActiveMediaSessionListener =
174 new MediaSessionManager.OnActiveSessionsChangedListener() {
175 @Override
176 public void onActiveSessionsChanged(List<MediaController> controllers) {
177 updateMediaController(controllers);
178 }
179 };
Youngsang Chof1647922015-12-17 13:39:39 -0800180
Winson Chung2a82fe52017-02-02 14:43:34 -0800181 /**
182 * Handler for messages from the PIP controller.
183 */
Hongwei Wang43a752b2019-09-17 20:20:30 +0000184 private class PipManagerPinnedStackListener extends PinnedStackListener {
Winson Chung2a82fe52017-02-02 14:43:34 -0800185 @Override
Winson Chung44315c62017-08-24 13:35:24 -0700186 public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
187 if (mState == STATE_PIP) {
188 if (mImeVisible != imeVisible) {
189 if (imeVisible) {
190 // Save the IME height adjustment, and offset to not occlude the IME
191 mPipBounds.offset(0, -imeHeight);
192 mImeHeightAdjustment = imeHeight;
193 } else {
194 // Apply the inverse adjustment when the IME is hidden
195 mPipBounds.offset(0, mImeHeightAdjustment);
196 }
197 mImeVisible = imeVisible;
198 resizePinnedStack(STATE_PIP);
199 }
200 }
201 }
Winson Chung2a82fe52017-02-02 14:43:34 -0800202
203 @Override
Hongwei Wang43a752b2019-09-17 20:20:30 +0000204 public void onMovementBoundsChanged(Rect animatingBounds, boolean fromImeAdjustment,
205 boolean fromShelfAdjustment) {
Winson Chung2a82fe52017-02-02 14:43:34 -0800206 mHandler.post(() -> {
Hongwei Wang43a752b2019-09-17 20:20:30 +0000207 // Populate the inset / normal bounds and DisplayInfo from mPipBoundsHandler first.
208 mPipBoundsHandler.onMovementBoundsChanged(mTmpInsetBounds, mTmpNormalBounds,
209 animatingBounds, mTmpDisplayInfo);
210 mDefaultPipBounds.set(animatingBounds);
Winson Chung2a82fe52017-02-02 14:43:34 -0800211 });
212 }
213
214 @Override
Winson Chungb6de8722017-06-02 12:45:51 -0700215 public void onActionsChanged(ParceledListSlice actions) {
216 mCustomActions = actions;
217 mHandler.post(() -> {
218 for (int i = mListeners.size() - 1; i >= 0; --i) {
219 mListeners.get(i).onPipMenuActionsChanged(mCustomActions);
220 }
221 });
222 }
Winson Chung2a82fe52017-02-02 14:43:34 -0800223 }
224
Youngsang Chof1647922015-12-17 13:39:39 -0800225 private PipManager() { }
226
227 /**
228 * Initializes {@link PipManager}.
229 */
230 public void initialize(Context context) {
231 if (mInitialized) {
232 return;
233 }
234 mInitialized = true;
235 mContext = context;
Hongwei Wang43a752b2019-09-17 20:20:30 +0000236 mPipBoundsHandler = new PipBoundsHandler(context);
Wale Ogunwale65ebd952018-04-25 15:41:44 -0700237 mActivityTaskManager = ActivityTaskManager.getService();
Winson Chung2cf6ad82017-11-09 17:36:59 -0800238 ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
Jaewan Kim73ef3512016-07-18 13:50:33 +0900239 IntentFilter intentFilter = new IntentFilter();
240 intentFilter.addAction(Intent.ACTION_MEDIA_RESOURCE_GRANTED);
Kyeongkab.Namd61de152018-10-09 14:55:42 +0900241 mContext.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, intentFilter,
242 null, null);
Jaewan Kim73ef3512016-07-18 13:50:33 +0900243
Jaewan Kim8af250e2017-02-14 19:12:01 +0900244 if (sSettingsPackageAndClassNamePairList == null) {
245 String[] settings = mContext.getResources().getStringArray(
246 R.array.tv_pip_settings_class_name);
247 sSettingsPackageAndClassNamePairList = new ArrayList<>();
248 if (settings != null) {
249 for (int i = 0; i < settings.length; i++) {
250 Pair<String, String> entry = null;
251 String[] packageAndClassName =
252 settings[i].split(SETTINGS_PACKAGE_AND_CLASS_DELIMITER);
253 switch (packageAndClassName.length) {
254 case 1:
255 entry = Pair.<String, String>create(packageAndClassName[0], null);
256 break;
257 case 2:
Shuichi.Noguchid2067822018-01-13 21:47:04 +0900258 if (packageAndClassName[1] != null) {
259 entry = Pair.<String, String>create(packageAndClassName[0],
260 packageAndClassName[1].startsWith(".")
261 ? packageAndClassName[0] + packageAndClassName[1]
262 : packageAndClassName[1]);
Jaewan Kimdc298752017-02-24 10:45:12 +0900263 }
Shuichi.Noguchid2067822018-01-13 21:47:04 +0900264 break;
Jaewan Kim8af250e2017-02-14 19:12:01 +0900265 }
266 if (entry != null) {
267 sSettingsPackageAndClassNamePairList.add(entry);
Jaewan Kimdc298752017-02-24 10:45:12 +0900268 } else {
269 Log.w(TAG, "Ignoring malformed settings name " + settings[i]);
Jaewan Kim8af250e2017-02-14 19:12:01 +0900270 }
271 }
272 }
273 }
274
Winson Chung8068ec22017-06-02 09:58:00 -0700275 // Initialize the last orientation and apply the current configuration
276 Configuration initialConfig = mContext.getResources().getConfiguration();
277 mLastOrientation = initialConfig.orientation;
278 loadConfigurationsAndApply(initialConfig);
279
Jaewan Kim73ef3512016-07-18 13:50:33 +0900280 mMediaSessionManager =
281 (MediaSessionManager) mContext.getSystemService(Context.MEDIA_SESSION_SERVICE);
Winson Chung2a82fe52017-02-02 14:43:34 -0800282
283 try {
Hongwei Wang43a752b2019-09-17 20:20:30 +0000284 WindowManagerWrapper.getInstance().addPinnedStackListener(mPinnedStackListener);
Winson Chung2a82fe52017-02-02 14:43:34 -0800285 } catch (RemoteException e) {
286 Log.e(TAG, "Failed to register pinned stack listener", e);
287 }
Jaewan Kim26c63562017-04-26 15:41:43 +0900288
289 mPipNotification = new PipNotification(context);
Jaewan Kim73ef3512016-07-18 13:50:33 +0900290 }
291
Winson Chung0c5a5922017-05-22 17:41:06 -0700292 private void loadConfigurationsAndApply(Configuration newConfig) {
293 if (mLastOrientation != newConfig.orientation) {
294 // Don't resize the pinned stack on orientation change. TV does not care about this case
295 // and this could clobber the existing animation to the new bounds calculated by WM.
296 mLastOrientation = newConfig.orientation;
297 return;
298 }
299
Jaewan Kim73ef3512016-07-18 13:50:33 +0900300 Resources res = mContext.getResources();
Jaewan Kimd89a6942016-04-08 11:41:45 +0900301 mSettingsPipBounds = Rect.unflattenFromString(res.getString(
302 R.string.pip_settings_bounds));
Jaewan Kimc92a7d12016-02-15 17:33:25 -0800303 mMenuModePipBounds = Rect.unflattenFromString(res.getString(
Jaewan Kim8f584b82016-03-22 22:16:59 +0900304 R.string.pip_menu_bounds));
Youngsang Chof1647922015-12-17 13:39:39 -0800305
Jaewan Kim73ef3512016-07-18 13:50:33 +0900306 // Reset the PIP bounds and apply. PIP bounds can be changed by two reasons.
307 // 1. Configuration changed due to the language change (RTL <-> RTL)
308 // 2. SystemUI restarts after the crash
309 mPipBounds = isSettingsShown() ? mSettingsPipBounds : mDefaultPipBounds;
Jaewan Kime6309232017-04-14 15:26:20 +0900310 resizePinnedStack(getPinnedStackInfo() == null ? STATE_NO_PIP : STATE_PIP);
Youngsang Chof1647922015-12-17 13:39:39 -0800311 }
312
Jaewan Kimc552b042016-01-18 16:08:45 +0900313 /**
Jaewan Kim2d4d07c2016-05-20 10:28:17 +0900314 * Updates the PIP per configuration changed.
315 */
Winson Chung0c5a5922017-05-22 17:41:06 -0700316 public void onConfigurationChanged(Configuration newConfig) {
317 loadConfigurationsAndApply(newConfig);
Jaewan Kim26c63562017-04-26 15:41:43 +0900318 mPipNotification.onConfigurationChanged(mContext);
Jaewan Kim2d4d07c2016-05-20 10:28:17 +0900319 }
320
321 /**
Jaewan Kimf0fd2182016-04-20 21:17:58 +0900322 * Shows the picture-in-picture menu if an activity is in picture-in-picture mode.
Jaewan Kimc552b042016-01-18 16:08:45 +0900323 */
Winson Chungac52f282017-03-30 14:44:52 -0700324 public void showPictureInPictureMenu() {
Winson Chung0a657372017-05-12 15:04:54 -0700325 if (getState() == STATE_PIP) {
Wale Ogunwale480dca02016-02-06 13:58:29 -0800326 resizePinnedStack(STATE_PIP_MENU);
Jaewan Kimc552b042016-01-18 16:08:45 +0900327 }
328 }
329
Youngsang Chof1647922015-12-17 13:39:39 -0800330 /**
Jaewan Kim977dcdc2016-01-20 19:21:08 +0900331 * Closes PIP (PIPed activity and PIP system UI).
Youngsang Chof1647922015-12-17 13:39:39 -0800332 */
333 public void closePip() {
Youngsang Cho336007b2016-02-22 11:17:29 -0800334 closePipInternal(true);
Youngsang Cho23df6992016-01-26 17:51:33 -0800335 }
336
Youngsang Cho336007b2016-02-22 11:17:29 -0800337 private void closePipInternal(boolean removePipStack) {
Youngsang Chof1647922015-12-17 13:39:39 -0800338 mState = STATE_NO_PIP;
Youngsang Choad8ceb02016-01-15 16:59:27 -0800339 mPipTaskId = TASK_ID_NO_PIP;
Jaewan Kim62338192016-02-25 10:00:05 -0800340 mPipMediaController = null;
341 mMediaSessionManager.removeOnActiveSessionsChangedListener(mActiveMediaSessionListener);
Youngsang Cho23df6992016-01-26 17:51:33 -0800342 if (removePipStack) {
343 try {
Wale Ogunwale04d9cb52018-04-30 13:55:07 -0700344 mActivityTaskManager.removeStack(mPinnedStackId);
Youngsang Cho23df6992016-01-26 17:51:33 -0800345 } catch (RemoteException e) {
346 Log.e(TAG, "removeStack failed", e);
Wale Ogunwale89be5762017-10-04 13:27:49 -0700347 } finally {
348 mPinnedStackId = INVALID_STACK_ID;
Youngsang Cho23df6992016-01-26 17:51:33 -0800349 }
Youngsang Chof1647922015-12-17 13:39:39 -0800350 }
Jaewan Kimc92a7d12016-02-15 17:33:25 -0800351 for (int i = mListeners.size() - 1; i >= 0; --i) {
352 mListeners.get(i).onPipActivityClosed();
353 }
Jaewan Kimf40fcdc2016-03-04 17:58:22 +0900354 mHandler.removeCallbacks(mClosePipRunnable);
Jaewan Kimf0fd2182016-04-20 21:17:58 +0900355 updatePipVisibility(false);
Youngsang Chof1647922015-12-17 13:39:39 -0800356 }
357
358 /**
Jaewan Kim977dcdc2016-01-20 19:21:08 +0900359 * Moves the PIPed activity to the fullscreen and closes PIP system UI.
Youngsang Chof1647922015-12-17 13:39:39 -0800360 */
Jaewan Kima0d4d252016-03-31 13:37:10 +0900361 void movePipToFullscreen() {
Youngsang Choad8ceb02016-01-15 16:59:27 -0800362 mPipTaskId = TASK_ID_NO_PIP;
Jaewan Kimb0033642016-04-22 18:41:37 +0900363 for (int i = mListeners.size() - 1; i >= 0; --i) {
364 mListeners.get(i).onMoveToFullscreen();
365 }
Winson Chungf04c1dd2017-02-24 16:47:37 -0800366 resizePinnedStack(STATE_NO_PIP);
Jaewan Kim47fe2862016-10-10 16:23:28 +0900367 updatePipVisibility(false);
Youngsang Chof1647922015-12-17 13:39:39 -0800368 }
369
370 /**
Wale Ogunwale480dca02016-02-06 13:58:29 -0800371 * Suspends resizing operation on the Pip until {@link #resumePipResizing} is called
372 * @param reason The reason for suspending resizing operations on the Pip.
373 */
374 public void suspendPipResizing(int reason) {
375 if (DEBUG) Log.d(TAG,
376 "suspendPipResizing() reason=" + reason + " callers=" + Debug.getCallers(2));
377 mSuspendPipResizingReason |= reason;
378 }
379
380 /**
381 * Resumes resizing operation on the Pip that was previously suspended.
382 * @param reason The reason resizing operations on the Pip was suspended.
383 */
384 public void resumePipResizing(int reason) {
385 if ((mSuspendPipResizingReason & reason) == 0) {
386 return;
387 }
388 if (DEBUG) Log.d(TAG,
389 "resumePipResizing() reason=" + reason + " callers=" + Debug.getCallers(2));
390 mSuspendPipResizingReason &= ~reason;
391 mHandler.post(mResizePinnedStackRunnable);
392 }
393
394 /**
395 * Resize the Pip to the appropriate size for the input state.
396 * @param state In Pip state also used to determine the new size for the Pip.
397 */
Jaewan Kima0d4d252016-03-31 13:37:10 +0900398 void resizePinnedStack(int state) {
Jaewan Kim26c63562017-04-26 15:41:43 +0900399 if (DEBUG) Log.d(TAG, "resizePinnedStack() state=" + state, new Exception());
Winson Chungf04c1dd2017-02-24 16:47:37 -0800400 boolean wasStateNoPip = (mState == STATE_NO_PIP);
Wale Ogunwale480dca02016-02-06 13:58:29 -0800401 for (int i = mListeners.size() - 1; i >= 0; --i) {
402 mListeners.get(i).onPipResizeAboutToStart();
403 }
Wale Ogunwale480dca02016-02-06 13:58:29 -0800404 if (mSuspendPipResizingReason != 0) {
Winson Chung0a657372017-05-12 15:04:54 -0700405 mResumeResizePinnedStackRunnableState = state;
Winson Chungd62acec2017-03-31 19:42:32 -0700406 if (DEBUG) Log.d(TAG, "resizePinnedStack() deferring"
407 + " mSuspendPipResizingReason=" + mSuspendPipResizingReason
Winson Chung0a657372017-05-12 15:04:54 -0700408 + " mResumeResizePinnedStackRunnableState="
409 + mResumeResizePinnedStackRunnableState);
Wale Ogunwale480dca02016-02-06 13:58:29 -0800410 return;
411 }
Winson Chungd62acec2017-03-31 19:42:32 -0700412 mState = state;
Jaewan Kimc92a7d12016-02-15 17:33:25 -0800413 switch (mState) {
414 case STATE_NO_PIP:
415 mCurrentPipBounds = null;
Winson Chungf04c1dd2017-02-24 16:47:37 -0800416 // If the state was already STATE_NO_PIP, then do not resize the stack below as it
417 // will not exist
418 if (wasStateNoPip) {
419 return;
420 }
Jaewan Kimc92a7d12016-02-15 17:33:25 -0800421 break;
422 case STATE_PIP_MENU:
423 mCurrentPipBounds = mMenuModePipBounds;
424 break;
Jaewan Kime6309232017-04-14 15:26:20 +0900425 case STATE_PIP:
Jaewan Kima0d4d252016-03-31 13:37:10 +0900426 mCurrentPipBounds = mPipBounds;
427 break;
Jaewan Kimc92a7d12016-02-15 17:33:25 -0800428 default:
429 mCurrentPipBounds = mPipBounds;
430 break;
431 }
Winson Chungf04c1dd2017-02-24 16:47:37 -0800432 try {
433 int animationDurationMs = -1;
Evan Roskydbe2ce52019-07-18 11:13:17 -0700434 mActivityTaskManager.animateResizePinnedStack(mPinnedStackId, mCurrentPipBounds,
435 animationDurationMs);
Winson Chungf04c1dd2017-02-24 16:47:37 -0800436 } catch (RemoteException e) {
437 Log.e(TAG, "resizeStack failed", e);
Wale Ogunwale480dca02016-02-06 13:58:29 -0800438 }
439 }
440
441 /**
Winson Chung0a657372017-05-12 15:04:54 -0700442 * @return the current state, or the pending state if the state change was previously suspended.
443 */
444 private int getState() {
445 if (mSuspendPipResizingReason != 0) {
446 return mResumeResizePinnedStackRunnableState;
447 }
448 return mState;
449 }
450
451 /**
Jaewan Kima0d4d252016-03-31 13:37:10 +0900452 * Returns the default PIP bound.
Jaewan Kimc92a7d12016-02-15 17:33:25 -0800453 */
454 public Rect getPipBounds() {
Jaewan Kima0d4d252016-03-31 13:37:10 +0900455 return mPipBounds;
Jaewan Kimc92a7d12016-02-15 17:33:25 -0800456 }
457
458 /**
Youngsang Chof1647922015-12-17 13:39:39 -0800459 * Shows PIP menu UI by launching {@link PipMenuActivity}. It also locates the pinned
Jaewan Kim8f584b82016-03-22 22:16:59 +0900460 * stack to the centered PIP bound {@link R.config_centeredPictureInPictureBounds}.
Youngsang Chof1647922015-12-17 13:39:39 -0800461 */
Wale Ogunwale480dca02016-02-06 13:58:29 -0800462 private void showPipMenu() {
Youngsang Chof1647922015-12-17 13:39:39 -0800463 if (DEBUG) Log.d(TAG, "showPipMenu()");
Youngsang Chof1647922015-12-17 13:39:39 -0800464 mState = STATE_PIP_MENU;
465 for (int i = mListeners.size() - 1; i >= 0; --i) {
466 mListeners.get(i).onShowPipMenu();
467 }
468 Intent intent = new Intent(mContext, PipMenuActivity.class);
469 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Winson Chungb6de8722017-06-02 12:45:51 -0700470 intent.putExtra(PipMenuActivity.EXTRA_CUSTOM_ACTIONS, mCustomActions);
Jaewan Kim1a9dc562016-02-17 13:41:51 -0800471 mContext.startActivity(intent);
Youngsang Chof1647922015-12-17 13:39:39 -0800472 }
473
Jaewan Kima0d4d252016-03-31 13:37:10 +0900474 /**
475 * Adds a {@link Listener} to PipManager.
476 */
Youngsang Chof1647922015-12-17 13:39:39 -0800477 public void addListener(Listener listener) {
478 mListeners.add(listener);
479 }
480
Jaewan Kima0d4d252016-03-31 13:37:10 +0900481 /**
482 * Removes a {@link Listener} from PipManager.
483 */
Youngsang Chof1647922015-12-17 13:39:39 -0800484 public void removeListener(Listener listener) {
485 mListeners.remove(listener);
486 }
487
Jaewan Kima0d4d252016-03-31 13:37:10 +0900488 /**
489 * Adds a {@link MediaListener} to PipManager.
490 */
491 public void addMediaListener(MediaListener listener) {
492 mMediaListeners.add(listener);
493 }
494
495 /**
496 * Removes a {@link MediaListener} from PipManager.
497 */
498 public void removeMediaListener(MediaListener listener) {
499 mMediaListeners.remove(listener);
500 }
501
Jaewan Kimc92a7d12016-02-15 17:33:25 -0800502 /**
503 * Returns {@code true} if PIP is shown.
504 */
505 public boolean isPipShown() {
Jaewan Kim82ac50d2016-03-21 17:34:28 +0900506 return mState != STATE_NO_PIP;
Youngsang Chof1647922015-12-17 13:39:39 -0800507 }
508
Jaewan Kim73ef3512016-07-18 13:50:33 +0900509 private StackInfo getPinnedStackInfo() {
510 StackInfo stackInfo = null;
511 try {
Wale Ogunwale04d9cb52018-04-30 13:55:07 -0700512 stackInfo = ActivityTaskManager.getService().getStackInfo(
Wale Ogunwale68278562017-09-23 17:13:55 -0700513 WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED);
Jaewan Kim73ef3512016-07-18 13:50:33 +0900514 } catch (RemoteException e) {
515 Log.e(TAG, "getStackInfo failed", e);
516 }
517 return stackInfo;
518 }
519
Youngsang Choad8ceb02016-01-15 16:59:27 -0800520 private void handleMediaResourceGranted(String[] packageNames) {
Winson Chung0a657372017-05-12 15:04:54 -0700521 if (getState() == STATE_NO_PIP) {
Dongwon Kang353d8d72016-04-13 21:55:22 -0700522 mLastPackagesResourceGranted = packageNames;
523 } else {
524 boolean requestedFromLastPackages = false;
525 if (mLastPackagesResourceGranted != null) {
526 for (String packageName : mLastPackagesResourceGranted) {
527 for (String newPackageName : packageNames) {
528 if (TextUtils.equals(newPackageName, packageName)) {
529 requestedFromLastPackages = true;
530 break;
531 }
Youngsang Choad8ceb02016-01-15 16:59:27 -0800532 }
533 }
534 }
Dongwon Kang353d8d72016-04-13 21:55:22 -0700535 mLastPackagesResourceGranted = packageNames;
536 if (!requestedFromLastPackages) {
537 closePip();
538 }
Youngsang Choad8ceb02016-01-15 16:59:27 -0800539 }
540 }
541
Jaewan Kim62338192016-02-25 10:00:05 -0800542 private void updateMediaController(List<MediaController> controllers) {
543 MediaController mediaController = null;
Winson Chung0a657372017-05-12 15:04:54 -0700544 if (controllers != null && getState() != STATE_NO_PIP && mPipComponentName != null) {
Jaewan Kim62338192016-02-25 10:00:05 -0800545 for (int i = controllers.size() - 1; i >= 0; i--) {
546 MediaController controller = controllers.get(i);
547 // We assumes that an app with PIPable activity
548 // keeps the single instance of media controller especially when PIP is on.
549 if (controller.getPackageName().equals(mPipComponentName.getPackageName())) {
550 mediaController = controller;
551 break;
552 }
553 }
554 }
555 if (mPipMediaController != mediaController) {
556 mPipMediaController = mediaController;
Jaewan Kima0d4d252016-03-31 13:37:10 +0900557 for (int i = mMediaListeners.size() - 1; i >= 0; i--) {
558 mMediaListeners.get(i).onMediaControllerChanged();
Jaewan Kim62338192016-02-25 10:00:05 -0800559 }
Jaewan Kimf40fcdc2016-03-04 17:58:22 +0900560 if (mPipMediaController == null) {
561 mHandler.postDelayed(mClosePipRunnable,
562 CLOSE_PIP_WHEN_MEDIA_SESSION_GONE_TIMEOUT_MS);
563 } else {
564 mHandler.removeCallbacks(mClosePipRunnable);
565 }
Jaewan Kim62338192016-02-25 10:00:05 -0800566 }
567 }
568
569 /**
570 * Gets the {@link android.media.session.MediaController} for the PIPed activity.
571 */
572 MediaController getMediaController() {
573 return mPipMediaController;
574 }
575
Jaewan Kim8f584b82016-03-22 22:16:59 +0900576 /**
577 * Returns the PIPed activity's playback state.
Jaewan Kim26c63562017-04-26 15:41:43 +0900578 * This returns one of {@link #PLAYBACK_STATE_PLAYING}, {@link #PLAYBACK_STATE_PAUSED},
579 * or {@link #PLAYBACK_STATE_UNAVAILABLE}.
Jaewan Kim8f584b82016-03-22 22:16:59 +0900580 */
581 int getPlaybackState() {
582 if (mPipMediaController == null || mPipMediaController.getPlaybackState() == null) {
583 return PLAYBACK_STATE_UNAVAILABLE;
584 }
585 int state = mPipMediaController.getPlaybackState().getState();
586 boolean isPlaying = (state == PlaybackState.STATE_BUFFERING
587 || state == PlaybackState.STATE_CONNECTING
588 || state == PlaybackState.STATE_PLAYING
589 || state == PlaybackState.STATE_FAST_FORWARDING
590 || state == PlaybackState.STATE_REWINDING
591 || state == PlaybackState.STATE_SKIPPING_TO_PREVIOUS
592 || state == PlaybackState.STATE_SKIPPING_TO_NEXT);
593 long actions = mPipMediaController.getPlaybackState().getActions();
594 if (!isPlaying && ((actions & PlaybackState.ACTION_PLAY) != 0)) {
595 return PLAYBACK_STATE_PAUSED;
596 } else if (isPlaying && ((actions & PlaybackState.ACTION_PAUSE) != 0)) {
597 return PLAYBACK_STATE_PLAYING;
598 }
599 return PLAYBACK_STATE_UNAVAILABLE;
600 }
601
Jaewan Kim73ef3512016-07-18 13:50:33 +0900602 private boolean isSettingsShown() {
603 List<RunningTaskInfo> runningTasks;
604 try {
Wale Ogunwale04d9cb52018-04-30 13:55:07 -0700605 runningTasks = mActivityTaskManager.getTasks(1);
Winson Chung61c9e5a2017-10-11 10:39:32 -0700606 if (runningTasks.isEmpty()) {
Jaewan Kim73ef3512016-07-18 13:50:33 +0900607 return false;
608 }
609 } catch (RemoteException e) {
610 Log.d(TAG, "Failed to detect top activity", e);
611 return false;
612 }
613 ComponentName topActivity = runningTasks.get(0).topActivity;
Jaewan Kimd89a6942016-04-08 11:41:45 +0900614 for (Pair<String, String> componentName : sSettingsPackageAndClassNamePairList) {
615 String packageName = componentName.first;
Andrii Kulian8290f8f2016-06-30 17:51:32 -0700616 if (topActivity.getPackageName().equals(packageName)) {
Jaewan Kimd89a6942016-04-08 11:41:45 +0900617 String className = componentName.second;
618 if (className == null || topActivity.getClassName().equals(className)) {
619 return true;
620 }
621 }
622 }
623 return false;
624 }
625
Winson Chung67f5c8b2018-09-24 12:09:19 -0700626 private TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() {
Youngsang Chof1647922015-12-17 13:39:39 -0800627 @Override
Jaewan Kim938a50b2016-03-14 17:35:43 +0900628 public void onTaskStackChanged() {
Jaewan Kim17ca4e32016-10-31 13:55:43 +0900629 if (DEBUG) Log.d(TAG, "onTaskStackChanged()");
Shuichi.Noguchie9d2dd62017-11-22 17:24:17 +0900630
Winson Chung0a657372017-05-12 15:04:54 -0700631 if (getState() != STATE_NO_PIP) {
Jaewan Kimd89a6942016-04-08 11:41:45 +0900632 boolean hasPip = false;
633
Jaewan Kim73ef3512016-07-18 13:50:33 +0900634 StackInfo stackInfo = getPinnedStackInfo();
635 if (stackInfo == null || stackInfo.taskIds == null) {
636 Log.w(TAG, "There is nothing in pinned stack");
637 closePipInternal(false);
Jaewan Kim938a50b2016-03-14 17:35:43 +0900638 return;
639 }
640 for (int i = stackInfo.taskIds.length - 1; i >= 0; --i) {
641 if (stackInfo.taskIds[i] == mPipTaskId) {
642 // PIP task is still alive.
Jaewan Kimd89a6942016-04-08 11:41:45 +0900643 hasPip = true;
644 break;
Jaewan Kim938a50b2016-03-14 17:35:43 +0900645 }
646 }
Jaewan Kimd89a6942016-04-08 11:41:45 +0900647 if (!hasPip) {
648 // PIP task doesn't exist anymore in PINNED_STACK.
649 closePipInternal(true);
650 return;
651 }
652 }
Winson Chung0a657372017-05-12 15:04:54 -0700653 if (getState() == STATE_PIP) {
Jaewan Kim73ef3512016-07-18 13:50:33 +0900654 Rect bounds = isSettingsShown() ? mSettingsPipBounds : mDefaultPipBounds;
655 if (mPipBounds != bounds) {
656 mPipBounds = bounds;
Jaewan Kime6309232017-04-14 15:26:20 +0900657 resizePinnedStack(STATE_PIP);
Jaewan Kimd89a6942016-04-08 11:41:45 +0900658 }
Jaewan Kim938a50b2016-03-14 17:35:43 +0900659 }
Youngsang Chof1647922015-12-17 13:39:39 -0800660 }
661
662 @Override
Wale Ogunwale89be5762017-10-04 13:27:49 -0700663 public void onActivityPinned(String packageName, int userId, int taskId, int stackId) {
Wale Ogunwale480dca02016-02-06 13:58:29 -0800664 if (DEBUG) Log.d(TAG, "onActivityPinned()");
Shuichi.Noguchie9d2dd62017-11-22 17:24:17 +0900665
Jaewan Kim73ef3512016-07-18 13:50:33 +0900666 StackInfo stackInfo = getPinnedStackInfo();
667 if (stackInfo == null) {
668 Log.w(TAG, "Cannot find pinned stack");
Jaewan Kim938a50b2016-03-14 17:35:43 +0900669 return;
670 }
671 if (DEBUG) Log.d(TAG, "PINNED_STACK:" + stackInfo);
Wale Ogunwale89be5762017-10-04 13:27:49 -0700672 mPinnedStackId = stackInfo.stackId;
Jaewan Kim938a50b2016-03-14 17:35:43 +0900673 mPipTaskId = stackInfo.taskIds[stackInfo.taskIds.length - 1];
674 mPipComponentName = ComponentName.unflattenFromString(
675 stackInfo.taskNames[stackInfo.taskNames.length - 1]);
Jaewan Kime6309232017-04-14 15:26:20 +0900676 // Set state to STATE_PIP so we show it when the pinned stack animation ends.
677 mState = STATE_PIP;
Jaewan Kim938a50b2016-03-14 17:35:43 +0900678 mCurrentPipBounds = mPipBounds;
Jaewan Kim938a50b2016-03-14 17:35:43 +0900679 mMediaSessionManager.addOnActiveSessionsChangedListener(
680 mActiveMediaSessionListener, null);
681 updateMediaController(mMediaSessionManager.getActiveSessions(null));
Jaewan Kim938a50b2016-03-14 17:35:43 +0900682 for (int i = mListeners.size() - 1; i >= 0; i--) {
683 mListeners.get(i).onPipEntered();
684 }
Jaewan Kimf0fd2182016-04-20 21:17:58 +0900685 updatePipVisibility(true);
Youngsang Chof1647922015-12-17 13:39:39 -0800686 }
Wale Ogunwalecc25a8a2016-01-23 14:31:37 -0800687
688 @Override
Winson Chunge6385a22017-05-02 18:15:16 -0700689 public void onPinnedActivityRestartAttempt(boolean clearedTask) {
Wale Ogunwale480dca02016-02-06 13:58:29 -0800690 if (DEBUG) Log.d(TAG, "onPinnedActivityRestartAttempt()");
Shuichi.Noguchie9d2dd62017-11-22 17:24:17 +0900691
Jaewan Kim938a50b2016-03-14 17:35:43 +0900692 // If PIPed activity is launched again by Launcher or intent, make it fullscreen.
693 movePipToFullscreen();
Wale Ogunwalecc25a8a2016-01-23 14:31:37 -0800694 }
Wale Ogunwale480dca02016-02-06 13:58:29 -0800695
696 @Override
697 public void onPinnedStackAnimationEnded() {
698 if (DEBUG) Log.d(TAG, "onPinnedStackAnimationEnded()");
Shuichi.Noguchie9d2dd62017-11-22 17:24:17 +0900699
Winson Chung0a657372017-05-12 15:04:54 -0700700 switch (getState()) {
Jaewan Kim938a50b2016-03-14 17:35:43 +0900701 case STATE_PIP_MENU:
702 showPipMenu();
703 break;
704 }
Wale Ogunwale480dca02016-02-06 13:58:29 -0800705 }
Jaewan Kim938a50b2016-03-14 17:35:43 +0900706 };
Youngsang Chof1647922015-12-17 13:39:39 -0800707
708 /**
709 * A listener interface to receive notification on changes in PIP.
710 */
711 public interface Listener {
Jaewan Kim82ac50d2016-03-21 17:34:28 +0900712 /**
713 * Invoked when an activity is pinned and PIP manager is set corresponding information.
714 * Classes must use this instead of {@link android.app.ITaskStackListener.onActivityPinned}
715 * because there's no guarantee for the PIP manager be return relavent information
Jaewan Kima0d4d252016-03-31 13:37:10 +0900716 * correctly. (e.g. {@link isPipShown}).
Jaewan Kim82ac50d2016-03-21 17:34:28 +0900717 */
718 void onPipEntered();
Wale Ogunwale480dca02016-02-06 13:58:29 -0800719 /** Invoked when a PIPed activity is closed. */
Youngsang Chof1647922015-12-17 13:39:39 -0800720 void onPipActivityClosed();
Wale Ogunwale480dca02016-02-06 13:58:29 -0800721 /** Invoked when the PIP menu gets shown. */
Youngsang Chof1647922015-12-17 13:39:39 -0800722 void onShowPipMenu();
Winson Chungb6de8722017-06-02 12:45:51 -0700723 /** Invoked when the PIP menu actions change. */
724 void onPipMenuActionsChanged(ParceledListSlice actions);
Jaewan Kim10a86912016-04-04 16:01:51 +0900725 /** Invoked when the PIPed activity is about to return back to the fullscreen. */
Youngsang Chof1647922015-12-17 13:39:39 -0800726 void onMoveToFullscreen();
Wale Ogunwale480dca02016-02-06 13:58:29 -0800727 /** Invoked when we are above to start resizing the Pip. */
728 void onPipResizeAboutToStart();
Jaewan Kima0d4d252016-03-31 13:37:10 +0900729 }
730
731 /**
732 * A listener interface to receive change in PIP's media controller
733 */
734 public interface MediaListener {
Jaewan Kim62338192016-02-25 10:00:05 -0800735 /** Invoked when the MediaController on PIPed activity is changed. */
736 void onMediaControllerChanged();
Youngsang Chof1647922015-12-17 13:39:39 -0800737 }
738
739 /**
740 * Gets an instance of {@link PipManager}.
741 */
742 public static PipManager getInstance() {
743 if (sPipManager == null) {
744 sPipManager = new PipManager();
745 }
746 return sPipManager;
747 }
Jaewan Kima0d4d252016-03-31 13:37:10 +0900748
Winsonab216602016-08-09 14:05:20 -0700749 private void updatePipVisibility(final boolean visible) {
Winson Chung67f5c8b2018-09-24 12:09:19 -0700750 Dependency.get(UiOffloadThread.class).submit(() -> {
751 WindowManagerWrapper.getInstance().setPipVisibility(visible);
752 });
Jaewan Kimf0fd2182016-04-20 21:17:58 +0900753 }
Youngsang Chof1647922015-12-17 13:39:39 -0800754}