Handle choosing and adding accounts for a managed profile

The user id can now be passed through the app as a fragment argument.

Bug: 15466880
Change-Id: I0e2be20551b4ec8c9226640ac74ea74115156ccd
diff --git a/src/com/android/settings/Utils.java b/src/com/android/settings/Utils.java
index 8211816..d03305d 100644
--- a/src/com/android/settings/Utils.java
+++ b/src/com/android/settings/Utils.java
@@ -16,7 +16,11 @@
 
 package com.android.settings;
 
+import static android.content.Intent.EXTRA_USER;
+
+import android.annotation.Nullable;
 import android.app.ActivityManager;
+import android.app.ActivityManagerNative;
 import android.app.AlertDialog;
 import android.app.Dialog;
 import android.app.Fragment;
@@ -61,6 +65,7 @@
 import android.view.ViewGroup;
 import android.widget.ListView;
 import android.widget.TabWidget;
+
 import com.android.settings.dashboard.DashboardCategory;
 import com.android.settings.dashboard.DashboardTile;
 
@@ -73,6 +78,7 @@
 
 public final class Utils {
     private static final String TAG = "Settings";
+
     /**
      * Set the preference's title to the matching activity's label.
      */
@@ -110,6 +116,8 @@
      */
     private static final String META_DATA_PREFERENCE_SUMMARY = "com.android.settings.summary";
 
+    private static final String SETTINGS_PACKAGE_NAME = "com.android.settings";
+
     /**
      * Finds a matching activity for a preference's intent. If a matching
      * activity is not found, it will remove the preference.
@@ -613,28 +621,100 @@
     }
 
     /**
-     * Returns the {@link UserHandle} of the profile that a settings screen should refer to.
+     * Returns the target user for a Settings activity.
      *
-     * <p> This takes into account the id of the user that triggered the settings screen.
+     * The target user can be either the current user, the user that launched this activity or
+     * the user contained as an extra in the arguments or intent extras.
+     *
+     * Note: This is secure in the sense that it only returns a target user different to the current
+     * one if the app launching this activity is the Settings app itself, running in the same user
+     * or in one that is in the same profile group, or if the user id is provided by the system.
      */
-   public static UserHandle getProfileToDisplay(IActivityManager am, IBinder activityToken,
-           Bundle arguments) {
-       int currentUser = UserHandle.getCallingUserId();
-       // Check to see if it was called from a different user
+    public static UserHandle getSecureTargetUser(IBinder activityToken,
+           UserManager um, @Nullable Bundle arguments, @Nullable Bundle intentExtras) {
+        UserHandle currentUser = new UserHandle(UserHandle.myUserId());
+        IActivityManager am = ActivityManagerNative.getDefault();
+        try {
+            String launchedFromPackage = am.getLaunchedFromPackage(activityToken);
+            boolean launchedFromSettingsApp = SETTINGS_PACKAGE_NAME.equals(launchedFromPackage);
+
+            UserHandle launchedFromUser = new UserHandle(UserHandle.getUserId(
+                    am.getLaunchedFromUid(activityToken)));
+            if (launchedFromUser != null && !launchedFromUser.equals(currentUser)) {
+                // Check it's secure
+                if (isProfileOf(um, launchedFromUser)) {
+                    return launchedFromUser;
+                }
+            }
+            UserHandle extrasUser = intentExtras != null
+                    ? (UserHandle) intentExtras.getParcelable(EXTRA_USER) : null;
+            if (extrasUser != null && !extrasUser.equals(currentUser)) {
+                // Check it's secure
+                if (launchedFromSettingsApp && isProfileOf(um, extrasUser)) {
+                    return extrasUser;
+                }
+            }
+            UserHandle argumentsUser = arguments != null
+                    ? (UserHandle) arguments.getParcelable(EXTRA_USER) : null;
+            if (argumentsUser != null && !argumentsUser.equals(currentUser)) {
+                // Check it's secure
+                if (launchedFromSettingsApp && isProfileOf(um, argumentsUser)) {
+                    return argumentsUser;
+                }
+            }
+        } catch (RemoteException e) {
+            // Should not happen
+            Log.v(TAG, "Could not talk to activity manager.", e);
+        }
+        return currentUser;
+   }
+
+    /**
+     * Returns the target user for a Settings activity.
+     *
+     * The target user can be either the current user, the user that launched this activity or
+     * the user contained as an extra in the arguments or intent extras.
+     *
+     * You should use {@link #getSecureTargetUser(IBinder, UserManager, Bundle, Bundle)} if
+     * possible.
+     *
+     * @see #getInsecureTargetUser(IBinder, Bundle, Bundle)
+     */
+   public static UserHandle getInsecureTargetUser(IBinder activityToken, @Nullable Bundle arguments,
+           @Nullable Bundle intentExtras) {
+       UserHandle currentUser = new UserHandle(UserHandle.myUserId());
+       IActivityManager am = ActivityManagerNative.getDefault();
        try {
-           int launchedFromUser = UserHandle.getUserId(am.getLaunchedFromUid(activityToken));
-           if (launchedFromUser != currentUser) {
-               // This is a forwarded intent
-               return new UserHandle(launchedFromUser);
+           UserHandle launchedFromUser = new UserHandle(UserHandle.getUserId(
+                   am.getLaunchedFromUid(activityToken)));
+           if (launchedFromUser != null && !launchedFromUser.equals(currentUser)) {
+               return launchedFromUser;
+           }
+           UserHandle extrasUser = intentExtras != null
+                   ? (UserHandle) intentExtras.getParcelable(EXTRA_USER) : null;
+           if (extrasUser != null && !extrasUser.equals(currentUser)) {
+               return extrasUser;
+           }
+           UserHandle argumentsUser = arguments != null
+                   ? (UserHandle) arguments.getParcelable(EXTRA_USER) : null;
+           if (argumentsUser != null && !argumentsUser.equals(currentUser)) {
+               return argumentsUser;
            }
        } catch (RemoteException e) {
            // Should not happen
-           Log.v(TAG, "Could not get launching user.");
+           Log.v(TAG, "Could not talk to activity manager.", e);
+           return null;
        }
-       // TODO: Check fragment arguments. See: http://b/15466880
+       return currentUser;
+   }
 
-       // Default to current profile
-       return new UserHandle(currentUser);
+   /**
+    * Returns true if the user provided is in the same profiles group as the current user.
+    */
+   private static boolean isProfileOf(UserManager um, UserHandle otherUser) {
+       if (um == null || otherUser == null) return false;
+       return (UserHandle.myUserId() == otherUser.getIdentifier())
+               || um.getUserProfiles().contains(otherUser);
    }
 
     /**
diff --git a/src/com/android/settings/accounts/AccountPreferenceBase.java b/src/com/android/settings/accounts/AccountPreferenceBase.java
index 6125adc..cfe2cb2 100644
--- a/src/com/android/settings/accounts/AccountPreferenceBase.java
+++ b/src/com/android/settings/accounts/AccountPreferenceBase.java
@@ -1,4 +1,5 @@
 /*
+
  * Copyright (C) 2008 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
@@ -21,6 +22,7 @@
 import android.accounts.AuthenticatorDescription;
 import android.app.Activity;
 import android.app.ActivityManagerNative;
+import android.app.IActivityManager;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.SyncAdapterType;
@@ -68,9 +70,10 @@
     public void onCreate(Bundle icicle) {
         super.onCreate(icicle);
         mUm = (UserManager) getSystemService(Context.USER_SERVICE);
-        mUserHandle = Utils.getProfileToDisplay(ActivityManagerNative.getDefault(),
-                getActivity().getActivityToken(), icicle);
-        mAuthenticatorHelper = new AuthenticatorHelper(getActivity(), mUserHandle, mUm, this);
+        final Activity activity = getActivity();
+        mUserHandle = Utils.getSecureTargetUser(activity.getActivityToken(), mUm, getArguments(),
+                activity.getIntent().getExtras());
+        mAuthenticatorHelper = new AuthenticatorHelper(activity, mUserHandle, mUm, this);
     }
 
     /**
@@ -172,8 +175,8 @@
                     // correct text colors. Control colors will still be wrong,
                     // but there's not much we can do about it since we can't
                     // reference local color resources.
-                    final Context targetCtx = getActivity().createPackageContext(
-                            desc.packageName, 0);
+                    final Context targetCtx = getActivity().createPackageContextAsUser(
+                            desc.packageName, 0, mUserHandle);
                     final Theme baseTheme = getResources().newTheme();
                     baseTheme.applyStyle(com.android.settings.R.style.Theme_SettingsBase, true);
                     final Context themedCtx = new ContextThemeWrapper(targetCtx, 0);
diff --git a/src/com/android/settings/accounts/AccountSettings.java b/src/com/android/settings/accounts/AccountSettings.java
index e60bed9..13878d7 100644
--- a/src/com/android/settings/accounts/AccountSettings.java
+++ b/src/com/android/settings/accounts/AccountSettings.java
@@ -16,14 +16,13 @@
 
 package com.android.settings.accounts;
 
+
 import android.accounts.Account;
 import android.accounts.AccountManager;
-import android.accounts.OnAccountsUpdateListener;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.content.pm.UserInfo;
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
 import android.os.UserHandle;
@@ -39,13 +38,17 @@
 import com.android.settings.Utils;
 
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collections;
 import java.util.Comparator;
 
+import static android.content.Intent.EXTRA_USER;
+
 /**
  * Settings screen for the account types on the device.
  * This shows all account types available for personal and work profiles.
+ *
+ * An extra {@link UserHandle} can be specified in the intent as {@link EXTRA_USER}, if the user for
+ * which the action needs to be performed is different to the one the Settings App will run in.
  */
 public class AccountSettings extends SettingsPreferenceFragment
         implements AuthenticatorHelper.OnAccountsUpdateListener,
@@ -146,7 +149,7 @@
         }
         final ProfileData profileData = new ProfileData();
         profileData.preferenceGroup = (PreferenceGroup) findPreference(categoryKey);
-        if (mUm.hasUserRestriction(UserManager.DISALLOW_MODIFY_ACCOUNTS)) {
+        if (mUm.hasUserRestriction(UserManager.DISALLOW_MODIFY_ACCOUNTS, userHandle)) {
             removePreference(addAccountKey);
         } else {
             profileData.addAccountPreference = findPreference(addAccountKey);
@@ -189,7 +192,7 @@
     private void updateAccountTypes(ProfileData profileData) {
         profileData.preferenceGroup.removeAll();
         final ArrayList<AccountPreference> preferences = getAccountTypePreferences(
-                profileData.authenticatorHelper);
+                profileData.authenticatorHelper, profileData.userHandle);
         final int count = preferences.size();
         for (int i = 0; i < count; i++) {
             profileData.preferenceGroup.addPreference(preferences.get(i));
@@ -199,7 +202,8 @@
         }
     }
 
-    private ArrayList<AccountPreference> getAccountTypePreferences(AuthenticatorHelper helper) {
+    private ArrayList<AccountPreference> getAccountTypePreferences(AuthenticatorHelper helper,
+            UserHandle userHandle) {
         final String[] accountTypes = helper.getEnabledAccountTypes();
         final ArrayList<AccountPreference> accountTypePreferences =
                 new ArrayList<AccountPreference>(accountTypes.length);
@@ -212,7 +216,7 @@
             }
 
             final Account[] accounts = AccountManager.get(getActivity())
-                    .getAccountsByType(accountType);
+                    .getAccountsByTypeAsUser(accountType, userHandle);
             final boolean skipToAccount = accounts.length == 1
                     && !helper.hasAccountPreferences(accountType);
 
@@ -220,6 +224,7 @@
                 final Bundle fragmentArguments = new Bundle();
                 fragmentArguments.putParcelable(AccountSyncSettings.ACCOUNT_KEY,
                         accounts[0]);
+                fragmentArguments.putParcelable(EXTRA_USER, userHandle);
 
                 accountTypePreferences.add(new AccountPreference(getActivity(), label,
                         AccountSyncSettings.class.getName(), fragmentArguments,
@@ -229,6 +234,7 @@
                 fragmentArguments.putString(ManageAccountsSettings.KEY_ACCOUNT_TYPE, accountType);
                 fragmentArguments.putString(ManageAccountsSettings.KEY_ACCOUNT_LABEL,
                         label.toString());
+                fragmentArguments.putParcelable(EXTRA_USER, userHandle);
 
                 accountTypePreferences.add(new AccountPreference(getActivity(), label,
                         ManageAccountsSettings.class.getName(), fragmentArguments,
@@ -254,7 +260,7 @@
             ProfileData profileData = mProfiles.valueAt(i);
             if (preference == profileData.addAccountPreference) {
                 Intent intent = new Intent(ADD_ACCOUNT_ACTION);
-                intent.putExtra(Intent.EXTRA_USER_HANDLE, profileData.userHandle);
+                intent.putExtra(EXTRA_USER, profileData.userHandle);
                 startActivity(intent);
                 return true;
             }
diff --git a/src/com/android/settings/accounts/AccountSyncSettings.java b/src/com/android/settings/accounts/AccountSyncSettings.java
index 458adca..1bde8a3 100644
--- a/src/com/android/settings/accounts/AccountSyncSettings.java
+++ b/src/com/android/settings/accounts/AccountSyncSettings.java
@@ -90,6 +90,8 @@
                         new DialogInterface.OnClickListener() {
                     @Override
                     public void onClick(DialogInterface dialog, int which) {
+                        // TODO: We need an API to remove an account from a different user.
+                        // See: http://b/15466880
                         AccountManager.get(AccountSyncSettings.this.getActivity())
                                 .removeAccount(mAccount,
                                 new AccountManagerCallback<Boolean>() {
diff --git a/src/com/android/settings/accounts/AddAccountSettings.java b/src/com/android/settings/accounts/AddAccountSettings.java
index 34b6c8d..0af0260 100644
--- a/src/com/android/settings/accounts/AddAccountSettings.java
+++ b/src/com/android/settings/accounts/AddAccountSettings.java
@@ -22,22 +22,25 @@
 import android.accounts.AuthenticatorException;
 import android.accounts.OperationCanceledException;
 import android.app.Activity;
+import android.app.ActivityManagerNative;
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
 import android.os.Bundle;
+import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.Log;
 import android.widget.Toast;
 
 import com.android.settings.R;
-import com.android.settings.Settings;
 import com.android.settings.Utils;
 
 import java.io.IOException;
 
+import static android.content.Intent.EXTRA_USER;
+
 /**
- * Entry point Actiivty for account setup. Works as follows
+ * Entry point Activity for account setup. Works as follows
  *
  * 1) When the other Activities launch this Activity, it launches {@link ChooseAccountActivity}
  *    without showing anything.
@@ -50,6 +53,9 @@
  * currently delegate the work to the other Activity. When we let this Activity do that work, users
  * would see the list of account types when leaving this Activity, since the UI is already ready
  * when returning from each account setup, which doesn't look good.
+ *
+ * An extra {@link UserHandle} can be specified in the intent as {@link EXTRA_USER}, if the user for
+ * which the action needs to be performed is different to the one the Settings App will run in.
  */
 public class AddAccountSettings extends Activity {
     /**
@@ -90,6 +96,7 @@
                     addAccountOptions.putParcelable(KEY_CALLER_IDENTITY, mPendingIntent);
                     addAccountOptions.putBoolean(EXTRA_HAS_MULTIPLE_USERS,
                             Utils.hasMultipleUsers(AddAccountSettings.this));
+                    addAccountOptions.putParcelable(EXTRA_USER, mUserHandle);
                     intent.putExtras(addAccountOptions);
                     startActivityForResult(intent, ADD_ACCOUNT_REQUEST);
                 } else {
@@ -116,6 +123,7 @@
     };
 
     private boolean mAddAccountCalled = false;
+    private UserHandle mUserHandle;
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
@@ -127,7 +135,9 @@
         }
 
         final UserManager um = (UserManager) getSystemService(Context.USER_SERVICE);
-        if (um.hasUserRestriction(UserManager.DISALLOW_MODIFY_ACCOUNTS)) {
+        mUserHandle = Utils.getSecureTargetUser(getActivityToken(), um, null /* arguments */,
+                getIntent().getExtras());
+        if (um.hasUserRestriction(UserManager.DISALLOW_MODIFY_ACCOUNTS, mUserHandle)) {
             // We aren't allowed to add an account.
             Toast.makeText(this, R.string.user_cannot_add_accounts_message, Toast.LENGTH_LONG)
                     .show();
@@ -150,6 +160,7 @@
         if (accountTypes != null) {
             intent.putExtra(AccountPreferenceBase.ACCOUNT_TYPES_FILTER_KEY, accountTypes);
         }
+        intent.putExtra(EXTRA_USER, mUserHandle);
         startActivityForResult(intent, CHOOSE_ACCOUNT_REQUEST);
     }
 
@@ -188,6 +199,14 @@
         mPendingIntent = PendingIntent.getBroadcast(this, 0, new Intent(), 0);
         addAccountOptions.putParcelable(KEY_CALLER_IDENTITY, mPendingIntent);
         addAccountOptions.putBoolean(EXTRA_HAS_MULTIPLE_USERS, Utils.hasMultipleUsers(this));
+        // TODO: We need an API to add an account to a different user. See: http://b/15466880
+        int userId = mUserHandle.getIdentifier();
+        int callingUserId = UserHandle.getCallingUserId();
+        if (userId != callingUserId) {
+            Log.w(TAG, "Cannot add an account for user " + userId + " from " + callingUserId + ".");
+            finish();
+            return;
+        }
         AccountManager.get(this).addAccount(
                 accountType,
                 null, /* authTokenType */
diff --git a/src/com/android/settings/accounts/ChooseAccountActivity.java b/src/com/android/settings/accounts/ChooseAccountActivity.java
index 631fe47..7c0dbdb 100644
--- a/src/com/android/settings/accounts/ChooseAccountActivity.java
+++ b/src/com/android/settings/accounts/ChooseAccountActivity.java
@@ -18,6 +18,7 @@
 
 import android.accounts.AccountManager;
 import android.accounts.AuthenticatorDescription;
+import android.app.ActivityManagerNative;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
@@ -26,13 +27,18 @@
 import android.content.res.Resources;
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
+import android.os.UserHandle;
+import android.os.UserManager;
 import android.preference.Preference;
 import android.preference.PreferenceActivity;
 import android.preference.PreferenceGroup;
 import android.preference.PreferenceScreen;
 import android.util.Log;
+
 import com.android.internal.util.CharSequences;
 import com.android.settings.R;
+import com.android.settings.Utils;
+
 import com.google.android.collect.Maps;
 
 import java.util.ArrayList;
@@ -41,8 +47,13 @@
 import java.util.HashSet;
 import java.util.Map;
 
+import static android.content.Intent.EXTRA_USER;
+
 /**
  * Activity asking a user to select an account to be set up.
+ *
+ * An extra {@link UserHandle} can be specified in the intent as {@link EXTRA_USER}, if the user for
+ * which the action needs to be performed is different to the one the Settings App will run in.
  */
 public class ChooseAccountActivity extends PreferenceActivity {
 
@@ -55,7 +66,10 @@
     private HashMap<String, ArrayList<String>> mAccountTypeToAuthorities = null;
     private Map<String, AuthenticatorDescription> mTypeToAuthDescription
             = new HashMap<String, AuthenticatorDescription>();
-    
+    // The UserHandle of the user we are choosing an account for
+    private UserHandle mUserHandle;
+    private UserManager mUm;
+
     private static class ProviderEntry implements Comparable<ProviderEntry> {
         private final CharSequence name;
         private final String type;
@@ -92,6 +106,9 @@
             }
         }
         mAddAccountGroup = getPreferenceScreen();
+        mUm = UserManager.get(this);
+        mUserHandle = Utils.getSecureTargetUser(getActivityToken(), mUm, null /* arguments */,
+                getIntent().getExtras());
         updateAuthDescriptions();
     }
 
@@ -100,7 +117,8 @@
      * and update any UI that depends on AuthenticatorDescriptions in onAuthDescriptionsUpdated().
      */
     private void updateAuthDescriptions() {
-        mAuthDescs = AccountManager.get(this).getAuthenticatorTypes();
+        mAuthDescs = AccountManager.get(this).getAuthenticatorTypesAsUser(
+                mUserHandle.getIdentifier());
         for (int i = 0; i < mAuthDescs.length; i++) {
             mTypeToAuthDescription.put(mAuthDescs[i].type, mAuthDescs[i]);
         }
@@ -168,7 +186,8 @@
     public ArrayList<String> getAuthoritiesForAccountType(String type) {
         if (mAccountTypeToAuthorities == null) {
             mAccountTypeToAuthorities = Maps.newHashMap();
-            SyncAdapterType[] syncAdapters = ContentResolver.getSyncAdapterTypes();
+            SyncAdapterType[] syncAdapters = ContentResolver.getSyncAdapterTypesAsUser(
+                    mUserHandle.getIdentifier());
             for (int i = 0, n = syncAdapters.length; i < n; i++) {
                 final SyncAdapterType sa = syncAdapters[i];
                 ArrayList<String> authorities = mAccountTypeToAuthorities.get(sa.accountType);
@@ -196,8 +215,9 @@
         if (mTypeToAuthDescription.containsKey(accountType)) {
             try {
                 AuthenticatorDescription desc = mTypeToAuthDescription.get(accountType);
-                Context authContext = createPackageContext(desc.packageName, 0);
-                icon = authContext.getResources().getDrawable(desc.iconId);
+                Context authContext = createPackageContextAsUser(desc.packageName, 0, mUserHandle);
+                icon = mUm.getBadgedDrawableForUser(
+                        authContext.getResources().getDrawable(desc.iconId), mUserHandle);
             } catch (PackageManager.NameNotFoundException e) {
                 // TODO: place holder icon for missing account icons?
                 Log.w(TAG, "No icon name for account type " + accountType);
@@ -219,7 +239,7 @@
         if (mTypeToAuthDescription.containsKey(accountType)) {
             try {
                 AuthenticatorDescription desc = mTypeToAuthDescription.get(accountType);
-                Context authContext = createPackageContext(desc.packageName, 0);
+                Context authContext = createPackageContextAsUser(desc.packageName, 0, mUserHandle);
                 label = authContext.getResources().getText(desc.labelId);
             } catch (PackageManager.NameNotFoundException e) {
                 Log.w(TAG, "No label name for account type " + accountType);
@@ -245,6 +265,7 @@
     private void finishWithAccountType(String accountType) {
         Intent intent = new Intent();
         intent.putExtra(AddAccountSettings.EXTRA_SELECTED_ACCOUNT, accountType);
+        intent.putExtra(EXTRA_USER, mUserHandle);
         setResult(RESULT_OK, intent);
         finish();
     }
diff --git a/src/com/android/settings/bluetooth/MessageAccessSettings.java b/src/com/android/settings/bluetooth/MessageAccessSettings.java
index bef59c8..913357c 100644
--- a/src/com/android/settings/bluetooth/MessageAccessSettings.java
+++ b/src/com/android/settings/bluetooth/MessageAccessSettings.java
@@ -54,8 +54,9 @@
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        mUserHandle = Utils.getProfileToDisplay(ActivityManagerNative.getDefault(),
-                getActivity().getActivityToken(), savedInstanceState);
+
+        // TODO: Define behavior for managed profile. See: http://b/16287773
+        mUserHandle = new UserHandle(UserHandle.myUserId());
 
         addPreferencesFromResource(R.xml.bluetooth_message_access);
     }