OSDN Git Service

Add a shell command for IMMS to test instant apps
authorYohei Yukawa <yukawa@google.com>
Mon, 21 May 2018 16:47:53 +0000 (09:47 -0700)
committerYohei Yukawa <yukawa@google.com>
Mon, 21 May 2018 16:47:53 +0000 (09:47 -0700)
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 <APK>'
  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

services/core/java/com/android/server/InputMethodManagerService.java

index 5b446ca..19170f8 100644 (file)
@@ -621,6 +621,13 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
     private final int mHardKeyboardBehavior;
 
     /**
+     * Whether we temporarily allow IMEs implemented in instant apps to run for testing.
+     *
+     * <p>Note: This is quite dangerous.  Don't forget to reset after you finish testing.</p>
+     */
+    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}
      * will not affect those tasks that are already posted.
@@ -1068,7 +1075,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
                 final PackageManager pm = mContext.getPackageManager();
                 final List<ResolveInfo> 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<ResolveInfo> 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<String, List<InputMethodSubtype>> 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<ResolveInfo> 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<N; i++) {
                 InputMethodInfo info = mMethodList.get(i);
                 p.println("  InputMethod #" + i + ":");
@@ -4710,6 +4735,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
             if ("refresh_debug_properties".equals(cmd)) {
                 return refreshDebugProperties();
             }
+            if ("set-bind-instant-service-allowed".equals(cmd)) {
+                return setBindInstantServiceAllowed();
+            }
 
             // For existing "adb shell ime <command>".
             if ("ime".equals(cmd)) {
@@ -4740,6 +4768,12 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
 
         @BinderThread
         @ShellCommandResult
+        private int setBindInstantServiceAllowed() {
+            return mService.handleSetBindInstantServiceAllowed(this);
+        }
+
+        @BinderThread
+        @ShellCommandResult
         private int refreshDebugProperties() {
             DebugFlags.FLAG_OPTIMIZE_START_INPUT.refresh();
             return ShellCommandResult.SUCCESS;
@@ -4756,6 +4790,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
                 pw.println("    Synonym of dumpsys.");
                 pw.println("  ime <command> [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.");
             }
         }
 
@@ -4804,6 +4841,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.
      * @return Exit code of the command.