From f80087cae1227c48159a2a177729b14f4eb32e48 Mon Sep 17 00:00:00 2001 From: Yohei Yukawa Date: Mon, 21 May 2018 09:47:53 -0700 Subject: [PATCH] Add a shell command for IMMS to test instant apps This change adds a shell command to allow InputMethodManagerService (IMMS) to bind to InputMethodService provided by instant apps, like we did so for AccessibilityManagerService [1]. Note that binding to an InputMethodService provided by instant apps is not a supported scenario. In theory we can avoid doing this by having a separate APK for MockIme instead of putting everything into CtsInputMethodTestCases.apk but that configuration is not yet supported by our test harness hence it doesn't work right now hence having this special mode for testing is the only way we have right now. [1]: Ifced735a9a6e495747372dd8b00fdd64933a09c7 d223db316d11a625a73f86392e7055d5c6f26e7d Bug: 79484568 Test: Manually verified as follows: 1. Build a test IME that has android:targetSandboxVersion="2" in its AndroidManifest.xml. 2. Install the test IME APK with 'adb install --instant ' 3. adb shell ime list -a -s 4. Make sure that the test IME is not in the list. 5. adb shell cmd input_method set-bind-instant-service-allowed true 6. adb shell ime list -a -s 7. Make sure that the test IME is now in the list. 8. Select that test IME. 9. Make sure that the test IME is working. 10. adb shell cmd input_method set-bind-instant-service-allowed false 11. Make sure that the test IME is no longer the current IME. 12. adb shell ime list -a -s 13. Make sure that the test IME is no longer in the list. Test: cts-tradefed run cts-instant -m CtsInputMethodTestCases Change-Id: I4383129fd9e229a849282e874aff5d4eef1f49f8 --- .../server/InputMethodManagerService.java | 100 ++++++++++++++++-- 1 file changed, 92 insertions(+), 8 deletions(-) diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java index 5b446ca114596..19170f8ff5b01 100644 --- a/services/core/java/com/android/server/InputMethodManagerService.java +++ b/services/core/java/com/android/server/InputMethodManagerService.java @@ -620,6 +620,13 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @HardKeyboardBehavior private final int mHardKeyboardBehavior; + /** + * Whether we temporarily allow IMEs implemented in instant apps to run for testing. + * + *

Note: This is quite dangerous. Don't forget to reset after you finish testing.

+ */ + private boolean mBindInstantServiceAllowed = false; + /** * Internal state snapshot when {@link #MSG_START_INPUT} message is about to be posted to the * internal message queue. Any subsequent state change inside {@link InputMethodManagerService} @@ -1068,7 +1075,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub final PackageManager pm = mContext.getPackageManager(); final List services = pm.queryIntentServicesAsUser( new Intent(InputMethod.SERVICE_INTERFACE).setPackage(packageName), - PackageManager.MATCH_DISABLED_COMPONENTS, getChangingUserId()); + getComponentMatchingFlags(PackageManager.MATCH_DISABLED_COMPONENTS), + getChangingUserId()); // No need to lock this because we access it only on getRegisteredHandler(). if (!services.isEmpty()) { mImePackageAppeared = true; @@ -1612,12 +1620,16 @@ public class InputMethodManagerService extends IInputMethodManager.Stub return true; } - private boolean bindCurrentInputMethodService( + @GuardedBy("mMethodMap") + private boolean bindCurrentInputMethodServiceLocked( Intent service, ServiceConnection conn, int flags) { if (service == null || conn == null) { Slog.e(TAG, "--- bind failed: service = " + service + ", conn = " + conn); return false; } + if (mBindInstantServiceAllowed) { + flags |= Context.BIND_ALLOW_INSTANT; + } return mContext.bindServiceAsUser(service, conn, flags, new UserHandle(mSettings.getCurrentUserId())); } @@ -1960,7 +1972,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub com.android.internal.R.string.input_method_binding_label); mCurIntent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity( mContext, 0, new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS), 0)); - if (bindCurrentInputMethodService(mCurIntent, this, IME_CONNECTION_BIND_FLAGS)) { + if (bindCurrentInputMethodServiceLocked(mCurIntent, this, IME_CONNECTION_BIND_FLAGS)) { mLastBindTime = SystemClock.uptimeMillis(); mHaveConnection = true; mCurId = info.getId(); @@ -2612,7 +2624,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub resultReceiver)); mInputShown = true; if (mHaveConnection && !mVisibleBound) { - bindCurrentInputMethodService( + bindCurrentInputMethodServiceLocked( mCurIntent, mVisibleConnection, IME_VISIBLE_BIND_FLAGS); mVisibleBound = true; } @@ -2627,7 +2639,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub SystemClock.uptimeMillis()-mLastBindTime,1); Slog.w(TAG, "Force disconnect/connect to the IME in showCurrentInputLocked()"); mContext.unbindService(this); - bindCurrentInputMethodService(mCurIntent, this, IME_CONNECTION_BIND_FLAGS); + bindCurrentInputMethodServiceLocked(mCurIntent, this, IME_CONNECTION_BIND_FLAGS); } else { if (DEBUG) { Slog.d(TAG, "Can't show input: connection = " + mHaveConnection + ", time = " @@ -3590,6 +3602,16 @@ public class InputMethodManagerService extends IInputMethodManager.Stub return false; } + @PackageManager.ResolveInfoFlags + private int getComponentMatchingFlags(@PackageManager.ResolveInfoFlags int baseFlags) { + synchronized (mMethodMap) { + if (mBindInstantServiceAllowed) { + baseFlags |= PackageManager.MATCH_INSTANT; + } + return baseFlags; + } + } + @GuardedBy("mMethodMap") void buildInputMethodListLocked(boolean resetDefaultEnabledIme) { if (DEBUG) { @@ -3613,7 +3635,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub // services depending on the unlock state for the specified user. final List services = pm.queryIntentServicesAsUser( new Intent(InputMethod.SERVICE_INTERFACE), - PackageManager.GET_META_DATA | PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS, + getComponentMatchingFlags(PackageManager.GET_META_DATA + | PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS), mSettings.getCurrentUserId()); final HashMap> additionalSubtypeMap = @@ -3655,7 +3678,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub // conservative, but it seems we cannot use it for now (Issue 35176630). final List allInputMethodServices = pm.queryIntentServicesAsUser( new Intent(InputMethod.SERVICE_INTERFACE), - PackageManager.MATCH_DISABLED_COMPONENTS, mSettings.getCurrentUserId()); + getComponentMatchingFlags(PackageManager.MATCH_DISABLED_COMPONENTS), + mSettings.getCurrentUserId()); final int N = allInputMethodServices.size(); for (int i = 0; i < N; ++i) { final ServiceInfo si = allInputMethodServices.get(i).serviceInfo; @@ -4598,7 +4622,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub synchronized (mMethodMap) { p.println("Current Input Method Manager state:"); int N = mMethodList.size(); - p.println(" Input Methods: mMethodMapUpdateCount=" + mMethodMapUpdateCount); + p.println(" Input Methods: mMethodMapUpdateCount=" + mMethodMapUpdateCount + + " mBindInstantServiceAllowed=" + mBindInstantServiceAllowed); for (int i=0; i". if ("ime".equals(cmd)) { @@ -4738,6 +4766,12 @@ public class InputMethodManagerService extends IInputMethodManager.Stub return handleDefaultCommands(cmd); } + @BinderThread + @ShellCommandResult + private int setBindInstantServiceAllowed() { + return mService.handleSetBindInstantServiceAllowed(this); + } + @BinderThread @ShellCommandResult private int refreshDebugProperties() { @@ -4756,6 +4790,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub pw.println(" Synonym of dumpsys."); pw.println(" ime [options]"); pw.println(" Manipulate IMEs. Run \"ime help\" for details."); + pw.println(" set-bind-instant-service-allowed true|false "); + pw.println(" Set whether binding to services provided by instant apps is " + + "allowed."); } } @@ -4803,6 +4840,53 @@ public class InputMethodManagerService extends IInputMethodManager.Stub // ---------------------------------------------------------------------- // Shell command handlers: + /** + * Handles {@code adb shell cmd input_method set-bind-instant-service-allowed}. + * + * @param shellCommand {@link ShellCommand} object that is handling this command. + * @return Exit code of the command. + */ + @BinderThread + @RequiresPermission(android.Manifest.permission.MANAGE_BIND_INSTANT_SERVICE) + @ShellCommandResult + private int handleSetBindInstantServiceAllowed(@NonNull ShellCommand shellCommand) { + final String allowedString = shellCommand.getNextArgRequired(); + if (allowedString == null) { + shellCommand.getErrPrintWriter().println("Error: no true/false specified"); + return ShellCommandResult.FAILURE; + } + final boolean allowed = Boolean.parseBoolean(allowedString); + synchronized (mMethodMap) { + if (mContext.checkCallingOrSelfPermission( + android.Manifest.permission.MANAGE_BIND_INSTANT_SERVICE) + != PackageManager.PERMISSION_GRANTED) { + shellCommand.getErrPrintWriter().print( + "Caller must have MANAGE_BIND_INSTANT_SERVICE permission"); + return ShellCommandResult.FAILURE; + } + + if (mBindInstantServiceAllowed == allowed) { + // Nothing to do. + return ShellCommandResult.SUCCESS; + } + mBindInstantServiceAllowed = allowed; + + // Rebuild everything. + final long ident = Binder.clearCallingIdentity(); + try { + // Reset the current IME + resetSelectedInputMethodAndSubtypeLocked(null); + // Also reset the settings of the current IME + mSettings.putSelectedInputMethod(null); + buildInputMethodListLocked(false /* resetDefaultEnabledIme */); + updateInputMethodsFromSettingsLocked(true /* enabledMayChange */); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + return ShellCommandResult.SUCCESS; + } + /** * Handles {@code adb shell ime list}. * @param shellCommand {@link ShellCommand} object that is handling this command.