OSDN Git Service

Application restrictions API
authorAmith Yamasani <yamasani@google.com>
Sat, 2 Mar 2013 01:04:38 +0000 (17:04 -0800)
committerAmith Yamasani <yamasani@google.com>
Thu, 21 Mar 2013 05:29:59 +0000 (22:29 -0700)
Adds the ability for apps to export some restrictions. The restrictions
are presented in Settings based on the restriction type. The user's
selections are stored by UserManagerService and provided to the
target user's application as a list of RestrictionEntry objects which
contain the key, value(s).

Also introduce a manifest entry for system apps to request that the
app be automatically installed in all users, so that they cannot be
deselected by the owner user.

Shared account filtering for non-whitelisted apps.

Change-Id: I15b741e3c0f3448883cb364c130783f1f6ea7ce6

17 files changed:
api/current.txt
core/java/android/accounts/AccountManager.java
core/java/android/app/Application.java
core/java/android/content/Context.java
core/java/android/content/Intent.java
core/java/android/content/RestrictionEntry.aidl [new file with mode: 0644]
core/java/android/content/RestrictionEntry.java [new file with mode: 0644]
core/java/android/content/pm/PackageInfo.java
core/java/android/content/pm/PackageParser.java
core/java/android/os/IUserManager.aidl
core/java/android/os/UserManager.java
core/res/res/values/attrs_manifest.xml
core/res/res/values/config.xml
core/res/res/values/public.xml
core/res/res/values/symbols.xml
services/java/com/android/server/accounts/AccountManagerService.java
services/java/com/android/server/pm/UserManagerService.java

index 12dc6b4..f4b4d53 100644 (file)
@@ -846,6 +846,7 @@ package android {
     field public static final int reqNavigation = 16843306; // 0x101022a
     field public static final int reqTouchScreen = 16843303; // 0x1010227
     field public static final int required = 16843406; // 0x101028e
+    field public static final int requiredForAllUsers = 16843728; // 0x10103d0
     field public static final int requiresFadingEdge = 16843685; // 0x10103a5
     field public static final int requiresSmallestWidthDp = 16843620; // 0x1010364
     field public static final int resizeMode = 16843619; // 0x1010363
@@ -5405,6 +5406,7 @@ package android.content {
     method public abstract java.lang.String[] fileList();
     method public abstract android.content.Context getApplicationContext();
     method public abstract android.content.pm.ApplicationInfo getApplicationInfo();
+    method public java.util.List<android.content.RestrictionEntry> getApplicationRestrictions();
     method public abstract android.content.res.AssetManager getAssets();
     method public abstract java.io.File getCacheDir();
     method public abstract java.lang.ClassLoader getClassLoader();
@@ -5848,6 +5850,7 @@ package android.content {
     field public static final java.lang.String ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE = "android.intent.action.EXTERNAL_APPLICATIONS_UNAVAILABLE";
     field public static final java.lang.String ACTION_FACTORY_TEST = "android.intent.action.FACTORY_TEST";
     field public static final java.lang.String ACTION_GET_CONTENT = "android.intent.action.GET_CONTENT";
+    field public static final java.lang.String ACTION_GET_RESTRICTION_ENTRIES = "android.intent.action.GET_RESTRICTION_ENTRIES";
     field public static final java.lang.String ACTION_GTALK_SERVICE_CONNECTED = "android.intent.action.GTALK_CONNECTED";
     field public static final java.lang.String ACTION_GTALK_SERVICE_DISCONNECTED = "android.intent.action.GTALK_DISCONNECTED";
     field public static final java.lang.String ACTION_HEADSET_PLUG = "android.intent.action.HEADSET_PLUG";
@@ -5989,6 +5992,7 @@ package android.content {
     field public static final java.lang.String EXTRA_REFERRER = "android.intent.extra.REFERRER";
     field public static final java.lang.String EXTRA_REMOTE_INTENT_TOKEN = "android.intent.extra.remote_intent_token";
     field public static final java.lang.String EXTRA_REPLACING = "android.intent.extra.REPLACING";
+    field public static final java.lang.String EXTRA_RESTRICTIONS = "android.intent.extra.restrictions";
     field public static final java.lang.String EXTRA_RETURN_RESULT = "android.intent.extra.RETURN_RESULT";
     field public static final java.lang.String EXTRA_SHORTCUT_ICON = "android.intent.extra.shortcut.ICON";
     field public static final java.lang.String EXTRA_SHORTCUT_ICON_RESOURCE = "android.intent.extra.shortcut.ICON_RESOURCE";
@@ -6231,6 +6235,33 @@ package android.content {
     ctor public ReceiverCallNotAllowedException(java.lang.String);
   }
 
+  public class RestrictionEntry implements android.os.Parcelable {
+    ctor public RestrictionEntry(java.lang.String, java.lang.String);
+    ctor public RestrictionEntry(java.lang.String, boolean);
+    ctor public RestrictionEntry(java.lang.String, java.lang.String[]);
+    ctor public RestrictionEntry(android.os.Parcel);
+    method public int describeContents();
+    method public boolean getBooleanValue();
+    method public java.lang.String[] getMultipleValues();
+    method public java.lang.String getStringValue();
+    method public void setMultipleValues(java.lang.String[]);
+    method public void setValue(java.lang.String);
+    method public void setValue(boolean);
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator CREATOR;
+    field public static final int TYPE_BOOLEAN = 1; // 0x1
+    field public static final int TYPE_CHOICE = 2; // 0x2
+    field public static final int TYPE_CHOICE_LEVEL = 3; // 0x3
+    field public static final int TYPE_MULTI_SELECT = 4; // 0x4
+    field public static final int TYPE_NULL = 0; // 0x0
+    field public java.lang.String[] choices;
+    field public java.lang.String description;
+    field public java.lang.String key;
+    field public java.lang.String title;
+    field public int type;
+    field public java.lang.String[] values;
+  }
+
   public class SearchRecentSuggestionsProvider extends android.content.ContentProvider {
     ctor public SearchRecentSuggestionsProvider();
     method public int delete(android.net.Uri, java.lang.String, java.lang.String[]);
index f8b7a0c..313260f 100644 (file)
@@ -152,6 +152,9 @@ public class AccountManager {
     public static final int ERROR_CODE_BAD_ARGUMENTS = 7;
     public static final int ERROR_CODE_BAD_REQUEST = 8;
 
+    /** @hide */
+    public static final int ERROR_CODE_USER_RESTRICTED = 100;
+
     /**
      * Bundle key used for the {@link String} account name in results
      * from methods which return information about a particular account.
@@ -1526,7 +1529,7 @@ public class AccountManager {
             }
 
             public void onError(int code, String message) {
-                if (code == ERROR_CODE_CANCELED) {
+                if (code == ERROR_CODE_CANCELED || code == ERROR_CODE_USER_RESTRICTED) {
                     // the authenticator indicated that this request was canceled, do so now
                     cancel(true /* mayInterruptIfRunning */);
                     return;
index 132388e..7b07438 100644 (file)
 package android.app;
 
 import java.util.ArrayList;
+import java.util.List;
 
 import android.content.ComponentCallbacks;
 import android.content.ComponentCallbacks2;
 import android.content.Context;
 import android.content.ContextWrapper;
 import android.content.Intent;
+import android.content.RestrictionEntry;
 import android.content.res.Configuration;
 import android.os.Bundle;
+import android.os.UserManager;
 
 /**
  * Base class for those who need to maintain global application state. You can
@@ -131,6 +134,11 @@ public class Application extends ContextWrapper implements ComponentCallbacks2 {
         }
     }
 
+    public List<RestrictionEntry> getApplicationRestrictions() {
+        return ((UserManager) getSystemService(USER_SERVICE))
+                .getApplicationRestrictions(getPackageName(), android.os.Process.myUserHandle());
+    }
+
     public void registerComponentCallbacks(ComponentCallbacks callback) {
         synchronized (mComponentCallbacks) {
             mComponentCallbacks.add(callback);
index 8a9eed2..7dd76cd 100644 (file)
@@ -45,6 +45,7 @@ import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.util.List;
 
 /**
  * Interface to global information about an application environment.  This is
@@ -286,6 +287,15 @@ public abstract class Context {
     public abstract Context getApplicationContext();
 
     /**
+     * Returns the list of restrictions for the application, or null if there are no
+     * restrictions.
+     * @return
+     */
+    public List<RestrictionEntry> getApplicationRestrictions() {
+        return getApplicationContext().getApplicationRestrictions();
+    }
+
+    /**
      * Add a new {@link ComponentCallbacks} to the base application of the
      * Context, which will be called at the same times as the ComponentCallbacks
      * methods of activities and other components are called.  Note that you
index 53c47d2..e1461e3 100644 (file)
@@ -2415,6 +2415,15 @@ public class Intent implements Parcelable, Cloneable {
             "android.intent.action.PRE_BOOT_COMPLETED";
 
     /**
+     * Broadcast to a specific application to query any supported restrictions to impose
+     * on restricted users. The response should contain an extra {@link #EXTRA_RESTRICTIONS}
+     * which is of type <code>ArrayList&lt;RestrictionEntry&gt;</code>.
+     * @see RestrictionEntry
+     */
+    public static final String ACTION_GET_RESTRICTION_ENTRIES =
+            "android.intent.action.GET_RESTRICTION_ENTRIES";
+
+    /**
      * Sent the first time a user is starting, to allow system apps to
      * perform one time initialization.  (This will not be seen by third
      * party applications because a newly initialized user does not have any
@@ -3146,6 +3155,12 @@ public class Intent implements Parcelable, Cloneable {
     public static final String EXTRA_USER_HANDLE =
             "android.intent.extra.user_handle";
 
+    /**
+     * Extra used in the response from a BroadcastReceiver that handles
+     * {@link #ACTION_GET_RESTRICTION_ENTRIES}.
+     */
+    public static final String EXTRA_RESTRICTIONS = "android.intent.extra.restrictions";
+
     // ---------------------------------------------------------------------
     // ---------------------------------------------------------------------
     // Intent flags (see mFlags variable).
diff --git a/core/java/android/content/RestrictionEntry.aidl b/core/java/android/content/RestrictionEntry.aidl
new file mode 100644 (file)
index 0000000..b93eee3
--- /dev/null
@@ -0,0 +1,20 @@
+/*
+**
+** Copyright 2013, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.content;
+
+parcelable RestrictionEntry;
diff --git a/core/java/android/content/RestrictionEntry.java b/core/java/android/content/RestrictionEntry.java
new file mode 100644 (file)
index 0000000..196460c
--- /dev/null
@@ -0,0 +1,275 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Inherited;
+
+/**
+ * Applications can expose restrictions for a restricted user on a
+ * multiuser device. The administrator can configure these restrictions that will then be
+ * applied to the restricted user. Each RestrictionsEntry is one configurable restriction.
+ * <p/>
+ * Any application that chooses to expose such restrictions does so by implementing a
+ * receiver that handles the {@link Intent.ACTION_GET_RESTRICTION_ENTRIES} action.
+ * The receiver then returns a result bundle that contains an entry called "restrictions", whose
+ * value is an ArrayList<RestrictionsEntry>.
+ */
+public class RestrictionEntry implements Parcelable {
+
+    /**
+     * A type of restriction. Use this one for information that needs to be transferred across
+     * but shouldn't be presented to the user in the UI.
+     */
+    public static final int TYPE_NULL         = 0;
+    /**
+     * A type of restriction. Use this for storing true/false values, typically presented as
+     * a checkbox in the UI.
+     */
+    public static final int TYPE_BOOLEAN      = 1;
+    /**
+     * A type of restriction. Use this for storing a string value, typically presented as
+     * a single-select list. The {@link #values} and {@link #choices} need to have the list of
+     * possible values and the corresponding localized strings, respectively, to present in the UI.
+     */
+    public static final int TYPE_CHOICE       = 2;
+    /**
+     * A type of restriction. Use this for storing a string value, typically presented as
+     * a single-select list. The {@link #values} and {@link #choices} need to have the list of
+     * possible values and the corresponding localized strings, respectively, to present in the UI.
+     * The presentation could imply that values in lower array indices are included when a
+     * particular value is chosen.
+     */
+    public static final int TYPE_CHOICE_LEVEL = 3;
+    /**
+     * A type of restriction. Use this for presenting a multi-select list where more than one
+     * entry can be selected, such as for choosing specific titles to white-list.
+     * The {@link #values} and {@link #choices} need to have the list of
+     * possible values and the corresponding localized strings, respectively, to present in the UI.
+     * Use {@link #getMultipleValues()} and {@link #setMultipleValues(String[])} to manipulate
+     * the selections.
+     */
+    public static final int TYPE_MULTI_SELECT = 4;
+
+    /** The type of restriction. */
+    public int type;
+
+    /** The unique key that identifies the restriction. */
+    public String key;
+
+    /** The user-visible title of the restriction. */
+    public String title;
+
+    /** The user-visible secondary description of the restriction. */
+    public String description;
+
+    /** The user-visible set of choices used for single-select and multi-select lists. */
+    public String [] choices;
+
+    /** The values corresponding to the user-visible choices. The value(s) of this entry will
+     * one or more of these, returned by {@link #getMultipleValues()} and
+     * {@link #getStringValue()}.
+     */
+    public String [] values;
+
+    /* The chosen value, whose content depends on the type of the restriction. */
+    private String currentValue;
+    /* List of selected choices in the multi-select case. */
+    private String[] currentValues;
+
+    /**
+     * Constructor for {@link #TYPE_CHOICE} and {@link #TYPE_CHOICE_LEVEL} types.
+     * @param key the unique key for this restriction
+     * @param value the current value
+     */
+    public RestrictionEntry(String key, String value) {
+        this.key = key;
+        this.currentValue = value;
+    }
+
+    /**
+     * Constructor for {@link #TYPE_BOOLEAN} type.
+     * @param key the unique key for this restriction
+     * @param value the current value
+     */
+    public RestrictionEntry(String key, boolean value) {
+        this.key = key;
+        setValue(value);
+    }
+
+    /**
+     * Constructor for {@link #TYPE_MULTI_SELECT} type.
+     * @param key the unique key for this restriction
+     * @param multipleValues the list of values that are currently selected
+     */
+    public RestrictionEntry(String key, String[] multipleValues) {
+        this.key = key;
+        this.currentValues = multipleValues;
+    }
+
+    /**
+     * Returns the current value. Null for {@link #TYPE_MULTI_SELECT} type.
+     * @return the current value
+     */
+    public String getStringValue() {
+        return currentValue;
+    }
+
+    /**
+     * Returns the list of current selections. Null if the type is not {@link #TYPE_MULTI_SELECT}.
+     * @return the list of current selections.
+     */
+    public String[] getMultipleValues() {
+        return currentValues;
+    }
+
+    /**
+     * Returns the current boolean value for entries of type {@link #TYPE_BOOLEAN}.
+     * @return the current value
+     */
+    public boolean getBooleanValue() {
+        return Boolean.parseBoolean(currentValue);
+    }
+
+    /**
+     * Set the current string value.
+     * @param s the current value
+     */
+    public void setValue(String s) {
+        currentValue = s;
+    }
+
+    /**
+     * Sets the current boolean value.
+     * @param b the current value
+     */
+    public void setValue(boolean b) {
+        currentValue = Boolean.toString(b);
+    }
+
+    /**
+     * Sets the current list of selected values.
+     * @param values the current list of selected values
+     */
+    public void setMultipleValues(String[] values) {
+        currentValues = values;
+    }
+
+    private boolean equalArrays(String[] one, String[] other) {
+        if (one.length != other.length) return false;
+        for (int i = 0; i < one.length; i++) {
+            if (!one[i].equals(other[i])) return false;
+        }
+        return true;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o == this) return true;
+        if (!(o instanceof RestrictionEntry)) return false;
+        final RestrictionEntry other = (RestrictionEntry) o;
+        // Make sure that either currentValue matches or currentValues matches.
+        return type == other.type && key.equals(other.key)
+                &&
+                ((currentValues == null && other.currentValues == null
+                  && currentValue != null && currentValue.equals(other.currentValue))
+                 ||
+                 (currentValue == null && other.currentValue == null
+                  && currentValues != null && equalArrays(currentValues, other.currentValues)));
+    }
+
+    @Override
+    public int hashCode() {
+        int result = 17;
+        result = 31 * result + key.hashCode();
+        if (currentValue != null) {
+            result = 31 * result + currentValue.hashCode();
+        } else if (currentValues != null) {
+            for (String value : currentValues) {
+                if (value != null) {
+                    result = 31 * result + value.hashCode();
+                }
+            }
+        }
+        return result;
+    }
+
+    private String[] readArray(Parcel in) {
+        int count = in.readInt();
+        String[] values = new String[count];
+        for (int i = 0; i < count; i++) {
+            values[i] = in.readString();
+        }
+        return values;
+    }
+
+    public RestrictionEntry(Parcel in) {
+        type = in.readInt();
+        key = in.readString();
+        title = in.readString();
+        description = in.readString();
+        choices = readArray(in);
+        values = readArray(in);
+        currentValue = in.readString();
+        currentValues = readArray(in);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    private void writeArray(Parcel dest, String[] values) {
+        if (values == null) {
+            dest.writeInt(0);
+        } else {
+            dest.writeInt(values.length);
+            for (int i = 0; i < values.length; i++) {
+                dest.writeString(values[i]);
+            }
+        }
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(type);
+        dest.writeString(key);
+        dest.writeString(title);
+        dest.writeString(description);
+        writeArray(dest, choices);
+        writeArray(dest, values);
+        dest.writeString(currentValue);
+        writeArray(dest, currentValues);
+    }
+
+    public static final Creator<RestrictionEntry> CREATOR = new Creator<RestrictionEntry>() {
+        public RestrictionEntry createFromParcel(Parcel source) {
+            return new RestrictionEntry(source);
+        }
+
+        public RestrictionEntry[] newArray(int size) {
+            return new RestrictionEntry[size];
+        }
+    };
+
+    @Override
+    public String toString() {
+        return "RestrictionsEntry {type=" + type + ", key=" + key + ", value=" + currentValue + "}";
+    }
+}
index 85f7aa5..77ca7f6 100644 (file)
@@ -217,7 +217,10 @@ public class PackageInfo implements Parcelable {
      * @hide
      */
     public int installLocation = INSTALL_LOCATION_INTERNAL_ONLY;
-    
+
+    /** @hide */
+    public boolean requiredForAllUsers;
+
     public PackageInfo() {
     }
 
@@ -258,6 +261,7 @@ public class PackageInfo implements Parcelable {
         dest.writeTypedArray(configPreferences, parcelableFlags);
         dest.writeTypedArray(reqFeatures, parcelableFlags);
         dest.writeInt(installLocation);
+        dest.writeInt(requiredForAllUsers ? 1 : 0);
     }
 
     public static final Parcelable.Creator<PackageInfo> CREATOR
@@ -296,5 +300,6 @@ public class PackageInfo implements Parcelable {
         configPreferences = source.createTypedArray(ConfigurationInfo.CREATOR);
         reqFeatures = source.createTypedArray(FeatureInfo.CREATOR);
         installLocation = source.readInt();
+        requiredForAllUsers = source.readInt() != 0;
     }
 }
index 5eac903..149b8e5 100644 (file)
@@ -289,6 +289,7 @@ public class PackageParser {
         pi.sharedUserLabel = p.mSharedUserLabel;
         pi.applicationInfo = generateApplicationInfo(p, flags, state, userId);
         pi.installLocation = p.installLocation;
+        pi.requiredForAllUsers = p.mRequiredForAllUsers;
         pi.firstInstallTime = firstInstallTime;
         pi.lastUpdateTime = lastUpdateTime;
         if ((flags&PackageManager.GET_GIDS) != 0) {
@@ -1760,6 +1761,11 @@ public class PackageParser {
                     false)) {
                 ai.flags |= ApplicationInfo.FLAG_PERSISTENT;
             }
+            if (sa.getBoolean(
+                    com.android.internal.R.styleable.AndroidManifestApplication_requiredForAllUsers,
+                    false)) {
+                owner.mRequiredForAllUsers = true;
+            }
         }
 
         if (sa.getBoolean(
@@ -3271,6 +3277,9 @@ public class PackageParser {
 
         public int installLocation;
 
+        /* An app that's required for all users and cannot be uninstalled for a user */
+        public boolean mRequiredForAllUsers;
+
         /**
          * Digest suitable for comparing whether this package's manifest is the
          * same as another.
index 34c9740..4c2d7a6 100644 (file)
@@ -20,6 +20,7 @@ package android.os;
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
 import android.content.pm.UserInfo;
+import android.content.RestrictionEntry;
 import android.graphics.Bitmap;
 
 /**
@@ -40,4 +41,7 @@ interface IUserManager {
     int getUserHandle(int userSerialNumber);
     Bundle getUserRestrictions(int userHandle);
     void setUserRestrictions(in Bundle restrictions, int userHandle);
+    void setApplicationRestrictions(in String packageName, in List<RestrictionEntry> entries,
+            int userHandle);
+    List<RestrictionEntry> getApplicationRestrictions(in String packageName, int userHandle);
 }
index 51e3e7c..7c05528 100644 (file)
@@ -17,6 +17,7 @@ package android.os;
 
 import android.app.ActivityManagerNative;
 import android.content.Context;
+import android.content.RestrictionEntry;
 import android.content.pm.UserInfo;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
@@ -126,7 +127,19 @@ public class UserManager {
     public boolean isUserAGoat() {
         return false;
     }
+
+    /**
+     * @hide
+     */
+    public boolean isUserRestricted() {
+        try {
+            return mService.getUserInfo(getUserHandle()).isRestricted();
+        } catch (RemoteException re) {
+            Log.w(TAG, "Could not check if user restricted ", re);
+            return false;
+        }
+    }
+
     /**
      * Return whether the given user is actively running.  This means that
      * the user is in the "started" state, not "stopped" -- it is currently
@@ -450,10 +463,34 @@ public class UserManager {
     }
 
     /**
-     * Returns whether the current user is allow to toggle location sharing settings.
+     * Returns whether the current user is allowed to toggle location sharing settings.
      * @hide
      */
     public boolean isLocationSharingToggleAllowed() {
         return getUserRestrictions().getBoolean(ALLOW_CONFIG_LOCATION_ACCESS);
     }
+
+    /**
+     * @hide
+     */
+    public List<RestrictionEntry> getApplicationRestrictions(String packageName, UserHandle user) {
+        try {
+            return mService.getApplicationRestrictions(packageName, user.getIdentifier());
+        } catch (RemoteException re) {
+            Log.w(TAG, "Could not get application restrictions for user " + user.getIdentifier());
+        }
+        return null;
+    }
+
+    /**
+     * @hide
+     */
+    public void setApplicationRestrictions(String packageName, List<RestrictionEntry> entries,
+            UserHandle user) {
+        try {
+            mService.setApplicationRestrictions(packageName, entries, user.getIdentifier());
+        } catch (RemoteException re) {
+            Log.w(TAG, "Could not set application restrictions for user " + user.getIdentifier());
+        }
+    }
 }
index f1d8c03..6f59817 100644 (file)
          not normally be used by applications; it requires that the system keep
          your application running at all times. -->
     <attr name="persistent" format="boolean" />
-    
+
+    <!-- Flag to specify if this application needs to be present for all users. Only pre-installed
+         applications can request this feature. Default value is false. -->
+    <attr name="requiredForAllUsers" format="boolean" />
+
     <!-- Flag indicating whether the application can be debugged, even when
          running on a device that is running in user mode. -->
     <attr name="debuggable" format="boolean" />
              for normal behavior. -->
         <attr name="hasCode" format="boolean" />
         <attr name="persistent" />
+        <attr name="requiredForAllUsers" />
         <!-- Specify whether the components in this application are enabled or not (that is, can be
              instantiated by the system).
              If "false", it overrides any component specific values (a value of "true" will not
index 6a8407f..7a6a1e9 100644 (file)
     <string name="config_chooseTypeAndAccountActivity"
             >android/android.accounts.ChooseTypeAndAccountActivity</string>
 
+    <!-- Apps that are authorized to access shared accounts, overridden by product overlays -->
+    <string name="config_appsAuthorizedForSharedAccounts"></string>
 </resources>
index 42e5cf1..ef0ae03 100644 (file)
       <eat-comment />
 
   <public type="attr" name="windowOverscan" />
+  <public type="attr" name="requiredForAllUsers" />
   <public type="style" name="Theme.NoTitleBar.Overscan" />
   <public type="style" name="Theme.Light.NoTitleBar.Overscan" />
   <public type="style" name="Theme.Black.NoTitleBar.Overscan" />
index 614ac07..a763c90 100644 (file)
   <java-symbol type="string" name="owner_name" />
   <java-symbol type="string" name="config_chooseAccountActivity" />
   <java-symbol type="string" name="config_chooseTypeAndAccountActivity" />
+  <java-symbol type="string" name="config_appsAuthorizedForSharedAccounts" />
 
 
   <java-symbol type="plurals" name="abbrev_in_num_days" />
index 49295f5..09daf56 100644 (file)
@@ -455,7 +455,6 @@ public class AccountManagerService
 
     @Override
     public void onServiceChanged(AuthenticatorDescription desc, int userId, boolean removed) {
-        Slog.d(TAG, "onServiceChanged() for userId " + userId);
         validateAccountsInternal(getUserAccounts(userId), false /* invalidateAuthenticatorCache */);
     }
 
@@ -588,16 +587,12 @@ public class AccountManagerService
                     if (result != null) {
                         if (result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT, false)) {
                             // Create a Session for the target user and pass in the bundle
-                            Slog.i(TAG, "getAccountCredentialsForCloning returned success, "
-                                    + "sending result to target user");
                             completeCloningAccount(result, account, toAccounts);
                         } else {
-                            Slog.e(TAG, "getAccountCredentialsForCloning returned failure");
                             clonePassword(fromAccounts, toAccounts, account);
                         }
                         return;
                     } else {
-                        Slog.e(TAG, "getAccountCredentialsForCloning returned null");
                         clonePassword(fromAccounts, toAccounts, account);
                         super.onResult(result);
                     }
@@ -645,15 +640,12 @@ public class AccountManagerService
                     if (result != null) {
                         if (result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT, false)) {
                             // TODO: Anything?
-                            Slog.i(TAG, "addAccount returned success");
                         } else {
                             // TODO: Show error notification
                             // TODO: Should we remove the shadow account to avoid retries?
-                            Slog.e(TAG, "addAccountFromCredentials returned failure");
                         }
                         return;
                     } else {
-                        Slog.e(TAG, "addAccountFromCredentials returned null");
                         super.onResult(result);
                     }
                 }
@@ -1433,6 +1425,17 @@ public class AccountManagerService
         if (accountType == null) throw new IllegalArgumentException("accountType is null");
         checkManageAccountsPermission();
 
+        // Is user allowed to modify accounts?
+        if (!getUserManager().getUserRestrictions(Binder.getCallingUserHandle())
+                .getBoolean(UserManager.ALLOW_MODIFY_ACCOUNTS)) {
+            try {
+                response.onError(AccountManager.ERROR_CODE_USER_RESTRICTED,
+                        "User is not allowed to add an account!");
+            } catch (RemoteException re) {
+            }
+            return;
+        }
+
         UserAccounts accounts = getUserAccountsForCaller();
         final int pid = Binder.getCallingPid();
         final int uid = Binder.getCallingUid();
@@ -1573,17 +1576,19 @@ public class AccountManagerService
         private volatile Account[] mAccountsOfType = null;
         private volatile ArrayList<Account> mAccountsWithFeatures = null;
         private volatile int mCurrentAccount = 0;
+        private int mCallingUid;
 
         public GetAccountsByTypeAndFeatureSession(UserAccounts accounts,
-                IAccountManagerResponse response, String type, String[] features) {
+                IAccountManagerResponse response, String type, String[] features, int callingUid) {
             super(accounts, response, type, false /* expectActivityLaunch */,
                     true /* stripAuthTokenFromResult */);
+            mCallingUid = callingUid;
             mFeatures = features;
         }
 
         public void run() throws RemoteException {
             synchronized (mAccounts.cacheLock) {
-                mAccountsOfType = getAccountsFromCacheLocked(mAccounts, mAccountType);
+                mAccountsOfType = getAccountsFromCacheLocked(mAccounts, mAccountType, mCallingUid);
             }
             // check whether each account matches the requested features
             mAccountsWithFeatures = new ArrayList<Account>(mAccountsOfType.length);
@@ -1668,10 +1673,11 @@ public class AccountManagerService
     public Account[] getAccounts(int userId) {
         checkReadAccountsPermission();
         UserAccounts accounts = getUserAccounts(userId);
+        int callingUid = Binder.getCallingUid();
         long identityToken = clearCallingIdentity();
         try {
             synchronized (accounts.cacheLock) {
-                return getAccountsFromCacheLocked(accounts, null);
+                return getAccountsFromCacheLocked(accounts, null, callingUid);
             }
         } finally {
             restoreCallingIdentity(identityToken);
@@ -1711,7 +1717,8 @@ public class AccountManagerService
                 UserAccounts userAccounts = getUserAccounts(userId);
                 if (userAccounts == null) continue;
                 synchronized (userAccounts.cacheLock) {
-                    Account[] accounts = getAccountsFromCacheLocked(userAccounts, null);
+                    Account[] accounts = getAccountsFromCacheLocked(userAccounts, null,
+                            Binder.getCallingUid());
                     for (int a = 0; a < accounts.length; a++) {
                         runningAccounts.add(new AccountAndUser(accounts[a], userId));
                     }
@@ -1725,9 +1732,10 @@ public class AccountManagerService
 
     @Override
     public Account[] getAccountsAsUser(String type, int userId) {
+        final int callingUid = Binder.getCallingUid();
         // Only allow the system process to read accounts of other users
         if (userId != UserHandle.getCallingUserId()
-                && Binder.getCallingUid() != android.os.Process.myUid()) {
+                && callingUid != android.os.Process.myUid()) {
             throw new SecurityException("User " + UserHandle.getCallingUserId()
                     + " trying to get account for " + userId);
         }
@@ -1742,7 +1750,7 @@ public class AccountManagerService
         long identityToken = clearCallingIdentity();
         try {
             synchronized (accounts.cacheLock) {
-                return getAccountsFromCacheLocked(accounts, type);
+                return getAccountsFromCacheLocked(accounts, type, callingUid);
             }
         } finally {
             restoreCallingIdentity(identityToken);
@@ -1826,19 +1834,21 @@ public class AccountManagerService
         if (type == null) throw new IllegalArgumentException("accountType is null");
         checkReadAccountsPermission();
         UserAccounts userAccounts = getUserAccountsForCaller();
+        int callingUid = Binder.getCallingUid();
         long identityToken = clearCallingIdentity();
         try {
             if (features == null || features.length == 0) {
                 Account[] accounts;
                 synchronized (userAccounts.cacheLock) {
-                    accounts = getAccountsFromCacheLocked(userAccounts, type);
+                    accounts = getAccountsFromCacheLocked(userAccounts, type, callingUid);
                 }
                 Bundle result = new Bundle();
                 result.putParcelableArray(AccountManager.KEY_ACCOUNTS, accounts);
                 onResult(response, result);
                 return;
             }
-            new GetAccountsByTypeAndFeatureSession(userAccounts, response, type, features).bind();
+            new GetAccountsByTypeAndFeatureSession(userAccounts, response, type, features,
+                    callingUid).bind();
         } finally {
             restoreCallingIdentity(identityToken);
         }
@@ -2352,7 +2362,8 @@ public class AccountManagerService
                     }
                 }
             } else {
-                Account[] accounts = getAccountsFromCacheLocked(userAccounts, null /* type */);
+                Account[] accounts = getAccountsFromCacheLocked(userAccounts, null /* type */,
+                        android.os.Process.myUid());
                 fout.println("Accounts: " + accounts.length);
                 for (Account account : accounts) {
                     fout.println("  " + account);
@@ -2691,13 +2702,56 @@ public class AccountManagerService
         accounts.accountCache.put(account.type, newAccountsForType);
     }
 
-    protected Account[] getAccountsFromCacheLocked(UserAccounts userAccounts, String accountType) {
+    private Account[] filterSharedAccounts(UserAccounts userAccounts, Account[] unfiltered,
+            int callingUid) {
+        if (getUserManager() == null || userAccounts == null || userAccounts.userId < 0
+                || callingUid == android.os.Process.myUid()) {
+            return unfiltered;
+        }
+        if (mUserManager.getUserInfo(userAccounts.userId).isRestricted()) {
+            String[] packages = mPackageManager.getPackagesForUid(callingUid);
+            // If any of the packages includes a white listed package, return the full set,
+            // otherwise return non-shared accounts only.
+            // This might be a temporary way to specify a whitelist
+            String whiteList = mContext.getResources().getString(
+                    com.android.internal.R.string.config_appsAuthorizedForSharedAccounts);
+            for (String packageName : packages) {
+                if (whiteList.contains(";" + packageName + ";")) {
+                    return unfiltered;
+                }
+            }
+            ArrayList<Account> allowed = new ArrayList<Account>();
+            Account[] sharedAccounts = getSharedAccountsAsUser(userAccounts.userId);
+            if (sharedAccounts == null || sharedAccounts.length == 0) return unfiltered;
+            for (Account account : unfiltered) {
+                boolean found = false;
+                for (Account shared : sharedAccounts) {
+                    if (shared.equals(account)) {
+                        found = true;
+                        break;
+                    }
+                }
+                if (!found) {
+                    allowed.add(account);
+                }
+            }
+            Account[] filtered = new Account[allowed.size()];
+            allowed.toArray(filtered);
+            return filtered;
+        } else {
+            return unfiltered;
+        }
+    }
+
+    protected Account[] getAccountsFromCacheLocked(UserAccounts userAccounts, String accountType,
+            int callingUid) {
         if (accountType != null) {
             final Account[] accounts = userAccounts.accountCache.get(accountType);
             if (accounts == null) {
                 return EMPTY_ACCOUNT_ARRAY;
             } else {
-                return Arrays.copyOf(accounts, accounts.length);
+                return filterSharedAccounts(userAccounts, Arrays.copyOf(accounts, accounts.length),
+                        callingUid);
             }
         } else {
             int totalLength = 0;
@@ -2714,7 +2768,7 @@ public class AccountManagerService
                         accountsOfType.length);
                 totalLength += accountsOfType.length;
             }
-            return accounts;
+            return filterSharedAccounts(userAccounts, accounts, callingUid);
         }
     }
 
index 1414cbd..636b0e5 100644 (file)
@@ -25,7 +25,10 @@ import android.app.IStopUserCallback;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
+import android.content.RestrictionEntry;
+import android.content.SharedPreferences;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.UserInfo;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
@@ -83,11 +86,17 @@ public class UserManagerService extends IUserManager.Stub {
     private static final String TAG_USERS = "users";
     private static final String TAG_USER = "user";
     private static final String TAG_RESTRICTIONS = "restrictions";
+    private static final String TAG_ENTRY = "entry";
+    private static final String TAG_VALUE = "value";
+    private static final String ATTR_KEY = "key";
+    private static final String ATTR_MULTIPLE = "m";
 
     private static final String USER_INFO_DIR = "system" + File.separator + "users";
     private static final String USER_LIST_FILENAME = "userlist.xml";
     private static final String USER_PHOTO_FILENAME = "photo.png";
 
+    private static final String RESTRICTIONS_FILE_PREFIX = "res_";
+
     private static final int MIN_USER_ID = 10;
 
     private static final int USER_VERSION = 2;
@@ -947,6 +956,151 @@ public class UserManagerService extends IUserManager.Stub {
     }
 
     @Override
+    public List<RestrictionEntry> getApplicationRestrictions(String packageName, int userId) {
+        if (UserHandle.getCallingUserId() != userId
+                || Binder.getCallingUid() != getUidForPackage(packageName)) {
+            checkManageUsersPermission("Only system can get restrictions for other users/apps");
+        }
+        synchronized (mPackagesLock) {
+            // Read the restrictions from XML
+            return readApplicationRestrictionsLocked(packageName, userId);
+        }
+    }
+
+    @Override
+    public void setApplicationRestrictions(String packageName, List<RestrictionEntry> entries,
+            int userId) {
+        if (UserHandle.getCallingUserId() != userId
+                || Binder.getCallingUid() != getUidForPackage(packageName)) {
+            checkManageUsersPermission("Only system can set restrictions for other users/apps");
+        }
+        synchronized (mPackagesLock) {
+            // Write the restrictions to XML
+            writeApplicationRestrictionsLocked(packageName, entries, userId);
+        }
+    }
+
+    private int getUidForPackage(String packageName) {
+        try {
+            return mContext.getPackageManager().getApplicationInfo(packageName,
+                    PackageManager.GET_UNINSTALLED_PACKAGES).uid;
+        } catch (NameNotFoundException nnfe) {
+            return -1;
+        }
+    }
+
+    private List<RestrictionEntry> readApplicationRestrictionsLocked(String packageName,
+            int userId) {
+        final ArrayList<RestrictionEntry> entries = new ArrayList<RestrictionEntry>();
+        final ArrayList<String> values = new ArrayList<String>();
+
+        FileInputStream fis = null;
+        try {
+            AtomicFile restrictionsFile =
+                    new AtomicFile(new File(Environment.getUserSystemDirectory(userId),
+                            RESTRICTIONS_FILE_PREFIX + packageName + ".xml"));
+            fis = restrictionsFile.openRead();
+            XmlPullParser parser = Xml.newPullParser();
+            parser.setInput(fis, null);
+            int type;
+            while ((type = parser.next()) != XmlPullParser.START_TAG
+                    && type != XmlPullParser.END_DOCUMENT) {
+                ;
+            }
+
+            if (type != XmlPullParser.START_TAG) {
+                Slog.e(LOG_TAG, "Unable to read restrictions file "
+                        + restrictionsFile.getBaseFile());
+                return entries;
+            }
+
+            while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
+                if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_ENTRY)) {
+                    String key = parser.getAttributeValue(null, ATTR_KEY);
+                    String multiple = parser.getAttributeValue(null, ATTR_MULTIPLE);
+                    if (multiple != null) {
+                        int count = Integer.parseInt(multiple);
+                        while (count > 0 && (type = parser.next()) != XmlPullParser.END_DOCUMENT) {
+                            if (type == XmlPullParser.START_TAG
+                                    && parser.getName().equals(TAG_VALUE)) {
+                                values.add(parser.nextText().trim());
+                                count--;
+                            }
+                        }
+                        String [] valueStrings = new String[values.size()];
+                        values.toArray(valueStrings);
+                        Slog.d(LOG_TAG, "Got RestrictionEntry " + key + "," + valueStrings);
+                        RestrictionEntry entry = new RestrictionEntry(key, valueStrings);
+                        entries.add(entry);
+                    } else {
+                        String value = parser.nextText().trim();
+                        Slog.d(LOG_TAG, "Got RestrictionEntry " + key + "," + value);
+                        RestrictionEntry entry = new RestrictionEntry(key, value);
+                        entries.add(entry);
+                    }
+                }
+            }
+
+        } catch (IOException ioe) {
+        } catch (XmlPullParserException pe) {
+        } finally {
+            if (fis != null) {
+                try {
+                    fis.close();
+                } catch (IOException e) {
+                }
+            }
+        }
+        return entries;
+    }
+
+    private void writeApplicationRestrictionsLocked(String packageName,
+            List<RestrictionEntry> entries, int userId) {
+        FileOutputStream fos = null;
+        AtomicFile restrictionsFile = new AtomicFile(
+                new File(Environment.getUserSystemDirectory(userId),
+                        RESTRICTIONS_FILE_PREFIX + packageName + ".xml"));
+        try {
+            fos = restrictionsFile.startWrite();
+            final BufferedOutputStream bos = new BufferedOutputStream(fos);
+
+            // XmlSerializer serializer = XmlUtils.serializerInstance();
+            final XmlSerializer serializer = new FastXmlSerializer();
+            serializer.setOutput(bos, "utf-8");
+            serializer.startDocument(null, true);
+            serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
+
+            serializer.startTag(null, TAG_RESTRICTIONS);
+
+            for (RestrictionEntry entry : entries) {
+                serializer.startTag(null, TAG_ENTRY);
+                serializer.attribute(null, ATTR_KEY, entry.key);
+                if (entry.getStringValue() != null || entry.getMultipleValues() == null) {
+                    String value = entry.getStringValue();
+                    serializer.text(value != null ? value : "");
+                } else {
+                    String[] values = entry.getMultipleValues();
+                    serializer.attribute(null, ATTR_MULTIPLE, Integer.toString(values.length));
+                    for (String value : values) {
+                        serializer.startTag(null, TAG_VALUE);
+                        serializer.text(value != null ? value : "");
+                        serializer.endTag(null, TAG_VALUE);
+                    }
+                }
+                serializer.endTag(null, TAG_ENTRY);
+            }
+
+            serializer.endTag(null, TAG_RESTRICTIONS);
+
+            serializer.endDocument();
+            restrictionsFile.finishWrite(fos);
+        } catch (Exception e) {
+            restrictionsFile.failWrite(fos);
+            Slog.e(LOG_TAG, "Error writing application restrictions list");
+        }
+    }
+
+    @Override
     public int getUserSerialNumber(int userHandle) {
         synchronized (mPackagesLock) {
             if (!exists(userHandle)) return -1;