New API to allow third-party apps to bind widgets

Change-Id: I1a3761c1a0f557a32d4d3bdd0207567fec918ba7
diff --git a/api/16.txt b/api/16.txt
index 9e9f880..a04faf0 100644
--- a/api/16.txt
+++ b/api/16.txt
@@ -4151,7 +4151,6 @@
   }
 
   public class AppWidgetManager {
-    method public void bindAppWidgetId(int, android.content.ComponentName);
     method public int[] getAppWidgetIds(android.content.ComponentName);
     method public android.appwidget.AppWidgetProviderInfo getAppWidgetInfo(int);
     method public java.util.List<android.appwidget.AppWidgetProviderInfo> getInstalledProviders();
diff --git a/api/current.txt b/api/current.txt
index 621d0a3..d10d4c5 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -4313,7 +4313,7 @@
   }
 
   public class AppWidgetManager {
-    method public void bindAppWidgetId(int, android.content.ComponentName);
+    method public boolean bindAppWidgetIdIfAllowed(int, android.content.ComponentName);
     method public android.os.Bundle getAppWidgetExtras(int);
     method public int[] getAppWidgetIds(android.content.ComponentName);
     method public android.appwidget.AppWidgetProviderInfo getAppWidgetInfo(int);
@@ -4327,6 +4327,7 @@
     method public void updateAppWidget(int, android.widget.RemoteViews);
     method public void updateAppWidget(android.content.ComponentName, android.widget.RemoteViews);
     method public void updateAppWidgetExtras(int, android.os.Bundle);
+    field public static final java.lang.String ACTION_APPWIDGET_BIND = "android.appwidget.action.APPWIDGET_BIND";
     field public static final java.lang.String ACTION_APPWIDGET_CONFIGURE = "android.appwidget.action.APPWIDGET_CONFIGURE";
     field public static final java.lang.String ACTION_APPWIDGET_DELETED = "android.appwidget.action.APPWIDGET_DELETED";
     field public static final java.lang.String ACTION_APPWIDGET_DISABLED = "android.appwidget.action.APPWIDGET_DISABLED";
@@ -4341,6 +4342,7 @@
     field public static final java.lang.String EXTRA_APPWIDGET_MAX_WIDTH = "appWidgetMaxWidth";
     field public static final java.lang.String EXTRA_APPWIDGET_MIN_HEIGHT = "appWidgetMinHeight";
     field public static final java.lang.String EXTRA_APPWIDGET_MIN_WIDTH = "appWidgetMinWidth";
+    field public static final java.lang.String EXTRA_APPWIDGET_PROVIDER = "appWidgetProvider";
     field public static final java.lang.String EXTRA_CUSTOM_EXTRAS = "customExtras";
     field public static final java.lang.String EXTRA_CUSTOM_INFO = "customInfo";
     field public static final int INVALID_APPWIDGET_ID = 0; // 0x0
diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java
index 83ab817..f2e909e 100644
--- a/core/java/android/appwidget/AppWidgetManager.java
+++ b/core/java/android/appwidget/AppWidgetManager.java
@@ -80,6 +80,46 @@
     public static final String ACTION_APPWIDGET_PICK = "android.appwidget.action.APPWIDGET_PICK";
 
     /**
+     * Send this from your {@link AppWidgetHost} activity when you want to bind an AppWidget to
+     * display and bindAppWidgetIdIfAllowed returns false.
+     * <p>
+     * You must supply the following extras:
+     * <table>
+     *   <tr>
+     *     <td>{@link #EXTRA_APPWIDGET_ID}</td>
+     *     <td>A newly allocated appWidgetId, which will be bound to the AppWidget provider
+     *         you provide.</td>
+     *  </tr>
+     *  <tr>
+     *     <td>{@link #EXTRA_APPWIDGET_PROVIDER}</td>
+     *     <td>The BroadcastReceiver that will be the AppWidget provider for this AppWidget.
+     *     </td>
+     *  </tr>
+     * </table>
+     *
+     * <p>
+     * The system will respond with an onActivityResult call with the following extras in
+     * the intent:
+     * <table>
+     *   <tr>
+     *     <td>{@link #EXTRA_APPWIDGET_ID}</td>
+     *     <td>The appWidgetId that you supplied in the original intent.</td>
+     *  </tr>
+     * </table>
+     * <p>
+     * When you receive the result from the AppWidget bind activity, if the resultCode is
+     * {@link android.app.Activity#RESULT_OK}, the AppWidget has been bound.  You should then
+     * check the AppWidgetProviderInfo for the returned AppWidget, and if it has one, launch its
+     * configuration activity.  If {@link android.app.Activity#RESULT_CANCELED} is returned, you
+     * should delete
+     * the appWidgetId.
+     *
+     * @see #ACTION_APPWIDGET_CONFIGURE
+     *
+     */
+    public static final String ACTION_APPWIDGET_BIND = "android.appwidget.action.APPWIDGET_BIND";
+
+    /**
      * Sent when it is time to configure your AppWidget while it is being added to a host.
      * This action is not sent as a broadcast to the AppWidget provider, but as a startActivity
      * to the activity specified in the {@link AppWidgetProviderInfo AppWidgetProviderInfo meta-data}.
@@ -144,6 +184,13 @@
     public static final String EXTRA_APPWIDGET_IDS = "appWidgetIds";
 
     /**
+     * An intent extra that contains the component name of a AppWidget provider.
+     * <p>
+     * The value will be an ComponentName.
+     */
+    public static final String EXTRA_APPWIDGET_PROVIDER = "appWidgetProvider";
+
+    /**
      * An intent extra to pass to the AppWidget picker containing a {@link java.util.List} of
      * {@link AppWidgetProviderInfo} objects to mix in to the list of AppWidgets that are
      * installed.  (This is how the launcher shows the search widget).
@@ -501,12 +548,14 @@
     /**
      * Set the component for a given appWidgetId.
      *
-     * <p class="note">You need the APPWIDGET_LIST permission.  This method is to be used by the
-     * AppWidget picker.
+     * <p class="note">You need the BIND_APPWIDGET permission or the user must have enabled binding
+     *         widgets always for your component. This method is used by the AppWidget picker and
+     *         should not be used by other apps.
      *
      * @param appWidgetId     The AppWidget instance for which to set the RemoteViews.
      * @param provider      The {@link android.content.BroadcastReceiver} that will be the AppWidget
      *                      provider for this AppWidget.
+     * @hide
      */
     public void bindAppWidgetId(int appWidgetId, ComponentName provider) {
         try {
@@ -518,6 +567,68 @@
     }
 
     /**
+     * Set the component for a given appWidgetId.
+     *
+     * <p class="note">You need the BIND_APPWIDGET permission or the user must have enabled binding
+     *         widgets always for your component. Should be used by apps that host widgets; if this
+     *         method returns false, call {@link #ACTION_APPWIDGET_BIND} to request permission to
+     *         bind
+     *
+     * @param appWidgetId     The AppWidget instance for which to set the RemoteViews.
+     * @param provider      The {@link android.content.BroadcastReceiver} that will be the AppWidget
+     *                      provider for this AppWidget.
+     * @return true if this component has permission to bind the AppWidget
+     */
+    public boolean bindAppWidgetIdIfAllowed(int appWidgetId, ComponentName provider) {
+        if (mContext == null) {
+            return false;
+        }
+        try {
+            return sService.bindAppWidgetIdIfAllowed(
+                    mContext.getPackageName(), appWidgetId, provider);
+        }
+        catch (RemoteException e) {
+            throw new RuntimeException("system server dead?", e);
+        }
+    }
+
+    /**
+     * Query if a given package was granted permission by the user to bind app widgets
+     *
+     * <p class="note">You need the MODIFY_APPWIDGET_BIND_PERMISSIONS permission
+     *
+     * @param packageName        The package for which the permission is being queried
+     * @return true if the package was granted permission by the user to bind app widgets
+     * @hide
+     */
+    public boolean hasBindAppWidgetPermission(String packageName) {
+        try {
+            return sService.hasBindAppWidgetPermission(packageName);
+        }
+        catch (RemoteException e) {
+            throw new RuntimeException("system server dead?", e);
+        }
+    }
+
+    /**
+     * Changes any user-granted permission for the given package to bind app widgets
+     *
+     * <p class="note">You need the MODIFY_APPWIDGET_BIND_PERMISSIONS permission
+     *
+     * @param provider        The package whose permission is being changed
+     * @param permission      Whether to give the package permission to bind widgets
+     * @hide
+     */
+    public void setBindAppWidgetPermission(String packageName, boolean permission) {
+        try {
+            sService.setBindAppWidgetPermission(packageName, permission);
+        }
+        catch (RemoteException e) {
+            throw new RuntimeException("system server dead?", e);
+        }
+    }
+
+    /**
      * Binds the RemoteViewsService for a given appWidgetId and intent.
      *
      * The appWidgetId specified must already be bound to the calling AppWidgetHost via
diff --git a/core/java/com/android/internal/appwidget/IAppWidgetService.aidl b/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
index b1b57e7..327fe07 100644
--- a/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
+++ b/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
@@ -50,7 +50,11 @@
     void notifyAppWidgetViewDataChanged(in int[] appWidgetIds, int viewId);
     List<AppWidgetProviderInfo> getInstalledProviders();
     AppWidgetProviderInfo getAppWidgetInfo(int appWidgetId);
+    boolean hasBindAppWidgetPermission(in String packageName);
+    void setBindAppWidgetPermission(in String packageName, in boolean permission);
     void bindAppWidgetId(int appWidgetId, in ComponentName provider);
+    boolean bindAppWidgetIdIfAllowed(
+            in String packageName, int appWidgetId, in ComponentName provider);
     void bindRemoteViewsService(int appWidgetId, in Intent intent, in IBinder connection);
     void unbindRemoteViewsService(int appWidgetId, in Intent intent);
     int[] getAppWidgetIds(in ComponentName provider);
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 4443bc8..e522371 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1472,6 +1472,13 @@
         android:description="@string/permdesc_bindGadget"
         android:protectionLevel="signature|system" />
 
+    <!-- Internal permission allowing an application to query/set which
+         applications can bind AppWidgets.
+         @hide -->
+    <permission android:name="android.permission.MODIFY_APPWIDGET_BIND_PERMISSIONS"
+        android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
+        android:protectionLevel="signature|system" />
+
     <!-- Allows applications to change the background data setting
          @hide pending API council -->
     <permission android:name="android.permission.CHANGE_BACKGROUND_DATA_SETTING"
diff --git a/services/java/com/android/server/AppWidgetService.java b/services/java/com/android/server/AppWidgetService.java
index bf958a5..7e71b08 100644
--- a/services/java/com/android/server/AppWidgetService.java
+++ b/services/java/com/android/server/AppWidgetService.java
@@ -208,6 +208,23 @@
     }
 
     @Override
+    public boolean bindAppWidgetIdIfAllowed(
+            String packageName, int appWidgetId, ComponentName provider) throws RemoteException {
+        return getImplForUser().bindAppWidgetIdIfAllowed(packageName, appWidgetId, provider);
+    }
+
+    @Override
+    public boolean hasBindAppWidgetPermission(String packageName) throws RemoteException {
+        return getImplForUser().hasBindAppWidgetPermission(packageName);
+    }
+
+    @Override
+    public void setBindAppWidgetPermission(String packageName, boolean permission)
+            throws RemoteException {
+        getImplForUser().setBindAppWidgetPermission(packageName, permission);
+    }
+
+    @Override
     public void bindRemoteViewsService(int appWidgetId, Intent intent, IBinder connection)
             throws RemoteException {
         getImplForUser().bindRemoteViewsService(appWidgetId, intent, connection);
diff --git a/services/java/com/android/server/AppWidgetServiceImpl.java b/services/java/com/android/server/AppWidgetServiceImpl.java
index 3b43b9b..a0b8c531 100644
--- a/services/java/com/android/server/AppWidgetServiceImpl.java
+++ b/services/java/com/android/server/AppWidgetServiceImpl.java
@@ -24,8 +24,8 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
-import android.content.ServiceConnection;
 import android.content.Intent.FilterComparison;
+import android.content.ServiceConnection;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
@@ -56,7 +56,6 @@
 import com.android.internal.util.FastXmlSerializer;
 import com.android.internal.widget.IRemoteViewsAdapterConnection;
 import com.android.internal.widget.IRemoteViewsFactory;
-import com.android.server.am.ActivityManagerService;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -167,6 +166,8 @@
     int mNextAppWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID + 1;
     final ArrayList<AppWidgetId> mAppWidgetIds = new ArrayList<AppWidgetId>();
     ArrayList<Host> mHosts = new ArrayList<Host>();
+    // set of package names
+    HashSet<String> mPackagesWithBindWidgetPermission = new HashSet<String>();
     boolean mSafeMode;
     int mUserId;
     boolean mStateLoaded;
@@ -493,10 +494,7 @@
         }
     }
 
-    public void bindAppWidgetId(int appWidgetId, ComponentName provider) {
-        mContext.enforceCallingPermission(android.Manifest.permission.BIND_APPWIDGET,
-                "bindGagetId appWidgetId=" + appWidgetId + " provider=" + provider);
-
+    private void bindAppWidgetIdImpl(int appWidgetId, ComponentName provider) {
         final long ident = Binder.clearCallingIdentity();
         try {
             synchronized (mAppWidgetIds) {
@@ -541,6 +539,67 @@
         }
     }
 
+    public void bindAppWidgetId(int appWidgetId, ComponentName provider) {
+        mContext.enforceCallingPermission(android.Manifest.permission.BIND_APPWIDGET,
+            "bindAppWidgetId appWidgetId=" + appWidgetId + " provider=" + provider);
+        bindAppWidgetIdImpl(appWidgetId, provider);
+    }
+
+    public boolean bindAppWidgetIdIfAllowed(
+            String packageName, int appWidgetId, ComponentName provider) {
+        try {
+            mContext.enforceCallingPermission(android.Manifest.permission.BIND_APPWIDGET, null);
+        } catch (SecurityException se) {
+            if (!callerHasBindAppWidgetPermission(packageName)) {
+                return false;
+            }
+        }
+        bindAppWidgetIdImpl(appWidgetId, provider);
+        return true;
+    }
+
+    private boolean callerHasBindAppWidgetPermission(String packageName) {
+        int callingUid = Binder.getCallingUid();
+        try {
+            if (!UserId.isSameApp(callingUid, getUidForPackage(packageName))) {
+                return false;
+            }
+        } catch (Exception e) {
+            return false;
+        }
+        synchronized (mAppWidgetIds) {
+            ensureStateLoadedLocked();
+            return mPackagesWithBindWidgetPermission.contains(packageName);
+        }
+    }
+
+    public boolean hasBindAppWidgetPermission(String packageName) {
+        mContext.enforceCallingPermission(
+                android.Manifest.permission.MODIFY_APPWIDGET_BIND_PERMISSIONS,
+                "hasBindAppWidgetPermission packageName=" + packageName);
+
+        synchronized (mAppWidgetIds) {
+            ensureStateLoadedLocked();
+            return mPackagesWithBindWidgetPermission.contains(packageName);
+        }
+    }
+
+    public void setBindAppWidgetPermission(String packageName, boolean permission) {
+        mContext.enforceCallingPermission(
+                android.Manifest.permission.MODIFY_APPWIDGET_BIND_PERMISSIONS,
+                "setBindAppWidgetPermission packageName=" + packageName);
+
+        synchronized (mAppWidgetIds) {
+            ensureStateLoadedLocked();
+            if (permission) {
+                mPackagesWithBindWidgetPermission.add(packageName);
+            } else {
+                mPackagesWithBindWidgetPermission.remove(packageName);
+            }
+        }
+        saveStateLocked();
+    }
+
     // Binds to a specific RemoteViewsService
     public void bindRemoteViewsService(int appWidgetId, Intent intent, IBinder connection) {
         synchronized (mAppWidgetIds) {
@@ -1375,6 +1434,13 @@
                 out.endTag(null, "g");
             }
 
+            Iterator<String> it = mPackagesWithBindWidgetPermission.iterator();
+            while (it.hasNext()) {
+                out.startTag(null, "b");
+                out.attribute(null, "packageName", it.next());
+                out.endTag(null, "b");
+            }
+
             out.endTag(null, "gs");
 
             out.endDocument();
@@ -1445,6 +1511,11 @@
                                     .parseInt(parser.getAttributeValue(null, "id"), 16);
                             mHosts.add(host);
                         }
+                    } else if ("b".equals(tag)) {
+                        String packageName = parser.getAttributeValue(null, "packageName");
+                        if (packageName != null) {
+                            mPackagesWithBindWidgetPermission.add(packageName);
+                        }
                     } else if ("g".equals(tag)) {
                         AppWidgetId id = new AppWidgetId();
                         id.appWidgetId = Integer.parseInt(parser.getAttributeValue(null, "id"), 16);