From abaefa0c671d0a2f23e4ad4dfcdf080f6020d19f Mon Sep 17 00:00:00 2001 From: Ben Lin Date: Thu, 6 Aug 2020 18:16:25 -0700 Subject: Migrate from PipMenuActivity -> PipMenuView. Bug: 161710689 Test: Build and run Change-Id: I879502085daefd3dbfbe437410a29c3353c5cf1a --- libs/WindowManager/Shell/res/layout/pip_menu.xml | 100 +++ .../Shell/res/layout/pip_menu_activity.xml | 100 --- .../android/systemui/wmshell/CarWMShellModule.java | 10 - packages/SystemUI/AndroidManifest.xml | 14 - .../com/android/systemui/pip/PipTaskOrganizer.java | 53 ++ .../com/android/systemui/pip/phone/PipManager.java | 6 +- .../systemui/pip/phone/PipMenuActivity.java | 730 --------------------- .../pip/phone/PipMenuActivityController.java | 375 +++-------- .../android/systemui/pip/phone/PipMenuView.java | 497 ++++++++++++++ .../systemui/pip/phone/PipMotionHelper.java | 14 +- .../pip/phone/PipResizeGestureHandler.java | 9 +- .../systemui/pip/phone/PipTouchHandler.java | 9 +- .../pip/phone/dagger/PipMenuActivityClass.java | 30 - .../android/systemui/wmshell/WMShellModule.java | 10 - 14 files changed, 755 insertions(+), 1202 deletions(-) create mode 100644 libs/WindowManager/Shell/res/layout/pip_menu.xml delete mode 100644 libs/WindowManager/Shell/res/layout/pip_menu_activity.xml delete mode 100644 packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java create mode 100644 packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuView.java delete mode 100644 packages/SystemUI/src/com/android/systemui/pip/phone/dagger/PipMenuActivityClass.java diff --git a/libs/WindowManager/Shell/res/layout/pip_menu.xml b/libs/WindowManager/Shell/res/layout/pip_menu.xml new file mode 100644 index 000000000000..2e0a5e09e34f --- /dev/null +++ b/libs/WindowManager/Shell/res/layout/pip_menu.xml @@ -0,0 +1,100 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/libs/WindowManager/Shell/res/layout/pip_menu_activity.xml b/libs/WindowManager/Shell/res/layout/pip_menu_activity.xml deleted file mode 100644 index 2e0a5e09e34f..000000000000 --- a/libs/WindowManager/Shell/res/layout/pip_menu_activity.xml +++ /dev/null @@ -1,100 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/packages/CarSystemUI/src/com/android/systemui/wmshell/CarWMShellModule.java b/packages/CarSystemUI/src/com/android/systemui/wmshell/CarWMShellModule.java index fd6685ffdb8f..6d31a8d69ebe 100644 --- a/packages/CarSystemUI/src/com/android/systemui/wmshell/CarWMShellModule.java +++ b/packages/CarSystemUI/src/com/android/systemui/wmshell/CarWMShellModule.java @@ -22,8 +22,6 @@ import android.view.IWindowManager; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.pip.phone.PipMenuActivity; -import com.android.systemui.pip.phone.dagger.PipMenuActivityClass; import com.android.systemui.wm.DisplaySystemBarsController; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayImeController; @@ -43,12 +41,4 @@ public class CarWMShellModule { return new DisplaySystemBarsController(context, wmService, displayController, mainHandler, transactionPool); } - - /** TODO(b/150319024): PipMenuActivity will move to a Window */ - @SysUISingleton - @PipMenuActivityClass - @Provides - Class providePipMenuActivityClass() { - return PipMenuActivity.class; - } } diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index af008b996172..7f4f580abf94 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -530,20 +530,6 @@ androidprv:alwaysFocusable="true" android:excludeFromRecents="true" /> - - mInitialState = new HashMap<>(); private final Optional mSplitScreenOptional; protected final ShellTaskOrganizer mTaskOrganizer; + private SurfaceControlViewHost mPipViewHost; + private SurfaceControl mPipMenuSurface; // These callbacks are called on the update thread private final PipAnimationController.PipAnimationCallback mPipAnimationCallback = @@ -212,6 +219,7 @@ public class PipTaskOrganizer extends TaskOrganizer implements ShellTaskOrganize @NonNull DisplayController displayController, @NonNull PipUiEventLogger pipUiEventLogger, @NonNull ShellTaskOrganizer shellTaskOrganizer) { + mContext = context; mMainHandler = new Handler(Looper.getMainLooper()); mUpdateHandler = new Handler(PipUpdateThread.get().getLooper(), mUpdateCallbacks); mPipBoundsHandler = boundsHandler; @@ -503,6 +511,45 @@ public class PipTaskOrganizer extends TaskOrganizer implements ShellTaskOrganize } } + /** + * Setup the ViewHost and attach the provided menu view to the ViewHost. + */ + public void attachPipMenuViewHost(View menuView, WindowManager.LayoutParams lp) { + if (mPipMenuSurface != null) { + Log.e(TAG, "PIP Menu View already created and attached."); + return; + } + + if (Looper.getMainLooper() != Looper.myLooper()) { + throw new RuntimeException("PipMenuView needs to be attached on the main thread."); + } + + mPipViewHost = new SurfaceControlViewHost(mContext, mContext.getDisplay(), + (android.os.Binder) null); + mPipMenuSurface = mPipViewHost.getSurfacePackage().getSurfaceControl(); + SurfaceControl.Transaction transaction = new SurfaceControl.Transaction(); + transaction.reparent(mPipMenuSurface, mLeash); + transaction.show(mPipMenuSurface); + transaction.setRelativeLayer(mPipMenuSurface, mLeash, 1); + transaction.apply(); + mPipViewHost.setView(menuView, lp); + } + + + /** + * Releases the PIP Menu's View host, remove it from PIP task surface. + */ + public void detachPipMenuViewHost() { + if (mPipMenuSurface != null) { + SurfaceControl.Transaction transaction = new SurfaceControl.Transaction(); + transaction.remove(mPipMenuSurface); + transaction.apply(); + mPipMenuSurface = null; + mPipViewHost = null; + } + } + + /** * Note that dismissing PiP is now originated from SystemUI, see {@link #exitPip(int)}. * Meanwhile this callback is invoked whenever the task is removed. For instance: @@ -838,6 +885,12 @@ public class PipTaskOrganizer extends TaskOrganizer implements ShellTaskOrganize WindowContainerTransaction wct = new WindowContainerTransaction(); prepareFinishResizeTransaction(destinationBounds, direction, tx, wct); applyFinishBoundsResize(wct, direction); + runOnMainHandler(() -> { + if (mPipViewHost != null) { + mPipViewHost.relayout(PipMenuActivityController.getPipMenuLayoutParams( + destinationBounds.width(), destinationBounds.height())); + } + }); } private void prepareFinishResizeTransaction(Rect destinationBounds, diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java index d8864ec72dc3..5ef5b902327e 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java @@ -49,7 +49,6 @@ import com.android.systemui.pip.PipBoundsHandler; import com.android.systemui.pip.PipSurfaceTransactionHelper; import com.android.systemui.pip.PipTaskOrganizer; import com.android.systemui.pip.PipUiEventLogger; -import com.android.systemui.pip.phone.dagger.PipMenuActivityClass; import com.android.systemui.shared.recents.IPinnedStackAnimationListener; import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.InputConsumerController; @@ -267,7 +266,6 @@ public class PipManager implements BasePipManager, PipTaskOrganizer.PipTransitio @Inject public PipManager(Context context, BroadcastDispatcher broadcastDispatcher, - @PipMenuActivityClass Class pipMenuActivityClass, ConfigurationController configController, DeviceConfigProxy deviceConfig, DisplayController displayController, @@ -296,8 +294,8 @@ public class PipManager implements BasePipManager, PipTaskOrganizer.PipTransitio mPipTaskOrganizer.registerPipTransitionCallback(this); mInputConsumerController = InputConsumerController.getPipInputConsumer(); mMediaController = new PipMediaController(context, mActivityManager, broadcastDispatcher); - mMenuController = new PipMenuActivityController(context, pipMenuActivityClass, - mMediaController, mInputConsumerController); + mMenuController = new PipMenuActivityController(context, + mMediaController, mInputConsumerController, mPipTaskOrganizer); mTouchHandler = new PipTouchHandler(context, mActivityManager, mMenuController, mInputConsumerController, mPipBoundsHandler, mPipTaskOrganizer, floatingContentCoordinator, deviceConfig, sysUiState, pipUiEventLogger); diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java deleted file mode 100644 index 1b1b2de05883..000000000000 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java +++ /dev/null @@ -1,730 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.systemui.pip.phone; - -import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK; -import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; -import static android.provider.Settings.ACTION_PICTURE_IN_PICTURE_SETTINGS; -import static android.view.accessibility.AccessibilityManager.FLAG_CONTENT_CONTROLS; -import static android.view.accessibility.AccessibilityManager.FLAG_CONTENT_ICONS; -import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK; - -import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_ACTIONS; -import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_ALLOW_TIMEOUT; -import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_CONTROLLER_MESSENGER; -import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_DISMISS_FRACTION; -import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_MENU_STATE; -import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_SHOW_MENU_WITH_DELAY; -import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_SHOW_RESIZE_HANDLE; -import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_STACK_BOUNDS; -import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_WILL_RESIZE_MENU; -import static com.android.systemui.pip.phone.PipMenuActivityController.MENU_STATE_CLOSE; -import static com.android.systemui.pip.phone.PipMenuActivityController.MENU_STATE_FULL; -import static com.android.systemui.pip.phone.PipMenuActivityController.MENU_STATE_NONE; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.AnimatorSet; -import android.animation.ObjectAnimator; -import android.animation.ValueAnimator; -import android.annotation.Nullable; -import android.app.Activity; -import android.app.ActivityManager; -import android.app.PendingIntent.CanceledException; -import android.app.RemoteAction; -import android.content.ComponentName; -import android.content.Intent; -import android.content.pm.ParceledListSlice; -import android.graphics.Color; -import android.graphics.Rect; -import android.graphics.drawable.ColorDrawable; -import android.graphics.drawable.Drawable; -import android.net.Uri; -import android.os.Bundle; -import android.os.Handler; -import android.os.Looper; -import android.os.Message; -import android.os.Messenger; -import android.os.RemoteException; -import android.os.UserHandle; -import android.util.Log; -import android.util.Pair; -import android.view.KeyEvent; -import android.view.LayoutInflater; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup; -import android.view.WindowManager.LayoutParams; -import android.view.accessibility.AccessibilityManager; -import android.view.accessibility.AccessibilityNodeInfo; -import android.widget.FrameLayout; -import android.widget.ImageButton; -import android.widget.LinearLayout; - -import com.android.systemui.Interpolators; -import com.android.wm.shell.R; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -/** - * Translucent activity that gets started on top of a task in PIP to allow the user to control it. - * TODO(b/150319024): PipMenuActivity will move to a Window - */ -public class PipMenuActivity extends Activity { - - private static final String TAG = "PipMenuActivity"; - - private static final int MESSAGE_INVALID_TYPE = -1; - - public static final int MESSAGE_SHOW_MENU = 1; - public static final int MESSAGE_POKE_MENU = 2; - public static final int MESSAGE_HIDE_MENU = 3; - public static final int MESSAGE_UPDATE_ACTIONS = 4; - public static final int MESSAGE_UPDATE_DISMISS_FRACTION = 5; - public static final int MESSAGE_ANIMATION_ENDED = 6; - public static final int MESSAGE_POINTER_EVENT = 7; - public static final int MESSAGE_MENU_EXPANDED = 8; - public static final int MESSAGE_FADE_OUT_MENU = 9; - public static final int MESSAGE_UPDATE_MENU_LAYOUT = 10; - - private static final int INITIAL_DISMISS_DELAY = 3500; - private static final int POST_INTERACTION_DISMISS_DELAY = 2000; - private static final long MENU_FADE_DURATION = 125; - private static final long MENU_SLOW_FADE_DURATION = 175; - private static final long MENU_SHOW_ON_EXPAND_START_DELAY = 30; - - private static final float MENU_BACKGROUND_ALPHA = 0.3f; - private static final float DISMISS_BACKGROUND_ALPHA = 0.6f; - - private static final float DISABLED_ACTION_ALPHA = 0.54f; - - private static final boolean ENABLE_RESIZE_HANDLE = false; - - private int mMenuState; - private boolean mResize = true; - private boolean mAllowMenuTimeout = true; - private boolean mAllowTouches = true; - - private final List mActions = new ArrayList<>(); - - private AccessibilityManager mAccessibilityManager; - private Drawable mBackgroundDrawable; - private View mMenuContainer; - private LinearLayout mActionsGroup; - private int mBetweenActionPaddingLand; - - private AnimatorSet mMenuContainerAnimator; - - private ValueAnimator.AnimatorUpdateListener mMenuBgUpdateListener = - new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - final float alpha = (float) animation.getAnimatedValue(); - mBackgroundDrawable.setAlpha((int) (MENU_BACKGROUND_ALPHA*alpha*255)); - } - }; - - private Handler mHandler = new Handler(Looper.getMainLooper()) { - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MESSAGE_SHOW_MENU: { - final Bundle data = (Bundle) msg.obj; - showMenu(data.getInt(EXTRA_MENU_STATE), - data.getParcelable(EXTRA_STACK_BOUNDS), - data.getBoolean(EXTRA_ALLOW_TIMEOUT), - data.getBoolean(EXTRA_WILL_RESIZE_MENU), - data.getBoolean(EXTRA_SHOW_MENU_WITH_DELAY), - data.getBoolean(EXTRA_SHOW_RESIZE_HANDLE)); - break; - } - case MESSAGE_POKE_MENU: - cancelDelayedFinish(); - break; - case MESSAGE_HIDE_MENU: - hideMenu((Runnable) msg.obj); - break; - case MESSAGE_UPDATE_ACTIONS: { - final Bundle data = (Bundle) msg.obj; - final ParceledListSlice actions = data.getParcelable( - EXTRA_ACTIONS); - setActions(data.getParcelable(EXTRA_STACK_BOUNDS), actions != null - ? actions.getList() : Collections.emptyList()); - break; - } - case MESSAGE_UPDATE_DISMISS_FRACTION: { - final Bundle data = (Bundle) msg.obj; - updateDismissFraction(data.getFloat(EXTRA_DISMISS_FRACTION)); - break; - } - case MESSAGE_ANIMATION_ENDED: { - mAllowTouches = true; - break; - } - case MESSAGE_POINTER_EVENT: { - final MotionEvent ev = (MotionEvent) msg.obj; - dispatchPointerEvent(ev); - break; - } - case MESSAGE_MENU_EXPANDED : { - if (mMenuContainerAnimator == null) { - return; - } - mMenuContainerAnimator.setStartDelay(MENU_SHOW_ON_EXPAND_START_DELAY); - mMenuContainerAnimator.start(); - break; - } - case MESSAGE_FADE_OUT_MENU: { - fadeOutMenu(); - break; - } - case MESSAGE_UPDATE_MENU_LAYOUT: { - if (mPipMenuIconsAlgorithm == null) { - return; - } - final Rect bounds = (Rect) msg.obj; - mPipMenuIconsAlgorithm.onBoundsChanged(bounds); - break; - } - } - } - }; - private Messenger mToControllerMessenger; - private Messenger mMessenger = new Messenger(mHandler); - - private final Runnable mFinishRunnable = this::hideMenu; - - protected View mViewRoot; - protected View mSettingsButton; - protected View mDismissButton; - protected View mResizeHandle; - protected View mTopEndContainer; - protected PipMenuIconsAlgorithm mPipMenuIconsAlgorithm; - - @Override - protected void onCreate(@Nullable Bundle savedInstanceState) { - // Set the flags to allow us to watch for outside touches and also hide the menu and start - // manipulating the PIP in the same touch gesture - getWindow().addFlags(LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH); - - super.onCreate(savedInstanceState); - - setContentView(R.layout.pip_menu_activity); - - mAccessibilityManager = getSystemService(AccessibilityManager.class); - mBackgroundDrawable = new ColorDrawable(Color.BLACK); - mBackgroundDrawable.setAlpha(0); - mViewRoot = findViewById(R.id.background); - mViewRoot.setBackground(mBackgroundDrawable); - mMenuContainer = findViewById(R.id.menu_container); - mMenuContainer.setAlpha(0); - mTopEndContainer = findViewById(R.id.top_end_container); - mSettingsButton = findViewById(R.id.settings); - mSettingsButton.setAlpha(0); - mSettingsButton.setOnClickListener((v) -> { - if (v.getAlpha() != 0) { - showSettings(); - } - }); - mDismissButton = findViewById(R.id.dismiss); - mDismissButton.setAlpha(0); - mDismissButton.setOnClickListener(v -> dismissPip()); - findViewById(R.id.expand_button).setOnClickListener(v -> { - if (mMenuContainer.getAlpha() != 0) { - expandPip(); - } - }); - mResizeHandle = findViewById(R.id.resize_handle); - mResizeHandle.setAlpha(0); - mActionsGroup = findViewById(R.id.actions_group); - mBetweenActionPaddingLand = getResources().getDimensionPixelSize( - R.dimen.pip_between_action_padding_land); - mPipMenuIconsAlgorithm = new PipMenuIconsAlgorithm(this.getApplicationContext()); - mPipMenuIconsAlgorithm.bindViews((ViewGroup) mViewRoot, (ViewGroup) mTopEndContainer, - mResizeHandle, mSettingsButton, mDismissButton); - updateFromIntent(getIntent()); - setTitle(R.string.pip_menu_title); - setDisablePreviewScreenshots(true); - - // Hide without an animation. - getWindow().setExitTransition(null); - - initAccessibility(); - } - - private void initAccessibility() { - getWindow().getDecorView().setAccessibilityDelegate(new View.AccessibilityDelegate() { - @Override - public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(host, info); - String label = getResources().getString(R.string.pip_menu_title); - info.addAction(new AccessibilityNodeInfo.AccessibilityAction(ACTION_CLICK, label)); - } - - @Override - public boolean performAccessibilityAction(View host, int action, Bundle args) { - if (action == ACTION_CLICK && mMenuState == MENU_STATE_CLOSE) { - Message m = Message.obtain(); - m.what = PipMenuActivityController.MESSAGE_SHOW_MENU; - sendMessage(m, "Could not notify controller to show PIP menu"); - } - return super.performAccessibilityAction(host, action, args); - } - }); - } - - @Override - public boolean onKeyUp(int keyCode, KeyEvent event) { - if (keyCode == KeyEvent.KEYCODE_ESCAPE) { - hideMenu(); - return true; - } - return super.onKeyUp(keyCode, event); - } - - @Override - protected void onNewIntent(Intent intent) { - super.onNewIntent(intent); - updateFromIntent(intent); - } - - @Override - public void onUserInteraction() { - if (mAllowMenuTimeout) { - repostDelayedFinish(POST_INTERACTION_DISMISS_DELAY); - } - } - - @Override - protected void onUserLeaveHint() { - super.onUserLeaveHint(); - - // If another task is starting on top of the menu, then hide and finish it so that it can be - // recreated on the top next time it starts - hideMenu(); - } - - @Override - public void onTopResumedActivityChanged(boolean isTopResumedActivity) { - super.onTopResumedActivityChanged(isTopResumedActivity); - if (!isTopResumedActivity && mMenuState != MENU_STATE_NONE) { - hideMenu(); - } - } - - @Override - protected void onStop() { - super.onStop(); - - // In cases such as device lock, hide and finish it so that it can be recreated on the top - // next time it starts, see also {@link #onUserLeaveHint} - hideMenu(); - cancelDelayedFinish(); - } - - @Override - protected void onDestroy() { - super.onDestroy(); - - // Fallback, if we are destroyed for any other reason (like when the task is being reset), - // also reset the callback. - notifyActivityCallback(null); - } - - @Override - public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode) { - if (!isInPictureInPictureMode) { - finish(); - } - } - - /** - * Dispatch a pointer event from {@link PipTouchHandler}. - */ - private void dispatchPointerEvent(MotionEvent event) { - if (event.isTouchEvent()) { - dispatchTouchEvent(event); - } else { - dispatchGenericMotionEvent(event); - } - } - - @Override - public boolean dispatchTouchEvent(MotionEvent ev) { - if (!mAllowTouches) { - return false; - } - - // On the first action outside the window, hide the menu - switch (ev.getAction()) { - case MotionEvent.ACTION_OUTSIDE: - hideMenu(); - return true; - } - return super.dispatchTouchEvent(ev); - } - - @Override - public void finish() { - notifyActivityCallback(null); - super.finish(); - } - - @Override - public void setTaskDescription(ActivityManager.TaskDescription taskDescription) { - // Do nothing - } - - private void showMenu(int menuState, Rect stackBounds, boolean allowMenuTimeout, - boolean resizeMenuOnShow, boolean withDelay, boolean showResizeHandle) { - mAllowMenuTimeout = allowMenuTimeout; - if (mMenuState != menuState) { - // Disallow touches if the menu needs to resize while showing, and we are transitioning - // to/from a full menu state. - boolean disallowTouchesUntilAnimationEnd = resizeMenuOnShow && - (mMenuState == MENU_STATE_FULL || menuState == MENU_STATE_FULL); - mAllowTouches = !disallowTouchesUntilAnimationEnd; - cancelDelayedFinish(); - updateActionViews(stackBounds); - if (mMenuContainerAnimator != null) { - mMenuContainerAnimator.cancel(); - } - mMenuContainerAnimator = new AnimatorSet(); - ObjectAnimator menuAnim = ObjectAnimator.ofFloat(mMenuContainer, View.ALPHA, - mMenuContainer.getAlpha(), 1f); - menuAnim.addUpdateListener(mMenuBgUpdateListener); - ObjectAnimator settingsAnim = ObjectAnimator.ofFloat(mSettingsButton, View.ALPHA, - mSettingsButton.getAlpha(), 1f); - ObjectAnimator dismissAnim = ObjectAnimator.ofFloat(mDismissButton, View.ALPHA, - mDismissButton.getAlpha(), 1f); - ObjectAnimator resizeAnim = ObjectAnimator.ofFloat(mResizeHandle, View.ALPHA, - mResizeHandle.getAlpha(), - ENABLE_RESIZE_HANDLE && menuState == MENU_STATE_CLOSE && showResizeHandle - ? 1f : 0f); - if (menuState == MENU_STATE_FULL) { - mMenuContainerAnimator.playTogether(menuAnim, settingsAnim, dismissAnim, - resizeAnim); - } else { - mMenuContainerAnimator.playTogether(dismissAnim, resizeAnim); - } - mMenuContainerAnimator.setInterpolator(Interpolators.ALPHA_IN); - mMenuContainerAnimator.setDuration(menuState == MENU_STATE_CLOSE - ? MENU_FADE_DURATION - : MENU_SLOW_FADE_DURATION); - if (allowMenuTimeout) { - mMenuContainerAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - repostDelayedFinish(INITIAL_DISMISS_DELAY); - } - }); - } - if (withDelay) { - // starts the menu container animation after window expansion is completed - notifyMenuStateChange(menuState, resizeMenuOnShow, MESSAGE_MENU_EXPANDED); - } else { - notifyMenuStateChange(menuState, resizeMenuOnShow, MESSAGE_INVALID_TYPE); - mMenuContainerAnimator.start(); - } - } else { - // If we are already visible, then just start the delayed dismiss and unregister any - // existing input consumers from the previous drag - if (allowMenuTimeout) { - repostDelayedFinish(POST_INTERACTION_DISMISS_DELAY); - } - } - } - - /** - * Different from {@link #hideMenu()}, this function does not try to finish this menu activity - * and instead, it fades out the controls by setting the alpha to 0 directly without menu - * visibility callbacks invoked. - */ - private void fadeOutMenu() { - mMenuContainer.setAlpha(0f); - mSettingsButton.setAlpha(0f); - mDismissButton.setAlpha(0f); - mResizeHandle.setAlpha(0f); - } - - private void hideMenu() { - hideMenu(null); - } - - private void hideMenu(Runnable animationEndCallback) { - hideMenu(animationEndCallback, true /* notifyMenuVisibility */, false /* isDismissing */, - true /* animate */); - } - - private void hideMenu(final Runnable animationFinishedRunnable, boolean notifyMenuVisibility, - boolean isDismissing, boolean animate) { - if (mMenuState != MENU_STATE_NONE) { - cancelDelayedFinish(); - if (notifyMenuVisibility) { - notifyMenuStateChange(MENU_STATE_NONE, mResize, MESSAGE_INVALID_TYPE); - } - mMenuContainerAnimator = new AnimatorSet(); - ObjectAnimator menuAnim = ObjectAnimator.ofFloat(mMenuContainer, View.ALPHA, - mMenuContainer.getAlpha(), 0f); - menuAnim.addUpdateListener(mMenuBgUpdateListener); - ObjectAnimator settingsAnim = ObjectAnimator.ofFloat(mSettingsButton, View.ALPHA, - mSettingsButton.getAlpha(), 0f); - ObjectAnimator dismissAnim = ObjectAnimator.ofFloat(mDismissButton, View.ALPHA, - mDismissButton.getAlpha(), 0f); - ObjectAnimator resizeAnim = ObjectAnimator.ofFloat(mResizeHandle, View.ALPHA, - mResizeHandle.getAlpha(), 0f); - mMenuContainerAnimator.playTogether(menuAnim, settingsAnim, dismissAnim, resizeAnim); - mMenuContainerAnimator.setInterpolator(Interpolators.ALPHA_OUT); - mMenuContainerAnimator.setDuration(animate ? MENU_FADE_DURATION : 0); - mMenuContainerAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - if (animationFinishedRunnable != null) { - animationFinishedRunnable.run(); - } - - if (!isDismissing) { - // If we are dismissing the PiP, then don't try to pre-emptively finish the - // menu activity - finish(); - } - } - }); - mMenuContainerAnimator.start(); - } else { - // If the menu is not visible, just finish now - finish(); - } - } - - private void updateFromIntent(Intent intent) { - mToControllerMessenger = intent.getParcelableExtra(EXTRA_CONTROLLER_MESSENGER); - if (mToControllerMessenger == null) { - Log.w(TAG, "Controller messenger is null. Stopping."); - finish(); - return; - } - notifyActivityCallback(mMessenger); - - ParceledListSlice actions = intent.getParcelableExtra(EXTRA_ACTIONS); - if (actions != null) { - mActions.clear(); - mActions.addAll(actions.getList()); - } - - final int menuState = intent.getIntExtra(EXTRA_MENU_STATE, MENU_STATE_NONE); - if (menuState != MENU_STATE_NONE) { - Rect stackBounds = intent.getParcelableExtra(EXTRA_STACK_BOUNDS); - boolean allowMenuTimeout = intent.getBooleanExtra(EXTRA_ALLOW_TIMEOUT, true); - boolean willResizeMenu = intent.getBooleanExtra(EXTRA_WILL_RESIZE_MENU, false); - boolean withDelay = intent.getBooleanExtra(EXTRA_SHOW_MENU_WITH_DELAY, false); - boolean showResizeHandle = intent.getBooleanExtra(EXTRA_SHOW_RESIZE_HANDLE, false); - showMenu(menuState, stackBounds, allowMenuTimeout, willResizeMenu, withDelay, - showResizeHandle); - } - } - - private void setActions(Rect stackBounds, List actions) { - mActions.clear(); - mActions.addAll(actions); - updateActionViews(stackBounds); - } - - private void updateActionViews(Rect stackBounds) { - ViewGroup expandContainer = findViewById(R.id.expand_container); - ViewGroup actionsContainer = findViewById(R.id.actions_container); - actionsContainer.setOnTouchListener((v, ev) -> { - // Do nothing, prevent click through to parent - return true; - }); - - if (mActions.isEmpty() || mMenuState == MENU_STATE_CLOSE) { - actionsContainer.setVisibility(View.INVISIBLE); - } else { - actionsContainer.setVisibility(View.VISIBLE); - if (mActionsGroup != null) { - // Ensure we have as many buttons as actions - final LayoutInflater inflater = LayoutInflater.from(this); - while (mActionsGroup.getChildCount() < mActions.size()) { - final ImageButton actionView = (ImageButton) inflater.inflate( - R.layout.pip_menu_action, mActionsGroup, false); - mActionsGroup.addView(actionView); - } - - // Update the visibility of all views - for (int i = 0; i < mActionsGroup.getChildCount(); i++) { - mActionsGroup.getChildAt(i).setVisibility(i < mActions.size() - ? View.VISIBLE - : View.GONE); - } - - // Recreate the layout - final boolean isLandscapePip = stackBounds != null && - (stackBounds.width() > stackBounds.height()); - for (int i = 0; i < mActions.size(); i++) { - final RemoteAction action = mActions.get(i); - final ImageButton actionView = (ImageButton) mActionsGroup.getChildAt(i); - - // TODO: Check if the action drawable has changed before we reload it - action.getIcon().loadDrawableAsync(this, d -> { - d.setTint(Color.WHITE); - actionView.setImageDrawable(d); - }, mHandler); - actionView.setContentDescription(action.getContentDescription()); - if (action.isEnabled()) { - actionView.setOnClickListener(v -> { - mHandler.post(() -> { - try { - action.getActionIntent().send(); - } catch (CanceledException e) { - Log.w(TAG, "Failed to send action", e); - } - }); - }); - } - actionView.setEnabled(action.isEnabled()); - actionView.setAlpha(action.isEnabled() ? 1f : DISABLED_ACTION_ALPHA); - - // Update the margin between actions - LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) - actionView.getLayoutParams(); - lp.leftMargin = (isLandscapePip && i > 0) ? mBetweenActionPaddingLand : 0; - } - } - - // Update the expand container margin to adjust the center of the expand button to - // account for the existence of the action container - FrameLayout.LayoutParams expandedLp = - (FrameLayout.LayoutParams) expandContainer.getLayoutParams(); - expandedLp.topMargin = getResources().getDimensionPixelSize( - R.dimen.pip_action_padding); - expandedLp.bottomMargin = getResources().getDimensionPixelSize( - R.dimen.pip_expand_container_edge_margin); - expandContainer.requestLayout(); - } - } - - private void updateDismissFraction(float fraction) { - int alpha; - final float menuAlpha = 1 - fraction; - if (mMenuState == MENU_STATE_FULL) { - mMenuContainer.setAlpha(menuAlpha); - mSettingsButton.setAlpha(menuAlpha); - mDismissButton.setAlpha(menuAlpha); - final float interpolatedAlpha = - MENU_BACKGROUND_ALPHA * menuAlpha + DISMISS_BACKGROUND_ALPHA * fraction; - alpha = (int) (interpolatedAlpha * 255); - } else { - if (mMenuState == MENU_STATE_CLOSE) { - mDismissButton.setAlpha(menuAlpha); - } - alpha = (int) (fraction * DISMISS_BACKGROUND_ALPHA * 255); - } - mBackgroundDrawable.setAlpha(alpha); - } - - private void notifyMenuStateChange(int menuState, boolean resize, int callbackWhat) { - mMenuState = menuState; - mResize = resize; - Message m = Message.obtain(); - m.what = PipMenuActivityController.MESSAGE_MENU_STATE_CHANGED; - m.arg1 = menuState; - m.arg2 = resize ? 1 : 0; - if (callbackWhat != MESSAGE_INVALID_TYPE) { - // This message could be sent across processes when in secondary user. - // Make the receiver end sending back via our own Messenger - m.replyTo = mMessenger; - final Bundle data = new Bundle(1); - data.putInt(PipMenuActivityController.EXTRA_MESSAGE_CALLBACK_WHAT, callbackWhat); - m.obj = data; - } - sendMessage(m, "Could not notify controller of PIP menu visibility"); - } - - private void expandPip() { - // Do not notify menu visibility when hiding the menu, the controller will do this when it - // handles the message - hideMenu(() -> { - sendEmptyMessage(PipMenuActivityController.MESSAGE_EXPAND_PIP, - "Could not notify controller to expand PIP"); - }, false /* notifyMenuVisibility */, false /* isDismissing */, true /* animate */); - } - - private void dismissPip() { - // Since tapping on the close-button invokes a double-tap wait callback in PipTouchHandler, - // we want to disable animating the fadeout animation of the buttons in order to call on - // PipTouchHandler#onPipDismiss fast enough. - final boolean animate = mMenuState != MENU_STATE_CLOSE; - // Do not notify menu visibility when hiding the menu, the controller will do this when it - // handles the message - hideMenu(() -> { - sendEmptyMessage(PipMenuActivityController.MESSAGE_DISMISS_PIP, - "Could not notify controller to dismiss PIP"); - }, false /* notifyMenuVisibility */, true /* isDismissing */, animate); - } - - private void showSettings() { - final Pair topPipActivityInfo = - PipUtils.getTopPipActivity(this, ActivityManager.getService()); - if (topPipActivityInfo.first != null) { - final UserHandle user = UserHandle.of(topPipActivityInfo.second); - final Intent settingsIntent = new Intent(ACTION_PICTURE_IN_PICTURE_SETTINGS, - Uri.fromParts("package", topPipActivityInfo.first.getPackageName(), null)); - settingsIntent.putExtra(Intent.EXTRA_USER_HANDLE, user); - settingsIntent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK); - startActivity(settingsIntent); - } - } - - private void notifyActivityCallback(Messenger callback) { - Message m = Message.obtain(); - m.what = PipMenuActivityController.MESSAGE_UPDATE_ACTIVITY_CALLBACK; - m.replyTo = callback; - m.arg1 = mResize ? 1 : 0; - sendMessage(m, "Could not notify controller of activity finished"); - } - - private void sendEmptyMessage(int what, String errorMsg) { - Message m = Message.obtain(); - m.what = what; - sendMessage(m, errorMsg); - } - - private void sendMessage(Message m, String errorMsg) { - if (mToControllerMessenger == null) { - return; - } - try { - mToControllerMessenger.send(m); - } catch (RemoteException e) { - Log.e(TAG, errorMsg, e); - } - } - - private void cancelDelayedFinish() { - mHandler.removeCallbacks(mFinishRunnable); - } - - private void repostDelayedFinish(int delay) { - int recommendedTimeout = mAccessibilityManager.getRecommendedTimeoutMillis(delay, - FLAG_CONTENT_ICONS | FLAG_CONTENT_CONTROLS); - mHandler.removeCallbacks(mFinishRunnable); - mHandler.postDelayed(mFinishRunnable, recommendedTimeout); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java index 383f6b3bf79d..873ba26b39ab 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java @@ -19,27 +19,20 @@ package com.android.systemui.pip.phone; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; -import android.annotation.Nullable; import android.app.ActivityManager.StackInfo; -import android.app.ActivityOptions; import android.app.ActivityTaskManager; import android.app.RemoteAction; import android.content.Context; -import android.content.Intent; import android.content.pm.ParceledListSlice; +import android.graphics.PixelFormat; import android.graphics.Rect; -import android.os.Bundle; import android.os.Debug; -import android.os.Handler; -import android.os.Looper; -import android.os.Message; -import android.os.Messenger; import android.os.RemoteException; -import android.os.SystemClock; -import android.os.UserHandle; import android.util.Log; import android.view.MotionEvent; +import android.view.WindowManager; +import com.android.systemui.pip.PipTaskOrganizer; import com.android.systemui.pip.phone.PipMediaController.ActionListener; import com.android.systemui.shared.system.InputConsumerController; @@ -58,30 +51,10 @@ public class PipMenuActivityController { private static final String TAG = "PipMenuActController"; private static final boolean DEBUG = false; - public static final String EXTRA_CONTROLLER_MESSENGER = "messenger"; - public static final String EXTRA_ACTIONS = "actions"; - public static final String EXTRA_STACK_BOUNDS = "stack_bounds"; - public static final String EXTRA_ALLOW_TIMEOUT = "allow_timeout"; - public static final String EXTRA_WILL_RESIZE_MENU = "resize_menu_on_show"; - public static final String EXTRA_DISMISS_FRACTION = "dismiss_fraction"; - public static final String EXTRA_MENU_STATE = "menu_state"; - public static final String EXTRA_SHOW_MENU_WITH_DELAY = "show_menu_with_delay"; - public static final String EXTRA_SHOW_RESIZE_HANDLE = "show_resize_handle"; - public static final String EXTRA_MESSAGE_CALLBACK_WHAT = "message_callback_what"; - - public static final int MESSAGE_MENU_STATE_CHANGED = 100; - public static final int MESSAGE_EXPAND_PIP = 101; - public static final int MESSAGE_DISMISS_PIP = 103; - public static final int MESSAGE_UPDATE_ACTIVITY_CALLBACK = 104; - public static final int MESSAGE_SHOW_MENU = 107; - public static final int MENU_STATE_NONE = 0; public static final int MENU_STATE_CLOSE = 1; public static final int MENU_STATE_FULL = 2; - // The duration to wait before we consider the start activity as having timed out - private static final long START_ACTIVITY_REQUEST_TIMEOUT_MS = 300; - /** * A listener interface to receive notification on changes in PIP. */ @@ -110,9 +83,8 @@ public class PipMenuActivityController { void onPipShowMenu(); } - /** TODO(b/150319024): PipMenuActivity will move to a Window */ - private Class mPipMenuActivityClass; private Context mContext; + private PipTaskOrganizer mPipTaskOrganizer; private PipMediaController mMediaController; private InputConsumerController mInputConsumerController; @@ -121,63 +93,7 @@ public class PipMenuActivityController { private ParceledListSlice mMediaActions; private int mMenuState; - // The dismiss fraction update is sent frequently, so use a temporary bundle for the message - private Bundle mTmpDismissFractionData = new Bundle(); - - private Runnable mOnAnimationEndRunnable; - private boolean mStartActivityRequested; - private long mStartActivityRequestedTime; - private Messenger mToActivityMessenger; - private Handler mHandler = new Handler(Looper.getMainLooper()) { - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MESSAGE_MENU_STATE_CHANGED: { - final int menuState = msg.arg1; - final boolean resize = msg.arg2 != 0; - onMenuStateChanged(menuState, resize, - getMenuStateChangeFinishedCallback(msg.replyTo, (Bundle) msg.obj)); - break; - } - case MESSAGE_EXPAND_PIP: { - mListeners.forEach(Listener::onPipExpand); - break; - } - case MESSAGE_DISMISS_PIP: { - mListeners.forEach(Listener::onPipDismiss); - break; - } - case MESSAGE_SHOW_MENU: { - mListeners.forEach(Listener::onPipShowMenu); - break; - } - case MESSAGE_UPDATE_ACTIVITY_CALLBACK: { - mToActivityMessenger = msg.replyTo; - setStartActivityRequested(false); - if (mOnAnimationEndRunnable != null) { - mOnAnimationEndRunnable.run(); - mOnAnimationEndRunnable = null; - } - // Mark the menu as invisible once the activity finishes as well - if (mToActivityMessenger == null) { - final boolean resize = msg.arg1 != 0; - onMenuStateChanged(MENU_STATE_NONE, resize, null /* callback */); - } - break; - } - } - } - }; - private Messenger mMessenger = new Messenger(mHandler); - - private Runnable mStartActivityRequestedTimeoutRunnable = () -> { - setStartActivityRequested(false); - if (mOnAnimationEndRunnable != null) { - mOnAnimationEndRunnable.run(); - mOnAnimationEndRunnable = null; - } - Log.e(TAG, "Expected start menu activity request timed out"); - }; + private PipMenuView mPipMenuView; private ActionListener mMediaActionListener = new ActionListener() { @Override @@ -187,39 +103,40 @@ public class PipMenuActivityController { } }; - public PipMenuActivityController(Context context, Class pipMenuActivityClass, - PipMediaController mediaController, InputConsumerController inputConsumerController + public PipMenuActivityController(Context context, + PipMediaController mediaController, InputConsumerController inputConsumerController, + PipTaskOrganizer pipTaskOrganizer ) { mContext = context; mMediaController = mediaController; mInputConsumerController = inputConsumerController; - mPipMenuActivityClass = pipMenuActivityClass; + mPipTaskOrganizer = pipTaskOrganizer; } - public boolean isMenuActivityVisible() { - return mToActivityMessenger != null; + public boolean isMenuVisible() { + return mPipMenuView != null && mMenuState != MENU_STATE_NONE; } public void onActivityPinned() { + if (mPipMenuView == null) { + WindowManager.LayoutParams lp = + getPipMenuLayoutParams(0, 0); + mPipMenuView = new PipMenuView(mContext, this); + mPipTaskOrganizer.attachPipMenuViewHost(mPipMenuView, lp); + } mInputConsumerController.registerInputConsumer(true /* withSfVsync */); } public void onActivityUnpinned() { hideMenu(); mInputConsumerController.unregisterInputConsumer(); - setStartActivityRequested(false); + mPipTaskOrganizer.detachPipMenuViewHost(); + mPipMenuView = null; } public void onPinnedStackAnimationEnded() { - // Note: Only active menu activities care about this event - if (mToActivityMessenger != null) { - Message m = Message.obtain(); - m.what = PipMenuActivity.MESSAGE_ANIMATION_ENDED; - try { - mToActivityMessenger.send(m); - } catch (RemoteException e) { - Log.e(TAG, "Could not notify menu pinned animation ended", e); - } + if (isMenuVisible()) { + mPipMenuView.onPipAnimationEnded(); } } @@ -236,27 +153,13 @@ public class PipMenuActivityController { * Updates the appearance of the menu and scrim on top of the PiP while dismissing. */ public void setDismissFraction(float fraction) { + final boolean isMenuVisible = isMenuVisible(); if (DEBUG) { - Log.d(TAG, "setDismissFraction() hasActivity=" + (mToActivityMessenger != null) + Log.d(TAG, "setDismissFraction() isMenuVisible=" + isMenuVisible + " fraction=" + fraction); } - if (mToActivityMessenger != null) { - mTmpDismissFractionData.clear(); - mTmpDismissFractionData.putFloat(EXTRA_DISMISS_FRACTION, fraction); - Message m = Message.obtain(); - m.what = PipMenuActivity.MESSAGE_UPDATE_DISMISS_FRACTION; - m.obj = mTmpDismissFractionData; - try { - mToActivityMessenger.send(m); - } catch (RemoteException e) { - Log.e(TAG, "Could not notify menu to update dismiss fraction", e); - } - } else if (!mStartActivityRequested || isStartActivityRequestedElapsed()) { - // If we haven't requested the start activity, or if it previously took too long to - // start, then start it - startMenuActivity(MENU_STATE_NONE, null /* stackBounds */, - false /* allowMenuTimeout */, false /* resizeMenuOnShow */, - false /* withDelay */, false /* showResizeHandle */); + if (isMenuVisible) { + mPipMenuView.updateDismissFraction(fraction); } } @@ -282,27 +185,11 @@ public class PipMenuActivityController { false /* withDelay */, showResizeHandle); } - private Runnable getMenuStateChangeFinishedCallback(@Nullable Messenger replyTo, - @Nullable Bundle data) { - if (replyTo == null || data == null) { - return null; - } - return () -> { - try { - final Message m = Message.obtain(); - m.what = data.getInt(EXTRA_MESSAGE_CALLBACK_WHAT); - replyTo.send(m); - } catch (RemoteException e) { - // ignored - } - }; - } - private void showMenuInternal(int menuState, Rect stackBounds, boolean allowMenuTimeout, boolean willResizeMenu, boolean withDelay, boolean showResizeHandle) { if (DEBUG) { Log.d(TAG, "showMenu() state=" + menuState - + " hasActivity=" + (mToActivityMessenger != null) + + " isMenuVisible=" + isMenuVisible() + " allowMenuTimeout=" + allowMenuTimeout + " willResizeMenu=" + willResizeMenu + " withDelay=" + withDelay @@ -310,64 +197,34 @@ public class PipMenuActivityController { + " callers=\n" + Debug.getCallers(5, " ")); } - if (mToActivityMessenger != null) { - Bundle data = new Bundle(); - data.putInt(EXTRA_MENU_STATE, menuState); - if (stackBounds != null) { - data.putParcelable(EXTRA_STACK_BOUNDS, stackBounds); - } - data.putBoolean(EXTRA_ALLOW_TIMEOUT, allowMenuTimeout); - data.putBoolean(EXTRA_WILL_RESIZE_MENU, willResizeMenu); - data.putBoolean(EXTRA_SHOW_MENU_WITH_DELAY, withDelay); - data.putBoolean(EXTRA_SHOW_RESIZE_HANDLE, showResizeHandle); - Message m = Message.obtain(); - m.what = PipMenuActivity.MESSAGE_SHOW_MENU; - m.obj = data; - try { - mToActivityMessenger.send(m); - } catch (RemoteException e) { - Log.e(TAG, "Could not notify menu to show", e); - } - } else if (!mStartActivityRequested || isStartActivityRequestedElapsed()) { - // If we haven't requested the start activity, or if it previously took too long to - // start, then start it - startMenuActivity(menuState, stackBounds, allowMenuTimeout, willResizeMenu, withDelay, - showResizeHandle); + if (mPipMenuView == null) { + Log.e(TAG, "PipMenu has not been attached yet."); + return; } + mPipMenuView.showMenu(menuState, stackBounds, allowMenuTimeout, willResizeMenu, withDelay, + showResizeHandle); } /** * Pokes the menu, indicating that the user is interacting with it. */ public void pokeMenu() { + final boolean isMenuVisible = isMenuVisible(); if (DEBUG) { - Log.d(TAG, "pokeMenu() hasActivity=" + (mToActivityMessenger != null)); + Log.d(TAG, "pokeMenu() isMenuVisible=" + isMenuVisible); } - if (mToActivityMessenger != null) { - Message m = Message.obtain(); - m.what = PipMenuActivity.MESSAGE_POKE_MENU; - try { - mToActivityMessenger.send(m); - } catch (RemoteException e) { - Log.e(TAG, "Could not notify poke menu", e); - } + if (isMenuVisible) { + mPipMenuView.pokeMenu(); } } private void fadeOutMenu() { + final boolean isMenuVisible = isMenuVisible(); if (DEBUG) { - Log.d(TAG, "fadeOutMenu() state=" + mMenuState - + " hasActivity=" + (mToActivityMessenger != null) - + " callers=\n" + Debug.getCallers(5, " ")); + Log.d(TAG, "fadeOutMenu() isMenuVisible=" + isMenuVisible); } - if (mToActivityMessenger != null) { - Message m = Message.obtain(); - m.what = PipMenuActivity.MESSAGE_FADE_OUT_MENU; - try { - mToActivityMessenger.send(m); - } catch (RemoteException e) { - Log.e(TAG, "Could not notify menu to fade out", e); - } + if (isMenuVisible) { + mPipMenuView.fadeOutMenu(); } } @@ -375,19 +232,14 @@ public class PipMenuActivityController { * Hides the menu activity. */ public void hideMenu() { + final boolean isMenuVisible = isMenuVisible(); if (DEBUG) { Log.d(TAG, "hideMenu() state=" + mMenuState - + " hasActivity=" + (mToActivityMessenger != null) + + " isMenuVisible=" + isMenuVisible + " callers=\n" + Debug.getCallers(5, " ")); } - if (mToActivityMessenger != null) { - Message m = Message.obtain(); - m.what = PipMenuActivity.MESSAGE_HIDE_MENU; - try { - mToActivityMessenger.send(m); - } catch (RemoteException e) { - Log.e(TAG, "Could not notify menu to hide", e); - } + if (isMenuVisible) { + mPipMenuView.hideMenu(); } } @@ -395,29 +247,11 @@ public class PipMenuActivityController { * Hides the menu activity. */ public void hideMenu(Runnable onStartCallback, Runnable onEndCallback) { - if (mStartActivityRequested) { - // If the menu has been start-requested, but not actually started, then we defer the - // trigger callback until the menu has started and called back to the controller. - mOnAnimationEndRunnable = onEndCallback; - onStartCallback.run(); - - // Fallback for b/63752800, we have started the PipMenuActivity but it has not made any - // callbacks. Don't continue to wait for the menu to show past some timeout. - mHandler.removeCallbacks(mStartActivityRequestedTimeoutRunnable); - mHandler.postDelayed(mStartActivityRequestedTimeoutRunnable, - START_ACTIVITY_REQUEST_TIMEOUT_MS); - } else if (mMenuState != MENU_STATE_NONE && mToActivityMessenger != null) { + if (isMenuVisible()) { // If the menu is visible in either the closed or full state, then hide the menu and // trigger the animation trigger afterwards onStartCallback.run(); - Message m = Message.obtain(); - m.what = PipMenuActivity.MESSAGE_HIDE_MENU; - m.obj = onEndCallback; - try { - mToActivityMessenger.send(m); - } catch (RemoteException e) { - Log.e(TAG, "Could not notify hide menu", e); - } + mPipMenuView.hideMenu(onEndCallback); } } @@ -438,6 +272,18 @@ public class PipMenuActivityController { updateMenuActions(); } + void onPipExpand() { + mListeners.forEach(Listener::onPipExpand); + } + + void onPipDismiss() { + mListeners.forEach(Listener::onPipDismiss); + } + + void onPipShowMenu() { + mListeners.forEach(Listener::onPipShowMenu); + } + /** * @return the best set of actions to show in the PiP menu. */ @@ -449,47 +295,20 @@ public class PipMenuActivityController { } /** - * Starts the menu activity on the top task of the pinned stack. + * Returns a default LayoutParams for the PIP Menu. + * @param width the PIP stack width. + * @param height the PIP stack height. */ - private void startMenuActivity(int menuState, Rect stackBounds, boolean allowMenuTimeout, - boolean willResizeMenu, boolean withDelay, boolean showResizeHandle) { - try { - StackInfo pinnedStackInfo = ActivityTaskManager.getService().getStackInfo( - WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED); - if (pinnedStackInfo != null && pinnedStackInfo.taskIds != null && - pinnedStackInfo.taskIds.length > 0) { - Intent intent = new Intent(mContext, mPipMenuActivityClass); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - intent.putExtra(EXTRA_CONTROLLER_MESSENGER, mMessenger); - intent.putExtra(EXTRA_ACTIONS, resolveMenuActions()); - if (stackBounds != null) { - intent.putExtra(EXTRA_STACK_BOUNDS, stackBounds); - } - intent.putExtra(EXTRA_MENU_STATE, menuState); - intent.putExtra(EXTRA_ALLOW_TIMEOUT, allowMenuTimeout); - intent.putExtra(EXTRA_WILL_RESIZE_MENU, willResizeMenu); - intent.putExtra(EXTRA_SHOW_MENU_WITH_DELAY, withDelay); - intent.putExtra(EXTRA_SHOW_RESIZE_HANDLE, showResizeHandle); - ActivityOptions options = ActivityOptions.makeCustomAnimation(mContext, 0, 0); - options.setLaunchTaskId( - pinnedStackInfo.taskIds[pinnedStackInfo.taskIds.length - 1]); - options.setTaskOverlay(true, true /* canResume */); - mContext.startActivityAsUser(intent, options.toBundle(), UserHandle.CURRENT); - setStartActivityRequested(true); - } else { - Log.e(TAG, "No PIP tasks found"); - } - } catch (RemoteException e) { - setStartActivityRequested(false); - Log.e(TAG, "Error showing PIP menu activity", e); - } + public static WindowManager.LayoutParams getPipMenuLayoutParams(int width, int height) { + return new WindowManager.LayoutParams(width, height, + WindowManager.LayoutParams.TYPE_APPLICATION, 0, PixelFormat.TRANSLUCENT); } /** - * Updates the PiP menu activity with the best set of actions provided. + * Updates the PiP menu with the best set of actions provided. */ private void updateMenuActions() { - if (mToActivityMessenger != null) { + if (isMenuVisible()) { // Fetch the pinned stack bounds Rect stackBounds = null; try { @@ -499,20 +318,10 @@ public class PipMenuActivityController { stackBounds = pinnedStackInfo.bounds; } } catch (RemoteException e) { - Log.e(TAG, "Error showing PIP menu activity", e); + Log.e(TAG, "Error showing PIP menu", e); } - Bundle data = new Bundle(); - data.putParcelable(EXTRA_STACK_BOUNDS, stackBounds); - data.putParcelable(EXTRA_ACTIONS, resolveMenuActions()); - Message m = Message.obtain(); - m.what = PipMenuActivity.MESSAGE_UPDATE_ACTIONS; - m.obj = data; - try { - mToActivityMessenger.send(m); - } catch (RemoteException e) { - Log.e(TAG, "Could not notify menu activity to update actions", e); - } + mPipMenuView.setActions(stackBounds, resolveMenuActions().getList()); } } @@ -523,18 +332,10 @@ public class PipMenuActivityController { return actions != null && actions.getList().size() > 0; } - /** - * @return whether the time of the activity request has exceeded the timeout. - */ - private boolean isStartActivityRequestedElapsed() { - return (SystemClock.uptimeMillis() - mStartActivityRequestedTime) - >= START_ACTIVITY_REQUEST_TIMEOUT_MS; - } - /** * Handles changes in menu visibility. */ - private void onMenuStateChanged(int menuState, boolean resize, Runnable callback) { + void onMenuStateChanged(int menuState, boolean resize, Runnable callback) { if (DEBUG) { Log.d(TAG, "onMenuStateChanged() mMenuState=" + mMenuState + " menuState=" + menuState + " resize=" + resize @@ -556,25 +357,14 @@ public class PipMenuActivityController { mMenuState = menuState; } - private void setStartActivityRequested(boolean requested) { - mHandler.removeCallbacks(mStartActivityRequestedTimeoutRunnable); - mStartActivityRequested = requested; - mStartActivityRequestedTime = requested ? SystemClock.uptimeMillis() : 0; - } - /** * Handles a pointer event sent from pip input consumer. */ void handlePointerEvent(MotionEvent ev) { - if (mToActivityMessenger != null) { - Message m = Message.obtain(); - m.what = PipMenuActivity.MESSAGE_POINTER_EVENT; - m.obj = ev; - try { - mToActivityMessenger.send(m); - } catch (RemoteException e) { - Log.e(TAG, "Could not dispatch touch event", e); - } + if (ev.isTouchEvent()) { + mPipMenuView.dispatchTouchEvent(ev); + } else { + mPipMenuView.dispatchGenericMotionEvent(ev); } } @@ -582,15 +372,14 @@ public class PipMenuActivityController { * Tell the PIP Menu to recalculate its layout given its current position on the display. */ public void updateMenuLayout(Rect bounds) { - if (mToActivityMessenger != null) { - Message m = Message.obtain(); - m.what = PipMenuActivity.MESSAGE_UPDATE_MENU_LAYOUT; - m.obj = bounds; - try { - mToActivityMessenger.send(m); - } catch (RemoteException e) { - Log.e(TAG, "Could not dispatch touch event", e); - } + final boolean isMenuVisible = isMenuVisible(); + if (DEBUG) { + Log.d(TAG, "updateMenuLayout() state=" + mMenuState + + " isMenuVisible=" + isMenuVisible + + " callers=\n" + Debug.getCallers(5, " ")); + } + if (isMenuVisible) { + mPipMenuView.updateMenuLayout(bounds); } } @@ -598,9 +387,7 @@ public class PipMenuActivityController { final String innerPrefix = prefix + " "; pw.println(prefix + TAG); pw.println(innerPrefix + "mMenuState=" + mMenuState); - pw.println(innerPrefix + "mToActivityMessenger=" + mToActivityMessenger); + pw.println(innerPrefix + "mPipMenuView=" + mPipMenuView); pw.println(innerPrefix + "mListeners=" + mListeners.size()); - pw.println(innerPrefix + "mStartActivityRequested=" + mStartActivityRequested); - pw.println(innerPrefix + "mStartActivityRequestedTime=" + mStartActivityRequestedTime); } } diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuView.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuView.java new file mode 100644 index 000000000000..993bfe04f9a3 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuView.java @@ -0,0 +1,497 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.pip.phone; + +import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK; +import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; +import static android.provider.Settings.ACTION_PICTURE_IN_PICTURE_SETTINGS; +import static android.view.accessibility.AccessibilityManager.FLAG_CONTENT_CONTROLS; +import static android.view.accessibility.AccessibilityManager.FLAG_CONTENT_ICONS; +import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK; + +import static com.android.systemui.pip.phone.PipMenuActivityController.MENU_STATE_CLOSE; +import static com.android.systemui.pip.phone.PipMenuActivityController.MENU_STATE_FULL; +import static com.android.systemui.pip.phone.PipMenuActivityController.MENU_STATE_NONE; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.animation.ValueAnimator; +import android.app.ActivityManager; +import android.app.PendingIntent.CanceledException; +import android.app.RemoteAction; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.graphics.Color; +import android.graphics.Rect; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.os.UserHandle; +import android.util.Log; +import android.util.Pair; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.accessibility.AccessibilityManager; +import android.view.accessibility.AccessibilityNodeInfo; +import android.widget.FrameLayout; +import android.widget.ImageButton; +import android.widget.LinearLayout; + +import com.android.systemui.Interpolators; +import com.android.systemui.R; + +import java.util.ArrayList; +import java.util.List; + +/** + * Translucent window that gets started on top of a task in PIP to allow the user to control it. + */ +public class PipMenuView extends FrameLayout { + + private static final String TAG = "PipMenuView"; + + private static final int MESSAGE_INVALID_TYPE = -1; + public static final int MESSAGE_MENU_EXPANDED = 8; + + private static final int INITIAL_DISMISS_DELAY = 3500; + private static final int POST_INTERACTION_DISMISS_DELAY = 2000; + private static final long MENU_FADE_DURATION = 125; + private static final long MENU_SLOW_FADE_DURATION = 175; + private static final long MENU_SHOW_ON_EXPAND_START_DELAY = 30; + + private static final float MENU_BACKGROUND_ALPHA = 0.3f; + private static final float DISMISS_BACKGROUND_ALPHA = 0.6f; + + private static final float DISABLED_ACTION_ALPHA = 0.54f; + + private static final boolean ENABLE_RESIZE_HANDLE = false; + + private int mMenuState; + private boolean mResize = true; + private boolean mAllowMenuTimeout = true; + private boolean mAllowTouches = true; + + private final List mActions = new ArrayList<>(); + + private AccessibilityManager mAccessibilityManager; + private Drawable mBackgroundDrawable; + private View mMenuContainer; + private LinearLayout mActionsGroup; + private int mBetweenActionPaddingLand; + + private AnimatorSet mMenuContainerAnimator; + private PipMenuActivityController mController; + + private ValueAnimator.AnimatorUpdateListener mMenuBgUpdateListener = + new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + final float alpha = (float) animation.getAnimatedValue(); + mBackgroundDrawable.setAlpha((int) (MENU_BACKGROUND_ALPHA * alpha * 255)); + } + }; + + private Handler mHandler = new Handler(); + + private final Runnable mHideMenuRunnable = this::hideMenu; + + protected View mViewRoot; + protected View mSettingsButton; + protected View mDismissButton; + protected View mResizeHandle; + protected View mTopEndContainer; + protected PipMenuIconsAlgorithm mPipMenuIconsAlgorithm; + + public PipMenuView(Context context, PipMenuActivityController controller) { + super(context, null, 0); + mContext = context; + mController = controller; + + mAccessibilityManager = context.getSystemService(AccessibilityManager.class); + inflate(context, R.layout.pip_menu, this); + + mBackgroundDrawable = new ColorDrawable(Color.BLACK); + mBackgroundDrawable.setAlpha(0); + mViewRoot = findViewById(R.id.background); + mViewRoot.setBackground(mBackgroundDrawable); + mMenuContainer = findViewById(R.id.menu_container); + mMenuContainer.setAlpha(0); + mTopEndContainer = findViewById(R.id.top_end_container); + mSettingsButton = findViewById(R.id.settings); + mSettingsButton.setAlpha(0); + mSettingsButton.setOnClickListener((v) -> { + if (v.getAlpha() != 0) { + showSettings(); + } + }); + mDismissButton = findViewById(R.id.dismiss); + mDismissButton.setAlpha(0); + mDismissButton.setOnClickListener(v -> dismissPip()); + findViewById(R.id.expand_button).setOnClickListener(v -> { + if (mMenuContainer.getAlpha() != 0) { + expandPip(); + } + }); + // TODO (b/161710689): Remove this once focusability for Windowless window is working + findViewById(R.id.expand_button).setFocusable(false); + mDismissButton.setFocusable(false); + mSettingsButton.setFocusable(false); + + mResizeHandle = findViewById(R.id.resize_handle); + mResizeHandle.setAlpha(0); + mActionsGroup = findViewById(R.id.actions_group); + mBetweenActionPaddingLand = getResources().getDimensionPixelSize( + R.dimen.pip_between_action_padding_land); + mPipMenuIconsAlgorithm = new PipMenuIconsAlgorithm(mContext); + mPipMenuIconsAlgorithm.bindViews((ViewGroup) mViewRoot, (ViewGroup) mTopEndContainer, + mResizeHandle, mSettingsButton, mDismissButton); + + initAccessibility(); + } + + private void initAccessibility() { + this.setAccessibilityDelegate(new View.AccessibilityDelegate() { + @Override + public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(host, info); + String label = getResources().getString(R.string.pip_menu_title); + info.addAction(new AccessibilityNodeInfo.AccessibilityAction(ACTION_CLICK, label)); + } + + @Override + public boolean performAccessibilityAction(View host, int action, Bundle args) { + if (action == ACTION_CLICK && mMenuState == MENU_STATE_CLOSE) { + mController.onPipShowMenu(); + } + return super.performAccessibilityAction(host, action, args); + } + }); + } + + @Override + public boolean onKeyUp(int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_ESCAPE) { + hideMenu(); + return true; + } + return super.onKeyUp(keyCode, event); + } + + @Override + public boolean dispatchTouchEvent(MotionEvent ev) { + if (!mAllowTouches) { + return false; + } + + if (mAllowMenuTimeout) { + repostDelayedHide(POST_INTERACTION_DISMISS_DELAY); + } + + return super.dispatchTouchEvent(ev); + } + + @Override + public boolean dispatchGenericMotionEvent(MotionEvent event) { + if (mAllowMenuTimeout) { + repostDelayedHide(POST_INTERACTION_DISMISS_DELAY); + } + + return super.dispatchGenericMotionEvent(event); + } + + void showMenu(int menuState, Rect stackBounds, boolean allowMenuTimeout, + boolean resizeMenuOnShow, boolean withDelay, boolean showResizeHandle) { + mAllowMenuTimeout = allowMenuTimeout; + if (mMenuState != menuState) { + // Disallow touches if the menu needs to resize while showing, and we are transitioning + // to/from a full menu state. + boolean disallowTouchesUntilAnimationEnd = resizeMenuOnShow + && (mMenuState == MENU_STATE_FULL || menuState == MENU_STATE_FULL); + mAllowTouches = !disallowTouchesUntilAnimationEnd; + cancelDelayedHide(); + updateActionViews(stackBounds); + if (mMenuContainerAnimator != null) { + mMenuContainerAnimator.cancel(); + } + mMenuContainerAnimator = new AnimatorSet(); + ObjectAnimator menuAnim = ObjectAnimator.ofFloat(mMenuContainer, View.ALPHA, + mMenuContainer.getAlpha(), 1f); + menuAnim.addUpdateListener(mMenuBgUpdateListener); + ObjectAnimator settingsAnim = ObjectAnimator.ofFloat(mSettingsButton, View.ALPHA, + mSettingsButton.getAlpha(), 1f); + ObjectAnimator dismissAnim = ObjectAnimator.ofFloat(mDismissButton, View.ALPHA, + mDismissButton.getAlpha(), 1f); + ObjectAnimator resizeAnim = ObjectAnimator.ofFloat(mResizeHandle, View.ALPHA, + mResizeHandle.getAlpha(), + ENABLE_RESIZE_HANDLE && menuState == MENU_STATE_CLOSE && showResizeHandle + ? 1f : 0f); + if (menuState == MENU_STATE_FULL) { + mMenuContainerAnimator.playTogether(menuAnim, settingsAnim, dismissAnim, + resizeAnim); + } else { + mMenuContainerAnimator.playTogether(dismissAnim, resizeAnim); + } + mMenuContainerAnimator.setInterpolator(Interpolators.ALPHA_IN); + mMenuContainerAnimator.setDuration(menuState == MENU_STATE_CLOSE + ? MENU_FADE_DURATION + : MENU_SLOW_FADE_DURATION); + if (allowMenuTimeout) { + mMenuContainerAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + repostDelayedHide(INITIAL_DISMISS_DELAY); + } + }); + } + if (withDelay) { + // starts the menu container animation after window expansion is completed + notifyMenuStateChange(menuState, resizeMenuOnShow, () -> { + if (mMenuContainerAnimator == null) { + return; + } + mMenuContainerAnimator.setStartDelay(MENU_SHOW_ON_EXPAND_START_DELAY); + mMenuContainerAnimator.start(); + }); + } else { + notifyMenuStateChange(menuState, resizeMenuOnShow, null); + mMenuContainerAnimator.start(); + } + } else { + // If we are already visible, then just start the delayed dismiss and unregister any + // existing input consumers from the previous drag + if (allowMenuTimeout) { + repostDelayedHide(POST_INTERACTION_DISMISS_DELAY); + } + } + } + + /** + * Different from {@link #hideMenu()}, this function does not try to finish this menu activity + * and instead, it fades out the controls by setting the alpha to 0 directly without menu + * visibility callbacks invoked. + */ + void fadeOutMenu() { + mMenuContainer.setAlpha(0f); + mSettingsButton.setAlpha(0f); + mDismissButton.setAlpha(0f); + mResizeHandle.setAlpha(0f); + } + + void pokeMenu() { + cancelDelayedHide(); + } + + void onPipAnimationEnded() { + mAllowTouches = true; + } + + void updateMenuLayout(Rect bounds) { + mPipMenuIconsAlgorithm.onBoundsChanged(bounds); + } + + void hideMenu() { + hideMenu(null); + } + + void hideMenu(Runnable animationEndCallback) { + hideMenu(animationEndCallback, true /* notifyMenuVisibility */, false); + } + + private void hideMenu(final Runnable animationFinishedRunnable, boolean notifyMenuVisibility, + boolean animate) { + if (mMenuState != MENU_STATE_NONE) { + cancelDelayedHide(); + if (notifyMenuVisibility) { + notifyMenuStateChange(MENU_STATE_NONE, mResize, null); + } + mMenuContainerAnimator = new AnimatorSet(); + ObjectAnimator menuAnim = ObjectAnimator.ofFloat(mMenuContainer, View.ALPHA, + mMenuContainer.getAlpha(), 0f); + menuAnim.addUpdateListener(mMenuBgUpdateListener); + ObjectAnimator settingsAnim = ObjectAnimator.ofFloat(mSettingsButton, View.ALPHA, + mSettingsButton.getAlpha(), 0f); + ObjectAnimator dismissAnim = ObjectAnimator.ofFloat(mDismissButton, View.ALPHA, + mDismissButton.getAlpha(), 0f); + ObjectAnimator resizeAnim = ObjectAnimator.ofFloat(mResizeHandle, View.ALPHA, + mResizeHandle.getAlpha(), 0f); + mMenuContainerAnimator.playTogether(menuAnim, settingsAnim, dismissAnim, resizeAnim); + mMenuContainerAnimator.setInterpolator(Interpolators.ALPHA_OUT); + mMenuContainerAnimator.setDuration(animate ? MENU_FADE_DURATION : 0); + mMenuContainerAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (animationFinishedRunnable != null) { + animationFinishedRunnable.run(); + } + } + }); + mMenuContainerAnimator.start(); + } + } + + void setActions(Rect stackBounds, List actions) { + mActions.clear(); + mActions.addAll(actions); + updateActionViews(stackBounds); + } + + private void updateActionViews(Rect stackBounds) { + ViewGroup expandContainer = findViewById(R.id.expand_container); + ViewGroup actionsContainer = findViewById(R.id.actions_container); + actionsContainer.setOnTouchListener((v, ev) -> { + // Do nothing, prevent click through to parent + return true; + }); + + if (mActions.isEmpty() || mMenuState == MENU_STATE_CLOSE) { + actionsContainer.setVisibility(View.INVISIBLE); + } else { + actionsContainer.setVisibility(View.VISIBLE); + if (mActionsGroup != null) { + // Ensure we have as many buttons as actions + final LayoutInflater inflater = LayoutInflater.from(mContext); + while (mActionsGroup.getChildCount() < mActions.size()) { + final ImageButton actionView = (ImageButton) inflater.inflate( + R.layout.pip_menu_action, mActionsGroup, false); + mActionsGroup.addView(actionView); + } + + // Update the visibility of all views + for (int i = 0; i < mActionsGroup.getChildCount(); i++) { + mActionsGroup.getChildAt(i).setVisibility(i < mActions.size() + ? View.VISIBLE + : View.GONE); + } + + // Recreate the layout + final boolean isLandscapePip = stackBounds != null + && (stackBounds.width() > stackBounds.height()); + for (int i = 0; i < mActions.size(); i++) { + final RemoteAction action = mActions.get(i); + final ImageButton actionView = (ImageButton) mActionsGroup.getChildAt(i); + + // TODO: Check if the action drawable has changed before we reload it + action.getIcon().loadDrawableAsync(mContext, d -> { + d.setTint(Color.WHITE); + actionView.setImageDrawable(d); + }, mHandler); + actionView.setContentDescription(action.getContentDescription()); + if (action.isEnabled()) { + actionView.setOnClickListener(v -> { + mHandler.post(() -> { + try { + action.getActionIntent().send(); + } catch (CanceledException e) { + Log.w(TAG, "Failed to send action", e); + } + }); + }); + } + actionView.setEnabled(action.isEnabled()); + actionView.setAlpha(action.isEnabled() ? 1f : DISABLED_ACTION_ALPHA); + + // Update the margin between actions + LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) + actionView.getLayoutParams(); + lp.leftMargin = (isLandscapePip && i > 0) ? mBetweenActionPaddingLand : 0; + } + } + + // Update the expand container margin to adjust the center of the expand button to + // account for the existence of the action container + FrameLayout.LayoutParams expandedLp = + (FrameLayout.LayoutParams) expandContainer.getLayoutParams(); + expandedLp.topMargin = getResources().getDimensionPixelSize( + R.dimen.pip_action_padding); + expandedLp.bottomMargin = getResources().getDimensionPixelSize( + R.dimen.pip_expand_container_edge_margin); + expandContainer.requestLayout(); + } + } + + void updateDismissFraction(float fraction) { + int alpha; + final float menuAlpha = 1 - fraction; + if (mMenuState == MENU_STATE_FULL) { + mMenuContainer.setAlpha(menuAlpha); + mSettingsButton.setAlpha(menuAlpha); + mDismissButton.setAlpha(menuAlpha); + final float interpolatedAlpha = + MENU_BACKGROUND_ALPHA * menuAlpha + DISMISS_BACKGROUND_ALPHA * fraction; + alpha = (int) (interpolatedAlpha * 255); + } else { + if (mMenuState == MENU_STATE_CLOSE) { + mDismissButton.setAlpha(menuAlpha); + } + alpha = (int) (fraction * DISMISS_BACKGROUND_ALPHA * 255); + } + mBackgroundDrawable.setAlpha(alpha); + } + + private void notifyMenuStateChange(int menuState, boolean resize, Runnable callback) { + mMenuState = menuState; + mController.onMenuStateChanged(menuState, resize, callback); + } + + private void expandPip() { + // Do not notify menu visibility when hiding the menu, the controller will do this when it + // handles the message + hideMenu(mController::onPipExpand, false /* notifyMenuVisibility */, true /* animate */); + } + + private void dismissPip() { + // Since tapping on the close-button invokes a double-tap wait callback in PipTouchHandler, + // we want to disable animating the fadeout animation of the buttons in order to call on + // PipTouchHandler#onPipDismiss fast enough. + final boolean animate = mMenuState != MENU_STATE_CLOSE; + // Do not notify menu visibility when hiding the menu, the controller will do this when it + // handles the message + hideMenu(mController::onPipDismiss, false /* notifyMenuVisibility */, animate); + } + + private void showSettings() { + final Pair topPipActivityInfo = + PipUtils.getTopPipActivity(mContext, ActivityManager.getService()); + if (topPipActivityInfo.first != null) { + final UserHandle user = UserHandle.of(topPipActivityInfo.second); + final Intent settingsIntent = new Intent(ACTION_PICTURE_IN_PICTURE_SETTINGS, + Uri.fromParts("package", topPipActivityInfo.first.getPackageName(), null)); + settingsIntent.putExtra(Intent.EXTRA_USER_HANDLE, user); + settingsIntent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK); + mContext.startActivity(settingsIntent); + } + } + + private void cancelDelayedHide() { + mHandler.removeCallbacks(mHideMenuRunnable); + } + + private void repostDelayedHide(int delay) { + int recommendedTimeout = mAccessibilityManager.getRecommendedTimeoutMillis(delay, + FLAG_CONTENT_ICONS | FLAG_CONTENT_CONTROLS); + mHandler.removeCallbacks(mHideMenuRunnable); + mHandler.postDelayed(mHideMenuRunnable, recommendedTimeout); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java index 19138fdba788..dcee2a5b4b20 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java @@ -23,6 +23,8 @@ import android.content.Context; import android.graphics.PointF; import android.graphics.Rect; import android.os.Debug; +import android.os.Handler; +import android.os.Looper; import android.util.Log; import android.view.Choreographer; @@ -66,6 +68,8 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, private PipMenuActivityController mMenuController; private PipSnapAlgorithm mSnapAlgorithm; + private final Handler mMainHandler = new Handler(Looper.getMainLooper()); + /** PIP's current bounds on the screen. */ private final Rect mBounds = new Rect(); @@ -128,8 +132,10 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, SpringForce.STIFFNESS_LOW, SpringForce.DAMPING_RATIO_LOW_BOUNCY); private final Consumer mUpdateBoundsCallback = (Rect newBounds) -> { - mMenuController.updateMenuLayout(newBounds); - mBounds.set(newBounds); + mMainHandler.post(() -> { + mMenuController.updateMenuLayout(newBounds); + mBounds.set(newBounds); + }); }; /** @@ -253,7 +259,9 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, mTemporaryBounds.set(toBounds); mPipTaskOrganizer.scheduleUserResizePip(mBounds, mTemporaryBounds, (Rect newBounds) -> { - mMenuController.updateMenuLayout(newBounds); + mMainHandler.post(() -> { + mMenuController.updateMenuLayout(newBounds); + }); }); } } else { diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java index 2800bb938149..f6853ecf8ee7 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java @@ -111,6 +111,7 @@ public class PipResizeGestureHandler { private InputMonitor mInputMonitor; private InputEventReceiver mInputEventReceiver; private PipTaskOrganizer mPipTaskOrganizer; + private PipMenuActivityController mPipMenuActivityController; private PipUiEventLogger mPipUiEventLogger; private int mCtrlType; @@ -119,7 +120,7 @@ public class PipResizeGestureHandler { PipMotionHelper motionHelper, DeviceConfigProxy deviceConfig, PipTaskOrganizer pipTaskOrganizer, Function movementBoundsSupplier, Runnable updateMovementBoundsRunnable, SysUiState sysUiState, - PipUiEventLogger pipUiEventLogger) { + PipUiEventLogger pipUiEventLogger, PipMenuActivityController menuActivityController) { mContext = context; mDisplayId = context.getDisplayId(); mMainExecutor = context.getMainExecutor(); @@ -129,6 +130,7 @@ public class PipResizeGestureHandler { mMovementBoundsSupplier = movementBoundsSupplier; mUpdateMovementBoundsRunnable = updateMovementBoundsRunnable; mSysUiState = sysUiState; + mPipMenuActivityController = menuActivityController; mPipUiEventLogger = pipUiEventLogger; context.getDisplay().getRealSize(mMaxSize); @@ -298,6 +300,7 @@ public class PipResizeGestureHandler { float x = ev.getX(); float y = ev.getY(); if (action == MotionEvent.ACTION_DOWN) { + final Rect currentPipBounds = mMotionHelper.getBounds(); mLastResizeBounds.setEmpty(); mAllowGesture = isInValidSysUiState() && isWithinTouchRegion((int) x, (int) y); if (mAllowGesture) { @@ -305,6 +308,10 @@ public class PipResizeGestureHandler { mDownPoint.set(x, y); mLastDownBounds.set(mMotionHelper.getBounds()); } + if (!currentPipBounds.contains((int) ev.getX(), (int) ev.getY()) + && mPipMenuActivityController.isMenuVisible()) { + mPipMenuActivityController.hideMenu(); + } } else if (mAllowGesture) { switch (action) { diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java index 1b84c1417c51..9693f235a4ff 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java @@ -230,7 +230,7 @@ public class PipTouchHandler { mPipResizeGestureHandler = new PipResizeGestureHandler(context, pipBoundsHandler, mMotionHelper, deviceConfig, pipTaskOrganizer, this::getMovementBounds, - this::updateMovementBounds, sysUiState, pipUiEventLogger); + this::updateMovementBounds, sysUiState, pipUiEventLogger, menuController); mTouchState = new PipTouchState(ViewConfiguration.get(context), mHandler, () -> mMenuController.showMenuWithDelay(MENU_STATE_FULL, mMotionHelper.getBounds(), true /* allowMenuTimeout */, willResizeMenu(), shouldShowResizeHandle()), @@ -773,10 +773,7 @@ public class PipTouchHandler { * Updates the appearance of the menu and scrim on top of the PiP while dismissing. */ private void updateDismissFraction() { - // Skip updating the dismiss fraction when the IME is showing. This is to work around an - // issue where starting the menu activity for the dismiss overlay will steal the window - // focus, which closes the IME. - if (mMenuController != null && !mIsImeShowing) { + if (mMenuController != null) { Rect bounds = mMotionHelper.getBounds(); final float target = mInsetBounds.bottom; float fraction = 0f; @@ -784,7 +781,7 @@ public class PipTouchHandler { final float distance = bounds.bottom - target; fraction = Math.min(distance / bounds.height(), 1f); } - if (Float.compare(fraction, 0f) != 0 || mMenuController.isMenuActivityVisible()) { + if (Float.compare(fraction, 0f) != 0 || mMenuController.isMenuVisible()) { // Update if the fraction > 0, or if fraction == 0 and the menu was already visible mMenuController.setDismissFraction(fraction); } diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/dagger/PipMenuActivityClass.java b/packages/SystemUI/src/com/android/systemui/pip/phone/dagger/PipMenuActivityClass.java deleted file mode 100644 index 114c30e625aa..000000000000 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/dagger/PipMenuActivityClass.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.pip.phone.dagger; - -import static java.lang.annotation.RetentionPolicy.RUNTIME; - -import java.lang.annotation.Documented; -import java.lang.annotation.Retention; - -import javax.inject.Qualifier; - -@Qualifier -@Documented -@Retention(RUNTIME) -public @interface PipMenuActivityClass { -} diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java index 7b4547689c8b..6a2ca44bfc17 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java @@ -22,8 +22,6 @@ import android.view.IWindowManager; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.pip.phone.PipMenuActivity; -import com.android.systemui.pip.phone.dagger.PipMenuActivityClass; import com.android.systemui.stackdivider.SplitScreen; import com.android.systemui.stackdivider.SplitScreenController; import com.android.wm.shell.ShellTaskOrganizer; @@ -50,14 +48,6 @@ public class WMShellModule { return new DisplayImeController(wmService, displayController, mainHandler, transactionPool); } - /** TODO(b/150319024): PipMenuActivity will move to a Window */ - @SysUISingleton - @PipMenuActivityClass - @Provides - static Class providePipMenuActivityClass() { - return PipMenuActivity.class; - } - @SysUISingleton @Provides static SplitScreen provideSplitScreen(Context context, -- cgit v1.2.3-59-g8ed1b