Supports showing Ime shortcuts in the system shortcuts UI.
Bug: 273483670 Test: locally tested. Change-Id: I49a59ab8688aaf193a79c610738a1db951f2bfdf
This commit is contained in:
@@ -471,9 +471,22 @@ interface IWindowManager
|
||||
* Requests Keyboard Shortcuts from the displayed window.
|
||||
*
|
||||
* @param receiver The receiver to deliver the results to.
|
||||
* @param deviceId The deviceId of KeyEvent by which this request is triggered, or -1 if it's
|
||||
* not triggered by a KeyEvent.
|
||||
* @see #requestImeKeyboardShortcuts(IResultReceiver, int)
|
||||
*/
|
||||
void requestAppKeyboardShortcuts(IResultReceiver receiver, int deviceId);
|
||||
|
||||
/**
|
||||
* Requests Keyboard Shortcuts from currently selected IME.
|
||||
*
|
||||
* @param receiver The receiver to deliver the results to.
|
||||
* @param deviceId The deviceId of KeyEvent by which this request is triggered, or -1 if it's
|
||||
* not triggered by a KeyEvent.
|
||||
* @see #requestAppKeyboardShortcuts(IResultReceiver, int)
|
||||
*/
|
||||
void requestImeKeyboardShortcuts(IResultReceiver receiver, int deviceId);
|
||||
|
||||
/**
|
||||
* Retrieves the current stable insets from the primary display.
|
||||
*/
|
||||
|
||||
@@ -1384,14 +1384,27 @@ public interface WindowManager extends ViewManager {
|
||||
"android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED";
|
||||
|
||||
/**
|
||||
* Request for keyboard shortcuts to be retrieved asynchronously.
|
||||
* Request for app's keyboard shortcuts to be retrieved asynchronously.
|
||||
*
|
||||
* @param receiver The callback to be triggered when the result is ready.
|
||||
* @param deviceId The deviceId of KeyEvent by which this request is triggered, or -1 if it's
|
||||
* not triggered by a KeyEvent.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public void requestAppKeyboardShortcuts(final KeyboardShortcutsReceiver receiver, int deviceId);
|
||||
|
||||
/**
|
||||
* Request for ime's keyboard shortcuts to be retrieved asynchronously.
|
||||
*
|
||||
* @param receiver The callback to be triggered when the result is ready.
|
||||
* @param deviceId The deviceId of KeyEvent by which this request is triggered, or -1 if it's
|
||||
* not triggered by a KeyEvent.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
default void requestImeKeyboardShortcuts(KeyboardShortcutsReceiver receiver, int deviceId) {};
|
||||
|
||||
/**
|
||||
* Return the touch region for the current IME window, or an empty region if there is none.
|
||||
*
|
||||
|
||||
@@ -215,14 +215,36 @@ public final class WindowManagerImpl implements WindowManager {
|
||||
@Override
|
||||
public void send(int resultCode, Bundle resultData) throws RemoteException {
|
||||
List<KeyboardShortcutGroup> result =
|
||||
resultData.getParcelableArrayList(PARCEL_KEY_SHORTCUTS_ARRAY, android.view.KeyboardShortcutGroup.class);
|
||||
resultData.getParcelableArrayList(PARCEL_KEY_SHORTCUTS_ARRAY,
|
||||
android.view.KeyboardShortcutGroup.class);
|
||||
receiver.onKeyboardShortcutsReceived(result);
|
||||
}
|
||||
};
|
||||
try {
|
||||
WindowManagerGlobal.getWindowManagerService()
|
||||
.requestAppKeyboardShortcuts(resultReceiver, deviceId);
|
||||
.requestAppKeyboardShortcuts(resultReceiver, deviceId);
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestImeKeyboardShortcuts(
|
||||
final KeyboardShortcutsReceiver receiver, int deviceId) {
|
||||
IResultReceiver resultReceiver = new IResultReceiver.Stub() {
|
||||
@Override
|
||||
public void send(int resultCode, Bundle resultData) throws RemoteException {
|
||||
List<KeyboardShortcutGroup> result =
|
||||
resultData.getParcelableArrayList(PARCEL_KEY_SHORTCUTS_ARRAY,
|
||||
android.view.KeyboardShortcutGroup.class);
|
||||
receiver.onKeyboardShortcutsReceived(result);
|
||||
}
|
||||
};
|
||||
try {
|
||||
WindowManagerGlobal.getWindowManagerService()
|
||||
.requestImeKeyboardShortcuts(resultReceiver, deviceId);
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3379,6 +3379,12 @@
|
||||
"group": "WM_DEBUG_REMOTE_ANIMATIONS",
|
||||
"at": "com\/android\/server\/wm\/RemoteAnimationController.java"
|
||||
},
|
||||
"975028389": {
|
||||
"message": "unable to call receiver for empty keyboard shortcuts",
|
||||
"level": "ERROR",
|
||||
"group": "WM_ERROR",
|
||||
"at": "com\/android\/server\/wm\/WindowManagerService.java"
|
||||
},
|
||||
"975275467": {
|
||||
"message": "Set animatingExit: reason=remove\/isAnimating win=%s",
|
||||
"level": "VERBOSE",
|
||||
|
||||
@@ -56,7 +56,6 @@ import android.view.View.AccessibilityDelegate;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
import android.view.WindowManager.KeyboardShortcutsReceiver;
|
||||
import android.view.accessibility.AccessibilityNodeInfo;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
@@ -337,6 +336,12 @@ public final class KeyboardShortcutListSearch {
|
||||
mSpecialCharacterNames.put(KeyEvent.KEYCODE_MUHENKAN, "無変換");
|
||||
mSpecialCharacterNames.put(KeyEvent.KEYCODE_HENKAN, "変換");
|
||||
mSpecialCharacterNames.put(KeyEvent.KEYCODE_KATAKANA_HIRAGANA, "かな");
|
||||
mSpecialCharacterNames.put(KeyEvent.KEYCODE_ALT_LEFT, "Alt");
|
||||
mSpecialCharacterNames.put(KeyEvent.KEYCODE_ALT_RIGHT, "Alt");
|
||||
mSpecialCharacterNames.put(KeyEvent.KEYCODE_CTRL_LEFT, "Ctrl");
|
||||
mSpecialCharacterNames.put(KeyEvent.KEYCODE_CTRL_RIGHT, "Ctrl");
|
||||
mSpecialCharacterNames.put(KeyEvent.KEYCODE_SHIFT_LEFT, "Shift");
|
||||
mSpecialCharacterNames.put(KeyEvent.KEYCODE_SHIFT_RIGHT, "Shift");
|
||||
|
||||
mModifierNames.put(KeyEvent.META_META_ON, "Meta");
|
||||
mModifierNames.put(KeyEvent.META_CTRL_ON, "Ctrl");
|
||||
@@ -411,27 +416,45 @@ public final class KeyboardShortcutListSearch {
|
||||
mKeyCharacterMap = mBackupKeyCharacterMap;
|
||||
}
|
||||
|
||||
private boolean mAppShortcutsReceived;
|
||||
private boolean mImeShortcutsReceived;
|
||||
|
||||
@VisibleForTesting
|
||||
void showKeyboardShortcuts(int deviceId) {
|
||||
retrieveKeyCharacterMap(deviceId);
|
||||
mWindowManager.requestAppKeyboardShortcuts(new KeyboardShortcutsReceiver() {
|
||||
@Override
|
||||
public void onKeyboardShortcutsReceived(
|
||||
final List<KeyboardShortcutGroup> result) {
|
||||
// Add specific app shortcuts
|
||||
if (result.isEmpty()) {
|
||||
mKeySearchResultMap.put(SHORTCUT_SPECIFICAPP_INDEX, false);
|
||||
} else {
|
||||
mSpecificAppGroup = reMapToKeyboardShortcutMultiMappingGroup(result);
|
||||
mKeySearchResultMap.put(SHORTCUT_SPECIFICAPP_INDEX, true);
|
||||
}
|
||||
mFullShortsGroup.add(SHORTCUT_SYSTEM_INDEX, mSystemGroup);
|
||||
mFullShortsGroup.add(SHORTCUT_INPUT_INDEX, mInputGroup);
|
||||
mFullShortsGroup.add(SHORTCUT_OPENAPPS_INDEX, mOpenAppsGroup);
|
||||
mFullShortsGroup.add(SHORTCUT_SPECIFICAPP_INDEX, mSpecificAppGroup);
|
||||
showKeyboardShortcutSearchList(mFullShortsGroup);
|
||||
mAppShortcutsReceived = false;
|
||||
mImeShortcutsReceived = false;
|
||||
mWindowManager.requestAppKeyboardShortcuts(result -> {
|
||||
// Add specific app shortcuts
|
||||
if (result.isEmpty()) {
|
||||
mKeySearchResultMap.put(SHORTCUT_SPECIFICAPP_INDEX, false);
|
||||
} else {
|
||||
mSpecificAppGroup.addAll(reMapToKeyboardShortcutMultiMappingGroup(result));
|
||||
mKeySearchResultMap.put(SHORTCUT_SPECIFICAPP_INDEX, true);
|
||||
}
|
||||
mAppShortcutsReceived = true;
|
||||
if (mImeShortcutsReceived) {
|
||||
mergeAndShowKeyboardShortcutsGroups();
|
||||
}
|
||||
}, deviceId);
|
||||
mWindowManager.requestImeKeyboardShortcuts(result -> {
|
||||
// Add specific Ime shortcuts
|
||||
if (!result.isEmpty()) {
|
||||
mInputGroup.addAll(reMapToKeyboardShortcutMultiMappingGroup(result));
|
||||
}
|
||||
mImeShortcutsReceived = true;
|
||||
if (mAppShortcutsReceived) {
|
||||
mergeAndShowKeyboardShortcutsGroups();
|
||||
}
|
||||
}, deviceId);
|
||||
}
|
||||
|
||||
private void mergeAndShowKeyboardShortcutsGroups() {
|
||||
mFullShortsGroup.add(SHORTCUT_SYSTEM_INDEX, mSystemGroup);
|
||||
mFullShortsGroup.add(SHORTCUT_INPUT_INDEX, mInputGroup);
|
||||
mFullShortsGroup.add(SHORTCUT_OPENAPPS_INDEX, mOpenAppsGroup);
|
||||
mFullShortsGroup.add(SHORTCUT_SPECIFICAPP_INDEX, mSpecificAppGroup);
|
||||
showKeyboardShortcutSearchList(mFullShortsGroup);
|
||||
}
|
||||
|
||||
// The original data structure is only for 1-to-1 shortcut mapping, so remap the old
|
||||
|
||||
@@ -56,7 +56,6 @@ import android.view.View.AccessibilityDelegate;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
import android.view.WindowManager.KeyboardShortcutsReceiver;
|
||||
import android.view.accessibility.AccessibilityNodeInfo;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
@@ -129,6 +128,9 @@ public final class KeyboardShortcuts {
|
||||
private KeyCharacterMap mKeyCharacterMap;
|
||||
private KeyCharacterMap mBackupKeyCharacterMap;
|
||||
|
||||
@Nullable private List<KeyboardShortcutGroup> mReceivedAppShortcutGroups = null;
|
||||
@Nullable private List<KeyboardShortcutGroup> mReceivedImeShortcutGroups = null;
|
||||
|
||||
@VisibleForTesting
|
||||
KeyboardShortcuts(Context context, WindowManager windowManager) {
|
||||
this.mContext = new ContextThemeWrapper(
|
||||
@@ -324,6 +326,12 @@ public final class KeyboardShortcuts {
|
||||
mSpecialCharacterNames.put(KeyEvent.KEYCODE_MUHENKAN, "無変換");
|
||||
mSpecialCharacterNames.put(KeyEvent.KEYCODE_HENKAN, "変換");
|
||||
mSpecialCharacterNames.put(KeyEvent.KEYCODE_KATAKANA_HIRAGANA, "かな");
|
||||
mSpecialCharacterNames.put(KeyEvent.KEYCODE_ALT_LEFT, "Alt");
|
||||
mSpecialCharacterNames.put(KeyEvent.KEYCODE_ALT_RIGHT, "Alt");
|
||||
mSpecialCharacterNames.put(KeyEvent.KEYCODE_CTRL_LEFT, "Ctrl");
|
||||
mSpecialCharacterNames.put(KeyEvent.KEYCODE_CTRL_RIGHT, "Ctrl");
|
||||
mSpecialCharacterNames.put(KeyEvent.KEYCODE_SHIFT_LEFT, "Shift");
|
||||
mSpecialCharacterNames.put(KeyEvent.KEYCODE_SHIFT_RIGHT, "Shift");
|
||||
|
||||
mModifierNames.put(KeyEvent.META_META_ON, "Meta");
|
||||
mModifierNames.put(KeyEvent.META_CTRL_ON, "Ctrl");
|
||||
@@ -382,18 +390,36 @@ public final class KeyboardShortcuts {
|
||||
@VisibleForTesting
|
||||
void showKeyboardShortcuts(int deviceId) {
|
||||
retrieveKeyCharacterMap(deviceId);
|
||||
mWindowManager.requestAppKeyboardShortcuts(new KeyboardShortcutsReceiver() {
|
||||
@Override
|
||||
public void onKeyboardShortcutsReceived(
|
||||
final List<KeyboardShortcutGroup> result) {
|
||||
result.add(getSystemShortcuts());
|
||||
final KeyboardShortcutGroup appShortcuts = getDefaultApplicationShortcuts();
|
||||
if (appShortcuts != null) {
|
||||
result.add(appShortcuts);
|
||||
}
|
||||
showKeyboardShortcutsDialog(result);
|
||||
}
|
||||
}, deviceId);
|
||||
mReceivedAppShortcutGroups = null;
|
||||
mReceivedImeShortcutGroups = null;
|
||||
mWindowManager.requestAppKeyboardShortcuts(
|
||||
result -> {
|
||||
mReceivedAppShortcutGroups = result;
|
||||
maybeMergeAndShowKeyboardShortcuts();
|
||||
}, deviceId);
|
||||
mWindowManager.requestImeKeyboardShortcuts(
|
||||
result -> {
|
||||
mReceivedImeShortcutGroups = result;
|
||||
maybeMergeAndShowKeyboardShortcuts();
|
||||
}, deviceId);
|
||||
}
|
||||
|
||||
private void maybeMergeAndShowKeyboardShortcuts() {
|
||||
if (mReceivedAppShortcutGroups == null || mReceivedImeShortcutGroups == null) {
|
||||
return;
|
||||
}
|
||||
List<KeyboardShortcutGroup> shortcutGroups = mReceivedAppShortcutGroups;
|
||||
shortcutGroups.addAll(mReceivedImeShortcutGroups);
|
||||
mReceivedAppShortcutGroups = null;
|
||||
mReceivedImeShortcutGroups = null;
|
||||
|
||||
final KeyboardShortcutGroup defaultAppShortcuts =
|
||||
getDefaultApplicationShortcuts();
|
||||
if (defaultAppShortcuts != null) {
|
||||
shortcutGroups.add(defaultAppShortcuts);
|
||||
}
|
||||
shortcutGroups.add(getSystemShortcuts());
|
||||
showKeyboardShortcutsDialog(shortcutGroups);
|
||||
}
|
||||
|
||||
private void dismissKeyboardShortcuts() {
|
||||
|
||||
@@ -76,5 +76,6 @@ public class KeyboardShortcutListSearchTest extends SysuiTestCase {
|
||||
mKeyboardShortcutListSearch.toggle(mContext, DEVICE_ID);
|
||||
|
||||
verify(mWindowManager).requestAppKeyboardShortcuts(any(), anyInt());
|
||||
verify(mWindowManager).requestImeKeyboardShortcuts(any(), anyInt());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,5 +75,6 @@ public class KeyboardShortcutsTest extends SysuiTestCase {
|
||||
mKeyboardShortcuts.toggle(mContext, DEVICE_ID);
|
||||
|
||||
verify(mWindowManager).requestAppKeyboardShortcuts(any(), anyInt());
|
||||
verify(mWindowManager).requestImeKeyboardShortcuts(any(), anyInt());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7185,15 +7185,45 @@ public class WindowManagerService extends IWindowManager.Stub
|
||||
|
||||
@Override
|
||||
public void requestAppKeyboardShortcuts(IResultReceiver receiver, int deviceId) {
|
||||
mContext.enforceCallingOrSelfPermission(REGISTER_WINDOW_MANAGER_LISTENERS,
|
||||
"requestAppKeyboardShortcuts");
|
||||
enforceRegisterWindowManagerListenersPermission("requestAppKeyboardShortcuts");
|
||||
|
||||
WindowState focusedWindow = getFocusedWindow();
|
||||
if (focusedWindow == null || focusedWindow.mClient == null) {
|
||||
notifyReceiverWithEmptyBundle(receiver);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
WindowState focusedWindow = getFocusedWindow();
|
||||
if (focusedWindow != null && focusedWindow.mClient != null) {
|
||||
getFocusedWindow().mClient.requestAppKeyboardShortcuts(receiver, deviceId);
|
||||
}
|
||||
focusedWindow.mClient.requestAppKeyboardShortcuts(receiver, deviceId);
|
||||
} catch (RemoteException e) {
|
||||
notifyReceiverWithEmptyBundle(receiver);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestImeKeyboardShortcuts(IResultReceiver receiver, int deviceId) {
|
||||
enforceRegisterWindowManagerListenersPermission("requestImeKeyboardShortcuts");
|
||||
|
||||
WindowState imeWindow = mRoot.getCurrentInputMethodWindow();
|
||||
if (imeWindow == null || imeWindow.mClient == null) {
|
||||
notifyReceiverWithEmptyBundle(receiver);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
imeWindow.mClient.requestAppKeyboardShortcuts(receiver, deviceId);
|
||||
} catch (RemoteException e) {
|
||||
notifyReceiverWithEmptyBundle(receiver);
|
||||
}
|
||||
}
|
||||
|
||||
private void enforceRegisterWindowManagerListenersPermission(String message) {
|
||||
mContext.enforceCallingOrSelfPermission(REGISTER_WINDOW_MANAGER_LISTENERS, message);
|
||||
}
|
||||
|
||||
private static void notifyReceiverWithEmptyBundle(IResultReceiver receiver) {
|
||||
try {
|
||||
receiver.send(0, Bundle.EMPTY);
|
||||
} catch (RemoteException e) {
|
||||
ProtoLog.e(WM_ERROR, "unable to call receiver for empty keyboard shortcuts");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -52,6 +52,7 @@ import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
@@ -99,6 +100,7 @@ import androidx.test.filters.SmallTest;
|
||||
import androidx.test.platform.app.InstrumentationRegistry;
|
||||
|
||||
import com.android.compatibility.common.util.AdoptShellPermissionsRule;
|
||||
import com.android.internal.os.IResultReceiver;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
@@ -905,6 +907,56 @@ public class WindowManagerServiceTests extends WindowTestsBase {
|
||||
argThat(h -> (h.inputConfig & InputConfig.SPY) == InputConfig.SPY));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRequestKeyboardShortcuts_noWindow() {
|
||||
doNothing().when(mWm.mContext).enforceCallingOrSelfPermission(anyString(), anyString());
|
||||
doReturn(null).when(mWm).getFocusedWindowLocked();
|
||||
doReturn(null).when(mWm.mRoot).getCurrentInputMethodWindow();
|
||||
|
||||
TestResultReceiver receiver = new TestResultReceiver();
|
||||
mWm.requestAppKeyboardShortcuts(receiver, 0);
|
||||
assertNotNull(receiver.resultData);
|
||||
assertTrue(receiver.resultData.isEmpty());
|
||||
|
||||
receiver = new TestResultReceiver();
|
||||
mWm.requestImeKeyboardShortcuts(receiver, 0);
|
||||
assertNotNull(receiver.resultData);
|
||||
assertTrue(receiver.resultData.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRequestKeyboardShortcuts() throws RemoteException {
|
||||
final IWindow window = mock(IWindow.class);
|
||||
final IBinder binder = mock(IBinder.class);
|
||||
doReturn(binder).when(window).asBinder();
|
||||
final WindowState windowState =
|
||||
createWindow(null, TYPE_BASE_APPLICATION, mDisplayContent, "appWin", window);
|
||||
doNothing().when(mWm.mContext).enforceCallingOrSelfPermission(anyString(), anyString());
|
||||
doReturn(windowState).when(mWm).getFocusedWindowLocked();
|
||||
doReturn(windowState).when(mWm.mRoot).getCurrentInputMethodWindow();
|
||||
|
||||
TestResultReceiver receiver = new TestResultReceiver();
|
||||
mWm.requestAppKeyboardShortcuts(receiver, 0);
|
||||
mWm.requestImeKeyboardShortcuts(receiver, 0);
|
||||
verify(window, times(2)).requestAppKeyboardShortcuts(receiver, 0);
|
||||
}
|
||||
|
||||
class TestResultReceiver implements IResultReceiver {
|
||||
public android.os.Bundle resultData;
|
||||
private final IBinder mBinder = mock(IBinder.class);
|
||||
|
||||
@Override
|
||||
public void send(int resultCode, android.os.Bundle resultData)
|
||||
throws android.os.RemoteException {
|
||||
this.resultData = resultData;
|
||||
}
|
||||
|
||||
@Override
|
||||
public android.os.IBinder asBinder() {
|
||||
return mBinder;
|
||||
}
|
||||
}
|
||||
|
||||
private void setupActivityWithLaunchCookie(IBinder launchCookie, WindowContainerToken wct) {
|
||||
final WindowContainer.RemoteToken remoteToken = mock(WindowContainer.RemoteToken.class);
|
||||
when(remoteToken.toWindowContainerToken()).thenReturn(wct);
|
||||
|
||||
Reference in New Issue
Block a user