OSDN Git Service

Switch and store keyboard layouts based on IME subtype.
authorMichael Wright <michaelwr@google.com>
Tue, 19 Jan 2016 19:23:51 +0000 (11:23 -0800)
committerMichael Wright <michaelwr@google.com>
Thu, 21 Jan 2016 22:03:43 +0000 (14:03 -0800)
Rather than associate the keyboard layout solely with a specific
hardware model, we should also associate it with a given IME subtype.
This lets users switch between various languages and have the
keyboard change in unison with them so they can use the appropriate
layouts for each language.

This change adds initial support for associating IME subtypes and
keyboard layouts. We still need to:
  - Remove support for the old style of layout association once the
    Settings apps begins to use the new APIs
  - Automatically select an appropriate layout based on the given
    subtype (or set a reasonable universal default such as QWERTY)

Bug: 25752812

Change-Id: Ie88ce1ab77dbfe03ab51d89c1dc9e0a7ddbb3216

core/java/android/hardware/input/IInputManager.aidl
core/java/android/hardware/input/InputManager.java
core/java/android/hardware/input/TouchCalibration.java
core/java/android/view/WindowManagerPolicy.java
core/java/com/android/internal/inputmethod/InputMethodSubtypeHandle.java [new file with mode: 0644]
services/core/java/com/android/server/input/InputManagerService.java
services/core/java/com/android/server/input/PersistentDataStore.java
services/core/java/com/android/server/policy/PhoneWindowManager.java
services/core/java/com/android/server/wm/WindowManagerService.java

index 5d969b1..c4ee347 100644 (file)
@@ -25,6 +25,8 @@ import android.os.IBinder;
 import android.view.InputDevice;
 import android.view.InputEvent;
 import android.view.PointerIcon;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodSubtype;
 
 /** @hide */
 interface IInputManager {
@@ -59,6 +61,11 @@ interface IInputManager {
             String keyboardLayoutDescriptor);
     void removeKeyboardLayoutForInputDevice(in InputDeviceIdentifier identifier,
             String keyboardLayoutDescriptor);
+    KeyboardLayout getKeyboardLayoutForInputDevice(in InputDeviceIdentifier identifier,
+            in InputMethodInfo imeInfo, in InputMethodSubtype imeSubtype);
+    void setKeyboardLayoutForInputDevice(in InputDeviceIdentifier identifier,
+            in InputMethodInfo imeInfo, in InputMethodSubtype imeSubtype,
+            String keyboardLayoutDescriptor);
 
     // Registers an input devices changed listener.
     void registerInputDevicesChangedListener(IInputDevicesChangedListener listener);
index 9972f49..cbe3412 100644 (file)
@@ -16,7 +16,7 @@
 
 package android.hardware.input;
 
-import android.view.PointerIcon;
+import com.android.internal.inputmethod.InputMethodSubtypeHandle;
 import com.android.internal.os.SomeArgs;
 import com.android.internal.util.ArrayUtils;
 
@@ -40,6 +40,9 @@ import android.util.Log;
 import android.util.SparseArray;
 import android.view.InputDevice;
 import android.view.InputEvent;
+import android.view.PointerIcon;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodSubtype;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -643,6 +646,51 @@ public final class InputManager {
         }
     }
 
+
+    /**
+     * Gets the keyboard layout for the specified input device and IME subtype.
+     *
+     * @param identifier The identifier for the input device.
+     * @param inputMethodInfo The input method.
+     * @param inputMethodSubtype The input method subtype.
+     *
+     * @return The associated {@link KeyboardLayout}, or null if one has not been set.
+     *
+     * @hide
+     */
+    public KeyboardLayout getKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
+            InputMethodInfo inputMethodInfo, InputMethodSubtype inputMethodSubtype) {
+        try {
+            return mIm.getKeyboardLayoutForInputDevice(
+                    identifier, inputMethodInfo, inputMethodSubtype);
+        } catch (RemoteException ex) {
+            Log.w(TAG, "Could not set keyboard layout.", ex);
+            return null;
+        }
+    }
+
+    /**
+     * Sets the keyboard layout for the specified input device and IME subtype pair.
+     *
+     * @param identifier The identifier for the input device.
+     * @param inputMethodInfo The input method with which to associate the keyboard layout.
+     * @param inputMethodSubtype The input method subtype which which to associate the keyboard
+     *                           layout.
+     * @param keyboardLayoutDescriptor The descriptor of the keyboard layout to set
+     *
+     * @hide
+     */
+    public void setKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
+            InputMethodInfo inputMethodInfo, InputMethodSubtype inputMethodSubtype,
+            String keyboardLayoutDescriptor) {
+        try {
+            mIm.setKeyboardLayoutForInputDevice(identifier, inputMethodInfo,
+                    inputMethodSubtype, keyboardLayoutDescriptor);
+        } catch (RemoteException ex) {
+            Log.w(TAG, "Could not set keyboard layout.", ex);
+        }
+    }
+
     /**
      * Gets the TouchCalibration applied to the specified input device's coordinates.
      *
index 025fad0..15503ed 100644 (file)
@@ -123,4 +123,10 @@ public class TouchCalibration implements Parcelable {
                Float.floatToIntBits(mYScale)  ^
                Float.floatToIntBits(mYOffset);
     }
+
+    @Override
+    public String toString() {
+        return String.format("[%f, %f, %f, %f, %f, %f]",
+                mXScale, mXYMix, mXOffset, mYXMix, mYScale, mYOffset);
+    }
 }
index 4a1142f..6e38b32 100644 (file)
@@ -441,12 +441,6 @@ public interface WindowManagerPolicy {
         public int getCameraLensCoverState();
 
         /**
-         * Switch the keyboard layout for the given device.
-         * Direction should be +1 or -1 to go to the next or previous keyboard layout.
-         */
-        public void switchKeyboardLayout(int deviceId, int direction);
-
-        /**
          * Switch the input method, to be precise, input method subtype.
          *
          * @param forwardDirection {@code true} to rotate in a forward direction.
diff --git a/core/java/com/android/internal/inputmethod/InputMethodSubtypeHandle.java b/core/java/com/android/internal/inputmethod/InputMethodSubtypeHandle.java
new file mode 100644 (file)
index 0000000..975021e
--- /dev/null
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2016 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.internal.inputmethod;
+
+import android.text.TextUtils;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodSubtype;
+
+import java.util.Objects;
+
+public class InputMethodSubtypeHandle {
+    private final String mInputMethodId;
+    private final int mSubtypeId;
+
+    public InputMethodSubtypeHandle(InputMethodInfo info, InputMethodSubtype subtype) {
+        mInputMethodId = info.getId();
+        if (subtype != null) {
+            mSubtypeId = subtype.hashCode();
+        } else {
+            mSubtypeId = 0;
+        }
+    }
+
+    public InputMethodSubtypeHandle(String inputMethodId, int subtypeId) {
+        mInputMethodId = inputMethodId;
+        mSubtypeId = subtypeId;
+    }
+
+    public String getInputMethodId() {
+        return mInputMethodId;
+    }
+
+    public int getSubtypeId() {
+        return mSubtypeId;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o == null || !(o instanceof InputMethodSubtypeHandle)) {
+            return false;
+        }
+        InputMethodSubtypeHandle other = (InputMethodSubtypeHandle) o;
+        return TextUtils.equals(mInputMethodId, other.getInputMethodId())
+                && mSubtypeId == other.getSubtypeId();
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(mInputMethodId) * 31 + mSubtypeId;
+    }
+
+    @Override
+    public String toString() {
+        return "InputMethodSubtypeHandle{mInputMethodId=" + mInputMethodId
+            + ", mSubtypeId=" + mSubtypeId + "}";
+    }
+}
index ee91b63..573afd6 100644 (file)
@@ -18,6 +18,7 @@ package com.android.server.input;
 
 import android.annotation.Nullable;
 import android.view.Display;
+import com.android.internal.inputmethod.InputMethodSubtypeHandle;
 import com.android.internal.os.SomeArgs;
 import com.android.internal.R;
 import com.android.internal.util.XmlUtils;
@@ -67,6 +68,8 @@ import android.os.Message;
 import android.os.MessageQueue;
 import android.os.Process;
 import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.os.ShellCommand;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.provider.Settings.SettingNotFoundException;
@@ -97,6 +100,7 @@ import java.io.IOException;
 import java.io.InputStreamReader;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -157,8 +161,7 @@ public class InputManagerService extends IInputManager.Stub
     private final ArrayList<InputDevice>
             mTempFullKeyboards = new ArrayList<InputDevice>(); // handler thread only
     private boolean mKeyboardLayoutNotificationShown;
-    private PendingIntent mKeyboardLayoutIntent;
-    private Toast mSwitchedKeyboardLayoutToast;
+    private InputMethodSubtypeHandle mCurrentImeHandle;
 
     // State for vibrator tokens.
     private Object mVibratorLock = new Object();
@@ -1297,6 +1300,82 @@ public class InputManagerService extends IInputManager.Stub
     }
 
     @Override // Binder call
+    @Nullable
+    public KeyboardLayout getKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
+            InputMethodInfo imeInfo, InputMethodSubtype imeSubtype) {
+        InputMethodSubtypeHandle handle = new InputMethodSubtypeHandle(imeInfo, imeSubtype);
+        String key = getLayoutDescriptor(identifier);
+        final String keyboardLayoutDescriptor;
+        synchronized (mDataStore) {
+            keyboardLayoutDescriptor = mDataStore.getKeyboardLayout(key, handle);
+        }
+
+        if (keyboardLayoutDescriptor == null) {
+            return null;
+        }
+
+        final KeyboardLayout[] result = new KeyboardLayout[1];
+        visitKeyboardLayout(keyboardLayoutDescriptor, new KeyboardLayoutVisitor() {
+            @Override
+            public void visitKeyboardLayout(Resources resources,
+                    int keyboardLayoutResId, KeyboardLayout layout) {
+                result[0] = layout;
+            }
+        });
+        if (result[0] == null) {
+            Slog.w(TAG, "Could not get keyboard layout with descriptor '"
+                    + keyboardLayoutDescriptor + "'.");
+        }
+        return result[0];
+    }
+
+    @Override
+    public void setKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
+            InputMethodInfo imeInfo, InputMethodSubtype imeSubtype,
+            String keyboardLayoutDescriptor) {
+        if (!checkCallingPermission(android.Manifest.permission.SET_KEYBOARD_LAYOUT,
+                "setKeyboardLayoutForInputDevice()")) {
+            throw new SecurityException("Requires SET_KEYBOARD_LAYOUT permission");
+        }
+        if (keyboardLayoutDescriptor == null) {
+            throw new IllegalArgumentException("keyboardLayoutDescriptor must not be null");
+        }
+        if (imeInfo == null || imeSubtype == null) {
+            throw new IllegalArgumentException("imeInfo and imeSubtype must not be null");
+        }
+        InputMethodSubtypeHandle handle = new InputMethodSubtypeHandle(imeInfo, imeSubtype);
+        setKeyboardLayoutForInputDeviceInner(identifier, handle, keyboardLayoutDescriptor);
+    }
+
+    private void setKeyboardLayoutForInputDeviceInner(InputDeviceIdentifier identifier,
+            InputMethodSubtypeHandle imeHandle, String keyboardLayoutDescriptor) {
+        String key = getLayoutDescriptor(identifier);
+        synchronized (mDataStore) {
+            try {
+                if (mDataStore.setKeyboardLayout(key, imeHandle, keyboardLayoutDescriptor)) {
+                    if (DEBUG) {
+                        Slog.d(TAG, "Set keyboard layout " + keyboardLayoutDescriptor +
+                                " for subtype " + imeHandle + " and device " + identifier +
+                                " using key " + key);
+                    }
+                    if (imeHandle.equals(mCurrentImeHandle)) {
+                        if (DEBUG) {
+                            Slog.d(TAG, "Layout for current subtype changed, switching layout");
+                        }
+                        SomeArgs args = SomeArgs.obtain();
+                        args.arg1 = identifier;
+                        args.arg2 = imeHandle;
+                        mHandler.obtainMessage(MSG_SWITCH_KEYBOARD_LAYOUT, args).sendToTarget();
+                    }
+                    mHandler.sendEmptyMessage(MSG_RELOAD_KEYBOARD_LAYOUTS);
+                }
+            } finally {
+                mDataStore.saveIfNeeded();
+            }
+        }
+    }
+
+    @Override // Binder call
     public void addKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
             String keyboardLayoutDescriptor) {
         if (!checkCallingPermission(android.Manifest.permission.SET_KEYBOARD_LAYOUT,
@@ -1315,8 +1394,7 @@ public class InputManagerService extends IInputManager.Stub
                     oldLayout = mDataStore.getCurrentKeyboardLayout(identifier.getDescriptor());
                 }
                 if (mDataStore.addKeyboardLayout(key, keyboardLayoutDescriptor)
-                        && !Objects.equal(oldLayout,
-                                mDataStore.getCurrentKeyboardLayout(key))) {
+                        && !Objects.equal(oldLayout, mDataStore.getCurrentKeyboardLayout(key))) {
                     mHandler.sendEmptyMessage(MSG_RELOAD_KEYBOARD_LAYOUTS);
                 }
             } finally {
@@ -1366,45 +1444,44 @@ public class InputManagerService extends IInputManager.Stub
             Slog.i(TAG, "InputMethodSubtype changed: userId=" + userId
                     + " ime=" + inputMethodInfo + " subtype=" + subtype);
         }
-    }
-
-    public void switchKeyboardLayout(int deviceId, int direction) {
-        mHandler.obtainMessage(MSG_SWITCH_KEYBOARD_LAYOUT, deviceId, direction).sendToTarget();
+        if (inputMethodInfo == null) {
+            Slog.d(TAG, "No InputMethod is running, ignoring change");
+            return;
+        }
+        if (subtype != null && !"keyboard".equals(subtype.getMode())) {
+            Slog.d(TAG, "InputMethodSubtype changed to non-keyboard subtype, ignoring change");
+            return;
+        }
+        InputMethodSubtypeHandle handle = new InputMethodSubtypeHandle(inputMethodInfo, subtype);
+        if (!handle.equals(mCurrentImeHandle)) {
+            mCurrentImeHandle = handle;
+            handleSwitchKeyboardLayout(null, handle);
+        }
     }
 
     // Must be called on handler.
-    private void handleSwitchKeyboardLayout(int deviceId, int direction) {
-        final InputDevice device = getInputDevice(deviceId);
-        if (device != null) {
-            final boolean changed;
-            final String keyboardLayoutDescriptor;
-
-            String key = getLayoutDescriptor(device.getIdentifier());
-            synchronized (mDataStore) {
-                try {
-                    changed = mDataStore.switchKeyboardLayout(key, direction);
-                    keyboardLayoutDescriptor = mDataStore.getCurrentKeyboardLayout(
-                            key);
-                } finally {
-                    mDataStore.saveIfNeeded();
-                }
-            }
-
-            if (changed) {
-                if (mSwitchedKeyboardLayoutToast != null) {
-                    mSwitchedKeyboardLayoutToast.cancel();
-                    mSwitchedKeyboardLayoutToast = null;
+    private void handleSwitchKeyboardLayout(@Nullable InputDeviceIdentifier identifier,
+            InputMethodSubtypeHandle handle) {
+        synchronized (mInputDevicesLock) {
+            for (InputDevice device : mInputDevices) {
+                if (identifier != null && !device.getIdentifier().equals(identifier) ||
+                        !device.isFullKeyboard()) {
+                    continue;
                 }
-                if (keyboardLayoutDescriptor != null) {
-                    KeyboardLayout keyboardLayout = getKeyboardLayout(keyboardLayoutDescriptor);
-                    if (keyboardLayout != null) {
-                        mSwitchedKeyboardLayoutToast = Toast.makeText(
-                                mContext, keyboardLayout.getLabel(), Toast.LENGTH_SHORT);
-                        mSwitchedKeyboardLayoutToast.show();
+                String key = getLayoutDescriptor(device.getIdentifier());
+                boolean changed = false;
+                synchronized (mDataStore) {
+                    try {
+                        if (mDataStore.switchKeyboardLayout(key, handle)) {
+                            changed = true;
+                        }
+                    } finally {
+                        mDataStore.saveIfNeeded();
                     }
                 }
-
-                reloadKeyboardLayouts();
+                if (changed) {
+                    reloadKeyboardLayouts();
+                }
             }
         }
     }
@@ -1616,7 +1693,7 @@ public class InputManagerService extends IInputManager.Stub
     }
 
     @Override
-    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+    public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
         if (mContext.checkCallingOrSelfPermission(Manifest.permission.DUMP)
                 != PackageManager.PERMISSION_GRANTED) {
             pw.println("Permission Denial: can't dump InputManager from from pid="
@@ -1630,8 +1707,48 @@ public class InputManagerService extends IInputManager.Stub
         if (dumpStr != null) {
             pw.println(dumpStr);
         }
+        pw.println("  Keyboard Layouts:");
+        visitAllKeyboardLayouts(new KeyboardLayoutVisitor() {
+            @Override
+            public void visitKeyboardLayout(Resources resources,
+                    int keyboardLayoutResId, KeyboardLayout layout) {
+                pw.println("    \"" + layout + "\": " + layout.getDescriptor());
+            }
+        });
+        pw.println();
+        synchronized(mDataStore) {
+            mDataStore.dump(pw, "  ");
+        }
+    }
+
+    @Override
+    public void onShellCommand(FileDescriptor in, FileDescriptor out,
+            FileDescriptor err, String[] args, ResultReceiver resultReceiver) {
+        (new Shell()).exec(this, in, out, err, args, resultReceiver);
+    }
+
+    public int onShellCommand(Shell shell, String cmd) {
+        if (TextUtils.isEmpty(cmd)) {
+            shell.onHelp();
+            return 1;
+        }
+        if (cmd.equals("setlayout")) {
+            if (!checkCallingPermission(android.Manifest.permission.SET_KEYBOARD_LAYOUT,
+                    "onShellCommand()")) {
+                throw new SecurityException("Requires SET_KEYBOARD_LAYOUT permission");
+            }
+            InputMethodSubtypeHandle handle = new InputMethodSubtypeHandle(
+                    shell.getNextArgRequired(), Integer.parseInt(shell.getNextArgRequired()));
+            String descriptor = shell.getNextArgRequired();
+            int vid = Integer.decode(shell.getNextArgRequired());
+            int pid = Integer.decode(shell.getNextArgRequired());
+            InputDeviceIdentifier id = new InputDeviceIdentifier(descriptor, vid, pid);
+            setKeyboardLayoutForInputDeviceInner(id, handle, shell.getNextArgRequired());
+        }
+        return 0;
     }
 
+
     private boolean checkCallingPermission(String permission, String func) {
         // Quick check: if the calling permission is me, it's all okay.
         if (Binder.getCallingPid() == Process.myPid()) {
@@ -1937,9 +2054,12 @@ public class InputManagerService extends IInputManager.Stub
                 case MSG_DELIVER_INPUT_DEVICES_CHANGED:
                     deliverInputDevicesChanged((InputDevice[])msg.obj);
                     break;
-                case MSG_SWITCH_KEYBOARD_LAYOUT:
-                    handleSwitchKeyboardLayout(msg.arg1, msg.arg2);
+                case MSG_SWITCH_KEYBOARD_LAYOUT: {
+                    SomeArgs args = (SomeArgs)msg.obj;
+                    handleSwitchKeyboardLayout((InputDeviceIdentifier)args.arg1,
+                            (InputMethodSubtypeHandle)args.arg2);
                     break;
+                }
                 case MSG_RELOAD_KEYBOARD_LAYOUTS:
                     reloadKeyboardLayouts();
                     break;
@@ -2106,6 +2226,25 @@ public class InputManagerService extends IInputManager.Stub
         }
     }
 
+    private class Shell extends ShellCommand {
+        @Override
+        public int onCommand(String cmd) {
+            return onShellCommand(this, cmd);
+        }
+
+        @Override
+        public void onHelp() {
+            final PrintWriter pw = getOutPrintWriter();
+            pw.println("Input manager commands:");
+            pw.println("  help");
+            pw.println("    Print this help text.");
+            pw.println("");
+            pw.println("  setlayout IME_ID IME_SUPTYPE_HASH_CODE"
+                    + " DEVICE_DESCRIPTOR VENDOR_ID PRODUCT_ID KEYBOARD_DESCRIPTOR");
+            pw.println("    Sets a keyboard layout for a given IME subtype and input device pair");
+        }
+    }
+
     private final class LocalService extends InputManagerInternal {
         @Override
         public void setDisplayViewports(
index f6d7244..e97aca8 100644 (file)
@@ -16,6 +16,7 @@
 
 package com.android.server.input;
 
+import com.android.internal.inputmethod.InputMethodSubtypeHandle;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.FastXmlSerializer;
 import com.android.internal.util.XmlUtils;
@@ -26,6 +27,8 @@ import org.xmlpull.v1.XmlSerializer;
 
 import android.view.Surface;
 import android.hardware.input.TouchCalibration;
+import android.text.TextUtils;
+import android.util.ArrayMap;
 import android.util.AtomicFile;
 import android.util.Slog;
 import android.util.Xml;
@@ -37,10 +40,13 @@ import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.PrintWriter;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
@@ -131,9 +137,26 @@ final class PersistentDataStore {
         }
         return state.getKeyboardLayouts();
     }
+    public String getKeyboardLayout(String inputDeviceDescriptor,
+            InputMethodSubtypeHandle imeHandle) {
+        InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, false);
+        if (state == null) {
+            return null;
+        }
+        return state.getKeyboardLayout(imeHandle);
+    }
 
-    public boolean addKeyboardLayout(String inputDeviceDescriptor,
-            String keyboardLayoutDescriptor) {
+    public boolean setKeyboardLayout(String inputDeviceDescriptor,
+            InputMethodSubtypeHandle imeHandle, String keyboardLayoutDescriptor) {
+        InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, true);
+        if (state.setKeyboardLayout(imeHandle, keyboardLayoutDescriptor)) {
+            setDirty();
+            return true;
+        }
+        return false;
+    }
+
+    public boolean addKeyboardLayout(String inputDeviceDescriptor, String keyboardLayoutDescriptor) {
         InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, true);
         if (state.addKeyboardLayout(keyboardLayoutDescriptor)) {
             setDirty();
@@ -152,9 +175,10 @@ final class PersistentDataStore {
         return false;
     }
 
-    public boolean switchKeyboardLayout(String inputDeviceDescriptor, int direction) {
+    public boolean switchKeyboardLayout(String inputDeviceDescriptor,
+            InputMethodSubtypeHandle imeHandle) {
         InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, false);
-        if (state != null && state.switchKeyboardLayout(direction)) {
+        if (state != null && state.switchKeyboardLayout(imeHandle)) {
             setDirty();
             return true;
         }
@@ -301,13 +325,26 @@ final class PersistentDataStore {
         serializer.endDocument();
     }
 
+    public void dump(PrintWriter pw, String prefix) {
+        pw.println(prefix + "PersistentDataStore");
+        pw.println(prefix + "  mLoaded=" + mLoaded);
+        pw.println(prefix + "  mDirty=" + mDirty);
+        pw.println(prefix + "  InputDeviceStates:");
+        int i = 0;
+        for (Map.Entry<String, InputDeviceState> entry : mInputDevices.entrySet()) {
+            pw.println(prefix + "    " + i++ + ": " + entry.getKey());
+            entry.getValue().dump(pw, prefix + "      ");
+        }
+    }
+
     private static final class InputDeviceState {
         private static final String[] CALIBRATION_NAME = { "x_scale",
                 "x_ymix", "x_offset", "y_xmix", "y_scale", "y_offset" };
 
         private TouchCalibration[] mTouchCalibration = new TouchCalibration[4];
         private String mCurrentKeyboardLayout;
-        private ArrayList<String> mKeyboardLayouts = new ArrayList<String>();
+        private List<String> mUnassociatedKeyboardLayouts = new ArrayList<>();
+        private ArrayMap<InputMethodSubtypeHandle, String> mKeyboardLayouts = new ArrayMap<>();
 
         public TouchCalibration getTouchCalibration(int surfaceRotation) {
             try {
@@ -345,18 +382,34 @@ final class PersistentDataStore {
         }
 
         public String[] getKeyboardLayouts() {
-            if (mKeyboardLayouts.isEmpty()) {
+            if (mUnassociatedKeyboardLayouts.isEmpty()) {
                 return (String[])ArrayUtils.emptyArray(String.class);
             }
-            return mKeyboardLayouts.toArray(new String[mKeyboardLayouts.size()]);
+            return mUnassociatedKeyboardLayouts.toArray(
+                    new String[mUnassociatedKeyboardLayouts.size()]);
+        }
+
+        public String getKeyboardLayout(InputMethodSubtypeHandle handle) {
+            return mKeyboardLayouts.get(handle);
+        }
+
+        public boolean setKeyboardLayout(InputMethodSubtypeHandle imeHandle,
+                String keyboardLayout) {
+            String existingLayout = mKeyboardLayouts.get(imeHandle);
+            if (TextUtils.equals(existingLayout, keyboardLayout)) {
+                return false;
+            }
+            mKeyboardLayouts.put(imeHandle, keyboardLayout);
+            return true;
         }
 
         public boolean addKeyboardLayout(String keyboardLayout) {
-            int index = Collections.binarySearch(mKeyboardLayouts, keyboardLayout);
+            int index = Collections.binarySearch(
+                    mUnassociatedKeyboardLayouts, keyboardLayout);
             if (index >= 0) {
                 return false;
             }
-            mKeyboardLayouts.add(-index - 1, keyboardLayout);
+            mUnassociatedKeyboardLayouts.add(-index - 1, keyboardLayout);
             if (mCurrentKeyboardLayout == null) {
                 mCurrentKeyboardLayout = keyboardLayout;
             }
@@ -364,11 +417,11 @@ final class PersistentDataStore {
         }
 
         public boolean removeKeyboardLayout(String keyboardLayout) {
-            int index = Collections.binarySearch(mKeyboardLayouts, keyboardLayout);
+            int index = Collections.binarySearch(mUnassociatedKeyboardLayouts, keyboardLayout);
             if (index < 0) {
                 return false;
             }
-            mKeyboardLayouts.remove(index);
+            mUnassociatedKeyboardLayouts.remove(index);
             updateCurrentKeyboardLayoutIfRemoved(keyboardLayout, index);
             return true;
         }
@@ -376,41 +429,34 @@ final class PersistentDataStore {
         private void updateCurrentKeyboardLayoutIfRemoved(
                 String removedKeyboardLayout, int removedIndex) {
             if (Objects.equal(mCurrentKeyboardLayout, removedKeyboardLayout)) {
-                if (!mKeyboardLayouts.isEmpty()) {
+                if (!mUnassociatedKeyboardLayouts.isEmpty()) {
                     int index = removedIndex;
-                    if (index == mKeyboardLayouts.size()) {
+                    if (index == mUnassociatedKeyboardLayouts.size()) {
                         index = 0;
                     }
-                    mCurrentKeyboardLayout = mKeyboardLayouts.get(index);
+                    mCurrentKeyboardLayout = mUnassociatedKeyboardLayouts.get(index);
                 } else {
                     mCurrentKeyboardLayout = null;
                 }
             }
         }
 
-        public boolean switchKeyboardLayout(int direction) {
-            final int size = mKeyboardLayouts.size();
-            if (size < 2) {
-                return false;
-            }
-            int index = Collections.binarySearch(mKeyboardLayouts, mCurrentKeyboardLayout);
-            assert index >= 0;
-            if (direction > 0) {
-                index = (index + 1) % size;
-            } else {
-                index = (index + size - 1) % size;
+        public boolean switchKeyboardLayout(InputMethodSubtypeHandle imeHandle) {
+            final String layout = mKeyboardLayouts.get(imeHandle);
+            if (layout != null && !TextUtils.equals(mCurrentKeyboardLayout, layout)) {
+                mCurrentKeyboardLayout = layout;
+                return true;
             }
-            mCurrentKeyboardLayout = mKeyboardLayouts.get(index);
-            return true;
+            return false;
         }
 
         public boolean removeUninstalledKeyboardLayouts(Set<String> availableKeyboardLayouts) {
             boolean changed = false;
-            for (int i = mKeyboardLayouts.size(); i-- > 0; ) {
-                String keyboardLayout = mKeyboardLayouts.get(i);
+            for (int i = mUnassociatedKeyboardLayouts.size(); i-- > 0; ) {
+                String keyboardLayout = mUnassociatedKeyboardLayouts.get(i);
                 if (!availableKeyboardLayouts.contains(keyboardLayout)) {
                     Slog.i(TAG, "Removing uninstalled keyboard layout " + keyboardLayout);
-                    mKeyboardLayouts.remove(i);
+                    mUnassociatedKeyboardLayouts.remove(i);
                     updateCurrentKeyboardLayoutIfRemoved(keyboardLayout, i);
                     changed = true;
                 }
@@ -428,13 +474,8 @@ final class PersistentDataStore {
                         throw new XmlPullParserException(
                                 "Missing descriptor attribute on keyboard-layout.");
                     }
-                    String current = parser.getAttributeValue(null, "current");
-                    if (mKeyboardLayouts.contains(descriptor)) {
-                        throw new XmlPullParserException(
-                                "Found duplicate keyboard layout.");
-                    }
 
-                    mKeyboardLayouts.add(descriptor);
+                    String current = parser.getAttributeValue(null, "current");
                     if (current != null && current.equals("true")) {
                         if (mCurrentKeyboardLayout != null) {
                             throw new XmlPullParserException(
@@ -442,6 +483,32 @@ final class PersistentDataStore {
                         }
                         mCurrentKeyboardLayout = descriptor;
                     }
+
+                    String inputMethodId = parser.getAttributeValue(null, "input-method-id");
+                    String inputMethodSubtypeId =
+                        parser.getAttributeValue(null, "input-method-subtype-id");
+                    if (inputMethodId == null && inputMethodSubtypeId != null
+                            || inputMethodId != null && inputMethodSubtypeId == null) {
+                        throw new XmlPullParserException(
+                                "Found an incomplete input method description");
+                    }
+
+                    if (inputMethodSubtypeId != null) {
+                        InputMethodSubtypeHandle handle = new InputMethodSubtypeHandle(
+                                inputMethodId, Integer.parseInt(inputMethodSubtypeId));
+                        if (mKeyboardLayouts.containsKey(handle)) {
+                            throw new XmlPullParserException(
+                                    "Found duplicate subtype to keyboard layout mapping: "
+                                    + handle);
+                        }
+                        mKeyboardLayouts.put(handle, descriptor);
+                    } else {
+                        if (mUnassociatedKeyboardLayouts.contains(descriptor)) {
+                            throw new XmlPullParserException(
+                                    "Found duplicate unassociated keyboard layout: " + descriptor);
+                        }
+                        mUnassociatedKeyboardLayouts.add(descriptor);
+                    }
                 } else if (parser.getName().equals("calibration")) {
                     String format = parser.getAttributeValue(null, "format");
                     String rotation = parser.getAttributeValue(null, "rotation");
@@ -492,19 +559,31 @@ final class PersistentDataStore {
             }
 
             // Maintain invariant that layouts are sorted.
-            Collections.sort(mKeyboardLayouts);
+            Collections.sort(mUnassociatedKeyboardLayouts);
 
             // Maintain invariant that there is always a current keyboard layout unless
             // there are none installed.
-            if (mCurrentKeyboardLayout == null && !mKeyboardLayouts.isEmpty()) {
-                mCurrentKeyboardLayout = mKeyboardLayouts.get(0);
+            if (mCurrentKeyboardLayout == null && !mUnassociatedKeyboardLayouts.isEmpty()) {
+                mCurrentKeyboardLayout = mUnassociatedKeyboardLayouts.get(0);
             }
         }
 
         public void saveToXml(XmlSerializer serializer) throws IOException {
-            for (String layout : mKeyboardLayouts) {
+            for (String layout : mUnassociatedKeyboardLayouts) {
+                serializer.startTag(null, "keyboard-layout");
+                serializer.attribute(null, "descriptor", layout);
+                serializer.endTag(null, "keyboard-layout");
+            }
+
+            final int N = mKeyboardLayouts.size();
+            for (int i = 0; i < N; i++) {
+                final InputMethodSubtypeHandle handle = mKeyboardLayouts.keyAt(i);
+                final String layout = mKeyboardLayouts.valueAt(i);
                 serializer.startTag(null, "keyboard-layout");
                 serializer.attribute(null, "descriptor", layout);
+                serializer.attribute(null, "input-method-id", handle.getInputMethodId());
+                serializer.attribute(null, "input-method-subtype-id",
+                        Integer.toString(handle.getSubtypeId()));
                 if (layout.equals(mCurrentKeyboardLayout)) {
                     serializer.attribute(null, "current", "true");
                 }
@@ -529,6 +608,22 @@ final class PersistentDataStore {
             }
         }
 
+        private void dump(final PrintWriter pw, final String prefix) {
+            pw.println(prefix + "CurrentKeyboardLayout=" + mCurrentKeyboardLayout);
+            pw.println(prefix + "UnassociatedKeyboardLayouts=" + mUnassociatedKeyboardLayouts);
+            pw.println(prefix + "TouchCalibration=" + Arrays.toString(mTouchCalibration));
+            pw.println(prefix + "Subtype to Layout Mappings:");
+            final int N = mKeyboardLayouts.size();
+            if (N != 0) {
+                for (int i = 0; i < N; i++) {
+                    pw.println(prefix + "  " + mKeyboardLayouts.keyAt(i) + ": "
+                            + mKeyboardLayouts.valueAt(i));
+                }
+            } else {
+                pw.println(prefix + "  <none>");
+            }
+        }
+
         private static String surfaceRotationToString(int surfaceRotation) {
             switch (surfaceRotation) {
                 case Surface.ROTATION_0:   return "0";
index 806c4ca..784e73b 100644 (file)
@@ -3124,15 +3124,6 @@ public class PhoneWindowManager implements WindowManagerPolicy {
             hideRecentApps(true, false);
         }
 
-        // Handle keyboard layout switching.
-        // TODO: Deprecate this behavior when we fully migrate to IME subtype-based layout rotation.
-        if (down && repeatCount == 0 && keyCode == KeyEvent.KEYCODE_SPACE
-                && ((metaState & KeyEvent.META_CTRL_MASK) != 0)) {
-            int direction = (metaState & KeyEvent.META_SHIFT_MASK) != 0 ? -1 : 1;
-            mWindowManagerFuncs.switchKeyboardLayout(event.getDeviceId(), direction);
-            return -1;
-        }
-
         // Handle input method switching.
         if (down && repeatCount == 0
                 && (keyCode == KeyEvent.KEYCODE_LANGUAGE_SWITCH
index 4b0015e..3ffec23 100644 (file)
@@ -5316,12 +5316,6 @@ public class WindowManagerService extends IWindowManager.Stub
 
     // Called by window manager policy.  Not exposed externally.
     @Override
-    public void switchKeyboardLayout(int deviceId, int direction) {
-        mInputManager.switchKeyboardLayout(deviceId, direction);
-    }
-
-    // Called by window manager policy.  Not exposed externally.
-    @Override
     public void switchInputMethod(boolean forwardDirection) {
         final InputMethodManagerInternal inputMethodManagerInternal =
                 LocalServices.getService(InputMethodManagerInternal.class);