Merge "Add support for per-package policy for sending premium SMS." into jb-mr1-dev
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 24fc8b0..b1dd657 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -59,6 +59,7 @@
     <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
     <uses-permission android:name="android.permission.COPY_PROTECTED_DATA" />
     <uses-permission android:name="android.permission.MANAGE_USERS" />
+    <uses-permission android:name="android.permission.READ_PROFILE" />
 
     <application android:label="@string/settings_label"
             android:icon="@mipmap/ic_launcher_settings"
@@ -1489,5 +1490,12 @@
             </intent-filter>
         </receiver>
 
+        <!-- Watch for ContactsContract.Profile changes and update the user's photo.  -->
+        <receiver android:name=".users.ProfileUpdateReceiver">
+            <intent-filter>
+                <action android:name="android.provider.Contacts.PROFILE_CHANGED" />
+            </intent-filter>
+        </receiver>
+
     </application>
 </manifest>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 4df184e..cc2bb44 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -794,6 +794,12 @@
     <!--  Title for PreferenceScreen to launch picker for security method when there is none [CHAR LIMIT=22] -->
     <string name="unlock_set_unlock_launch_picker_title">Screen lock</string>
 
+    <!--  Title for PreferenceScreen to launch picker for a user-selected widget that will live on lock screen [CHAR LIMIT=22] -->
+    <string name="choose_user_selected_lockscreen_widget_picker_title">Widget</string>
+
+    <!--  String to display if there is no user-selected widget on lock screen [CHAR LIMIT=22] -->
+    <string name="widget_none">None</string>
+
     <!--  Title for PreferenceScreen to change security method: None/Pattern/PIN/Password [CHAR LIMIT=22] -->
     <string name="unlock_set_unlock_launch_picker_change_title">Change lock screen</string>
 
@@ -2342,6 +2348,8 @@
     <string name="lockpattern_too_many_failed_confirmation_attempts_header">Too many incorrect attempts!</string>
     <!-- Security & location settings screen, change unlock pattern screen countdown hint on bottom of screen after too many incorrect attempts -->
     <string name="lockpattern_too_many_failed_confirmation_attempts_footer">Try again in <xliff:g id="number">%d</xliff:g> seconds.</string>
+    <!-- Displayed when user launches a widget configuration activity that was uninstalled -->
+    <string name="activity_not_found">Application is not installed on your phone.</string>
 
     <!-- ChooseLockPatternTutorial --> <skip />
     <!-- ChooseLockPatternTutorial, button labels: This is to cancel the tutorial -->
@@ -2387,7 +2395,7 @@
  damage to your phone or loss of data that may result from using
  these apps.</string>
     <!-- Applications settings screen, setting check box title. If checked, the system will send package verification requests to package verifiers on the device who will ultimately allow or reject the installation of applications. [CHAR LIMIT=30] -->
-    <string name="verify_applications">App check</string>
+    <string name="verify_applications">Verify apps</string>
     <!-- Applications settings screen, setting check box summary. This is the summary for "App Check" checkbox. [CHAR LIMIT=none] -->
     <string name="verify_applications_summary">Disallow or warn before installation of apps that may cause harm</string>
     <!-- Applications settings screen, setting check box title. If checked, applications show more settings options. -->
@@ -2993,18 +3001,17 @@
     <string name="accessibility_screen_magnification_title">Screen magnification</string>
     <!-- Summary for the accessibility preference screen to enable screen magnification. [CHAR LIMIT=35] -->
     <string name="accessibility_screen_magnification_summary">
-        When screen magnification is on, you can:\n
-        \n
-        Temporary zoom-in: Triple-tap &amp; hold.\n
-        Toggle permanent zoom state: Triple-tap &amp; release.\n
-        \n
-        Adjust zoomed area: Triple-tap &amp; hold to zoom, then drag your finger across the screen.\n
-        Pan when zoomed-in: Drag two or more fingers across the screen.\n
-        \n
-        Adjust zoom level when zoomed-out: Triple-tap &amp; hold to zoom, then drag one or more fingers.\n
-        Adjust zoom level when zoomed-in: Pinch with two or more fingers.\n
-    </string>
+        When screen magnification is on, you can\:\n\n
 
+        Zoom: Triple-tap &amp; hold.\n
+        Zoom &amp; pan: Triple-tap &amp; hold, then drag your finger.\n
+        Toggle zoom in or out: Triple tap &amp; release.\n\n
+
+        While you\'re zoomed in, you can:\n
+
+        Pan: Drag two or more fingers across the screen.\n
+        Adjust zoom level: Pinch or expand using two or more fingers.
+    </string>
     <!-- Title for the accessibility preference to enable large text. [CHAR LIMIT=35] -->
     <string name="accessibility_toggle_large_text_preference_title">Large text</string>
     <!-- Title for the accessibility preference to enable screen magnification. [CHAR LIMIT=35] -->
diff --git a/res/xml/security_settings_chooser.xml b/res/xml/security_settings_chooser.xml
index 60d3a9f..98422ba 100644
--- a/res/xml/security_settings_chooser.xml
+++ b/res/xml/security_settings_chooser.xml
@@ -27,6 +27,12 @@
             android:persistent="false"/>
 
         <PreferenceScreen
+           android:key="choose_user_selected_lockscreen_widget"
+           android:title="@string/choose_user_selected_lockscreen_widget_picker_title"
+           android:summary=""
+           android:persistent="false"/>
+
+        <PreferenceScreen
             android:fragment="com.android.settings.OwnerInfoSettings"
             android:key="owner_info_settings"
             android:title="@string/owner_info_settings_title"
diff --git a/res/xml/security_settings_lockscreen.xml b/res/xml/security_settings_lockscreen.xml
index fbdee60..06ec934 100644
--- a/res/xml/security_settings_lockscreen.xml
+++ b/res/xml/security_settings_lockscreen.xml
@@ -25,7 +25,11 @@
             android:title="@string/unlock_set_unlock_launch_picker_title"
             android:summary="@string/unlock_set_unlock_mode_off"
             android:persistent="false"/>
-
+        <PreferenceScreen
+            android:fragment="com.android.settings.OwnerInfoSettings"
+            android:key="owner_info_settings"
+            android:title="@string/owner_info_settings_title"
+            android:summary="@string/owner_info_settings_summary"/>
     </PreferenceCategory>
 
 </PreferenceScreen>
diff --git a/res/xml/security_settings_password.xml b/res/xml/security_settings_password.xml
index 0e9c71d..4fb2606 100644
--- a/res/xml/security_settings_password.xml
+++ b/res/xml/security_settings_password.xml
@@ -26,6 +26,12 @@
             android:summary="@string/unlock_set_unlock_mode_password"
             android:persistent="false"/>
 
+        <PreferenceScreen
+           android:key="choose_user_selected_lockscreen_widget"
+           android:title="@string/choose_user_selected_lockscreen_widget_picker_title"
+           android:summary=""
+           android:persistent="false"/>
+
         <ListPreference
             android:key="lock_after_timeout"
             android:title="@string/lock_after_timeout"
diff --git a/res/xml/security_settings_pattern.xml b/res/xml/security_settings_pattern.xml
index b91b2b6..ebc9c41 100644
--- a/res/xml/security_settings_pattern.xml
+++ b/res/xml/security_settings_pattern.xml
@@ -25,6 +25,11 @@
             android:title="@string/unlock_set_unlock_launch_picker_title"
             android:summary="@string/unlock_set_unlock_mode_pattern"
             android:persistent="false"/>
+        <PreferenceScreen
+           android:key="choose_user_selected_lockscreen_widget"
+           android:title="@string/choose_user_selected_lockscreen_widget_picker_title"
+           android:summary=""
+           android:persistent="false"/>
 
         <CheckBoxPreference
             android:key="visiblepattern"
diff --git a/res/xml/security_settings_pin.xml b/res/xml/security_settings_pin.xml
index 4562a9a..b908140 100644
--- a/res/xml/security_settings_pin.xml
+++ b/res/xml/security_settings_pin.xml
@@ -25,7 +25,11 @@
             android:title="@string/unlock_set_unlock_launch_picker_title"
             android:summary="@string/unlock_set_unlock_mode_pin"
             android:persistent="false"/>
-
+        <PreferenceScreen
+           android:key="choose_user_selected_lockscreen_widget"
+           android:title="@string/choose_user_selected_lockscreen_widget_picker_title"
+           android:summary=""
+           android:persistent="false"/>
         <ListPreference
             android:key="lock_after_timeout"
             android:title="@string/lock_after_timeout"
diff --git a/src/com/android/settings/AppWidgetPickActivity.java b/src/com/android/settings/AppWidgetPickActivity.java
index 176ac80..f573edf 100644
--- a/src/com/android/settings/AppWidgetPickActivity.java
+++ b/src/com/android/settings/AppWidgetPickActivity.java
@@ -74,7 +74,7 @@
             finish();
         }
     }
-    
+
     /**
      * Create list entries for any custom widgets requested through
      * {@link AppWidgetManager#EXTRA_CUSTOM_INFO}.
@@ -129,7 +129,7 @@
         }
 
         if (LOGD) Log.d(TAG, "Using " + customInfo.size() + " custom items");
-        putAppWidgetItems(customInfo, customExtras, items);
+        putAppWidgetItems(customInfo, customExtras, items, 0, 0, true);
     }
     
     /**
@@ -166,12 +166,13 @@
      * inserting extras if provided.
      */
     void putAppWidgetItems(List<AppWidgetProviderInfo> appWidgets,
-            List<Bundle> customExtras, List<PickAdapter.Item> items) {
+            List<Bundle> customExtras, List<PickAdapter.Item> items, int categoryFilter,
+            int featuresFilter, boolean ignoreFilters) {
         if (appWidgets == null) return;
         final int size = appWidgets.size();
         for (int i = 0; i < size; i++) {
             AppWidgetProviderInfo info = appWidgets.get(i);
-            
+
             CharSequence label = info.label;
             Drawable icon = null;
 
@@ -184,18 +185,28 @@
             }
             
             PickAdapter.Item item = new PickAdapter.Item(this, label, icon);
-            
+
             item.packageName = info.provider.getPackageName();
             item.className = info.provider.getClassName();
             
             if (customExtras != null) {
                 item.extras = customExtras.get(i);
             }
-            
+
+            // We remove any widgets whose category isn't included in the filter
+            if (!ignoreFilters && (info.widgetCategory & categoryFilter) == 0) {
+                continue;
+            }
+
+            // We remove any widgets who don't have all the features in the features filter
+            if (!ignoreFilters && (info.widgetFeatures & featuresFilter) != featuresFilter) {
+                continue;
+            }
+
             items.add(item);
         }
     }
-    
+
     /**
      * Build and return list of items to be shown in dialog. This will mix both
      * installed {@link AppWidgetProviderInfo} and those provided through
@@ -203,28 +214,50 @@
      */
     @Override
     protected List<PickAdapter.Item> getItems() {
-        List<PickAdapter.Item> items = new ArrayList<PickAdapter.Item>();
-        
-        putInstalledAppWidgets(items);
-        putCustomAppWidgets(items);
-        
-        // Sort all items together by label
-        Collections.sort(items, new Comparator<PickAdapter.Item>() {
-                Collator mCollator = Collator.getInstance();
-                public int compare(PickAdapter.Item lhs, PickAdapter.Item rhs) {
-                    return mCollator.compare(lhs.label, rhs.label);
-                }
-            });
+        final Intent intent = getIntent();
+        boolean sortCustomAppWidgets =
+                intent.getBooleanExtra(AppWidgetManager.EXTRA_CUSTOM_SORT, true);
 
+        List<PickAdapter.Item> items = new ArrayList<PickAdapter.Item>();
+
+        int categoryFilter = AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN;
+        if (intent.getExtras().containsKey(AppWidgetManager.EXTRA_CATEGORY_FILTER)) {
+            categoryFilter = intent.getExtras().getInt(AppWidgetManager.EXTRA_CATEGORY_FILTER);
+        }
+
+        // If not specified, we don't filter on any specific
+        int featuresFilter = AppWidgetProviderInfo.WIDGET_FEATURES_NONE;
+        if (intent.getExtras().containsKey(AppWidgetManager.EXTRA_FEATURES_FILTER)) {
+            featuresFilter = intent.getExtras().getInt(AppWidgetManager.EXTRA_CATEGORY_FILTER);
+        }
+
+        putInstalledAppWidgets(items, categoryFilter, featuresFilter);
+
+        // Sort all items together by label
+        if (sortCustomAppWidgets) {
+            putCustomAppWidgets(items);
+        }
+        Collections.sort(items, new Comparator<PickAdapter.Item>() {
+            Collator mCollator = Collator.getInstance();
+
+            public int compare(PickAdapter.Item lhs, PickAdapter.Item rhs) {
+                return mCollator.compare(lhs.label, rhs.label);
+            }
+        });
+        if (!sortCustomAppWidgets) {
+            List<PickAdapter.Item> customItems = new ArrayList<PickAdapter.Item>();
+            putCustomAppWidgets(customItems);
+            items.addAll(0, customItems);
+        }
         return items;
     }
 
     /**
      * Create list entries for installed {@link AppWidgetProviderInfo} widgets.
      */
-    void putInstalledAppWidgets(List<PickAdapter.Item> items) {
+    void putInstalledAppWidgets(List<PickAdapter.Item> items, int categoryFilter, int featuresFilter) {
         List<AppWidgetProviderInfo> installed = mAppWidgetManager.getInstalledProviders();
-        putAppWidgetItems(installed, null, items);
+        putAppWidgetItems(installed, null, items, categoryFilter, featuresFilter, false);
     }
 
     /**
diff --git a/src/com/android/settings/CredentialStorage.java b/src/com/android/settings/CredentialStorage.java
index 07b1776..c737c7d 100644
--- a/src/com/android/settings/CredentialStorage.java
+++ b/src/com/android/settings/CredentialStorage.java
@@ -25,6 +25,7 @@
 import android.os.AsyncTask;
 import android.os.Bundle;
 import android.os.RemoteException;
+import android.os.UserHandle;
 import android.security.Credentials;
 import android.security.KeyChain.KeyChainConnection;
 import android.security.KeyChain;
@@ -104,17 +105,24 @@
      */
     private int mRetriesRemaining = -1;
 
-    @Override protected void onResume() {
+    @Override
+    protected void onResume() {
         super.onResume();
 
+        if (UserHandle.myUserId() != UserHandle.USER_OWNER) {
+            Log.i(TAG, "Cannot install to CredentialStorage as non-primary user");
+            finish();
+            return;
+        }
+
         Intent intent = getIntent();
         String action = intent.getAction();
 
         if (ACTION_RESET.equals(action)) {
             new ResetDialog();
         } else {
-            if (ACTION_INSTALL.equals(action) &&
-                    "com.android.certinstaller".equals(getCallingPackage())) {
+            if (ACTION_INSTALL.equals(action)
+                    && "com.android.certinstaller".equals(getCallingPackage())) {
                 mInstallBundle = intent.getExtras();
             }
             // ACTION_UNLOCK also handled here in addition to ACTION_INSTALL
diff --git a/src/com/android/settings/DataUsageSummary.java b/src/com/android/settings/DataUsageSummary.java
index 9837193..7c1832f6 100644
--- a/src/com/android/settings/DataUsageSummary.java
+++ b/src/com/android/settings/DataUsageSummary.java
@@ -48,6 +48,7 @@
 import static com.android.settings.Utils.prepareCustomPreferencesList;
 
 import android.animation.LayoutTransition;
+import android.app.ActivityManager;
 import android.app.AlertDialog;
 import android.app.Dialog;
 import android.app.DialogFragment;
@@ -127,7 +128,6 @@
 import android.widget.TabWidget;
 import android.widget.TextView;
 
-import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneConstants;
 import com.android.settings.drawable.InsetBoundsDrawable;
 import com.android.settings.net.ChartData;
@@ -447,20 +447,24 @@
     public void onPrepareOptionsMenu(Menu menu) {
         final Context context = getActivity();
         final boolean appDetailMode = isAppDetailMode();
+        final boolean isOwner = ActivityManager.getCurrentUser() == UserHandle.USER_OWNER;
 
         mMenuDataRoaming = menu.findItem(R.id.data_usage_menu_roaming);
         mMenuDataRoaming.setVisible(hasReadyMobileRadio(context) && !appDetailMode);
         mMenuDataRoaming.setChecked(getDataRoaming());
+        mMenuDataRoaming.setVisible(isOwner);
 
         mMenuRestrictBackground = menu.findItem(R.id.data_usage_menu_restrict_background);
         mMenuRestrictBackground.setVisible(hasReadyMobileRadio(context) && !appDetailMode);
         mMenuRestrictBackground.setChecked(mPolicyManager.getRestrictBackground());
+        mMenuRestrictBackground.setVisible(isOwner);
 
         mMenuAutoSync = menu.findItem(R.id.data_usage_menu_auto_sync);
         mMenuAutoSync.setChecked(ContentResolver.getMasterSyncAutomatically());
+        mMenuAutoSync.setVisible(isOwner && !appDetailMode);
 
         final MenuItem split4g = menu.findItem(R.id.data_usage_menu_split_4g);
-        split4g.setVisible(hasReadyMobile4gRadio(context) && !appDetailMode);
+        split4g.setVisible(hasReadyMobile4gRadio(context) && isOwner && !appDetailMode);
         split4g.setChecked(isMobilePolicySplit());
 
         final MenuItem showWifi = menu.findItem(R.id.data_usage_menu_show_wifi);
@@ -481,7 +485,7 @@
 
         final MenuItem metered = menu.findItem(R.id.data_usage_menu_metered);
         if (hasReadyMobileRadio(context) || hasWifiRadio(context)) {
-            metered.setVisible(!appDetailMode);
+            metered.setVisible(isOwner && !appDetailMode);
         } else {
             metered.setVisible(false);
         }
@@ -681,6 +685,7 @@
 
         final Context context = getActivity();
         final String currentTab = mTabHost.getCurrentTabTag();
+        final boolean isOwner = ActivityManager.getCurrentUser() == UserHandle.USER_OWNER;
 
         if (currentTab == null) {
             Log.w(TAG, "no tab selected; hiding body");
@@ -695,7 +700,7 @@
 
         if (LOGD) Log.d(TAG, "updateBody() with currentTab=" + currentTab);
 
-        mDataEnabledView.setVisibility(View.VISIBLE);
+        mDataEnabledView.setVisibility(isOwner ? View.VISIBLE : View.GONE);
 
         // TODO: remove mobile tabs when SIM isn't ready
         final TelephonyManager tele = TelephonyManager.from(context);
@@ -774,8 +779,8 @@
         mChart.bindNetworkPolicy(null);
 
         // show icon and all labels appearing under this app
-        final int appId = mCurrentApp.appId;
-        final UidDetail detail = mUidDetailProvider.getUidDetail(appId, true);
+        final int uid = mCurrentApp.key;
+        final UidDetail detail = mUidDetailProvider.getUidDetail(uid, true);
         mAppIcon.setImageDrawable(detail.icon);
 
         mAppTitles.removeAllViews();
@@ -788,14 +793,21 @@
         }
 
         // enable settings button when package provides it
-        // TODO: target torwards entire UID instead of just first package
-        final String[] packageNames = pm.getPackagesForUid(appId);
+        final String[] packageNames = pm.getPackagesForUid(uid);
         if (packageNames != null && packageNames.length > 0) {
             mAppSettingsIntent = new Intent(Intent.ACTION_MANAGE_NETWORK_USAGE);
-            mAppSettingsIntent.setPackage(packageNames[0]);
             mAppSettingsIntent.addCategory(Intent.CATEGORY_DEFAULT);
 
-            final boolean matchFound = pm.resolveActivity(mAppSettingsIntent, 0) != null;
+            // Search for match across all packages
+            boolean matchFound = false;
+            for (String packageName : packageNames) {
+                mAppSettingsIntent.setPackage(packageName);
+                if (pm.resolveActivity(mAppSettingsIntent, 0) != null) {
+                    matchFound = true;
+                    break;
+                }
+            }
+
             mAppSettings.setEnabled(matchFound);
             mAppSettings.setVisibility(View.VISIBLE);
 
@@ -806,7 +818,7 @@
 
         updateDetailData();
 
-        if (UserHandle.isApp(appId) && !mPolicyManager.getRestrictBackground()
+        if (UserHandle.isApp(uid) && !mPolicyManager.getRestrictBackground()
                 && isBandwidthControlEnabled() && hasReadyMobileRadio(context)) {
             setPreferenceTitle(mAppRestrictView, R.string.data_usage_app_restrict_background);
             setPreferenceSummary(mAppRestrictView,
@@ -855,7 +867,8 @@
     }
 
     private boolean isNetworkPolicyModifiable(NetworkPolicy policy) {
-        return policy != null && isBandwidthControlEnabled() && mDataEnabled.isChecked();
+        return policy != null && isBandwidthControlEnabled() && mDataEnabled.isChecked()
+                && ActivityManager.getCurrentUser() == UserHandle.USER_OWNER;
     }
 
     private boolean isBandwidthControlEnabled() {
@@ -886,16 +899,16 @@
     }
 
     private boolean getAppRestrictBackground() {
-        final int appId = mCurrentApp.appId;
-        final int uidPolicy = mPolicyManager.getAppPolicy(appId);
+        final int uid = mCurrentApp.key;
+        final int uidPolicy = mPolicyManager.getUidPolicy(uid);
         return (uidPolicy & POLICY_REJECT_METERED_BACKGROUND) != 0;
     }
 
     private void setAppRestrictBackground(boolean restrictBackground) {
         if (LOGD) Log.d(TAG, "setAppRestrictBackground()");
-        final int appId = mCurrentApp.appId;
-        mPolicyManager.setAppPolicy(appId,
-                restrictBackground ? POLICY_REJECT_METERED_BACKGROUND : POLICY_NONE);
+        final int uid = mCurrentApp.key;
+        mPolicyManager.setUidPolicy(
+                uid, restrictBackground ? POLICY_REJECT_METERED_BACKGROUND : POLICY_NONE);
         mAppRestrict.setChecked(restrictBackground);
     }
 
@@ -1080,7 +1093,7 @@
             // TODO: sigh, remove this hack once we understand 6450986
             if (mUidDetailProvider == null || app == null) return;
 
-            final UidDetail detail = mUidDetailProvider.getUidDetail(app.appId, true);
+            final UidDetail detail = mUidDetailProvider.getUidDetail(app.key, true);
             AppDetailsFragment.show(DataUsageSummary.this, app, detail.label);
         }
     };
@@ -1224,9 +1237,9 @@
 
         @Override
         public void onLoadFinished(Loader<NetworkStats> loader, NetworkStats data) {
-            final int[] restrictedAppIds = mPolicyManager.getAppsWithPolicy(
+            final int[] restrictedUids = mPolicyManager.getUidsWithPolicy(
                     POLICY_REJECT_METERED_BACKGROUND);
-            mAdapter.bindStats(data, restrictedAppIds);
+            mAdapter.bindStats(data, restrictedUids);
             updateEmptyVisible();
         }
 
@@ -1408,17 +1421,17 @@
     }
 
     public static class AppItem implements Comparable<AppItem>, Parcelable {
-        public final int appId;
+        public final int key;
         public boolean restricted;
         public SparseBooleanArray uids = new SparseBooleanArray();
         public long total;
 
-        public AppItem(int appId) {
-            this.appId = appId;
+        public AppItem(int key) {
+            this.key = key;
         }
 
         public AppItem(Parcel parcel) {
-            appId = parcel.readInt();
+            key = parcel.readInt();
             uids = parcel.readSparseBooleanArray();
             total = parcel.readLong();
         }
@@ -1429,7 +1442,7 @@
 
         @Override
         public void writeToParcel(Parcel dest, int flags) {
-            dest.writeInt(appId);
+            dest.writeInt(key);
             dest.writeSparseBooleanArray(uids);
             dest.writeLong(total);
         }
@@ -1475,49 +1488,56 @@
         /**
          * Bind the given {@link NetworkStats}, or {@code null} to clear list.
          */
-        public void bindStats(NetworkStats stats, int[] restrictedAppIds) {
+        public void bindStats(NetworkStats stats, int[] restrictedUids) {
             mItems.clear();
 
-            final AppItem systemItem = new AppItem(android.os.Process.SYSTEM_UID);
-            final SparseArray<AppItem> knownUids = new SparseArray<AppItem>();
+            final int currentUserId = ActivityManager.getCurrentUser();
+            final SparseArray<AppItem> knownItems = new SparseArray<AppItem>();
 
             NetworkStats.Entry entry = null;
             final int size = stats != null ? stats.size() : 0;
             for (int i = 0; i < size; i++) {
                 entry = stats.getValues(i, entry);
 
-                final boolean isApp = UserHandle.isApp(entry.uid);
-                final int appId = isApp ? UserHandle.getAppId(entry.uid) : entry.uid;
-                if (isApp || appId == UID_REMOVED || appId == UID_TETHERING) {
-                    AppItem item = knownUids.get(appId);
-                    if (item == null) {
-                        item = new AppItem(appId);
-                        knownUids.put(appId, item);
-                        mItems.add(item);
+                // Decide how to collapse items together
+                final int uid = entry.uid;
+                final int collapseKey;
+                if (UserHandle.isApp(uid)) {
+                    if (UserHandle.getUserId(uid) == currentUserId) {
+                        collapseKey = uid;
+                    } else {
+                        collapseKey = UidDetailProvider.buildKeyForUser(UserHandle.getUserId(uid));
                     }
-
-                    item.total += entry.rxBytes + entry.txBytes;
-                    item.addUid(entry.uid);
+                } else if (uid == UID_REMOVED || uid == UID_TETHERING) {
+                    collapseKey = uid;
                 } else {
-                    systemItem.total += entry.rxBytes + entry.txBytes;
-                    systemItem.addUid(entry.uid);
+                    collapseKey = android.os.Process.SYSTEM_UID;
                 }
+
+                AppItem item = knownItems.get(collapseKey);
+                if (item == null) {
+                    item = new AppItem(collapseKey);
+                    mItems.add(item);
+                    knownItems.put(item.key, item);
+                }
+                item.addUid(uid);
+                item.total += entry.rxBytes + entry.txBytes;
             }
 
-            for (int appId : restrictedAppIds) {
-                AppItem item = knownUids.get(appId);
+            for (int uid : restrictedUids) {
+                // Only splice in restricted state for current user
+                if (UserHandle.getUserId(uid) != currentUserId) continue;
+
+                AppItem item = knownItems.get(uid);
                 if (item == null) {
-                    item = new AppItem(appId);
+                    item = new AppItem(uid);
                     item.total = -1;
                     mItems.add(item);
+                    knownItems.put(item.key, item);
                 }
                 item.restricted = true;
             }
 
-            if (systemItem.total > 0) {
-                mItems.add(systemItem);
-            }
-
             Collections.sort(mItems);
             mLargest = (mItems.size() > 0) ? mItems.get(0).total : 0;
             notifyDataSetChanged();
@@ -1535,7 +1555,7 @@
 
         @Override
         public long getItemId(int position) {
-            return mItems.get(position).appId;
+            return mItems.get(position).key;
         }
 
         @Override
@@ -2126,7 +2146,7 @@
                 existing.cancel(false);
             }
 
-            final UidDetail cachedDetail = provider.getUidDetail(item.appId, false);
+            final UidDetail cachedDetail = provider.getUidDetail(item.key, false);
             if (cachedDetail != null) {
                 bindView(cachedDetail, target);
             } else {
@@ -2155,7 +2175,7 @@
 
         @Override
         protected UidDetail doInBackground(Void... params) {
-            return mProvider.getUidDetail(mItem.appId, true);
+            return mProvider.getUidDetail(mItem.key, true);
         }
 
         @Override
diff --git a/src/com/android/settings/SecuritySettings.java b/src/com/android/settings/SecuritySettings.java
index e309c37..ce59fdd 100644
--- a/src/com/android/settings/SecuritySettings.java
+++ b/src/com/android/settings/SecuritySettings.java
@@ -21,13 +21,18 @@
 
 import android.app.Activity;
 import android.app.AlertDialog;
+import android.app.KeyguardManager;
 import android.app.admin.DevicePolicyManager;
+import android.appwidget.AppWidgetHost;
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.ActivityNotFoundException;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
-import android.net.Uri;
 import android.os.Bundle;
 import android.os.UserHandle;
 import android.os.Vibrator;
@@ -41,8 +46,8 @@
 import android.security.KeyStore;
 import android.telephony.TelephonyManager;
 import android.util.Log;
+import android.widget.Toast;
 
-import com.android.internal.telephony.Phone;
 import com.android.internal.widget.LockPatternUtils;
 
 import java.util.ArrayList;
@@ -53,9 +58,12 @@
  */
 public class SecuritySettings extends SettingsPreferenceFragment
         implements OnPreferenceChangeListener, DialogInterface.OnClickListener {
+    static final String TAG = "SecuritySettings";
 
     // Lock Settings
     private static final String KEY_UNLOCK_SET_OR_CHANGE = "unlock_set_or_change";
+    private static final String KEY_CHOOSE_USER_SELECTED_LOCKSCREEN_WIDGET =
+            "choose_user_selected_lockscreen_widget";
     private static final String KEY_BIOMETRIC_WEAK_IMPROVE_MATCHING =
             "biometric_weak_improve_matching";
     private static final String KEY_BIOMETRIC_WEAK_LIVELINESS = "biometric_weak_liveliness";
@@ -64,9 +72,12 @@
     private static final String KEY_TACTILE_FEEDBACK_ENABLED = "unlock_tactile_feedback";
     private static final String KEY_SECURITY_CATEGORY = "security_category";
     private static final String KEY_LOCK_AFTER_TIMEOUT = "lock_after_timeout";
+    private static final String EXTRA_NO_WIDGET = "com.android.settings.NO_WIDGET";
     private static final int SET_OR_CHANGE_LOCK_METHOD_REQUEST = 123;
     private static final int CONFIRM_EXISTING_FOR_BIOMETRIC_WEAK_IMPROVE_REQUEST = 124;
     private static final int CONFIRM_EXISTING_FOR_BIOMETRIC_WEAK_LIVELINESS_OFF = 125;
+    private static final int REQUEST_PICK_APPWIDGET = 126;
+    private static final int REQUEST_CREATE_APPWIDGET = 127;
 
     // Misc Settings
     private static final String KEY_SIM_LOCK = "sim_lock";
@@ -80,6 +91,7 @@
     DevicePolicyManager mDPM;
 
     private ChooseLockSettingsHelper mChooseLockSettingsHelper;
+    private Preference mUserSelectedWidget;
     private LockPatternUtils mLockPatternUtils;
     private ListPreference mLockAfter;
 
@@ -241,6 +253,25 @@
             mToggleVerifyApps.setEnabled(false);
         }
 
+        mUserSelectedWidget = root.findPreference(KEY_CHOOSE_USER_SELECTED_LOCKSCREEN_WIDGET);
+        if (mUserSelectedWidget != null) {
+            AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(getActivity());
+            int appWidgetId = -1;
+            String appWidgetIdString = Settings.Secure.getString(
+                    getContentResolver(), Settings.Secure.LOCK_SCREEN_USER_SELECTED_APPWIDGET_ID);
+            if (appWidgetIdString != null) {;
+                appWidgetId = (int) Integer.decode(appWidgetIdString);
+            }
+            if (appWidgetId == -1) {
+                mUserSelectedWidget.setSummary(getResources().getString(R.string.widget_none));
+            } else {
+                AppWidgetProviderInfo appWidget = appWidgetManager.getAppWidgetInfo(appWidgetId);
+                if (appWidget != null) {
+                    mUserSelectedWidget.setSummary(appWidget.label);
+                }
+            }
+        }
+
         return root;
     }
 
@@ -393,6 +424,17 @@
         }
     }
 
+    void startActivityForResultSafely(Intent intent, int requestCode) {
+        try {
+            startActivityForResult(intent, requestCode);
+        } catch (ActivityNotFoundException e) {
+            Toast.makeText(getActivity(), R.string.activity_not_found, Toast.LENGTH_SHORT).show();
+        } catch (SecurityException e) {
+            Toast.makeText(getActivity(), R.string.activity_not_found, Toast.LENGTH_SHORT).show();
+            Log.e(TAG, "Settings does not have the permission to launch " + intent, e);
+        }
+    }
+
     @Override
     public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
         final String key = preference.getKey();
@@ -401,6 +443,38 @@
         if (KEY_UNLOCK_SET_OR_CHANGE.equals(key)) {
             startFragment(this, "com.android.settings.ChooseLockGeneric$ChooseLockGenericFragment",
                     SET_OR_CHANGE_LOCK_METHOD_REQUEST, null);
+        } else if (KEY_CHOOSE_USER_SELECTED_LOCKSCREEN_WIDGET.equals(key)) {
+            // Create intent to pick widget
+            Intent pickIntent = new Intent(AppWidgetManager.ACTION_APPWIDGET_PICK);
+            // Found in KeyguardHostView.java
+            final int KEYGUARD_HOST_ID = 0x4B455947;
+            int appWidgetId = AppWidgetHost.allocateAppWidgetIdForHost(
+                    "com.android.internal.policy.impl.keyguard", KEYGUARD_HOST_ID);
+            if (appWidgetId != -1) {
+                pickIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
+                pickIntent.putExtra(AppWidgetManager.EXTRA_CUSTOM_SORT, false);
+                pickIntent.putExtra(AppWidgetManager.EXTRA_CATEGORY_FILTER,
+                        AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD);
+
+                // Add an entry for "none" to let someone select no widget
+                AppWidgetProviderInfo noneInfo = new AppWidgetProviderInfo();
+                ArrayList<AppWidgetProviderInfo> extraInfos = new ArrayList<AppWidgetProviderInfo>();
+                noneInfo.label = getResources().getString(R.string.widget_none);
+                noneInfo.provider = new ComponentName("", "");
+                extraInfos.add(noneInfo);
+
+                ArrayList<Bundle> extraExtras = new ArrayList<Bundle>();
+                Bundle b = new Bundle();
+                b.putBoolean(EXTRA_NO_WIDGET, true);
+                extraExtras.add(b);
+
+                // Launch the widget picker
+                pickIntent.putExtra(AppWidgetManager.EXTRA_CUSTOM_INFO, extraInfos);
+                pickIntent.putExtra(AppWidgetManager.EXTRA_CUSTOM_EXTRAS, extraExtras);
+                startActivityForResult(pickIntent, REQUEST_PICK_APPWIDGET);
+            } else {
+                Log.e(TAG, "Unable to allocate an AppWidget id in lock screen");
+            }
         } else if (KEY_BIOMETRIC_WEAK_IMPROVE_MATCHING.equals(key)) {
             ChooseLockSettingsHelper helper =
                     new ChooseLockSettingsHelper(this.getActivity(), this);
@@ -479,10 +553,45 @@
                 resultCode == Activity.RESULT_OK) {
             final LockPatternUtils lockPatternUtils = mChooseLockSettingsHelper.utils();
             lockPatternUtils.setBiometricWeakLivelinessEnabled(false);
-	    // Setting the mBiometricWeakLiveliness checked value to false is handled when onResume
-	    // is called by grabbing the value from lockPatternUtils.  We can't set it here
-	    // because mBiometricWeakLiveliness could be null
+            // Setting the mBiometricWeakLiveliness checked value to false is handled when onResume
+            // is called by grabbing the value from lockPatternUtils.  We can't set it here
+            // because mBiometricWeakLiveliness could be null
             return;
+        } else if (requestCode == REQUEST_PICK_APPWIDGET ||
+                requestCode == REQUEST_CREATE_APPWIDGET) {
+            int appWidgetId = (data == null) ? -1 : data.getIntExtra(
+                    AppWidgetManager.EXTRA_APPWIDGET_ID, -1);
+            if (requestCode == REQUEST_PICK_APPWIDGET && resultCode == Activity.RESULT_OK) {
+                AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(getActivity());
+                boolean noWidget = data.getBooleanExtra(EXTRA_NO_WIDGET, false);
+
+                AppWidgetProviderInfo appWidget = null;
+                if (!noWidget) {
+                    appWidget = appWidgetManager.getAppWidgetInfo(appWidgetId);
+                }
+
+                if (!noWidget && appWidget.configure != null) {
+                    // Launch over to configure widget, if needed
+                    Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_CONFIGURE);
+                    intent.setComponent(appWidget.configure);
+                    intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
+
+                    startActivityForResultSafely(intent, REQUEST_CREATE_APPWIDGET);
+                } else {
+                    // Otherwise just add it
+                    if (noWidget) {
+                        data.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1);
+                    }
+                    onActivityResult(REQUEST_CREATE_APPWIDGET, Activity.RESULT_OK, data);
+                }
+            } else if (requestCode == REQUEST_CREATE_APPWIDGET && resultCode == Activity.RESULT_OK) {
+                Settings.Secure.putString(getContentResolver(),
+                        Settings.Secure.LOCK_SCREEN_USER_SELECTED_APPWIDGET_ID,
+                        Integer.toString(appWidgetId));
+
+            } else {
+                AppWidgetHost.deleteAppWidgetIdForHost(appWidgetId);
+            }
         }
         createPreferenceHierarchy();
     }
diff --git a/src/com/android/settings/Settings.java b/src/com/android/settings/Settings.java
index 88b3e87..bf31386 100644
--- a/src/com/android/settings/Settings.java
+++ b/src/com/android/settings/Settings.java
@@ -100,6 +100,7 @@
             R.id.wireless_section,
             R.id.wifi_settings,
             R.id.bluetooth_settings,
+            R.id.data_usage_settings,
             R.id.device_section,
             R.id.sound_settings,
             R.id.display_settings,
diff --git a/src/com/android/settings/applications/ManageApplications.java b/src/com/android/settings/applications/ManageApplications.java
index 1240d43..0a73b02 100644
--- a/src/com/android/settings/applications/ManageApplications.java
+++ b/src/com/android/settings/applications/ManageApplications.java
@@ -20,6 +20,7 @@
 import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND;
 
 import android.app.Activity;
+import android.app.ActivityManager;
 import android.app.AlertDialog;
 import android.app.Fragment;
 import android.app.INotificationManager;
@@ -41,6 +42,7 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.UserHandle;
 import android.preference.PreferenceActivity;
 import android.preference.PreferenceFrameLayout;
 import android.provider.Settings;
@@ -1119,11 +1121,15 @@
                                 + prefActivities.get(i).getPackageName());
                         pm.clearPackagePreferredActivities(prefActivities.get(i).getPackageName());
                     }
-                    final int[] restrictedAppIds = npm.getAppsWithPolicy(
+                    final int[] restrictedUids = npm.getUidsWithPolicy(
                             POLICY_REJECT_METERED_BACKGROUND);
-                    for (int i : restrictedAppIds) {
-                        if (DEBUG) Log.v(TAG, "Clearing data policy: " + i);
-                        npm.setAppPolicy(i, POLICY_NONE);
+                    final int currentUserId = ActivityManager.getCurrentUser();
+                    for (int uid : restrictedUids) {
+                        // Only reset for current user
+                        if (UserHandle.getUserId(uid) == currentUserId) {
+                            if (DEBUG) Log.v(TAG, "Clearing data policy: " + uid);
+                            npm.setUidPolicy(uid, POLICY_NONE);
+                        }
                     }
                     handler.post(new Runnable() {
                         @Override public void run() {
diff --git a/src/com/android/settings/deviceinfo/StorageMeasurement.java b/src/com/android/settings/deviceinfo/StorageMeasurement.java
index 50238f3..772ac0d 100644
--- a/src/com/android/settings/deviceinfo/StorageMeasurement.java
+++ b/src/com/android/settings/deviceinfo/StorageMeasurement.java
@@ -96,6 +96,9 @@
     }
 
     public static class MeasurementDetails {
+        public long totalSize;
+        public long availSize;
+
         /**
          * Total apps disk usage.
          * <p>
@@ -111,6 +114,11 @@
         public long appsSize;
 
         /**
+         * Total cache disk usage by apps.
+         */
+        public long cacheSize;
+
+        /**
          * Total media disk usage, categorized by types such as
          * {@link Environment#DIRECTORY_MUSIC}.
          * <p>
@@ -237,34 +245,36 @@
         }
 
         private void addStatsLocked(PackageStats stats) {
-            final long externalSize = stats.externalCodeSize + stats.externalDataSize
-                    + stats.externalCacheSize + stats.externalMediaSize;
-
             if (mIsInternal) {
-                final long codeSize;
-                final long dataSize;
+                long codeSize = stats.codeSize;
+                long dataSize = stats.dataSize;
+                long cacheSize = stats.cacheSize;
                 if (Environment.isExternalStorageEmulated()) {
-                    // OBB is shared on emulated storage, so count once as code,
-                    // and data includes emulated storage.
-                    codeSize = stats.codeSize + stats.externalObbSize;
-                    dataSize = stats.dataSize + externalSize;
-                } else {
-                    codeSize = stats.codeSize;
-                    dataSize = stats.dataSize;
+                    // Include emulated storage when measuring internal. OBB is
+                    // shared on emulated storage, so treat as code.
+                    codeSize += stats.externalCodeSize + stats.externalObbSize;
+                    dataSize += stats.externalDataSize + stats.externalMediaSize;
+                    cacheSize += stats.externalCacheSize;
                 }
 
-                // Include code and combined data for current user
+                // Count code and data for current user
                 if (stats.userHandle == mCurrentUser) {
                     mDetails.appsSize += codeSize;
                     mDetails.appsSize += dataSize;
                 }
 
-                // Include combined data for user summary
+                // User summary only includes data (code is only counted once
+                // for the current user)
                 addValue(mDetails.usersSize, stats.userHandle, dataSize);
 
+                // Include cache for all users
+                mDetails.cacheSize += cacheSize;
+
             } else {
                 // Physical storage; only count external sizes
-                mDetails.appsSize += externalSize + stats.externalObbSize;
+                mDetails.appsSize += stats.externalCodeSize + stats.externalDataSize
+                        + stats.externalMediaSize + stats.externalObbSize;
+                mDetails.cacheSize += stats.externalCacheSize;
             }
         }
     }
@@ -389,6 +399,9 @@
             final MeasurementDetails details = new MeasurementDetails();
             final Message finished = obtainMessage(MSG_COMPLETED, details);
 
+            details.totalSize = mTotalSize;
+            details.availSize = mAvailSize;
+
             final UserManager userManager = (UserManager) context.getSystemService(
                     Context.USER_SERVICE);
             final List<UserInfo> users = userManager.getUsers();
diff --git a/src/com/android/settings/deviceinfo/StorageVolumePreferenceCategory.java b/src/com/android/settings/deviceinfo/StorageVolumePreferenceCategory.java
index 469dbc7..44d40a0 100644
--- a/src/com/android/settings/deviceinfo/StorageVolumePreferenceCategory.java
+++ b/src/com/android/settings/deviceinfo/StorageVolumePreferenceCategory.java
@@ -312,6 +312,10 @@
         final boolean showDetails = mVolume == null || mVolume.isPrimary();
         if (!showDetails) return;
 
+        // Count caches as available space, since system manages them
+        mItemTotal.setSummary(formatSize(details.totalSize));
+        mItemAvailable.setSummary(formatSize(details.availSize + details.cacheSize));
+
         mUsageBarPreference.clear();
 
         updatePreference(mItemApps, details.appsSize);
@@ -326,7 +330,7 @@
         updatePreference(mItemMusic, musicSize);
 
         final long downloadsSize = totalValues(details.mediaSize, Environment.DIRECTORY_DOWNLOADS);
-        updatePreference(mItemDownloads, musicSize);
+        updatePreference(mItemDownloads, downloadsSize);
 
         updatePreference(mItemMisc, details.miscSize);
 
diff --git a/src/com/android/settings/net/UidDetailProvider.java b/src/com/android/settings/net/UidDetailProvider.java
index dd2b8c0..37b99dd 100644
--- a/src/com/android/settings/net/UidDetailProvider.java
+++ b/src/com/android/settings/net/UidDetailProvider.java
@@ -21,20 +21,30 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.UserInfo;
 import android.content.res.Resources;
 import android.graphics.drawable.Drawable;
 import android.net.ConnectivityManager;
 import android.net.TrafficStats;
+import android.os.UserManager;
 import android.text.TextUtils;
 import android.util.SparseArray;
 
 import com.android.settings.R;
 import com.android.settings.Utils;
 
+/**
+ * Return details about a specific UID, handling special cases like
+ * {@link TrafficStats#UID_TETHERING} and {@link UserInfo}.
+ */
 public class UidDetailProvider {
     private final Context mContext;
     private final SparseArray<UidDetail> mUidDetailCache;
 
+    public static int buildKeyForUser(int userHandle) {
+        return -(2000 + userHandle);
+    }
+
     public UidDetailProvider(Context context) {
         mContext = context.getApplicationContext();
         mUidDetailCache = new SparseArray<UidDetail>();
@@ -101,10 +111,21 @@
                 return detail;
         }
 
+        // Handle keys that are actually user handles
+        if (uid <= -2000) {
+            final int userHandle = (-uid) - 2000;
+            final UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+            final UserInfo info = um.getUserInfo(userHandle);
+            if (info != null) {
+                detail.label = res.getString(R.string.running_process_item_user_label, info.name);
+                detail.icon = Drawable.createFromPath(info.iconPath);
+                return detail;
+            }
+        }
+
         // otherwise fall back to using packagemanager labels
         final String[] packageNames = pm.getPackagesForUid(uid);
         final int length = packageNames != null ? packageNames.length : 0;
-
         try {
             if (length == 1) {
                 final ApplicationInfo info = pm.getApplicationInfo(packageNames[0], 0);
diff --git a/src/com/android/settings/users/ProfileUpdateReceiver.java b/src/com/android/settings/users/ProfileUpdateReceiver.java
new file mode 100644
index 0000000..5513608
--- /dev/null
+++ b/src/com/android/settings/users/ProfileUpdateReceiver.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2012 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 com.android.settings.users;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.UserInfo;
+import android.net.Uri;
+import android.os.ParcelFileDescriptor;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Profile;
+import android.util.Log;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Watches for changes to Me Profile in Contacts and writes the photo to the User Manager.
+ */
+public class ProfileUpdateReceiver extends BroadcastReceiver {
+
+    @Override
+    public void onReceive(final Context context, Intent intent) {
+        // Profile changed, lets get the photo and write to user manager
+        new Thread() {
+            public void run() {
+                copyProfilePhoto(context, null);
+            }
+        }.start();
+    }
+
+    /* Used by UserSettings as well. Call this on a non-ui thread. */
+    static boolean copyProfilePhoto(Context context, UserInfo user) {
+        Uri contactUri = Profile.CONTENT_URI;
+
+        InputStream avatarDataStream = Contacts.openContactPhotoInputStream(
+                    context.getContentResolver(),
+                    contactUri, true);
+        // If there's no profile photo, assign a default avatar
+        if (avatarDataStream == null) {
+            return false;
+        }
+        int userId = user != null ? user.id : UserHandle.myUserId();
+        UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE);
+        ParcelFileDescriptor fd = um.setUserIcon(userId);
+        FileOutputStream os = new ParcelFileDescriptor.AutoCloseOutputStream(fd);
+        byte[] buffer = new byte[4096];
+        int readSize;
+        try {
+            while ((readSize = avatarDataStream.read(buffer)) > 0) {
+                os.write(buffer, 0, readSize);
+            }
+            return true;
+        } catch (IOException ioe) {
+            Log.e("copyProfilePhoto", "Error copying profile photo " + ioe);
+        } finally {
+            try {
+                os.close();
+                avatarDataStream.close();
+            } catch (IOException ioe) { }
+        }
+        return false;
+    }
+}
diff --git a/src/com/android/settings/users/UserSettings.java b/src/com/android/settings/users/UserSettings.java
index fe1bd90..28fe4c1 100644
--- a/src/com/android/settings/users/UserSettings.java
+++ b/src/com/android/settings/users/UserSettings.java
@@ -31,6 +31,7 @@
 import android.graphics.Bitmap.CompressFormat;
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
+import android.os.AsyncTask;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Message;
@@ -98,10 +99,10 @@
     private EditTextPreference mNicknamePreference;
     private int mRemovingUserId = -1;
     private boolean mAddingUser;
+    private boolean mProfileExists;
 
     private final Object mUserLock = new Object();
     private UserManager mUserManager;
-    private boolean mProfileChanged;
 
     private Handler mHandler = new Handler() {
         @Override
@@ -114,13 +115,6 @@
         }
     };
 
-    private ContentObserver mProfileObserver = new ContentObserver(mHandler) {
-        @Override
-        public void onChange(boolean selfChange) {
-            mProfileChanged = true;
-        }
-    };
-
     private BroadcastReceiver mUserChangeReceiver = new BroadcastReceiver() {
 
         @Override
@@ -143,11 +137,8 @@
         mNicknamePreference = (EditTextPreference) findPreference(KEY_USER_NICKNAME);
         mNicknamePreference.setOnPreferenceChangeListener(this);
         mNicknamePreference.setSummary(mUserManager.getUserInfo(UserHandle.myUserId()).name);
-        loadProfile(false);
+        loadProfile();
         setHasOptionsMenu(true);
-        // Register to watch for profile changes
-        getActivity().getContentResolver().registerContentObserver(
-                ContactsContract.Profile.CONTENT_URI, false, mProfileObserver);
         getActivity().registerReceiver(mUserChangeReceiver,
                 new IntentFilter(Intent.ACTION_USER_REMOVED));
     }
@@ -155,17 +146,13 @@
     @Override
     public void onResume() {
         super.onResume();
-        if (mProfileChanged) {
-            loadProfile(true);
-            mProfileChanged = false;
-        }
+        loadProfile();
         updateUserList();
     }
 
     @Override
     public void onDestroy() {
         super.onDestroy();
-        getActivity().getContentResolver().unregisterContentObserver(mProfileObserver);
         getActivity().unregisterReceiver(mUserChangeReceiver);
     }
 
@@ -198,13 +185,29 @@
         }
     }
 
-    private void loadProfile(boolean force) {
-        UserInfo user = mUserManager.getUserInfo(UserHandle.myUserId());
-        if (force || user.iconPath == null || user.iconPath.equals("")) {
-            assignProfilePhoto(user);
-        }
-        String profileName = getProfileName();
+    private void loadProfile() {
+        mProfileExists = false;
+        new AsyncTask<Void, Void, String>() {
+            @Override
+            protected void onPostExecute(String result) {
+                finishLoadProfile(result);
+            }
+
+            @Override
+            protected String doInBackground(Void... values) {
+                UserInfo user = mUserManager.getUserInfo(UserHandle.myUserId());
+                if (user.iconPath == null || user.iconPath.equals("")) {
+                    assignProfilePhoto(user);
+                }
+                String profileName = getProfileName();
+                return profileName;
+            }
+        }.execute();
+    }
+
+    private void finishLoadProfile(String profileName) {
         mMePreference.setTitle(profileName);
+        setPhotoId(mMePreference, mUserManager.getUserInfo(UserHandle.myUserId()));
     }
 
     private void onAddUserClicked() {
@@ -343,37 +346,10 @@
         getActivity().invalidateOptionsMenu();
     }
 
-    /* TODO: Put this in an AsyncTask */
     private void assignProfilePhoto(final UserInfo user) {
-        // If the contact is "me", then use my local profile photo. Otherwise, build a
-        // uri to get the avatar of the contact.
-        Uri contactUri = Profile.CONTENT_URI;
-
-        InputStream avatarDataStream = Contacts.openContactPhotoInputStream(
-                    getActivity().getContentResolver(),
-                    contactUri, true);
-        // If there's no profile photo, assign a default avatar
-        if (avatarDataStream == null) {
+        if (!ProfileUpdateReceiver.copyProfilePhoto(getActivity(), user)) {
             assignDefaultPhoto(user);
-            setPhotoId(mMePreference, user);
-            return;
         }
-
-        ParcelFileDescriptor fd = mUserManager.setUserIcon(user.id);
-        FileOutputStream os = new ParcelFileDescriptor.AutoCloseOutputStream(fd);
-        byte[] buffer = new byte[4096];
-        int readSize;
-        try {
-            while ((readSize = avatarDataStream.read(buffer)) > 0) {
-                os.write(buffer, 0, readSize);
-            }
-            os.close();
-            avatarDataStream.close();
-        } catch (IOException ioe) {
-            Log.e(TAG, "Error copying profile photo " + ioe);
-        }
-
-        setPhotoId(mMePreference, user);
     }
 
     private String getProfileName() {
@@ -387,6 +363,7 @@
 
         try {
             if (cursor.moveToFirst()) {
+                mProfileExists = true;
                 return cursor.getString(cursor.getColumnIndex(Phone.DISPLAY_NAME));
             }
         } finally {
@@ -421,8 +398,14 @@
     @Override
     public boolean onPreferenceClick(Preference pref) {
         if (pref == mMePreference) {
-            Intent editProfile = new Intent(Intent.ACTION_EDIT);
-            editProfile.setData(ContactsContract.Profile.CONTENT_URI);
+            Intent editProfile;
+            if (!mProfileExists) {
+                editProfile = new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI);
+                // TODO: Make this a proper API
+                editProfile.putExtra("newLocalProfile", true);
+            } else {
+                editProfile = new Intent(Intent.ACTION_EDIT, ContactsContract.Profile.CONTENT_URI);
+            }
             // To make sure that it returns back here when done
             // TODO: Make this a proper API
             editProfile.putExtra("finishActivityOnSaveCompleted", true);