blob: cb0c411b27538132e0ded6f6303693a4a9bd9436 [file] [log] [blame]
/*
* 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.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.WindowManager.ScreenshotSource.SCREENSHOT_OVERVIEW;
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_SHELL_ONE_HANDED;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_PIP;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_SHELL_TRANSITIONS;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_SPLIT_SCREEN;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_STARTING_WINDOW;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SMARTSPACE_TRANSITION_CONTROLLER;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SUPPORTS_WINDOW_CORNERS;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_WINDOW_CORNER_RADIUS;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING;
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 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.graphics.Bitmap;
import android.graphics.Insets;
import android.graphics.Rect;
import android.graphics.Region;
import android.hardware.input.InputManager;
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.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.accessibility.AccessibilityManager;
import androidx.annotation.NonNull;
import com.android.internal.accessibility.dialog.AccessibilityButtonChooserActivity;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.policy.ScreenDecorationsUtils;
import com.android.internal.util.ScreenshotHelper;
import com.android.systemui.Dumpable;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
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.recents.OverviewProxyService.OverviewProxyListener;
import com.android.systemui.settings.CurrentUserTracker;
import com.android.systemui.shared.recents.IOverviewProxy;
import com.android.systemui.shared.recents.ISystemUiProxy;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.shared.system.smartspace.SmartspaceTransitionController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.phone.StatusBarWindowCallback;
import com.android.systemui.statusbar.policy.CallbackController;
import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
import com.android.wm.shell.onehanded.OneHanded;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.pip.PipAnimationController;
import com.android.wm.shell.splitscreen.SplitScreen;
import com.android.wm.shell.startingsurface.StartingSurface;
import com.android.wm.shell.transition.ShellTransitions;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.function.BiConsumer;
import javax.inject.Inject;
import dagger.Lazy;
/**
* Class to send information from overview to launcher with a binder.
*/
@SysUISingleton
public class OverviewProxyService extends CurrentUserTracker implements
CallbackController<OverviewProxyListener>, NavigationModeController.ModeChangedListener,
Dumpable {
private 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 Optional<Pip> mPipOptional;
private final Optional<Lazy<StatusBar>> mStatusBarOptionalLazy;
private final Optional<LegacySplitScreen> mLegacySplitScreenOptional;
private final Optional<SplitScreen> mSplitScreenOptional;
private SysUiState mSysUiState;
private final Handler mHandler;
private final Lazy<NavigationBarController> mNavBarControllerLazy;
private final NotificationShadeWindowController mStatusBarWinController;
private final Runnable mConnectionRunnable = this::internalConnectToCurrentUser;
private final ComponentName mRecentsComponentName;
private final List<OverviewProxyListener> mConnectionCallbacks = new ArrayList<>();
private final Intent mQuickStepIntent;
private final ScreenshotHelper mScreenshotHelper;
private final Optional<OneHanded> mOneHandedOptional;
private final CommandQueue mCommandQueue;
private final ShellTransitions mShellTransitions;
private final Optional<StartingSurface> mStartingSurface;
private final SmartspaceTransitionController mSmartspaceTransitionController;
private Region mActiveNavBarRegion;
private IOverviewProxy mOverviewProxy;
private int mConnectionBackoffAttempts;
private boolean mBound;
private boolean mIsEnabled;
private int mCurrentBoundedUserId = -1;
private float mNavBarButtonAlpha;
private boolean mInputFocusTransferStarted;
private float mInputFocusTransferStartY;
private long mInputFocusTransferStartMillis;
private float mWindowCornerRadius;
private boolean mSupportsRoundedCornersOnWindows;
private int mNavBarMode = NAV_BAR_MODE_3BUTTON;
@VisibleForTesting
public ISystemUiProxy mSysUiProxy = new ISystemUiProxy.Stub() {
@Override
public void startScreenPinning(int taskId) {
if (!verifyCaller("startScreenPinning")) {
return;
}
final long token = Binder.clearCallingIdentity();
try {
mHandler.post(() -> {
mStatusBarOptionalLazy.ifPresent(
statusBarLazy -> statusBarLazy.get().showScreenPinningRequest(taskId,
false /* allowCancel */));
});
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override
public void stopScreenPinning() {
if (!verifyCaller("stopScreenPinning")) {
return;
}
final long token = Binder.clearCallingIdentity();
try {
mHandler.post(() -> {
try {
ActivityTaskManager.getService().stopSystemLockTaskMode();
} catch (RemoteException e) {
Log.e(TAG_OPS, "Failed to stop screen pinning");
}
});
} finally {
Binder.restoreCallingIdentity(token);
}
}
// TODO: change the method signature to use (boolean inputFocusTransferStarted)
@Override
public void onStatusBarMotionEvent(MotionEvent event) {
if (!verifyCaller("onStatusBarMotionEvent")) {
return;
}
final long token = Binder.clearCallingIdentity();
try {
// TODO move this logic to message queue
mStatusBarOptionalLazy.ifPresent(statusBarLazy -> {
StatusBar statusBar = statusBarLazy.get();
if (event.getActionMasked() == ACTION_DOWN) {
statusBar.getPanelController().startExpandLatencyTracking();
}
mHandler.post(()-> {
int action = event.getActionMasked();
if (action == ACTION_DOWN) {
mInputFocusTransferStarted = true;
mInputFocusTransferStartY = event.getY();
mInputFocusTransferStartMillis = event.getEventTime();
statusBar.onInputFocusTransfer(
mInputFocusTransferStarted, false /* cancel */,
0 /* velocity */);
}
if (action == ACTION_UP || action == ACTION_CANCEL) {
mInputFocusTransferStarted = false;
statusBar.onInputFocusTransfer(mInputFocusTransferStarted,
action == ACTION_CANCEL,
(event.getY() - mInputFocusTransferStartY)
/ (event.getEventTime() - mInputFocusTransferStartMillis));
}
event.recycle();
});
});
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override
public void onBackPressed() throws RemoteException {
if (!verifyCaller("onBackPressed")) {
return;
}
final long token = Binder.clearCallingIdentity();
try {
mHandler.post(() -> {
sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK);
sendEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK);
notifyBackAction(true, -1, -1, true, false);
});
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override
public void setHomeRotationEnabled(boolean enabled) {
if (!verifyCaller("setHomeRotationEnabled")) {
return;
}
final long token = Binder.clearCallingIdentity();
try {
mHandler.post(() -> {
mHandler.post(() -> notifyHomeRotationEnabled(enabled));
});
} finally {
Binder.restoreCallingIdentity(token);
}
}
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 InputManager.getInstance()
.injectInputEvent(ev, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
}
@Override
public void onOverviewShown(boolean fromHome) {
if (!verifyCaller("onOverviewShown")) {
return;
}
final long token = Binder.clearCallingIdentity();
try {
mHandler.post(() -> {
for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
mConnectionCallbacks.get(i).onOverviewShown(fromHome);
}
});
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override
public Rect getNonMinimizedSplitScreenSecondaryBounds() {
if (!verifyCaller("getNonMinimizedSplitScreenSecondaryBounds")) {
return null;
}
final long token = Binder.clearCallingIdentity();
try {
return mLegacySplitScreenOptional.map(splitScreen ->
splitScreen.getDividerView().getNonMinimizedSplitScreenSecondaryBounds())
.orElse(null);
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override
public void setNavBarButtonAlpha(float alpha, boolean animate) {
if (!verifyCaller("setNavBarButtonAlpha")) {
return;
}
final long token = Binder.clearCallingIdentity();
try {
mNavBarButtonAlpha = alpha;
mHandler.post(() -> notifyNavBarButtonAlphaChanged(alpha, animate));
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override
public void onAssistantProgress(@FloatRange(from = 0.0, to = 1.0) float progress) {
if (!verifyCaller("onAssistantProgress")) {
return;
}
final long token = Binder.clearCallingIdentity();
try {
mHandler.post(() -> notifyAssistantProgress(progress));
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override
public void onAssistantGestureCompletion(float velocity) {
if (!verifyCaller("onAssistantGestureCompletion")) {
return;
}
final long token = Binder.clearCallingIdentity();
try {
mHandler.post(() -> notifyAssistantGestureCompletion(velocity));
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override
public void startAssistant(Bundle bundle) {
if (!verifyCaller("startAssistant")) {
return;
}
final long token = Binder.clearCallingIdentity();
try {
mHandler.post(() -> notifyStartAssistant(bundle));
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override
public void notifyAccessibilityButtonClicked(int displayId) {
if (!verifyCaller("notifyAccessibilityButtonClicked")) {
return;
}
final long token = Binder.clearCallingIdentity();
try {
AccessibilityManager.getInstance(mContext)
.notifyAccessibilityButtonClicked(displayId);
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override
public void notifyAccessibilityButtonLongClicked() {
if (!verifyCaller("notifyAccessibilityButtonLongClicked")) {
return;
}
final long token = Binder.clearCallingIdentity();
try {
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, UserHandle.CURRENT);
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override
public void handleImageAsScreenshot(Bitmap screenImage, Rect locationInScreen,
Insets visibleInsets, int taskId) {
// Deprecated
}
@Override
public void setSplitScreenMinimized(boolean minimized) {
mLegacySplitScreenOptional.ifPresent(
splitScreen -> splitScreen.setMinimized(minimized));
}
@Override
public void notifySwipeToHomeFinished() {
if (!verifyCaller("notifySwipeToHomeFinished")) {
return;
}
final long token = Binder.clearCallingIdentity();
try {
mPipOptional.ifPresent(
pip -> pip.setPinnedStackAnimationType(
PipAnimationController.ANIM_TYPE_ALPHA));
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override
public void notifySwipeUpGestureStarted() {
if (!verifyCaller("notifySwipeUpGestureStarted")) {
return;
}
final long token = Binder.clearCallingIdentity();
try {
mHandler.post(() -> notifySwipeUpGestureStartedInternal());
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override
public void notifyPrioritizedRotation(@Surface.Rotation int rotation) {
if (!verifyCaller("notifyPrioritizedRotation")) {
return;
}
final long token = Binder.clearCallingIdentity();
try {
mHandler.post(() -> notifyPrioritizedRotationInternal(rotation));
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override
public void handleImageBundleAsScreenshot(Bundle screenImageBundle, Rect locationInScreen,
Insets visibleInsets, Task.TaskKey task) {
mScreenshotHelper.provideScreenshot(
screenImageBundle,
locationInScreen,
visibleInsets,
task.id,
task.userId,
task.sourceComponent,
SCREENSHOT_OVERVIEW,
mHandler,
null);
}
@Override
public void expandNotificationPanel() {
if (!verifyCaller("expandNotificationPanel")) {
return;
}
final long token = Binder.clearCallingIdentity();
try {
mCommandQueue.handleSystemKey(KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN);
} finally {
Binder.restoreCallingIdentity(token);
}
}
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 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) {
updateEnabledState();
// Reconnect immediately, instead of waiting for resume to arrive.
startConnectionToCurrentUser();
}
};
private final ServiceConnection mOverviewServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
if (SysUiState.DEBUG) {
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();
retryConnectionWithBackoff();
return;
}
mCurrentBoundedUserId = getCurrentUserId();
mOverviewProxy = IOverviewProxy.Stub.asInterface(service);
Bundle params = new Bundle();
params.putBinder(KEY_EXTRA_SYSUI_PROXY, mSysUiProxy.asBinder());
params.putFloat(KEY_EXTRA_WINDOW_CORNER_RADIUS, mWindowCornerRadius);
params.putBoolean(KEY_EXTRA_SUPPORTS_WINDOW_CORNERS, mSupportsRoundedCornersOnWindows);
mPipOptional.ifPresent((pip) -> params.putBinder(
KEY_EXTRA_SHELL_PIP,
pip.createExternalInterface().asBinder()));
mSplitScreenOptional.ifPresent((splitscreen) -> params.putBinder(
KEY_EXTRA_SHELL_SPLIT_SCREEN,
splitscreen.createExternalInterface().asBinder()));
mOneHandedOptional.ifPresent((onehanded) -> params.putBinder(
KEY_EXTRA_SHELL_ONE_HANDED,
onehanded.createExternalInterface().asBinder()));
params.putBinder(KEY_EXTRA_SHELL_SHELL_TRANSITIONS,
mShellTransitions.createExternalInterface().asBinder());
mStartingSurface.ifPresent((startingwindow) -> params.putBinder(
KEY_EXTRA_SHELL_STARTING_WINDOW,
startingwindow.createExternalInterface().asBinder()));
params.putBinder(
KEY_EXTRA_SMARTSPACE_TRANSITION_CONTROLLER,
mSmartspaceTransitionController.createExternalInterface().asBinder());
try {
mOverviewProxy.onInitialize(params);
} catch (RemoteException e) {
mCurrentBoundedUserId = -1;
Log.e(TAG_OPS, "Failed to call onInitialize()", e);
}
dispatchNavButtonBounds();
// Force-update the systemui state flags
updateSystemUiStateFlags();
notifySystemUiStateFlags(mSysUiState.getFlags());
notifyConnectionChanged();
}
@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;
private final BiConsumer<Rect, Rect> mSplitScreenBoundsChangeListener =
this::notifySplitScreenBoundsChanged;
// This is the death handler for the binder from the launcher service
private final IBinder.DeathRecipient mOverviewServiceDeathRcpt
= this::cleanupAfterDeath;
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
@Inject
public OverviewProxyService(Context context, CommandQueue commandQueue,
Lazy<NavigationBarController> navBarControllerLazy,
NavigationModeController navModeController,
NotificationShadeWindowController statusBarWinController, SysUiState sysUiState,
Optional<Pip> pipOptional,
Optional<LegacySplitScreen> legacySplitScreenOptional,
Optional<SplitScreen> splitScreenOptional,
Optional<Lazy<StatusBar>> statusBarOptionalLazy,
Optional<OneHanded> oneHandedOptional,
BroadcastDispatcher broadcastDispatcher,
ShellTransitions shellTransitions,
Optional<StartingSurface> startingSurface,
SmartspaceTransitionController smartspaceTransitionController) {
super(broadcastDispatcher);
mContext = context;
mPipOptional = pipOptional;
mStatusBarOptionalLazy = statusBarOptionalLazy;
mHandler = new Handler();
mNavBarControllerLazy = navBarControllerLazy;
mStatusBarWinController = statusBarWinController;
mConnectionBackoffAttempts = 0;
mRecentsComponentName = ComponentName.unflattenFromString(context.getString(
com.android.internal.R.string.config_recentsComponentName));
mQuickStepIntent = new Intent(ACTION_QUICKSTEP)
.setPackage(mRecentsComponentName.getPackageName());
mWindowCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(mContext.getResources());
mSupportsRoundedCornersOnWindows = ScreenDecorationsUtils
.supportsRoundedCornersOnWindows(mContext.getResources());
mSysUiState = sysUiState;
mSysUiState.addCallback(this::notifySystemUiStateFlags);
mOneHandedOptional = oneHandedOptional;
mShellTransitions = shellTransitions;
// Assumes device always starts with back button until launcher tells it that it does not
mNavBarButtonAlpha = 1.0f;
// 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);
// Listen for tracing state changes
commandQueue.addCallback(new CommandQueue.Callbacks() {
@Override
public void onTracingStateChanged(boolean enabled) {
mSysUiState.setFlag(SYSUI_STATE_TRACING_ENABLED, enabled)
.commitUpdate(mContext.getDisplayId());
}
});
mCommandQueue = commandQueue;
mSplitScreenOptional = splitScreenOptional;
legacySplitScreenOptional.ifPresent(splitScreen ->
splitScreen.registerBoundsChangeListener(mSplitScreenBoundsChangeListener));
mLegacySplitScreenOptional = legacySplitScreenOptional;
// Listen for user setup
startTracking();
// Connect to the service
updateEnabledState();
startConnectionToCurrentUser();
mStartingSurface = startingSurface;
mSmartspaceTransitionController = smartspaceTransitionController;
}
@Override
public void onUserSwitched(int newUserId) {
mConnectionBackoffAttempts = 0;
internalConnectToCurrentUser();
}
public void notifyBackAction(boolean completed, int downX, int downY, boolean isButton,
boolean gestureSwipeLeft) {
try {
if (mOverviewProxy != null) {
mOverviewProxy.onBackAction(completed, downX, downY, isButton, gestureSwipeLeft);
}
} catch (RemoteException e) {
Log.e(TAG_OPS, "Failed to notify back action", e);
}
}
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);
}
if (navBarFragment != null) {
navBarFragment.updateSystemUiStateFlags(-1);
}
if (navBarView != null) {
navBarView.updatePanelSystemUiStateFlags();
navBarView.updateDisabledSystemUiStateFlags();
}
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 bouncerShowing) {
mSysUiState.setFlag(SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING,
keyguardShowing && !keyguardOccluded)
.setFlag(SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED,
keyguardShowing && keyguardOccluded)
.setFlag(SYSUI_STATE_BOUNCER_SHOWING, bouncerShowing)
.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(() -> {
mStatusBarOptionalLazy.ifPresent(statusBarLazy -> {
mInputFocusTransferStarted = false;
statusBarLazy.get().onInputFocusTransfer(false, true /* cancel */,
0 /* velocity */);
});
});
}
startConnectionToCurrentUser();
// Clean up the minimized state if launcher dies
mLegacySplitScreenOptional.ifPresent(
splitScreen -> splitScreen.setMinimized(false));
}
public void startConnectionToCurrentUser() {
if (mHandler.getLooper() != Looper.myLooper()) {
mHandler.post(mConnectionRunnable);
} else {
internalConnectToCurrentUser();
}
}
private void internalConnectToCurrentUser() {
disconnectFromLauncherService();
// 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);
Intent launcherServiceIntent = new Intent(ACTION_QUICKSTEP)
.setPackage(mRecentsComponentName.getPackageName());
try {
mBound = mContext.bindServiceAsUser(launcherServiceIntent,
mOverviewServiceConnection,
Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
UserHandle.of(getCurrentUserId()));
} 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);
listener.onNavBarButtonAlphaChanged(mNavBarButtonAlpha, false);
}
@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() {
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;
notifyNavBarButtonAlphaChanged(1f, false /* animate */);
notifyConnectionChanged();
}
}
private void notifyNavBarButtonAlphaChanged(float alpha, boolean animate) {
for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
mConnectionCallbacks.get(i).onNavBarButtonAlphaChanged(alpha, animate);
}
}
private void notifyHomeRotationEnabled(boolean enabled) {
for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
mConnectionCallbacks.get(i).onHomeRotationEnabled(enabled);
}
}
private void notifyConnectionChanged() {
for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
mConnectionCallbacks.get(i).onConnectionChanged(mOverviewProxy != null);
}
}
public void notifyQuickStepStarted() {
for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
mConnectionCallbacks.get(i).onQuickStepStarted();
}
}
private void notifyPrioritizedRotationInternal(@Surface.Rotation int rotation) {
for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
mConnectionCallbacks.get(i).onPrioritizedRotation(rotation);
}
}
public void notifyQuickScrubStarted() {
for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
mConnectionCallbacks.get(i).onQuickScrubStarted();
}
}
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 notifySwipeUpGestureStartedInternal() {
for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
mConnectionCallbacks.get(i).onSwipeUpGestureStarted();
}
}
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);
}
}
/**
* Notifies the Launcher of split screen size changes
*
* @param secondaryWindowBounds Bounds of the secondary window including the insets
* @param secondaryWindowInsets stable insets received by the secondary window
*/
public void notifySplitScreenBoundsChanged(
Rect secondaryWindowBounds, Rect secondaryWindowInsets) {
try {
if (mOverviewProxy != null) {
mOverviewProxy.onSplitScreenSecondaryBoundsChanged(
secondaryWindowBounds, secondaryWindowInsets);
} else {
Log.e(TAG_OPS, "Failed to get overview proxy for split screen bounds.");
}
} catch (RemoteException e) {
Log.e(TAG_OPS, "Failed to call onSplitScreenSecondaryBoundsChanged()", e);
}
}
void notifyToggleRecentApps() {
for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
mConnectionCallbacks.get(i).onToggleRecentApps();
}
}
public void notifyImeWindowStatus(int displayId, IBinder token, int vis, int backDisposition,
boolean showImeSwitcher) {
try {
if (mOverviewProxy != null) {
mOverviewProxy.onImeWindowStatusChanged(displayId, token, vis, backDisposition,
showImeSwitcher);
} else {
Log.e(TAG_OPS, "Failed to get overview proxy for setting IME status.");
}
} catch (RemoteException e) {
Log.e(TAG_OPS, "Failed to call notifyImeWindowStatus()", e);
}
}
private void updateEnabledState() {
final int currentUser = ActivityManagerWrapper.getInstance().getCurrentUserId();
mIsEnabled = mContext.getPackageManager().resolveServiceAsUser(mQuickStepIntent,
MATCH_SYSTEM_ONLY, currentUser) != null;
}
@Override
public void onNavigationModeChanged(int mode) {
mNavBarMode = mode;
}
@Override
public void dump(FileDescriptor fd, 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(" mWindowCornerRadius="); pw.println(mWindowCornerRadius);
pw.print(" mSupportsRoundedCornersOnWindows="); pw.println(mSupportsRoundedCornersOnWindows);
pw.print(" mNavBarButtonAlpha="); pw.println(mNavBarButtonAlpha);
pw.print(" mActiveNavBarRegion="); pw.println(mActiveNavBarRegion);
pw.print(" mNavBarMode="); pw.println(mNavBarMode);
mSysUiState.dump(fd, pw, args);
}
public interface OverviewProxyListener {
default void onConnectionChanged(boolean isConnected) {}
default void onQuickStepStarted() {}
default void onSwipeUpGestureStarted() {}
default void onPrioritizedRotation(@Surface.Rotation int rotation) {}
default void onOverviewShown(boolean fromHome) {}
default void onQuickScrubStarted() {}
/** Notify the recents app (overview) is started by 3-button navigation. */
default void onToggleRecentApps() {}
/** Notify changes in the nav bar button alpha */
default void onNavBarButtonAlphaChanged(float alpha, boolean animate) {}
default void onHomeRotationEnabled(boolean enabled) {}
default void onSystemUiStateChanged(int sysuiStateFlags) {}
default void onAssistantProgress(@FloatRange(from = 0.0, to = 1.0) float progress) {}
default void onAssistantGestureCompletion(float velocity) {}
default void startAssistant(Bundle bundle) {}
default void onImeWindowStatusChanged(int displayId, IBinder token, int vis,
int backDisposition, boolean showImeSwitcher) {}
}
}