/* * Copyright (C) 2017 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.recents; import static android.content.Intent.ACTION_PACKAGE_ADDED; import static android.content.Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST; import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY; import static android.view.MotionEvent.ACTION_CANCEL; import static android.view.MotionEvent.ACTION_DOWN; import static android.view.MotionEvent.ACTION_UP; import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON; import static com.android.internal.accessibility.common.ShortcutConstants.CHOOSER_PACKAGE_NAME; import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY; import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_UNFOLD_ANIMATION_FORWARDER; import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_UNLOCK_ANIMATION_CONTROLLER; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_AWAKE; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DEVICE_DOZING; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DEVICE_DREAMING; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_GOING_AWAY; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_TRACING_ENABLED; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_WAKEFULNESS_TRANSITION; import android.annotation.FloatRange; import android.app.ActivityTaskManager; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.ServiceConnection; import android.content.pm.ResolveInfo; import android.graphics.Region; import android.hardware.input.InputManager; import android.hardware.input.InputManagerGlobal; import android.os.Binder; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.PatternMatcher; import android.os.Process; import android.os.RemoteException; import android.os.SystemClock; import android.os.UserHandle; import android.util.Log; import android.view.InputDevice; import android.view.KeyCharacterMap; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.Surface; import android.view.SurfaceControl; import android.view.accessibility.AccessibilityManager; import android.view.inputmethod.InputMethodManager; import androidx.annotation.NonNull; import com.android.internal.accessibility.dialog.AccessibilityButtonChooserActivity; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.AssistUtils; import com.android.internal.app.IVoiceInteractionSessionListener; import com.android.internal.logging.UiEventLogger; import com.android.internal.util.ScreenshotHelper; import com.android.internal.util.ScreenshotRequest; import com.android.systemui.Dumpable; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.model.SysUiState; import com.android.systemui.navigationbar.NavigationBar; import com.android.systemui.navigationbar.NavigationBarController; import com.android.systemui.navigationbar.NavigationBarView; import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.navigationbar.buttons.KeyButtonView; import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener; import com.android.systemui.scene.domain.interactor.SceneInteractor; import com.android.systemui.settings.DisplayTracker; import com.android.systemui.settings.UserTracker; import com.android.systemui.shade.ShadeViewController; import com.android.systemui.shared.recents.IOverviewProxy; import com.android.systemui.shared.recents.ISystemUiProxy; import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.phone.CentralSurfaces; import com.android.systemui.statusbar.phone.StatusBarWindowCallback; import com.android.systemui.statusbar.policy.CallbackController; import com.android.systemui.unfold.progress.UnfoldTransitionProgressForwarder; import com.android.wm.shell.sysui.ShellInterface; import dagger.Lazy; import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; import java.util.function.Supplier; import javax.inject.Inject; import javax.inject.Provider; /** * Class to send information from overview to launcher with a binder. */ @SysUISingleton public class OverviewProxyService implements CallbackController, NavigationModeController.ModeChangedListener, Dumpable { @VisibleForTesting static final String ACTION_QUICKSTEP = "android.intent.action.QUICKSTEP_SERVICE"; public static final String TAG_OPS = "OverviewProxyService"; private static final long BACKOFF_MILLIS = 1000; private static final long DEFERRED_CALLBACK_MILLIS = 5000; // Max backoff caps at 5 mins private static final long MAX_BACKOFF_MILLIS = 10 * 60 * 1000; private final Context mContext; private final FeatureFlags mFeatureFlags; private final Executor mMainExecutor; private final ShellInterface mShellInterface; private final Lazy> mCentralSurfacesOptionalLazy; private final Lazy mShadeViewControllerLazy; private SysUiState mSysUiState; private final Handler mHandler; private final Lazy mNavBarControllerLazy; private final NotificationShadeWindowController mStatusBarWinController; private final Provider mSceneInteractor; private final Runnable mConnectionRunnable = () -> internalConnectToCurrentUser("runnable: startConnectionToCurrentUser"); private final ComponentName mRecentsComponentName; private final List mConnectionCallbacks = new ArrayList<>(); private final Intent mQuickStepIntent; private final ScreenshotHelper mScreenshotHelper; private final CommandQueue mCommandQueue; private final UserTracker mUserTracker; private final KeyguardUnlockAnimationController mSysuiUnlockAnimationController; private final Optional mUnfoldTransitionProgressForwarder; private final UiEventLogger mUiEventLogger; private final DisplayTracker mDisplayTracker; private Region mActiveNavBarRegion; private SurfaceControl mNavigationBarSurface; private IOverviewProxy mOverviewProxy; private int mConnectionBackoffAttempts; private boolean mBound; private boolean mIsEnabled; private int mCurrentBoundedUserId = -1; private boolean mInputFocusTransferStarted; private float mInputFocusTransferStartY; private long mInputFocusTransferStartMillis; private int mNavBarMode = NAV_BAR_MODE_3BUTTON; @VisibleForTesting public ISystemUiProxy mSysUiProxy = new ISystemUiProxy.Stub() { @Override public void startScreenPinning(int taskId) { verifyCallerAndClearCallingIdentityPostMain("startScreenPinning", () -> mCentralSurfacesOptionalLazy.get().ifPresent( statusBar -> statusBar.showScreenPinningRequest(taskId, false /* allowCancel */))); } @Override public void stopScreenPinning() { verifyCallerAndClearCallingIdentityPostMain("stopScreenPinning", () -> { try { ActivityTaskManager.getService().stopSystemLockTaskMode(); } catch (RemoteException e) { Log.e(TAG_OPS, "Failed to stop screen pinning"); } }); } // TODO: change the method signature to use (boolean inputFocusTransferStarted) @Override public void onStatusBarTouchEvent(MotionEvent event) { verifyCallerAndClearCallingIdentity("onStatusBarTouchEvent", () -> { // TODO move this logic to message queue mCentralSurfacesOptionalLazy.get().ifPresent(centralSurfaces -> { if (event.getActionMasked() == ACTION_DOWN) { mShadeViewControllerLazy.get().startExpandLatencyTracking(); } mHandler.post(() -> { int action = event.getActionMasked(); if (action == ACTION_DOWN) { mInputFocusTransferStarted = true; mInputFocusTransferStartY = event.getY(); mInputFocusTransferStartMillis = event.getEventTime(); // If scene framework is enabled, set the scene container window to // visible and let the touch "slip" into that window. if (mFeatureFlags.isEnabled(Flags.SCENE_CONTAINER)) { mSceneInteractor.get().setVisible(true, "swipe down on launcher"); } else { centralSurfaces.onInputFocusTransfer( mInputFocusTransferStarted, false /* cancel */, 0 /* velocity */); } } if (action == ACTION_UP || action == ACTION_CANCEL) { mInputFocusTransferStarted = false; if (!mFeatureFlags.isEnabled(Flags.SCENE_CONTAINER)) { float velocity = (event.getY() - mInputFocusTransferStartY) / (event.getEventTime() - mInputFocusTransferStartMillis); centralSurfaces.onInputFocusTransfer(mInputFocusTransferStarted, action == ACTION_CANCEL, velocity); } } event.recycle(); }); }); }); } @Override public void onStatusBarTrackpadEvent(MotionEvent event) { verifyCallerAndClearCallingIdentityPostMain("onStatusBarTrackpadEvent", () -> mCentralSurfacesOptionalLazy.get().ifPresent(centralSurfaces -> centralSurfaces.onStatusBarTrackpadEvent(event))); } @Override public void onBackPressed() { verifyCallerAndClearCallingIdentityPostMain("onBackPressed", () -> { sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK); sendEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK); }); } @Override public void onImeSwitcherPressed() { // TODO(b/204901476) We're intentionally using the default display for now since // Launcher/Taskbar isn't display aware. mContext.getSystemService(InputMethodManager.class) .showInputMethodPickerFromSystem(true /* showAuxiliarySubtypes */, mDisplayTracker.getDefaultDisplayId()); mUiEventLogger.log(KeyButtonView.NavBarButtonEvent.NAVBAR_IME_SWITCHER_BUTTON_TAP); } @Override public void setHomeRotationEnabled(boolean enabled) { verifyCallerAndClearCallingIdentityPostMain("setHomeRotationEnabled", () -> mHandler.post(() -> notifyHomeRotationEnabled(enabled))); } @Override public void notifyTaskbarStatus(boolean visible, boolean stashed) { verifyCallerAndClearCallingIdentityPostMain("notifyTaskbarStatus", () -> onTaskbarStatusUpdated(visible, stashed)); } @Override public void notifyTaskbarAutohideSuspend(boolean suspend) { verifyCallerAndClearCallingIdentityPostMain("notifyTaskbarAutohideSuspend", () -> onTaskbarAutohideSuspend(suspend)); } private boolean sendEvent(int action, int code) { long when = SystemClock.uptimeMillis(); final KeyEvent ev = new KeyEvent(when, when, action, code, 0 /* repeat */, 0 /* metaState */, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /* scancode */, KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY, InputDevice.SOURCE_KEYBOARD); ev.setDisplayId(mContext.getDisplay().getDisplayId()); return InputManagerGlobal.getInstance() .injectInputEvent(ev, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC); } @Override public void onOverviewShown(boolean fromHome) { verifyCallerAndClearCallingIdentityPostMain("onOverviewShown", () -> { for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) { mConnectionCallbacks.get(i).onOverviewShown(fromHome); } }); } @Override public void onAssistantProgress(@FloatRange(from = 0.0, to = 1.0) float progress) { verifyCallerAndClearCallingIdentityPostMain("onAssistantProgress", () -> notifyAssistantProgress(progress)); } @Override public void onAssistantGestureCompletion(float velocity) { verifyCallerAndClearCallingIdentityPostMain("onAssistantGestureCompletion", () -> notifyAssistantGestureCompletion(velocity)); } @Override public void startAssistant(Bundle bundle) { verifyCallerAndClearCallingIdentityPostMain("startAssistant", () -> notifyStartAssistant(bundle)); } @Override public void setAssistantOverridesRequested(int[] invocationTypes) { verifyCallerAndClearCallingIdentityPostMain("setAssistantOverridesRequested", () -> notifyAssistantOverrideRequested(invocationTypes)); } @Override public void notifyAccessibilityButtonClicked(int displayId) { verifyCallerAndClearCallingIdentity("notifyAccessibilityButtonClicked", () -> AccessibilityManager.getInstance(mContext) .notifyAccessibilityButtonClicked(displayId)); } @Override public void notifyAccessibilityButtonLongClicked() { verifyCallerAndClearCallingIdentity("notifyAccessibilityButtonLongClicked", () -> { final Intent intent = new Intent(AccessibilityManager.ACTION_CHOOSE_ACCESSIBILITY_BUTTON); final String chooserClassName = AccessibilityButtonChooserActivity .class.getName(); intent.setClassName(CHOOSER_PACKAGE_NAME, chooserClassName); intent.addFlags( Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); mContext.startActivityAsUser(intent, mUserTracker.getUserHandle()); }); } @Override public void notifyPrioritizedRotation(@Surface.Rotation int rotation) { verifyCallerAndClearCallingIdentityPostMain("notifyPrioritizedRotation", () -> notifyPrioritizedRotationInternal(rotation)); } @Override public void takeScreenshot(ScreenshotRequest request) { mScreenshotHelper.takeScreenshot(request, mHandler, null); } @Override public void expandNotificationPanel() { verifyCallerAndClearCallingIdentityPostMain("expandNotificationPanel", () -> mCommandQueue.handleSystemKey(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN))); } @Override public void toggleNotificationPanel() { verifyCallerAndClearCallingIdentityPostMain("toggleNotificationPanel", () -> mCommandQueue.togglePanel()); } private boolean verifyCaller(String reason) { final int callerId = Binder.getCallingUserHandle().getIdentifier(); if (callerId != mCurrentBoundedUserId) { Log.w(TAG_OPS, "Launcher called sysui with invalid user: " + callerId + ", reason: " + reason); return false; } return true; } private T verifyCallerAndClearCallingIdentity(String reason, Supplier supplier) { if (!verifyCaller(reason)) { return null; } final long token = Binder.clearCallingIdentity(); try { return supplier.get(); } finally { Binder.restoreCallingIdentity(token); } } private void verifyCallerAndClearCallingIdentity(String reason, Runnable runnable) { verifyCallerAndClearCallingIdentity(reason, () -> { runnable.run(); return null; }); } private void verifyCallerAndClearCallingIdentityPostMain(String reason, Runnable runnable) { verifyCallerAndClearCallingIdentity(reason, () -> mHandler.post(runnable)); } }; private final Runnable mDeferredConnectionCallback = () -> { Log.w(TAG_OPS, "Binder supposed established connection but actual connection to service " + "timed out, trying again"); retryConnectionWithBackoff(); }; private final BroadcastReceiver mLauncherStateChangedReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { // If adding, bind immediately if (Objects.equals(intent.getAction(), ACTION_PACKAGE_ADDED)) { updateEnabledAndBinding(); return; } // ACTION_PACKAGE_CHANGED String[] compsList = intent.getStringArrayExtra(EXTRA_CHANGED_COMPONENT_NAME_LIST); if (compsList == null) { return; } // Only rebind for TouchInteractionService component from launcher ResolveInfo ri = context.getPackageManager() .resolveService(new Intent(ACTION_QUICKSTEP), 0); String interestingComponent = ri.serviceInfo.name; for (String component : compsList) { if (interestingComponent.equals(component)) { Log.i(TAG_OPS, "Rebinding for component [" + component + "] change"); updateEnabledAndBinding(); return; } } } }; private final ServiceConnection mOverviewServiceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { Log.d(TAG_OPS, "Overview proxy service connected"); mConnectionBackoffAttempts = 0; mHandler.removeCallbacks(mDeferredConnectionCallback); try { service.linkToDeath(mOverviewServiceDeathRcpt, 0); } catch (RemoteException e) { // Failed to link to death (process may have died between binding and connecting), // just unbind the service for now and retry again Log.e(TAG_OPS, "Lost connection to launcher service", e); disconnectFromLauncherService("Lost connection to launcher service"); retryConnectionWithBackoff(); return; } mCurrentBoundedUserId = mUserTracker.getUserId(); mOverviewProxy = IOverviewProxy.Stub.asInterface(service); Bundle params = new Bundle(); params.putBinder(KEY_EXTRA_SYSUI_PROXY, mSysUiProxy.asBinder()); params.putBinder(KEY_EXTRA_UNLOCK_ANIMATION_CONTROLLER, mSysuiUnlockAnimationController.asBinder()); mUnfoldTransitionProgressForwarder.ifPresent( unfoldProgressForwarder -> params.putBinder( KEY_EXTRA_UNFOLD_ANIMATION_FORWARDER, unfoldProgressForwarder.asBinder())); // Add all the interfaces exposed by the shell mShellInterface.createExternalInterfaces(params); try { Log.d(TAG_OPS, "OverviewProxyService connected, initializing overview proxy"); mOverviewProxy.onInitialize(params); } catch (RemoteException e) { mCurrentBoundedUserId = -1; Log.e(TAG_OPS, "Failed to call onInitialize()", e); } dispatchNavButtonBounds(); dispatchNavigationBarSurface(); // Force-update the systemui state flags updateSystemUiStateFlags(); notifySystemUiStateFlags(mSysUiState.getFlags()); notifyConnectionChanged(); if (mLatchForOnUserChanging != null) { mLatchForOnUserChanging.countDown(); mLatchForOnUserChanging = null; } } @Override public void onNullBinding(ComponentName name) { Log.w(TAG_OPS, "Null binding of '" + name + "', try reconnecting"); mCurrentBoundedUserId = -1; retryConnectionWithBackoff(); } @Override public void onBindingDied(ComponentName name) { Log.w(TAG_OPS, "Binding died of '" + name + "', try reconnecting"); mCurrentBoundedUserId = -1; retryConnectionWithBackoff(); } @Override public void onServiceDisconnected(ComponentName name) { Log.w(TAG_OPS, "Service disconnected"); // Do nothing mCurrentBoundedUserId = -1; } }; private final StatusBarWindowCallback mStatusBarWindowCallback = this::onStatusBarStateChanged; // This is the death handler for the binder from the launcher service private final IBinder.DeathRecipient mOverviewServiceDeathRcpt = this::cleanupAfterDeath; private final IVoiceInteractionSessionListener mVoiceInteractionSessionListener = new IVoiceInteractionSessionListener.Stub() { @Override public void onVoiceSessionShown() { // Do nothing } @Override public void onVoiceSessionHidden() { // Do nothing } @Override public void onVoiceSessionWindowVisibilityChanged(boolean visible) { mContext.getMainExecutor().execute(() -> OverviewProxyService.this.onVoiceSessionWindowVisibilityChanged(visible)); } @Override public void onSetUiHints(Bundle hints) { // Do nothing } }; private CountDownLatch mLatchForOnUserChanging; private final UserTracker.Callback mUserChangedCallback = new UserTracker.Callback() { @Override public void onUserChanging(int newUser, @NonNull Context userContext, CountDownLatch latch) { mConnectionBackoffAttempts = 0; mLatchForOnUserChanging = latch; internalConnectToCurrentUser("User changed"); } }; @SuppressWarnings("OptionalUsedAsFieldOrParameterType") @Inject public OverviewProxyService(Context context, @Main Executor mainExecutor, CommandQueue commandQueue, ShellInterface shellInterface, Lazy navBarControllerLazy, Lazy> centralSurfacesOptionalLazy, Lazy shadeViewControllerLazy, NavigationModeController navModeController, NotificationShadeWindowController statusBarWinController, SysUiState sysUiState, Provider sceneInteractor, UserTracker userTracker, WakefulnessLifecycle wakefulnessLifecycle, UiEventLogger uiEventLogger, DisplayTracker displayTracker, KeyguardUnlockAnimationController sysuiUnlockAnimationController, AssistUtils assistUtils, FeatureFlags featureFlags, DumpManager dumpManager, Optional unfoldTransitionProgressForwarder ) { // b/241601880: This component shouldn't be running for a non-primary user if (!Process.myUserHandle().equals(UserHandle.SYSTEM)) { Log.e(TAG_OPS, "Unexpected initialization for non-primary user", new Throwable()); } mContext = context; mFeatureFlags = featureFlags; mMainExecutor = mainExecutor; mShellInterface = shellInterface; mCentralSurfacesOptionalLazy = centralSurfacesOptionalLazy; mShadeViewControllerLazy = shadeViewControllerLazy; mHandler = new Handler(); mNavBarControllerLazy = navBarControllerLazy; mStatusBarWinController = statusBarWinController; mSceneInteractor = sceneInteractor; mUserTracker = userTracker; mConnectionBackoffAttempts = 0; mRecentsComponentName = ComponentName.unflattenFromString(context.getString( com.android.internal.R.string.config_recentsComponentName)); mQuickStepIntent = new Intent(ACTION_QUICKSTEP) .setPackage(mRecentsComponentName.getPackageName()); mSysUiState = sysUiState; mSysUiState.addCallback(this::notifySystemUiStateFlags); mUiEventLogger = uiEventLogger; mDisplayTracker = displayTracker; mUnfoldTransitionProgressForwarder = unfoldTransitionProgressForwarder; mSysuiUnlockAnimationController = sysuiUnlockAnimationController; dumpManager.registerDumpable(getClass().getSimpleName(), this); // Listen for nav bar mode changes mNavBarMode = navModeController.addListener(this); // Listen for launcher package changes IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED); filter.addDataScheme("package"); filter.addDataSchemeSpecificPart(mRecentsComponentName.getPackageName(), PatternMatcher.PATTERN_LITERAL); filter.addAction(Intent.ACTION_PACKAGE_CHANGED); mContext.registerReceiver(mLauncherStateChangedReceiver, filter); // Listen for status bar state changes statusBarWinController.registerCallback(mStatusBarWindowCallback); mScreenshotHelper = new ScreenshotHelper(context); commandQueue.addCallback(new CommandQueue.Callbacks() { // Listen for tracing state changes @Override public void onTracingStateChanged(boolean enabled) { mSysUiState.setFlag(SYSUI_STATE_TRACING_ENABLED, enabled) .commitUpdate(mContext.getDisplayId()); } @Override public void enterStageSplitFromRunningApp(boolean leftOrTop) { if (mOverviewProxy != null) { try { mOverviewProxy.enterStageSplitFromRunningApp(leftOrTop); } catch (RemoteException e) { Log.w(TAG_OPS, "Unable to enter stage split from the current running app"); } } } }); mCommandQueue = commandQueue; // Listen for user setup mUserTracker.addCallback(mUserChangedCallback, mMainExecutor); wakefulnessLifecycle.addObserver(mWakefulnessLifecycleObserver); // Connect to the service updateEnabledAndBinding(); // Listen for assistant changes assistUtils.registerVoiceInteractionSessionListener(mVoiceInteractionSessionListener); } public void onVoiceSessionWindowVisibilityChanged(boolean visible) { mSysUiState.setFlag(SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING, visible) .commitUpdate(mContext.getDisplayId()); } /** * Called when the navigation bar surface is created or changed */ public void onNavigationBarSurfaceChanged(SurfaceControl navbarSurface) { mNavigationBarSurface = navbarSurface; dispatchNavigationBarSurface(); } private void dispatchNavigationBarSurface() { try { if (mOverviewProxy != null) { // Catch all for cases where the surface is no longer valid if (mNavigationBarSurface != null && !mNavigationBarSurface.isValid()) { mNavigationBarSurface = null; } mOverviewProxy.onNavigationBarSurface(mNavigationBarSurface); } } catch (RemoteException e) { Log.e(TAG_OPS, "Failed to notify back action", e); } } private void updateEnabledAndBinding() { updateEnabledState(); startConnectionToCurrentUser(); } private void updateSystemUiStateFlags() { final NavigationBar navBarFragment = mNavBarControllerLazy.get().getDefaultNavigationBar(); final NavigationBarView navBarView = mNavBarControllerLazy.get().getNavigationBarView(mContext.getDisplayId()); if (SysUiState.DEBUG) { Log.d(TAG_OPS, "Updating sysui state flags: navBarFragment=" + navBarFragment + " navBarView=" + navBarView + " shadeViewController=" + mShadeViewControllerLazy.get()); } if (navBarFragment != null) { navBarFragment.updateSystemUiStateFlags(); } if (navBarView != null) { navBarView.updateDisabledSystemUiStateFlags(mSysUiState); } mShadeViewControllerLazy.get().updateSystemUiStateFlags(); if (mStatusBarWinController != null) { mStatusBarWinController.notifyStateChangedCallbacks(); } } private void notifySystemUiStateFlags(int flags) { if (SysUiState.DEBUG) { Log.d(TAG_OPS, "Notifying sysui state change to overview service: proxy=" + mOverviewProxy + " flags=" + flags); } try { if (mOverviewProxy != null) { mOverviewProxy.onSystemUiStateChanged(flags); } } catch (RemoteException e) { Log.e(TAG_OPS, "Failed to notify sysui state change", e); } } private void onStatusBarStateChanged(boolean keyguardShowing, boolean keyguardOccluded, boolean keyguardGoingAway, boolean bouncerShowing, boolean isDozing, boolean panelExpanded, boolean isDreaming) { mSysUiState.setFlag(SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING, keyguardShowing && !keyguardOccluded) .setFlag(SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED, keyguardShowing && keyguardOccluded) .setFlag(SYSUI_STATE_STATUS_BAR_KEYGUARD_GOING_AWAY, keyguardGoingAway) .setFlag(SYSUI_STATE_BOUNCER_SHOWING, bouncerShowing) .setFlag(SYSUI_STATE_DEVICE_DOZING, isDozing) .setFlag(SYSUI_STATE_DEVICE_DREAMING, isDreaming) .commitUpdate(mContext.getDisplayId()); } /** * Sets the navbar region which can receive touch inputs */ public void onActiveNavBarRegionChanges(Region activeRegion) { mActiveNavBarRegion = activeRegion; dispatchNavButtonBounds(); } private void dispatchNavButtonBounds() { if (mOverviewProxy != null && mActiveNavBarRegion != null) { try { mOverviewProxy.onActiveNavBarRegionChanges(mActiveNavBarRegion); } catch (RemoteException e) { Log.e(TAG_OPS, "Failed to call onActiveNavBarRegionChanges()", e); } } } public void cleanupAfterDeath() { if (mInputFocusTransferStarted) { mHandler.post(() -> { mCentralSurfacesOptionalLazy.get().ifPresent(centralSurfaces -> { mInputFocusTransferStarted = false; centralSurfaces.onInputFocusTransfer(false, true /* cancel */, 0 /* velocity */); }); }); } startConnectionToCurrentUser(); } public void startConnectionToCurrentUser() { Log.v(TAG_OPS, "startConnectionToCurrentUser: connection is restarted"); if (mHandler.getLooper() != Looper.myLooper()) { mHandler.post(mConnectionRunnable); } else { internalConnectToCurrentUser("startConnectionToCurrentUser"); } } private void internalConnectToCurrentUser(String reason) { disconnectFromLauncherService(reason); // If user has not setup yet or already connected, do not try to connect if (!isEnabled()) { Log.v(TAG_OPS, "Cannot attempt connection, is enabled " + isEnabled()); return; } mHandler.removeCallbacks(mConnectionRunnable); try { mBound = mContext.bindServiceAsUser(mQuickStepIntent, mOverviewServiceConnection, Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE, UserHandle.of(mUserTracker.getUserId())); } catch (SecurityException e) { Log.e(TAG_OPS, "Unable to bind because of security error", e); } if (mBound) { // Ensure that connection has been established even if it thinks it is bound mHandler.postDelayed(mDeferredConnectionCallback, DEFERRED_CALLBACK_MILLIS); } else { // Retry after exponential backoff timeout retryConnectionWithBackoff(); } } private void retryConnectionWithBackoff() { if (mHandler.hasCallbacks(mConnectionRunnable)) { return; } final long timeoutMs = (long) Math.min( Math.scalb(BACKOFF_MILLIS, mConnectionBackoffAttempts), MAX_BACKOFF_MILLIS); mHandler.postDelayed(mConnectionRunnable, timeoutMs); mConnectionBackoffAttempts++; Log.w(TAG_OPS, "Failed to connect on attempt " + mConnectionBackoffAttempts + " will try again in " + timeoutMs + "ms"); } @Override public void addCallback(@NonNull OverviewProxyListener listener) { if (!mConnectionCallbacks.contains(listener)) { mConnectionCallbacks.add(listener); } listener.onConnectionChanged(mOverviewProxy != null); } @Override public void removeCallback(@NonNull OverviewProxyListener listener) { mConnectionCallbacks.remove(listener); } public boolean shouldShowSwipeUpUI() { return isEnabled() && !QuickStepContract.isLegacyMode(mNavBarMode); } public boolean isEnabled() { return mIsEnabled; } public IOverviewProxy getProxy() { return mOverviewProxy; } private void disconnectFromLauncherService(String disconnectReason) { Log.d(TAG_OPS, "disconnectFromLauncherService bound?: " + mBound + " currentProxy: " + mOverviewProxy + " disconnectReason: " + disconnectReason, new Throwable()); if (mBound) { // Always unbind the service (ie. if called through onNullBinding or onBindingDied) mContext.unbindService(mOverviewServiceConnection); mBound = false; } if (mOverviewProxy != null) { mOverviewProxy.asBinder().unlinkToDeath(mOverviewServiceDeathRcpt, 0); mOverviewProxy = null; notifyConnectionChanged(); } } private void notifyHomeRotationEnabled(boolean enabled) { for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) { mConnectionCallbacks.get(i).onHomeRotationEnabled(enabled); } } private void onTaskbarStatusUpdated(boolean visible, boolean stashed) { for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) { mConnectionCallbacks.get(i).onTaskbarStatusUpdated(visible, stashed); } } private void onTaskbarAutohideSuspend(boolean suspend) { for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) { mConnectionCallbacks.get(i).onTaskbarAutohideSuspend(suspend); } } private void notifyConnectionChanged() { for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) { mConnectionCallbacks.get(i).onConnectionChanged(mOverviewProxy != null); } } private void notifyPrioritizedRotationInternal(@Surface.Rotation int rotation) { for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) { mConnectionCallbacks.get(i).onPrioritizedRotation(rotation); } } private void notifyAssistantProgress(@FloatRange(from = 0.0, to = 1.0) float progress) { for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) { mConnectionCallbacks.get(i).onAssistantProgress(progress); } } private void notifyAssistantGestureCompletion(float velocity) { for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) { mConnectionCallbacks.get(i).onAssistantGestureCompletion(velocity); } } private void notifyStartAssistant(Bundle bundle) { for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) { mConnectionCallbacks.get(i).startAssistant(bundle); } } private void notifyAssistantOverrideRequested(int[] invocationTypes) { for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) { mConnectionCallbacks.get(i).setAssistantOverridesRequested(invocationTypes); } } public void notifyAssistantVisibilityChanged(float visibility) { try { if (mOverviewProxy != null) { mOverviewProxy.onAssistantVisibilityChanged(visibility); } else { Log.e(TAG_OPS, "Failed to get overview proxy for assistant visibility."); } } catch (RemoteException e) { Log.e(TAG_OPS, "Failed to call notifyAssistantVisibilityChanged()", e); } } private final WakefulnessLifecycle.Observer mWakefulnessLifecycleObserver = new WakefulnessLifecycle.Observer() { @Override public void onStartedWakingUp() { mSysUiState .setFlag(SYSUI_STATE_AWAKE, true) .setFlag(SYSUI_STATE_WAKEFULNESS_TRANSITION, true) .commitUpdate(mContext.getDisplayId()); } @Override public void onFinishedWakingUp() { mSysUiState .setFlag(SYSUI_STATE_AWAKE, true) .setFlag(SYSUI_STATE_WAKEFULNESS_TRANSITION, false) .commitUpdate(mContext.getDisplayId()); } @Override public void onStartedGoingToSleep() { mSysUiState .setFlag(SYSUI_STATE_AWAKE, false) .setFlag(SYSUI_STATE_WAKEFULNESS_TRANSITION, true) .commitUpdate(mContext.getDisplayId()); } @Override public void onFinishedGoingToSleep() { mSysUiState .setFlag(SYSUI_STATE_AWAKE, false) .setFlag(SYSUI_STATE_WAKEFULNESS_TRANSITION, false) .commitUpdate(mContext.getDisplayId()); } }; void notifyToggleRecentApps() { for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) { mConnectionCallbacks.get(i).onToggleRecentApps(); } } public void disable(int displayId, int state1, int state2, boolean animate) { try { if (mOverviewProxy != null) { mOverviewProxy.disable(displayId, state1, state2, animate); } else { Log.e(TAG_OPS, "Failed to get overview proxy for disable flags."); } } catch (RemoteException e) { Log.e(TAG_OPS, "Failed to call disable()", e); } } public void onRotationProposal(int rotation, boolean isValid) { try { if (mOverviewProxy != null) { mOverviewProxy.onRotationProposal(rotation, isValid); } else { Log.e(TAG_OPS, "Failed to get overview proxy for proposing rotation."); } } catch (RemoteException e) { Log.e(TAG_OPS, "Failed to call onRotationProposal()", e); } } public void onSystemBarAttributesChanged(int displayId, int behavior) { try { if (mOverviewProxy != null) { mOverviewProxy.onSystemBarAttributesChanged(displayId, behavior); } else { Log.e(TAG_OPS, "Failed to get overview proxy for system bar attr change."); } } catch (RemoteException e) { Log.e(TAG_OPS, "Failed to call onSystemBarAttributesChanged()", e); } } public void onNavButtonsDarkIntensityChanged(float darkIntensity) { try { if (mOverviewProxy != null) { mOverviewProxy.onNavButtonsDarkIntensityChanged(darkIntensity); } else { Log.e(TAG_OPS, "Failed to get overview proxy to update nav buttons dark intensity"); } } catch (RemoteException e) { Log.e(TAG_OPS, "Failed to call onNavButtonsDarkIntensityChanged()", e); } } private void updateEnabledState() { final int currentUser = mUserTracker.getUserId(); mIsEnabled = mContext.getPackageManager().resolveServiceAsUser(mQuickStepIntent, MATCH_SYSTEM_ONLY, currentUser) != null; } @Override public void onNavigationModeChanged(int mode) { mNavBarMode = mode; } @Override public void dump(PrintWriter pw, String[] args) { pw.println(TAG_OPS + " state:"); pw.print(" isConnected="); pw.println(mOverviewProxy != null); pw.print(" mIsEnabled="); pw.println(isEnabled()); pw.print(" mRecentsComponentName="); pw.println(mRecentsComponentName); pw.print(" mQuickStepIntent="); pw.println(mQuickStepIntent); pw.print(" mBound="); pw.println(mBound); pw.print(" mCurrentBoundedUserId="); pw.println(mCurrentBoundedUserId); pw.print(" mConnectionBackoffAttempts="); pw.println(mConnectionBackoffAttempts); pw.print(" mInputFocusTransferStarted="); pw.println(mInputFocusTransferStarted); pw.print(" mInputFocusTransferStartY="); pw.println(mInputFocusTransferStartY); pw.print(" mInputFocusTransferStartMillis="); pw.println(mInputFocusTransferStartMillis); pw.print(" mActiveNavBarRegion="); pw.println(mActiveNavBarRegion); pw.print(" mNavigationBarSurface="); pw.println(mNavigationBarSurface); pw.print(" mNavBarMode="); pw.println(mNavBarMode); mSysUiState.dump(pw, args); } public interface OverviewProxyListener { default void onConnectionChanged(boolean isConnected) {} default void onPrioritizedRotation(@Surface.Rotation int rotation) {} default void onOverviewShown(boolean fromHome) {} /** Notify the recents app (overview) is started by 3-button navigation. */ default void onToggleRecentApps() {} default void onHomeRotationEnabled(boolean enabled) {} default void onTaskbarStatusUpdated(boolean visible, boolean stashed) {} default void onTaskbarAutohideSuspend(boolean suspend) {} default void onAssistantProgress(@FloatRange(from = 0.0, to = 1.0) float progress) {} default void onAssistantGestureCompletion(float velocity) {} default void startAssistant(Bundle bundle) {} default void setAssistantOverridesRequested(int[] invocationTypes) {} } /** * Shuts down this service at the end of a testcase. *

* The in-production service is never shuts down, and it was not designed with testing in mind. * This unregisters the mechanisms by which the service will be revived after a testcase. *

* NOTE: This is a stop-gap introduced when first added some tests to this class. It should * probably be replaced by proper lifecycle management on this class. */ @VisibleForTesting() void shutdownForTest() { mContext.unregisterReceiver(mLauncherStateChangedReceiver); mIsEnabled = false; mHandler.removeCallbacks(mConnectionRunnable); disconnectFromLauncherService("Shutdown for test"); } }