diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 489a79f5f5e73..b063140d4efd9 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -1051,6 +1051,39 @@ --> 0 + + 0 + + + 0 + + + 0 + + + 0 + + 3500 diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index d96b1de32c82e..846ec602385f5 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -459,6 +459,10 @@ + + + + diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 193b27d4e37b3..1890a1f7573a1 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -93,8 +93,10 @@ import static com.android.server.wm.WindowManagerPolicyProto.ROTATION_MODE; import static com.android.server.wm.WindowManagerPolicyProto.SCREEN_ON_FULLY; import static com.android.server.wm.WindowManagerPolicyProto.WINDOW_MANAGER_DRAW_COMPLETE; +import android.accessibilityservice.AccessibilityServiceInfo; import android.annotation.Nullable; import android.app.ActivityManager; +import android.app.ActivityManager.RecentTaskInfo; import android.app.ActivityManagerInternal; import android.app.ActivityTaskManager; import android.app.AppOpsManager; @@ -105,6 +107,7 @@ import android.app.SearchManager; import android.app.UiModeManager; import android.content.ActivityNotFoundException; import android.content.BroadcastReceiver; +import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; @@ -113,6 +116,7 @@ import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; import android.content.res.Resources; @@ -191,6 +195,7 @@ import android.view.autofill.AutofillManagerInternal; import com.android.internal.R; import com.android.internal.accessibility.AccessibilityShortcutController; +import com.android.internal.accessibility.util.AccessibilityUtils; import com.android.internal.app.AssistUtils; import com.android.internal.inputmethod.SoftInputShowHideReason; import com.android.internal.logging.MetricsLogger; @@ -230,6 +235,7 @@ import java.io.IOException; import java.io.PrintWriter; import java.util.HashSet; import java.util.List; +import java.util.Set; /** * WindowManagerPolicy implementation for the Android phone UI. This @@ -308,6 +314,22 @@ public class PhoneWindowManager implements WindowManagerPolicy { static final int PENDING_KEY_NULL = -1; + // Must match: config_shortPressOnStemPrimaryBehavior in config.xml + static final int SHORT_PRESS_PRIMARY_NOTHING = 0; + static final int SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS = 1; + + // Must match: config_longPressOnStemPrimaryBehavior in config.xml + static final int LONG_PRESS_PRIMARY_NOTHING = 0; + static final int LONG_PRESS_PRIMARY_LAUNCH_VOICE_ASSISTANT = 1; + + // Must match: config_doublePressOnStemPrimaryBehavior in config.xml + static final int DOUBLE_PRESS_PRIMARY_NOTHING = 0; + static final int DOUBLE_PRESS_PRIMARY_SWITCH_RECENT_APP = 1; + + // Must match: config_triplePressOnStemPrimaryBehavior in config.xml + static final int TRIPLE_PRESS_PRIMARY_NOTHING = 0; + static final int TRIPLE_PRESS_PRIMARY_TOGGLE_ACCESSIBILITY = 1; + static public final String SYSTEM_DIALOG_REASON_KEY = "reason"; static public final String SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS = "globalactions"; static public final String SYSTEM_DIALOG_REASON_RECENT_APPS = "recentapps"; @@ -315,6 +337,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { static public final String SYSTEM_DIALOG_REASON_ASSIST = "assist"; static public final String SYSTEM_DIALOG_REASON_SCREENSHOT = "screenshot"; + private static final String TALKBACK_LABEL = "TalkBack"; + private static final int POWER_BUTTON_SUPPRESSION_DELAY_DEFAULT_MILLIS = 800; private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder() .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) @@ -487,6 +511,10 @@ public class PhoneWindowManager implements WindowManagerPolicy { int mShortPressOnSleepBehavior; int mShortPressOnWindowBehavior; int mPowerVolUpBehavior; + int mShortPressOnStemPrimaryBehavior; + int mDoublePressOnStemPrimaryBehavior; + int mTriplePressOnStemPrimaryBehavior; + int mLongPressOnStemPrimaryBehavior; boolean mHasSoftInput = false; boolean mHapticTextHandleEnabled; boolean mUseTvRouting; @@ -1192,6 +1220,170 @@ public class PhoneWindowManager implements WindowManagerPolicy { return mLongPressOnPowerBehavior; } + private void stemPrimaryPress(int count) { + if (DEBUG_INPUT) { + Slog.d(TAG, "stemPrimaryPress: " + count); + } + if (count == 3) { + stemPrimaryTriplePressAction(mTriplePressOnStemPrimaryBehavior); + } else if (count == 2) { + stemPrimaryDoublePressAction(mDoublePressOnStemPrimaryBehavior); + } else if (count == 1) { + stemPrimarySinglePressAction(mShortPressOnStemPrimaryBehavior); + } + } + + private void stemPrimarySinglePressAction(int behavior) { + switch (behavior) { + case SHORT_PRESS_PRIMARY_NOTHING: + break; + case SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS: + if (DEBUG_INPUT) { + Slog.d(TAG, "Executing stem primary short press action behavior."); + } + final boolean keyguardActive = + mKeyguardDelegate != null && mKeyguardDelegate.isShowing(); + if (!keyguardActive) { + Intent intent = new Intent(Intent.ACTION_ALL_APPS); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); + startActivityAsUser(intent, UserHandle.CURRENT_OR_SELF); + } + break; + } + } + + private void stemPrimaryDoublePressAction(int behavior) { + switch (behavior) { + case DOUBLE_PRESS_PRIMARY_NOTHING: + break; + case DOUBLE_PRESS_PRIMARY_SWITCH_RECENT_APP: + if (DEBUG_INPUT) { + Slog.d(TAG, "Executing stem primary double press action behavior."); + } + final boolean keyguardActive = mKeyguardDelegate == null + ? false + : mKeyguardDelegate.isShowing(); + if (!keyguardActive) { + switchRecentTask(); + } + break; + } + } + + private void stemPrimaryTriplePressAction(int behavior) { + switch (behavior) { + case TRIPLE_PRESS_PRIMARY_NOTHING: + break; + case TRIPLE_PRESS_PRIMARY_TOGGLE_ACCESSIBILITY: + if (DEBUG_INPUT) { + Slog.d(TAG, "Executing stem primary triple press action behavior."); + } + toggleTalkBack(); + break; + } + } + + private void stemPrimaryLongPress() { + if (DEBUG_INPUT) { + Slog.d(TAG, "Executing stem primary long press action behavior."); + } + + switch (mLongPressOnStemPrimaryBehavior) { + case LONG_PRESS_PRIMARY_NOTHING: + break; + case LONG_PRESS_PRIMARY_LAUNCH_VOICE_ASSISTANT: + launchVoiceAssist(/* allowDuringSetup= */false); + break; + } + } + + private void toggleTalkBack() { + final ComponentName componentName = getTalkbackComponent(); + if (componentName == null) { + return; + } + + final Set enabledServices = + AccessibilityUtils.getEnabledServicesFromSettings(mContext, mCurrentUserId); + + AccessibilityUtils.setAccessibilityServiceState(mContext, componentName, + !enabledServices.contains(componentName)); + } + + private ComponentName getTalkbackComponent() { + AccessibilityManager accessibilityManager = mContext.getSystemService( + AccessibilityManager.class); + List serviceInfos = + accessibilityManager.getInstalledAccessibilityServiceList(); + + for (AccessibilityServiceInfo service : serviceInfos) { + final ServiceInfo serviceInfo = service.getResolveInfo().serviceInfo; + if (isTalkback(serviceInfo)) { + return new ComponentName(serviceInfo.packageName, serviceInfo.name); + } + } + return null; + } + + private boolean isTalkback(ServiceInfo info) { + String label = info.loadLabel(mPackageManager).toString(); + return label.equals(TALKBACK_LABEL); + } + + /** + * Load most recent task (expect current task) and bring it to the front. + */ + private void switchRecentTask() { + RecentTaskInfo targetTask = mActivityTaskManagerInternal.getMostRecentTaskFromBackground(); + if (targetTask == null) { + if (DEBUG_INPUT) { + Slog.w(TAG, "No recent task available! Show watch face."); + } + goHome(); + return; + } + + if (DEBUG_INPUT) { + Slog.d( + TAG, + "Starting task from recents. id=" + + targetTask.id + + ", persistentId=" + + targetTask.persistentId + + ", topActivity=" + + targetTask.topActivity + + ", baseIntent=" + + targetTask.baseIntent); + } + try { + ActivityManager.getService().startActivityFromRecents(targetTask.persistentId, null); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to start task " + targetTask.persistentId + " from recents", e); + } + } + + private int getMaxMultiPressStemPrimaryCount() { + switch (mTriplePressOnStemPrimaryBehavior) { + case TRIPLE_PRESS_PRIMARY_NOTHING: + break; + case TRIPLE_PRESS_PRIMARY_TOGGLE_ACCESSIBILITY: + if (Settings.System.getIntForUser( + mContext.getContentResolver(), + Settings.System.WEAR_ACCESSIBILITY_GESTURE_ENABLED, + /* def= */ 0, + UserHandle.USER_CURRENT) + == 1) { + return 3; + } + break; + } + if (mDoublePressOnStemPrimaryBehavior != DOUBLE_PRESS_PRIMARY_NOTHING) { + return 2; + } + return 1; + } + private boolean hasLongPressOnPowerBehavior() { return getResolvedLongPressOnPowerBehavior() != LONG_PRESS_POWER_NOTHING; } @@ -1204,6 +1396,16 @@ public class PhoneWindowManager implements WindowManagerPolicy { return mLongPressOnBackBehavior != LONG_PRESS_BACK_NOTHING; } + private boolean hasLongPressOnStemPrimaryBehavior() { + return mLongPressOnStemPrimaryBehavior != LONG_PRESS_PRIMARY_NOTHING; + } + + private boolean hasStemPrimaryBehavior() { + return getMaxMultiPressStemPrimaryCount() > 1 + || hasLongPressOnStemPrimaryBehavior() + || mShortPressOnStemPrimaryBehavior != SHORT_PRESS_PRIMARY_NOTHING; + } + private void interceptScreenshotChord() { mHandler.removeCallbacks(mScreenshotRunnable); mScreenshotRunnable.setScreenshotType(TAKE_SCREENSHOT_FULLSCREEN); @@ -2037,6 +2239,35 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } + /** + * Rule for single stem primary key gesture. + */ + private final class StemPrimaryKeyRule extends SingleKeyGestureDetector.SingleKeyRule { + StemPrimaryKeyRule(int gestures) { + super(mContext, KeyEvent.KEYCODE_STEM_PRIMARY, gestures); + } + + @Override + int getMaxMultiPressCount() { + return getMaxMultiPressStemPrimaryCount(); + } + + @Override + void onPress(long downTime) { + stemPrimaryPress(1 /*count*/); + } + + @Override + void onLongPress(long eventTime) { + stemPrimaryLongPress(); + } + + @Override + void onMultiPress(long downTime, int count) { + stemPrimaryPress(count); + } + } + private void initSingleKeyGestureRules() { mSingleKeyGestureDetector = new SingleKeyGestureDetector(); @@ -2052,6 +2283,13 @@ public class PhoneWindowManager implements WindowManagerPolicy { if (hasLongPressOnBackBehavior()) { mSingleKeyGestureDetector.addRule(new BackKeyRule(KEY_LONGPRESS)); } + if (hasStemPrimaryBehavior()) { + int stemPrimaryKeyGestures = 0; + if (hasLongPressOnStemPrimaryBehavior()) { + stemPrimaryKeyGestures |= KEY_LONGPRESS; + } + mSingleKeyGestureDetector.addRule(new StemPrimaryKeyRule(stemPrimaryKeyGestures)); + } } /** @@ -2080,6 +2318,14 @@ public class PhoneWindowManager implements WindowManagerPolicy { if (mPackageManager.hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)) { mShortPressOnWindowBehavior = SHORT_PRESS_WINDOW_PICTURE_IN_PICTURE; } + mShortPressOnStemPrimaryBehavior = mContext.getResources().getInteger( + com.android.internal.R.integer.config_shortPressOnStemPrimaryBehavior); + mLongPressOnStemPrimaryBehavior = mContext.getResources().getInteger( + com.android.internal.R.integer.config_longPressOnStemPrimaryBehavior); + mDoublePressOnStemPrimaryBehavior = mContext.getResources().getInteger( + com.android.internal.R.integer.config_doublePressOnStemPrimaryBehavior); + mTriplePressOnStemPrimaryBehavior = mContext.getResources().getInteger( + com.android.internal.R.integer.config_triplePressOnStemPrimaryBehavior); } public void updateSettings() { @@ -5380,6 +5626,22 @@ public class PhoneWindowManager implements WindowManagerPolicy { pw.print(prefix); pw.print("mShortPressOnWindowBehavior="); pw.println(shortPressOnWindowBehaviorToString(mShortPressOnWindowBehavior)); + pw.print(prefix); + pw.print("mShortPressOnStemPrimaryBehavior="); + pw.println(shortPressOnStemPrimaryBehaviorToString( + mShortPressOnStemPrimaryBehavior)); + pw.print(prefix); + pw.print("mDoublePressOnStemPrimaryBehavior="); + pw.println(doublePressOnStemPrimaryBehaviorToString( + mDoublePressOnStemPrimaryBehavior)); + pw.print(prefix); + pw.print("mTriplePressOnStemPrimaryBehavior="); + pw.println(triplePressOnStemPrimaryBehaviorToString( + mTriplePressOnStemPrimaryBehavior)); + pw.print(prefix); + pw.print("mLongPressOnStemPrimaryBehavior="); + pw.println(longPressOnStemPrimaryBehaviorToString( + mLongPressOnStemPrimaryBehavior)); pw.print(prefix); pw.print("mAllowStartActivityForLongPressOnPowerDuringSetup="); pw.println(mAllowStartActivityForLongPressOnPowerDuringSetup); @@ -5596,6 +5858,50 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } + private static String shortPressOnStemPrimaryBehaviorToString(int behavior) { + switch (behavior) { + case SHORT_PRESS_PRIMARY_NOTHING: + return "SHORT_PRESS_PRIMARY_NOTHING"; + case SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS: + return "SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS"; + default: + return Integer.toString(behavior); + } + } + + private static String doublePressOnStemPrimaryBehaviorToString(int behavior) { + switch (behavior) { + case DOUBLE_PRESS_PRIMARY_NOTHING: + return "DOUBLE_PRESS_PRIMARY_NOTHING"; + case DOUBLE_PRESS_PRIMARY_SWITCH_RECENT_APP: + return "DOUBLE_PRESS_PRIMARY_SWITCH_RECENT_APP"; + default: + return Integer.toString(behavior); + } + } + + private static String triplePressOnStemPrimaryBehaviorToString(int behavior) { + switch (behavior) { + case TRIPLE_PRESS_PRIMARY_NOTHING: + return "TRIPLE_PRESS_PRIMARY_NOTHING"; + case TRIPLE_PRESS_PRIMARY_TOGGLE_ACCESSIBILITY: + return "TRIPLE_PRESS_PRIMARY_TOGGLE_ACCESSIBILITY"; + default: + return Integer.toString(behavior); + } + } + + private static String longPressOnStemPrimaryBehaviorToString(int behavior) { + switch (behavior) { + case LONG_PRESS_PRIMARY_NOTHING: + return "LONG_PRESS_PRIMARY_NOTHING"; + case LONG_PRESS_PRIMARY_LAUNCH_VOICE_ASSISTANT: + return "LONG_PRESS_PRIMARY_LAUNCH_VOICE_ASSISTANT"; + default: + return Integer.toString(behavior); + } + } + private static String lidBehaviorToString(int behavior) { switch (behavior) { case LID_BEHAVIOR_LOCK: diff --git a/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java b/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java index 1d4943fc1b034..b7fb6e53cc272 100644 --- a/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java +++ b/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java @@ -53,6 +53,7 @@ public final class SingleKeyGestureDetector { // Key code of current key down event, reset when key up. private int mDownKeyCode = KeyEvent.KEYCODE_UNKNOWN; private volatile boolean mHandledByLongPress = false; + private volatile boolean mHandledByMultiPress = false; private final Handler mHandler; private long mLastDownTime = 0; private static final long MULTI_PRESS_TIMEOUT = ViewConfiguration.getMultiPressTimeout(); @@ -271,6 +272,7 @@ public final class SingleKeyGestureDetector { mKeyPressCounter + 1, mActiveRule); msg.setAsynchronous(true); mHandler.sendMessage(msg); + mHandledByMultiPress = true; mKeyPressCounter = 0; } } @@ -284,8 +286,9 @@ public final class SingleKeyGestureDetector { return false; } - if (mHandledByLongPress) { + if (mHandledByLongPress || mHandledByMultiPress) { mHandledByLongPress = false; + mHandledByMultiPress = false; mKeyPressCounter = 0; return true; } @@ -339,6 +342,7 @@ public final class SingleKeyGestureDetector { } mHandledByLongPress = false; + mHandledByMultiPress = false; mDownKeyCode = KeyEvent.KEYCODE_UNKNOWN; } diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java index 9c68f2df8b83e..e505d8529383d 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java @@ -19,6 +19,7 @@ package com.android.server.wm; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; +import android.app.ActivityManager; import android.app.AppProtoEnums; import android.app.IActivityManager; import android.app.IApplicationThread; @@ -688,4 +689,7 @@ public abstract class ActivityTaskManagerInternal { public abstract void registerActivityStartInterceptor( @ActivityInterceptorCallback.OrderedId int id, ActivityInterceptorCallback callback); + + /** Get the most recent task excluding the first running task (the one on the front most). */ + public abstract ActivityManager.RecentTaskInfo getMostRecentTaskFromBackground(); } diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 886b05f25dc86..a1a357e91b8f9 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -6609,5 +6609,33 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { mActivityInterceptorCallbacks.put(id, callback); } } + + @Override + public ActivityManager.RecentTaskInfo getMostRecentTaskFromBackground() { + List runningTaskInfoList = getTasks(1); + ActivityManager.RunningTaskInfo runningTaskInfo; + if (runningTaskInfoList.size() > 0) { + runningTaskInfo = runningTaskInfoList.get(0); + } else { + Slog.i(TAG, "No running task found!"); + return null; + } + // Get 2 most recent tasks. + List recentTaskInfoList = + getRecentTasks( + 2, + ActivityManager.RECENT_IGNORE_UNAVAILABLE, + mContext.getUserId()) + .getList(); + ActivityManager.RecentTaskInfo targetTask = null; + for (ActivityManager.RecentTaskInfo info : recentTaskInfoList) { + // Find a recent task that is not the current running task on screen. + if (info.id != runningTaskInfo.id) { + targetTask = info; + break; + } + } + return targetTask; + } } }