PipMenuActivityController.java revision 81d406104a1661658eba8755de59bf1df575e4c7
1/* 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.pip.phone; 18 19import static android.app.ActivityManager.StackId.PINNED_STACK_ID; 20 21import android.app.ActivityManager.StackInfo; 22import android.app.ActivityOptions; 23import android.app.IActivityManager; 24import android.app.RemoteAction; 25import android.content.Context; 26import android.content.Intent; 27import android.content.pm.ParceledListSlice; 28import android.graphics.Rect; 29import android.os.Handler; 30import android.os.Message; 31import android.os.Messenger; 32import android.os.RemoteException; 33import android.os.UserHandle; 34import android.util.Log; 35import android.util.Pair; 36import android.view.IWindowManager; 37 38import com.android.systemui.pip.phone.PipMediaController.ActionListener; 39 40import java.io.PrintWriter; 41import java.util.ArrayList; 42import java.util.List; 43 44/** 45 * Manages the PiP menu activity which can show menu options or a scrim. 46 * 47 * The current media session provides actions whenever there are no valid actions provided by the 48 * current PiP activity. Otherwise, those actions always take precedence. 49 */ 50public class PipMenuActivityController { 51 52 private static final String TAG = "PipMenuActController"; 53 54 public static final String EXTRA_CONTROLLER_MESSENGER = "messenger"; 55 public static final String EXTRA_ACTIONS = "actions"; 56 public static final String EXTRA_STACK_BOUNDS = "stack_bounds"; 57 public static final String EXTRA_MOVEMENT_BOUNDS = "movement_bounds"; 58 public static final String EXTRA_SHOW_MENU = "show_menu"; 59 60 public static final int MESSAGE_MENU_VISIBILITY_CHANGED = 100; 61 public static final int MESSAGE_EXPAND_PIP = 101; 62 public static final int MESSAGE_MINIMIZE_PIP = 102; 63 public static final int MESSAGE_DISMISS_PIP = 103; 64 public static final int MESSAGE_UPDATE_ACTIVITY_CALLBACK = 104; 65 public static final int MESSAGE_REGISTER_INPUT_CONSUMER = 105; 66 67 /** 68 * A listener interface to receive notification on changes in PIP. 69 */ 70 public interface Listener { 71 /** 72 * Called when the PIP menu visibility changes. 73 * 74 * @param menuVisible whether or not the menu is visible 75 * @param resize whether or not to resize the PiP with the visibility change 76 */ 77 void onPipMenuVisibilityChanged(boolean menuVisible, boolean resize); 78 79 /** 80 * Called when the PIP requested to be expanded. 81 */ 82 void onPipExpand(); 83 84 /** 85 * Called when the PIP requested to be minimized. 86 */ 87 void onPipMinimize(); 88 89 /** 90 * Called when the PIP requested to be dismissed. 91 */ 92 void onPipDismiss(); 93 } 94 95 private Context mContext; 96 private IActivityManager mActivityManager; 97 private PipMediaController mMediaController; 98 private InputConsumerController mInputConsumerController; 99 100 private ArrayList<Listener> mListeners = new ArrayList<>(); 101 private ParceledListSlice mAppActions; 102 private ParceledListSlice mMediaActions; 103 private boolean mMenuVisible; 104 105 private boolean mStartActivityRequested; 106 private Messenger mToActivityMessenger; 107 private Messenger mMessenger = new Messenger(new Handler() { 108 @Override 109 public void handleMessage(Message msg) { 110 switch (msg.what) { 111 case MESSAGE_MENU_VISIBILITY_CHANGED: { 112 boolean visible = msg.arg1 > 0; 113 onMenuVisibilityChanged(visible, true /* resize */); 114 break; 115 } 116 case MESSAGE_EXPAND_PIP: { 117 mListeners.forEach(l -> l.onPipExpand()); 118 // Preemptively mark the menu as invisible once we expand the PiP, but don't 119 // resize as we will be animating the stack 120 onMenuVisibilityChanged(false, false /* resize */); 121 break; 122 } 123 case MESSAGE_MINIMIZE_PIP: { 124 mListeners.forEach(l -> l.onPipMinimize()); 125 break; 126 } 127 case MESSAGE_DISMISS_PIP: { 128 mListeners.forEach(l -> l.onPipDismiss()); 129 // Preemptively mark the menu as invisible once we dismiss the PiP, but don't 130 // resize as we'll be removing the stack in place 131 onMenuVisibilityChanged(false, false /* resize */); 132 break; 133 } 134 case MESSAGE_REGISTER_INPUT_CONSUMER: { 135 mInputConsumerController.registerInputConsumer(); 136 break; 137 } 138 case MESSAGE_UPDATE_ACTIVITY_CALLBACK: { 139 mToActivityMessenger = msg.replyTo; 140 mStartActivityRequested = false; 141 // Mark the menu as invisible once the activity finishes as well 142 if (mToActivityMessenger == null) { 143 onMenuVisibilityChanged(false, true /* resize */); 144 } 145 break; 146 } 147 } 148 } 149 }); 150 151 private ActionListener mMediaActionListener = new ActionListener() { 152 @Override 153 public void onMediaActionsChanged(List<RemoteAction> mediaActions) { 154 mMediaActions = new ParceledListSlice<>(mediaActions); 155 updateMenuActions(); 156 } 157 }; 158 159 public PipMenuActivityController(Context context, IActivityManager activityManager, 160 PipMediaController mediaController, InputConsumerController inputConsumerController) { 161 mContext = context; 162 mActivityManager = activityManager; 163 mMediaController = mediaController; 164 mInputConsumerController = inputConsumerController; 165 } 166 167 public void onActivityPinned() { 168 if (!mMenuVisible) { 169 // If the menu is not visible, then re-register the input consumer if it is not already 170 // registered 171 mInputConsumerController.registerInputConsumer(); 172 } 173 } 174 175 /** 176 * Adds a new menu activity listener. 177 */ 178 public void addListener(Listener listener) { 179 if (!mListeners.contains(listener)) { 180 mListeners.add(listener); 181 } 182 } 183 184 /** 185 * Updates the appearance of the menu and scrim on top of the PiP while dismissing. 186 */ 187 public void setDismissFraction(float fraction) { 188 if (mToActivityMessenger != null) { 189 Message m = Message.obtain(); 190 m.what = PipMenuActivity.MESSAGE_UPDATE_DISMISS_FRACTION; 191 m.obj = fraction; 192 try { 193 mToActivityMessenger.send(m); 194 } catch (RemoteException e) { 195 Log.e(TAG, "Could not notify menu to show", e); 196 } 197 } else if (!mStartActivityRequested) { 198 startMenuActivity(null /* stackBounds */, null /* movementBounds */, 199 false /* showMenu */); 200 } 201 } 202 203 /** 204 * Shows the menu activity. 205 */ 206 public void showMenu(Rect stackBounds, Rect movementBounds) { 207 if (mToActivityMessenger != null) { 208 Message m = Message.obtain(); 209 m.what = PipMenuActivity.MESSAGE_SHOW_MENU; 210 m.obj = new Pair<>(stackBounds, movementBounds); 211 try { 212 mToActivityMessenger.send(m); 213 } catch (RemoteException e) { 214 Log.e(TAG, "Could not notify menu to show", e); 215 } 216 } else if (!mStartActivityRequested) { 217 startMenuActivity(stackBounds, movementBounds, true /* showMenu */); 218 } 219 } 220 221 /** 222 * Pokes the menu, indicating that the user is interacting with it. 223 */ 224 public void pokeMenu() { 225 if (mToActivityMessenger != null) { 226 Message m = Message.obtain(); 227 m.what = PipMenuActivity.MESSAGE_POKE_MENU; 228 try { 229 mToActivityMessenger.send(m); 230 } catch (RemoteException e) { 231 Log.e(TAG, "Could not notify poke menu", e); 232 } 233 } 234 } 235 236 /** 237 * Hides the menu activity. 238 */ 239 public void hideMenu() { 240 if (mToActivityMessenger != null) { 241 Message m = Message.obtain(); 242 m.what = PipMenuActivity.MESSAGE_HIDE_MENU; 243 try { 244 mToActivityMessenger.send(m); 245 } catch (RemoteException e) { 246 Log.e(TAG, "Could not notify menu to hide", e); 247 } 248 } 249 } 250 251 /** 252 * @return whether the menu is currently visible. 253 */ 254 public boolean isMenuVisible() { 255 return mMenuVisible; 256 } 257 258 /** 259 * Sets the menu actions to the actions provided by the current PiP activity. 260 */ 261 public void setAppActions(ParceledListSlice appActions) { 262 mAppActions = appActions; 263 updateMenuActions(); 264 } 265 266 /** 267 * @return the best set of actions to show in the PiP menu. 268 */ 269 private ParceledListSlice resolveMenuActions() { 270 if (isValidActions(mAppActions)) { 271 return mAppActions; 272 } 273 return mMediaActions; 274 } 275 276 /** 277 * Starts the menu activity on the top task of the pinned stack. 278 */ 279 private void startMenuActivity(Rect stackBounds, Rect movementBounds, boolean showMenu) { 280 try { 281 StackInfo pinnedStackInfo = mActivityManager.getStackInfo(PINNED_STACK_ID); 282 if (pinnedStackInfo != null && pinnedStackInfo.taskIds != null && 283 pinnedStackInfo.taskIds.length > 0) { 284 Intent intent = new Intent(mContext, PipMenuActivity.class); 285 intent.putExtra(EXTRA_CONTROLLER_MESSENGER, mMessenger); 286 intent.putExtra(EXTRA_ACTIONS, resolveMenuActions()); 287 if (stackBounds != null) { 288 intent.putExtra(EXTRA_STACK_BOUNDS, stackBounds.flattenToString()); 289 } 290 if (movementBounds != null) { 291 intent.putExtra(EXTRA_MOVEMENT_BOUNDS, movementBounds.flattenToString()); 292 } 293 intent.putExtra(EXTRA_SHOW_MENU, showMenu); 294 ActivityOptions options = ActivityOptions.makeCustomAnimation(mContext, 0, 0); 295 options.setLaunchTaskId( 296 pinnedStackInfo.taskIds[pinnedStackInfo.taskIds.length - 1]); 297 options.setTaskOverlay(true, true /* canResume */); 298 mContext.startActivityAsUser(intent, options.toBundle(), UserHandle.CURRENT); 299 mStartActivityRequested = true; 300 } else { 301 Log.e(TAG, "No PIP tasks found"); 302 } 303 } catch (RemoteException e) { 304 mStartActivityRequested = false; 305 Log.e(TAG, "Error showing PIP menu activity", e); 306 } 307 } 308 309 /** 310 * Updates the PiP menu activity with the best set of actions provided. 311 */ 312 private void updateMenuActions() { 313 if (mToActivityMessenger != null) { 314 // Fetch the pinned stack bounds 315 Rect stackBounds = null; 316 try { 317 StackInfo pinnedStackInfo = mActivityManager.getStackInfo(PINNED_STACK_ID); 318 if (pinnedStackInfo != null) { 319 stackBounds = pinnedStackInfo.bounds; 320 } 321 } catch (RemoteException e) { 322 Log.e(TAG, "Error showing PIP menu activity", e); 323 } 324 325 Message m = Message.obtain(); 326 m.what = PipMenuActivity.MESSAGE_UPDATE_ACTIONS; 327 m.obj = new Pair<>(stackBounds, resolveMenuActions()); 328 try { 329 mToActivityMessenger.send(m); 330 } catch (RemoteException e) { 331 Log.e(TAG, "Could not notify menu activity to update actions", e); 332 } 333 } 334 } 335 336 /** 337 * Returns whether the set of actions are valid. 338 */ 339 private boolean isValidActions(ParceledListSlice actions) { 340 return actions != null && actions.getList().size() > 0; 341 } 342 343 /** 344 * Handles changes in menu visibility. 345 */ 346 private void onMenuVisibilityChanged(boolean visible, boolean resize) { 347 if (visible) { 348 mInputConsumerController.unregisterInputConsumer(); 349 } else { 350 mInputConsumerController.registerInputConsumer(); 351 } 352 if (visible != mMenuVisible) { 353 mListeners.forEach(l -> l.onPipMenuVisibilityChanged(visible, resize)); 354 if (visible) { 355 // Once visible, start listening for media action changes. This call will trigger 356 // the menu actions to be updated again. 357 mMediaController.addListener(mMediaActionListener); 358 } else { 359 // Once hidden, stop listening for media action changes. This call will trigger 360 // the menu actions to be updated again. 361 mMediaController.removeListener(mMediaActionListener); 362 } 363 } 364 mMenuVisible = visible; 365 } 366 367 public void dump(PrintWriter pw, String prefix) { 368 final String innerPrefix = prefix + " "; 369 pw.println(prefix + TAG); 370 pw.println(innerPrefix + "mMenuVisible=" + mMenuVisible); 371 pw.println(innerPrefix + "mListeners=" + mListeners.size()); 372 } 373} 374