Store the current IME's token in the system bar for changing the current IME to a shortcut IME from the system bar

Bug: 3212206
Bug: 3201828

- Added a shortcut IME button. This will be used for calling a shortcut IME (e.g. Voice input)
- Made the positions of IME buttons left aligned
- IME token is required to change IME because of the security reasons.

Change-Id: I48ba5e2509b3aa1bfd2394f9201427fa6b93c6d3
diff --git a/packages/SystemUI/res/layout-xlarge/status_bar.xml b/packages/SystemUI/res/layout-xlarge/status_bar.xml
index 488dbba..d4e9c53 100644
--- a/packages/SystemUI/res/layout-xlarge/status_bar.xml
+++ b/packages/SystemUI/res/layout-xlarge/status_bar.xml
@@ -167,11 +167,19 @@
                     />
             </com.android.systemui.statusbar.tablet.ShirtPocket>
             <com.android.systemui.statusbar.tablet.InputMethodButton
-                android:id="@+id/imeButton"
+                android:id="@+id/imeSwitchButton"
                 android:layout_width="wrap_content"
                 android:layout_height="match_parent"
                 android:layout_marginLeft="8dip"
-                android:src="@drawable/ic_sysbar_ime"
+                android:src="@drawable/ic_sysbar_ime_default"
+                android:visibility="invisible"
+                />
+            <com.android.systemui.statusbar.tablet.InputMethodButton
+                android:id="@+id/imeShortcutButton"
+                android:layout_width="wrap_content"
+                android:layout_height="match_parent"
+                android:layout_marginLeft="8dip"
+                android:src="@drawable/ic_sysbar_ime_default"
                 android:visibility="invisible"
                 />
         </LinearLayout>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index ed2ed1c..37939df 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -82,7 +82,7 @@
         public void animateCollapse();
         public void setLightsOn(boolean on);
         public void setMenuKeyVisible(boolean visible);
-        public void setIMEButtonVisible(boolean visible);
+        public void setIMEButtonVisible(IBinder token, boolean visible);
     }
 
     public CommandQueue(Callbacks callbacks, StatusBarIconList list) {
@@ -165,10 +165,10 @@
         }
     }
 
-    public void setIMEButtonVisible(boolean visible) {
+    public void setIMEButtonVisible(IBinder token, boolean visible) {
         synchronized (mList) {
             mHandler.removeMessages(MSG_SHOW_IME_BUTTON);
-            mHandler.obtainMessage(MSG_SHOW_IME_BUTTON, visible ? 1 : 0, 0, null).sendToTarget();
+            mHandler.obtainMessage(MSG_SHOW_IME_BUTTON, visible ? 1 : 0, 0, token).sendToTarget();
         }
     }
 
@@ -233,7 +233,7 @@
                     mCallbacks.setMenuKeyVisible(msg.arg1 != 0);
                     break;
                 case MSG_SHOW_IME_BUTTON:
-                    mCallbacks.setIMEButtonVisible(msg.arg1 != 0);
+                    mCallbacks.setIMEButtonVisible((IBinder)msg.obj, msg.arg1 != 0);
                     break;
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBar.java
index 731f6cd..d7f3730 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBar.java
@@ -65,9 +65,10 @@
         mBarService = IStatusBarService.Stub.asInterface(
                 ServiceManager.getService(Context.STATUS_BAR_SERVICE));
         int[] switches = new int[4];
+        ArrayList<IBinder> binders = new ArrayList<IBinder>();
         try {
             mBarService.registerStatusBar(mCommandQueue, iconList, notificationKeys, notifications,
-                    switches);
+                    switches, binders);
         } catch (RemoteException ex) {
             // If the system process isn't there we're doomed anyway.
         }
@@ -75,7 +76,8 @@
         disable(switches[0]);
         setLightsOn(switches[1] != 0);
         setMenuKeyVisible(switches[2] != 0);
-        setIMEButtonVisible(switches[3] != 0);
+        // StatusBarManagerService has a back up of IME token and it's restored here.
+        setIMEButtonVisible(binders.get(0), switches[3] != 0);
 
         // Set up the initial icon state
         int N = iconList.size();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index 915fa2f..5391178 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -1016,7 +1016,7 @@
 
     // Not supported
     public void setMenuKeyVisible(boolean visible) { }
-    public void setIMEButtonVisible(boolean visible) { }
+    public void setIMEButtonVisible(IBinder token, boolean visible) { }
 
     private class Launcher implements View.OnClickListener {
         private PendingIntent mIntent;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/InputMethodButton.java b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/InputMethodButton.java
index a025d63..6ebd568 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/InputMethodButton.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/InputMethodButton.java
@@ -20,6 +20,7 @@
 import android.content.pm.PackageManager;
 import android.graphics.drawable.Drawable;
 import android.os.Handler;
+import android.os.IBinder;
 import android.provider.Settings;
 import android.util.Log;
 import android.util.Slog;
@@ -33,24 +34,39 @@
 import com.android.server.InputMethodManagerService;
 import com.android.systemui.R;
 
+import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 public class InputMethodButton extends ImageView {
 
     private static final String  TAG = "StatusBar/InputMethodButton";
     private static final boolean DEBUG = false;
 
-    private boolean mKeyboardShown;
-    private ImageView mIcon;
+    private static final int ID_IME_SWITCH_BUTTON = R.id.imeSwitchButton;
+    private static final int ID_IME_SHORTCUT_BUTTON = R.id.imeShortcutButton;
+
     // other services we wish to talk to
-    private InputMethodManager mImm;
+    private final InputMethodManager mImm;
+    private final int mId;
+    // Cache of InputMethodsInfo
+    private final HashMap<String, InputMethodInfo> mInputMethodsInfo =
+            new HashMap<String, InputMethodInfo>();
+    private ImageView mIcon;
+    private IBinder mToken;
+    private boolean mKeyboardShown;
+    private InputMethodInfo mShortcutInfo;
+    private InputMethodSubtype mShortcutSubtype;
 
     public InputMethodButton(Context context, AttributeSet attrs) {
         super(context, attrs);
 
         mKeyboardShown = false;
+        // Resource Id of the input method button. This id is defined in status_bar.xml
+        mId = getId();
         // IME hookup
-        mImm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
+        mImm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
         // TODO: read the current icon & visibility state directly from the service
 
         // TODO: register for notifications about changes to visibility & subtype from service
@@ -58,14 +74,24 @@
         setOnClickListener(new View.OnClickListener() {
             @Override
             public void onClick(View v) {
-                mImm.showInputMethodSubtypePicker();
+                switch (mId) {
+                    case ID_IME_SWITCH_BUTTON:
+                        mImm.showInputMethodSubtypePicker();
+                        break;
+                    case ID_IME_SHORTCUT_BUTTON:
+                        if (mToken != null && mShortcutInfo != null) {
+                            mImm.setInputMethodAndSubtype(
+                                    mToken, mShortcutInfo.getId(), mShortcutSubtype);
+                        }
+                        break;
+                }
             }
         });
     }
 
     @Override
     protected void onAttachedToWindow() {
-        mIcon = (ImageView) findViewById(R.id.imeButton);
+        mIcon = (ImageView) findViewById(mId);
 
         refreshStatusIcon(mKeyboardShown);
     }
@@ -73,25 +99,44 @@
     private InputMethodInfo getCurrentInputMethodInfo() {
         String curInputMethodId = Settings.Secure.getString(getContext()
                 .getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
-        List<InputMethodInfo> imis = mImm.getEnabledInputMethodList();
-        if (curInputMethodId != null) {
-            for (InputMethodInfo imi: imis) {
-                if (imi.getId().equals(curInputMethodId)) {
-                    return imi;
-                }
+        if (!mInputMethodsInfo.containsKey(curInputMethodId)) {
+            mInputMethodsInfo.clear();
+            List<InputMethodInfo> imis = mImm.getInputMethodList();
+            for (int i = 0; i < imis.size(); ++i) {
+                InputMethodInfo imi = imis.get(i);
+                mInputMethodsInfo.put(imi.getId(), imi);
+            }
+        }
+        return mInputMethodsInfo.get(curInputMethodId);
+    }
+
+    // TODO: Need to show an appropriate drawable for this shortcut button,
+    // if there are two or more shortcut input methods contained in this button.
+    // And need to add other methods to handle multiple shortcuts as appropriate.
+    private Drawable getShortcutInputMethodAndSubtypeDrawable() {
+        Map<InputMethodInfo, List<InputMethodSubtype>> shortcuts =
+                mImm.getShortcutInputMethodsAndSubtypes();
+        if (shortcuts.size() > 0) {
+            for (InputMethodInfo imi: shortcuts.keySet()) {
+                List<InputMethodSubtype> subtypes = shortcuts.get(imi);
+                // TODO: Returns the first found IMI for now. Should handle all shortcuts as
+                // appropriate.
+                mShortcutInfo = imi;
+                // TODO: Pick up the first found subtype for now. Should handle all subtypes
+                // as appropriate.
+                mShortcutSubtype = subtypes.size() > 0 ? subtypes.get(0) : null;
+                return getSubtypeIcon(mShortcutInfo, mShortcutSubtype);
             }
         }
         return null;
     }
 
-    private Drawable getCurrentSubtypeIcon() {
+    private Drawable getSubtypeIcon(InputMethodInfo imi, InputMethodSubtype subtype) {
         final PackageManager pm = getContext().getPackageManager();
-        InputMethodInfo imi = getCurrentInputMethodInfo();
-        InputMethodSubtype subtype = mImm.getCurrentInputMethodSubtype();
-        Drawable icon = null;
         if (imi != null) {
             if (DEBUG) {
-                Log.d(TAG, "--- Update icons of IME: " + imi.getPackageName() + "," + subtype);
+                Log.d(TAG, "Update icons of IME: " + imi.getPackageName() + ","
+                        + subtype.getLocale() + "," + subtype.getMode());
             }
             if (subtype != null) {
                 return pm.getDrawable(imi.getPackageName(), subtype.getIconResId(),
@@ -104,7 +149,7 @@
                 try {
                     return pm.getApplicationInfo(imi.getPackageName(), 0).loadIcon(pm);
                 } catch (PackageManager.NameNotFoundException e) {
-                    Log.w(TAG, "Current IME cann't be found: " + imi.getPackageName());
+                    Log.w(TAG, "IME can't be found: " + imi.getPackageName());
                 }
             }
         }
@@ -118,7 +163,18 @@
         } else {
             setVisibility(View.VISIBLE);
         }
-        Drawable icon = getCurrentSubtypeIcon();
+        Drawable icon = null;
+        switch (mId) {
+            case ID_IME_SWITCH_BUTTON:
+                // TODO: Just showing the first shortcut IME subtype for now. Should handle all
+                // shortcuts as appropriate.
+                icon = getSubtypeIcon(getCurrentInputMethodInfo(),
+                        mImm.getCurrentInputMethodSubtype());
+                break;
+            case ID_IME_SHORTCUT_BUTTON:
+                icon = getShortcutInputMethodAndSubtypeDrawable();
+                break;
+        }
         if (icon == null) {
             mIcon.setImageResource(R.drawable.ic_sysbar_ime_default);
         } else {
@@ -126,7 +182,8 @@
         }
     }
 
-    public void setIMEButtonVisible(boolean visible) {
+    public void setIMEButtonVisible(IBinder token, boolean visible) {
+        mToken = token;
         mKeyboardShown = visible;
         refreshStatusIcon(mKeyboardShown);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java
index a934cd7..6db5b2b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java
@@ -107,7 +107,8 @@
     View mMenuButton;
     View mRecentButton;
 
-    InputMethodButton mInputMethodButton;
+    InputMethodButton mInputMethodSwitchButton;
+    InputMethodButton mInputMethodShortcutButton;
 
     NotificationPanel mNotificationPanel;
     NotificationPeekPanel mNotificationPeekWindow;
@@ -294,7 +295,8 @@
         mRecentButton.setOnClickListener(mOnClickListener);
 
         // The bar contents buttons
-        mInputMethodButton = (InputMethodButton) sb.findViewById(R.id.imeButton);
+        mInputMethodSwitchButton = (InputMethodButton) sb.findViewById(R.id.imeSwitchButton);
+        mInputMethodShortcutButton = (InputMethodButton) sb.findViewById(R.id.imeShortcutButton);
 
         // "shadows" of the status bar features, for lights-out mode
         mBackShadow = sb.findViewById(R.id.back_shadow);
@@ -645,11 +647,12 @@
         mMenuButton.setVisibility(visible ? View.VISIBLE : View.GONE);
     }
 
-    public void setIMEButtonVisible(boolean visible) {
+    public void setIMEButtonVisible(IBinder token, boolean visible) {
         if (DEBUG) {
             Slog.d(TAG, (visible?"showing":"hiding") + " the IME button");
         }
-        mInputMethodButton.setIMEButtonVisible(visible);
+        mInputMethodSwitchButton.setIMEButtonVisible(token, visible);
+        mInputMethodShortcutButton.setIMEButtonVisible(token, visible);
     }
 
     private void setAreThereNotifications() {