Merge "Fix crash in Settings > Security" 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/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/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);