Enable multi-user function for IMF

Bug: 6931482

Change-Id: Ib23849d352db33f0747aa9d5a178f00ac726c13b
diff --git a/services/java/com/android/server/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java
index c685473..6952d72 100644
--- a/services/java/com/android/server/InputMethodManagerService.java
+++ b/services/java/com/android/server/InputMethodManagerService.java
@@ -34,7 +34,9 @@
 import org.xmlpull.v1.XmlSerializer;
 
 import android.app.ActivityManagerNative;
+import android.app.AppGlobals;
 import android.app.AlertDialog;
+import android.app.IUserSwitchObserver;
 import android.app.KeyguardManager;
 import android.app.Notification;
 import android.app.NotificationManager;
@@ -49,6 +51,7 @@
 import android.content.IntentFilter;
 import android.content.ServiceConnection;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ResolveInfo;
@@ -63,12 +66,15 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.IInterface;
+import android.os.IRemoteCallback;
 import android.os.Message;
+import android.os.Process;
 import android.os.Parcel;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.ServiceManager;
 import android.os.SystemClock;
+import android.os.UserHandle;
 import android.provider.Settings;
 import android.provider.Settings.Secure;
 import android.provider.Settings.SettingNotFoundException;
@@ -378,6 +384,8 @@
     private InputMethodInfo[] mIms;
     private int[] mSubtypeIds;
     private Locale mLastSystemLocale;
+    private final MyPackageMonitor mMyPackageMonitor = new MyPackageMonitor();
+    private final IPackageManager mIPackageManager;
 
     class SettingsObserver extends ContentObserver {
         SettingsObserver(Handler handler) {
@@ -398,37 +406,55 @@
         }
     }
 
-    class ScreenOnOffReceiver extends android.content.BroadcastReceiver {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)) {
-                mScreenOn = true;
-                refreshImeWindowVisibilityLocked();
-            } else if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
-                mScreenOn = false;
-                setImeWindowVisibilityStatusHiddenLocked();
-            } else if (intent.getAction().equals(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)) {
-                hideInputMethodMenu();
-                return;
-            } else {
-                Slog.w(TAG, "Unexpected intent " + intent);
-            }
-
+    class ImmsBroadcastReceiver extends android.content.BroadcastReceiver {
+        private void updateActive() {
             // Inform the current client of the change in active status
             if (mCurClient != null && mCurClient.client != null) {
                 executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO(
                         MSG_SET_ACTIVE, mScreenOn ? 1 : 0, mCurClient));
             }
         }
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            final String action = intent.getAction();
+            if (Intent.ACTION_SCREEN_ON.equals(action)) {
+                mScreenOn = true;
+                refreshImeWindowVisibilityLocked();
+                updateActive();
+                return;
+            } else if (Intent.ACTION_SCREEN_OFF.equals(action)) {
+                mScreenOn = false;
+                setImeWindowVisibilityStatusHiddenLocked();
+                updateActive();
+                return;
+            } else if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)) {
+                hideInputMethodMenu();
+                // No need to updateActive
+                return;
+            } else {
+                Slog.w(TAG, "Unexpected intent " + intent);
+            }
+        }
     }
 
     class MyPackageMonitor extends PackageMonitor {
-        
+        private boolean isChangingPackagesOfCurrentUser() {
+            final int userId = getChangingUserId();
+            final boolean retval = userId == mSettings.getCurrentUserId();
+            if (DEBUG) {
+                Slog.d(TAG, "--- ignore this call back from a background user: " + userId);
+            }
+            return retval;
+        }
+
         @Override
         public boolean onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit) {
+            if (!isChangingPackagesOfCurrentUser()) {
+                return false;
+            }
             synchronized (mMethodMap) {
-                String curInputMethodId = Settings.Secure.getString(mContext
-                        .getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
+                String curInputMethodId = mSettings.getSelectedInputMethod();
                 final int N = mMethodList.size();
                 if (curInputMethodId != null) {
                     for (int i=0; i<N; i++) {
@@ -453,10 +479,12 @@
 
         @Override
         public void onSomePackagesChanged() {
+            if (!isChangingPackagesOfCurrentUser()) {
+                return;
+            }
             synchronized (mMethodMap) {
                 InputMethodInfo curIm = null;
-                String curInputMethodId = Settings.Secure.getString(mContext
-                        .getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
+                String curInputMethodId = mSettings.getSelectedInputMethod();
                 final int N = mMethodList.size();
                 if (curInputMethodId != null) {
                     for (int i=0; i<N; i++) {
@@ -489,9 +517,9 @@
                             || change == PACKAGE_PERMANENT_CHANGE) {
                         ServiceInfo si = null;
                         try {
-                            si = mContext.getPackageManager().getServiceInfo(
-                                    curIm.getComponent(), 0);
-                        } catch (PackageManager.NameNotFoundException ex) {
+                            si = mIPackageManager.getServiceInfo(
+                                    curIm.getComponent(), 0, mSettings.getCurrentUserId());
+                        } catch (RemoteException ex) {
                         }
                         if (si == null) {
                             // Uh oh, current input method is no longer around!
@@ -565,6 +593,7 @@
     }
 
     public InputMethodManagerService(Context context, WindowManagerService windowManager) {
+        mIPackageManager = AppGlobals.getPackageManager();
         mContext = context;
         mRes = context.getResources();
         mHandler = new Handler(this);
@@ -601,23 +630,44 @@
         }
         mImListManager = new InputMethodAndSubtypeListManager(context, this);
 
-        (new MyPackageMonitor()).register(mContext, null, true);
-
-        IntentFilter screenOnOffFilt = new IntentFilter();
-        screenOnOffFilt.addAction(Intent.ACTION_SCREEN_ON);
-        screenOnOffFilt.addAction(Intent.ACTION_SCREEN_OFF);
-        screenOnOffFilt.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
-        mContext.registerReceiver(new ScreenOnOffReceiver(), screenOnOffFilt);
+        final IntentFilter broadcastFilter = new IntentFilter();
+        broadcastFilter.addAction(Intent.ACTION_SCREEN_ON);
+        broadcastFilter.addAction(Intent.ACTION_SCREEN_OFF);
+        broadcastFilter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
+        mContext.registerReceiver(new ImmsBroadcastReceiver(), broadcastFilter);
 
         mNotificationShown = false;
+        int userId = 0;
+        try {
+            ActivityManagerNative.getDefault().registerUserSwitchObserver(
+                    new IUserSwitchObserver.Stub() {
+                        @Override
+                        public void onUserSwitching(int newUserId, IRemoteCallback reply) {
+                            switchUser(newUserId);
+                            if (reply != null) {
+                                try {
+                                    reply.sendResult(null);
+                                } catch (RemoteException e) {
+                                }
+                            }
+                        }
+
+                        @Override
+                        public void onUserSwitchComplete(int newUserId) throws RemoteException {
+                        }
+                    });
+            userId = ActivityManagerNative.getDefault().getCurrentUser().id;
+        } catch (RemoteException e) {
+            Slog.w(TAG, "Couldn't get current user ID; guessing it's 0", e);
+        }
+        mMyPackageMonitor.register(mContext, null, true);
 
         // mSettings should be created before buildInputMethodListLocked
         mSettings = new InputMethodSettings(
-                mRes, context.getContentResolver(), mMethodMap, mMethodList);
+                mRes, context.getContentResolver(), mMethodMap, mMethodList, userId);
 
         // Just checking if defaultImiId is empty or not
-        final String defaultImiId = Settings.Secure.getString(
-                mContext.getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
+        final String defaultImiId = mSettings.getSelectedInputMethod();
         mImeSelectedOnBoot = !TextUtils.isEmpty(defaultImiId);
 
         buildInputMethodListLocked(mMethodList, mMethodMap);
@@ -646,24 +696,6 @@
                 }, filter);
     }
 
-    private void checkCurrentLocaleChangedLocked() {
-        if (!mSystemReady) {
-            // not system ready
-            return;
-        }
-        final Locale newLocale = mRes.getConfiguration().locale;
-        if (newLocale != null && !newLocale.equals(mLastSystemLocale)) {
-            if (DEBUG) {
-                Slog.i(TAG, "Locale has been changed to " + newLocale);
-            }
-            buildInputMethodListLocked(mMethodList, mMethodMap);
-            // Reset the current ime to the proper one
-            resetDefaultImeLocked(mContext);
-            updateFromSettingsLocked();
-            mLastSystemLocale = newLocale;
-        }
-    }
-
     private void resetDefaultImeLocked(Context context) {
         // Do not reset the default (current) IME when it is a 3rd-party IME
         if (mCurMethodId != null && !isSystemIme(mMethodMap.get(mCurMethodId))) {
@@ -688,6 +720,52 @@
         }
     }
 
+    private void resetAllInternalStateLocked(boolean updateOnlyWhenLocaleChanged) {
+        if (!mSystemReady) {
+            // not system ready
+            return;
+        }
+        final Locale newLocale = mRes.getConfiguration().locale;
+        if (!updateOnlyWhenLocaleChanged
+                || (newLocale != null && !newLocale.equals(mLastSystemLocale))) {
+            if (!updateOnlyWhenLocaleChanged) {
+                hideCurrentInputLocked(0, null);
+                mCurMethodId = null;
+                unbindCurrentMethodLocked(true);
+            }
+            if (DEBUG) {
+                Slog.i(TAG, "Locale has been changed to " + newLocale);
+            }
+            buildInputMethodListLocked(mMethodList, mMethodMap);
+            if (!updateOnlyWhenLocaleChanged) {
+                final String selectedImiId = mSettings.getSelectedInputMethod();
+                if (TextUtils.isEmpty(selectedImiId)) {
+                    // This is the first time of the user switch and
+                    // set the current ime to the proper one.
+                    resetDefaultImeLocked(mContext);
+                }
+            }
+            updateFromSettingsLocked();
+            mLastSystemLocale = newLocale;
+            if (!updateOnlyWhenLocaleChanged) {
+                try {
+                    startInputInnerLocked();
+                } catch (RuntimeException e) {
+                    Slog.w(TAG, "Unexpected exception", e);
+                }
+            }
+        }
+    }
+
+    private void checkCurrentLocaleChangedLocked() {
+        resetAllInternalStateLocked(true);
+    }
+
+    private void switchUser(int newUserId) {
+        mSettings.setCurrentUserId(newUserId);
+        resetAllInternalStateLocked(false);
+    }
+
     private boolean isValidSystemDefaultIme(InputMethodInfo imi, Context context) {
         if (!mSystemReady) {
             return false;
@@ -748,10 +826,13 @@
 
     public void systemReady(StatusBarManagerService statusBar) {
         synchronized (mMethodMap) {
+            if (DEBUG) {
+                Slog.d(TAG, "--- systemReady");
+            }
             if (!mSystemReady) {
                 mSystemReady = true;
-                mKeyguardManager = (KeyguardManager)
-                        mContext.getSystemService(Context.KEYGUARD_SERVICE);
+                mKeyguardManager =
+                        (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
                 mNotificationManager = (NotificationManager)
                         mContext.getSystemService(Context.NOTIFICATION_SERVICE);
                 mStatusBar = statusBar;
@@ -802,8 +883,42 @@
         setImeWindowStatus(mCurToken, mImeWindowVis, mBackDisposition);
     }
 
+    // ---------------------------------------------------------------------------------------
+    // Check whether or not this is a valid IPC. Assumes an IPC is valid when either
+    // 1) it comes from the system process
+    // 2) the calling process' user id is identical to the current user id IMMS thinks.
+    private boolean calledFromValidUser() {
+        final int uid = Binder.getCallingUid();
+        final int userId = UserHandle.getUserId(uid);
+        if (DEBUG) {
+            Slog.d(TAG, "--- calledFromForegroundUserOrSystemProcess ? "
+                    + "calling uid = " + uid + " system uid = " + Process.SYSTEM_UID
+                    + " calling userId = " + userId + ", foreground user id = "
+                    + mSettings.getCurrentUserId());
+        }
+        if (uid == Process.SYSTEM_UID || userId == mSettings.getCurrentUserId()) {
+            return true;
+        } else {
+            Slog.w(TAG, "--- IPC called from background users. Ignore. \n" + getStackTrace());
+            return false;
+        }
+    }
+
+    private boolean bindCurrentInputMethodService(
+            Intent service, ServiceConnection conn, int flags) {
+        if (service == null || conn == null) {
+            Slog.e(TAG, "--- bind failed: service = " + service + ", conn = " + conn);
+            return false;
+        }
+        return mContext.bindService(service, conn, flags, mSettings.getCurrentUserId());
+    }
+
     @Override
     public List<InputMethodInfo> getInputMethodList() {
+        // TODO: Make this work even for non-current users?
+        if (!calledFromValidUser()) {
+            return Collections.emptyList();
+        }
         synchronized (mMethodMap) {
             return new ArrayList<InputMethodInfo>(mMethodList);
         }
@@ -811,6 +926,10 @@
 
     @Override
     public List<InputMethodInfo> getEnabledInputMethodList() {
+        // TODO: Make this work even for non-current users?
+        if (!calledFromValidUser()) {
+            return Collections.emptyList();
+        }
         synchronized (mMethodMap) {
             return mSettings.getEnabledInputMethodListLocked();
         }
@@ -820,7 +939,7 @@
             getExplicitlyOrImplicitlyEnabledInputMethodsAndSubtypeListLocked() {
         HashMap<InputMethodInfo, List<InputMethodSubtype>> enabledInputMethodAndSubtypes =
                 new HashMap<InputMethodInfo, List<InputMethodSubtype>>();
-        for (InputMethodInfo imi: getEnabledInputMethodList()) {
+        for (InputMethodInfo imi: mSettings.getEnabledInputMethodListLocked()) {
             enabledInputMethodAndSubtypes.put(
                     imi, getEnabledInputMethodSubtypeListLocked(imi, true));
         }
@@ -843,6 +962,10 @@
     @Override
     public List<InputMethodSubtype> getEnabledInputMethodSubtypeList(InputMethodInfo imi,
             boolean allowsImplicitlySelectedSubtypes) {
+        // TODO: Make this work even for non-current users?
+        if (!calledFromValidUser()) {
+            return Collections.emptyList();
+        }
         synchronized (mMethodMap) {
             return getEnabledInputMethodSubtypeListLocked(imi, allowsImplicitlySelectedSubtypes);
         }
@@ -851,6 +974,9 @@
     @Override
     public void addClient(IInputMethodClient client,
             IInputContext inputContext, int uid, int pid) {
+        if (!calledFromValidUser()) {
+            return;
+        }
         synchronized (mMethodMap) {
             mClients.put(client.asBinder(), new ClientState(client,
                     inputContext, uid, pid));
@@ -859,6 +985,9 @@
 
     @Override
     public void removeClient(IInputMethodClient client) {
+        if (!calledFromValidUser()) {
+            return;
+        }
         synchronized (mMethodMap) {
             mClients.remove(client.asBinder());
         }
@@ -1060,7 +1189,7 @@
                 com.android.internal.R.string.input_method_binding_label);
         mCurIntent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity(
                 mContext, 0, new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS), 0));
-        if (mContext.bindService(mCurIntent, this, Context.BIND_AUTO_CREATE
+        if (bindCurrentInputMethodService(mCurIntent, this, Context.BIND_AUTO_CREATE
                 | Context.BIND_NOT_VISIBLE)) {
             mLastBindTime = SystemClock.uptimeMillis();
             mHaveConnection = true;
@@ -1084,6 +1213,9 @@
     @Override
     public InputBindResult startInput(IInputMethodClient client,
             IInputContext inputContext, EditorInfo attribute, int controlFlags) {
+        if (!calledFromValidUser()) {
+            return null;
+        }
         synchronized (mMethodMap) {
             final long ident = Binder.clearCallingIdentity();
             try {
@@ -1242,10 +1374,12 @@
                     if (DEBUG) Slog.d(TAG, "show a small icon for the input method");
                     CharSequence contentDescription = null;
                     try {
-                        PackageManager packageManager = mContext.getPackageManager();
+                        // Use PackageManager to load label
+                        final PackageManager packageManager = mContext.getPackageManager();
                         contentDescription = packageManager.getApplicationLabel(
-                                packageManager.getApplicationInfo(packageName, 0));
-                    } catch (NameNotFoundException nnfe) {
+                                mIPackageManager.getApplicationInfo(packageName, 0,
+                                        mSettings.getCurrentUserId()));
+                    } catch (RemoteException e) {
                         /* ignore */
                     }
                     if (mStatusBar != null) {
@@ -1309,13 +1443,14 @@
         }
     }
 
+    // Caution! This method is called in this class. Handle multi-user carefully
     @SuppressWarnings("deprecation")
     @Override
     public void setImeWindowStatus(IBinder token, int vis, int backDisposition) {
-        int uid = Binder.getCallingUid();
-        long ident = Binder.clearCallingIdentity();
+        final long ident = Binder.clearCallingIdentity();
         try {
             if (token == null || mCurToken != token) {
+                int uid = Binder.getCallingUid();
                 Slog.w(TAG, "Ignoring setImeWindowStatus of uid " + uid + " token: " + token);
                 return;
             }
@@ -1329,10 +1464,14 @@
                 final boolean iconVisibility = (vis & InputMethodService.IME_ACTIVE) != 0;
                 final InputMethodInfo imi = mMethodMap.get(mCurMethodId);
                 if (imi != null && iconVisibility && needsToShowImeSwitchOngoingNotification()) {
+                    // Used to load label
                     final PackageManager pm = mContext.getPackageManager();
                     final CharSequence title = mRes.getText(
                             com.android.internal.R.string.select_input_method);
                     final CharSequence imiLabel = imi.loadLabel(pm);
+                    if (DEBUG) {
+                        Slog.d(TAG, "--- imiLabel = " + imiLabel);
+                    }
                     final CharSequence summary = mCurrentSubtype != null
                             ? TextUtils.concat(mCurrentSubtype.getDisplayName(mContext,
                                         imi.getPackageName(), imi.getServiceInfo().applicationInfo),
@@ -1363,6 +1502,9 @@
 
     @Override
     public void registerSuggestionSpansForNotification(SuggestionSpan[] spans) {
+        if (!calledFromValidUser()) {
+            return;
+        }
         synchronized (mMethodMap) {
             final InputMethodInfo currentImi = mMethodMap.get(mCurMethodId);
             for (int i = 0; i < spans.length; ++i) {
@@ -1377,6 +1519,9 @@
 
     @Override
     public boolean notifySuggestionPicked(SuggestionSpan span, String originalString, int index) {
+        if (!calledFromValidUser()) {
+            return false;
+        }
         synchronized (mMethodMap) {
             final InputMethodInfo targetImi = mSecureSuggestionSpans.get(span);
             // TODO: Do not send the intent if the process of the targetImi is already dead.
@@ -1404,12 +1549,10 @@
         // ENABLED_INPUT_METHODS is taking care of keeping them correctly in
         // sync, so we will never have a DEFAULT_INPUT_METHOD that is not
         // enabled.
-        String id = Settings.Secure.getString(mContext.getContentResolver(),
-                Settings.Secure.DEFAULT_INPUT_METHOD);
+        String id = mSettings.getSelectedInputMethod();
         // There is no input method selected, try to choose new applicable input method.
         if (TextUtils.isEmpty(id) && chooseNewDefaultIMELocked()) {
-            id = Settings.Secure.getString(mContext.getContentResolver(),
-                    Settings.Secure.DEFAULT_INPUT_METHOD);
+            id = mSettings.getSelectedInputMethod();
         }
         if (!TextUtils.isEmpty(id)) {
             try {
@@ -1446,7 +1589,7 @@
             } else {
                 // If subtype is null, try to find the most applicable one from
                 // getCurrentInputMethodSubtype.
-                newSubtype = getCurrentInputMethodSubtype();
+                newSubtype = getCurrentInputMethodSubtypeLocked();
             }
             if (newSubtype == null || oldSubtype == null) {
                 Slog.w(TAG, "Illegal subtype state: old subtype = " + oldSubtype
@@ -1493,6 +1636,9 @@
     @Override
     public boolean showSoftInput(IInputMethodClient client, int flags,
             ResultReceiver resultReceiver) {
+        if (!calledFromValidUser()) {
+            return false;
+        }
         int uid = Binder.getCallingUid();
         long ident = Binder.clearCallingIdentity();
         try {
@@ -1541,7 +1687,8 @@
                     resultReceiver));
             mInputShown = true;
             if (mHaveConnection && !mVisibleBound) {
-                mContext.bindService(mCurIntent, mVisibleConnection, Context.BIND_AUTO_CREATE);
+                bindCurrentInputMethodService(
+                        mCurIntent, mVisibleConnection, Context.BIND_AUTO_CREATE);
                 mVisibleBound = true;
             }
             res = true;
@@ -1555,8 +1702,13 @@
                     SystemClock.uptimeMillis()-mLastBindTime,1);
             Slog.w(TAG, "Force disconnect/connect to the IME in showCurrentInputLocked()");
             mContext.unbindService(this);
-            mContext.bindService(mCurIntent, this, Context.BIND_AUTO_CREATE
+            bindCurrentInputMethodService(mCurIntent, this, Context.BIND_AUTO_CREATE
                     | Context.BIND_NOT_VISIBLE);
+        } else {
+            if (DEBUG) {
+                Slog.d(TAG, "Can't show input: connection = " + mHaveConnection + ", time = "
+                        + ((mLastBindTime+TIME_TO_RECONNECT) - SystemClock.uptimeMillis()));
+            }
         }
 
         return res;
@@ -1565,6 +1717,9 @@
     @Override
     public boolean hideSoftInput(IInputMethodClient client, int flags,
             ResultReceiver resultReceiver) {
+        if (!calledFromValidUser()) {
+            return false;
+        }
         int uid = Binder.getCallingUid();
         long ident = Binder.clearCallingIdentity();
         try {
@@ -1630,6 +1785,9 @@
     public InputBindResult windowGainedFocus(IInputMethodClient client, IBinder windowToken,
             int controlFlags, int softInputMode, int windowFlags,
             EditorInfo attribute, IInputContext inputContext) {
+        if (!calledFromValidUser()) {
+            return null;
+        }
         InputBindResult res = null;
         long ident = Binder.clearCallingIdentity();
         try {
@@ -1770,6 +1928,9 @@
 
     @Override
     public void showInputMethodPickerFromClient(IInputMethodClient client) {
+        if (!calledFromValidUser()) {
+            return;
+        }
         synchronized (mMethodMap) {
             if (mCurClient == null || client == null
                     || mCurClient.client.asBinder() != client.asBinder()) {
@@ -1785,11 +1946,17 @@
 
     @Override
     public void setInputMethod(IBinder token, String id) {
+        if (!calledFromValidUser()) {
+            return;
+        }
         setInputMethodWithSubtypeId(token, id, NOT_A_SUBTYPE_ID);
     }
 
     @Override
     public void setInputMethodAndSubtype(IBinder token, String id, InputMethodSubtype subtype) {
+        if (!calledFromValidUser()) {
+            return;
+        }
         synchronized (mMethodMap) {
             if (subtype != null) {
                 setInputMethodWithSubtypeId(token, id, getSubtypeIdFromHashCode(
@@ -1803,6 +1970,9 @@
     @Override
     public void showInputMethodAndSubtypeEnablerFromClient(
             IInputMethodClient client, String inputMethodId) {
+        if (!calledFromValidUser()) {
+            return;
+        }
         synchronized (mMethodMap) {
             if (mCurClient == null || client == null
                 || mCurClient.client.asBinder() != client.asBinder()) {
@@ -1815,6 +1985,9 @@
 
     @Override
     public boolean switchToLastInputMethod(IBinder token) {
+        if (!calledFromValidUser()) {
+            return false;
+        }
         synchronized (mMethodMap) {
             final Pair<String, String> lastIme = mSettings.getLastInputMethodAndSubtypeLocked();
             final InputMethodInfo lastImi;
@@ -1882,6 +2055,9 @@
 
     @Override
     public boolean switchToNextInputMethod(IBinder token, boolean onlyCurrentIme) {
+        if (!calledFromValidUser()) {
+            return false;
+        }
         synchronized (mMethodMap) {
             final ImeSubtypeListItem nextSubtype = mImListManager.getNextInputMethod(
                     onlyCurrentIme, mMethodMap.get(mCurMethodId), mCurrentSubtype);
@@ -1895,6 +2071,9 @@
 
     @Override
     public InputMethodSubtype getLastInputMethodSubtype() {
+        if (!calledFromValidUser()) {
+            return null;
+        }
         synchronized (mMethodMap) {
             final Pair<String, String> lastIme = mSettings.getLastInputMethodAndSubtypeLocked();
             // TODO: Handle the case of the last IME with no subtypes
@@ -1917,14 +2096,22 @@
 
     @Override
     public void setAdditionalInputMethodSubtypes(String imiId, InputMethodSubtype[] subtypes) {
+        if (!calledFromValidUser()) {
+            return;
+        }
         // By this IPC call, only a process which shares the same uid with the IME can add
         // additional input method subtypes to the IME.
         if (TextUtils.isEmpty(imiId) || subtypes == null || subtypes.length == 0) return;
         synchronized (mMethodMap) {
             final InputMethodInfo imi = mMethodMap.get(imiId);
             if (imi == null) return;
-            final PackageManager pm = mContext.getPackageManager();
-            final String[] packageInfos = pm.getPackagesForUid(Binder.getCallingUid());
+            final String[] packageInfos;
+            try {
+                packageInfos = mIPackageManager.getPackagesForUid(Binder.getCallingUid());
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Failed to get package infos");
+                return;
+            }
             if (packageInfos != null) {
                 final int packageNum = packageInfos.length;
                 for (int i = 0; i < packageNum; ++i) {
@@ -1971,6 +2158,9 @@
 
     @Override
     public void hideMySoftInput(IBinder token, int flags) {
+        if (!calledFromValidUser()) {
+            return;
+        }
         synchronized (mMethodMap) {
             if (token == null || mCurToken != token) {
                 if (DEBUG) Slog.w(TAG, "Ignoring hideInputMethod of uid "
@@ -1988,6 +2178,9 @@
 
     @Override
     public void showMySoftInput(IBinder token, int flags) {
+        if (!calledFromValidUser()) {
+            return;
+        }
         synchronized (mMethodMap) {
             if (token == null || mCurToken != token) {
                 Slog.w(TAG, "Ignoring showMySoftInput of uid "
@@ -2224,19 +2417,22 @@
 
     void buildInputMethodListLocked(ArrayList<InputMethodInfo> list,
             HashMap<String, InputMethodInfo> map) {
+        if (DEBUG) {
+            Slog.d(TAG, "--- re-buildInputMethodList " + ", \n ------ \n" + getStackTrace());
+        }
         list.clear();
         map.clear();
 
-        PackageManager pm = mContext.getPackageManager();
+        // Use for queryIntentServicesAsUser
+        final PackageManager pm = mContext.getPackageManager();
         final Configuration config = mRes.getConfiguration();
         final boolean haveHardKeyboard = config.keyboard == Configuration.KEYBOARD_QWERTY;
-        String disabledSysImes = Settings.Secure.getString(mContext.getContentResolver(),
-                Secure.DISABLED_SYSTEM_INPUT_METHODS);
+        String disabledSysImes = mSettings.getDisabledSystemInputMethods();
         if (disabledSysImes == null) disabledSysImes = "";
 
-        List<ResolveInfo> services = pm.queryIntentServices(
+        final List<ResolveInfo> services = pm.queryIntentServicesAsUser(
                 new Intent(InputMethod.SERVICE_INTERFACE),
-                PackageManager.GET_META_DATA);
+                PackageManager.GET_META_DATA, mSettings.getCurrentUserId());
 
         final HashMap<String, List<InputMethodSubtype>> additionalSubtypes =
                 mFileManager.getAllAdditionalInputMethodSubtypes();
@@ -2279,8 +2475,7 @@
             }
         }
 
-        final String defaultImiId = Settings.Secure.getString(mContext
-                .getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
+        final String defaultImiId = mSettings.getSelectedInputMethod();
         if (!TextUtils.isEmpty(defaultImiId)) {
             if (!map.containsKey(defaultImiId)) {
                 Slog.w(TAG, "Default IME is uninstalled. Choose new default IME.");
@@ -2331,11 +2526,9 @@
         if (DEBUG) Slog.v(TAG, "Show switching menu");
 
         final Context context = mContext;
-        final PackageManager pm = context.getPackageManager();
         final boolean isScreenLocked = isScreenLocked();
 
-        final String lastInputMethodId = Settings.Secure.getString(context
-                .getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
+        final String lastInputMethodId = mSettings.getSelectedInputMethod();
         int lastInputMethodSubtypeId = getSelectedInputMethodSubtypeId(lastInputMethodId);
         if (DEBUG) Slog.v(TAG, "Current IME: " + lastInputMethodId);
 
@@ -2353,7 +2546,7 @@
                             showSubtypes, mInputShown, isScreenLocked);
 
             if (lastInputMethodSubtypeId == NOT_A_SUBTYPE_ID) {
-                final InputMethodSubtype currentSubtype = getCurrentInputMethodSubtype();
+                final InputMethodSubtype currentSubtype = getCurrentInputMethodSubtypeLocked();
                 if (currentSubtype != null) {
                     final InputMethodInfo currentImi = mMethodMap.get(mCurMethodId);
                     lastInputMethodSubtypeId =
@@ -2582,6 +2775,10 @@
 
     @Override
     public boolean setInputMethodEnabled(String id, boolean enabled) {
+        // TODO: Make this work even for non-current users?
+        if (!calledFromValidUser()) {
+            return false;
+        }
         synchronized (mMethodMap) {
             if (mContext.checkCallingOrSelfPermission(
                     android.Manifest.permission.WRITE_SECURE_SETTINGS)
@@ -2626,8 +2823,7 @@
             if (mSettings.buildAndPutEnabledInputMethodsStrRemovingIdLocked(
                     builder, enabledInputMethodsList, id)) {
                 // Disabled input method is currently selected, switch to another one.
-                String selId = Settings.Secure.getString(mContext.getContentResolver(),
-                        Settings.Secure.DEFAULT_INPUT_METHOD);
+                final String selId = mSettings.getSelectedInputMethod();
                 if (id.equals(selId) && !chooseNewDefaultIMELocked()) {
                     Slog.i(TAG, "Can't find new IME, unsetting the current input method.");
                     resetSelectedInputMethodAndSubtypeLocked("");
@@ -2674,7 +2870,7 @@
             } else {
                 mSettings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
                 // If the subtype is not specified, choose the most applicable one
-                mCurrentSubtype = getCurrentInputMethodSubtype();
+                mCurrentSubtype = getCurrentInputMethodSubtypeLocked();
             }
         }
 
@@ -2716,14 +2912,8 @@
         if (imi == null) {
             return NOT_A_SUBTYPE_ID;
         }
-        int subtypeId;
-        try {
-            subtypeId = Settings.Secure.getInt(mContext.getContentResolver(),
-                    Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE);
-        } catch (SettingNotFoundException e) {
-            return NOT_A_SUBTYPE_ID;
-        }
-        return getSubtypeIdFromHashCode(imi, subtypeId);
+        final int subtypeHashCode = mSettings.getSelectedInputMethodSubtypeHashCode();
+        return getSubtypeIdFromHashCode(imi, subtypeHashCode);
     }
 
     private static boolean isValidSubtypeId(InputMethodInfo imi, int subtypeHashCode) {
@@ -2886,7 +3076,7 @@
             }
             InputMethodSubtype subtype = null;
             final List<InputMethodSubtype> enabledSubtypes =
-                    getEnabledInputMethodSubtypeList(imi, true);
+                    getEnabledInputMethodSubtypeListLocked(imi, true);
             // 1. Search by the current subtype's locale from enabledSubtypes.
             if (mCurrentSubtype != null) {
                 subtype = findLastResortApplicableSubtypeLocked(
@@ -2955,49 +3145,53 @@
      */
     @Override
     public InputMethodSubtype getCurrentInputMethodSubtype() {
+        // TODO: Make this work even for non-current users?
+        if (!calledFromValidUser()) {
+            return null;
+        }
+        synchronized (mMethodMap) {
+            return getCurrentInputMethodSubtypeLocked();
+        }
+    }
+
+    private InputMethodSubtype getCurrentInputMethodSubtypeLocked() {
         if (mCurMethodId == null) {
             return null;
         }
-        boolean subtypeIsSelected = false;
-        try {
-            subtypeIsSelected = Settings.Secure.getInt(mContext.getContentResolver(),
-                    Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE) != NOT_A_SUBTYPE_ID;
-        } catch (SettingNotFoundException e) {
+        final boolean subtypeIsSelected =
+                mSettings.getSelectedInputMethodSubtypeHashCode() != NOT_A_SUBTYPE_ID;
+        final InputMethodInfo imi = mMethodMap.get(mCurMethodId);
+        if (imi == null || imi.getSubtypeCount() == 0) {
+            return null;
         }
-        synchronized (mMethodMap) {
-            final InputMethodInfo imi = mMethodMap.get(mCurMethodId);
-            if (imi == null || imi.getSubtypeCount() == 0) {
-                return null;
-            }
-            if (!subtypeIsSelected || mCurrentSubtype == null
-                    || !isValidSubtypeId(imi, mCurrentSubtype.hashCode())) {
-                int subtypeId = getSelectedInputMethodSubtypeId(mCurMethodId);
-                if (subtypeId == NOT_A_SUBTYPE_ID) {
-                    // If there are no selected subtypes, the framework will try to find
-                    // the most applicable subtype from explicitly or implicitly enabled
-                    // subtypes.
-                    List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypes =
-                            getEnabledInputMethodSubtypeList(imi, true);
-                    // If there is only one explicitly or implicitly enabled subtype,
-                    // just returns it.
-                    if (explicitlyOrImplicitlyEnabledSubtypes.size() == 1) {
-                        mCurrentSubtype = explicitlyOrImplicitlyEnabledSubtypes.get(0);
-                    } else if (explicitlyOrImplicitlyEnabledSubtypes.size() > 1) {
+        if (!subtypeIsSelected || mCurrentSubtype == null
+                || !isValidSubtypeId(imi, mCurrentSubtype.hashCode())) {
+            int subtypeId = getSelectedInputMethodSubtypeId(mCurMethodId);
+            if (subtypeId == NOT_A_SUBTYPE_ID) {
+                // If there are no selected subtypes, the framework will try to find
+                // the most applicable subtype from explicitly or implicitly enabled
+                // subtypes.
+                List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypes =
+                        getEnabledInputMethodSubtypeListLocked(imi, true);
+                // If there is only one explicitly or implicitly enabled subtype,
+                // just returns it.
+                if (explicitlyOrImplicitlyEnabledSubtypes.size() == 1) {
+                    mCurrentSubtype = explicitlyOrImplicitlyEnabledSubtypes.get(0);
+                } else if (explicitlyOrImplicitlyEnabledSubtypes.size() > 1) {
+                    mCurrentSubtype = findLastResortApplicableSubtypeLocked(
+                            mRes, explicitlyOrImplicitlyEnabledSubtypes,
+                            SUBTYPE_MODE_KEYBOARD, null, true);
+                    if (mCurrentSubtype == null) {
                         mCurrentSubtype = findLastResortApplicableSubtypeLocked(
-                                mRes, explicitlyOrImplicitlyEnabledSubtypes,
-                                SUBTYPE_MODE_KEYBOARD, null, true);
-                        if (mCurrentSubtype == null) {
-                            mCurrentSubtype = findLastResortApplicableSubtypeLocked(
-                                    mRes, explicitlyOrImplicitlyEnabledSubtypes, null, null,
-                                    true);
-                        }
+                                mRes, explicitlyOrImplicitlyEnabledSubtypes, null, null,
+                                true);
                     }
-                } else {
-                    mCurrentSubtype = getSubtypes(imi).get(subtypeId);
                 }
+            } else {
+                mCurrentSubtype = getSubtypes(imi).get(subtypeId);
             }
-            return mCurrentSubtype;
         }
+        return mCurrentSubtype;
     }
 
     private void addShortcutInputMethodAndSubtypes(InputMethodInfo imi,
@@ -3042,6 +3236,10 @@
 
     @Override
     public boolean setCurrentInputMethodSubtype(InputMethodSubtype subtype) {
+        // TODO: Make this work even for non-current users?
+        if (!calledFromValidUser()) {
+            return false;
+        }
         synchronized (mMethodMap) {
             if (subtype != null && mCurMethodId != null) {
                 InputMethodInfo imi = mMethodMap.get(mCurMethodId);
@@ -3057,6 +3255,7 @@
 
     private static class InputMethodAndSubtypeListManager {
         private final Context mContext;
+        // Used to load label
         private final PackageManager mPm;
         private final InputMethodManagerService mImms;
         private final String mSystemLocaleStr;
@@ -3193,6 +3392,7 @@
         private final ArrayList<InputMethodInfo> mMethodList;
 
         private String mEnabledInputMethodsStrCache;
+        private int mCurrentUserId;
 
         private static void buildEnabledInputMethodsSettingString(
                 StringBuilder builder, Pair<String, ArrayList<String>> pair) {
@@ -3208,13 +3408,24 @@
 
         public InputMethodSettings(
                 Resources res, ContentResolver resolver,
-                HashMap<String, InputMethodInfo> methodMap, ArrayList<InputMethodInfo> methodList) {
+                HashMap<String, InputMethodInfo> methodMap, ArrayList<InputMethodInfo> methodList,
+                int userId) {
+            setCurrentUserId(userId);
             mRes = res;
             mResolver = resolver;
             mMethodMap = methodMap;
             mMethodList = methodList;
         }
 
+        public void setCurrentUserId(int userId) {
+            if (DEBUG) {
+                Slog.d(TAG, "--- Swtich the current user from " + mCurrentUserId + " to "
+                        + userId + ", new ime = " + getSelectedInputMethod());
+            }
+            // IMMS settings are kept per user, so keep track of current user
+            mCurrentUserId = userId;
+        }
+
         public List<InputMethodInfo> getEnabledInputMethodListLocked() {
             return createEnabledInputMethodListLocked(
                     getEnabledInputMethodsAndSubtypeListLocked());
@@ -3363,15 +3574,20 @@
         }
 
         private void putEnabledInputMethodsStr(String str) {
-            Settings.Secure.putString(mResolver, Settings.Secure.ENABLED_INPUT_METHODS, str);
+            Settings.Secure.putStringForUser(
+                    mResolver, Settings.Secure.ENABLED_INPUT_METHODS, str, mCurrentUserId);
             mEnabledInputMethodsStrCache = str;
+            if (DEBUG) {
+                Slog.d(TAG, "putEnabledInputMethodStr: " + str);
+            }
         }
 
         private String getEnabledInputMethodsStr() {
-            mEnabledInputMethodsStrCache = Settings.Secure.getString(
-                    mResolver, Settings.Secure.ENABLED_INPUT_METHODS);
+            mEnabledInputMethodsStrCache = Settings.Secure.getStringForUser(
+                    mResolver, Settings.Secure.ENABLED_INPUT_METHODS, mCurrentUserId);
             if (DEBUG) {
-                Slog.d(TAG, "getEnabledInputMethodsStr: " + mEnabledInputMethodsStrCache);
+                Slog.d(TAG, "getEnabledInputMethodsStr: " + mEnabledInputMethodsStrCache
+                        + ", " + mCurrentUserId);
             }
             return mEnabledInputMethodsStrCache;
         }
@@ -3426,8 +3642,8 @@
             if (DEBUG) {
                 Slog.d(TAG, "putSubtypeHistoryStr: " + str);
             }
-            Settings.Secure.putString(
-                    mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, str);
+            Settings.Secure.putStringForUser(
+                    mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, str, mCurrentUserId);
         }
 
         public Pair<String, String> getLastInputMethodAndSubtypeLocked() {
@@ -3546,20 +3762,57 @@
 
         private String getSubtypeHistoryStr() {
             if (DEBUG) {
-                Slog.d(TAG, "getSubtypeHistoryStr: " + Settings.Secure.getString(
-                        mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY));
+                Slog.d(TAG, "getSubtypeHistoryStr: " + Settings.Secure.getStringForUser(
+                        mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, mCurrentUserId));
             }
-            return Settings.Secure.getString(
-                    mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY);
+            return Settings.Secure.getStringForUser(
+                    mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, mCurrentUserId);
         }
 
         public void putSelectedInputMethod(String imeId) {
-            Settings.Secure.putString(mResolver, Settings.Secure.DEFAULT_INPUT_METHOD, imeId);
+            if (DEBUG) {
+                Slog.d(TAG, "putSelectedInputMethodStr: " + imeId + ", "
+                        + mCurrentUserId);
+            }
+            Settings.Secure.putStringForUser(
+                    mResolver, Settings.Secure.DEFAULT_INPUT_METHOD, imeId, mCurrentUserId);
         }
 
         public void putSelectedSubtype(int subtypeId) {
-            Settings.Secure.putInt(
-                    mResolver, Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, subtypeId);
+            if (DEBUG) {
+                Slog.d(TAG, "putSelectedInputMethodSubtypeStr: " + subtypeId + ", "
+                        + mCurrentUserId);
+            }
+            Settings.Secure.putIntForUser(mResolver, Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE,
+                    subtypeId, mCurrentUserId);
+        }
+
+        public String getDisabledSystemInputMethods() {
+            return Settings.Secure.getStringForUser(
+                    mResolver, Settings.Secure.DISABLED_SYSTEM_INPUT_METHODS, mCurrentUserId);
+        }
+
+        public String getSelectedInputMethod() {
+            if (DEBUG) {
+                Slog.d(TAG, "getSelectedInputMethodStr: " + Settings.Secure.getStringForUser(
+                        mResolver, Settings.Secure.DEFAULT_INPUT_METHOD, mCurrentUserId)
+                        + ", " + mCurrentUserId);
+            }
+            return Settings.Secure.getStringForUser(
+                    mResolver, Settings.Secure.DEFAULT_INPUT_METHOD, mCurrentUserId);
+        }
+
+        public int getSelectedInputMethodSubtypeHashCode() {
+            try {
+                return Settings.Secure.getIntForUser(
+                        mResolver, Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, mCurrentUserId);
+            } catch (SettingNotFoundException e) {
+                return NOT_A_SUBTYPE_ID;
+            }
+        }
+
+        public int getCurrentUserId() {
+            return mCurrentUserId;
         }
     }
 
@@ -3762,6 +4015,20 @@
     }
 
     // ----------------------------------------------------------------------
+    // Utilities for debug
+    private static String getStackTrace() {
+        final StringBuilder sb = new StringBuilder();
+        try {
+            throw new RuntimeException();
+        } catch (RuntimeException e) {
+            final StackTraceElement[] frames = e.getStackTrace();
+            // Start at 1 because the first frame is here and we don't care about it
+            for (int j = 1; j < frames.length; ++j) {
+                sb.append(frames[j].toString() + "\n");
+            }
+        }
+        return sb.toString();
+    }
 
     @Override
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {