diff --git a/services/core/java/com/android/server/policy/GlobalKeyIntent.java b/services/core/java/com/android/server/policy/GlobalKeyIntent.java new file mode 100644 index 0000000000000..f8682be7c49ab --- /dev/null +++ b/services/core/java/com/android/server/policy/GlobalKeyIntent.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2021 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.server.policy; + +import android.annotation.NonNull; +import android.content.ComponentName; +import android.content.Intent; +import android.util.Log; +import android.view.KeyEvent; + +/** + * This class wrapped the Intent for global key ops. + */ +public final class GlobalKeyIntent { + private static final String EXTRA_BEGAN_FROM_NON_INTERACTIVE = + "EXTRA_BEGAN_FROM_NON_INTERACTIVE"; + + private final ComponentName mComponentName; + private final KeyEvent mKeyEvent; + private final boolean mBeganFromNonInteractive; + + GlobalKeyIntent(@NonNull ComponentName componentName, @NonNull KeyEvent event, + boolean beganFromNonInteractive) { + mComponentName = componentName; + mKeyEvent = new KeyEvent(event); + mBeganFromNonInteractive = beganFromNonInteractive; + } + + Intent getIntent() { + final Intent intent = new Intent(Intent.ACTION_GLOBAL_BUTTON) + .setComponent(mComponentName) + .setFlags(Intent.FLAG_RECEIVER_FOREGROUND) + .putExtra(Intent.EXTRA_KEY_EVENT, mKeyEvent) + .putExtra(EXTRA_BEGAN_FROM_NON_INTERACTIVE, mBeganFromNonInteractive); + return intent; + } + + /** + * Get the {@link KeyEvent} information of {@link Intent#ACTION_GLOBAL_BUTTON}. + */ + public KeyEvent getKeyEvent() { + return mKeyEvent; + } + + /** + * Indicate if the global key is dispatched from non-interactive mode. + * Information of {@link Intent#ACTION_GLOBAL_BUTTON}. + */ + public boolean beganFromNonInteractive() { + return mBeganFromNonInteractive; + } + + /** + * Generate a GlobalKeyIntent from {@link Intent}, the action must be + * {@link Intent#ACTION_GLOBAL_BUTTON}. + * + * @param intent The received intent of the global key. + */ + public static GlobalKeyIntent from(@NonNull Intent intent) { + if (intent.getAction() != Intent.ACTION_GLOBAL_BUTTON) { + Log.wtf("GlobalKeyIntent", "Intent should be ACTION_GLOBAL_BUTTON"); + return null; + } + + final KeyEvent event = intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT); + final boolean fromNonInteractive = + intent.getBooleanExtra(EXTRA_BEGAN_FROM_NON_INTERACTIVE, false); + return new GlobalKeyIntent(intent.getComponent(), event, fromNonInteractive); + } +} diff --git a/services/core/java/com/android/server/policy/GlobalKeyManager.java b/services/core/java/com/android/server/policy/GlobalKeyManager.java index 157f8256ce508..2d48452b45f07 100644 --- a/services/core/java/com/android/server/policy/GlobalKeyManager.java +++ b/services/core/java/com/android/server/policy/GlobalKeyManager.java @@ -39,6 +39,9 @@ import java.io.PrintWriter; * broadcast to the specified component. The action of the intent will be * {@link Intent#ACTION_GLOBAL_BUTTON} and the KeyEvent will be included in the intent with * {@link Intent#EXTRA_KEY_EVENT}. + * + * Use {@link GlobalKeyIntent} to get detail information from received {@link Intent}, includes + * {@link KeyEvent} and the information about if the key is dispatched from non-interactive mode. */ final class GlobalKeyManager { @@ -49,13 +52,15 @@ final class GlobalKeyManager { private static final String TAG_KEY = "key"; private static final String ATTR_KEY_CODE = "keyCode"; private static final String ATTR_COMPONENT = "component"; + private static final String ATTR_DISPATCH_WHEN_NON_INTERACTIVE = "dispatchWhenNonInteractive"; private static final int GLOBAL_KEY_FILE_VERSION = 1; - private SparseArray mKeyMapping; + private SparseArray mKeyMapping; + private boolean mBeganFromNonInteractive = false; public GlobalKeyManager(Context context) { - mKeyMapping = new SparseArray(); + mKeyMapping = new SparseArray<>(); loadGlobalKeys(context); } @@ -69,13 +74,15 @@ final class GlobalKeyManager { */ boolean handleGlobalKey(Context context, int keyCode, KeyEvent event) { if (mKeyMapping.size() > 0) { - ComponentName component = mKeyMapping.get(keyCode); - if (component != null) { - Intent intent = new Intent(Intent.ACTION_GLOBAL_BUTTON) - .setComponent(component) - .setFlags(Intent.FLAG_RECEIVER_FOREGROUND) - .putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(event)); + GlobalKeyAction action = mKeyMapping.get(keyCode); + if (action != null) { + final Intent intent = new GlobalKeyIntent(action.mComponentName, event, + mBeganFromNonInteractive).getIntent(); context.sendBroadcastAsUser(intent, UserHandle.CURRENT, null); + + if (event.getAction() == KeyEvent.ACTION_UP) { + mBeganFromNonInteractive = false; + } return true; } } @@ -85,10 +92,35 @@ final class GlobalKeyManager { /** * Returns {@code true} if the key will be handled globally. */ - boolean shouldHandleGlobalKey(int keyCode, KeyEvent event) { + boolean shouldHandleGlobalKey(int keyCode) { return mKeyMapping.get(keyCode) != null; } + /** + * Returns {@code true} if the key will be handled globally. + */ + boolean shouldDispatchFromNonInteractive(int keyCode) { + final GlobalKeyAction action = mKeyMapping.get(keyCode); + if (action == null) { + return false; + } + + return action.mDispatchWhenNonInteractive; + } + + void setBeganFromNonInteractive() { + mBeganFromNonInteractive = true; + } + + class GlobalKeyAction { + private ComponentName mComponentName; + private boolean mDispatchWhenNonInteractive; + GlobalKeyAction(String componentName, String dispatchWhenNonInteractive) { + mComponentName = ComponentName.unflattenFromString(componentName); + mDispatchWhenNonInteractive = Boolean.valueOf(dispatchWhenNonInteractive); + } + } + private void loadGlobalKeys(Context context) { XmlResourceParser parser = null; try { @@ -105,10 +137,12 @@ final class GlobalKeyManager { if (TAG_KEY.equals(element)) { String keyCodeName = parser.getAttributeValue(null, ATTR_KEY_CODE); String componentName = parser.getAttributeValue(null, ATTR_COMPONENT); + String dispatchWhenNonInteractive = + parser.getAttributeValue(null, ATTR_DISPATCH_WHEN_NON_INTERACTIVE); int keyCode = KeyEvent.keyCodeFromString(keyCodeName); if (keyCode != KeyEvent.KEYCODE_UNKNOWN) { - mKeyMapping.put(keyCode, ComponentName.unflattenFromString( - componentName)); + mKeyMapping.put(keyCode, new GlobalKeyAction( + componentName, dispatchWhenNonInteractive)); } } } @@ -138,7 +172,9 @@ final class GlobalKeyManager { pw.print(prefix); pw.print(KeyEvent.keyCodeToString(mKeyMapping.keyAt(i))); pw.print("="); - pw.println(mKeyMapping.valueAt(i).flattenToString()); + pw.print(mKeyMapping.valueAt(i).mComponentName.flattenToString()); + pw.print(",dispatchWhenNonInteractive="); + pw.println(mKeyMapping.valueAt(i).mDispatchWhenNonInteractive); } pw.print(prefix); pw.println("}"); } diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index b56736ee9ab7d..d73203df81c40 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -3434,7 +3434,16 @@ public class PhoneWindowManager implements WindowManagerPolicy { // If the key would be handled globally, just return the result, don't worry about special // key processing. if (isValidGlobalKey(keyCode) - && mGlobalKeyManager.shouldHandleGlobalKey(keyCode, event)) { + && mGlobalKeyManager.shouldHandleGlobalKey(keyCode)) { + // Dispatch if global key defined dispatchWhenNonInteractive. + if (!interactive && isWakeKey && down + && mGlobalKeyManager.shouldDispatchFromNonInteractive(keyCode)) { + mGlobalKeyManager.setBeganFromNonInteractive(); + result = ACTION_PASS_TO_USER; + // Since we're dispatching the input, reset the pending key + mPendingWakeKey = PENDING_KEY_NULL; + } + if (isWakeKey) { wakeUpFromWakeKey(event); } @@ -3988,6 +3997,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { Slog.e(TAG, "RemoteException when checking if dreaming", e); } } + // Otherwise, consume events since the user can't see what is being // interacted with. return false;