Input settings: Associate keyboard layouts with device/IME subtype.

Implements selection of keyboard layouts using the new InputManager
methods that associate keyboard layouts to
device/InputMethodInfo/InputMethodSubtype
See: Ie88ce1ab77dbfe03ab51d89c1dc9e0a7ddbb3216

Bug: 25752812
Change-Id: Ib76880d66391ca37978054de80f4b3b5147cecc3
diff --git a/src/com/android/settings/SettingsActivity.java b/src/com/android/settings/SettingsActivity.java
index dc134ff..bcb1ce9 100644
--- a/src/com/android/settings/SettingsActivity.java
+++ b/src/com/android/settings/SettingsActivity.java
@@ -80,6 +80,7 @@
 import com.android.settings.inputmethod.AvailableVirtualKeyboardFragment;
 import com.android.settings.inputmethod.InputMethodAndLanguageSettings;
 import com.android.settings.inputmethod.KeyboardLayoutPickerFragment;
+import com.android.settings.inputmethod.KeyboardLayoutPickerFragment2;
 import com.android.settings.inputmethod.SpellCheckersSettings;
 import com.android.settings.inputmethod.UserDictionaryList;
 import com.android.settings.localepicker.LocaleListEditor;
@@ -293,6 +294,7 @@
             TrustedCredentialsSettings.class.getName(),
             PaymentSettings.class.getName(),
             KeyboardLayoutPickerFragment.class.getName(),
+            KeyboardLayoutPickerFragment2.class.getName(),
             ZenModeSettings.class.getName(),
             SoundSettings.class.getName(),
             ConfigureNotificationSettings.class.getName(),
diff --git a/src/com/android/settings/inputmethod/KeyboardLayoutDialogFragment.java b/src/com/android/settings/inputmethod/KeyboardLayoutDialogFragment.java
index 68ceeef..ad7a2b1 100644
--- a/src/com/android/settings/inputmethod/KeyboardLayoutDialogFragment.java
+++ b/src/com/android/settings/inputmethod/KeyboardLayoutDialogFragment.java
@@ -301,7 +301,7 @@
         }
     }
 
-    static final class KeyboardLayoutLoader extends AsyncTaskLoader<Keyboards> {
+    private static final class KeyboardLayoutLoader extends AsyncTaskLoader<Keyboards> {
         private final InputDeviceIdentifier mInputDeviceIdentifier;
 
         public KeyboardLayoutLoader(Context context, InputDeviceIdentifier inputDeviceIdentifier) {
diff --git a/src/com/android/settings/inputmethod/KeyboardLayoutPickerFragment2.java b/src/com/android/settings/inputmethod/KeyboardLayoutPickerFragment2.java
new file mode 100644
index 0000000..28c82e2
--- /dev/null
+++ b/src/com/android/settings/inputmethod/KeyboardLayoutPickerFragment2.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2012 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.settings.inputmethod;
+
+import android.app.Activity;
+import android.hardware.input.InputDeviceIdentifier;
+import android.hardware.input.InputManager;
+import android.hardware.input.InputManager.InputDeviceListener;
+import android.hardware.input.KeyboardLayout;
+import android.os.Bundle;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceScreen;
+import android.view.InputDevice;
+
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodSubtype;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.util.Preconditions;
+import com.android.settings.R;
+import com.android.settings.SettingsPreferenceFragment;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+public final class KeyboardLayoutPickerFragment2 extends SettingsPreferenceFragment
+        implements InputDeviceListener {
+
+    private InputDeviceIdentifier mInputDeviceIdentifier;
+    private int mInputDeviceId = -1;
+    private InputManager mIm;
+    private InputMethodInfo mImi;
+    private InputMethodSubtype mSubtype;
+    private KeyboardLayout[] mKeyboardLayouts;
+    private Map<Preference, KeyboardLayout> mPreferenceMap = new HashMap<>();
+
+    // TODO: Make these constants public API for b/25752827
+
+    /**
+     * Intent extra: The input device descriptor of the keyboard whose keyboard
+     * layout is to be changed.
+     */
+    public static final String EXTRA_INPUT_DEVICE_IDENTIFIER = "input_device_identifier";
+
+    /**
+     * Intent extra: The associated {@link InputMethodInfo}.
+     */
+    public static final String EXTRA_INPUT_METHOD_INFO = "input_method_info";
+
+    /**
+     * Intent extra: The associated {@link InputMethodSubtype}.
+     */
+    public static final String EXTRA_INPUT_METHOD_SUBTYPE = "input_method_subtype";
+
+    @Override
+    protected int getMetricsCategory() {
+        return MetricsLogger.INPUTMETHOD_KEYBOARD;
+    }
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        Activity activity = Preconditions.checkNotNull(getActivity());
+
+        mInputDeviceIdentifier = activity.getIntent().getParcelableExtra(
+                EXTRA_INPUT_DEVICE_IDENTIFIER);
+        mImi = activity.getIntent().getParcelableExtra(EXTRA_INPUT_METHOD_INFO);
+        mSubtype = activity.getIntent().getParcelableExtra(EXTRA_INPUT_METHOD_SUBTYPE);
+
+        if (mInputDeviceIdentifier == null || mImi == null || mSubtype == null) {
+            activity.finish();
+        }
+
+        mIm = activity.getSystemService(InputManager.class);
+        mKeyboardLayouts = mIm.getKeyboardLayoutsForInputDevice(mInputDeviceIdentifier);
+        Arrays.sort(mKeyboardLayouts);
+        setPreferenceScreen(createPreferenceHierarchy());
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+
+        mIm.registerInputDeviceListener(this, null);
+
+        InputDevice inputDevice =
+                mIm.getInputDeviceByDescriptor(mInputDeviceIdentifier.getDescriptor());
+        if (inputDevice == null) {
+            getActivity().finish();
+            return;
+        }
+        mInputDeviceId = inputDevice.getId();
+    }
+
+    @Override
+    public void onPause() {
+        mIm.unregisterInputDeviceListener(this);
+        mInputDeviceId = -1;
+
+        super.onPause();
+    }
+
+    @Override
+    public boolean onPreferenceTreeClick(Preference preference) {
+        KeyboardLayout layout = mPreferenceMap.get(preference);
+        if (layout != null) {
+            mIm.setKeyboardLayoutForInputDevice(mInputDeviceIdentifier, mImi, mSubtype,
+                    layout.getDescriptor());
+            getActivity().finish();
+            return true;
+        }
+        return super.onPreferenceTreeClick(preference);
+    }
+
+    @Override
+    public void onInputDeviceAdded(int deviceId) {}
+
+    @Override
+    public void onInputDeviceChanged(int deviceId) {}
+
+    @Override
+    public void onInputDeviceRemoved(int deviceId) {
+        if (mInputDeviceId >= 0 && deviceId == mInputDeviceId) {
+            getActivity().finish();
+        }
+    }
+
+    private PreferenceScreen createPreferenceHierarchy() {
+        PreferenceScreen root = getPreferenceManager().createPreferenceScreen(getActivity());
+
+        for (KeyboardLayout layout : mKeyboardLayouts) {
+            Preference pref = new Preference(getPrefContext());
+            pref.setTitle(layout.getLabel());
+            root.addPreference(pref);
+            mPreferenceMap.put(pref, layout);
+        }
+
+        root.setTitle(PhysicalKeyboardFragment.getDisplayName(getContext(), mImi, mSubtype));
+        return root;
+    }
+}
diff --git a/src/com/android/settings/inputmethod/PhysicalKeyboardFragment.java b/src/com/android/settings/inputmethod/PhysicalKeyboardFragment.java
index 8f7d99e..74d474c 100644
--- a/src/com/android/settings/inputmethod/PhysicalKeyboardFragment.java
+++ b/src/com/android/settings/inputmethod/PhysicalKeyboardFragment.java
@@ -18,6 +18,8 @@
 
 import android.app.Activity;
 import android.app.LoaderManager;
+import android.content.AsyncTaskLoader;
+import android.content.Context;
 import android.content.Intent;
 import android.content.Loader;
 import android.database.ContentObserver;
@@ -34,6 +36,8 @@
 import android.util.Pair;
 import android.view.InputDevice;
 import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.InputMethodSubtype;
 import android.widget.Toast;
 
 import com.android.internal.inputmethod.InputMethodUtils;
@@ -44,22 +48,25 @@
 import com.android.settings.SettingsPreferenceFragment;
 
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 
 public final class PhysicalKeyboardFragment extends SettingsPreferenceFragment
-        implements LoaderManager.LoaderCallbacks<KeyboardLayoutDialogFragment.Keyboards>,
+        implements LoaderManager.LoaderCallbacks<PhysicalKeyboardFragment.Keyboards>,
         InputManager.InputDeviceListener {
 
     private static final int USER_SYSTEM = 0;
     private static final String KEYBOARD_ASSISTANCE_CATEGORY = "keyboard_assistance_category";
     private static final String SHOW_VIRTUAL_KEYBOARD_SWITCH = "show_virtual_keyboard_switch";
     private static final String KEYBOARD_SHORTCUTS_HELPER = "keyboard_shortcuts_helper";
+    private static final String IM_SUBTYPE_MODE_KEYBOARD = "keyboard";
 
-    private final ArrayList<PreferenceCategory> mHardKeyboardPreferenceList = new ArrayList<>();
     private final HashMap<Integer, Pair<InputDeviceIdentifier, PreferenceCategory>> mLoaderReference
             = new HashMap<>();
+    private final Map<InputMethodInfo, List<InputMethodSubtype>> mImiSubtypes = new HashMap<>();
     private InputManager mIm;
+    private InputMethodManager mImm;
     private PreferenceCategory mKeyboardAssistanceCategory;
     private SwitchPreference mShowVirtualKeyboardSwitch;
     private InputMethodUtils.InputMethodSettings mSettings;
@@ -69,6 +76,7 @@
         Activity activity = Preconditions.checkNotNull(getActivity());
         addPreferencesFromResource(R.xml.physical_keyboard_settings);
         mIm = Preconditions.checkNotNull(activity.getSystemService(InputManager.class));
+        mImm = Preconditions.checkNotNull(activity.getSystemService(InputMethodManager.class));
         mSettings = new InputMethodUtils.InputMethodSettings(
                 activity.getResources(),
                 getContentResolver(),
@@ -110,29 +118,32 @@
     }
 
     @Override
-    public Loader<KeyboardLayoutDialogFragment.Keyboards> onCreateLoader(int id, Bundle args) {
-        InputDeviceIdentifier deviceId = mLoaderReference.get(id).first;
-        return new KeyboardLayoutDialogFragment.KeyboardLayoutLoader(
-                getActivity().getBaseContext(), deviceId);
+    public Loader<Keyboards> onCreateLoader(int id, Bundle args) {
+        final InputDeviceIdentifier deviceId = mLoaderReference.get(id).first;
+        return new KeyboardLayoutLoader(
+                getActivity().getBaseContext(), mIm, mImiSubtypes, deviceId);
     }
 
     @Override
-    public void onLoadFinished(
-            final Loader<KeyboardLayoutDialogFragment.Keyboards> loader,
-            KeyboardLayoutDialogFragment.Keyboards data) {
+    public void onLoadFinished(Loader<Keyboards> loader, Keyboards data) {
         // TODO: Investigate why this is being called twice.
         final InputDeviceIdentifier deviceId = mLoaderReference.get(loader.getId()).first;
         final PreferenceCategory category = mLoaderReference.get(loader.getId()).second;
         category.removeAll();
-        for (KeyboardLayout layout : data.keyboardLayouts) {
-            if (layout != null) {
-                Preference pref = new Preference(getPrefContext(), null);
-                pref.setTitle(layout.getLabel());
-                pref.setSummary(layout.getCollection());
+        for (Keyboards.KeyboardInfo info : data.mInfos) {
+            Preference pref = new Preference(getPrefContext(), null);
+            final InputMethodInfo imi = info.mImi;
+            final InputMethodSubtype imSubtype = info.mImSubtype;
+            if (imi != null && imSubtype != null) {
+                pref.setTitle(getDisplayName(getContext(), imi, imSubtype));
+                KeyboardLayout layout = info.mLayout;
+                if (layout != null) {
+                    pref.setSummary(layout.getLabel());
+                }
                 pref.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
                     @Override
                     public boolean onPreferenceClick(Preference preference) {
-                        showKeyboardLayoutScreen(deviceId);
+                        showKeyboardLayoutScreen(deviceId, imi, imSubtype);
                         return true;
                     }
                 });
@@ -142,7 +153,7 @@
     }
 
     @Override
-    public void onLoaderReset(Loader<KeyboardLayoutDialogFragment.Keyboards> loader) {}
+    public void onLoaderReset(Loader<Keyboards> loader) {}
 
     @Override
     public void onInputDeviceAdded(int deviceId) {
@@ -166,38 +177,21 @@
 
     private void updateHardKeyboards() {
         clearHardKeyboardsData();
+        loadInputMethodInfoSubtypes();
         final int[] devices = InputDevice.getDeviceIds();
         for (int deviceIndex = 0; deviceIndex < devices.length; deviceIndex++) {
             InputDevice device = InputDevice.getDevice(devices[deviceIndex]);
             if (device != null
                     && !device.isVirtual()
                     && device.isFullKeyboard()) {
-                final InputDeviceIdentifier deviceId = device.getIdentifier();
-                final String keyboardLayoutDescriptor =
-                        mIm.getCurrentKeyboardLayoutForInputDevice(deviceId);
-                final KeyboardLayout keyboardLayout = keyboardLayoutDescriptor != null ?
-                        mIm.getKeyboardLayout(keyboardLayoutDescriptor) : null;
-
                 final PreferenceCategory category = new PreferenceCategory(getPrefContext(), null);
                 category.setTitle(device.getName());
-                if (keyboardLayout != null) {
-                    category.setSummary(keyboardLayout.toString());
-                } else {
-                    category.setSummary(R.string.keyboard_layout_default_label);
-                }
-                mLoaderReference.put(deviceIndex, new Pair(deviceId, category));
-                mHardKeyboardPreferenceList.add(category);
+                category.setOrder(0);
+                mLoaderReference.put(deviceIndex, new Pair(device.getIdentifier(), category));
+                getPreferenceScreen().addPreference(category);
             }
         }
-
-        Collections.sort(mHardKeyboardPreferenceList);
-        final int count = mHardKeyboardPreferenceList.size();
-        for (int i = 0; i < count; i++) {
-            final PreferenceCategory category = mHardKeyboardPreferenceList.get(i);
-            category.setOrder(i);
-            getPreferenceScreen().addPreference(category);
-        }
-        mKeyboardAssistanceCategory.setOrder(count);
+        mKeyboardAssistanceCategory.setOrder(1);
         getPreferenceScreen().addPreference(mKeyboardAssistanceCategory);
 
         for (int deviceIndex : mLoaderReference.keySet()) {
@@ -206,11 +200,16 @@
         updateShowVirtualKeyboardSwitch();
     }
 
-    private void showKeyboardLayoutScreen(InputDeviceIdentifier inputDeviceIdentifier) {
+    private void showKeyboardLayoutScreen(
+            InputDeviceIdentifier inputDeviceIdentifier,
+            InputMethodInfo imi,
+            InputMethodSubtype imSubtype) {
         final Intent intent = new Intent(Intent.ACTION_MAIN);
         intent.setClass(getActivity(), Settings.KeyboardLayoutPickerActivity.class);
-        intent.putExtra(KeyboardLayoutPickerFragment.EXTRA_INPUT_DEVICE_IDENTIFIER,
+        intent.putExtra(KeyboardLayoutPickerFragment2.EXTRA_INPUT_DEVICE_IDENTIFIER,
                 inputDeviceIdentifier);
+        intent.putExtra(KeyboardLayoutPickerFragment2.EXTRA_INPUT_METHOD_INFO, imi);
+        intent.putExtra(KeyboardLayoutPickerFragment2.EXTRA_INPUT_METHOD_SUBTYPE, imSubtype);
         startActivity(intent);
     }
 
@@ -220,7 +219,21 @@
             getLoaderManager().destroyLoader(index);
         }
         mLoaderReference.clear();
-        mHardKeyboardPreferenceList.clear();
+    }
+
+    private void loadInputMethodInfoSubtypes() {
+        mImiSubtypes.clear();
+        final List<InputMethodInfo> imis = mImm.getEnabledInputMethodList();
+        for (InputMethodInfo imi : imis) {
+            final List<InputMethodSubtype> subtypes = new ArrayList<>();
+            for (InputMethodSubtype subtype : mImm.getEnabledInputMethodSubtypeList(
+                    imi, true /* allowsImplicitlySelectedSubtypes */)) {
+                if (IM_SUBTYPE_MODE_KEYBOARD.equalsIgnoreCase(subtype.getMode())) {
+                    subtypes.add(subtype);
+                }
+            }
+            mImiSubtypes.put(imi, subtypes);
+        }
     }
 
     private void registerShowVirtualKeyboardSettingsObserver() {
@@ -261,4 +274,77 @@
             updateShowVirtualKeyboardSwitch();
         }
     };
+
+    static String getDisplayName(
+            Context context, InputMethodInfo imi, InputMethodSubtype imSubtype) {
+        CharSequence imSubtypeName =  imSubtype.getDisplayName(
+                context, imi.getPackageName(),
+                imi.getServiceInfo().applicationInfo);
+        CharSequence imeName = imi.loadLabel(context.getPackageManager());
+        return String.format(
+                context.getString(R.string.physical_device_title), imSubtypeName, imeName);
+    }
+
+    private static final class KeyboardLayoutLoader extends AsyncTaskLoader<Keyboards> {
+
+        private final Map<InputMethodInfo, List<InputMethodSubtype>> mImiSubtypes;
+        private final InputDeviceIdentifier mInputDeviceIdentifier;
+        private final InputManager mIm;
+
+        public KeyboardLayoutLoader(
+                Context context,
+                InputManager im,
+                Map<InputMethodInfo, List<InputMethodSubtype>> imiSubtypes,
+                InputDeviceIdentifier inputDeviceIdentifier) {
+            super(context);
+            mIm = Preconditions.checkNotNull(im);
+            mInputDeviceIdentifier = Preconditions.checkNotNull(inputDeviceIdentifier);
+            mImiSubtypes = new HashMap<>(imiSubtypes);
+        }
+
+        @Override
+        public Keyboards loadInBackground() {
+            final Keyboards keyboards = new Keyboards();
+            for (InputMethodInfo imi : mImiSubtypes.keySet()) {
+                for (InputMethodSubtype subtype : mImiSubtypes.get(imi)) {
+                    final KeyboardLayout layout = mIm.getKeyboardLayoutForInputDevice(
+                            mInputDeviceIdentifier, imi, subtype);
+                    keyboards.mInfos.add(new Keyboards.KeyboardInfo(imi, subtype, layout));
+                }
+            }
+            return keyboards;
+        }
+
+        @Override
+        protected void onStartLoading() {
+            super.onStartLoading();
+            forceLoad();
+        }
+
+        @Override
+        protected void onStopLoading() {
+            super.onStopLoading();
+            cancelLoad();
+        }
+    }
+
+    public static final class Keyboards {
+
+        public final ArrayList<KeyboardInfo> mInfos = new ArrayList<>();
+
+        public static final class KeyboardInfo {
+
+            public final InputMethodInfo mImi;
+            public final InputMethodSubtype mImSubtype;
+            public final KeyboardLayout mLayout;
+
+            public KeyboardInfo(
+                    InputMethodInfo imi, InputMethodSubtype imSubtype, KeyboardLayout layout) {
+                mImi = imi;
+                mImSubtype = imSubtype;
+                mLayout = layout;
+            }
+        }
+    }
+
 }