Merge "Watch for profile changes and push to user manager" into jb-mr1-dev
diff --git a/res/values/strings.xml b/res/values/strings.xml
index f1b4a5e..f22013e 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 -->
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/SecuritySettings.java b/src/com/android/settings/SecuritySettings.java
index e309c37..e9e3a19 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,23 @@
             mToggleVerifyApps.setEnabled(false);
         }
 
+        mUserSelectedWidget = root.findPreference(KEY_CHOOSE_USER_SELECTED_LOCKSCREEN_WIDGET);
+        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 +422,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 +441,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 +551,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/TetherSettings.java b/src/com/android/settings/TetherSettings.java
index f6c1734..1564803 100644
--- a/src/com/android/settings/TetherSettings.java
+++ b/src/com/android/settings/TetherSettings.java
@@ -48,6 +48,7 @@
 
 import java.io.InputStream;
 import java.util.ArrayList;
+import java.util.concurrent.atomic.AtomicReference;
 import java.util.Locale;
 
 /*
@@ -78,7 +79,7 @@
     private String[] mWifiRegexs;
 
     private String[] mBluetoothRegexs;
-    private BluetoothPan mBluetoothPan;
+    private AtomicReference<BluetoothPan> mBluetoothPan;
 
     private static final String WIFI_AP_SSID_AND_SECURITY = "wifi_ap_ssid_and_security";
     private static final int CONFIG_SUBTEXT = R.string.wifi_tether_configure_subtext;
@@ -151,7 +152,8 @@
         if (!bluetoothAvailable) {
             getPreferenceScreen().removePreference(mBluetoothTether);
         } else {
-            if (mBluetoothPan != null && mBluetoothPan.isTetheringOn()) {
+            BluetoothPan pan = mBluetoothPan.get();
+            if (pan != null && pan.isTetheringOn()) {
                 mBluetoothTether.setChecked(true);
             } else {
                 mBluetoothTether.setChecked(false);
@@ -188,10 +190,10 @@
     private BluetoothProfile.ServiceListener mProfileServiceListener =
         new BluetoothProfile.ServiceListener() {
         public void onServiceConnected(int profile, BluetoothProfile proxy) {
-            mBluetoothPan = (BluetoothPan) proxy;
+            mBluetoothPan.set((BluetoothPan) proxy);
         }
         public void onServiceDisconnected(int profile) {
-            mBluetoothPan = null;
+            mBluetoothPan.set(null);
         }
     };
 
@@ -235,8 +237,9 @@
                     switch (intent
                             .getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR)) {
                         case BluetoothAdapter.STATE_ON:
-                            if(mBluetoothPan != null) {
-                                mBluetoothPan.setBluetoothTethering(true);
+                            BluetoothPan bluetoothPan = mBluetoothPan.get();
+                            if (bluetoothPan != null) {
+                                bluetoothPan.setBluetoothTethering(true);
                                 mBluetoothEnableForTether = false;
                             }
                             break;
@@ -389,26 +392,31 @@
         } else if (btState == BluetoothAdapter.STATE_TURNING_ON) {
             mBluetoothTether.setEnabled(false);
             mBluetoothTether.setSummary(R.string.bluetooth_turning_on);
-        } else if (btState == BluetoothAdapter.STATE_ON &&
-                    mBluetoothPan != null && mBluetoothPan.isTetheringOn()) {
-            mBluetoothTether.setChecked(true);
-            mBluetoothTether.setEnabled(true);
-            int bluetoothTethered = mBluetoothPan.getConnectedDevices().size();
-            if (bluetoothTethered > 1) {
-                String summary = getString(
-                        R.string.bluetooth_tethering_devices_connected_subtext, bluetoothTethered);
-                mBluetoothTether.setSummary(summary);
-            } else if (bluetoothTethered == 1) {
-                mBluetoothTether.setSummary(R.string.bluetooth_tethering_device_connected_subtext);
-            } else if (bluetoothErrored) {
-                mBluetoothTether.setSummary(R.string.bluetooth_tethering_errored_subtext);
-            } else {
-                mBluetoothTether.setSummary(R.string.bluetooth_tethering_available_subtext);
-            }
         } else {
-            mBluetoothTether.setEnabled(true);
-            mBluetoothTether.setChecked(false);
-            mBluetoothTether.setSummary(R.string.bluetooth_tethering_off_subtext);
+            BluetoothPan bluetoothPan = mBluetoothPan.get();
+            if (btState == BluetoothAdapter.STATE_ON && bluetoothPan != null &&
+                    bluetoothPan.isTetheringOn()) {
+                mBluetoothTether.setChecked(true);
+                mBluetoothTether.setEnabled(true);
+                int bluetoothTethered = bluetoothPan.getConnectedDevices().size();
+                if (bluetoothTethered > 1) {
+                    String summary = getString(
+                            R.string.bluetooth_tethering_devices_connected_subtext,
+                            bluetoothTethered);
+                    mBluetoothTether.setSummary(summary);
+                } else if (bluetoothTethered == 1) {
+                    mBluetoothTether.setSummary(
+                            R.string.bluetooth_tethering_device_connected_subtext);
+                } else if (bluetoothErrored) {
+                    mBluetoothTether.setSummary(R.string.bluetooth_tethering_errored_subtext);
+                } else {
+                    mBluetoothTether.setSummary(R.string.bluetooth_tethering_available_subtext);
+                }
+            } else {
+                mBluetoothTether.setEnabled(true);
+                mBluetoothTether.setChecked(false);
+                mBluetoothTether.setSummary(R.string.bluetooth_tethering_off_subtext);
+            }
         }
     }
 
@@ -476,7 +484,8 @@
                     mBluetoothTether.setSummary(R.string.bluetooth_turning_on);
                     mBluetoothTether.setEnabled(false);
                 } else {
-                    mBluetoothPan.setBluetoothTethering(true);
+                    BluetoothPan bluetoothPan = mBluetoothPan.get();
+                    if (bluetoothPan != null) bluetoothPan.setBluetoothTethering(true);
                     mBluetoothTether.setSummary(R.string.bluetooth_tethering_available_subtext);
                 }
                 break;
@@ -528,7 +537,8 @@
                     errored = true;
                 }
 
-                mBluetoothPan.setBluetoothTethering(false);
+                BluetoothPan bluetoothPan = mBluetoothPan.get();
+                if (bluetoothPan != null) bluetoothPan.setBluetoothTethering(false);
                 if (errored) {
                     mBluetoothTether.setSummary(R.string.bluetooth_tethering_errored_subtext);
                 } else {
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);