Notify app widget hosts when cross-profile providers change.

1. Notify the hosts in the group if a package gets white-listed or
   un-white-listed for providing widgets across profiles.

2. Notify hosts in the group for provider changes when a profile
   is deleted.

bug:17126070

Change-Id: Ic719c1c68e45842d50decc95603f1d61583b8f02
diff --git a/core/java/android/app/admin/DevicePolicyManagerInternal.java b/core/java/android/app/admin/DevicePolicyManagerInternal.java
index edd8199..a1f1d92 100644
--- a/core/java/android/app/admin/DevicePolicyManagerInternal.java
+++ b/core/java/android/app/admin/DevicePolicyManagerInternal.java
@@ -26,6 +26,22 @@
 public abstract class DevicePolicyManagerInternal {
 
     /**
+     * Listener for changes in the white-listed packages to show cross-profile
+     * widgets.
+     */
+    public interface OnCrossProfileWidgetProvidersChangeListener {
+
+        /**
+         * Called when the white-listed packages to show cross-profile widgets
+         * have changed for a given user.
+         *
+         * @param profileId The profile for which the white-listed packages changed.
+         * @param packages The white-listed packages.
+         */
+        public void onCrossProfileWidgetProvidersChanged(int profileId, List<String> packages);
+    }
+
+    /**
      * Gets the packages whose widget providers are white-listed to be
      * available in the parent user.
      *
@@ -35,4 +51,13 @@
      *    profile.
      */
     public abstract List<String> getCrossProfileWidgetProviders(int profileId);
+
+    /**
+     * Adds a listener for changes in the white-listed packages to show
+     * cross-profile app widgets.
+     *
+     * @param listener The listener to add.
+     */
+    public abstract void addOnCrossProfileWidgetProvidersChangeListener(
+            OnCrossProfileWidgetProvidersChangeListener listener);
 }
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index 609ffda..c6aa30b 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -20,6 +20,7 @@
 import android.app.AppGlobals;
 import android.app.PendingIntent;
 import android.app.admin.DevicePolicyManagerInternal;
+import android.app.admin.DevicePolicyManagerInternal.OnCrossProfileWidgetProvidersChangeListener;
 import android.appwidget.AppWidgetManager;
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.BroadcastReceiver;
@@ -102,7 +103,8 @@
 import java.util.Map;
 import java.util.Set;
 
-class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBackupProvider {
+class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBackupProvider,
+        OnCrossProfileWidgetProvidersChangeListener {
     private static final String TAG = "AppWidgetServiceImpl";
 
     private static boolean DEBUG = false;
@@ -199,6 +201,7 @@
         mSecurityPolicy = new SecurityPolicy();
         computeMaximumWidgetBitmapMemory();
         registerBroadcastReceiver();
+        registerOnCrossProfileProvidersChangedListener();
     }
 
     private void computeMaximumWidgetBitmapMemory() {
@@ -243,6 +246,15 @@
                 userFilter, null, null);
     }
 
+    private void registerOnCrossProfileProvidersChangedListener() {
+        DevicePolicyManagerInternal devicePolicyManager = LocalServices.getService(
+                DevicePolicyManagerInternal.class);
+        // The device policy is an optional component.
+        if (devicePolicyManager != null) {
+            devicePolicyManager.addOnCrossProfileWidgetProvidersChangeListener(this);
+        }
+    }
+
     public void setSafeMode(boolean safeMode) {
         mSafeMode = safeMode;
     }
@@ -368,7 +380,7 @@
                 saveGroupStateAsync(userId);
 
                 // If the set of providers has been modified, notify each active AppWidgetHost
-                scheduleNotifyHostsForProvidersChangedLocked();
+                scheduleNotifyGroupHostsForProvidersChangedLocked(userId);
             }
         }
     }
@@ -1657,11 +1669,27 @@
         }
     }
 
-    private void scheduleNotifyHostsForProvidersChangedLocked() {
+    private void scheduleNotifyGroupHostsForProvidersChangedLocked(int userId) {
+        final int[] profileIds = mSecurityPolicy.getEnabledGroupProfileIds(userId);
+
         final int N = mHosts.size();
         for (int i = N - 1; i >= 0; i--) {
             Host host = mHosts.get(i);
 
+            boolean hostInGroup = false;
+            final int M = profileIds.length;
+            for (int j = 0; j < M; j++) {
+                final int profileId = profileIds[j];
+                if (host.getUserId() == profileId) {
+                    hostInGroup = true;
+                    break;
+                }
+            }
+
+            if (!hostInGroup) {
+                continue;
+            }
+
             if (host == null || host.zombie || host.callbacks == null) {
                 continue;
             }
@@ -2617,6 +2645,9 @@
 
     private void onUserStopped(int userId) {
         synchronized (mLock) {
+            boolean providersChanged = false;
+            boolean crossProfileWidgetsChanged = false;
+
             // Remove widgets that have both host and provider in the user.
             final int widgetCount = mWidgets.size();
             for (int i = widgetCount - 1; i >= 0; i--) {
@@ -2645,6 +2676,7 @@
             for (int i = hostCount - 1; i >= 0; i--) {
                 Host host = mHosts.get(i);
                 if (host.getUserId() == userId) {
+                    crossProfileWidgetsChanged |= !host.widgets.isEmpty();
                     deleteHostLocked(host);
                 }
             }
@@ -2654,6 +2686,8 @@
             for (int i = providerCount - 1; i >= 0; i--) {
                 Provider provider = mProviders.get(i);
                 if (provider.getUserId() == userId) {
+                    crossProfileWidgetsChanged |= !provider.widgets.isEmpty();
+                    providersChanged = true;
                     deleteProviderLocked(provider);
                 }
             }
@@ -2678,6 +2712,17 @@
             if (nextIdIndex >= 0) {
                 mNextAppWidgetIds.removeAt(nextIdIndex);
             }
+
+            // Announce removed provider changes to all hosts in the group.
+            if (providersChanged) {
+                scheduleNotifyGroupHostsForProvidersChangedLocked(userId);
+            }
+
+            // Save state if removing a profile changed the group state.
+            // Nothing will be saved if the group parent was removed.
+            if (crossProfileWidgetsChanged) {
+                saveGroupStateAsync(userId);
+            }
         }
     }
 
@@ -2843,6 +2888,31 @@
         }
     }
 
+    @Override
+    public void onCrossProfileWidgetProvidersChanged(int userId, List<String> packages) {
+        final int parentId = mSecurityPolicy.getProfileParent(userId);
+        // We care only if the white-listed package is in a profile of
+        // the group parent as only the parent can add widgets from the
+        // profile and not the other way around.
+        if (parentId != userId) {
+            synchronized (mLock) {
+                boolean providersChanged = false;
+
+                final int packageCount = packages.size();
+                for (int i = 0; i < packageCount; i++) {
+                    String packageName = packages.get(i);
+                    providersChanged |= updateProvidersForPackageLocked(packageName,
+                            userId, null);
+                }
+
+                if (providersChanged) {
+                    saveGroupStateAsync(userId);
+                    scheduleNotifyGroupHostsForProvidersChangedLocked(userId);
+                }
+            }
+        }
+    }
+
     private final class CallbackHandler extends Handler {
         public static final int MSG_NOTIFY_UPDATE_APP_WIDGET = 1;
         public static final int MSG_NOTIFY_PROVIDER_CHANGED = 2;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 02f8bd5..21ba31e 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -165,6 +165,8 @@
     final UserManager mUserManager;
     final PowerManager.WakeLock mWakeLock;
 
+    final LocalService mLocalService;
+
     IPowerManager mIPowerManager;
     IWindowManager mIWindowManager;
     NotificationManager mNotificationManager;
@@ -790,6 +792,7 @@
                 PackageManager.FEATURE_DEVICE_ADMIN);
         mWakeLock = ((PowerManager)context.getSystemService(Context.POWER_SERVICE))
                 .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "DPM");
+        mLocalService = new LocalService();
         if (!mHasFeature) {
             // Skip the rest of the initialization
             return;
@@ -810,7 +813,7 @@
         filter.addDataScheme("package");
         context.registerReceiverAsUser(mReceiver, UserHandle.ALL, filter, null, mHandler);
 
-        LocalServices.addService(DevicePolicyManagerInternal.class, new LocalService());
+        LocalServices.addService(DevicePolicyManagerInternal.class, mLocalService);
     }
 
     /**
@@ -1899,44 +1902,71 @@
 
     @Override
     public boolean addCrossProfileWidgetProvider(ComponentName admin, String packageName) {
-        ActiveAdmin activeAdmin = getActiveAdminForCallerLocked(admin,
-                DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
-        if (activeAdmin.crossProfileWidgetProviders == null) {
-            activeAdmin.crossProfileWidgetProviders = new ArrayList<>();
+        final int userId = UserHandle.getCallingUserId();
+        List<String> changedProviders = null;
+
+        synchronized (this) {
+            ActiveAdmin activeAdmin = getActiveAdminForCallerLocked(admin,
+                    DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+            if (activeAdmin.crossProfileWidgetProviders == null) {
+                activeAdmin.crossProfileWidgetProviders = new ArrayList<>();
+            }
+            List<String> providers = activeAdmin.crossProfileWidgetProviders;
+            if (!providers.contains(packageName)) {
+                providers.add(packageName);
+                changedProviders = new ArrayList<>(providers);
+                saveSettingsLocked(userId);
+            }
         }
-        if (activeAdmin.crossProfileWidgetProviders.add(packageName)) {
-            saveSettingsLocked(UserHandle.getCallingUserId());
+
+        if (changedProviders != null) {
+            mLocalService.notifyCrossProfileProvidersChanged(userId, changedProviders);
             return true;
         }
+
         return false;
     }
 
     @Override
     public boolean removeCrossProfileWidgetProvider(ComponentName admin, String packageName) {
-        ActiveAdmin activeAdmin = getActiveAdminForCallerLocked(admin,
-                DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
-        if (activeAdmin.crossProfileWidgetProviders == null) {
-            return false;
+        final int userId = UserHandle.getCallingUserId();
+        List<String> changedProviders = null;
+
+        synchronized (this) {
+            ActiveAdmin activeAdmin = getActiveAdminForCallerLocked(admin,
+                    DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+            if (activeAdmin.crossProfileWidgetProviders == null) {
+                return false;
+            }
+            List<String> providers = activeAdmin.crossProfileWidgetProviders;
+            if (providers.remove(packageName)) {
+                changedProviders = new ArrayList<>(providers);
+                saveSettingsLocked(userId);
+            }
         }
-        if (activeAdmin.crossProfileWidgetProviders.remove(packageName)) {
-            saveSettingsLocked(UserHandle.getCallingUserId());
+
+        if (changedProviders != null) {
+            mLocalService.notifyCrossProfileProvidersChanged(userId, changedProviders);
             return true;
         }
+
         return false;
     }
 
     @Override
     public List<String> getCrossProfileWidgetProviders(ComponentName admin) {
-        ActiveAdmin activeAdmin = getActiveAdminForCallerLocked(admin,
-                DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
-        if (activeAdmin.crossProfileWidgetProviders == null
-                || activeAdmin.crossProfileWidgetProviders.isEmpty()) {
-            return null;
-        }
-        if (Binder.getCallingUid() == Process.myUid()) {
-            return new ArrayList<>(activeAdmin.crossProfileWidgetProviders);
-        } else {
-            return activeAdmin.crossProfileWidgetProviders;
+        synchronized (this) {
+            ActiveAdmin activeAdmin = getActiveAdminForCallerLocked(admin,
+                    DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+            if (activeAdmin.crossProfileWidgetProviders == null
+                    || activeAdmin.crossProfileWidgetProviders.isEmpty()) {
+                return null;
+            }
+            if (Binder.getCallingUid() == Process.myUid()) {
+                return new ArrayList<>(activeAdmin.crossProfileWidgetProviders);
+            } else {
+                return activeAdmin.crossProfileWidgetProviders;
+            }
         }
     }
 
@@ -4674,26 +4704,51 @@
     }
 
     private final class LocalService extends DevicePolicyManagerInternal {
+        private List<OnCrossProfileWidgetProvidersChangeListener> mWidgetProviderListeners;
+
         @Override
         public List<String> getCrossProfileWidgetProviders(int profileId) {
-            ComponentName ownerComponent = mDeviceOwner.getProfileOwnerComponent(profileId);
-            if (ownerComponent == null) {
-                return Collections.emptyList();
+            synchronized (DevicePolicyManagerService.this) {
+                ComponentName ownerComponent = mDeviceOwner.getProfileOwnerComponent(profileId);
+                if (ownerComponent == null) {
+                    return Collections.emptyList();
+                }
+
+                DevicePolicyData policy = getUserData(profileId);
+                ActiveAdmin admin = policy.mAdminMap.get(ownerComponent);
+
+                if (admin == null || admin.crossProfileWidgetProviders == null
+                        || admin.crossProfileWidgetProviders.isEmpty()) {
+                    return Collections.emptyList();
+                }
+
+                return admin.crossProfileWidgetProviders;
             }
+        }
 
-            DevicePolicyData policy = getUserData(profileId);
-            ActiveAdmin admin = policy.mAdminMap.get(ownerComponent);
-
-            if (admin == null) {
-                return Collections.emptyList();
+        @Override
+        public void addOnCrossProfileWidgetProvidersChangeListener(
+                OnCrossProfileWidgetProvidersChangeListener listener) {
+            synchronized (DevicePolicyManagerService.this) {
+                if (mWidgetProviderListeners == null) {
+                    mWidgetProviderListeners = new ArrayList<>();
+                }
+                if (!mWidgetProviderListeners.contains(listener)) {
+                    mWidgetProviderListeners.add(listener);
+                }
             }
+        }
 
-            if (admin.crossProfileWidgetProviders == null
-                    || admin.crossProfileWidgetProviders.isEmpty()) {
-                return Collections.emptyList();
+        private void notifyCrossProfileProvidersChanged(int userId, List<String> packages) {
+            final List<OnCrossProfileWidgetProvidersChangeListener> listeners;
+            synchronized (DevicePolicyManagerService.this) {
+                listeners = new ArrayList<>(mWidgetProviderListeners);
             }
-
-            return admin.crossProfileWidgetProviders;
+            final int listenerCount = listeners.size();
+            for (int i = 0; i < listenerCount; i++) {
+                OnCrossProfileWidgetProvidersChangeListener listener = listeners.get(i);
+                listener.onCrossProfileWidgetProvidersChanged(userId, packages);
+            }
         }
     }
 }