blob: 80682949cf693954b872932149cf5b2b7e798b5c [file] [log] [blame]
/*
* Copyright (C) 2022 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.accessibilityservice.AccessibilityService.GLOBAL_ACTION_ACCESSIBILITY_ALL_APPS;
import static android.view.MotionEvent.ACTION_CANCEL;
import static android.view.MotionEvent.ACTION_DOWN;
import static android.view.MotionEvent.ACTION_MOVE;
import static android.view.MotionEvent.ACTION_POINTER_DOWN;
import static android.view.MotionEvent.ACTION_POINTER_UP;
import static android.view.MotionEvent.ACTION_UP;
import static com.android.launcher3.Launcher.INTENT_ACTION_ALL_APPS_TOGGLE;
import static com.android.launcher3.MotionEventsUtils.isTrackpadMotionEvent;
import static com.android.launcher3.MotionEventsUtils.isTrackpadMultiFingerSwipe;
import static com.android.launcher3.config.FeatureFlags.ENABLE_TRACKPAD_GESTURE;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.window.WindowManagerProxy.MIN_TABLET_WIDTH;
import static com.android.quickstep.GestureState.DEFAULT_STATE;
import static com.android.quickstep.GestureState.TrackpadGestureType.getTrackpadGestureType;
import static com.android.quickstep.InputConsumer.TYPE_CURSOR_HOVER;
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.FLAG_USING_OTHER_ACTIVITY_INPUT_CONSUMER;
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.MOTION_DOWN;
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.MOTION_MOVE;
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.MOTION_UP;
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
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_NOTIFICATION_PANEL_EXPANDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_BACK_ANIMATION;
import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_BUBBLES;
import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_DESKTOP_MODE;
import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_DRAG_AND_DROP;
import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_ONE_HANDED;
import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_PIP;
import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_RECENT_TASKS;
import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_SHELL_TRANSITIONS;
import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_SPLIT_SCREEN;
import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_STARTING_WINDOW;
import android.annotation.TargetApi;
import android.app.PendingIntent;
import android.app.RemoteAction;
import android.app.Service;
import android.content.IIntentReceiver;
import android.content.IIntentSender;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.graphics.Region;
import android.graphics.drawable.Icon;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Looper;
import android.os.SystemClock;
import android.os.Trace;
import android.util.Log;
import android.view.Choreographer;
import android.view.InputDevice;
import android.view.InputEvent;
import android.view.MotionEvent;
import android.view.SurfaceControl;
import android.view.accessibility.AccessibilityManager;
import androidx.annotation.BinderThread;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.R;
import com.android.launcher3.anim.AnimatedFloat;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.provider.RestoreDbTask;
import com.android.launcher3.statemanager.StatefulActivity;
import com.android.launcher3.taskbar.TaskbarActivityContext;
import com.android.launcher3.taskbar.TaskbarManager;
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.shared.ResourceUtils;
import com.android.launcher3.testing.shared.TestProtocol;
import com.android.launcher3.uioverrides.flags.FlagsFactory;
import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.LockedUserState;
import com.android.launcher3.util.OnboardingPrefs;
import com.android.launcher3.util.SafeCloseable;
import com.android.launcher3.util.TraceHelper;
import com.android.quickstep.inputconsumers.AccessibilityInputConsumer;
import com.android.quickstep.inputconsumers.AssistantInputConsumer;
import com.android.quickstep.inputconsumers.DeviceLockedInputConsumer;
import com.android.quickstep.inputconsumers.NavHandleLongPressInputConsumer;
import com.android.quickstep.inputconsumers.OneHandedModeInputConsumer;
import com.android.quickstep.inputconsumers.OtherActivityInputConsumer;
import com.android.quickstep.inputconsumers.OverviewInputConsumer;
import com.android.quickstep.inputconsumers.OverviewWithoutFocusInputConsumer;
import com.android.quickstep.inputconsumers.ProgressDelegateInputConsumer;
import com.android.quickstep.inputconsumers.ResetGestureInputConsumer;
import com.android.quickstep.inputconsumers.ScreenPinnedInputConsumer;
import com.android.quickstep.inputconsumers.SysUiOverlayInputConsumer;
import com.android.quickstep.inputconsumers.TaskbarUnstashInputConsumer;
import com.android.quickstep.inputconsumers.TrackpadStatusBarInputConsumer;
import com.android.quickstep.util.ActiveGestureLog;
import com.android.quickstep.util.ActiveGestureLog.CompoundString;
import com.android.quickstep.util.AssistStateManager;
import com.android.quickstep.util.AssistUtils;
import com.android.systemui.shared.recents.IOverviewProxy;
import com.android.systemui.shared.recents.ISystemUiProxy;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.InputChannelCompat.InputEventReceiver;
import com.android.systemui.shared.system.InputConsumerController;
import com.android.systemui.shared.system.InputMonitorCompat;
import com.android.systemui.shared.system.smartspace.ISysuiUnlockAnimationController;
import com.android.systemui.unfold.progress.IUnfoldAnimation;
import com.android.wm.shell.back.IBackAnimation;
import com.android.wm.shell.bubbles.IBubbles;
import com.android.wm.shell.desktopmode.IDesktopMode;
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.recents.IRecentTasks;
import com.android.wm.shell.splitscreen.ISplitScreen;
import com.android.wm.shell.startingsurface.IStartingWindow;
import com.android.wm.shell.transition.IShellTransitions;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.util.function.Consumer;
import java.util.function.Function;
/**
* Service connected by system-UI for handling touch interaction.
*/
@TargetApi(Build.VERSION_CODES.R)
public class TouchInteractionService extends Service {
private static final String SUBSTRING_PREFIX = "; ";
private static final String NEWLINE_PREFIX = "\n\t\t\t-> ";
private static final String TAG = "TouchInteractionService";
private static final String HAS_ENABLED_QUICKSTEP_ONCE = "launcher.has_enabled_quickstep_once";
private final TISBinder mTISBinder = new TISBinder(this);
/**
* Local IOverviewProxy implementation with some methods for local components
*/
public static class TISBinder extends IOverviewProxy.Stub {
private final WeakReference<TouchInteractionService> mTis;
@Nullable private Runnable mOnOverviewTargetChangeListener = null;
private TISBinder(TouchInteractionService tis) {
mTis = new WeakReference<>(tis);
}
@BinderThread
public void onInitialize(Bundle bundle) {
ISystemUiProxy proxy = ISystemUiProxy.Stub.asInterface(
bundle.getBinder(KEY_EXTRA_SYSUI_PROXY));
IPip pip = IPip.Stub.asInterface(bundle.getBinder(KEY_EXTRA_SHELL_PIP));
IBubbles bubbles = IBubbles.Stub.asInterface(bundle.getBinder(KEY_EXTRA_SHELL_BUBBLES));
ISplitScreen splitscreen = ISplitScreen.Stub.asInterface(bundle.getBinder(
KEY_EXTRA_SHELL_SPLIT_SCREEN));
IOneHanded onehanded = IOneHanded.Stub.asInterface(
bundle.getBinder(KEY_EXTRA_SHELL_ONE_HANDED));
IShellTransitions shellTransitions = IShellTransitions.Stub.asInterface(
bundle.getBinder(KEY_EXTRA_SHELL_SHELL_TRANSITIONS));
IStartingWindow startingWindow = IStartingWindow.Stub.asInterface(
bundle.getBinder(KEY_EXTRA_SHELL_STARTING_WINDOW));
ISysuiUnlockAnimationController launcherUnlockAnimationController =
ISysuiUnlockAnimationController.Stub.asInterface(
bundle.getBinder(KEY_EXTRA_UNLOCK_ANIMATION_CONTROLLER));
IRecentTasks recentTasks = IRecentTasks.Stub.asInterface(
bundle.getBinder(KEY_EXTRA_SHELL_RECENT_TASKS));
IBackAnimation backAnimation = IBackAnimation.Stub.asInterface(
bundle.getBinder(KEY_EXTRA_SHELL_BACK_ANIMATION));
IDesktopMode desktopMode = IDesktopMode.Stub.asInterface(
bundle.getBinder(KEY_EXTRA_SHELL_DESKTOP_MODE));
IUnfoldAnimation unfoldTransition = IUnfoldAnimation.Stub.asInterface(
bundle.getBinder(KEY_EXTRA_UNFOLD_ANIMATION_FORWARDER));
IDragAndDrop dragAndDrop = IDragAndDrop.Stub.asInterface(
bundle.getBinder(KEY_EXTRA_SHELL_DRAG_AND_DROP));
MAIN_EXECUTOR.execute(() -> executeForTouchInteractionService(tis -> {
SystemUiProxy.INSTANCE.get(tis).setProxy(proxy, pip,
bubbles, splitscreen, onehanded, shellTransitions, startingWindow,
recentTasks, launcherUnlockAnimationController, backAnimation, desktopMode,
unfoldTransition, dragAndDrop);
tis.initInputMonitor("TISBinder#onInitialize()");
tis.preloadOverview(true /* fromInit */);
}));
sIsInitialized = true;
}
@BinderThread
@Override
public void onTaskbarToggled() {
if (!FeatureFlags.ENABLE_KEYBOARD_TASKBAR_TOGGLE.get()) return;
MAIN_EXECUTOR.execute(() -> executeForTouchInteractionService(tis -> {
TaskbarActivityContext activityContext =
tis.mTaskbarManager.getCurrentActivityContext();
if (activityContext != null) {
activityContext.toggleTaskbarStash();
}
}));
}
@BinderThread
public void onOverviewToggle() {
TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "onOverviewToggle");
executeForTouchInteractionService(tis -> {
// If currently screen pinning, do not enter overview
if (tis.mDeviceState.isScreenPinningActive()) {
return;
}
TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
tis.mOverviewCommandHelper.addCommand(OverviewCommandHelper.TYPE_TOGGLE);
});
}
@BinderThread
@Override
public void onOverviewShown(boolean triggeredFromAltTab) {
executeForTouchInteractionService(tis -> {
if (triggeredFromAltTab) {
TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
tis.mOverviewCommandHelper.addCommand(
OverviewCommandHelper.TYPE_KEYBOARD_INPUT);
} else {
tis.mOverviewCommandHelper.addCommand(OverviewCommandHelper.TYPE_SHOW);
}
});
}
@BinderThread
@Override
public void onOverviewHidden(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
executeForTouchInteractionService(tis -> {
if (triggeredFromAltTab && !triggeredFromHomeKey) {
// onOverviewShownFromAltTab hides the overview and ends at the target app
tis.mOverviewCommandHelper.addCommand(OverviewCommandHelper.TYPE_HIDE);
}
});
}
@BinderThread
@Override
public void onAssistantAvailable(boolean available, boolean longPressHomeEnabled) {
MAIN_EXECUTOR.execute(() -> executeForTouchInteractionService(tis -> {
tis.mDeviceState.setAssistantAvailable(available);
tis.onAssistantVisibilityChanged();
executeForTaskbarManager(taskbarManager -> taskbarManager
.onLongPressHomeEnabled(longPressHomeEnabled));
}));
}
@BinderThread
@Override
public void onAssistantVisibilityChanged(float visibility) {
MAIN_EXECUTOR.execute(() -> executeForTouchInteractionService(tis -> {
tis.mDeviceState.setAssistantVisibility(visibility);
tis.onAssistantVisibilityChanged();
}));
}
/**
* Sent when the assistant has been invoked with the given type (defined in AssistManager)
* and should be shown. This method is used if SystemUiProxy#setAssistantOverridesRequested
* was previously called including this invocation type.
*/
@Override
public void onAssistantOverrideInvoked(int invocationType) {
executeForTouchInteractionService(tis -> {
if (!AssistUtils.newInstance(tis).tryStartAssistOverride(invocationType)) {
Log.w(TAG, "Failed to invoke Assist override");
}
});
}
@Override
public void onNavigationBarSurface(SurfaceControl surface) {
// TODO: implement
if (surface != null) {
surface.release();
surface = null;
}
}
@BinderThread
public void onSystemUiStateChanged(int stateFlags) {
MAIN_EXECUTOR.execute(() -> executeForTouchInteractionService(tis -> {
int lastFlags = tis.mDeviceState.getSystemUiStateFlags();
tis.mDeviceState.setSystemUiFlags(stateFlags);
tis.onSystemUiFlagsChanged(lastFlags);
}));
}
@BinderThread
public void onActiveNavBarRegionChanges(Region region) {
MAIN_EXECUTOR.execute(() -> executeForTouchInteractionService(
tis -> tis.mDeviceState.setDeferredGestureRegion(region)));
}
@BinderThread
@Override
public void enterStageSplitFromRunningApp(boolean leftOrTop) {
executeForTouchInteractionService(tis -> {
StatefulActivity activity =
tis.mOverviewComponentObserver.getActivityInterface().getCreatedActivity();
if (activity != null) {
activity.enterStageSplitFromRunningApp(leftOrTop);
}
});
}
/**
* Preloads the Overview activity.
* <p>
* This method should only be used when the All Set page of the SUW is reached to safely
* preload the Launcher for the SUW first reveal.
*/
public void preloadOverviewForSUWAllSet() {
executeForTouchInteractionService(tis -> tis.preloadOverview(false, true));
}
@Override
public void onRotationProposal(int rotation, boolean isValid) {
executeForTaskbarManager(taskbarManager ->
taskbarManager.onRotationProposal(rotation, isValid));
}
@Override
public void disable(int displayId, int state1, int state2, boolean animate) {
executeForTaskbarManager(taskbarManager ->
taskbarManager.disableNavBarElements(displayId, state1, state2, animate));
}
@Override
public void onSystemBarAttributesChanged(int displayId, int behavior) {
executeForTaskbarManager(taskbarManager ->
taskbarManager.onSystemBarAttributesChanged(displayId, behavior));
}
@Override
public void onNavButtonsDarkIntensityChanged(float darkIntensity) {
executeForTaskbarManager(taskbarManager ->
taskbarManager.onNavButtonsDarkIntensityChanged(darkIntensity));
}
private void executeForTouchInteractionService(
@NonNull Consumer<TouchInteractionService> tisConsumer) {
TouchInteractionService tis = mTis.get();
if (tis == null) return;
tisConsumer.accept(tis);
}
private void executeForTaskbarManager(
@NonNull Consumer<TaskbarManager> taskbarManagerConsumer) {
MAIN_EXECUTOR.execute(() -> executeForTouchInteractionService(tis -> {
TaskbarManager taskbarManager = tis.mTaskbarManager;
if (taskbarManager == null) return;
taskbarManagerConsumer.accept(taskbarManager);
}));
}
/**
* Returns the {@link TaskbarManager}.
* <p>
* Returns {@code null} if TouchInteractionService is not connected
*/
@Nullable
public TaskbarManager getTaskbarManager() {
TouchInteractionService tis = mTis.get();
if (tis == null) return null;
return tis.mTaskbarManager;
}
/**
* Returns the {@link OverviewCommandHelper}.
* <p>
* Returns {@code null} if TouchInteractionService is not connected
*/
@Nullable
public OverviewCommandHelper getOverviewCommandHelper() {
TouchInteractionService tis = mTis.get();
if (tis == null) return null;
return tis.mOverviewCommandHelper;
}
/**
* Sets a proxy to bypass swipe up behavior
*/
public void setSwipeUpProxy(Function<GestureState, AnimatedFloat> proxy) {
TouchInteractionService tis = mTis.get();
if (tis == null) return;
tis.mSwipeUpProxyProvider = proxy != null ? proxy : (i -> null);
}
/**
* Sets the task id where gestures should be blocked
*/
public void setGestureBlockedTaskId(int taskId) {
TouchInteractionService tis = mTis.get();
if (tis == null) return;
tis.mDeviceState.setGestureBlockingTaskId(taskId);
}
/** Sets a listener to be run on Overview Target updates. */
public void setOverviewTargetChangeListener(@Nullable Runnable listener) {
mOnOverviewTargetChangeListener = listener;
}
protected void onOverviewTargetChange() {
if (mOnOverviewTargetChangeListener != null) {
mOnOverviewTargetChangeListener.run();
mOnOverviewTargetChangeListener = null;
}
}
}
private static boolean sConnected = false;
private static boolean sIsInitialized = false;
private RotationTouchHelper mRotationTouchHelper;
public static boolean isConnected() {
return sConnected;
}
public static boolean isInitialized() {
return sIsInitialized;
}
private final AbsSwipeUpHandler.Factory mLauncherSwipeHandlerFactory =
this::createLauncherSwipeHandler;
private final AbsSwipeUpHandler.Factory mFallbackSwipeHandlerFactory =
this::createFallbackSwipeHandler;
private ActivityManagerWrapper mAM;
private OverviewCommandHelper mOverviewCommandHelper;
private OverviewComponentObserver mOverviewComponentObserver;
private InputConsumerController mInputConsumer;
private RecentsAnimationDeviceState mDeviceState;
private TaskAnimationManager mTaskAnimationManager;
private @NonNull InputConsumer mUncheckedConsumer = InputConsumer.NO_OP;
private @NonNull InputConsumer mConsumer = InputConsumer.NO_OP;
private Choreographer mMainChoreographer;
private @Nullable ResetGestureInputConsumer mResetGestureInputConsumer;
private GestureState mGestureState = DEFAULT_STATE;
private InputMonitorCompat mInputMonitorCompat;
private InputEventReceiver mInputEventReceiver;
private TaskbarManager mTaskbarManager;
private Function<GestureState, AnimatedFloat> mSwipeUpProxyProvider = i -> null;
@Override
public void onCreate() {
super.onCreate();
// Initialize anything here that is needed in direct boot mode.
// Everything else should be initialized in onUserUnlocked() below.
mMainChoreographer = Choreographer.getInstance();
mAM = ActivityManagerWrapper.getInstance();
mDeviceState = new RecentsAnimationDeviceState(this, true);
mTaskbarManager = new TaskbarManager(this);
mRotationTouchHelper = mDeviceState.getRotationTouchHelper();
BootAwarePreloader.start(this);
// Call runOnUserUnlocked() before any other callbacks to ensure everything is initialized.
LockedUserState.get(this).runOnUserUnlocked(this::onUserUnlocked);
LockedUserState.get(this).runOnUserUnlocked(mTaskbarManager::onUserUnlocked);
mDeviceState.addNavigationModeChangedCallback(this::onNavigationModeChanged);
sConnected = true;
}
private void disposeEventHandlers(String reason) {
Log.d(TAG, "disposeEventHandlers: Reason: " + reason);
if (mInputEventReceiver != null) {
mInputEventReceiver.dispose();
mInputEventReceiver = null;
}
if (mInputMonitorCompat != null) {
mInputMonitorCompat.dispose();
mInputMonitorCompat = null;
}
}
private void initInputMonitor(String reason) {
disposeEventHandlers("Initializing input monitor due to: " + reason);
if (mDeviceState.isButtonNavMode() && !ENABLE_TRACKPAD_GESTURE.get()) {
return;
}
mInputMonitorCompat = new InputMonitorCompat("swipe-up", mDeviceState.getDisplayId());
mInputEventReceiver = mInputMonitorCompat.getInputReceiver(Looper.getMainLooper(),
mMainChoreographer, this::onInputEvent);
mRotationTouchHelper.updateGestureTouchRegions();
}
/**
* Called when the navigation mode changes, guaranteed to be after the device state has updated.
*/
private void onNavigationModeChanged() {
initInputMonitor("onNavigationModeChanged()");
resetHomeBounceSeenOnQuickstepEnabledFirstTime();
}
@UiThread
public void onUserUnlocked() {
mTaskAnimationManager = new TaskAnimationManager(this);
mOverviewComponentObserver = new OverviewComponentObserver(this, mDeviceState);
mOverviewCommandHelper = new OverviewCommandHelper(this,
mOverviewComponentObserver, mTaskAnimationManager);
mResetGestureInputConsumer = new ResetGestureInputConsumer(
mTaskAnimationManager, mTaskbarManager::getCurrentActivityContext);
mInputConsumer = InputConsumerController.getRecentsAnimationInputConsumer();
mInputConsumer.registerInputConsumer();
onSystemUiFlagsChanged(mDeviceState.getSystemUiStateFlags());
onAssistantVisibilityChanged();
// Initialize the task tracker
TopTaskTracker.INSTANCE.get(this);
// Temporarily disable model preload
// new ModelPreload().start(this);
resetHomeBounceSeenOnQuickstepEnabledFirstTime();
mOverviewComponentObserver.setOverviewChangeListener(this::onOverviewTargetChange);
onOverviewTargetChange(mOverviewComponentObserver.isHomeAndOverviewSame());
}
public OverviewCommandHelper getOverviewCommandHelper() {
return mOverviewCommandHelper;
}
private void resetHomeBounceSeenOnQuickstepEnabledFirstTime() {
if (!LockedUserState.get(this).isUserUnlocked() || mDeviceState.isButtonNavMode()) {
// Skip if not yet unlocked (can't read user shared prefs) or if the current navigation
// mode doesn't have gestures
return;
}
// Reset home bounce seen on quick step enabled for first time
SharedPreferences sharedPrefs = LauncherPrefs.getPrefs(this);
if (!sharedPrefs.getBoolean(HAS_ENABLED_QUICKSTEP_ONCE, true)) {
sharedPrefs.edit()
.putBoolean(HAS_ENABLED_QUICKSTEP_ONCE, true)
.putBoolean(OnboardingPrefs.HOME_BOUNCE_SEEN, false)
.apply();
}
}
private void onOverviewTargetChange(boolean isHomeAndOverviewSame) {
AccessibilityManager am = getSystemService(AccessibilityManager.class);
if (isHomeAndOverviewSame) {
am.registerSystemAction(createAllAppsAction(), GLOBAL_ACTION_ACCESSIBILITY_ALL_APPS);
} else {
am.unregisterSystemAction(GLOBAL_ACTION_ACCESSIBILITY_ALL_APPS);
}
StatefulActivity newOverviewActivity = mOverviewComponentObserver.getActivityInterface()
.getCreatedActivity();
if (newOverviewActivity != null) {
mTaskbarManager.setActivity(newOverviewActivity);
}
mTISBinder.onOverviewTargetChange();
}
private RemoteAction createAllAppsAction() {
final Intent homeIntent = new Intent(mOverviewComponentObserver.getHomeIntent())
.setAction(INTENT_ACTION_ALL_APPS_TOGGLE);
final PendingIntent actionPendingIntent;
if (FeatureFlags.ENABLE_ALL_APPS_SEARCH_IN_TASKBAR.get()) {
actionPendingIntent = new PendingIntent(new IIntentSender.Stub() {
@Override
public void send(int code, Intent intent, String resolvedType,
IBinder allowlistToken, IIntentReceiver finishedReceiver,
String requiredPermission, Bundle options) {
MAIN_EXECUTOR.execute(() -> mTaskbarManager.toggleAllApps(homeIntent));
}
});
} else {
actionPendingIntent = PendingIntent.getActivity(
this,
GLOBAL_ACTION_ACCESSIBILITY_ALL_APPS,
homeIntent,
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
}
return new RemoteAction(
Icon.createWithResource(this, R.drawable.ic_apps),
getString(R.string.all_apps_label),
getString(R.string.all_apps_label),
actionPendingIntent);
}
@UiThread
private void onSystemUiFlagsChanged(int lastSysUIFlags) {
if (LockedUserState.get(this).isUserUnlocked()) {
int systemUiStateFlags = mDeviceState.getSystemUiStateFlags();
SystemUiProxy.INSTANCE.get(this).setLastSystemUiStateFlags(systemUiStateFlags);
mOverviewComponentObserver.onSystemUiStateChanged();
mTaskbarManager.onSystemUiFlagsChanged(systemUiStateFlags);
int isShadeExpandedFlag =
SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED | SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
boolean wasExpanded = (lastSysUIFlags & isShadeExpandedFlag) != 0;
boolean isExpanded = (systemUiStateFlags & isShadeExpandedFlag) != 0;
if (wasExpanded != isExpanded && isExpanded) {
// End live tile when expanding the notification panel for the first time from
// overview.
mTaskAnimationManager.endLiveTile();
}
}
}
@UiThread
private void onAssistantVisibilityChanged() {
if (LockedUserState.get(this).isUserUnlocked()) {
mOverviewComponentObserver.getActivityInterface().onAssistantVisibilityChanged(
mDeviceState.getAssistantVisibility());
}
}
@Override
public void onDestroy() {
Log.d(TAG, "Touch service destroyed: user=" + getUserId());
sIsInitialized = false;
if (LockedUserState.get(this).isUserUnlocked()) {
mInputConsumer.unregisterInputConsumer();
mOverviewComponentObserver.onDestroy();
}
disposeEventHandlers("TouchInteractionService onDestroy()");
mDeviceState.destroy();
SystemUiProxy.INSTANCE.get(this).clearProxy();
getSystemService(AccessibilityManager.class)
.unregisterSystemAction(GLOBAL_ACTION_ACCESSIBILITY_ALL_APPS);
mTaskbarManager.destroy();
sConnected = false;
super.onDestroy();
}
@Override
public IBinder onBind(Intent intent) {
Log.d(TAG, "Touch service connected: user=" + getUserId());
return mTISBinder;
}
private void onInputEvent(InputEvent ev) {
if (!(ev instanceof MotionEvent)) {
Log.e(TAG, "Unknown event " + ev);
return;
}
MotionEvent event = (MotionEvent) ev;
TestLogging.recordMotionEvent(
TestProtocol.SEQUENCE_TIS, "TouchInteractionService.onInputEvent", event);
if (!LockedUserState.get(this).isUserUnlocked() || (mDeviceState.isButtonNavMode()
&& !isTrackpadMotionEvent(event))) {
return;
}
SafeCloseable traceToken = TraceHelper.INSTANCE.allowIpcs("TIS.onInputEvent");
final int action = event.getActionMasked();
// Note this will create a new consumer every mouse click, as after ACTION_UP from the click
// an ACTION_HOVER_ENTER will fire as well.
boolean isHoverActionWithoutConsumer =
event.isHoverEvent() && (mUncheckedConsumer.getType() & TYPE_CURSOR_HOVER) == 0;
if (action == ACTION_DOWN || isHoverActionWithoutConsumer) {
mRotationTouchHelper.setOrientationTransformIfNeeded(event);
if ((!mDeviceState.isOneHandedModeActive()
&& mRotationTouchHelper.isInSwipeUpTouchRegion(event))
|| isHoverActionWithoutConsumer) {
// Clone the previous gesture state since onConsumerAboutToBeSwitched might trigger
// onConsumerInactive and wipe the previous gesture state
GestureState prevGestureState = new GestureState(mGestureState);
GestureState newGestureState = createGestureState(mGestureState,
getTrackpadGestureType(event));
newGestureState.setSwipeUpStartTimeMs(SystemClock.uptimeMillis());
mConsumer.onConsumerAboutToBeSwitched();
mGestureState = newGestureState;
mConsumer = newConsumer(prevGestureState, mGestureState, event);
mUncheckedConsumer = mConsumer;
} else if (LockedUserState.get(this).isUserUnlocked()
&& (mDeviceState.isFullyGesturalNavMode() || isTrackpadMultiFingerSwipe(event))
&& mDeviceState.canTriggerAssistantAction(event)) {
mGestureState = createGestureState(mGestureState,
getTrackpadGestureType(event));
// Do not change mConsumer as if there is an ongoing QuickSwitch gesture, we
// should not interrupt it. QuickSwitch assumes that interruption can only
// happen if the next gesture is also quick switch.
mUncheckedConsumer = tryCreateAssistantInputConsumer(mGestureState, event);
} else if (mDeviceState.canTriggerOneHandedAction(event)) {
// Consume gesture event for triggering one handed feature.
mUncheckedConsumer = new OneHandedModeInputConsumer(this, mDeviceState,
InputConsumer.NO_OP, mInputMonitorCompat);
} else {
mUncheckedConsumer = InputConsumer.NO_OP;
}
} else {
// Other events
if (mUncheckedConsumer != InputConsumer.NO_OP) {
// Only transform the event if we are handling it in a proper consumer
mRotationTouchHelper.setOrientationTransformIfNeeded(event);
}
}
if (mUncheckedConsumer != InputConsumer.NO_OP) {
switch (event.getActionMasked()) {
case ACTION_DOWN:
// fall through
case ACTION_UP:
ActiveGestureLog.INSTANCE.addLog(
/* event= */ "onMotionEvent(" + (int) event.getRawX() + ", "
+ (int) event.getRawY() + "): "
+ MotionEvent.actionToString(event.getActionMasked()) + ", "
+ MotionEvent.classificationToString(event.getClassification()),
/* gestureEvent= */ event.getActionMasked() == ACTION_DOWN
? MOTION_DOWN
: MOTION_UP);
break;
case ACTION_MOVE:
ActiveGestureLog.INSTANCE.addLog("onMotionEvent: "
+ MotionEvent.actionToString(event.getActionMasked()) + ","
+ MotionEvent.classificationToString(event.getClassification())
+ ", pointerCount: " + event.getPointerCount(), MOTION_MOVE);
break;
default: {
ActiveGestureLog.INSTANCE.addLog("onMotionEvent: "
+ MotionEvent.actionToString(event.getActionMasked()) + ","
+ MotionEvent.classificationToString(event.getClassification()));
}
}
}
boolean cancelGesture = mGestureState.getActivityInterface() != null
&& mGestureState.getActivityInterface().shouldCancelCurrentGesture();
boolean cleanUpConsumer = (action == ACTION_UP || action == ACTION_CANCEL || cancelGesture)
&& mConsumer != null
&& !mConsumer.getActiveConsumerInHierarchy().isConsumerDetachedFromGesture();
if (cancelGesture) {
event.setAction(ACTION_CANCEL);
}
if (mGestureState.isTrackpadGesture() && (action == ACTION_POINTER_DOWN
|| action == ACTION_POINTER_UP)) {
// Skip ACTION_POINTER_DOWN and ACTION_POINTER_UP events from trackpad.
} else if (isCursorHoverEvent(event)) {
mUncheckedConsumer.onHoverEvent(event);
} else {
mUncheckedConsumer.onMotionEvent(event);
}
if (cleanUpConsumer) {
reset();
}
traceToken.close();
}
// Talkback generates hover events on touch, which we do not want to consume.
private boolean isCursorHoverEvent(MotionEvent event) {
return event.isHoverEvent() && event.getSource() == InputDevice.SOURCE_MOUSE;
}
private InputConsumer tryCreateAssistantInputConsumer(
GestureState gestureState, MotionEvent motionEvent) {
return tryCreateAssistantInputConsumer(
InputConsumer.NO_OP, gestureState, motionEvent, CompoundString.NO_OP);
}
private InputConsumer tryCreateAssistantInputConsumer(
InputConsumer base,
GestureState gestureState,
MotionEvent motionEvent,
CompoundString reasonString) {
if (mDeviceState.isGestureBlockedTask(gestureState.getRunningTask())) {
reasonString.append(SUBSTRING_PREFIX)
.append("is gesture-blocked task, using base input consumer");
return base;
} else {
reasonString.append(SUBSTRING_PREFIX).append("using AssistantInputConsumer");
return new AssistantInputConsumer(
this, gestureState, base, mInputMonitorCompat, mDeviceState, motionEvent);
}
}
public GestureState createGestureState(GestureState previousGestureState,
GestureState.TrackpadGestureType trackpadGestureType) {
final GestureState gestureState;
TopTaskTracker.CachedTaskInfo taskInfo;
if (mTaskAnimationManager.isRecentsAnimationRunning()) {
gestureState = new GestureState(mOverviewComponentObserver,
ActiveGestureLog.INSTANCE.getLogId());
taskInfo = previousGestureState.getRunningTask();
gestureState.updateRunningTask(taskInfo);
gestureState.updateLastStartedTaskIds(previousGestureState.getLastStartedTaskIds());
gestureState.updatePreviouslyAppearedTaskIds(
previousGestureState.getPreviouslyAppearedTaskIds());
} else {
gestureState = new GestureState(mOverviewComponentObserver,
ActiveGestureLog.INSTANCE.incrementLogId());
taskInfo = TopTaskTracker.INSTANCE.get(this).getCachedTopTask(false);
gestureState.updateRunningTask(taskInfo);
}
gestureState.setTrackpadGestureType(trackpadGestureType);
// Log initial state for the gesture.
ActiveGestureLog.INSTANCE.addLog(new CompoundString("Current running task package name=")
.append(taskInfo == null ? "no running task" : taskInfo.getPackageName()));
ActiveGestureLog.INSTANCE.addLog(new CompoundString("Current SystemUi state flags=")
.append(mDeviceState.getSystemUiStateString()));
return gestureState;
}
private InputConsumer newConsumer(
GestureState previousGestureState, GestureState newGestureState, MotionEvent event) {
AnimatedFloat progressProxy = mSwipeUpProxyProvider.apply(mGestureState);
if (progressProxy != null) {
InputConsumer consumer = new ProgressDelegateInputConsumer(
this, mTaskAnimationManager, mGestureState, mInputMonitorCompat, progressProxy);
logInputConsumerSelectionReason(consumer, newCompoundString(
"mSwipeUpProxyProvider has been set, using ProgressDelegateInputConsumer"));
return consumer;
}
boolean canStartSystemGesture =
mGestureState.isTrackpadGesture() ? mDeviceState.canStartTrackpadGesture()
: mDeviceState.canStartSystemGesture();
if (!LockedUserState.get(this).isUserUnlocked()) {
CompoundString reasonString = newCompoundString("device locked");
InputConsumer consumer;
if (canStartSystemGesture) {
// This handles apps launched in direct boot mode (e.g. dialer) as well as apps
// launched while device is locked even after exiting direct boot mode (e.g. camera).
consumer = createDeviceLockedInputConsumer(
newGestureState, reasonString.append(SUBSTRING_PREFIX)
.append("can start system gesture"));
} else {
consumer = getDefaultInputConsumer(
reasonString.append(SUBSTRING_PREFIX)
.append("cannot start system gesture"));
}
logInputConsumerSelectionReason(consumer, reasonString);
return consumer;
}
CompoundString reasonString;
InputConsumer base;
// When there is an existing recents animation running, bypass systemState check as this is
// a followup gesture and the first gesture started in a valid system state.
if (canStartSystemGesture || previousGestureState.isRecentsAnimationRunning()) {
reasonString = newCompoundString(canStartSystemGesture
? "can start system gesture" : "recents animation was running")
.append(", trying to use base consumer");
base = newBaseConsumer(previousGestureState, newGestureState, event, reasonString);
} else {
reasonString = newCompoundString(
"cannot start system gesture and recents animation was not running")
.append(", trying to use default input consumer");
base = getDefaultInputConsumer(reasonString);
}
if (mDeviceState.isGesturalNavMode() || newGestureState.isTrackpadGesture()) {
handleOrientationSetup(base);
}
if (mDeviceState.isFullyGesturalNavMode() || newGestureState.isTrackpadGesture()) {
String reasonPrefix = "device is in gesture navigation mode or 3-button mode with a"
+ " trackpad gesture";
if (mDeviceState.canTriggerAssistantAction(event)) {
reasonString.append(NEWLINE_PREFIX)
.append(reasonPrefix)
.append(SUBSTRING_PREFIX)
.append("gesture can trigger the assistant")
.append(", trying to use assistant input consumer");
base = tryCreateAssistantInputConsumer(base, newGestureState, event, reasonString);
}
// If Taskbar is present, we listen for swipe or cursor hover events to unstash it.
TaskbarActivityContext tac = mTaskbarManager.getCurrentActivityContext();
if (tac != null && !(base instanceof AssistantInputConsumer)) {
// Present always on large screen or on small screen w/ flag
DeviceProfile dp = tac.getDeviceProfile();
boolean useTaskbarConsumer = dp.isTaskbarPresent && !TaskbarManager.isPhoneMode(dp)
&& !tac.isInStashedLauncherState();
if (canStartSystemGesture && useTaskbarConsumer) {
reasonString.append(NEWLINE_PREFIX)
.append(reasonPrefix)
.append(SUBSTRING_PREFIX)
.append("TaskbarActivityContext != null, "
+ "using TaskbarUnstashInputConsumer");
base = new TaskbarUnstashInputConsumer(this, base, mInputMonitorCompat, tac,
mOverviewCommandHelper);
}
} else if (canStartSystemGesture && FeatureFlags.ENABLE_LONG_PRESS_NAV_HANDLE.get()
&& !previousGestureState.isRecentsAnimationRunning()) {
reasonString.append(NEWLINE_PREFIX)
.append(reasonPrefix)
.append(SUBSTRING_PREFIX)
.append("Long press nav handle enabled, "
+ "using NavHandleLongPressInputConsumer");
base = new NavHandleLongPressInputConsumer(this, base, mInputMonitorCompat);
}
if (mDeviceState.isBubblesExpanded()) {
reasonString = newCompoundString(reasonPrefix)
.append(SUBSTRING_PREFIX)
.append("bubbles expanded, trying to use default input consumer");
// Bubbles can handle home gesture itself.
base = getDefaultInputConsumer(reasonString);
}
if (mDeviceState.isSystemUiDialogShowing()) {
reasonString = newCompoundString(reasonPrefix)
.append(SUBSTRING_PREFIX)
.append("system dialog is showing, using SysUiOverlayInputConsumer");
base = new SysUiOverlayInputConsumer(
getBaseContext(), mDeviceState, mInputMonitorCompat);
}
if (ENABLE_TRACKPAD_GESTURE.get() && mGestureState.isTrackpadGesture()
&& canStartSystemGesture && !previousGestureState.isRecentsAnimationRunning()) {
reasonString = newCompoundString(reasonPrefix)
.append(SUBSTRING_PREFIX)
.append("Trackpad 3-finger gesture, using TrackpadStatusBarInputConsumer");
base = new TrackpadStatusBarInputConsumer(getBaseContext(), base,
mInputMonitorCompat);
}
if (mDeviceState.isScreenPinningActive()) {
reasonString = newCompoundString(reasonPrefix)
.append(SUBSTRING_PREFIX)
.append("screen pinning is active, using ScreenPinnedInputConsumer");
// Note: we only allow accessibility to wrap this, and it replaces the previous
// base input consumer (which should be NO_OP anyway since topTaskLocked == true).
base = new ScreenPinnedInputConsumer(this, newGestureState);
}
if (mDeviceState.canTriggerOneHandedAction(event)) {
reasonString.append(NEWLINE_PREFIX)
.append(reasonPrefix)
.append(SUBSTRING_PREFIX)
.append("gesture can trigger one handed mode")
.append(", using OneHandedModeInputConsumer");
base = new OneHandedModeInputConsumer(
this, mDeviceState, base, mInputMonitorCompat);
}
if (mDeviceState.isAccessibilityMenuAvailable()) {
reasonString.append(NEWLINE_PREFIX)
.append(reasonPrefix)
.append(SUBSTRING_PREFIX)
.append("accessibility menu is available")
.append(", using AccessibilityInputConsumer");
base = new AccessibilityInputConsumer(
this, mDeviceState, mGestureState, base, mInputMonitorCompat);
}
} else {
String reasonPrefix = "device is not in gesture navigation mode";
if (mDeviceState.isScreenPinningActive()) {
reasonString = newCompoundString(reasonPrefix)
.append(SUBSTRING_PREFIX)
.append("screen pinning is active, trying to use default input consumer");
base = getDefaultInputConsumer(reasonString);
}
if (mDeviceState.canTriggerOneHandedAction(event)) {
reasonString.append(NEWLINE_PREFIX)
.append(reasonPrefix)
.append(SUBSTRING_PREFIX)
.append("gesture can trigger one handed mode")
.append(", using OneHandedModeInputConsumer");
base = new OneHandedModeInputConsumer(
this, mDeviceState, base, mInputMonitorCompat);
}
}
logInputConsumerSelectionReason(base, reasonString);
return base;
}
private CompoundString newCompoundString(String substring) {
return new CompoundString(NEWLINE_PREFIX).append(substring);
}
private void logInputConsumerSelectionReason(
InputConsumer consumer, CompoundString reasonString) {
ActiveGestureLog.INSTANCE.addLog(new CompoundString("setInputConsumer: ")
.append(consumer.getName())
.append(". reason(s):")
.append(reasonString));
if ((consumer.getType() & InputConsumer.TYPE_OTHER_ACTIVITY) != 0) {
ActiveGestureLog.INSTANCE.trackEvent(FLAG_USING_OTHER_ACTIVITY_INPUT_CONSUMER);
}
}
private void handleOrientationSetup(InputConsumer baseInputConsumer) {
baseInputConsumer.notifyOrientationSetup();
}
private InputConsumer newBaseConsumer(
GestureState previousGestureState,
GestureState gestureState,
MotionEvent event,
CompoundString reasonString) {
if (mDeviceState.isKeyguardShowingOccluded()) {
// This handles apps showing over the lockscreen (e.g. camera)
return createDeviceLockedInputConsumer(
gestureState,
reasonString.append(SUBSTRING_PREFIX)
.append("keyguard is showing occluded")
.append(", trying to use device locked input consumer"));
}
reasonString.append(SUBSTRING_PREFIX).append("keyguard is not showing occluded");
// Use overview input consumer for sharesheets on top of home.
boolean forceOverviewInputConsumer = gestureState.getActivityInterface().isStarted()
&& gestureState.getRunningTask() != null
&& gestureState.getRunningTask().isRootChooseActivity();
// In the case where we are in an excluded, translucent overlay, ignore it and treat the
// running activity as the task behind the overlay.
TopTaskTracker.CachedTaskInfo otherVisibleTask = gestureState.getRunningTask() == null
? null
: gestureState.getRunningTask().otherVisibleTaskThisIsExcludedOver();
if (otherVisibleTask != null) {
ActiveGestureLog.INSTANCE.addLog(new CompoundString("Changing active task to ")
.append(otherVisibleTask.getPackageName())
.append(" because the previous task running on top of this one (")
.append(gestureState.getRunningTask().getPackageName())
.append(") was excluded from recents"));
gestureState.updateRunningTask(otherVisibleTask);
}
boolean previousGestureAnimatedToLauncher =
previousGestureState.isRunningAnimationToLauncher();
// with shell-transitions, home is resumed during recents animation, so
// explicitly check against recents animation too.
boolean launcherResumedThroughShellTransition =
gestureState.getActivityInterface().isResumed()
&& !previousGestureState.isRecentsAnimationRunning();
if (gestureState.getActivityInterface().isInLiveTileMode()) {
return createOverviewInputConsumer(
previousGestureState,
gestureState,
event,
forceOverviewInputConsumer,
reasonString.append(SUBSTRING_PREFIX)
.append("is in live tile mode, trying to use overview input consumer"));
} else if (gestureState.getRunningTask() == null) {
return getDefaultInputConsumer(reasonString.append(SUBSTRING_PREFIX)
.append("running task == null"));
} else if (previousGestureAnimatedToLauncher
|| launcherResumedThroughShellTransition
|| forceOverviewInputConsumer) {
return createOverviewInputConsumer(
previousGestureState,
gestureState,
event,
forceOverviewInputConsumer,
reasonString.append(SUBSTRING_PREFIX)
.append(previousGestureAnimatedToLauncher
? "previous gesture animated to launcher"
: (launcherResumedThroughShellTransition
? "launcher resumed through a shell transition"
: "forceOverviewInputConsumer == true"))
.append(", trying to use overview input consumer"));
} else if (mDeviceState.isGestureBlockedTask(gestureState.getRunningTask())) {
return getDefaultInputConsumer(reasonString.append(SUBSTRING_PREFIX)
.append("is gesture-blocked task, trying to use default input consumer"));
} else {
reasonString.append(SUBSTRING_PREFIX)
.append("using OtherActivityInputConsumer");
return createOtherActivityInputConsumer(gestureState, event);
}
}
public AbsSwipeUpHandler.Factory getSwipeUpHandlerFactory() {
return !mOverviewComponentObserver.isHomeAndOverviewSame()
? mFallbackSwipeHandlerFactory : mLauncherSwipeHandlerFactory;
}
private InputConsumer createOtherActivityInputConsumer(GestureState gestureState,
MotionEvent event) {
final AbsSwipeUpHandler.Factory factory = getSwipeUpHandlerFactory();
final boolean shouldDefer = !mOverviewComponentObserver.isHomeAndOverviewSame()
|| gestureState.getActivityInterface().deferStartingActivity(mDeviceState, event);
final boolean disableHorizontalSwipe = mDeviceState.isInExclusionRegion(event);
return new OtherActivityInputConsumer(this, mDeviceState, mTaskAnimationManager,
gestureState, shouldDefer, this::onConsumerInactive,
mInputMonitorCompat, mInputEventReceiver, disableHorizontalSwipe, factory);
}
private InputConsumer createDeviceLockedInputConsumer(
GestureState gestureState, CompoundString reasonString) {
if ((mDeviceState.isFullyGesturalNavMode() || gestureState.isTrackpadGesture())
&& gestureState.getRunningTask() != null) {
reasonString.append(SUBSTRING_PREFIX)
.append("device is in gesture nav mode or 3-button mode with a trackpad gesture"
+ "and running task != null")
.append(", using DeviceLockedInputConsumer");
return new DeviceLockedInputConsumer(
this, mDeviceState, mTaskAnimationManager, gestureState, mInputMonitorCompat);
} else {
return getDefaultInputConsumer(reasonString
.append(SUBSTRING_PREFIX)
.append((mDeviceState.isFullyGesturalNavMode()
|| gestureState.isTrackpadGesture())
? "running task == null"
: "device is not in gesture nav mode and it's not a trackpad gesture")
.append(", trying to use default input consumer"));
}
}
public InputConsumer createOverviewInputConsumer(
GestureState previousGestureState,
GestureState gestureState,
MotionEvent event,
boolean forceOverviewInputConsumer,
CompoundString reasonString) {
StatefulActivity activity = gestureState.getActivityInterface().getCreatedActivity();
if (activity == null) {
return getDefaultInputConsumer(
reasonString.append(SUBSTRING_PREFIX)
.append("activity == null, trying to use default input consumer"));
}
boolean hasWindowFocus = activity.getRootView().hasWindowFocus();
boolean isPreviousGestureAnimatingToLauncher =
previousGestureState.isRunningAnimationToLauncher();
boolean isInLiveTileMode = gestureState.getActivityInterface().isInLiveTileMode();
reasonString.append(SUBSTRING_PREFIX)
.append(hasWindowFocus
? "activity has window focus"
: (isPreviousGestureAnimatingToLauncher
? "previous gesture is still animating to launcher"
: isInLiveTileMode
? "device is in live mode"
: "all overview focus conditions failed"));
if (hasWindowFocus
|| isPreviousGestureAnimatingToLauncher
|| isInLiveTileMode) {
reasonString.append(SUBSTRING_PREFIX)
.append("overview should have focus, using OverviewInputConsumer");
return new OverviewInputConsumer(gestureState, activity, mInputMonitorCompat,
false /* startingInActivityBounds */);
} else {
reasonString.append(SUBSTRING_PREFIX).append(
"overview shouldn't have focus, using OverviewWithoutFocusInputConsumer");
final boolean disableHorizontalSwipe = mDeviceState.isInExclusionRegion(event);
return new OverviewWithoutFocusInputConsumer(activity, mDeviceState, gestureState,
mInputMonitorCompat, disableHorizontalSwipe);
}
}
/**
* To be called by the consumer when it's no longer active. This can be called by any consumer
* in the hierarchy at any point during the gesture (ie. if a delegate consumer starts
* intercepting touches, the base consumer can try to call this).
*/
private void onConsumerInactive(InputConsumer caller) {
if (mConsumer != null && mConsumer.getActiveConsumerInHierarchy() == caller) {
reset();
}
}
private void reset() {
mConsumer = mUncheckedConsumer = getDefaultInputConsumer();
mGestureState = DEFAULT_STATE;
// By default, use batching of the input events, but check receiver before using in the rare
// case that the monitor was disposed before the swipe settled
if (mInputEventReceiver != null) {
mInputEventReceiver.setBatchingEnabled(true);
}
}
private @NonNull InputConsumer getDefaultInputConsumer() {
return getDefaultInputConsumer(CompoundString.NO_OP);
}
/**
* Returns the {@link ResetGestureInputConsumer} if user is unlocked, else NO_OP.
*/
private @NonNull InputConsumer getDefaultInputConsumer(@NonNull CompoundString reasonString) {
if (mResetGestureInputConsumer != null) {
reasonString.append(SUBSTRING_PREFIX).append(
"mResetGestureInputConsumer initialized, using ResetGestureInputConsumer");
return mResetGestureInputConsumer;
} else {
reasonString.append(SUBSTRING_PREFIX).append(
"mResetGestureInputConsumer not initialized, using no-op input consumer");
// mResetGestureInputConsumer isn't initialized until onUserUnlocked(), so reset to
// NO_OP until then (we never want these to be null).
return InputConsumer.NO_OP;
}
}
private void preloadOverview(boolean fromInit) {
Trace.beginSection("preloadOverview(fromInit=" + fromInit + ")");
preloadOverview(fromInit, false);
Trace.endSection();
}
private void preloadOverview(boolean fromInit, boolean forSUWAllSet) {
if (!LockedUserState.get(this).isUserUnlocked()) {
return;
}
if (mDeviceState.isButtonNavMode() && !mOverviewComponentObserver.isHomeAndOverviewSame()) {
// Prevent the overview from being started before the real home on first boot.
return;
}
if ((RestoreDbTask.isPending(this) && !forSUWAllSet)
|| !mDeviceState.isUserSetupComplete()) {
// Preloading while a restore is pending may cause launcher to start the restore
// too early.
return;
}
final BaseActivityInterface activityInterface =
mOverviewComponentObserver.getActivityInterface();
final Intent overviewIntent = new Intent(
mOverviewComponentObserver.getOverviewIntentIgnoreSysUiState());
if (activityInterface.getCreatedActivity() != null && fromInit) {
// The activity has been created before the initialization of overview service. It is
// usually happens when booting or launcher is the top activity, so we should already
// have the latest state.
return;
}
// TODO(b/258022658): Remove temporary logging.
Log.i(TAG, "preloadOverview: forSUWAllSet=" + forSUWAllSet
+ ", isHomeAndOverviewSame=" + mOverviewComponentObserver.isHomeAndOverviewSame());
mTaskAnimationManager.preloadRecentsAnimation(overviewIntent);
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
if (!LockedUserState.get(this).isUserUnlocked()) {
return;
}
final BaseActivityInterface activityInterface =
mOverviewComponentObserver.getActivityInterface();
final BaseDraggingActivity activity = activityInterface.getCreatedActivity();
if (activity == null || activity.isStarted()) {
// We only care about the existing background activity.
return;
}
Configuration oldConfig = activity.getResources().getConfiguration();
boolean isFoldUnfold = isTablet(oldConfig) != isTablet(newConfig);
if (!isFoldUnfold && mOverviewComponentObserver.canHandleConfigChanges(
activity.getComponentName(),
activity.getResources().getConfiguration().diff(newConfig))) {
// Since navBar gestural height are different between portrait and landscape,
// can handle orientation changes and refresh navigation gestural region through
// onOneHandedModeChanged()
int newGesturalHeight = ResourceUtils.getNavbarSize(
ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE,
getApplicationContext().getResources());
mDeviceState.onOneHandedModeChanged(newGesturalHeight);
return;
}
preloadOverview(false /* fromInit */);
}
private static boolean isTablet(Configuration config) {
return config.smallestScreenWidthDp >= MIN_TABLET_WIDTH;
}
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] rawArgs) {
// Dump everything
FlagsFactory.dump(pw);
if (LockedUserState.get(this).isUserUnlocked()) {
PluginManagerWrapper.INSTANCE.get(getBaseContext()).dump(pw);
}
mDeviceState.dump(pw);
if (mOverviewComponentObserver != null) {
mOverviewComponentObserver.dump(pw);
}
if (mOverviewCommandHelper != null) {
mOverviewCommandHelper.dump(pw);
}
if (mGestureState != null) {
mGestureState.dump(pw);
}
pw.println("Input state:");
pw.println(" mInputMonitorCompat=" + mInputMonitorCompat);
pw.println(" mInputEventReceiver=" + mInputEventReceiver);
DisplayController.INSTANCE.get(this).dump(pw);
pw.println("TouchState:");
BaseDraggingActivity createdOverviewActivity = mOverviewComponentObserver == null ? null
: mOverviewComponentObserver.getActivityInterface().getCreatedActivity();
boolean resumed = mOverviewComponentObserver != null
&& mOverviewComponentObserver.getActivityInterface().isResumed();
pw.println(" createdOverviewActivity=" + createdOverviewActivity);
pw.println(" resumed=" + resumed);
pw.println(" mConsumer=" + mConsumer.getName());
ActiveGestureLog.INSTANCE.dump("", pw);
RecentsModel.INSTANCE.get(this).dump("", pw);
if (createdOverviewActivity != null) {
createdOverviewActivity.getDeviceProfile().dump(this, "", pw);
}
mTaskbarManager.dumpLogs("", pw);
pw.println("AssistStateManager:");
AssistStateManager.INSTANCE.get(this).dump(" ", pw);
}
private AbsSwipeUpHandler createLauncherSwipeHandler(
GestureState gestureState, long touchTimeMs) {
return new LauncherSwipeHandlerV2(this, mDeviceState, mTaskAnimationManager,
gestureState, touchTimeMs, mTaskAnimationManager.isRecentsAnimationRunning(),
mInputConsumer);
}
private AbsSwipeUpHandler createFallbackSwipeHandler(
GestureState gestureState, long touchTimeMs) {
return new FallbackSwipeHandler(this, mDeviceState, mTaskAnimationManager,
gestureState, touchTimeMs, mTaskAnimationManager.isRecentsAnimationRunning(),
mInputConsumer);
}
}