Merge "Add an API to get shortcut IMEs"
diff --git a/api/current.xml b/api/current.xml
index 42cdaf2..4025f3c 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -156003,22 +156003,22 @@
  visibility="public"
 >
 </field>
-<field name="GROUP_VISIBLE"
+<field name="GROUP_IS_READ_ONLY"
  type="java.lang.String"
  transient="false"
  volatile="false"
- value="&quot;group_visible&quot;"
+ value="&quot;group_is_read_only&quot;"
  static="true"
  final="true"
  deprecated="not deprecated"
  visibility="public"
 >
 </field>
-<field name="GROUP_IS_READ_ONLY"
+<field name="GROUP_VISIBLE"
  type="java.lang.String"
  transient="false"
  volatile="false"
- value="&quot;group_is_read_only&quot;"
+ value="&quot;group_visible&quot;"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -220894,6 +220894,17 @@
  visibility="public"
 >
 </method>
+<method name="getShortcutInputMethodsAndSubtypes"
+ return="java.util.List&lt;android.util.Pair&lt;android.view.inputmethod.InputMethodInfo, android.view.inputmethod.InputMethodSubtype&gt;&gt;"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="hideSoftInputFromInputMethod"
  return="void"
  abstract="false"
@@ -248473,7 +248484,7 @@
  deprecated="not deprecated"
  visibility="public"
 >
-<parameter name="t" type="T">
+<parameter name="arg0" type="T">
 </parameter>
 </method>
 </interface>
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index fe55f34..a5f3ade 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -23,6 +23,7 @@
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
+import android.os.Parcelable;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.ServiceManager;
@@ -47,6 +48,7 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
@@ -1452,6 +1454,38 @@
         }
     }
 
+    public List<Pair<InputMethodInfo, InputMethodSubtype>> getShortcutInputMethodsAndSubtypes() {
+        synchronized (mH) {
+            List<Pair<InputMethodInfo, InputMethodSubtype>> ret =
+                    new ArrayList<Pair<InputMethodInfo, InputMethodSubtype>>();
+            try {
+                // TODO: We should change the return type from List<Object> to List<Parcelable>
+                List<Object> info = mService.getShortcutInputMethodsAndSubtypes();
+                // "info" has imi1, subtype1, imi2, subtype2, imi3, subtype3,..... in the list
+                Object imi;
+                Object subtype;
+                if (info != null && info.size() > 0) {
+                    final int N = info.size();
+                    if (N % 2 == 0) {
+                        for (int i = 0; i < N;) {
+                            if ((imi = info.get(i++)) instanceof InputMethodInfo) {
+                                subtype = info.get(i++);
+                                ret.add(new Pair<InputMethodInfo, InputMethodSubtype> (
+                                        (InputMethodInfo)imi,
+                                        (subtype instanceof InputMethodSubtype) ?
+                                                (InputMethodSubtype)subtype : null));
+                            }
+                        }
+                    } else {
+                        Log.w(TAG, "The size of list was illegal.");
+                    }
+                }
+            } catch (RemoteException e) {
+                Log.w(TAG, "IME died: " + mCurId, e);
+            }
+            return ret;
+        }
+    }
     public boolean switchToLastInputMethod(IBinder imeToken) {
         synchronized (mH) {
             try {
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index 63d05ec..125b210 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -32,6 +32,9 @@
     List<InputMethodInfo> getInputMethodList();
     List<InputMethodInfo> getEnabledInputMethodList();
     List<InputMethodSubtype> getEnabledInputMethodSubtypeList(in InputMethodInfo imi);
+    // TODO: We should change the return type from List to List<Parcelable>
+    // Currently there is a bug that aidl doesn't accept List<Parcelable>
+    List getShortcutInputMethodsAndSubtypes();
     void addClient(in IInputMethodClient client,
             in IInputContext inputContext, int uid, int pid);
     void removeClient(in IInputMethodClient client);
diff --git a/services/java/com/android/server/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java
index e14345b..b2cc6bd 100644
--- a/services/java/com/android/server/InputMethodManagerService.java
+++ b/services/java/com/android/server/InputMethodManagerService.java
@@ -55,6 +55,7 @@
 import android.os.IInterface;
 import android.os.Message;
 import android.os.Parcel;
+import android.os.Parcelable;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.ServiceManager;
@@ -120,6 +121,9 @@
     // If IME doesn't support the system locale, the default subtype will be the first defined one.
     private static final int DEFAULT_SUBTYPE_ID = 0;
 
+    private static final String SUBTYPE_MODE_KEYBOARD = "keyboard";
+    private static final String SUBTYPE_MODE_VOICE = "voice";
+
     final Context mContext;
     final Handler mHandler;
     final InputMethodSettings mSettings;
@@ -235,6 +239,11 @@
      */
     private InputMethodSubtype mCurrentSubtype;
 
+    // This list contains the pairs of InputMethodInfo and InputMethodSubtype.
+    private List<Pair<InputMethodInfo, InputMethodSubtype>> mShortcutInputMethodsAndSubtypes;
+    // This list is used for returning the pairs of InputMethodInfo and InputMethodSubtype through
+    // aidl. This list has imi1, subtype1 imi2, subtype2...
+    private List mShortcutInputMethodsAndSubtypesObjectList;
 
     /**
      * Set to true if our ServiceConnection is currently actively bound to
@@ -983,6 +992,7 @@
                 mCurMethodId = null;
                 unbindCurrentMethodLocked(true);
             }
+            mShortcutInputMethodsAndSubtypes = null;
         } else {
             // There is no longer an input method set, so stop any current one.
             mCurMethodId = null;
@@ -1910,25 +1920,31 @@
         return NOT_A_SUBTYPE_ID;
     }
 
-    // If there are no selected subtypes, tries finding the most applicable one according to the
-    // current system locale
-    private int findApplicableSubtypeLocked(String id) {
-        InputMethodInfo imi = mMethodMap.get(id);
-        if (imi == null) {
-            return NOT_A_SUBTYPE_ID;
-        }
-        ArrayList<InputMethodSubtype> subtypes = imi.getSubtypes();
+    /**
+     * If there are no selected subtypes, tries finding the most applicable one according to the
+     * given locale.
+     * @param subtypes this function will search the most applicable subtype in subtypes
+     * @param mode subtypes will be filtered by mode
+     * @param locale subtypes will be filtered by locale
+     * @param defaultSubtypeId if this function can't find the most applicable subtype, it will
+     * return defaultSubtypeId
+     * @return the most applicable subtypeId
+     */
+    private int findLastResortApplicableSubtypeLocked(
+            List<InputMethodSubtype> subtypes, String mode, String locale, int defaultSubtypeId) {
         if (subtypes == null || subtypes.size() == 0) {
             return NOT_A_SUBTYPE_ID;
         }
-        final String locale = mContext.getResources().getConfiguration().locale.toString();
+        if (TextUtils.isEmpty(locale)) {
+            locale = mContext.getResources().getConfiguration().locale.toString();
+        }
         final String language = locale.substring(0, 2);
         boolean partialMatchFound = false;
-        int applicableSubtypeId = DEFAULT_SUBTYPE_ID;
+        int applicableSubtypeId = defaultSubtypeId;
         for (int i = 0; i < subtypes.size(); ++i) {
             final String subtypeLocale = subtypes.get(i).getLocale();
-            // An applicable subtype should be a keyboard subtype
-            if (subtypes.get(i).getMode().equalsIgnoreCase("keyboard")) {
+            // An applicable subtype should match "mode".
+            if (subtypes.get(i).getMode().equalsIgnoreCase(mode)) {
                 if (locale.equals(subtypeLocale)) {
                     // Exact match (e.g. system locale is "en_US" and subtype locale is "en_US")
                     applicableSubtypeId = i;
@@ -1950,34 +1966,129 @@
         return applicableSubtypeId;
     }
 
+    // If there are no selected shortcuts, tries finding the most applicable ones.
+    private Pair<InputMethodInfo, InputMethodSubtype>
+            findLastResortApplicableShortcutInputMethodAndSubtypeLocked(String mode) {
+        List<InputMethodInfo> imis = mSettings.getEnabledInputMethodListLocked();
+        InputMethodInfo mostApplicableIMI = null;
+        int mostApplicableSubtypeId = NOT_A_SUBTYPE_ID;
+        boolean foundInSystemIME = false;
+
+        // Search applicable subtype for each InputMethodInfo
+        for (InputMethodInfo imi: imis) {
+            int subtypeId = NOT_A_SUBTYPE_ID;
+            if (mCurrentSubtype != null) {
+                // 1. Search with the current subtype's locale and the enabled subtypes
+                subtypeId = findLastResortApplicableSubtypeLocked(
+                        mSettings.getEnabledInputMethodSubtypeListLocked(
+                        imi), mode, mCurrentSubtype.getLocale(), NOT_A_SUBTYPE_ID);
+                if (subtypeId == NOT_A_SUBTYPE_ID) {
+                    // 2. Search with the current subtype's locale and all subtypes
+                    subtypeId = findLastResortApplicableSubtypeLocked(imi.getSubtypes(),
+                            mode, mCurrentSubtype.getLocale(), NOT_A_SUBTYPE_ID);
+                }
+            }
+            // 3. Search with the system locale and the enabled subtypes
+            if (subtypeId == NOT_A_SUBTYPE_ID) {
+                subtypeId = findLastResortApplicableSubtypeLocked(
+                        mSettings.getEnabledInputMethodSubtypeListLocked(
+                        imi), mode, null, NOT_A_SUBTYPE_ID);
+            }
+            if (subtypeId == NOT_A_SUBTYPE_ID) {
+                // 4. Search with the system locale and all subtypes
+                subtypeId = findLastResortApplicableSubtypeLocked(imi.getSubtypes(),
+                        mode, null, NOT_A_SUBTYPE_ID);
+            }
+            if (subtypeId != NOT_A_SUBTYPE_ID) {
+                if (imi.getId().equals(mCurMethodId)) {
+                    // The current input method is the most applicable IME.
+                    mostApplicableIMI = imi;
+                    mostApplicableSubtypeId = subtypeId;
+                    break;
+                } else if ((imi.getServiceInfo().applicationInfo.flags
+                        & ApplicationInfo.FLAG_SYSTEM) != 0) {
+                    // The system input method is 2nd applicable IME.
+                    mostApplicableIMI = imi;
+                    mostApplicableSubtypeId = subtypeId;
+                    foundInSystemIME = true;
+                } else if (!foundInSystemIME) {
+                    mostApplicableIMI = imi;
+                    mostApplicableSubtypeId = subtypeId;
+                }
+            }
+        }
+        if (DEBUG) {
+            Slog.w(TAG, "Most applicable shortcut input method subtype was:"
+                    + mostApplicableIMI.getId() + "," + mostApplicableSubtypeId);
+        }
+        if (mostApplicableIMI != null && mostApplicableSubtypeId != NOT_A_SUBTYPE_ID) {
+            ArrayList<Parcelable> ret = new ArrayList<Parcelable>(2);
+            return new Pair<InputMethodInfo, InputMethodSubtype> (mostApplicableIMI,
+                    mostApplicableIMI.getSubtypes().get(mostApplicableSubtypeId));
+        } else {
+            return null;
+        }
+    }
+
     /**
      * @return Return the current subtype of this input method.
      */
     public InputMethodSubtype getCurrentInputMethodSubtype() {
+        boolean subtypeIsSelected = false;
+        try {
+            subtypeIsSelected = Settings.Secure.getInt(mContext.getContentResolver(),
+                    Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE) != NOT_A_SUBTYPE_ID;
+        } catch (SettingNotFoundException e) {
+        }
         synchronized (mMethodMap) {
-            boolean subtypeIsSelected = false;
-            try {
-                subtypeIsSelected = Settings.Secure.getInt(mContext.getContentResolver(),
-                                Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE) != NOT_A_SUBTYPE_ID;
-            } catch (SettingNotFoundException e) {
-            }
             if (!subtypeIsSelected || mCurrentSubtype == null) {
-                String lastInputMethodId =
-                        Settings.Secure.getString(mContext.getContentResolver(),
-                                Settings.Secure.DEFAULT_INPUT_METHOD);
+                String lastInputMethodId = Settings.Secure.getString(
+                        mContext.getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
                 int subtypeId = getSelectedInputMethodSubtypeId(lastInputMethodId);
                 if (subtypeId == NOT_A_SUBTYPE_ID) {
-                    subtypeId = findApplicableSubtypeLocked(lastInputMethodId);
+                    InputMethodInfo imi = mMethodMap.get(lastInputMethodId);
+                    if (imi != null) {
+                        // If there are no selected subtypes, the framework will try to find
+                        // the most applicable subtype from all subtypes whose mode is
+                        // SUBTYPE_MODE_KEYBOARD. This is an exceptional case, so we will hardcode
+                        // the mode.
+                        subtypeId = findLastResortApplicableSubtypeLocked(imi.getSubtypes(),
+                                SUBTYPE_MODE_KEYBOARD, null, DEFAULT_SUBTYPE_ID);
+                    }
                 }
                 if (subtypeId != NOT_A_SUBTYPE_ID) {
                     mCurrentSubtype =
                             mMethodMap.get(lastInputMethodId).getSubtypes().get(subtypeId);
+                } else {
+                    mCurrentSubtype = null;
                 }
             }
             return mCurrentSubtype;
         }
     }
 
+    // TODO: We should change the return type from List to List<Parcelable>
+    public List getShortcutInputMethodsAndSubtypes() {
+        synchronized (mMethodMap) {
+            if (mShortcutInputMethodsAndSubtypesObjectList != null) {
+                // If there are no selected shortcut subtypes, the framework will try to find
+                // the most applicable subtype from all subtypes whose mode is
+                // SUBTYPE_MODE_VOICE. This is an exceptional case, so we will hardcode the mode.
+                mShortcutInputMethodsAndSubtypes =
+                        new ArrayList<Pair<InputMethodInfo, InputMethodSubtype>>();
+                mShortcutInputMethodsAndSubtypes.add(
+                        findLastResortApplicableShortcutInputMethodAndSubtypeLocked(
+                                SUBTYPE_MODE_VOICE));
+                mShortcutInputMethodsAndSubtypesObjectList = new ArrayList<Parcelable>();
+                for (Pair ime: mShortcutInputMethodsAndSubtypes) {
+                    mShortcutInputMethodsAndSubtypesObjectList.add(ime.first);
+                    mShortcutInputMethodsAndSubtypesObjectList.add(ime.second);
+                }
+            }
+            return mShortcutInputMethodsAndSubtypesObjectList;
+        }
+    }
+
     public boolean setCurrentInputMethodSubtype(InputMethodSubtype subtype) {
         synchronized (mMethodMap) {
             if (subtype != null && mCurMethodId != null) {