diff options
| author | 2017-11-21 14:26:11 -0800 | |
|---|---|---|
| committer | 2017-12-08 00:55:11 +0000 | |
| commit | 89a6c48a8b2e54f9b93211c20a126edab0eefe35 (patch) | |
| tree | 24672532a571730b75767871d0f7d98f2f7ec259 | |
| parent | 7c95a682ba064c611a13cc94fa1ccea397d776a9 (diff) | |
Add support for VR InputMethod.
This change adds support for VR-only IMEs in InputMethod framework.
In order to set this VR IME, setVrInputMethod(ComponentName) should be
called by VrManager.
When VrManager calls setVrInputMethod(), IMMS changes updates
the selected input method in a transient way i.e. it doesn't
update the Settings or input history. Once VR mode finishes,
it restores last input from settings.
Bug: 63037786
Test: Manually using the sample app in bug.
Change-Id: I1db7981b5198e7e203d4578cae7e5b6d20037d0d
8 files changed, 152 insertions, 6 deletions
diff --git a/core/java/android/app/VrManager.java b/core/java/android/app/VrManager.java index 392387a99897..61b90e1766e5 100644 --- a/core/java/android/app/VrManager.java +++ b/core/java/android/app/VrManager.java @@ -4,6 +4,7 @@ import android.annotation.NonNull; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; +import android.annotation.TestApi; import android.content.ComponentName; import android.content.Context; import android.os.Handler; @@ -214,4 +215,22 @@ public class VrManager { e.rethrowFromSystemServer(); } } + + /** + * Start VR Input method for the packageName in {@link ComponentName}. + * This method notifies InputMethodManagerService to use VR IME instead of + * regular phone IME. + * @param componentName ComponentName of a VR InputMethod that should be set as selected + * input by InputMethodManagerService. + * @hide + */ + @TestApi + @RequiresPermission(android.Manifest.permission.RESTRICTED_VR_ACCESS) + public void setVrInputMethod(ComponentName componentName) { + try { + mService.setVrInputMethod(componentName); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } } diff --git a/core/java/android/service/vr/IVrManager.aidl b/core/java/android/service/vr/IVrManager.aidl index 7285fb40ae02..f7acfc5918a8 100644 --- a/core/java/android/service/vr/IVrManager.aidl +++ b/core/java/android/service/vr/IVrManager.aidl @@ -17,6 +17,7 @@ package android.service.vr; import android.app.Vr2dDisplayProperties; +import android.content.ComponentName; import android.service.vr.IVrStateCallbacks; import android.service.vr.IPersistentVrStateCallbacks; @@ -109,5 +110,13 @@ interface IVrManager { * @param standy True if the device is entering standby, false if it's exiting standby. */ void setStandbyEnabled(boolean standby); + + /** + * Start VR Input method for the given packageName in {@param componentName}. + * This method notifies InputMethodManagerService to use VR IME instead of + * regular phone IME. + */ + void setVrInputMethod(in ComponentName componentName); + } diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 92d1de8e5a24..4d96733255d7 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -697,6 +697,19 @@ public final class InputMethodManager { } } + /** + * Returns a list of VR InputMethod currently installed. + * @hide + */ + @RequiresPermission(android.Manifest.permission.RESTRICTED_VR_ACCESS) + public List<InputMethodInfo> getVrInputMethodList() { + try { + return mService.getVrInputMethodList(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + public List<InputMethodInfo> getEnabledInputMethodList() { try { return mService.getEnabledInputMethodList(); diff --git a/core/java/android/view/inputmethod/InputMethodManagerInternal.java b/core/java/android/view/inputmethod/InputMethodManagerInternal.java index 77df4e3883a7..e13813e5199b 100644 --- a/core/java/android/view/inputmethod/InputMethodManagerInternal.java +++ b/core/java/android/view/inputmethod/InputMethodManagerInternal.java @@ -16,6 +16,8 @@ package android.view.inputmethod; +import android.content.ComponentName; + /** * Input method manager local system service interface. * @@ -37,4 +39,9 @@ public interface InputMethodManagerInternal { * Hides the current input method, if visible. */ void hideCurrentInputMethod(); + + /** + * Switches to VR InputMethod defined in the packageName of {@param componentName}. + */ + void startVrInputMethodNoCheck(ComponentName componentName); } diff --git a/core/java/com/android/internal/inputmethod/InputMethodUtils.java b/core/java/com/android/internal/inputmethod/InputMethodUtils.java index 3e231d0aaa8b..57efae61a9c6 100644 --- a/core/java/com/android/internal/inputmethod/InputMethodUtils.java +++ b/core/java/com/android/internal/inputmethod/InputMethodUtils.java @@ -836,7 +836,6 @@ public class InputMethodUtils { private final Resources mRes; private final ContentResolver mResolver; private final HashMap<String, InputMethodInfo> mMethodMap; - private final ArrayList<InputMethodInfo> mMethodList; /** * On-memory data store to emulate when {@link #mCopyOnWrite} is {@code true}. @@ -906,7 +905,6 @@ public class InputMethodUtils { mRes = res; mResolver = resolver; mMethodMap = methodMap; - mMethodList = methodList; switchCurrentUser(userId, copyOnWrite); } @@ -1087,7 +1085,7 @@ public class InputMethodUtils { final ArrayList<InputMethodInfo> res = new ArrayList<>(); for (Pair<String, ArrayList<String>> ims: imsList) { InputMethodInfo info = mMethodMap.get(ims.first); - if (info != null) { + if (info != null && !info.isVrOnly()) { res.add(info); } } diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl index b9798075ad27..ca8624d9c01e 100644 --- a/core/java/com/android/internal/view/IInputMethodManager.aidl +++ b/core/java/com/android/internal/view/IInputMethodManager.aidl @@ -36,6 +36,7 @@ import com.android.internal.view.IInputMethodClient; interface IInputMethodManager { // TODO: Use ParceledListSlice instead List<InputMethodInfo> getInputMethodList(); + List<InputMethodInfo> getVrInputMethodList(); // TODO: Use ParceledListSlice instead List<InputMethodInfo> getEnabledInputMethodList(); List<InputMethodSubtype> getEnabledInputMethodSubtypeList(in String imiId, diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java index fc57a0d58400..dc35051d2dfe 100644 --- a/services/core/java/com/android/server/InputMethodManagerService.java +++ b/services/core/java/com/android/server/InputMethodManagerService.java @@ -108,6 +108,8 @@ import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; +import android.service.vr.IVrManager; +import android.service.vr.IVrStateCallbacks; import android.text.TextUtils; import android.text.style.SuggestionSpan; import android.util.ArrayMap; @@ -189,6 +191,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub static final int MSG_CREATE_SESSION = 1050; static final int MSG_START_INPUT = 2000; + static final int MSG_START_VR_INPUT = 2010; static final int MSG_UNBIND_CLIENT = 3000; static final int MSG_BIND_CLIENT = 3010; @@ -317,6 +320,28 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } + /** + * VR state callback. + * Listens for when VR mode finishes. + */ + private final IVrStateCallbacks mVrStateCallbacks = new IVrStateCallbacks.Stub() { + @Override + public void onVrStateChanged(boolean enabled) { + if (!enabled) { + restoreNonVrImeFromSettingsNoCheck(); + } + } + }; + + private void restoreNonVrImeFromSettingsNoCheck() { + // switch back to non-VR InputMethod from settings. + synchronized (mMethodMap) { + final String lastInputId = mSettings.getSelectedInputMethod(); + setInputMethodLocked(lastInputId, + mSettings.getSelectedInputMethodSubtypeId(lastInputId)); + } + } + static final class ClientState { final IInputMethodClient client; final IInputContext inputContext; @@ -863,6 +888,30 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } /** + * Start a VR InputMethod that matches IME with package name of {@param component}. + * Note: This method is called from {@link VrManager}. + */ + private void startVrInputMethodNoCheck(@Nullable ComponentName component) { + if (component == null) { + // clear the current VR-only IME (if any) and restore normal IME. + restoreNonVrImeFromSettingsNoCheck(); + return; + } + + synchronized (mMethodMap) { + String packageName = component.getPackageName(); + for (InputMethodInfo info : mMethodList) { + if (TextUtils.equals(info.getPackageName(), packageName) && info.isVrOnly()) { + // set this is as current inputMethod without updating settings. + setInputMethodEnabled(info.getId(), true); + setInputMethodLocked(info.getId(), NOT_A_SUBTYPE_ID); + break; + } + } + } + } + + /** * Handles {@link Intent#ACTION_LOCALE_CHANGED}. * * <p>Note: For historical reasons, {@link Intent#ACTION_LOCALE_CHANGED} has been sent to all @@ -1338,6 +1387,15 @@ public class InputMethodManagerService extends IInputMethodManager.Stub mFileManager = new InputMethodFileManager(mMethodMap, userId); mSwitchingController = InputMethodSubtypeSwitchingController.createInstanceLocked( mSettings, context); + // Register VR-state listener. + IVrManager vrManager = (IVrManager) ServiceManager.getService(Context.VR_SERVICE); + if (vrManager != null) { + try { + vrManager.registerListener(mVrStateCallbacks); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to register VR mode state listener."); + } + } } private void resetDefaultImeLocked(Context context) { @@ -1562,12 +1620,27 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @Override public List<InputMethodInfo> getInputMethodList() { + return getInputMethodList(false /* isVrOnly */); + } + + public List<InputMethodInfo> getVrInputMethodList() { + return getInputMethodList(true /* isVrOnly */); + } + + private List<InputMethodInfo> getInputMethodList(final boolean isVrOnly) { // TODO: Make this work even for non-current users? if (!calledFromValidUser()) { return Collections.emptyList(); } synchronized (mMethodMap) { - return new ArrayList<>(mMethodList); + ArrayList<InputMethodInfo> methodList = new ArrayList<>(); + for (InputMethodInfo info : mMethodList) { + + if (info.isVrOnly() == isVrOnly) { + methodList.add(info); + } + } + return methodList; } } @@ -3356,6 +3429,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub case MSG_SET_INTERACTIVE: handleSetInteractive(msg.arg1 != 0); return true; + case MSG_START_VR_INPUT: + startVrInputMethodNoCheck((ComponentName) msg.obj); + return true; case MSG_SWITCH_IME: handleSwitchInputMethod(msg.arg1 != 0); return true; @@ -3876,8 +3952,12 @@ public class InputMethodManagerService extends IInputMethodManager.Stub private void setSelectedInputMethodAndSubtypeLocked(InputMethodInfo imi, int subtypeId, boolean setSubtypeOnly) { - // Update the history of InputMethod and Subtype - mSettings.saveCurrentInputMethodAndSubtypeToHistory(mCurMethodId, mCurrentSubtype); + // Updates to InputMethod are transient in VR mode. Its not included in history. + final boolean isVrInput = imi != null && imi.isVrOnly(); + if (!isVrInput) { + // Update the history of InputMethod and Subtype + mSettings.saveCurrentInputMethodAndSubtypeToHistory(mCurMethodId, mCurrentSubtype); + } mCurUserActionNotificationSequenceNumber = Math.max(mCurUserActionNotificationSequenceNumber + 1, 1); @@ -3892,6 +3972,11 @@ public class InputMethodManagerService extends IInputMethodManager.Stub mCurUserActionNotificationSequenceNumber, mCurClient)); } + if (isVrInput) { + // Updates to InputMethod are transient in VR mode. Any changes to Settings are skipped. + return; + } + // Set Subtype here if (imi == null || subtypeId < 0) { mSettings.putSelectedSubtype(NOT_A_SUBTYPE_ID); @@ -4351,6 +4436,11 @@ public class InputMethodManagerService extends IInputMethodManager.Stub mHandler.removeMessages(MSG_HIDE_CURRENT_INPUT_METHOD); mHandler.sendEmptyMessage(MSG_HIDE_CURRENT_INPUT_METHOD); } + + @Override + public void startVrInputMethodNoCheck(@Nullable ComponentName componentName) { + mHandler.sendMessage(mHandler.obtainMessage(MSG_START_VR_INPUT, componentName)); + } } private static String imeWindowStatusToString(final int imeWindowVis) { diff --git a/services/core/java/com/android/server/vr/VrManagerService.java b/services/core/java/com/android/server/vr/VrManagerService.java index 1f4e64e8b1b3..7d55b68f2c96 100644 --- a/services/core/java/com/android/server/vr/VrManagerService.java +++ b/services/core/java/com/android/server/vr/VrManagerService.java @@ -59,6 +59,7 @@ import android.util.ArraySet; import android.util.Slog; import android.util.SparseArray; import com.android.server.wm.WindowManagerInternal; +import android.view.inputmethod.InputMethodManagerInternal; import com.android.internal.R; import com.android.internal.util.DumpUtils; @@ -609,6 +610,14 @@ public class VrManagerService extends SystemService implements EnabledComponentC } @Override + public void setVrInputMethod(ComponentName componentName) { + enforceCallerPermissionAnyOf(Manifest.permission.RESTRICTED_VR_ACCESS); + InputMethodManagerInternal imm = + LocalServices.getService(InputMethodManagerInternal.class); + imm.startVrInputMethodNoCheck(componentName); + } + + @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return; |