From 9c404e8f17c7bcb01edd5ffacd424c990f0e81c8 Mon Sep 17 00:00:00 2001 From: yzj Date: Thu, 9 Sep 2021 22:00:59 +0000 Subject: [PATCH] Import wear stem primary button gesture to master. Features imported in this change: 1. Single press stem primary button to open launcher. 2. Double press stem primary button to switch to most recent task. 3. Triple press stem primary button to toggle accessibility mode. 4. Long press stem primary button to trigger assistant. Bug: 197273843 Test: manual test Change-Id: I0e0d11b042f8ae966efb98f5bd4e25f906292330 --- core/res/res/values/config.xml | 33 ++ core/res/res/values/symbols.xml | 4 + .../server/policy/PhoneWindowManager.java | 306 ++++++++++++++++++ .../policy/SingleKeyGestureDetector.java | 6 +- .../wm/ActivityTaskManagerInternal.java | 4 + .../server/wm/ActivityTaskManagerService.java | 28 ++ 6 files changed, 380 insertions(+), 1 deletion(-) diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 2680c31914c00..ad3194feb82b5 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 253cd47d1b3e5..8954e30c0a016 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 5d34939919851..8eb764b4b722a 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); @@ -2033,6 +2235,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(); @@ -2048,6 +2279,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)); + } } /** @@ -2076,6 +2314,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() { @@ -5365,6 +5611,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); @@ -5581,6 +5843,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 960fa5044e3c0..36363d1210e08 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 48afb7c0c1b15..588f0b4016112 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; + } } }