Merge "Supports showing Ime shortcuts in the system shortcuts UI." into udc-qpr-dev

This commit is contained in:
Shu Chen
2023-06-03 13:15:37 +00:00
committed by Android (Google) Code Review
10 changed files with 226 additions and 39 deletions

View File

@@ -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.
*/

View File

@@ -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.
*

View File

@@ -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();
}
}

View File

@@ -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",

View File

@@ -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

View File

@@ -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() {

View File

@@ -76,5 +76,6 @@ public class KeyboardShortcutListSearchTest extends SysuiTestCase {
mKeyboardShortcutListSearch.toggle(mContext, DEVICE_ID);
verify(mWindowManager).requestAppKeyboardShortcuts(any(), anyInt());
verify(mWindowManager).requestImeKeyboardShortcuts(any(), anyInt());
}
}

View File

@@ -75,5 +75,6 @@ public class KeyboardShortcutsTest extends SysuiTestCase {
mKeyboardShortcuts.toggle(mContext, DEVICE_ID);
verify(mWindowManager).requestAppKeyboardShortcuts(any(), anyInt());
verify(mWindowManager).requestImeKeyboardShortcuts(any(), anyInt());
}
}

View File

@@ -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");
}
}

View File

@@ -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);