Fix crosstalk between users for widgets hosted in lockscreen

This was initially about the Clock widget crashing repeatedly on some
devices with multiple users. Turned out that there were race conditions
when switching users that could result in remote views of one user calling
back to the RemoteViewsAdapter in keyguard that in turn sent an incorrect widget id
to a different user's widget, resulting in a crash.

Since KeyguardHostView is instantiated in the same process for different users,
it needs to carry a user identity to pass along to AppWidgetService so that
remote views services were bound to the correct user and callbacks were attached and
detached properly.

Added some aidl calls that take the userId to do the binding properly. A more
complete fix might be needed in the future so that all calls from Keyguard carry
the user id.

Also, there was a problem in comparing host uid for secondary users, since Settings
for a secondary user has a different uid than keyguard. Not an issue on single-user
systems. Changed the host.uid comparison to accomodate for the secondary user.

Bug: 7450247
Change-Id: Idbc36e3c60023cac74174f6cb7f2b2130dd3052c
diff --git a/core/java/android/appwidget/AppWidgetHost.java b/core/java/android/appwidget/AppWidgetHost.java
index 24fd2e4..6a99ccd 100644
--- a/core/java/android/appwidget/AppWidgetHost.java
+++ b/core/java/android/appwidget/AppWidgetHost.java
@@ -154,6 +154,15 @@
      * becomes visible, i.e. from onStart() in your Activity.
      */
     public void startListening() {
+        startListeningAsUser(UserHandle.myUserId());
+    }
+
+    /**
+     * Start receiving onAppWidgetChanged calls for your AppWidgets.  Call this when your activity
+     * becomes visible, i.e. from onStart() in your Activity.
+     * @hide
+     */
+    public void startListeningAsUser(int userId) {
         int[] updatedIds;
         ArrayList<RemoteViews> updatedViews = new ArrayList<RemoteViews>();
 
@@ -161,7 +170,8 @@
             if (mPackageName == null) {
                 mPackageName = mContext.getPackageName();
             }
-            updatedIds = sService.startListening(mCallbacks, mPackageName, mHostId, updatedViews);
+            updatedIds = sService.startListeningAsUser(
+                    mCallbacks, mPackageName, mHostId, updatedViews, userId);
         }
         catch (RemoteException e) {
             throw new RuntimeException("system server dead?", e);
@@ -179,7 +189,7 @@
      */
     public void stopListening() {
         try {
-            sService.stopListening(mHostId);
+            sService.stopListeningAsUser(mHostId, UserHandle.myUserId());
         }
         catch (RemoteException e) {
             throw new RuntimeException("system server dead?", e);
@@ -187,6 +197,22 @@
     }
 
     /**
+     * Stop receiving onAppWidgetChanged calls for your AppWidgets.  Call this when your activity is
+     * no longer visible, i.e. from onStop() in your Activity.
+     * @hide
+     */
+    public void stopListeningAsUser(int userId) {
+        try {
+            sService.stopListeningAsUser(mHostId, userId);
+        }
+        catch (RemoteException e) {
+            throw new RuntimeException("system server dead?", e);
+        }
+        // Also clear the views
+        clearViews();
+    }
+
+    /**
      * Get a appWidgetId for a host in the calling process.
      *
      * @return a appWidgetId
diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java
index 77315f9..9c19766 100644
--- a/core/java/android/appwidget/AppWidgetManager.java
+++ b/core/java/android/appwidget/AppWidgetManager.java
@@ -23,6 +23,7 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.UserHandle;
 import android.util.DisplayMetrics;
 import android.util.TypedValue;
 import android.widget.RemoteViews;
@@ -749,11 +750,14 @@
      * @param intent        The intent of the service which will be providing the data to the
      *                      RemoteViewsAdapter.
      * @param connection    The callback interface to be notified when a connection is made or lost.
+     * @param userHandle    The user to bind to.
      * @hide
      */
-    public void bindRemoteViewsService(int appWidgetId, Intent intent, IBinder connection) {
+    public void bindRemoteViewsService(int appWidgetId, Intent intent, IBinder connection,
+            UserHandle userHandle) {
         try {
-            sService.bindRemoteViewsService(appWidgetId, intent, connection);
+            sService.bindRemoteViewsService(appWidgetId, intent, connection,
+                    userHandle.getIdentifier());
         }
         catch (RemoteException e) {
             throw new RuntimeException("system server dead?", e);
@@ -769,11 +773,12 @@
      * @param appWidgetId   The AppWidget instance for which to bind the RemoteViewsService.
      * @param intent        The intent of the service which will be providing the data to the
      *                      RemoteViewsAdapter.
+     * @param userHandle    The user to unbind from.
      * @hide
      */
-    public void unbindRemoteViewsService(int appWidgetId, Intent intent) {
+    public void unbindRemoteViewsService(int appWidgetId, Intent intent, UserHandle userHandle) {
         try {
-            sService.unbindRemoteViewsService(appWidgetId, intent);
+            sService.unbindRemoteViewsService(appWidgetId, intent, userHandle.getIdentifier());
         }
         catch (RemoteException e) {
             throw new RuntimeException("system server dead?", e);
diff --git a/core/java/android/widget/RemoteViewsAdapter.java b/core/java/android/widget/RemoteViewsAdapter.java
index e481702..c122bb2 100644
--- a/core/java/android/widget/RemoteViewsAdapter.java
+++ b/core/java/android/widget/RemoteViewsAdapter.java
@@ -29,7 +29,9 @@
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
+import android.os.Process;
 import android.os.RemoteException;
+import android.os.UserHandle;
 import android.util.Log;
 import android.util.Pair;
 import android.view.LayoutInflater;
@@ -40,6 +42,7 @@
 
 import com.android.internal.widget.IRemoteViewsAdapterConnection;
 import com.android.internal.widget.IRemoteViewsFactory;
+import com.android.internal.widget.LockPatternUtils;
 
 /**
  * An adapter to a RemoteViewsService which fetches and caches RemoteViews
@@ -106,6 +109,8 @@
     // construction (happens when we have a cached FixedSizeRemoteViewsCache).
     private boolean mDataReady = false;
 
+    int mUserId;
+
     /**
      * An interface for the RemoteAdapter to notify other classes when adapters
      * are actually connected to/disconnected from their actual services.
@@ -146,8 +151,16 @@
         public synchronized void bind(Context context, int appWidgetId, Intent intent) {
             if (!mIsConnecting) {
                 try {
+                    RemoteViewsAdapter adapter;
                     final AppWidgetManager mgr = AppWidgetManager.getInstance(context);
-                    mgr.bindRemoteViewsService(appWidgetId, intent, asBinder());
+                    if (Process.myUid() == Process.SYSTEM_UID
+                            && (adapter = mAdapter.get()) != null) {
+                        mgr.bindRemoteViewsService(appWidgetId, intent, asBinder(),
+                                new UserHandle(adapter.mUserId));
+                    } else {
+                        mgr.bindRemoteViewsService(appWidgetId, intent, asBinder(),
+                                Process.myUserHandle());
+                    }
                     mIsConnecting = true;
                 } catch (Exception e) {
                     Log.e("RemoteViewsAdapterServiceConnection", "bind(): " + e.getMessage());
@@ -159,8 +172,15 @@
 
         public synchronized void unbind(Context context, int appWidgetId, Intent intent) {
             try {
+                RemoteViewsAdapter adapter;
                 final AppWidgetManager mgr = AppWidgetManager.getInstance(context);
-                mgr.unbindRemoteViewsService(appWidgetId, intent);
+                if (Process.myUid() == Process.SYSTEM_UID
+                        && (adapter = mAdapter.get()) != null) {
+                    mgr.unbindRemoteViewsService(appWidgetId, intent,
+                            new UserHandle(adapter.mUserId));
+                } else {
+                    mgr.unbindRemoteViewsService(appWidgetId, intent, Process.myUserHandle());
+                }
                 mIsConnecting = false;
             } catch (Exception e) {
                 Log.e("RemoteViewsAdapterServiceConnection", "unbind(): " + e.getMessage());
@@ -761,6 +781,12 @@
         }
         mRequestedViews = new RemoteViewsFrameLayoutRefSet();
 
+        if (Process.myUid() == Process.SYSTEM_UID) {
+            mUserId = new LockPatternUtils(context).getCurrentUser();
+        } else {
+            mUserId = UserHandle.myUserId();
+        }
+
         // Strip the previously injected app widget id from service intent
         if (intent.hasExtra(RemoteViews.EXTRA_REMOTEADAPTER_APPWIDGET_ID)) {
             intent.removeExtra(RemoteViews.EXTRA_REMOTEADAPTER_APPWIDGET_ID);
diff --git a/core/java/com/android/internal/appwidget/IAppWidgetService.aidl b/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
index e81d389..e685e63 100644
--- a/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
+++ b/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
@@ -32,7 +32,10 @@
     //
     int[] startListening(IAppWidgetHost host, String packageName, int hostId,
             out List<RemoteViews> updatedViews);
+    int[] startListeningAsUser(IAppWidgetHost host, String packageName, int hostId,
+            out List<RemoteViews> updatedViews, int userId);
     void stopListening(int hostId);
+    void stopListeningAsUser(int hostId, int userId);
     int allocateAppWidgetId(String packageName, int hostId);
     void deleteAppWidgetId(int appWidgetId);
     void deleteHost(int hostId);
@@ -56,8 +59,8 @@
     void bindAppWidgetId(int appWidgetId, in ComponentName provider, in Bundle options);
     boolean bindAppWidgetIdIfAllowed(
             in String packageName, int appWidgetId, in ComponentName provider, in Bundle options);
-    void bindRemoteViewsService(int appWidgetId, in Intent intent, in IBinder connection);
-    void unbindRemoteViewsService(int appWidgetId, in Intent intent);
+    void bindRemoteViewsService(int appWidgetId, in Intent intent, in IBinder connection, int userId);
+    void unbindRemoteViewsService(int appWidgetId, in Intent intent, int userId);
     int[] getAppWidgetIds(in ComponentName provider);
 
 }
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardHostView.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardHostView.java
index 4fb7a61..8502d78 100644
--- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardHostView.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardHostView.java
@@ -101,6 +101,8 @@
     private boolean mSafeModeEnabled;
 
     private boolean mUserSetupCompleted;
+    // User for whom this host view was created
+    private int mUserId;
 
     /*package*/ interface TransportCallback {
         void onListenerDetached();
@@ -127,6 +129,7 @@
     public KeyguardHostView(Context context, AttributeSet attrs) {
         super(context, attrs);
         mLockPatternUtils = new LockPatternUtils(context);
+        mUserId = mLockPatternUtils.getCurrentUser();
         mAppWidgetHost = new AppWidgetHost(
                 context, APPWIDGET_HOST_ID, mOnClickHandler, Looper.myLooper());
         cleanupAppWidgetIds();
@@ -338,14 +341,14 @@
     @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
-        mAppWidgetHost.startListening();
+        mAppWidgetHost.startListeningAsUser(mUserId);
         KeyguardUpdateMonitor.getInstance(mContext).registerCallback(mUpdateMonitorCallbacks);
     }
 
     @Override
     protected void onDetachedFromWindow() {
         super.onDetachedFromWindow();
-        mAppWidgetHost.stopListening();
+        mAppWidgetHost.stopListeningAsUser(mUserId);
         KeyguardUpdateMonitor.getInstance(mContext).removeCallback(mUpdateMonitorCallbacks);
     }
 
diff --git a/services/java/com/android/server/AppWidgetService.java b/services/java/com/android/server/AppWidgetService.java
index b94183c..06aeb29 100644
--- a/services/java/com/android/server/AppWidgetService.java
+++ b/services/java/com/android/server/AppWidgetService.java
@@ -196,9 +196,14 @@
     }
 
     @Override
-    public void bindRemoteViewsService(int appWidgetId, Intent intent, IBinder connection)
-            throws RemoteException {
-        getImplForUser(getCallingOrCurrentUserId()).bindRemoteViewsService(
+    public void bindRemoteViewsService(int appWidgetId, Intent intent, IBinder connection,
+            int userId) throws RemoteException {
+        if (Binder.getCallingPid() != android.os.Process.myPid()
+                && userId != UserHandle.getCallingUserId()) {
+            throw new SecurityException("Call from non-system process. Calling uid = "
+                    + Binder.getCallingUid());
+        }
+        getImplForUser(userId).bindRemoteViewsService(
                 appWidgetId, intent, connection);
     }
 
@@ -209,6 +214,17 @@
                 packageName, hostId, updatedViews);
     }
 
+    @Override
+    public int[] startListeningAsUser(IAppWidgetHost host, String packageName, int hostId,
+            List<RemoteViews> updatedViews, int userId) throws RemoteException {
+        if (Binder.getCallingPid() != android.os.Process.myPid()
+                && userId != UserHandle.getCallingUserId()) {
+            throw new SecurityException("Call from non-system process. Calling uid = "
+                    + Binder.getCallingUid());
+        }
+        return getImplForUser(userId).startListening(host, packageName, hostId, updatedViews);
+    }
+
     public void onUserRemoved(int userId) {
         if (userId < 1) return;
         synchronized (mAppWidgetServices) {
@@ -306,8 +322,24 @@
     }
 
     @Override
-    public void unbindRemoteViewsService(int appWidgetId, Intent intent) throws RemoteException {
-        getImplForUser(getCallingOrCurrentUserId()).unbindRemoteViewsService(
+    public void stopListeningAsUser(int hostId, int userId) throws RemoteException {
+        if (Binder.getCallingPid() != android.os.Process.myPid()
+                && userId != UserHandle.getCallingUserId()) {
+            throw new SecurityException("Call from non-system process. Calling uid = "
+                    + Binder.getCallingUid());
+        }
+        getImplForUser(userId).stopListening(hostId);
+    }
+
+    @Override
+    public void unbindRemoteViewsService(int appWidgetId, Intent intent, int userId)
+            throws RemoteException {
+        if (Binder.getCallingPid() != android.os.Process.myPid()
+                && userId != UserHandle.getCallingUserId()) {
+            throw new SecurityException("Call from non-system process. Calling uid = "
+                    + Binder.getCallingUid());
+        }
+        getImplForUser(userId).unbindRemoteViewsService(
                 appWidgetId, intent);
     }
 
diff --git a/services/java/com/android/server/AppWidgetServiceImpl.java b/services/java/com/android/server/AppWidgetServiceImpl.java
index 854185d..e1e9eaf 100644
--- a/services/java/com/android/server/AppWidgetServiceImpl.java
+++ b/services/java/com/android/server/AppWidgetServiceImpl.java
@@ -116,6 +116,15 @@
         boolean zombie; // if we're in safe mode, don't prune this just because nobody references it
 
         int tag; // for use while saving state (the index)
+
+        boolean uidMatches(int callingUid) {
+            if (UserHandle.getAppId(callingUid) == Process.myUid()) {
+                // For a host that's in the system process, ignore the user id
+                return UserHandle.isSameApp(this.uid, callingUid);
+            } else {
+                return this.uid == callingUid;
+            }
+        }
     }
 
     static class AppWidgetId {
@@ -476,7 +485,7 @@
             boolean changed = false;
             for (int i = N - 1; i >= 0; i--) {
                 Host host = mHosts.get(i);
-                if (host.uid == callingUid) {
+                if (host.uidMatches(callingUid)) {
                     deleteHostLocked(host);
                     changed = true;
                 }
@@ -744,8 +753,6 @@
                 conn.disconnect();
                 mContext.unbindService(conn);
                 mBoundRemoteViewsServices.remove(key);
-            } else {
-                Log.e("AppWidgetService", "Error (unbindRemoteViewsService): Connection not bound");
             }
         }
     }
@@ -968,13 +975,8 @@
             for (int i = 0; i < N; i++) {
                 AppWidgetId id = lookupAppWidgetIdLocked(appWidgetIds[i]);
                 if (id == null) {
-                    String message = "AppWidgetId NPE: mUserId=" + mUserId
-                            + ", callingUid=" + Binder.getCallingUid()
-                            + ", appWidgetIds[i]=" + appWidgetIds[i]
-                            + "\n  mAppWidgets:\n" + getUserWidgets();
-                    throw new NullPointerException(message);
-                }
-                if (id.views != null) {
+                    Slog.w(TAG, "widget id " + appWidgetIds[i] + " not found!");
+                } else if (id.views != null) {
                     // Only trigger a partial update for a widget if it has received a full update
                     updateAppWidgetInstanceLocked(id, views, true);
                 }
@@ -982,18 +984,6 @@
         }
     }
 
-    private String getUserWidgets() {
-        StringBuffer sb = new StringBuffer();
-        for (AppWidgetId widget: mAppWidgetIds) {
-            sb.append("    id="); sb.append(widget.appWidgetId);
-            sb.append(", hostUid="); sb.append(widget.host.uid);
-            sb.append(", provider="); sb.append(widget.provider.info.provider.toString());
-            sb.append("\n");
-        }
-        sb.append("\n");
-        return sb.toString();
-    }
-
     public void notifyAppWidgetViewDataChanged(int[] appWidgetIds, int viewId) {
         if (appWidgetIds == null) {
             return;
@@ -1186,7 +1176,7 @@
     }
 
     boolean canAccessAppWidgetId(AppWidgetId id, int callingUid) {
-        if (id.host.uid == callingUid) {
+        if (id.host.uidMatches(callingUid)) {
             // Apps hosting the AppWidget have access to it.
             return true;
         }
@@ -1229,7 +1219,7 @@
         final int N = mHosts.size();
         for (int i = 0; i < N; i++) {
             Host h = mHosts.get(i);
-            if (h.uid == uid && h.hostId == hostId) {
+            if (h.uidMatches(uid) && h.hostId == hostId) {
                 return h;
             }
         }