diff options
author | Yohei Yukawa <yukawa@google.com> | 2017-12-11 17:24:55 -0800 |
---|---|---|
committer | Yohei Yukawa <yukawa@google.com> | 2017-12-11 17:24:55 -0800 |
commit | 926488d70d09baefee0489537b2915602deaeebf (patch) | |
tree | 548bc371294ce32cb39d83159f4b98dfee983440 | |
parent | 7a46c28d4571e037e26a28ea8e2a01312d916d47 (diff) |
Use IBinder#shellCommand() for 'adb shell ime'
This is a preparation CL to add a new command to 'adb shell ime'.
Currently 'ime' command is written in Java language that relies directly
on the internal Binder IPC interface IInputMethodManager.
This is not ideal because:
1. We have to keep maintaining IInputMethodManager methods used
only by the 'ime' command.
2. Adding new options to the 'ime' command is tedious when it
requires new methods in IInputMethodManager.
With this CL, all features of 'ime' command are re-implemented inside
InputMethodManagerService (IMMS) on top of Binder's "shell command"
feature [1]. Like 'am' command was gone recently [2], now 'ime' command
is also a simple shell wrapper to forward options to 'cmd input_method',
which allows us to 1) reduce the code duplication and 2) give non-zero
status code when the command fails with Java exception.
[1]: I76518ea6719d1d08a8ad8722a059c7f5fd86813a
9461b6f91f37fd32207da1bd734d9ea9629eb8e5
[2]: Ia8187196af597046fd2e7092dbf19ce1dc1ea457
1704e3cf0c445512f0a9644485dd3449e874556b
Bug: 70475949
Test: adb shell ime
Test: adb shell ime help
Test: adb shell ime dump
Test: adb shell ime list -a
Test: adb shell cmd input_method
Test: adb shell cmd input_method help
Test: adb shell cmd input_method dump
Test: adb shell cmd input_method list -a
Change-Id: I9a2dbbf1d4494addbe22c82e2c416eedc4d585f2
-rw-r--r-- | cmds/ime/Android.mk | 7 | ||||
-rwxr-xr-x | cmds/ime/ime | 8 | ||||
-rw-r--r-- | cmds/ime/src/com/android/commands/ime/Ime.java | 248 | ||||
-rw-r--r-- | core/java/com/android/internal/view/IInputMethodManager.aidl | 1 | ||||
-rw-r--r-- | services/core/java/com/android/server/InputMethodManagerService.java | 203 |
5 files changed, 180 insertions, 287 deletions
diff --git a/cmds/ime/Android.mk b/cmds/ime/Android.mk index 6803fc01154c..ca608e8c2c34 100644 --- a/cmds/ime/Android.mk +++ b/cmds/ime/Android.mk @@ -3,14 +3,7 @@ LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) -LOCAL_SRC_FILES := $(call all-subdir-java-files) -LOCAL_MODULE := imelib -LOCAL_MODULE_STEM := ime -include $(BUILD_JAVA_LIBRARY) - -include $(CLEAR_VARS) LOCAL_MODULE := ime LOCAL_MODULE_CLASS := EXECUTABLES LOCAL_SRC_FILES := ime -LOCAL_REQUIRED_MODULES := imelib include $(BUILD_PREBUILT) diff --git a/cmds/ime/ime b/cmds/ime/ime index 1a1fdd96da6e..180dc761055b 100755 --- a/cmds/ime/ime +++ b/cmds/ime/ime @@ -1,8 +1,2 @@ #!/system/bin/sh -# Script to start "pm" on the device, which has a very rudimentary -# shell. -# -base=/system -export CLASSPATH=$base/framework/ime.jar -exec app_process $base/bin com.android.commands.ime.Ime "$@" - +exec cmd input_method "$@" diff --git a/cmds/ime/src/com/android/commands/ime/Ime.java b/cmds/ime/src/com/android/commands/ime/Ime.java deleted file mode 100644 index 72a0af6c2d99..000000000000 --- a/cmds/ime/src/com/android/commands/ime/Ime.java +++ /dev/null @@ -1,248 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.commands.ime; - -import com.android.internal.view.IInputMethodManager; - -import android.os.RemoteException; -import android.os.ServiceManager; -import android.util.PrintStreamPrinter; -import android.util.Printer; -import android.view.inputmethod.InputMethodInfo; - -import java.util.List; - -public final class Ime { - IInputMethodManager mImm; - - private String[] mArgs; - private int mNextArg; - private String mCurArgData; - - private static final String IMM_NOT_RUNNING_ERR = - "Error: Could not access the Input Method Manager. Is the system running?"; - - public static void main(String[] args) { - new Ime().run(args); - } - - public void run(String[] args) { - if (args.length < 1) { - showUsage(); - return; - } - - mImm = IInputMethodManager.Stub.asInterface(ServiceManager.getService("input_method")); - if (mImm == null) { - System.err.println(IMM_NOT_RUNNING_ERR); - return; - } - - mArgs = args; - String op = args[0]; - mNextArg = 1; - - if ("list".equals(op)) { - runList(); - return; - } - - if ("enable".equals(op)) { - runSetEnabled(true); - return; - } - - if ("disable".equals(op)) { - runSetEnabled(false); - return; - } - - if ("set".equals(op)) { - runSet(); - return; - } - - if (op != null) { - System.err.println("Error: unknown command '" + op + "'"); - } - showUsage(); - } - - /** - * Execute the list sub-command. - */ - private void runList() { - String opt; - boolean all = false; - boolean brief = false; - while ((opt=nextOption()) != null) { - if (opt.equals("-a")) { - all = true; - } else if (opt.equals("-s")) { - brief = true; - } else { - System.err.println("Error: Unknown option: " + opt); - showUsage(); - return; - } - } - - - List<InputMethodInfo> methods; - if (!all) { - try { - methods = mImm.getEnabledInputMethodList(); - } catch (RemoteException e) { - System.err.println(e.toString()); - System.err.println(IMM_NOT_RUNNING_ERR); - return; - } - } else { - try { - methods = mImm.getInputMethodList(); - } catch (RemoteException e) { - System.err.println(e.toString()); - System.err.println(IMM_NOT_RUNNING_ERR); - return; - } - } - - if (methods != null) { - Printer pr = new PrintStreamPrinter(System.out); - for (int i=0; i<methods.size(); i++) { - InputMethodInfo imi = methods.get(i); - if (brief) { - System.out.println(imi.getId()); - } else { - System.out.println(imi.getId() + ":"); - imi.dump(pr, " "); - } - } - } - } - - private void runSetEnabled(boolean state) { - String id = nextArg(); - if (id == null) { - System.err.println("Error: no input method ID specified"); - showUsage(); - return; - } - - try { - boolean res = mImm.setInputMethodEnabled(id, state); - if (state) { - System.out.println("Input method " + id + ": " - + (res ? "already enabled" : "now enabled")); - } else { - System.out.println("Input method " + id + ": " - + (res ? "now disabled" : "already disabled")); - } - } catch (IllegalArgumentException e) { - System.err.println("Error: " + e.getMessage()); - return; - } catch (RemoteException e) { - System.err.println(e.toString()); - System.err.println(IMM_NOT_RUNNING_ERR); - return; - } - } - - private void runSet() { - String id = nextArg(); - if (id == null) { - System.err.println("Error: no input method ID specified"); - showUsage(); - return; - } - - try { - mImm.setInputMethod(null, id); - System.out.println("Input method " + id + " selected"); - } catch (IllegalArgumentException e) { - System.err.println("Error: " + e.getMessage()); - return; - } catch (RemoteException e) { - System.err.println(e.toString()); - System.err.println(IMM_NOT_RUNNING_ERR); - return; - } - } - - private String nextOption() { - if (mNextArg >= mArgs.length) { - return null; - } - String arg = mArgs[mNextArg]; - if (!arg.startsWith("-")) { - return null; - } - mNextArg++; - if (arg.equals("--")) { - return null; - } - if (arg.length() > 1 && arg.charAt(1) != '-') { - if (arg.length() > 2) { - mCurArgData = arg.substring(2); - return arg.substring(0, 2); - } else { - mCurArgData = null; - return arg; - } - } - mCurArgData = null; - return arg; - } - - private String nextOptionData() { - if (mCurArgData != null) { - return mCurArgData; - } - if (mNextArg >= mArgs.length) { - return null; - } - String data = mArgs[mNextArg]; - mNextArg++; - return data; - } - - private String nextArg() { - if (mNextArg >= mArgs.length) { - return null; - } - String arg = mArgs[mNextArg]; - mNextArg++; - return arg; - } - - private static void showUsage() { - System.err.println("usage: ime list [-a] [-s]"); - System.err.println(" ime enable ID"); - System.err.println(" ime disable ID"); - System.err.println(" ime set ID"); - System.err.println(""); - System.err.println("The list command prints all enabled input methods. Use"); - System.err.println("the -a option to see all input methods. Use"); - System.err.println("the -s option to see only a single summary line of each."); - System.err.println(""); - System.err.println("The enable command allows the given input method ID to be used."); - System.err.println(""); - System.err.println("The disable command disallows the given input method ID from use."); - System.err.println(""); - System.err.println("The set command switches to the given input method ID."); - } -} diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl index ca8624d9c01e..1fd5564773b1 100644 --- a/core/java/com/android/internal/view/IInputMethodManager.aidl +++ b/core/java/com/android/internal/view/IInputMethodManager.aidl @@ -80,7 +80,6 @@ interface IInputMethodManager { boolean switchToLastInputMethod(in IBinder token); boolean switchToNextInputMethod(in IBinder token, boolean onlyCurrentIme); boolean shouldOfferSwitchingToNextInputMethod(in IBinder token); - boolean setInputMethodEnabled(String id, boolean enabled); void setAdditionalInputMethodSubtypes(String id, in InputMethodSubtype[] subtypes); int getInputMethodWindowVisibleHeight(); void clearLastInputMethodWindowForTransition(in IBinder token); diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java index 37f1dc490fe3..e1d6b5da0db8 100644 --- a/services/core/java/com/android/server/InputMethodManagerService.java +++ b/services/core/java/com/android/server/InputMethodManagerService.java @@ -49,12 +49,14 @@ import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; +import android.Manifest; import android.annotation.BinderThread; import android.annotation.ColorInt; import android.annotation.IntDef; import android.annotation.MainThread; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.RequiresPermission; import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.ActivityManagerInternal; @@ -104,6 +106,8 @@ import android.os.Process; import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ServiceManager; +import android.os.ShellCallback; +import android.os.ShellCommand; import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; @@ -178,6 +182,13 @@ public class InputMethodManagerService extends IInputMethodManager.Stub static final boolean DEBUG_RESTORE = DEBUG || false; static final String TAG = "InputMethodManagerService"; + @Retention(SOURCE) + @IntDef({ShellCommandResult.SUCCESS, ShellCommandResult.FAILURE}) + private @interface ShellCommandResult { + int SUCCESS = 0; + int FAILURE = -1; + } + static final int MSG_SHOW_IM_SUBTYPE_PICKER = 1; static final int MSG_SHOW_IM_SUBTYPE_ENABLER = 2; static final int MSG_SHOW_IM_CONFIG = 3; @@ -3885,30 +3896,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub // ---------------------------------------------------------------------- - @Override - public boolean setInputMethodEnabled(String id, boolean enabled) { - // TODO: Make this work even for non-current users? - if (!calledFromValidUser()) { - return false; - } - synchronized (mMethodMap) { - if (mContext.checkCallingOrSelfPermission( - android.Manifest.permission.WRITE_SECURE_SETTINGS) - != PackageManager.PERMISSION_GRANTED) { - throw new SecurityException( - "Requires permission " - + android.Manifest.permission.WRITE_SECURE_SETTINGS); - } - - long ident = Binder.clearCallingIdentity(); - try { - return setInputMethodEnabledLocked(id, enabled); - } finally { - Binder.restoreCallingIdentity(ident); - } - } - } - boolean setInputMethodEnabledLocked(String id, boolean enabled) { // Make sure this is a valid input method. InputMethodInfo imm = mMethodMap.get(id); @@ -4633,4 +4620,172 @@ public class InputMethodManagerService extends IInputMethodManager.Stub p.println("No input method service."); } } + + @BinderThread + @Override + public void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out, + @Nullable FileDescriptor err, + @NonNull String[] args, @Nullable ShellCallback callback, + @NonNull ResultReceiver resultReceiver) throws RemoteException { + new ShellCommandImpl(this).exec( + this, in, out, err, args, callback, resultReceiver); + } + + private static final class ShellCommandImpl extends ShellCommand { + @NonNull + final InputMethodManagerService mService; + + ShellCommandImpl(InputMethodManagerService service) { + mService = service; + } + + @BinderThread + @ShellCommandResult + @Override + public int onCommand(@Nullable String cmd) { + if (cmd == null) { + return handleDefaultCommands(cmd); + } + switch (cmd) { + case "list": + return mService.handleShellCommandListInputMethods(this); + case "enable": + return mService.handleShellCommandEnableDisableInputMethod(this, true); + case "disable": + return mService.handleShellCommandEnableDisableInputMethod(this, false); + case "set": + return mService.handleShellCommandSetInputMethod(this); + default: + return handleDefaultCommands(cmd); + } + } + + @BinderThread + @Override + public void onHelp() { + try (PrintWriter pw = getOutPrintWriter()) { + pw.println("InputMethodManagerService commands:"); + pw.println(" help"); + pw.println(" Prints this help text."); + pw.println(" dump [options]"); + pw.println(" Synonym of dumpsys."); + pw.println(" list [-a] [-s]"); + pw.println(" prints all enabled input methods."); + pw.println(" -a: see all input methods"); + pw.println(" -s: only a single summary line of each"); + pw.println(" enable <ID>"); + pw.println(" allows the given input method ID to be used."); + pw.println(" disable <ID>"); + pw.println(" disallows the given input method ID to be used."); + pw.println(" set <ID>"); + pw.println(" switches to the given input method ID."); + } + } + } + + // ---------------------------------------------------------------------- + // Shell command handlers: + + /** + * Handles {@code adb shell ime list}. + * @param shellCommand {@link ShellCommand} object that is handling this command. + * @return Exit code of the command. + */ + @BinderThread + @ShellCommandResult + private int handleShellCommandListInputMethods(@NonNull ShellCommand shellCommand) { + boolean all = false; + boolean brief = false; + while (true) { + final String nextOption = shellCommand.getNextOption(); + if (nextOption == null) { + break; + } + switch (nextOption) { + case "-a": + all = true; + break; + case "-s": + brief = true; + break; + } + } + final List<InputMethodInfo> methods = all ? + getInputMethodList() : getEnabledInputMethodList(); + final PrintWriter pr = shellCommand.getOutPrintWriter(); + final Printer printer = x -> pr.println(x); + final int N = methods.size(); + for (int i = 0; i < N; ++i) { + if (brief) { + pr.println(methods.get(i).getId()); + } else { + pr.print(methods.get(i).getId()); pr.println(":"); + methods.get(i).dump(printer, " "); + } + } + return ShellCommandResult.SUCCESS; + } + + /** + * Handles {@code adb shell ime enable} and {@code adb shell ime disable}. + * @param shellCommand {@link ShellCommand} object that is handling this command. + * @param enabled {@code true} if the command was {@code adb shell ime enable}. + * @return Exit code of the command. + */ + @BinderThread + @ShellCommandResult + @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS) + private int handleShellCommandEnableDisableInputMethod( + @NonNull ShellCommand shellCommand, boolean enabled) { + if (!calledFromValidUser()) { + shellCommand.getErrPrintWriter().print( + "Must be called from the foreground user or with INTERACT_ACROSS_USERS_FULL"); + return ShellCommandResult.FAILURE; + } + final String id = shellCommand.getNextArgRequired(); + + final boolean previouslyEnabled; + synchronized (mMethodMap) { + if (mContext.checkCallingOrSelfPermission( + android.Manifest.permission.WRITE_SECURE_SETTINGS) + != PackageManager.PERMISSION_GRANTED) { + shellCommand.getErrPrintWriter().print( + "Caller must have WRITE_SECURE_SETTINGS permission"); + throw new SecurityException( + "Requires permission " + + android.Manifest.permission.WRITE_SECURE_SETTINGS); + } + + final long ident = Binder.clearCallingIdentity(); + try { + previouslyEnabled = setInputMethodEnabledLocked(id, enabled); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + final PrintWriter pr = shellCommand.getOutPrintWriter(); + pr.print("Input method "); + pr.print(id); + pr.print(": "); + pr.print((enabled == previouslyEnabled) ? "already " : "now "); + pr.println(enabled ? "enabled" : "disabled"); + return ShellCommandResult.SUCCESS; + } + + /** + * Handles {@code adb shell ime set}. + * @param shellCommand {@link ShellCommand} object that is handling this command. + * @return Exit code of the command. + */ + @BinderThread + @ShellCommandResult + private int handleShellCommandSetInputMethod(@NonNull ShellCommand shellCommand) { + final String id = shellCommand.getNextArgRequired(); + setInputMethod(null, id); + final PrintWriter pr = shellCommand.getOutPrintWriter(); + pr.print("Input method "); + pr.print(id); + pr.println(" selected"); + return ShellCommandResult.SUCCESS; + } } |