diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java new file mode 100644 index 0000000000..27de20cec4 --- /dev/null +++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java @@ -0,0 +1,1515 @@ +/* + * Copyright (C) 2019 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.quickstep; + +import static android.app.ActivityManager.RECENT_IGNORE_UNAVAILABLE; + +import static com.android.launcher3.testing.shared.TestProtocol.testLogD; +import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; +import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; +import static com.android.launcher3.util.SplitConfigurationOptions.StagePosition; +import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.RECENT_TASKS_MISSING; +import static com.android.quickstep.util.LogUtils.splitFailureMessage; + +import android.app.ActivityManager; +import android.app.ActivityOptions; +import android.app.PendingIntent; +import android.app.PictureInPictureParams; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +import android.content.pm.ShortcutInfo; +import android.graphics.Point; +import android.graphics.Rect; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.IBinder.DeathRecipient; +import android.os.Message; +import android.os.RemoteException; +import android.os.UserHandle; +import android.util.Log; +import android.view.IRecentsAnimationController; +import android.view.IRecentsAnimationRunner; +import android.view.IRemoteAnimationRunner; +import android.view.MotionEvent; +import android.view.RemoteAnimationAdapter; +import android.view.RemoteAnimationTarget; +import android.view.SurfaceControl; +import android.window.IOnBackInvokedCallback; +import android.window.RemoteTransition; +import android.window.TaskSnapshot; +import android.window.TransitionFilter; + +import androidx.annotation.MainThread; +import androidx.annotation.Nullable; +import androidx.annotation.WorkerThread; + +import com.android.internal.logging.InstanceId; +import com.android.internal.util.ScreenshotRequest; +import com.android.internal.view.AppearanceRegion; +import com.android.launcher3.config.FeatureFlags; +import com.android.launcher3.util.MainThreadInitializedObject; +import com.android.launcher3.util.Preconditions; +import com.android.quickstep.util.ActiveGestureLog; +import com.android.quickstep.util.AssistUtils; +import com.android.systemui.shared.recents.ISystemUiProxy; +import com.android.systemui.shared.recents.model.ThumbnailData; +import com.android.systemui.shared.system.RecentsAnimationControllerCompat; +import com.android.systemui.shared.system.RecentsAnimationListener; +import com.android.systemui.shared.system.smartspace.ILauncherUnlockAnimationController; +import com.android.systemui.shared.system.smartspace.ISysuiUnlockAnimationController; +import com.android.systemui.shared.system.smartspace.SmartspaceState; +import com.android.systemui.unfold.progress.IUnfoldAnimation; +import com.android.systemui.unfold.progress.IUnfoldTransitionListener; +import com.android.wm.shell.back.IBackAnimation; +import com.android.wm.shell.bubbles.IBubbles; +import com.android.wm.shell.bubbles.IBubblesListener; +import com.android.wm.shell.common.split.SplitScreenConstants.PersistentSnapPosition; +import com.android.wm.shell.desktopmode.IDesktopMode; +import com.android.wm.shell.desktopmode.IDesktopTaskListener; +import com.android.wm.shell.draganddrop.IDragAndDrop; +import com.android.wm.shell.onehanded.IOneHanded; +import com.android.wm.shell.pip.IPip; +import com.android.wm.shell.pip.IPipAnimationListener; +import com.android.wm.shell.recents.IRecentTasks; +import com.android.wm.shell.recents.IRecentTasksListener; +import com.android.wm.shell.splitscreen.ISplitScreen; +import com.android.wm.shell.splitscreen.ISplitScreenListener; +import com.android.wm.shell.splitscreen.ISplitSelectListener; +import com.android.wm.shell.startingsurface.IStartingWindow; +import com.android.wm.shell.startingsurface.IStartingWindowListener; +import com.android.wm.shell.transition.IHomeTransitionListener; +import com.android.wm.shell.transition.IShellTransitions; +import com.android.wm.shell.util.GroupedRecentTaskInfo; + +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedHashMap; + +/** + * Holds the reference to SystemUI. + */ +public class SystemUiProxy implements ISystemUiProxy { + private static final String TAG = SystemUiProxy.class.getSimpleName(); + + public static final MainThreadInitializedObject INSTANCE = + new MainThreadInitializedObject<>(SystemUiProxy::new); + + private static final int MSG_SET_SHELF_HEIGHT = 1; + private static final int MSG_SET_LAUNCHER_KEEP_CLEAR_AREA_HEIGHT = 2; + + private ISystemUiProxy mSystemUiProxy; + private IPip mPip; + private IBubbles mBubbles; + private ISysuiUnlockAnimationController mSysuiUnlockAnimationController; + private ISplitScreen mSplitScreen; + private IOneHanded mOneHanded; + private IShellTransitions mShellTransitions; + private IStartingWindow mStartingWindow; + private IRecentTasks mRecentTasks; + private IBackAnimation mBackAnimation; + private IDesktopMode mDesktopMode; + private IUnfoldAnimation mUnfoldAnimation; + private final DeathRecipient mSystemUiProxyDeathRecipient = () -> { + MAIN_EXECUTOR.execute(() -> clearProxy()); + }; + + // Save the listeners passed into the proxy since OverviewProxyService may not have been bound + // yet, and we'll need to set/register these listeners with SysUI when they do. Note that it is + // up to the caller to clear the listeners to prevent leaks as these can be held indefinitely + // in case SysUI needs to rebind. + private IPipAnimationListener mPipAnimationListener; + private IBubblesListener mBubblesListener; + private ISplitScreenListener mSplitScreenListener; + private ISplitSelectListener mSplitSelectListener; + private IStartingWindowListener mStartingWindowListener; + private ILauncherUnlockAnimationController mLauncherUnlockAnimationController; + private String mLauncherActivityClass; + private IRecentTasksListener mRecentTasksListener; + private IUnfoldTransitionListener mUnfoldAnimationListener; + private IDesktopTaskListener mDesktopTaskListener; + private final LinkedHashMap mRemoteTransitions = + new LinkedHashMap<>(); + private IBinder mOriginalTransactionToken = null; + private IOnBackInvokedCallback mBackToLauncherCallback; + private IRemoteAnimationRunner mBackToLauncherRunner; + private IDragAndDrop mDragAndDrop; + private IHomeTransitionListener mHomeTransitionListener; + + // Used to dedupe calls to SystemUI + private int mLastShelfHeight; + private boolean mLastShelfVisible; + + // Used to dedupe calls to SystemUI + private int mLastLauncherKeepClearAreaHeight; + private boolean mLastLauncherKeepClearAreaHeightVisible; + + private final Context mContext; + private final Handler mAsyncHandler; + + // TODO(141886704): Find a way to remove this + private int mLastSystemUiStateFlags; + + /** + * This is a singleton pending intent that is used to start recents via Shell (which is a + * different process). It is bare-bones, so it's expected that the component and options will + * be provided via fill-in intent. + */ + private final PendingIntent mRecentsPendingIntent; + + public SystemUiProxy(Context context) { + mContext = context; + mAsyncHandler = new Handler(UI_HELPER_EXECUTOR.getLooper(), this::handleMessageAsync); + final Intent baseIntent = new Intent().setPackage(mContext.getPackageName()); + final ActivityOptions options = ActivityOptions.makeBasic() + .setPendingIntentCreatorBackgroundActivityStartMode( + ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED); + mRecentsPendingIntent = PendingIntent.getActivity(mContext, 0, baseIntent, + PendingIntent.FLAG_MUTABLE | PendingIntent.FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT + | Intent.FILL_IN_COMPONENT, options.toBundle()); + } + + @Override + public void onBackPressed() { + if (mSystemUiProxy != null) { + try { + mSystemUiProxy.onBackPressed(); + } catch (RemoteException e) { + Log.w(TAG, "Failed call onBackPressed", e); + } + } + } + + @Override + public void onImeSwitcherPressed() { + if (mSystemUiProxy != null) { + try { + mSystemUiProxy.onImeSwitcherPressed(); + } catch (RemoteException e) { + Log.w(TAG, "Failed call onImeSwitcherPressed", e); + } + } + } + + @Override + public void setHomeRotationEnabled(boolean enabled) { + if (mSystemUiProxy != null) { + try { + mSystemUiProxy.setHomeRotationEnabled(enabled); + } catch (RemoteException e) { + Log.w(TAG, "Failed call onBackPressed", e); + } + } + } + + @Override + public IBinder asBinder() { + // Do nothing + return null; + } + + /** + * Sets proxy state, including death linkage, various listeners, and other configuration objects + */ + @MainThread + public void setProxy(ISystemUiProxy proxy, IPip pip, IBubbles bubbles, ISplitScreen splitScreen, + IOneHanded oneHanded, IShellTransitions shellTransitions, + IStartingWindow startingWindow, IRecentTasks recentTasks, + ISysuiUnlockAnimationController sysuiUnlockAnimationController, + IBackAnimation backAnimation, IDesktopMode desktopMode, + IUnfoldAnimation unfoldAnimation, IDragAndDrop dragAndDrop) { + Preconditions.assertUIThread(); + unlinkToDeath(); + mSystemUiProxy = proxy; + mPip = pip; + mBubbles = bubbles; + mSplitScreen = splitScreen; + mOneHanded = oneHanded; + mShellTransitions = shellTransitions; + mStartingWindow = startingWindow; + mSysuiUnlockAnimationController = sysuiUnlockAnimationController; + mRecentTasks = recentTasks; + mBackAnimation = backAnimation; + mDesktopMode = desktopMode; + mUnfoldAnimation = unfoldAnimation; + mDragAndDrop = dragAndDrop; + linkToDeath(); + // re-attach the listeners once missing due to setProxy has not been initialized yet. + setPipAnimationListener(mPipAnimationListener); + setBubblesListener(mBubblesListener); + registerSplitScreenListener(mSplitScreenListener); + registerSplitSelectListener(mSplitSelectListener); + setHomeTransitionListener(mHomeTransitionListener); + setStartingWindowListener(mStartingWindowListener); + setLauncherUnlockAnimationController( + mLauncherActivityClass, mLauncherUnlockAnimationController); + new LinkedHashMap<>(mRemoteTransitions).forEach(this::registerRemoteTransition); + setupTransactionQueue(); + registerRecentTasksListener(mRecentTasksListener); + setBackToLauncherCallback(mBackToLauncherCallback, mBackToLauncherRunner); + setUnfoldAnimationListener(mUnfoldAnimationListener); + setDesktopTaskListener(mDesktopTaskListener); + setAssistantOverridesRequested( + AssistUtils.newInstance(mContext).getSysUiAssistOverrideInvocationTypes()); + } + + /** + * Clear the proxy to release held resources and turn the majority of its operations into no-ops + */ + @MainThread + public void clearProxy() { + setProxy(null, null, null, null, null, null, null, null, null, null, null, null, null); + } + + // TODO(141886704): Find a way to remove this + public void setLastSystemUiStateFlags(int stateFlags) { + mLastSystemUiStateFlags = stateFlags; + } + + // TODO(141886704): Find a way to remove this + public int getLastSystemUiStateFlags() { + return mLastSystemUiStateFlags; + } + + public boolean isActive() { + return mSystemUiProxy != null; + } + + private void linkToDeath() { + if (mSystemUiProxy != null) { + try { + mSystemUiProxy.asBinder().linkToDeath(mSystemUiProxyDeathRecipient, 0 /* flags */); + } catch (RemoteException e) { + Log.e(TAG, "Failed to link sysui proxy death recipient"); + } + } + } + + private void unlinkToDeath() { + if (mSystemUiProxy != null) { + mSystemUiProxy.asBinder().unlinkToDeath(mSystemUiProxyDeathRecipient, 0 /* flags */); + } + } + + @Override + public void startScreenPinning(int taskId) { + if (mSystemUiProxy != null) { + try { + mSystemUiProxy.startScreenPinning(taskId); + } catch (RemoteException e) { + Log.w(TAG, "Failed call startScreenPinning", e); + } + } + } + + @Override + public void onOverviewShown(boolean fromHome) { + onOverviewShown(fromHome, TAG); + } + + public void onOverviewShown(boolean fromHome, String tag) { + if (mSystemUiProxy != null) { + try { + mSystemUiProxy.onOverviewShown(fromHome); + } catch (RemoteException e) { + Log.w(tag, "Failed call onOverviewShown from: " + (fromHome ? "home" : "app"), e); + } + } + } + + @MainThread + @Override + public void onStatusBarTouchEvent(MotionEvent event) { + Preconditions.assertUIThread(); + if (mSystemUiProxy != null) { + try { + mSystemUiProxy.onStatusBarTouchEvent(event); + } catch (RemoteException e) { + Log.w(TAG, "Failed call onStatusBarTouchEvent with arg: " + event, e); + } + } + } + + @Override + public void onStatusBarTrackpadEvent(MotionEvent event) { + if (mSystemUiProxy != null) { + try { + mSystemUiProxy.onStatusBarTrackpadEvent(event); + } catch (RemoteException e) { + Log.w(TAG, "Failed call onStatusBarTrackpadEvent with arg: " + event, e); + } + } + } + + @Override + public void onAssistantProgress(float progress) { + if (mSystemUiProxy != null) { + try { + mSystemUiProxy.onAssistantProgress(progress); + } catch (RemoteException e) { + Log.w(TAG, "Failed call onAssistantProgress with progress: " + progress, e); + } + } + } + + @Override + public void onAssistantGestureCompletion(float velocity) { + if (mSystemUiProxy != null) { + try { + mSystemUiProxy.onAssistantGestureCompletion(velocity); + } catch (RemoteException e) { + Log.w(TAG, "Failed call onAssistantGestureCompletion", e); + } + } + } + + @Override + public void startAssistant(Bundle args) { + if (mSystemUiProxy != null) { + try { + mSystemUiProxy.startAssistant(args); + } catch (RemoteException e) { + Log.w(TAG, "Failed call startAssistant", e); + } + } + } + + @Override + public void setAssistantOverridesRequested(int[] invocationTypes) { + if (mSystemUiProxy != null) { + try { + mSystemUiProxy.setAssistantOverridesRequested(invocationTypes); + } catch (RemoteException e) { + Log.w(TAG, "Failed call setAssistantOverridesRequested", e); + } + } + } + + @Override + public void animateNavBarLongPress(boolean isTouchDown, boolean shrink, long durationMs) { + if (mSystemUiProxy != null) { + try { + mSystemUiProxy.animateNavBarLongPress(isTouchDown, shrink, durationMs); + } catch (RemoteException e) { + Log.w(TAG, "Failed call animateNavBarLongPress", e); + } + } + } + + @Override + public void notifyAccessibilityButtonClicked(int displayId) { + if (mSystemUiProxy != null) { + try { + mSystemUiProxy.notifyAccessibilityButtonClicked(displayId); + } catch (RemoteException e) { + Log.w(TAG, "Failed call notifyAccessibilityButtonClicked", e); + } + } + } + + @Override + public void notifyAccessibilityButtonLongClicked() { + if (mSystemUiProxy != null) { + try { + mSystemUiProxy.notifyAccessibilityButtonLongClicked(); + } catch (RemoteException e) { + Log.w(TAG, "Failed call notifyAccessibilityButtonLongClicked", e); + } + } + } + + @Override + public void stopScreenPinning() { + if (mSystemUiProxy != null) { + try { + mSystemUiProxy.stopScreenPinning(); + } catch (RemoteException e) { + Log.w(TAG, "Failed call stopScreenPinning", e); + } + } + } + + @Override + public void notifyPrioritizedRotation(int rotation) { + if (mSystemUiProxy != null) { + try { + mSystemUiProxy.notifyPrioritizedRotation(rotation); + } catch (RemoteException e) { + Log.w(TAG, "Failed call notifyPrioritizedRotation with arg: " + rotation, e); + } + } + } + + @Override + public void notifyTaskbarStatus(boolean visible, boolean stashed) { + if (mSystemUiProxy != null) { + try { + mSystemUiProxy.notifyTaskbarStatus(visible, stashed); + } catch (RemoteException e) { + Log.w(TAG, "Failed call notifyTaskbarStatus with arg: " + + visible + ", " + stashed, e); + } + } + } + + /** + * NOTE: If called to suspend, caller MUST call this method to also un-suspend + * @param suspend should be true to stop auto-hide, false to resume normal behavior + */ + @Override + public void notifyTaskbarAutohideSuspend(boolean suspend) { + if (mSystemUiProxy != null) { + try { + mSystemUiProxy.notifyTaskbarAutohideSuspend(suspend); + } catch (RemoteException e) { + Log.w(TAG, "Failed call notifyTaskbarAutohideSuspend with arg: " + + suspend, e); + } + } + } + + @Override + public void takeScreenshot(ScreenshotRequest request) { + if (mSystemUiProxy != null) { + try { + mSystemUiProxy.takeScreenshot(request); + } catch (RemoteException e) { + Log.w(TAG, "Failed call takeScreenshot"); + } + } + } + + @Override + public void expandNotificationPanel() { + if (mSystemUiProxy != null) { + try { + mSystemUiProxy.expandNotificationPanel(); + } catch (RemoteException e) { + Log.w(TAG, "Failed call expandNotificationPanel", e); + } + } + } + + @Override + public void toggleNotificationPanel() { + if (mSystemUiProxy != null) { + try { + mSystemUiProxy.toggleNotificationPanel(); + } catch (RemoteException e) { + Log.w(TAG, "Failed call toggleNotificationPanel", e); + } + } + } + + // + // Pip + // + + /** + * Sets the shelf height. + */ + public void setShelfHeight(boolean visible, int shelfHeight) { + Message.obtain(mAsyncHandler, MSG_SET_SHELF_HEIGHT, + visible ? 1 : 0 , shelfHeight).sendToTarget(); + } + + @WorkerThread + private void setShelfHeightAsync(int visibleInt, int shelfHeight) { + boolean visible = visibleInt != 0; + boolean changed = visible != mLastShelfVisible || shelfHeight != mLastShelfHeight; + IPip pip = mPip; + if (pip != null && changed) { + mLastShelfVisible = visible; + mLastShelfHeight = shelfHeight; + try { + pip.setShelfHeight(visible, shelfHeight); + } catch (RemoteException e) { + Log.w(TAG, "Failed call setShelfHeight visible: " + visible + + " height: " + shelfHeight, e); + } + } + } + + /** + * Sets the height of the keep clear area that is going to be reported by + * the Launcher for the Hotseat. + */ + public void setLauncherKeepClearAreaHeight(boolean visible, int height) { + Message.obtain(mAsyncHandler, MSG_SET_LAUNCHER_KEEP_CLEAR_AREA_HEIGHT, + visible ? 1 : 0 , height).sendToTarget(); + } + + @WorkerThread + private void setLauncherKeepClearAreaHeight(int visibleInt, int height) { + boolean visible = visibleInt != 0; + boolean changed = visible != mLastLauncherKeepClearAreaHeightVisible + || height != mLastLauncherKeepClearAreaHeight; + IPip pip = mPip; + if (pip != null && changed) { + mLastLauncherKeepClearAreaHeightVisible = visible; + mLastLauncherKeepClearAreaHeight = height; + try { + pip.setLauncherKeepClearAreaHeight(visible, height); + } catch (RemoteException e) { + Log.w(TAG, "Failed call setLauncherKeepClearAreaHeight visible: " + visible + + " height: " + height, e); + } + } + } + + /** + * Sets listener to get pip animation callbacks. + */ + public void setPipAnimationListener(IPipAnimationListener listener) { + if (mPip != null) { + try { + mPip.setPipAnimationListener(listener); + } catch (RemoteException e) { + Log.w(TAG, "Failed call setPinnedStackAnimationListener", e); + } + } + mPipAnimationListener = listener; + } + + /** + * @return Destination bounds of auto-pip animation, {@code null} if the animation is not ready. + */ + @Nullable + public Rect startSwipePipToHome(ComponentName componentName, ActivityInfo activityInfo, + PictureInPictureParams pictureInPictureParams, int launcherRotation, + Rect hotseatKeepClearArea) { + if (mPip != null) { + try { + return mPip.startSwipePipToHome(componentName, activityInfo, + pictureInPictureParams, launcherRotation, hotseatKeepClearArea); + } catch (RemoteException e) { + Log.w(TAG, "Failed call startSwipePipToHome", e); + } + } + return null; + } + + /** + * Notifies WM Shell that launcher has finished the preparation of the animation for swipe to + * home. WM Shell can choose to fade out the overlay when entering PIP is finished, and WM Shell + * should be responsible for cleaning up the overlay. + */ + public void stopSwipePipToHome(int taskId, ComponentName componentName, Rect destinationBounds, + SurfaceControl overlay) { + if (mPip != null) { + try { + mPip.stopSwipePipToHome(taskId, componentName, destinationBounds, overlay); + } catch (RemoteException e) { + Log.w(TAG, "Failed call stopSwipePipToHome"); + } + } + } + + /** + * Notifies WM Shell that launcher has aborted all the animation for swipe to home. WM Shell + * can use this callback to clean up its internal states. + */ + public void abortSwipePipToHome(int taskId, ComponentName componentName) { + if (mPip != null) { + try { + mPip.abortSwipePipToHome(taskId, componentName); + } catch (RemoteException e) { + Log.w(TAG, "Failed call abortSwipePipToHome"); + } + } + } + + /** + * Sets the next pip animation type to be the alpha animation. + */ + public void setPipAnimationTypeToAlpha() { + if (mPip != null) { + try { + mPip.setPipAnimationTypeToAlpha(); + } catch (RemoteException e) { + Log.w(TAG, "Failed call setPipAnimationTypeToAlpha", e); + } + } + } + + /** + * Sets the app icon size in pixel used by Launcher all apps. + */ + public void setLauncherAppIconSize(int iconSizePx) { + if (mPip != null) { + try { + mPip.setLauncherAppIconSize(iconSizePx); + } catch (RemoteException e) { + Log.w(TAG, "Failed call setLauncherAppIconSize", e); + } + } + } + + // + // Bubbles + // + + /** + * Sets the listener to be notified of bubble state changes. + */ + public void setBubblesListener(IBubblesListener listener) { + if (mBubbles != null) { + try { + if (mBubblesListener != null) { + // Clear out any previous listener + mBubbles.unregisterBubbleListener(mBubblesListener); + } + if (listener != null) { + mBubbles.registerBubbleListener(listener); + } + } catch (RemoteException e) { + Log.w(TAG, "Failed call registerBubblesListener"); + } + } + mBubblesListener = listener; + } + + /** + * Tells SysUI to show the bubble with the provided key. + * @param key the key of the bubble to show. + * @param bubbleBarOffsetX the offset of the bubble bar from the edge of the screen on the X + * axis. + * @param bubbleBarOffsetY the offset of the bubble bar from the edge of the screen on the Y + * axis. + */ + public void showBubble(String key, int bubbleBarOffsetX, int bubbleBarOffsetY) { + if (mBubbles != null) { + try { + mBubbles.showBubble(key, bubbleBarOffsetX, bubbleBarOffsetY); + } catch (RemoteException e) { + Log.w(TAG, "Failed call showBubble"); + } + } + } + + /** + * Tells SysUI to remove the bubble with the provided key. + * @param key the key of the bubble to show. + */ + public void removeBubble(String key) { + if (mBubbles == null) return; + try { + mBubbles.removeBubble(key); + } catch (RemoteException e) { + Log.w(TAG, "Failed call removeBubble"); + } + } + + /** + * Tells SysUI to remove all bubbles. + */ + public void removeAllBubbles() { + if (mBubbles == null) return; + try { + mBubbles.removeAllBubbles(); + } catch (RemoteException e) { + Log.w(TAG, "Failed call removeAllBubbles"); + } + } + + /** + * Tells SysUI to collapse the bubbles. + */ + public void collapseBubbles() { + if (mBubbles != null) { + try { + mBubbles.collapseBubbles(); + } catch (RemoteException e) { + Log.w(TAG, "Failed call collapseBubbles"); + } + } + } + + /** + * Tells SysUI when the bubble is being dragged. + * Should be called only when the bubble bar is expanded. + * @param bubbleKey the key of the bubble to collapse/expand + * @param isBeingDragged whether the bubble is being dragged + */ + public void onBubbleDrag(@Nullable String bubbleKey, boolean isBeingDragged) { + if (mBubbles == null) return; + try { + mBubbles.onBubbleDrag(bubbleKey, isBeingDragged); + } catch (RemoteException e) { + Log.w(TAG, "Failed call onBubbleDrag"); + } + } + + /** + * Tells SysUI to show user education relative to the reference point provided. + * @param position the bubble bar top center position in Screen coordinates. + */ + public void showUserEducation(Point position) { + try { + mBubbles.showUserEducation(position.x, position.y); + } catch (RemoteException e) { + Log.w(TAG, "Failed call showUserEducation"); + } + } + + // + // Splitscreen + // + + public void registerSplitScreenListener(ISplitScreenListener listener) { + if (mSplitScreen != null) { + try { + mSplitScreen.registerSplitScreenListener(listener); + } catch (RemoteException e) { + Log.w(TAG, "Failed call registerSplitScreenListener"); + } + } + mSplitScreenListener = listener; + } + + public void unregisterSplitScreenListener(ISplitScreenListener listener) { + if (mSplitScreen != null) { + try { + mSplitScreen.unregisterSplitScreenListener(listener); + } catch (RemoteException e) { + Log.w(TAG, "Failed call unregisterSplitScreenListener"); + } + } + mSplitScreenListener = null; + } + + public void registerSplitSelectListener(ISplitSelectListener listener) { + if (mSplitScreen != null) { + try { + mSplitScreen.registerSplitSelectListener(listener); + } catch (RemoteException e) { + Log.w(TAG, "Failed call registerSplitSelectListener"); + } + } + mSplitSelectListener = listener; + } + + public void unregisterSplitSelectListener(ISplitSelectListener listener) { + if (mSplitScreen != null) { + try { + mSplitScreen.unregisterSplitSelectListener(listener); + } catch (RemoteException e) { + Log.w(TAG, "Failed call unregisterSplitSelectListener"); + } + } + mSplitSelectListener = null; + } + + /** Start multiple tasks in split-screen simultaneously. */ + public void startTasks(int taskId1, Bundle options1, int taskId2, Bundle options2, + @StagePosition int splitPosition, @PersistentSnapPosition int snapPosition, + RemoteTransition remoteTransition, InstanceId instanceId) { + if (mSystemUiProxy != null) { + try { + mSplitScreen.startTasks(taskId1, options1, taskId2, options2, splitPosition, + snapPosition, remoteTransition, instanceId); + } catch (RemoteException e) { + Log.w(TAG, splitFailureMessage("startTasks", "RemoteException"), e); + } + } + } + + public void startIntentAndTask(PendingIntent pendingIntent, int userId1, Bundle options1, + int taskId, Bundle options2, @StagePosition int splitPosition, + @PersistentSnapPosition int snapPosition, RemoteTransition remoteTransition, + InstanceId instanceId) { + if (mSystemUiProxy != null) { + try { + mSplitScreen.startIntentAndTask(pendingIntent, userId1, options1, taskId, options2, + splitPosition, snapPosition, remoteTransition, instanceId); + } catch (RemoteException e) { + Log.w(TAG, splitFailureMessage("startIntentAndTask", "RemoteException"), e); + } + } + } + + public void startIntents(PendingIntent pendingIntent1, int userId1, + @Nullable ShortcutInfo shortcutInfo1, Bundle options1, PendingIntent pendingIntent2, + int userId2, @Nullable ShortcutInfo shortcutInfo2, Bundle options2, + @StagePosition int splitPosition, @PersistentSnapPosition int snapPosition, + RemoteTransition remoteTransition, InstanceId instanceId) { + if (mSystemUiProxy != null) { + try { + mSplitScreen.startIntents(pendingIntent1, userId1, shortcutInfo1, options1, + pendingIntent2, userId2, shortcutInfo2, options2, splitPosition, + snapPosition, remoteTransition, instanceId); + } catch (RemoteException e) { + Log.w(TAG, splitFailureMessage("startIntents", "RemoteException"), e); + } + } + } + + public void startShortcutAndTask(ShortcutInfo shortcutInfo, Bundle options1, int taskId, + Bundle options2, @StagePosition int splitPosition, + @PersistentSnapPosition int snapPosition, RemoteTransition remoteTransition, + InstanceId instanceId) { + if (mSystemUiProxy != null) { + try { + mSplitScreen.startShortcutAndTask(shortcutInfo, options1, taskId, options2, + splitPosition, snapPosition, remoteTransition, instanceId); + } catch (RemoteException e) { + Log.w(TAG, splitFailureMessage("startShortcutAndTask", "RemoteException"), e); + } + } + } + + /** + * Start multiple tasks in split-screen simultaneously. + */ + public void startTasksWithLegacyTransition(int taskId1, Bundle options1, int taskId2, + Bundle options2, @StagePosition int splitPosition, + @PersistentSnapPosition int snapPosition, RemoteAnimationAdapter adapter, + InstanceId instanceId) { + if (mSystemUiProxy != null) { + try { + mSplitScreen.startTasksWithLegacyTransition(taskId1, options1, taskId2, options2, + splitPosition, snapPosition, adapter, instanceId); + } catch (RemoteException e) { + Log.w(TAG, splitFailureMessage( + "startTasksWithLegacyTransition", "RemoteException"), e); + } + } + } + + public void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent, int userId1, + Bundle options1, int taskId, Bundle options2, @StagePosition int splitPosition, + @PersistentSnapPosition int snapPosition, RemoteAnimationAdapter adapter, + InstanceId instanceId) { + if (mSystemUiProxy != null) { + try { + mSplitScreen.startIntentAndTaskWithLegacyTransition(pendingIntent, userId1, + options1, taskId, options2, splitPosition, snapPosition, adapter, + instanceId); + } catch (RemoteException e) { + Log.w(TAG, splitFailureMessage( + "startIntentAndTaskWithLegacyTransition", "RemoteException"), e); + } + } + } + + public void startShortcutAndTaskWithLegacyTransition(ShortcutInfo shortcutInfo, Bundle options1, + int taskId, Bundle options2, @StagePosition int splitPosition, + @PersistentSnapPosition int snapPosition, RemoteAnimationAdapter adapter, + InstanceId instanceId) { + if (mSystemUiProxy != null) { + try { + mSplitScreen.startShortcutAndTaskWithLegacyTransition(shortcutInfo, options1, + taskId, options2, splitPosition, snapPosition, adapter, instanceId); + } catch (RemoteException e) { + Log.w(TAG, splitFailureMessage( + "startShortcutAndTaskWithLegacyTransition", "RemoteException"), e); + } + } + } + + /** + * Starts a pair of intents or shortcuts in split-screen using legacy transition. Passing a + * non-null shortcut info means to start the app as a shortcut. + */ + public void startIntentsWithLegacyTransition(PendingIntent pendingIntent1, int userId1, + @Nullable ShortcutInfo shortcutInfo1, @Nullable Bundle options1, + PendingIntent pendingIntent2, int userId2, @Nullable ShortcutInfo shortcutInfo2, + @Nullable Bundle options2, @StagePosition int sidePosition, + @PersistentSnapPosition int snapPosition, RemoteAnimationAdapter adapter, + InstanceId instanceId) { + if (mSystemUiProxy != null) { + try { + mSplitScreen.startIntentsWithLegacyTransition(pendingIntent1, userId1, + shortcutInfo1, options1, pendingIntent2, userId2, shortcutInfo2, options2, + sidePosition, snapPosition, adapter, instanceId); + } catch (RemoteException e) { + Log.w(TAG, splitFailureMessage( + "startIntentsWithLegacyTransition", "RemoteException"), e); + } + } + } + + public void startShortcut(String packageName, String shortcutId, int position, + Bundle options, UserHandle user, InstanceId instanceId) { + if (mSplitScreen != null) { + try { + mSplitScreen.startShortcut(packageName, shortcutId, position, options, + user, instanceId); + } catch (RemoteException e) { + Log.w(TAG, splitFailureMessage("startShortcut", "RemoteException"), e); + } + } + } + + public void startIntent(PendingIntent intent, int userId, Intent fillInIntent, int position, + Bundle options, InstanceId instanceId) { + if (mSplitScreen != null) { + try { + mSplitScreen.startIntent(intent, userId, fillInIntent, position, options, + instanceId); + } catch (RemoteException e) { + Log.w(TAG, splitFailureMessage("startIntent", "RemoteException"), e); + } + } + } + + public void removeFromSideStage(int taskId) { + if (mSplitScreen != null) { + try { + mSplitScreen.removeFromSideStage(taskId); + } catch (RemoteException e) { + Log.w(TAG, "Failed call removeFromSideStage"); + } + } + } + + /** + * Call this when going to recents so that shell can set-up and provide appropriate leashes + * for animation (eg. DividerBar). + * + * @return RemoteAnimationTargets of windows that need to animate but only exist in shell. + */ + @Nullable + public RemoteAnimationTarget[] onGoingToRecentsLegacy(RemoteAnimationTarget[] apps) { + if (!TaskAnimationManager.ENABLE_SHELL_TRANSITIONS && mSplitScreen != null) { + try { + return mSplitScreen.onGoingToRecentsLegacy(apps); + } catch (RemoteException e) { + Log.w(TAG, "Failed call onGoingToRecentsLegacy"); + } + } + return null; + } + + @Nullable + public RemoteAnimationTarget[] onStartingSplitLegacy(RemoteAnimationTarget[] apps) { + if (mSplitScreen != null) { + try { + return mSplitScreen.onStartingSplitLegacy(apps); + } catch (RemoteException e) { + Log.w(TAG, "Failed call onStartingSplitLegacy"); + } + } + return null; + } + + // + // One handed + // + + public void startOneHandedMode() { + if (mOneHanded != null) { + try { + mOneHanded.startOneHanded(); + } catch (RemoteException e) { + Log.w(TAG, "Failed call startOneHandedMode", e); + } + } + } + + public void stopOneHandedMode() { + if (mOneHanded != null) { + try { + mOneHanded.stopOneHanded(); + } catch (RemoteException e) { + Log.w(TAG, "Failed call stopOneHandedMode", e); + } + } + } + + // + // Remote transitions + // + + public void registerRemoteTransition( + RemoteTransition remoteTransition, TransitionFilter filter) { + if (mShellTransitions != null) { + try { + mShellTransitions.registerRemote(filter, remoteTransition); + } catch (RemoteException e) { + Log.w(TAG, "Failed call registerRemoteTransition"); + } + } + if (!mRemoteTransitions.containsKey(remoteTransition)) { + mRemoteTransitions.put(remoteTransition, filter); + } + } + + public void unregisterRemoteTransition(RemoteTransition remoteTransition) { + if (mShellTransitions != null) { + try { + mShellTransitions.unregisterRemote(remoteTransition); + } catch (RemoteException e) { + Log.w(TAG, "Failed call registerRemoteTransition"); + } + } + mRemoteTransitions.remove(remoteTransition); + } + + public void setHomeTransitionListener(IHomeTransitionListener listener) { + if (!FeatureFlags.enableHomeTransitionListener()) { + return; + } + + mHomeTransitionListener = listener; + + if (mShellTransitions != null) { + try { + mShellTransitions.setHomeTransitionListener(listener); + } catch (RemoteException e) { + Log.w(TAG, "Failed call setHomeTransitionListener", e); + } + } else { + Log.w(TAG, "Unable to call setHomeTransitionListener because ShellTransitions is null"); + } + } + + /** + * Use SystemUI's transaction-queue instead of Launcher's independent one. This is necessary + * if Launcher and SystemUI need to coordinate transactions (eg. for shell transitions). + */ + public void shareTransactionQueue() { + if (mOriginalTransactionToken == null) { + mOriginalTransactionToken = SurfaceControl.Transaction.getDefaultApplyToken(); + } + setupTransactionQueue(); + } + + /** + * Switch back to using Launcher's independent transaction queue. + */ + public void unshareTransactionQueue() { + if (mOriginalTransactionToken == null) { + return; + } + SurfaceControl.Transaction.setDefaultApplyToken(mOriginalTransactionToken); + mOriginalTransactionToken = null; + } + + private void setupTransactionQueue() { + if (mOriginalTransactionToken == null) { + return; + } + if (mShellTransitions == null) { + SurfaceControl.Transaction.setDefaultApplyToken(mOriginalTransactionToken); + return; + } + final IBinder shellApplyToken; + try { + shellApplyToken = mShellTransitions.getShellApplyToken(); + } catch (RemoteException e) { + Log.e(TAG, "Error getting Shell's apply token", e); + return; + } + if (shellApplyToken == null) { + Log.e(TAG, "Didn't receive apply token from Shell"); + return; + } + SurfaceControl.Transaction.setDefaultApplyToken(shellApplyToken); + } + + // + // Starting window + // + + /** + * Sets listener to get callbacks when launching a task. + */ + public void setStartingWindowListener(IStartingWindowListener listener) { + if (mStartingWindow != null) { + try { + mStartingWindow.setStartingWindowListener(listener); + } catch (RemoteException e) { + Log.w(TAG, "Failed call setStartingWindowListener", e); + } + } + mStartingWindowListener = listener; + } + + // + // SmartSpace transitions + // + + /** + * Sets the instance of {@link ILauncherUnlockAnimationController} that System UI should use to + * control the launcher side of the unlock animation. This will also cause us to dispatch the + * current state of the smartspace to System UI (this will subsequently happen if the state + * changes). + */ + public void setLauncherUnlockAnimationController( + String activityClass, ILauncherUnlockAnimationController controller) { + if (mSysuiUnlockAnimationController != null) { + try { + mSysuiUnlockAnimationController.setLauncherUnlockController( + activityClass, controller); + if (controller != null) { + controller.dispatchSmartspaceStateToSysui(); + } + } catch (RemoteException e) { + Log.w(TAG, "Failed call setLauncherUnlockAnimationController", e); + } + } + mLauncherActivityClass = activityClass; + mLauncherUnlockAnimationController = controller; + } + + /** + * Tells System UI that the Launcher's smartspace state has been updated, so that it can prepare + * the unlock animation accordingly. + */ + public void notifySysuiSmartspaceStateUpdated(SmartspaceState state) { + if (mSysuiUnlockAnimationController != null) { + try { + mSysuiUnlockAnimationController.onLauncherSmartspaceStateUpdated(state); + } catch (RemoteException e) { + Log.w(TAG, "Failed call notifySysuiSmartspaceStateUpdated", e); + e.printStackTrace(); + } + } + } + + // + // Recents + // + + public void registerRecentTasksListener(IRecentTasksListener listener) { + if (mRecentTasks != null) { + try { + mRecentTasks.registerRecentTasksListener(listener); + } catch (RemoteException e) { + Log.w(TAG, "Failed call registerRecentTasksListener", e); + } + } + mRecentTasksListener = listener; + } + + public void unregisterRecentTasksListener(IRecentTasksListener listener) { + if (mRecentTasks != null) { + try { + mRecentTasks.unregisterRecentTasksListener(listener); + } catch (RemoteException e) { + Log.w(TAG, "Failed call unregisterRecentTasksListener"); + } + } + mRecentTasksListener = null; + } + + // + // Back navigation transitions + // + + /** Sets the launcher {@link android.window.IOnBackInvokedCallback} to shell */ + public void setBackToLauncherCallback(IOnBackInvokedCallback callback, + IRemoteAnimationRunner runner) { + mBackToLauncherCallback = callback; + mBackToLauncherRunner = runner; + if (mBackAnimation == null || mBackToLauncherCallback == null) { + return; + } + try { + mBackAnimation.setBackToLauncherCallback(callback, runner); + } catch (RemoteException | SecurityException e) { + Log.e(TAG, "Failed call setBackToLauncherCallback", e); + } + } + + /** Clears the previously registered {@link IOnBackInvokedCallback}. + * + * @param callback The previously registered callback instance. + */ + public void clearBackToLauncherCallback(IOnBackInvokedCallback callback) { + if (mBackToLauncherCallback != callback) { + return; + } + mBackToLauncherCallback = null; + mBackToLauncherRunner = null; + if (mBackAnimation == null) { + return; + } + try { + mBackAnimation.clearBackToLauncherCallback(); + } catch (RemoteException e) { + Log.e(TAG, "Failed call clearBackToLauncherCallback", e); + } + } + + /** + * Called when the status bar color needs to be customized when back navigation. + */ + public void customizeStatusBarAppearance(AppearanceRegion appearance) { + if (mBackAnimation == null) { + return; + } + try { + mBackAnimation.customizeStatusBarAppearance(appearance); + } catch (RemoteException e) { + Log.e(TAG, "Failed call useLauncherSysBarFlags", e); + } + } + + public ArrayList getRecentTasks(int numTasks, int userId) { + if (mRecentTasks == null) { + Log.w(TAG, "getRecentTasks() failed due to null mRecentTasks"); + return new ArrayList<>(); + } + try { + final GroupedRecentTaskInfo[] rawTasks = mRecentTasks.getRecentTasks(numTasks, + RECENT_IGNORE_UNAVAILABLE, userId); + if (rawTasks == null) { + return new ArrayList<>(); + } + return new ArrayList<>(Arrays.asList(rawTasks)); + } catch (RemoteException e) { + Log.w(TAG, "Failed call getRecentTasks", e); + return new ArrayList<>(); + } + } + + /** + * Gets the set of running tasks. + */ + public ArrayList getRunningTasks(int numTasks) { + if (mRecentTasks != null + && mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_PC)) { + try { + return new ArrayList<>(Arrays.asList(mRecentTasks.getRunningTasks(numTasks))); + } catch (RemoteException e) { + Log.w(TAG, "Failed call getRunningTasks", e); + } + } + return new ArrayList<>(); + } + + private boolean handleMessageAsync(Message msg) { + switch (msg.what) { + case MSG_SET_SHELF_HEIGHT: + setShelfHeightAsync(msg.arg1, msg.arg2); + return true; + case MSG_SET_LAUNCHER_KEEP_CLEAR_AREA_HEIGHT: + setLauncherKeepClearAreaHeight(msg.arg1, msg.arg2); + return true; + } + + return false; + } + + // + // Desktop Mode + // + + /** Call shell to show all apps active on the desktop */ + public void showDesktopApps(int displayId, @Nullable RemoteTransition transition) { + if (mDesktopMode != null) { + try { + mDesktopMode.showDesktopApps(displayId, transition); + } catch (RemoteException e) { + Log.w(TAG, "Failed call showDesktopApps", e); + } + } + } + + /** Call shell to stash desktop apps */ + public void stashDesktopApps(int displayId) { + if (mDesktopMode != null) { + try { + mDesktopMode.stashDesktopApps(displayId); + } catch (RemoteException e) { + Log.w(TAG, "Failed call stashDesktopApps", e); + } + } + } + + /** Call shell to hide desktop apps that may be stashed */ + public void hideStashedDesktopApps(int displayId) { + if (mDesktopMode != null) { + try { + mDesktopMode.hideStashedDesktopApps(displayId); + } catch (RemoteException e) { + Log.w(TAG, "Failed call hideStashedDesktopApps", e); + } + } + } + + /** + * If task with the given id is on the desktop, bring it to front + */ + public void showDesktopApp(int taskId) { + if (mDesktopMode != null) { + try { + mDesktopMode.showDesktopApp(taskId); + } catch (RemoteException e) { + Log.w(TAG, "Failed call showDesktopApp", e); + } + } + } + + /** Call shell to get number of visible freeform tasks */ + public int getVisibleDesktopTaskCount(int displayId) { + if (mDesktopMode != null) { + try { + return mDesktopMode.getVisibleTaskCount(displayId); + } catch (RemoteException e) { + Log.w(TAG, "Failed call getVisibleDesktopTaskCount", e); + } + } + return 0; + } + + /** Set a listener on shell to get updates about desktop task state */ + public void setDesktopTaskListener(@Nullable IDesktopTaskListener listener) { + mDesktopTaskListener = listener; + if (mDesktopMode != null) { + try { + mDesktopMode.setTaskListener(listener); + } catch (RemoteException e) { + Log.w(TAG, "Failed call setDesktopTaskListener", e); + } + } + } + + /** Perform cleanup transactions after animation to split select is complete */ + public void onDesktopSplitSelectAnimComplete(ActivityManager.RunningTaskInfo taskInfo) { + if (mDesktopMode != null) { + try { + mDesktopMode.onDesktopSplitSelectAnimComplete(taskInfo); + } catch (RemoteException e) { + Log.w(TAG, "Failed call onDesktopSplitSelectAnimComplete", e); + } + } + } + + // + // Unfold transition + // + + /** Sets the unfold animation lister to sysui. */ + public void setUnfoldAnimationListener(IUnfoldTransitionListener callback) { + mUnfoldAnimationListener = callback; + if (mUnfoldAnimation == null) { + return; + } + try { + Log.d(TAG, "Registering unfold animation receiver"); + mUnfoldAnimation.setListener(callback); + } catch (RemoteException e) { + Log.e(TAG, "Failed call setUnfoldAnimationListener", e); + } + } + + // + // Recents + // + + /** + * Starts the recents activity. The caller should manage the thread on which this is called. + */ + public boolean startRecentsActivity(Intent intent, ActivityOptions options, + RecentsAnimationListener listener) { + if (mRecentTasks == null) { + ActiveGestureLog.INSTANCE.addLog("Null mRecentTasks", RECENT_TASKS_MISSING); + return false; + } + final IRecentsAnimationRunner runner = new IRecentsAnimationRunner.Stub() { + @Override + public void onAnimationStart(IRecentsAnimationController controller, + RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers, + Rect homeContentInsets, Rect minimizedHomeBounds, Bundle extras) { + // Aidl bundles need to explicitly set class loader + // https://developer.android.com/guide/components/aidl#Bundles + if (extras != null) { + extras.setClassLoader(getClass().getClassLoader()); + } + listener.onAnimationStart(new RecentsAnimationControllerCompat(controller), apps, + wallpapers, homeContentInsets, minimizedHomeBounds, extras); + } + + @Override + public void onAnimationCanceled(int[] taskIds, TaskSnapshot[] taskSnapshots) { + listener.onAnimationCanceled( + ThumbnailData.wrap(taskIds, taskSnapshots)); + } + + @Override + public void onTasksAppeared(RemoteAnimationTarget[] apps) { + listener.onTasksAppeared(apps); + } + }; + final Bundle optsBundle = options.toBundle(); + try { + mRecentTasks.startRecentsTransition(mRecentsPendingIntent, intent, optsBundle, + mContext.getIApplicationThread(), runner); + return true; + } catch (RemoteException e) { + Log.e(TAG, "Error starting recents via shell", e); + return false; + } + } + + // + // Drag and drop + // + + /** + * For testing purposes. Returns `true` only if the shell drop target has shown and + * drawn and is ready to handle drag events and the subsequent drop. + */ + public boolean isDragAndDropReady() { + if (mDragAndDrop == null) { + return false; + } + try { + return mDragAndDrop.isReadyToHandleDrag(); + } catch (RemoteException e) { + Log.e(TAG, "Error querying drag state", e); + return false; + } + } + + public void dump(PrintWriter pw) { + pw.println(TAG + ":"); + + pw.println("\tmSystemUiProxy=" + mSystemUiProxy); + pw.println("\tmPip=" + mPip); + pw.println("\tmPipAnimationListener=" + mPipAnimationListener); + pw.println("\tmBubbles=" + mBubbles); + pw.println("\tmBubblesListener=" + mBubblesListener); + pw.println("\tmSplitScreen=" + mSplitScreen); + pw.println("\tmSplitScreenListener=" + mSplitScreenListener); + pw.println("\tmSplitSelectListener=" + mSplitSelectListener); + pw.println("\tmOneHanded=" + mOneHanded); + pw.println("\tmShellTransitions=" + mShellTransitions); + pw.println("\tmHomeTransitionListener=" + mHomeTransitionListener); + pw.println("\tmStartingWindow=" + mStartingWindow); + pw.println("\tmStartingWindowListener=" + mStartingWindowListener); + pw.println("\tmSysuiUnlockAnimationController=" + mSysuiUnlockAnimationController); + pw.println("\tmLauncherActivityClass=" + mLauncherActivityClass); + pw.println("\tmLauncherUnlockAnimationController=" + mLauncherUnlockAnimationController); + pw.println("\tmRecentTasks=" + mRecentTasks); + pw.println("\tmRecentTasksListener=" + mRecentTasksListener); + pw.println("\tmBackAnimation=" + mBackAnimation); + pw.println("\tmBackToLauncherCallback=" + mBackToLauncherCallback); + pw.println("\tmBackToLauncherRunner=" + mBackToLauncherRunner); + pw.println("\tmDesktopMode=" + mDesktopMode); + pw.println("\tmDesktopTaskListener=" + mDesktopTaskListener); + pw.println("\tmUnfoldAnimation=" + mUnfoldAnimation); + pw.println("\tmUnfoldAnimationListener=" + mUnfoldAnimationListener); + pw.println("\tmDragAndDrop=" + mDragAndDrop); + } +} diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.kt b/quickstep/src/com/android/quickstep/SystemUiProxy.kt deleted file mode 100644 index c70642a9ac..0000000000 --- a/quickstep/src/com/android/quickstep/SystemUiProxy.kt +++ /dev/null @@ -1,1232 +0,0 @@ -/* - * Copyright (C) 2019 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.quickstep - -import android.app.ActivityManager -import android.app.ActivityManager.RunningTaskInfo -import android.app.ActivityOptions -import android.app.PendingIntent -import android.app.PictureInPictureParams -import android.content.ComponentName -import android.content.Context -import android.content.Intent -import android.content.pm.ActivityInfo -import android.content.pm.PackageManager -import android.content.pm.ShortcutInfo -import android.graphics.Point -import android.graphics.Rect -import android.os.Bundle -import android.os.Handler -import android.os.IBinder -import android.os.Message -import android.os.RemoteException -import android.os.UserHandle -import android.util.Log -import android.view.IRecentsAnimationController -import android.view.IRecentsAnimationRunner -import android.view.IRemoteAnimationRunner -import android.view.MotionEvent -import android.view.RemoteAnimationAdapter -import android.view.RemoteAnimationTarget -import android.view.SurfaceControl -import android.window.IOnBackInvokedCallback -import android.window.RemoteTransition -import android.window.TaskSnapshot -import android.window.TransitionFilter -import androidx.annotation.MainThread -import androidx.annotation.WorkerThread -import com.android.internal.logging.InstanceId -import com.android.internal.util.ScreenshotRequest -import com.android.internal.view.AppearanceRegion -import com.android.launcher3.config.FeatureFlags -import com.android.launcher3.util.Executors -import com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR -import com.android.launcher3.util.MainThreadInitializedObject -import com.android.launcher3.util.Preconditions -import com.android.launcher3.util.SplitConfigurationOptions.StagePosition -import com.android.quickstep.util.ActiveGestureErrorDetector -import com.android.quickstep.util.ActiveGestureLog -import com.android.quickstep.util.AssistUtils -import com.android.quickstep.util.LogUtils.splitFailureMessage -import com.android.systemui.shared.recents.ISystemUiProxy -import com.android.systemui.shared.recents.model.ThumbnailData -import com.android.systemui.shared.system.RecentsAnimationControllerCompat -import com.android.systemui.shared.system.RecentsAnimationListener -import com.android.systemui.shared.system.smartspace.ILauncherUnlockAnimationController -import com.android.systemui.shared.system.smartspace.ISysuiUnlockAnimationController -import com.android.systemui.shared.system.smartspace.SmartspaceState -import com.android.systemui.unfold.progress.IUnfoldAnimation -import com.android.systemui.unfold.progress.IUnfoldTransitionListener -import com.android.wm.shell.back.IBackAnimation -import com.android.wm.shell.bubbles.IBubbles -import com.android.wm.shell.bubbles.IBubblesListener -import com.android.wm.shell.common.split.SplitScreenConstants.PersistentSnapPosition -import com.android.wm.shell.desktopmode.IDesktopMode -import com.android.wm.shell.desktopmode.IDesktopTaskListener -import com.android.wm.shell.draganddrop.IDragAndDrop -import com.android.wm.shell.onehanded.IOneHanded -import com.android.wm.shell.pip.IPip -import com.android.wm.shell.pip.IPipAnimationListener -import com.android.wm.shell.recents.IRecentTasks -import com.android.wm.shell.recents.IRecentTasksListener -import com.android.wm.shell.splitscreen.ISplitScreen -import com.android.wm.shell.splitscreen.ISplitScreenListener -import com.android.wm.shell.splitscreen.ISplitSelectListener -import com.android.wm.shell.startingsurface.IStartingWindow -import com.android.wm.shell.startingsurface.IStartingWindowListener -import com.android.wm.shell.transition.IHomeTransitionListener -import com.android.wm.shell.transition.IShellTransitions -import com.android.wm.shell.util.GroupedRecentTaskInfo -import java.io.PrintWriter - -/** Holds the reference to SystemUI. */ -class SystemUiProxy(private val context: Context) { - - private val asyncHandler = Handler(UI_HELPER_EXECUTOR.looper, this::handleMessageAsync) - - /** - * This is a singleton pending intent that is used to start recents via Shell (which is a - * different process). It is bare-bones, so it's expected that the component and options will be - * provided via fill-in intent. - */ - private val recentsPendingIntent = - PendingIntent.getActivity( - context, - 0, - Intent().setPackage(context.packageName), - PendingIntent.FLAG_MUTABLE or - PendingIntent.FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT or - Intent.FILL_IN_COMPONENT, - ActivityOptions.makeBasic() - .setPendingIntentCreatorBackgroundActivityStartMode( - ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED - ) - .toBundle() - ) - - private val systemUiProxyDeathRecipient = - IBinder.DeathRecipient { Executors.MAIN_EXECUTOR.execute { clearProxy() } } - - private var systemUiProxy: ISystemUiProxy? = null - - private var pip: IPip? = null - private var bubbles: IBubbles? = null - private var sysuiUnlockAnimationController: ISysuiUnlockAnimationController? = null - private var splitScreen: ISplitScreen? = null - private var oneHanded: IOneHanded? = null - private var shellTransitions: IShellTransitions? = null - private var startingWindow: IStartingWindow? = null - private var recentTasks: IRecentTasks? = null - private var backAnimation: IBackAnimation? = null - private var desktopMode: IDesktopMode? = null - private var unfoldAnimation: IUnfoldAnimation? = null - - // Save the listeners passed into the proxy since OverviewProxyService may not have been bound - // yet, and we'll need to set/register these listeners with SysUI when they do. Note that it is - // up to the caller to clear the listeners to prevent leaks as these can be held indefinitely - // in case SysUI needs to rebind. - private var pipAnimationListener: IPipAnimationListener? = null - private var bubblesListener: IBubblesListener? = null - private var splitScreenListener: ISplitScreenListener? = null - private var splitSelectListener: ISplitSelectListener? = null - private var startingWindowListener: IStartingWindowListener? = null - private var launcherUnlockAnimationController: ILauncherUnlockAnimationController? = null - private var launcherActivityClass: String? = null - private var recentTasksListener: IRecentTasksListener? = null - private var unfoldAnimationListener: IUnfoldTransitionListener? = null - private var desktopTaskListener: IDesktopTaskListener? = null - private val remoteTransitions = LinkedHashMap() - private var originalTransactionToken: IBinder? = null - private var backToLauncherCallback: IOnBackInvokedCallback? = null - private var backToLauncherRunner: IRemoteAnimationRunner? = null - private var dragAndDrop: IDragAndDrop? = null - private var homeTransitionListener: IHomeTransitionListener? = null - - // Used to dedupe calls to SystemUI - private var lastShelfHeight = 0 - private var lastShelfVisible = false - - // Used to dedupe calls to SystemUI - private var lastLauncherKeepClearAreaHeight = 0 - private var lastLauncherKeepClearAreaHeightVisible = false - - // TODO(141886704): Find a way to remove this - var lastSystemUiStateFlags = 0 - - /** - * Sets proxy state, including death linkage, various listeners, and other configuration objects - */ - @MainThread - fun setProxy( - proxy: ISystemUiProxy?, - pip: IPip?, - bubbles: IBubbles?, - splitScreen: ISplitScreen?, - oneHanded: IOneHanded?, - shellTransitions: IShellTransitions?, - startingWindow: IStartingWindow?, - recentTasks: IRecentTasks?, - sysuiUnlockAnimationController: ISysuiUnlockAnimationController?, - backAnimation: IBackAnimation?, - desktopMode: IDesktopMode?, - unfoldAnimation: IUnfoldAnimation?, - dragAndDrop: IDragAndDrop? - ) { - Preconditions.assertUIThread() - unlinkToDeath() - systemUiProxy = proxy - this.pip = pip - this.bubbles = bubbles - this.splitScreen = splitScreen - this.oneHanded = oneHanded - this.shellTransitions = shellTransitions - this.startingWindow = startingWindow - this.sysuiUnlockAnimationController = sysuiUnlockAnimationController - this.recentTasks = recentTasks - this.backAnimation = backAnimation - this.desktopMode = desktopMode - this.unfoldAnimation = unfoldAnimation - this.dragAndDrop = dragAndDrop - linkToDeath() - // re-attach the listeners once missing due to setProxy has not been initialized yet. - setPipAnimationListener(pipAnimationListener) - setBubblesListener(bubblesListener) - registerSplitScreenListener(splitScreenListener) - registerSplitSelectListener(splitSelectListener) - setHomeTransitionListener(homeTransitionListener) - setStartingWindowListener(startingWindowListener) - setLauncherUnlockAnimationController( - launcherActivityClass, - launcherUnlockAnimationController - ) - LinkedHashMap(remoteTransitions).forEach(this::registerRemoteTransition) - setupTransactionQueue() - registerRecentTasksListener(recentTasksListener) - setBackToLauncherCallback(backToLauncherCallback, backToLauncherRunner) - setUnfoldAnimationListener(unfoldAnimationListener) - setDesktopTaskListener(desktopTaskListener) - setAssistantOverridesRequested( - AssistUtils.newInstance(context).sysUiAssistOverrideInvocationTypes - ) - } - - /** - * Clear the proxy to release held resources and turn the majority of its operations into no-ops - */ - @MainThread - fun clearProxy() { - setProxy(null, null, null, null, null, null, null, null, null, null, null, null, null) - } - - val isActive: Boolean - get() = systemUiProxy != null - - private fun linkToDeath() { - tryOrLog("Failed to link sysui proxy death recipient") { - systemUiProxy?.asBinder()?.linkToDeath(systemUiProxyDeathRecipient, 0 /* flags */) - } - } - - private fun unlinkToDeath() { - systemUiProxy?.asBinder()?.unlinkToDeath(systemUiProxyDeathRecipient, 0 /* flags */) - } - - fun onBackPressed() { - tryOrLog("Failed call onBackPressed") { systemUiProxy?.onBackPressed() } - } - - fun onImeSwitcherPressed() { - tryOrLog("Failed call onImeSwitcherPressed") { systemUiProxy?.onImeSwitcherPressed() } - } - - fun setHomeRotationEnabled(enabled: Boolean) { - tryOrLog("Failed call setHomeRotationEnabled") { - systemUiProxy?.setHomeRotationEnabled(enabled) - } - } - - fun startScreenPinning(taskId: Int) { - tryOrLog("Failed call startScreenPinning") { systemUiProxy?.startScreenPinning(taskId) } - } - - fun onOverviewShown(fromHome: Boolean, tag: String?) { - try { - systemUiProxy?.onOverviewShown(fromHome) - } catch (e: RemoteException) { - Log.w(tag, "Failed call onOverviewShown from: ${if (fromHome) "home" else "app"}", e) - } - } - - @MainThread - fun onStatusBarTouchEvent(event: MotionEvent) { - Preconditions.assertUIThread() - tryOrLog("Failed call onStatusBarTouchEvent with arg: $event") { - systemUiProxy?.onStatusBarTouchEvent(event) - } - } - - fun onStatusBarTrackpadEvent(event: MotionEvent) { - tryOrLog("Failed call onStatusBarTrackpadEvent with arg: $event") { - systemUiProxy?.onStatusBarTrackpadEvent(event) - } - } - - fun onAssistantProgress(progress: Float) { - tryOrLog("Failed call onAssistantProgress with progress: $progress") { - systemUiProxy?.onAssistantProgress(progress) - } - } - - fun onAssistantGestureCompletion(velocity: Float) { - tryOrLog("Failed call onAssistantGestureCompletion") { - systemUiProxy?.onAssistantGestureCompletion(velocity) - } - } - - fun startAssistant(args: Bundle?) { - tryOrLog("Failed call startAssistant") { systemUiProxy?.startAssistant(args) } - } - - fun setAssistantOverridesRequested(invocationTypes: IntArray?) { - tryOrLog("Failed call setAssistantOverridesRequested") { - systemUiProxy?.setAssistantOverridesRequested(invocationTypes) - } - } - - fun animateNavBarLongPress(isTouchDown: Boolean, shrink:Boolean, durationMs: Long) { - tryOrLog("Failed call animateNavBarLongPress") { - systemUiProxy?.animateNavBarLongPress(isTouchDown, shrink, durationMs) - } - } - - fun notifyAccessibilityButtonClicked(displayId: Int) { - tryOrLog("Failed call notifyAccessibilityButtonClicked") { - systemUiProxy?.notifyAccessibilityButtonClicked(displayId) - } - } - - fun notifyAccessibilityButtonLongClicked() { - tryOrLog("Failed call notifyAccessibilityButtonLongClicked") { - systemUiProxy?.notifyAccessibilityButtonLongClicked() - } - } - - fun stopScreenPinning() { - tryOrLog("Failed call stopScreenPinning") { systemUiProxy?.stopScreenPinning() } - } - - fun notifyPrioritizedRotation(rotation: Int) { - tryOrLog("Failed call notifyPrioritizedRotation with arg: $rotation") { - systemUiProxy?.notifyPrioritizedRotation(rotation) - } - } - - fun notifyTaskbarStatus(visible: Boolean, stashed: Boolean) { - tryOrLog("Failed call notifyTaskbarStatus with arg: $visible, $stashed") { - systemUiProxy?.notifyTaskbarStatus(visible, stashed) - } - } - - /** - * NOTE: If called to suspend, caller MUST call this method to also un-suspend - * - * @param suspend should be true to stop auto-hide, false to resume normal behavior - */ - fun notifyTaskbarAutohideSuspend(suspend: Boolean) { - tryOrLog("Failed call notifyTaskbarAutohideSuspend with arg: $suspend") { - systemUiProxy?.notifyTaskbarAutohideSuspend(suspend) - } - } - - fun takeScreenshot(request: ScreenshotRequest?) { - tryOrLog("Failed call takeScreenshot") { systemUiProxy?.takeScreenshot(request) } - } - - fun expandNotificationPanel() { - tryOrLog("Failed call expandNotificationPanel") { systemUiProxy?.expandNotificationPanel() } - } - - fun toggleNotificationPanel() { - tryOrLog("Failed call toggleNotificationPanel") { systemUiProxy?.toggleNotificationPanel() } - } - - // - // Pip - // - /** Sets the shelf height. */ - fun setShelfHeight(visible: Boolean, shelfHeight: Int) { - Message.obtain(asyncHandler, MSG_SET_SHELF_HEIGHT, if (visible) 1 else 0, shelfHeight) - .sendToTarget() - } - - @WorkerThread - private fun setShelfHeightAsync(visibleInt: Int, shelfHeight: Int) { - val visible = visibleInt != 0 - if (visible == lastShelfVisible && shelfHeight == lastShelfHeight) return - - pip?.let { - tryOrLog("Failed call setShelfHeight visible: $visible height: $shelfHeight") { - it.setShelfHeight(visible, shelfHeight) - lastShelfVisible = visible - lastShelfHeight = shelfHeight - } - } - } - - /** - * Sets the height of the keep clear area that is going to be reported by the Launcher for the - * Hotseat. - */ - fun setLauncherKeepClearAreaHeight(visible: Boolean, height: Int) { - Message.obtain( - asyncHandler, - MSG_SET_LAUNCHER_KEEP_CLEAR_AREA_HEIGHT, - if (visible) 1 else 0, - height - ) - .sendToTarget() - } - - @WorkerThread - private fun setLauncherKeepClearAreaHeight(visibleInt: Int, height: Int) { - val visible = visibleInt != 0 - if ( - visible == lastLauncherKeepClearAreaHeightVisible && - height == lastLauncherKeepClearAreaHeight - ) - return - - pip?.let { - tryOrLog("Failed call setLauncherKeepClearAreaHeight vis: $visible height: $height") { - it.setLauncherKeepClearAreaHeight(visible, height) - lastLauncherKeepClearAreaHeightVisible = visible - lastLauncherKeepClearAreaHeight = height - } - } - } - - /** Sets listener to get pip animation callbacks. */ - fun setPipAnimationListener(listener: IPipAnimationListener?) { - tryOrLog("Failed call setPinnedStackAnimationListener") { - pip?.setPipAnimationListener(listener) - } - pipAnimationListener = listener - } - - /** @return Destination bounds of auto-pip animation, `null` if the animation is not ready. */ - fun startSwipePipToHome( - componentName: ComponentName?, - activityInfo: ActivityInfo?, - pictureInPictureParams: PictureInPictureParams?, - launcherRotation: Int, - hotseatKeepClearArea: Rect? - ): Rect? { - return tryOrElse("Failed call startSwipePipToHome", null) { - return pip?.startSwipePipToHome( - componentName, - activityInfo, - pictureInPictureParams, - launcherRotation, - hotseatKeepClearArea - ) - } - } - - /** - * Notifies WM Shell that launcher has finished the preparation of the animation for swipe to - * home. WM Shell can choose to fade out the overlay when entering PIP is finished, and WM Shell - * should be responsible for cleaning up the overlay. - */ - fun stopSwipePipToHome( - taskId: Int, - componentName: ComponentName?, - destinationBounds: Rect?, - overlay: SurfaceControl? - ) { - tryOrLog("Failed call stopSwipePipToHome") { - pip?.stopSwipePipToHome(taskId, componentName, destinationBounds, overlay) - } - } - - /** - * Notifies WM Shell that launcher has aborted all the animation for swipe to home. WM Shell can - * use this callback to clean up its internal states. - */ - fun abortSwipePipToHome(taskId: Int, componentName: ComponentName?) { - tryOrLog("Failed call abortSwipePipToHome") { - pip?.abortSwipePipToHome(taskId, componentName) - } - } - - /** Sets the next pip animation type to be the alpha animation. */ - fun setPipAnimationTypeToAlpha() { - tryOrLog("Failed call setPipAnimationTypeToAlpha") { pip?.setPipAnimationTypeToAlpha() } - } - - /** Sets the app icon size in pixel used by Launcher all apps. */ - fun setLauncherAppIconSize(iconSizePx: Int) { - tryOrLog("Failed call setLauncherAppIconSize") { pip?.setLauncherAppIconSize(iconSizePx) } - } - - // - // Bubbles - // - /** Sets the listener to be notified of bubble state changes. */ - fun setBubblesListener(listener: IBubblesListener?) { - bubbles?.let { - tryOrLog("Failed call setLauncherAppIconSize") { - bubblesListener?.let(it::unregisterBubbleListener) - listener?.let(it::registerBubbleListener) - } - } - bubblesListener = listener - } - - /** - * Tells SysUI to show the bubble with the provided key. - * - * @param key the key of the bubble to show. - * @param bubbleBarOffsetX the offset of the bubble bar from the edge of the screen on the X - * axis. - * @param bubbleBarOffsetY the offset of the bubble bar from the edge of the screen on the Y - * axis. - */ - fun showBubble(key: String?, bubbleBarOffsetX: Int, bubbleBarOffsetY: Int) { - tryOrLog("Failed call showBubble") { - bubbles?.showBubble(key, bubbleBarOffsetX, bubbleBarOffsetY) - } - } - - /** - * Tells SysUI to remove the bubble with the provided key. - * - * @param key the key of the bubble to show. - */ - fun removeBubble(key: String?) { - tryOrLog("Failed call removeBubble") { bubbles?.removeBubble(key) } - } - - /** Tells SysUI to remove all bubbles. */ - fun removeAllBubbles() { - tryOrLog("Failed call removeAllBubbles") { bubbles?.removeAllBubbles() } - } - - /** Tells SysUI to collapse the bubbles. */ - fun collapseBubbles() { - tryOrLog("Failed call collapseBubbles") { bubbles?.collapseBubbles() } - } - - /** - * Tells SysUI when the bubble is being dragged. Should be called only when the bubble bar is - * expanded. - * - * @param bubbleKey the key of the bubble to collapse/expand - * @param isBeingDragged whether the bubble is being dragged - */ - fun onBubbleDrag(bubbleKey: String?, isBeingDragged: Boolean) { - tryOrLog("Failed call onBubbleDrag") { bubbles?.onBubbleDrag(bubbleKey, isBeingDragged) } - } - - /** - * Tells SysUI to show user education relative to the reference point provided. - * - * @param position the bubble bar top center position in Screen coordinates. - */ - fun showUserEducation(position: Point) { - tryOrLog("Failed call showUserEducation") { - bubbles?.showUserEducation(position.x, position.y) - } - } - - // - // Splitscreen - // - fun registerSplitScreenListener(listener: ISplitScreenListener?) { - tryOrLog("Failed call registerSplitScreenListener") { - splitScreen?.registerSplitScreenListener(listener) - } - splitScreenListener = listener - } - - fun registerSplitSelectListener(listener: ISplitSelectListener?) { - tryOrLog("Failed call registerSplitScreenListener") { - splitScreen?.registerSplitSelectListener(listener) - } - splitSelectListener = listener - } - - /** Start multiple tasks in split-screen simultaneously. */ - fun startTasks( - taskId1: Int, - options1: Bundle?, - taskId2: Int, - options2: Bundle?, - @StagePosition splitPosition: Int, - @PersistentSnapPosition snapPosition: Int, - remoteTransition: RemoteTransition?, - instanceId: InstanceId? - ) { - tryOrLog(splitFailureMessage("startTasks", "RemoteException")) { - splitScreen?.startTasks( - taskId1, - options1, - taskId2, - options2, - splitPosition, - snapPosition, - remoteTransition, - instanceId - ) - } - } - - fun startIntentAndTask( - pendingIntent: PendingIntent?, - userId1: Int, - options1: Bundle?, - taskId: Int, - options2: Bundle?, - @StagePosition splitPosition: Int, - @PersistentSnapPosition snapPosition: Int, - remoteTransition: RemoteTransition?, - instanceId: InstanceId? - ) { - tryOrLog(splitFailureMessage("startIntentAndTask", "RemoteException")) { - splitScreen?.startIntentAndTask( - pendingIntent, - userId1, - options1, - taskId, - options2, - splitPosition, - snapPosition, - remoteTransition, - instanceId - ) - } - } - - fun startIntents( - pendingIntent1: PendingIntent?, - userId1: Int, - shortcutInfo1: ShortcutInfo?, - options1: Bundle?, - pendingIntent2: PendingIntent?, - userId2: Int, - shortcutInfo2: ShortcutInfo?, - options2: Bundle?, - @StagePosition splitPosition: Int, - @PersistentSnapPosition snapPosition: Int, - remoteTransition: RemoteTransition?, - instanceId: InstanceId? - ) { - tryOrLog(splitFailureMessage("startIntents", "RemoteException")) { - splitScreen?.startIntents( - pendingIntent1, - userId1, - shortcutInfo1, - options1, - pendingIntent2, - userId2, - shortcutInfo2, - options2, - splitPosition, - snapPosition, - remoteTransition, - instanceId - ) - } - } - - fun startShortcutAndTask( - shortcutInfo: ShortcutInfo?, - options1: Bundle?, - taskId: Int, - options2: Bundle?, - @StagePosition splitPosition: Int, - @PersistentSnapPosition snapPosition: Int, - remoteTransition: RemoteTransition?, - instanceId: InstanceId? - ) { - tryOrLog(splitFailureMessage("startShortcutAndTask", "RemoteException")) { - splitScreen?.startShortcutAndTask( - shortcutInfo, - options1, - taskId, - options2, - splitPosition, - snapPosition, - remoteTransition, - instanceId - ) - } - } - - /** Start multiple tasks in split-screen simultaneously. */ - fun startTasksWithLegacyTransition( - taskId1: Int, - options1: Bundle?, - taskId2: Int, - options2: Bundle?, - @StagePosition splitPosition: Int, - @PersistentSnapPosition snapPosition: Int, - adapter: RemoteAnimationAdapter?, - instanceId: InstanceId? - ) { - tryOrLog(splitFailureMessage("startTasksWithLegacyTransition", "RemoteException")) { - splitScreen?.startTasksWithLegacyTransition( - taskId1, - options1, - taskId2, - options2, - splitPosition, - snapPosition, - adapter, - instanceId - ) - } - } - - fun startIntentAndTaskWithLegacyTransition( - pendingIntent: PendingIntent?, - userId1: Int, - options1: Bundle?, - taskId: Int, - options2: Bundle?, - @StagePosition splitPosition: Int, - @PersistentSnapPosition snapPosition: Int, - adapter: RemoteAnimationAdapter?, - instanceId: InstanceId? - ) { - tryOrLog(splitFailureMessage("startIntentAndTaskWithLegacyTransition", "RemoteException")) { - splitScreen?.startIntentAndTaskWithLegacyTransition( - pendingIntent, - userId1, - options1, - taskId, - options2, - splitPosition, - snapPosition, - adapter, - instanceId - ) - } - } - - fun startShortcutAndTaskWithLegacyTransition( - shortcutInfo: ShortcutInfo?, - options1: Bundle?, - taskId: Int, - options2: Bundle?, - @StagePosition splitPosition: Int, - @PersistentSnapPosition snapPosition: Int, - adapter: RemoteAnimationAdapter?, - instanceId: InstanceId? - ) { - tryOrLog( - splitFailureMessage("startShortcutAndTaskWithLegacyTransition", "RemoteException") - ) { - splitScreen?.startShortcutAndTaskWithLegacyTransition( - shortcutInfo, - options1, - taskId, - options2, - splitPosition, - snapPosition, - adapter, - instanceId - ) - } - } - - /** - * Starts a pair of intents or shortcuts in split-screen using legacy transition. Passing a - * non-null shortcut info means to start the app as a shortcut. - */ - fun startIntentsWithLegacyTransition( - pendingIntent1: PendingIntent?, - userId1: Int, - shortcutInfo1: ShortcutInfo?, - options1: Bundle?, - pendingIntent2: PendingIntent?, - userId2: Int, - shortcutInfo2: ShortcutInfo?, - options2: Bundle?, - @StagePosition sidePosition: Int, - @PersistentSnapPosition snapPosition: Int, - adapter: RemoteAnimationAdapter?, - instanceId: InstanceId? - ) { - tryOrLog(splitFailureMessage("startIntentsWithLegacyTransition", "RemoteException")) { - splitScreen?.startIntentsWithLegacyTransition( - pendingIntent1, - userId1, - shortcutInfo1, - options1, - pendingIntent2, - userId2, - shortcutInfo2, - options2, - sidePosition, - snapPosition, - adapter, - instanceId - ) - } - } - - fun startShortcut( - packageName: String?, - shortcutId: String?, - position: Int, - options: Bundle?, - user: UserHandle?, - instanceId: InstanceId? - ) { - tryOrLog(splitFailureMessage("startShortcut", "RemoteException")) { - splitScreen?.startShortcut(packageName, shortcutId, position, options, user, instanceId) - } - } - - fun startIntent( - intent: PendingIntent?, - userId: Int, - fillInIntent: Intent?, - position: Int, - options: Bundle?, - instanceId: InstanceId? - ) { - tryOrLog(splitFailureMessage("startIntent", "RemoteException")) { - splitScreen?.startIntent(intent, userId, fillInIntent, position, options, instanceId) - } - } - - /** - * Call this when going to recents so that shell can set-up and provide appropriate leashes for - * animation (eg. DividerBar). - * - * @return RemoteAnimationTargets of windows that need to animate but only exist in shell. - */ - fun onGoingToRecentsLegacy( - apps: Array? - ): Array? { - if (TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) return null - return tryOrElse("Failed call onGoingToRecentsLegacy", null) { - return splitScreen?.onGoingToRecentsLegacy(apps) - } - } - - fun onStartingSplitLegacy(apps: Array?): Array? { - return tryOrElse("Failed call onStartingSplitLegacy", null) { - return splitScreen?.onStartingSplitLegacy(apps) - } - } - - // - // One handed - // - fun startOneHandedMode() { - tryOrLog("Failed call startOneHandedMode") { oneHanded?.startOneHanded() } - } - - fun stopOneHandedMode() { - tryOrLog("Failed call startOneHandedstopOneHandedModeMode") { oneHanded?.stopOneHanded() } - } - - // - // Remote transitions - // - fun registerRemoteTransition(remoteTransition: RemoteTransition, filter: TransitionFilter) { - tryOrLog("Failed call registerRemoteTransition") { - shellTransitions?.registerRemote(filter, remoteTransition) - } - if (!remoteTransitions.containsKey(remoteTransition)) { - remoteTransitions[remoteTransition] = filter - } - } - - fun unregisterRemoteTransition(remoteTransition: RemoteTransition) { - tryOrLog("Failed call unregisterRemoteTransition") { - shellTransitions?.unregisterRemote(remoteTransition) - } - remoteTransitions.remove(remoteTransition) - } - - fun setHomeTransitionListener(listener: IHomeTransitionListener?) { - if (!FeatureFlags.enableHomeTransitionListener()) return - - homeTransitionListener = listener - - tryOrLog("Failed call unregisterRemoteTransition") { - shellTransitions?.setHomeTransitionListener(listener) - ?: Log.w( - TAG, - "Unable to call setHomeTransitionListener because ShellTransitions is null" - ) - } - } - - /** - * Use SystemUI's transaction-queue instead of Launcher's independent one. This is necessary if - * Launcher and SystemUI need to coordinate transactions (eg. for shell transitions). - */ - fun shareTransactionQueue() { - if (originalTransactionToken == null) { - originalTransactionToken = SurfaceControl.Transaction.getDefaultApplyToken() - } - setupTransactionQueue() - } - - /** Switch back to using Launcher's independent transaction queue. */ - fun unshareTransactionQueue() { - originalTransactionToken?.let { SurfaceControl.Transaction.setDefaultApplyToken(it) } - originalTransactionToken = null - } - - private fun setupTransactionQueue() { - originalTransactionToken ?: return - - var transitions = shellTransitions ?: run { - SurfaceControl.Transaction.setDefaultApplyToken(originalTransactionToken) - return - } - tryOrLog("Error getting Shell's apply token") { - transitions.getShellApplyToken()?.apply { - SurfaceControl.Transaction.setDefaultApplyToken(this) - } ?: Log.e(TAG, "Didn't receive apply token from Shell") - } - } - - // - // Starting window - // - /** Sets listener to get callbacks when launching a task. */ - fun setStartingWindowListener(listener: IStartingWindowListener?) { - tryOrLog("Failed call setStartingWindowListener") { - startingWindow?.setStartingWindowListener(listener) - } - startingWindowListener = listener - } - - // - // SmartSpace transitions - // - /** - * Sets the instance of [ILauncherUnlockAnimationController] that System UI should use to - * control the launcher side of the unlock animation. This will also cause us to dispatch the - * current state of the smartspace to System UI (this will subsequently happen if the state - * changes). - */ - fun setLauncherUnlockAnimationController( - activityClass: String?, - controller: ILauncherUnlockAnimationController? - ) { - tryOrLog("Failed call setLauncherUnlockAnimationController") { - sysuiUnlockAnimationController?.let { - it.setLauncherUnlockController(activityClass, controller) - controller?.dispatchSmartspaceStateToSysui() - } - } - - launcherActivityClass = activityClass - launcherUnlockAnimationController = controller - } - - /** - * Tells System UI that the Launcher's smartspace state has been updated, so that it can prepare - * the unlock animation accordingly. - */ - fun notifySysuiSmartspaceStateUpdated(state: SmartspaceState?) { - tryOrLog("Failed call notifySysuiSmartspaceStateUpdated") { - sysuiUnlockAnimationController?.onLauncherSmartspaceStateUpdated(state) - } - } - - // - // Recents - // - fun registerRecentTasksListener(listener: IRecentTasksListener?) { - tryOrLog("Failed call registerRecentTasksListener") { - recentTasks?.registerRecentTasksListener(listener) - } - recentTasksListener = listener - } - - // - // Back navigation transitions - // - /** Sets the launcher [android.window.IOnBackInvokedCallback] to shell */ - fun setBackToLauncherCallback( - callback: IOnBackInvokedCallback?, - runner: IRemoteAnimationRunner? - ) { - backToLauncherCallback = callback - backToLauncherRunner = runner - - callback ?: return - tryOrLog("Failed call setBackToLauncherCallback") { - backAnimation?.setBackToLauncherCallback(callback, runner) - } - } - - /** - * Clears the previously registered [IOnBackInvokedCallback]. - * - * @param callback The previously registered callback instance. - */ - fun clearBackToLauncherCallback(callback: IOnBackInvokedCallback) { - if (backToLauncherCallback != callback) { - return - } - backToLauncherCallback = null - backToLauncherRunner = null - tryOrLog("Failed call clearBackToLauncherCallback") { - backAnimation?.clearBackToLauncherCallback() - } - } - - /** Called when the status bar color needs to be customized when back navigation. */ - fun customizeStatusBarAppearance(appearance: AppearanceRegion?) { - tryOrLog("Failed call customizeStatusBarAppearance") { - backAnimation?.customizeStatusBarAppearance(appearance) - } - } - - fun getRecentTasks(numTasks: Int, userId: Int): ArrayList { - return tryOrElse("Failed call getRecentTasks", ArrayList()) { - return recentTasks - ?.getRecentTasks(numTasks, ActivityManager.RECENT_IGNORE_UNAVAILABLE, userId) - ?.let { ArrayList(it.asList()) } - ?: ArrayList() - } - } - - /** Gets the set of running tasks. */ - fun getRunningTasks(numTasks: Int): ArrayList { - if (!context.packageManager.hasSystemFeature(PackageManager.FEATURE_PC)) { - return ArrayList() - } - - return tryOrElse("Failed call getRunningTasks", ArrayList()) { - recentTasks?.getRunningTasks(numTasks)?.let { ArrayList(it.asList()) } ?: ArrayList() - } - } - - private fun handleMessageAsync(msg: Message): Boolean { - when (msg.what) { - MSG_SET_SHELF_HEIGHT -> { - setShelfHeightAsync(msg.arg1, msg.arg2) - return true - } - MSG_SET_LAUNCHER_KEEP_CLEAR_AREA_HEIGHT -> { - setLauncherKeepClearAreaHeight(msg.arg1, msg.arg2) - return true - } - } - return false - } - - // - // Desktop Mode - // - /** Call shell to show all apps active on the desktop */ - fun showDesktopApps(displayId: Int, transition: RemoteTransition?) { - tryOrLog("Failed call showDesktopApps") { - desktopMode?.showDesktopApps(displayId, transition) - } - } - - /** Call shell to stash desktop apps */ - fun stashDesktopApps(displayId: Int) { - tryOrLog("Failed call stashDesktopApps") { desktopMode?.stashDesktopApps(displayId) } - } - - /** Call shell to hide desktop apps that may be stashed */ - fun hideStashedDesktopApps(displayId: Int) { - tryOrLog("Failed call hideStashedDesktopApps") { - desktopMode?.hideStashedDesktopApps(displayId) - } - } - - /** If task with the given id is on the desktop, bring it to front */ - fun showDesktopApp(taskId: Int) { - tryOrLog("Failed call showDesktopApp") { desktopMode?.showDesktopApp(taskId) } - } - - /** Call shell to get number of visible freeform tasks */ - fun getVisibleDesktopTaskCount(displayId: Int): Int { - return tryOrElse("Failed call getVisibleDesktopTaskCount", 0) { - return desktopMode?.getVisibleTaskCount(displayId) ?: 0 - } - } - - /** Set a listener on shell to get updates about desktop task state */ - fun setDesktopTaskListener(listener: IDesktopTaskListener?) { - desktopTaskListener = listener - tryOrLog("Failed call setDesktopTaskListener") { desktopMode?.setTaskListener(listener) } - } - - /** Perform cleanup transactions after animation to split select is complete */ - fun onDesktopSplitSelectAnimComplete(taskInfo: RunningTaskInfo?) { - tryOrLog("Failed call onDesktopSplitSelectAnimComplete") { - desktopMode?.onDesktopSplitSelectAnimComplete(taskInfo) - } - } - // - // Unfold transition - // - /** Sets the unfold animation lister to sysui. */ - fun setUnfoldAnimationListener(callback: IUnfoldTransitionListener?) { - unfoldAnimationListener = callback - tryOrLog("Failed call setUnfoldAnimationListener") { - unfoldAnimation?.setListener(callback) - } - } - - // - // Recents - // - /** Starts the recents activity. The caller should manage the thread on which this is called. */ - fun startRecentsActivity( - intent: Intent?, - options: ActivityOptions, - listener: RecentsAnimationListener - ): Boolean { - - val runner: IRecentsAnimationRunner = - object : IRecentsAnimationRunner.Stub() { - override fun onAnimationStart( - controller: IRecentsAnimationController, - apps: Array, - wallpapers: Array, - homeContentInsets: Rect, - minimizedHomeBounds: Rect, - extras: Bundle? - ) { - // Aidl bundles need to explicitly set class loader - // https://developer.android.com/guide/components/aidl#Bundles - extras?.classLoader = javaClass.classLoader - listener.onAnimationStart( - RecentsAnimationControllerCompat(controller), - apps, - wallpapers, - homeContentInsets, - minimizedHomeBounds, - extras - ) - } - - override fun onAnimationCanceled( - taskIds: IntArray, - taskSnapshots: Array - ) { - listener.onAnimationCanceled(ThumbnailData.wrap(taskIds, taskSnapshots)) - } - - override fun onTasksAppeared(apps: Array) { - listener.onTasksAppeared(apps) - } - } - - return tryOrElse("Error starting recents via shell", false) { - recentTasks?.let { - it.startRecentsTransition( - recentsPendingIntent, - intent, - options.toBundle(), - context.iApplicationThread, - runner - ) - return true - } - ?: run { - ActiveGestureLog.INSTANCE.addLog( - "Null recentTasks", - ActiveGestureErrorDetector.GestureEvent.RECENT_TASKS_MISSING - ) - false - } - } - } - - // - // Drag and drop - // - - /** - * For testing purposes. Returns `true` only if the shell drop target has shown and drawn and is - * ready to handle drag events and the subsequent drop. - */ - val isDragAndDropReady: Boolean - get() = - tryOrElse("Error querying drag state", false) { - dragAndDrop?.isReadyToHandleDrag ?: false - } - - fun dump(pw: PrintWriter) { - pw.println("$TAG:") - pw.println("\tsystemUiProxy=$systemUiProxy") - pw.println("\tpip=$pip") - pw.println("\tpipAnimationListener=$pipAnimationListener") - pw.println("\tbubbles=$bubbles") - pw.println("\tbubblesListener=$bubblesListener") - pw.println("\tsplitScreen=$splitScreen") - pw.println("\tsplitScreenListener=$splitScreenListener") - pw.println("\tsplitSelectListener=$splitSelectListener") - pw.println("\toneHanded=$oneHanded") - pw.println("\tshellTransitions=$shellTransitions") - pw.println("\thomeTransitionListener=$homeTransitionListener") - pw.println("\tstartingWindow=$startingWindow") - pw.println("\tstartingWindowListener=$startingWindowListener") - pw.println("\tsysuiUnlockAnimationController=$sysuiUnlockAnimationController") - pw.println("\tlauncherActivityClass=$launcherActivityClass") - pw.println("\tlauncherUnlockAnimationController=$launcherUnlockAnimationController") - pw.println("\trecentTasks=$recentTasks") - pw.println("\trecentTasksListener=$recentTasksListener") - pw.println("\tbackAnimation=$backAnimation") - pw.println("\tbackToLauncherCallback=$backToLauncherCallback") - pw.println("\tbackToLauncherRunner=$backToLauncherRunner") - pw.println("\tdesktopMode=$desktopMode") - pw.println("\tdesktopTaskListener=$desktopTaskListener") - pw.println("\tunfoldAnimation=$unfoldAnimation") - pw.println("\tunfoldAnimationListener=$unfoldAnimationListener") - pw.println("\tdragAndDrop=$dragAndDrop") - } - - companion object { - private const val TAG = "SystemUiProxy" - private const val MSG_SET_SHELF_HEIGHT = 1 - private const val MSG_SET_LAUNCHER_KEEP_CLEAR_AREA_HEIGHT = 2 - - @JvmField - val INSTANCE = MainThreadInitializedObject { context: Context -> SystemUiProxy(context) } - - private inline fun tryOrLog(msg: String, f: () -> Unit) = - try { - f() - } catch (e: RemoteException) { - Log.w(TAG, msg, e) - } - - private inline fun tryOrElse(msg: String, fallback: T, f: () -> T): T = - try { - f() - } catch (e: RemoteException) { - Log.w(TAG, msg, e) - fallback - } - } -}