OSDN Git Service

Change Input Settings UI flow.
authorAbodunrinwa Toki <toki@google.com>
Wed, 20 Jan 2016 18:43:20 +0000 (18:43 +0000)
committerAbodunrinwa Toki <toki@google.com>
Fri, 22 Jan 2016 18:24:44 +0000 (18:24 +0000)
1. Introduces new UI components as per the new flow
2. Temporarily disables components in the old flow that are to be
   replaced by the new flow. This is done so we can neatly revert
   to the old flow if there are issues with the new flow
3. AvailableVirtualKeyboardActivity now responds to
   android.settings.INPUT_METHOD_SETTINGS intents instead of
   InputMethodAndLanguageSettingsActivity

Bug: 25752812
Change-Id: I728d7ee185827ed328c16cb7abce244557a26518

13 files changed:
AndroidManifest.xml
res/values/strings.xml
res/xml/language_settings.xml
res/xml/physical_keyboard_settings.xml [new file with mode: 0644]
res/xml/virtual_keyboard_settings.xml [new file with mode: 0644]
src/com/android/settings/InstrumentedFragment.java
src/com/android/settings/Settings.java
src/com/android/settings/SettingsActivity.java
src/com/android/settings/inputmethod/AvailableVirtualKeyboardFragment.java [new file with mode: 0644]
src/com/android/settings/inputmethod/InputMethodAndLanguageSettings.java
src/com/android/settings/inputmethod/KeyboardLayoutDialogFragment.java
src/com/android/settings/inputmethod/PhysicalKeyboardFragment.java [new file with mode: 0644]
src/com/android/settings/inputmethod/VirtualKeyboardFragment.java [new file with mode: 0644]

index eff0397..4366b0b 100644 (file)
         </activity>
 
         <activity android:name="Settings$InputMethodAndLanguageSettingsActivity"
-                android:label="@string/language_keyboard_settings_title"
-                android:icon="@drawable/ic_settings_language"
-                android:taskAffinity="com.android.settings"
-                android:parentActivityName="Settings">
-            <intent-filter android:priority="1">
-                <action android:name="android.settings.INPUT_METHOD_SETTINGS" />
-                <category android:name="android.intent.category.DEFAULT" />
-            </intent-filter>
+            android:label="@string/language_keyboard_settings_title"
+            android:icon="@drawable/ic_settings_language"
+            android:taskAffinity="com.android.settings"
+            android:parentActivityName="Settings">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.intent.category.VOICE_LAUNCH" />
                 android:value="true" />
         </activity>
 
+        <activity android:name="Settings$AvailableVirtualKeyboardActivity"
+            android:label="@string/available_virtual_keyboard_category">
+            <intent-filter android:priority="1">
+                <action android:name="android.settings.INPUT_METHOD_SETTINGS" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+            <meta-data android:name="com.android.settings.FRAGMENT_CLASS"
+                android:value="com.android.settings.inputmethod.AvailableVirtualKeyboardFragment" />
+        </activity>
+
         <activity android:name="Settings$ManageAssistActivity"
                 android:label="@string/assist_and_voice_input_title"
                 android:taskAffinity="com.android.settings"
index 13868dc..15495c6 100644 (file)
     <!-- Toast that settings for an application is failed to open. -->
     <string name="failed_to_open_app_settings_toast">Failed to open settings for <xliff:g id="spell_application_name">%1$s</xliff:g></string>
 
+    <!-- Title for the 'keyboard and input methods' preference category. [CHAR LIMIT=35] -->
+    <string name="keyboard_and_input_methods_category">Keyboard and input methods</string>
+    <!-- Title for the 'virtual keyboard' preference sub-screen. [CHAR LIMIT=35] -->
+    <string name="virtual_keyboard_category">Virtual keyboard</string>
+    <!-- Title for the 'physical keyboard' preference sub-screen. [CHAR LIMIT=35] -->
+    <string name="physical_keyboard_category">Physical keyboard</string>
+    <!-- Title for the 'available virtual keyboard' preference sub-screen. [CHAR LIMIT=35] -->
+    <string name="available_virtual_keyboard_category">Available virtual keyboard</string>
+    <!-- Title for the button to trigger the 'available virtual keyboard' preference sub-screen. [CHAR LIMIT=35] -->
+    <string name="add_virtual_keyboard">Add a virtual keyboard</string>
+    <!-- Title for the 'keyboard assistance' preference category. [CHAR LIMIT=35] -->
+    <string name="keyboard_assistance_category">Keyboard assistance</string>
+    <!-- Title for the 'show virtual keyboard' preference switch. [CHAR LIMIT=35] -->
+    <string name="show_ime">Show virtual keyboard</string>
+    <!-- Summary text for the 'add virtual keyboard' preference sub-screen. [CHAR LIMIT=100] -->
+    <string name="show_ime_summary">Keep it on screen while physical keyboard is active</string>
+    <!-- Title for the button to trigger the 'keyboard shortcuts helper' dialog. [CHAR LIMIT=35] -->
+    <string name="keyboard_shortcuts_helper">Keyboard shortcuts helper</string>
+
     <!-- On Language & input settings screen, heading. Inside the "Language & input settings" screen, this is the header for settings that relate to mouse and trackpad devices. [CHAR LIMIT=40] -->
     <string name="pointer_settings_category">Mouse/trackpad</string>
     <!-- On Language & input settings screen, setting summary.  Setting for mouse pointer speed. [CHAR LIMIT=35] -->
index 83a0985..9cc0145 100644 (file)
             />
 
     <PreferenceCategory
-            android:key="keyboard_settings_category"
-            android:title="@string/keyboard_settings_category">
-
+        android:title="@string/keyboard_and_input_methods_category">
         <PreferenceScreen
-                android:key="current_input_method"
-                android:title="@string/current_input_method"
-                />
-        <!-- Enabled input method list will be populated programmatically here. -->
+            android:title="@string/virtual_keyboard_category"
+            android:fragment="com.android.settings.inputmethod.VirtualKeyboardFragment" />
+        <PreferenceScreen
+            android:title="@string/physical_keyboard_category"
+            android:fragment="com.android.settings.inputmethod.PhysicalKeyboardFragment" />
     </PreferenceCategory>
 
-    <PreferenceCategory
-            android:key="hard_keyboard"
-            android:title="@string/builtin_keyboard_settings_title"
-            android:persistent="false">
-        <!-- Additional preference screens are inserted here programmatically
-             with low order values to set the key map of each attached keyboard. -->
-    </PreferenceCategory>
+    <!-- Temporarily disabled: -->
+    <!--<PreferenceCategory-->
+            <!--android:key="keyboard_settings_category"-->
+            <!--android:title="@string/keyboard_settings_category">-->
+
+        <!--<PreferenceScreen-->
+                <!--android:key="current_input_method"-->
+                <!--android:title="@string/current_input_method"-->
+                <!--/>-->
+        <!--&lt;!&ndash; Enabled input method list will be populated programmatically here. &ndash;&gt;-->
+    <!--</PreferenceCategory>-->
+
+    <!--<PreferenceCategory-->
+            <!--android:key="hard_keyboard"-->
+            <!--android:title="@string/builtin_keyboard_settings_title"-->
+            <!--android:persistent="false">-->
+        <!--&lt;!&ndash; Additional preference screens are inserted here programmatically-->
+             <!--with low order values to set the key map of each attached keyboard. &ndash;&gt;-->
+    <!--</PreferenceCategory>-->
 
     <PreferenceCategory
             android:key="voice_category"
diff --git a/res/xml/physical_keyboard_settings.xml b/res/xml/physical_keyboard_settings.xml
new file mode 100644 (file)
index 0000000..cbb95c2
--- /dev/null
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+    android:title="@string/physical_keyboard_category">
+    <!-- Additional preference screens are inserted here programmatically
+         with low order values to set the key map of each attached keyboard. -->
+    <PreferenceCategory
+        android:key="keyboard_assistance_category"
+        android:title="@string/keyboard_assistance_category">
+        <SwitchPreference
+            android:key="show_virtual_keyboard_switch"
+            android:title="@string/show_ime"
+            android:summary="@string/show_ime_summary"
+            android:defaultValue="false" />
+
+        <!-- TODO: Get summary text from UX -->
+        <Preference
+            android:key="keyboard_shortcuts_helper"
+            android:title="@string/keyboard_shortcuts_helper"
+            android:summary="@null" />
+    </PreferenceCategory>
+</PreferenceScreen>
diff --git a/res/xml/virtual_keyboard_settings.xml b/res/xml/virtual_keyboard_settings.xml
new file mode 100644 (file)
index 0000000..e5a5f38
--- /dev/null
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+    android:title="@string/virtual_keyboard_category">
+    <!-- Enabled input method list will be populated programmatically here. -->
+    <PreferenceScreen
+        android:key="add_virtual_keyboard_screen"
+        android:title="@string/add_virtual_keyboard"
+        android:fragment="com.android.settings.inputmethod.AvailableVirtualKeyboardFragment" />
+</PreferenceScreen>
index bb2f948..ea39cf3 100644 (file)
@@ -38,6 +38,9 @@ public abstract class InstrumentedFragment extends PreferenceFragment {
     public static final int BILLING_CYCLE = UNDECLARED + 8;
     public static final int APP_DATA_USAGE = UNDECLARED + 9;
     public static final int USER_LOCALE_LIST = UNDECLARED + 10;
+    public static final int VIRTUAL_KEYBOARDS = UNDECLARED + 11;
+    public static final int PHYSICAL_KEYBOARDS = UNDECLARED + 12;
+    public static final int ENABLE_VIRTUAL_KEYBOARDS = UNDECLARED + 13;
 
     /**
      * Declare the view of this category.
index edd0b4c..1c636b8 100644 (file)
@@ -39,6 +39,7 @@ public class Settings extends SettingsActivity {
     public static class WifiSettingsActivity extends SettingsActivity { /* empty */ }
     public static class WifiP2pSettingsActivity extends SettingsActivity { /* empty */ }
     public static class InputMethodAndLanguageSettingsActivity extends SettingsActivity { /* empty */ }
+    public static class AvailableVirtualKeyboardActivity extends SettingsActivity { /* empty */ }
     public static class KeyboardLayoutPickerActivity extends SettingsActivity { /* empty */ }
     public static class InputMethodAndSubtypeEnablerActivity extends SettingsActivity { /* empty */ }
     public static class SpellCheckersSettingsActivity extends SettingsActivity { /* empty */ }
index 403e760..dc134ff 100644 (file)
@@ -77,6 +77,7 @@ import com.android.settings.deviceinfo.StorageSettings;
 import com.android.settings.fuelgauge.BatterySaverSettings;
 import com.android.settings.fuelgauge.PowerUsageDetail;
 import com.android.settings.fuelgauge.PowerUsageSummary;
+import com.android.settings.inputmethod.AvailableVirtualKeyboardFragment;
 import com.android.settings.inputmethod.InputMethodAndLanguageSettings;
 import com.android.settings.inputmethod.KeyboardLayoutPickerFragment;
 import com.android.settings.inputmethod.SpellCheckersSettings;
@@ -250,6 +251,7 @@ public class SettingsActivity extends SettingsDrawerActivity
             DateTimeSettings.class.getName(),
             LocaleListEditor.class.getName(),
             InputMethodAndLanguageSettings.class.getName(),
+            AvailableVirtualKeyboardFragment.class.getName(),
             SpellCheckersSettings.class.getName(),
             UserDictionaryList.class.getName(),
             UserDictionarySettings.class.getName(),
diff --git a/src/com/android/settings/inputmethod/AvailableVirtualKeyboardFragment.java b/src/com/android/settings/inputmethod/AvailableVirtualKeyboardFragment.java
new file mode 100644 (file)
index 0000000..2e4242a
--- /dev/null
@@ -0,0 +1,116 @@
+/*
+ * 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.settings.inputmethod;
+
+import android.app.Activity;
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.support.v7.preference.PreferenceScreen;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodManager;
+
+import com.android.settings.R;
+import com.android.settings.InstrumentedFragment;
+import com.android.settings.SettingsPreferenceFragment;
+
+import java.text.Collator;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+public final class AvailableVirtualKeyboardFragment extends SettingsPreferenceFragment
+        implements InputMethodPreference.OnSavePreferenceListener {
+
+    private final ArrayList<InputMethodPreference> mInputMethodPreferenceList = new ArrayList<>();
+    private InputMethodSettingValuesWrapper mInputMethodSettingValues;
+    private InputMethodManager mImm;
+    private DevicePolicyManager mDpm;
+
+    @Override
+    public void onCreatePreferences(Bundle bundle, String s) {
+        Activity activity = getActivity();
+        PreferenceScreen screen = getPreferenceManager().createPreferenceScreen(activity);
+        screen.setTitle(activity.getString(R.string.available_virtual_keyboard_category));
+        setPreferenceScreen(screen);
+        mInputMethodSettingValues = InputMethodSettingValuesWrapper.getInstance(activity);
+        mImm = activity.getSystemService(InputMethodManager.class);
+        mDpm = activity.getSystemService(DevicePolicyManager.class);
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        // Refresh internal states in mInputMethodSettingValues to keep the latest
+        // "InputMethodInfo"s and "InputMethodSubtype"s
+        mInputMethodSettingValues.refreshAllInputMethodAndSubtypes();
+        updateInputMethodPreferenceViews();
+    }
+
+    @Override
+    public void onSaveInputMethodPreference(final InputMethodPreference pref) {
+        final boolean hasHardwareKeyboard = getResources().getConfiguration().keyboard
+                == Configuration.KEYBOARD_QWERTY;
+        InputMethodAndSubtypeUtil.saveInputMethodSubtypeList(this, getContentResolver(),
+                mImm.getInputMethodList(), hasHardwareKeyboard);
+        // Update input method settings and preference list.
+        mInputMethodSettingValues.refreshAllInputMethodAndSubtypes();
+        for (final InputMethodPreference p : mInputMethodPreferenceList) {
+            p.updatePreferenceViews();
+        }
+    }
+
+    @Override
+    protected int getMetricsCategory() {
+        return InstrumentedFragment.ENABLE_VIRTUAL_KEYBOARDS;
+    }
+
+    private void updateInputMethodPreferenceViews() {
+        mInputMethodSettingValues.refreshAllInputMethodAndSubtypes();
+        // Clear existing "InputMethodPreference"s
+        mInputMethodPreferenceList.clear();
+        List<String> permittedList = mDpm.getPermittedInputMethodsForCurrentUser();
+        final Context context = getPrefContext();
+        final List<InputMethodInfo> imis = mInputMethodSettingValues.getInputMethodList();
+        final int N = (imis == null ? 0 : imis.size());
+        for (int i = 0; i < N; ++i) {
+            final InputMethodInfo imi = imis.get(i);
+            final boolean isAllowedByOrganization = permittedList == null
+                    || permittedList.contains(imi.getPackageName());
+            final InputMethodPreference pref = new InputMethodPreference(
+                    context, imi, true, isAllowedByOrganization, this);
+            mInputMethodPreferenceList.add(pref);
+        }
+        final Collator collator = Collator.getInstance();
+        Collections.sort(mInputMethodPreferenceList, new Comparator<InputMethodPreference>() {
+            @Override
+            public int compare(InputMethodPreference lhs, InputMethodPreference rhs) {
+                return lhs.compareTo(rhs, collator);
+            }
+        });
+        getPreferenceScreen().removeAll();
+        for (int i = 0; i < N; ++i) {
+            final InputMethodPreference pref = mInputMethodPreferenceList.get(i);
+            pref.setOrder(i);
+            getPreferenceScreen().addPreference(pref);
+            InputMethodAndSubtypeUtil.removeUnnecessaryNonPersistentPreference(pref);
+            pref.updatePreferenceViews();
+        }
+    }
+}
index 9022538..7564cc7 100644 (file)
@@ -157,12 +157,16 @@ public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment
                 startingIntent.getAction());
         if (mShowsOnlyFullImeAndKeyboardList) {
             getPreferenceScreen().removeAll();
-            getPreferenceScreen().addPreference(mHardKeyboardCategory);
+            if (mHardKeyboardCategory != null) {
+                getPreferenceScreen().addPreference(mHardKeyboardCategory);
+            }
             if (SHOW_INPUT_METHOD_SWITCHER_SETTINGS) {
                 getPreferenceScreen().addPreference(mShowInputMethodSelectorPref);
             }
-            mKeyboardSettingsCategory.removeAll();
-            getPreferenceScreen().addPreference(mKeyboardSettingsCategory);
+            if (mKeyboardSettingsCategory != null) {
+                mKeyboardSettingsCategory.removeAll();
+                getPreferenceScreen().addPreference(mKeyboardSettingsCategory);
+            }
         }
 
         // Build hard keyboard and game controller preference categories.
@@ -376,6 +380,10 @@ public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment
     }
 
     private void updateInputMethodPreferenceViews() {
+        if (mKeyboardSettingsCategory == null) {
+            return;
+        }
+
         synchronized (mInputMethodPreferenceList) {
             // Clear existing "InputMethodPreference"s
             for (final InputMethodPreference pref : mInputMethodPreferenceList) {
@@ -510,6 +518,10 @@ public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment
     }
 
     private void updateHardKeyboards() {
+        if (mHardKeyboardCategory == null) {
+            return;
+        }
+
         mHardKeyboardPreferenceList.clear();
         final int[] devices = InputDevice.getDeviceIds();
         for (int i = 0; i < devices.length; i++) {
index ad7a2b1..68ceeef 100644 (file)
@@ -301,7 +301,7 @@ public class KeyboardLayoutDialogFragment extends DialogFragment
         }
     }
 
-    private static final class KeyboardLayoutLoader extends AsyncTaskLoader<Keyboards> {
+    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/PhysicalKeyboardFragment.java b/src/com/android/settings/inputmethod/PhysicalKeyboardFragment.java
new file mode 100644 (file)
index 0000000..8f7d99e
--- /dev/null
@@ -0,0 +1,264 @@
+/*
+ * 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.settings.inputmethod;
+
+import android.app.Activity;
+import android.app.LoaderManager;
+import android.content.Intent;
+import android.content.Loader;
+import android.database.ContentObserver;
+import android.hardware.input.InputDeviceIdentifier;
+import android.hardware.input.InputManager;
+import android.hardware.input.KeyboardLayout;
+import android.os.Bundle;
+import android.os.Handler;
+import android.provider.Settings.Secure;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.Preference.OnPreferenceChangeListener;
+import android.support.v7.preference.PreferenceCategory;
+import android.support.v14.preference.SwitchPreference;
+import android.util.Pair;
+import android.view.InputDevice;
+import android.view.inputmethod.InputMethodInfo;
+import android.widget.Toast;
+
+import com.android.internal.inputmethod.InputMethodUtils;
+import com.android.internal.util.Preconditions;
+import com.android.settings.R;
+import com.android.settings.InstrumentedFragment;
+import com.android.settings.Settings;
+import com.android.settings.SettingsPreferenceFragment;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+
+public final class PhysicalKeyboardFragment extends SettingsPreferenceFragment
+        implements LoaderManager.LoaderCallbacks<KeyboardLayoutDialogFragment.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 final ArrayList<PreferenceCategory> mHardKeyboardPreferenceList = new ArrayList<>();
+    private final HashMap<Integer, Pair<InputDeviceIdentifier, PreferenceCategory>> mLoaderReference
+            = new HashMap<>();
+    private InputManager mIm;
+    private PreferenceCategory mKeyboardAssistanceCategory;
+    private SwitchPreference mShowVirtualKeyboardSwitch;
+    private InputMethodUtils.InputMethodSettings mSettings;
+
+    @Override
+    public void onCreatePreferences(Bundle bundle, String s) {
+        Activity activity = Preconditions.checkNotNull(getActivity());
+        addPreferencesFromResource(R.xml.physical_keyboard_settings);
+        mIm = Preconditions.checkNotNull(activity.getSystemService(InputManager.class));
+        mSettings = new InputMethodUtils.InputMethodSettings(
+                activity.getResources(),
+                getContentResolver(),
+                new HashMap<String, InputMethodInfo>(),
+                new ArrayList<InputMethodInfo>(),
+                USER_SYSTEM);
+        mKeyboardAssistanceCategory = Preconditions.checkNotNull(
+                (PreferenceCategory) findPreference(KEYBOARD_ASSISTANCE_CATEGORY));
+        mShowVirtualKeyboardSwitch = Preconditions.checkNotNull(
+                (SwitchPreference) mKeyboardAssistanceCategory.findPreference(
+                        SHOW_VIRTUAL_KEYBOARD_SWITCH));
+        findPreference(KEYBOARD_SHORTCUTS_HELPER).setOnPreferenceClickListener(
+                new Preference.OnPreferenceClickListener() {
+                    @Override
+                    public boolean onPreferenceClick(Preference preference) {
+                        toggleKeyboardShortcutsMenu();
+                        return true;
+                    }
+                });
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        updateHardKeyboards();
+        mIm.registerInputDeviceListener(this, null);
+        mShowVirtualKeyboardSwitch.setOnPreferenceChangeListener(
+                mShowVirtualKeyboardSwitchPreferenceChangeListener);
+        registerShowVirtualKeyboardSettingsObserver();
+    }
+
+    @Override
+    public void onPause() {
+        super.onPause();
+        clearHardKeyboardsData();
+        mIm.unregisterInputDeviceListener(this);
+        mShowVirtualKeyboardSwitch.setOnPreferenceChangeListener(null);
+        unregisterShowVirtualKeyboardSettingsObserver();
+    }
+
+    @Override
+    public Loader<KeyboardLayoutDialogFragment.Keyboards> onCreateLoader(int id, Bundle args) {
+        InputDeviceIdentifier deviceId = mLoaderReference.get(id).first;
+        return new KeyboardLayoutDialogFragment.KeyboardLayoutLoader(
+                getActivity().getBaseContext(), deviceId);
+    }
+
+    @Override
+    public void onLoadFinished(
+            final Loader<KeyboardLayoutDialogFragment.Keyboards> loader,
+            KeyboardLayoutDialogFragment.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());
+                pref.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
+                    @Override
+                    public boolean onPreferenceClick(Preference preference) {
+                        showKeyboardLayoutScreen(deviceId);
+                        return true;
+                    }
+                });
+                category.addPreference(pref);
+            }
+        }
+    }
+
+    @Override
+    public void onLoaderReset(Loader<KeyboardLayoutDialogFragment.Keyboards> loader) {}
+
+    @Override
+    public void onInputDeviceAdded(int deviceId) {
+        updateHardKeyboards();
+    }
+
+    @Override
+    public void onInputDeviceRemoved(int deviceId) {
+        updateHardKeyboards();
+    }
+
+    @Override
+    public void onInputDeviceChanged(int deviceId) {
+        updateHardKeyboards();
+    }
+
+    @Override
+    protected int getMetricsCategory() {
+        return InstrumentedFragment.PHYSICAL_KEYBOARDS;
+    }
+
+    private void updateHardKeyboards() {
+        clearHardKeyboardsData();
+        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);
+            }
+        }
+
+        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);
+        getPreferenceScreen().addPreference(mKeyboardAssistanceCategory);
+
+        for (int deviceIndex : mLoaderReference.keySet()) {
+            getLoaderManager().initLoader(deviceIndex, null, this);
+        }
+        updateShowVirtualKeyboardSwitch();
+    }
+
+    private void showKeyboardLayoutScreen(InputDeviceIdentifier inputDeviceIdentifier) {
+        final Intent intent = new Intent(Intent.ACTION_MAIN);
+        intent.setClass(getActivity(), Settings.KeyboardLayoutPickerActivity.class);
+        intent.putExtra(KeyboardLayoutPickerFragment.EXTRA_INPUT_DEVICE_IDENTIFIER,
+                inputDeviceIdentifier);
+        startActivity(intent);
+    }
+
+    private void clearHardKeyboardsData() {
+        getPreferenceScreen().removeAll();
+        for (int index = 0; index < mLoaderReference.size(); index++) {
+            getLoaderManager().destroyLoader(index);
+        }
+        mLoaderReference.clear();
+        mHardKeyboardPreferenceList.clear();
+    }
+
+    private void registerShowVirtualKeyboardSettingsObserver() {
+        unregisterShowVirtualKeyboardSettingsObserver();
+        getActivity().getContentResolver().registerContentObserver(
+                Secure.getUriFor(Secure.SHOW_IME_WITH_HARD_KEYBOARD),
+                false,
+                mContentObserver,
+                USER_SYSTEM);
+        updateShowVirtualKeyboardSwitch();
+    }
+
+    private void unregisterShowVirtualKeyboardSettingsObserver() {
+        getActivity().getContentResolver().unregisterContentObserver(mContentObserver);
+    }
+
+    private void updateShowVirtualKeyboardSwitch() {
+        mShowVirtualKeyboardSwitch.setChecked(mSettings.isShowImeWithHardKeyboardEnabled());
+    }
+
+    private void toggleKeyboardShortcutsMenu() {
+        // TODO: Implement.
+        Toast.makeText(getActivity(), "toggleKeyboardShortcutsMenu", Toast.LENGTH_SHORT).show();
+    }
+
+    private final OnPreferenceChangeListener mShowVirtualKeyboardSwitchPreferenceChangeListener =
+            new OnPreferenceChangeListener() {
+                @Override
+                public boolean onPreferenceChange(Preference preference, Object newValue) {
+                    mSettings.setShowImeWithHardKeyboard((Boolean) newValue);
+                    return false;
+                }
+            };
+
+    private final ContentObserver mContentObserver = new ContentObserver(new Handler(true)) {
+        @Override
+        public void onChange(boolean selfChange) {
+            updateShowVirtualKeyboardSwitch();
+        }
+    };
+}
diff --git a/src/com/android/settings/inputmethod/VirtualKeyboardFragment.java b/src/com/android/settings/inputmethod/VirtualKeyboardFragment.java
new file mode 100644 (file)
index 0000000..541c686
--- /dev/null
@@ -0,0 +1,107 @@
+/*
+ * 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.settings.inputmethod;
+
+import android.app.Activity;
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.os.Bundle;
+import android.support.v7.preference.Preference;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodManager;
+
+import com.android.internal.util.Preconditions;
+import com.android.settings.R;
+import com.android.settings.InstrumentedFragment;
+import com.android.settings.SettingsPreferenceFragment;
+
+import java.text.Collator;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+public final class VirtualKeyboardFragment extends SettingsPreferenceFragment {
+
+    private static final String ADD_VIRTUAL_KEYBOARD_SCREEN = "add_virtual_keyboard_screen";
+
+    private final ArrayList<InputMethodPreference> mInputMethodPreferenceList = new ArrayList<>();
+    private InputMethodManager mImm;
+    private DevicePolicyManager mDpm;
+    private Preference mAddVirtualKeyboardScreen;
+
+    @Override
+    public void onCreatePreferences(Bundle bundle, String s) {
+        Activity activity = Preconditions.checkNotNull(getActivity());
+        addPreferencesFromResource(R.xml.virtual_keyboard_settings);
+        mImm = Preconditions.checkNotNull(activity.getSystemService(InputMethodManager.class));
+        mDpm = Preconditions.checkNotNull(activity.getSystemService(DevicePolicyManager.class));
+        mAddVirtualKeyboardScreen = Preconditions.checkNotNull(
+                findPreference(ADD_VIRTUAL_KEYBOARD_SCREEN));
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        // Refresh internal states in mInputMethodSettingValues to keep the latest
+        // "InputMethodInfo"s and "InputMethodSubtype"s
+        updateInputMethodPreferenceViews();
+    }
+
+    @Override
+    protected int getMetricsCategory() {
+        return InstrumentedFragment.VIRTUAL_KEYBOARDS;
+    }
+
+    private void updateInputMethodPreferenceViews() {
+        // Clear existing "InputMethodPreference"s
+        mInputMethodPreferenceList.clear();
+        List<String> permittedList = mDpm.getPermittedInputMethodsForCurrentUser();
+        final Context context = getPrefContext();
+        final List<InputMethodInfo> imis = mImm.getEnabledInputMethodList();
+        final int N = (imis == null ? 0 : imis.size());
+        for (int i = 0; i < N; ++i) {
+            final InputMethodInfo imi = imis.get(i);
+            final boolean isAllowedByOrganization = permittedList == null
+                    || permittedList.contains(imi.getPackageName());
+            final InputMethodPreference pref = new InputMethodPreference(
+                    context,
+                    imi,
+                    false,  /* isImeEnabler */
+                    isAllowedByOrganization,
+                    null  /* this can be null since isImeEnabler is false */);
+            mInputMethodPreferenceList.add(pref);
+        }
+        final Collator collator = Collator.getInstance();
+        Collections.sort(mInputMethodPreferenceList, new Comparator<InputMethodPreference>() {
+            @Override
+            public int compare(InputMethodPreference lhs, InputMethodPreference rhs) {
+                return lhs.compareTo(rhs, collator);
+            }
+        });
+        getPreferenceScreen().removeAll();
+        for (int i = 0; i < N; ++i) {
+            final InputMethodPreference pref = mInputMethodPreferenceList.get(i);
+            pref.setOrder(i);
+            getPreferenceScreen().addPreference(pref);
+            InputMethodAndSubtypeUtil.removeUnnecessaryNonPersistentPreference(pref);
+            pref.updatePreferenceViews();
+        }
+        mAddVirtualKeyboardScreen.setOrder(N);
+        getPreferenceScreen().addPreference(mAddVirtualKeyboardScreen);
+    }
+}