Merge "Fixed bug where facelock wasn't set correctly for pattern tutorial"
diff --git a/res/layout/data_usage_cycles.xml b/res/layout/data_usage_cycles.xml
index 459ef6e..136fec6 100644
--- a/res/layout/data_usage_cycles.xml
+++ b/res/layout/data_usage_cycles.xml
@@ -17,8 +17,9 @@
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/cycles"
     android:layout_width="match_parent"
-    android:layout_height="wrap_content"
+    android:layout_height="40dip"
     android:orientation="horizontal"
+    android:gravity="center_vertical"
     android:paddingLeft="@*android:dimen/preference_item_padding_side"
     android:paddingRight="@*android:dimen/preference_item_padding_side">
 
diff --git a/res/layout/data_usage_header.xml b/res/layout/data_usage_header.xml
index 9dcb456..b27d88f 100644
--- a/res/layout/data_usage_header.xml
+++ b/res/layout/data_usage_header.xml
@@ -17,8 +17,6 @@
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
-    android:paddingLeft="@*android:dimen/preference_fragment_padding_side"
-    android:paddingRight="@*android:dimen/preference_fragment_padding_side"
     android:orientation="vertical"
     android:clipChildren="false"
     android:clipToPadding="false">
diff --git a/res/layout/data_usage_item.xml b/res/layout/data_usage_item.xml
index 7808173..fac3c6b 100644
--- a/res/layout/data_usage_item.xml
+++ b/res/layout/data_usage_item.xml
@@ -16,9 +16,7 @@
 
 <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:paddingLeft="@*android:dimen/preference_fragment_padding_side"
-    android:paddingRight="@*android:dimen/preference_fragment_padding_side">
+    android:layout_height="wrap_content">
 
     <include layout="@layout/app_percentage_item" />
 
diff --git a/res/layout/preference_inputmethod.xml b/res/layout/preference_inputmethod.xml
index 0e0c1ef..f1bd0a0 100644
--- a/res/layout/preference_inputmethod.xml
+++ b/res/layout/preference_inputmethod.xml
@@ -56,10 +56,12 @@
                 android:layout_height="wrap_content"
                 android:layout_below="@android:id/title"
                 android:layout_alignLeft="@android:id/title"
+                android:paddingBottom="3dip"
                 android:visibility="gone"
                 android:textAppearance="?android:attr/textAppearanceSmall"
                 android:textSize="13sp"
                 android:textColor="?android:attr/textColorSecondary"
+                android:background="?android:attr/selectableItemBackground"
                 android:maxLines="4" />
         </RelativeLayout>
     </LinearLayout>
diff --git a/res/values/bools.xml b/res/values/bools.xml
index 3d5c380..a7e6ac8 100644
--- a/res/values/bools.xml
+++ b/res/values/bools.xml
@@ -15,9 +15,6 @@
 -->
 
 <resources>
-    <!-- Whether or not there is a notification led that is too intrusive to be pulsing
-         constantly -->
-    <bool name="has_intrusive_led">false</bool>
     <!-- Whether or not the dock settings are to be displayed for this device when docked -->
     <bool name="has_dock_settings">false</bool>
     <!-- Whether there is a silent mode checkbox  -->
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 1874107..c59ba49 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -3485,13 +3485,13 @@
     <!-- Body of dialog shown to request confirmation that mobile data will be disabled. [CHAR LIMIT=NONE] -->
     <string name="data_usage_disable_mobile">Disable mobile data?</string>
     <!-- Checkbox label that will disable mobile network data connection when user-defined limit is reached. [CHAR LIMIT=32] -->
-    <string name="data_usage_disable_mobile_limit">Disable mobile data at limit</string>
+    <string name="data_usage_disable_mobile_limit">Set mobile data limit</string>
     <!-- Checkbox label that will disable 4G network data connection when user-defined limit is reached. [CHAR LIMIT=32] -->
-    <string name="data_usage_disable_4g_limit">Disable 4G data at limit</string>
+    <string name="data_usage_disable_4g_limit">Set 4G data limit</string>
     <!-- Checkbox label that will disable 2G-3G network data connection when user-defined limit is reached. [CHAR LIMIT=32] -->
-    <string name="data_usage_disable_3g_limit">Disable 2G-3G data at limit</string>
+    <string name="data_usage_disable_3g_limit">Set 2G-3G data limit</string>
     <!-- Checkbox label that will disable Wi-Fi network data connection when user-defined limit is reached. [CHAR LIMIT=32] -->
-    <string name="data_usage_disable_wifi_limit">Disable Wi-Fi data at limit</string>
+    <string name="data_usage_disable_wifi_limit">Set Wi-Fi data limit</string>
 
     <!-- Tab title for showing Wi-Fi data usage. [CHAR LIMIT=10] -->
     <string name="data_usage_tab_wifi">Wi-Fi</string>
diff --git a/src/com/android/settings/AccessibilityTutorialActivity.java b/src/com/android/settings/AccessibilityTutorialActivity.java
index 5039970..206aa94 100644
--- a/src/com/android/settings/AccessibilityTutorialActivity.java
+++ b/src/com/android/settings/AccessibilityTutorialActivity.java
@@ -522,6 +522,9 @@
         /** Whether this module is currently focused. */
         private boolean mIsVisible;
 
+        /** Handler for sending accessibility events after the current UI action. */
+        private InstructionHandler mHandler = new InstructionHandler();
+
         /**
          * Constructs a new tutorial module for the given context and controller
          * with the specified layout.
@@ -585,8 +588,11 @@
                 return;
             }
 
-            final String text = getContext().getString(resId, formatArgs);
+            final String text = mContext.getString(resId, formatArgs);
+            mHandler.addInstruction(text);
+        }
 
+        private void addInstructionSync(CharSequence text) {
             mInstructions.setVisibility(View.VISIBLE);
             mInstructions.setText(text);
             mInstructions.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
@@ -661,6 +667,24 @@
         protected void setFinishVisible(boolean visible) {
             mFinish.setVisibility(visible ? VISIBLE : GONE);
         }
+
+        private class InstructionHandler extends Handler {
+            private static final int ADD_INSTRUCTION = 1;
+
+            @Override
+            public void handleMessage(Message msg) {
+                switch (msg.what) {
+                    case ADD_INSTRUCTION:
+                        final String text = (String) msg.obj;
+                        addInstructionSync(text);
+                        break;
+                }
+            }
+
+            public void addInstruction(String text) {
+                obtainMessage(ADD_INSTRUCTION, text).sendToTarget();
+            }
+        }
     }
 
     /**
diff --git a/src/com/android/settings/ApnSettings.java b/src/com/android/settings/ApnSettings.java
index 00ef3a7..57762d4 100644
--- a/src/com/android/settings/ApnSettings.java
+++ b/src/com/android/settings/ApnSettings.java
@@ -149,8 +149,8 @@
             + android.os.SystemProperties.get(TelephonyProperties.PROPERTY_ICC_OPERATOR_NUMERIC, "")
             + "\"";
 
-        Cursor cursor = managedQuery(Telephony.Carriers.CONTENT_URI, new String[] {
-                "_id", "name", "apn", "type"}, where,
+        Cursor cursor = getContentResolver().query(Telephony.Carriers.CONTENT_URI, new String[] {
+                "_id", "name", "apn", "type"}, where, null,
                 Telephony.Carriers.DEFAULT_SORT_ORDER);
 
         PreferenceGroup apnList = (PreferenceGroup) findPreference("apn_list");
@@ -254,8 +254,8 @@
     private String getSelectedApnKey() {
         String key = null;
 
-        Cursor cursor = managedQuery(PREFERAPN_URI, new String[] {"_id"},
-                null, Telephony.Carriers.DEFAULT_SORT_ORDER);
+        Cursor cursor = getContentResolver().query(PREFERAPN_URI, new String[] {"_id"},
+                null, null, Telephony.Carriers.DEFAULT_SORT_ORDER);
         if (cursor.getCount() > 0) {
             cursor.moveToFirst();
             key = cursor.getString(ID_INDEX);
diff --git a/src/com/android/settings/CryptKeeper.java b/src/com/android/settings/CryptKeeper.java
index b1de932e7..ad2655f 100644
--- a/src/com/android/settings/CryptKeeper.java
+++ b/src/com/android/settings/CryptKeeper.java
@@ -88,6 +88,7 @@
 
     /** When encryption is detected, this flag indivates whether or not we've checked for erros. */
     private boolean mValidationComplete;
+    private boolean mValidationRequested;
     /** A flag to indicate that the volume is in a bad state (e.g. partially encrypted). */
     private boolean mEncryptionGoneBad;
 
@@ -159,6 +160,7 @@
         protected Boolean doInBackground(Void... params) {
             IMountService service = getMountService();
             try {
+                Log.d(TAG, "Validating encryption state.");
                 int state = service.getEncryptionState();
                 if (state == IMountService.ENCRYPTION_STATE_NONE) {
                     Log.w(TAG, "Unexpectedly in CryptKeeper even though there is no encryption.");
@@ -177,6 +179,8 @@
             if (Boolean.FALSE.equals(result)) {
                 Log.w(TAG, "Incomplete, or corrupted encryption detected. Prompting user to wipe.");
                 mEncryptionGoneBad = true;
+            } else {
+                Log.d(TAG, "Encryption state validated. Proceeding to configure UI");
             }
             setupUi();
         }
@@ -237,9 +241,6 @@
             mWakeLock = retained.wakelock;
             Log.d(TAG, "Restoring wakelock from NonConfigurationInstanceState");
         }
-
-        // Check the encryption status to ensure something hasn't gone bad.
-        new ValidationTask().execute((Void[]) null);
     }
 
     /**
@@ -251,10 +252,7 @@
     public void onStart() {
         super.onStart();
 
-        // Check to see why we were started.
-        if (mValidationComplete) {
-            setupUi();
-        }
+        setupUi();
     }
 
     /**
@@ -272,9 +270,13 @@
         if (!"".equals(progress) || isDebugView(FORCE_VIEW_PROGRESS)) {
             setContentView(R.layout.crypt_keeper_progress);
             encryptionProgressInit();
-        } else {
+        } else if (mValidationComplete) {
             setContentView(R.layout.crypt_keeper_password_entry);
             passwordEntryInit();
+        } else if (!mValidationRequested) {
+            // We're supposed to be encrypted, but no validation has been done.
+            new ValidationTask().execute((Void[]) null);
+            mValidationRequested = true;
         }
     }
 
diff --git a/src/com/android/settings/DataUsageSummary.java b/src/com/android/settings/DataUsageSummary.java
index 417c525..0e08075 100644
--- a/src/com/android/settings/DataUsageSummary.java
+++ b/src/com/android/settings/DataUsageSummary.java
@@ -25,11 +25,6 @@
 import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND;
 import static android.net.NetworkPolicyManager.computeLastCycleBoundary;
 import static android.net.NetworkPolicyManager.computeNextCycleBoundary;
-import static android.net.NetworkStats.SET_DEFAULT;
-import static android.net.NetworkStats.SET_FOREGROUND;
-import static android.net.NetworkStats.TAG_NONE;
-import static android.net.NetworkStatsHistory.FIELD_RX_BYTES;
-import static android.net.NetworkStatsHistory.FIELD_TX_BYTES;
 import static android.net.NetworkTemplate.MATCH_MOBILE_3G_LOWER;
 import static android.net.NetworkTemplate.MATCH_MOBILE_4G;
 import static android.net.NetworkTemplate.MATCH_MOBILE_ALL;
@@ -43,6 +38,7 @@
 import static android.text.format.DateUtils.FORMAT_SHOW_DATE;
 import static android.text.format.Time.TIMEZONE_UTC;
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+import static com.android.internal.util.Preconditions.checkNotNull;
 import static com.android.settings.Utils.prepareCustomPreferencesList;
 
 import android.animation.LayoutTransition;
@@ -58,15 +54,11 @@
 import android.content.Intent;
 import android.content.Loader;
 import android.content.SharedPreferences;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.res.Resources;
 import android.graphics.Color;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
-import android.graphics.drawable.InsetDrawable;
 import android.net.ConnectivityManager;
 import android.net.INetworkPolicyManager;
 import android.net.INetworkStatsService;
@@ -123,9 +115,12 @@
 
 import com.android.internal.telephony.Phone;
 import com.android.settings.drawable.InsetBoundsDrawable;
-import com.android.settings.drawable.DrawableWrapper;
+import com.android.settings.net.ChartData;
+import com.android.settings.net.ChartDataLoader;
 import com.android.settings.net.NetworkPolicyEditor;
 import com.android.settings.net.SummaryForAllUidLoader;
+import com.android.settings.net.UidDetail;
+import com.android.settings.net.UidDetailProvider;
 import com.android.settings.widget.ChartDataUsageView;
 import com.android.settings.widget.ChartDataUsageView.DataUsageChartListener;
 import com.android.settings.widget.PieChartView;
@@ -165,7 +160,8 @@
     private static final String TAG_CONFIRM_APP_RESTRICT = "confirmAppRestrict";
     private static final String TAG_APP_DETAILS = "appDetails";
 
-    private static final int LOADER_SUMMARY = 2;
+    private static final int LOADER_CHART_DATA = 2;
+    private static final int LOADER_SUMMARY = 3;
 
     private static final long KB_IN_BYTES = 1024;
     private static final long MB_IN_BYTES = KB_IN_BYTES * 1024;
@@ -188,6 +184,9 @@
     private ListView mListView;
     private DataUsageAdapter mAdapter;
 
+    /** Distance to inset content from sides, when needed. */
+    private int mInsetSide = 0;
+
     private ViewGroup mHeader;
 
     private ViewGroup mNetworkSwitchesContainer;
@@ -220,7 +219,8 @@
     private boolean mShowWifi = false;
     private boolean mShowEthernet = false;
 
-    private NetworkTemplate mTemplate = null;
+    private NetworkTemplate mTemplate;
+    private ChartData mChartData;
 
     private int[] mAppDetailUids = null;
 
@@ -228,11 +228,6 @@
 
     private NetworkPolicyEditor mPolicyEditor;
 
-    private NetworkStatsHistory mHistory;
-    private NetworkStatsHistory mDetailHistory;
-    private NetworkStatsHistory mDetailHistoryDefault;
-    private NetworkStatsHistory mDetailHistoryForeground;
-
     private String mCurrentTab = null;
     private String mIntentTab = null;
 
@@ -242,6 +237,8 @@
     /** Flag used to ignore listeners during binding. */
     private boolean mBinding;
 
+    private UidDetailProvider mUidDetailProvider;
+
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -273,25 +270,39 @@
         final Context context = inflater.getContext();
         final View view = inflater.inflate(R.layout.data_usage_summary, container, false);
 
+        mUidDetailProvider = new UidDetailProvider(context);
+
         mTabHost = (TabHost) view.findViewById(android.R.id.tabhost);
         mTabsContainer = (ViewGroup) view.findViewById(R.id.tabs_container);
         mTabWidget = (TabWidget) view.findViewById(android.R.id.tabs);
         mListView = (ListView) view.findViewById(android.R.id.list);
 
+        // decide if we need to manually inset our content, or if we should rely
+        // on parent container for inset.
+        final boolean shouldInset = mListView.getScrollBarStyle()
+                == View.SCROLLBARS_OUTSIDE_OVERLAY;
+        if (shouldInset) {
+            mInsetSide = view.getResources().getDimensionPixelOffset(
+                    com.android.internal.R.dimen.preference_fragment_padding_side);
+        } else {
+            mInsetSide = 0;
+        }
+
         // adjust padding around tabwidget as needed
         prepareCustomPreferencesList(container, view, mListView, true);
 
-        // inset selector and divider drawables
-        final int insetSide = view.getResources().getDimensionPixelOffset(
-                com.android.internal.R.dimen.preference_fragment_padding_side);
-        insetListViewDrawables(mListView, insetSide);
-
         mTabHost.setup();
         mTabHost.setOnTabChangedListener(mTabListener);
 
         mHeader = (ViewGroup) inflater.inflate(R.layout.data_usage_header, mListView, false);
         mListView.addHeaderView(mHeader, null, false);
 
+        if (mInsetSide > 0) {
+            // inset selector and divider drawables
+            insetListViewDrawables(mListView, mInsetSide);
+            mHeader.setPadding(mInsetSide, 0, mInsetSide, 0);
+        }
+
         {
             // bind network switches
             mNetworkSwitchesContainer = (ViewGroup) mHeader.findViewById(
@@ -346,7 +357,7 @@
         // only assign layout transitions once first layout is finished
         mListView.getViewTreeObserver().addOnGlobalLayoutListener(mFirstLayoutListener);
 
-        mAdapter = new DataUsageAdapter();
+        mAdapter = new DataUsageAdapter(mUidDetailProvider, mInsetSide);
         mListView.setOnItemClickListener(mListListener);
         mListView.setAdapter(mAdapter);
 
@@ -370,7 +381,10 @@
             @Override
             protected Void doInBackground(Void... params) {
                 try {
+                    // wait a few seconds before kicking off
+                    Thread.sleep(2 * DateUtils.SECOND_IN_MILLIS);
                     mStatsService.forceUpdate();
+                } catch (InterruptedException e) {
                 } catch (RemoteException e) {
                 }
                 return null;
@@ -382,7 +396,7 @@
                     updateBody();
                 }
             }
-        }.execute();
+        }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
     }
 
     @Override
@@ -393,21 +407,23 @@
     @Override
     public void onPrepareOptionsMenu(Menu menu) {
         final Context context = getActivity();
+        final boolean appDetailMode = isAppDetailMode();
 
         mMenuDataRoaming = menu.findItem(R.id.data_usage_menu_roaming);
-        mMenuDataRoaming.setVisible(hasMobileRadio(context));
+        mMenuDataRoaming.setVisible(hasMobileRadio(context) && !appDetailMode);
         mMenuDataRoaming.setChecked(getDataRoaming());
 
         mMenuRestrictBackground = menu.findItem(R.id.data_usage_menu_restrict_background);
+        mMenuRestrictBackground.setVisible(!appDetailMode);
         mMenuRestrictBackground.setChecked(getRestrictBackground());
 
         final MenuItem split4g = menu.findItem(R.id.data_usage_menu_split_4g);
-        split4g.setVisible(hasMobile4gRadio(context));
+        split4g.setVisible(hasMobile4gRadio(context) && !appDetailMode);
         split4g.setChecked(isMobilePolicySplit());
 
         final MenuItem showWifi = menu.findItem(R.id.data_usage_menu_show_wifi);
         if (hasWifiRadio(context) && hasMobileRadio(context)) {
-            showWifi.setVisible(true);
+            showWifi.setVisible(!appDetailMode);
             showWifi.setChecked(mShowWifi);
         } else {
             showWifi.setVisible(false);
@@ -416,7 +432,7 @@
 
         final MenuItem showEthernet = menu.findItem(R.id.data_usage_menu_show_ethernet);
         if (hasEthernet(context) && hasMobileRadio(context)) {
-            showEthernet.setVisible(true);
+            showEthernet.setVisible(!appDetailMode);
             showEthernet.setChecked(mShowEthernet);
         } else {
             showEthernet.setVisible(false);
@@ -478,6 +494,9 @@
 
         mDataEnabledView = null;
         mDisableAtLimitView = null;
+
+        mUidDetailProvider.clearCache();
+        mUidDetailProvider = null;
     }
 
     /**
@@ -495,6 +514,7 @@
             final LayoutTransition chartTransition = buildLayoutTransition();
             chartTransition.setStartDelay(LayoutTransition.APPEARING, 0);
             chartTransition.setStartDelay(LayoutTransition.DISAPPEARING, 0);
+            chartTransition.setAnimator(LayoutTransition.APPEARING, null);
             chartTransition.setAnimator(LayoutTransition.DISAPPEARING, null);
             mChart.setLayoutTransition(chartTransition);
         }
@@ -536,17 +556,14 @@
         mTabWidget.setVisibility(multipleTabs ? View.VISIBLE : View.GONE);
         if (mIntentTab != null) {
             if (Objects.equal(mIntentTab, mTabHost.getCurrentTabTag())) {
+                // already hit updateBody() when added; ignore
                 updateBody();
             } else {
                 mTabHost.setCurrentTabByTag(mIntentTab);
             }
             mIntentTab = null;
         } else {
-            if (mTabHost.getCurrentTab() == 0) {
-                updateBody();
-            } else {
-                mTabHost.setCurrentTab(0);
-            }
+            // already hit updateBody() when added; ignore
         }
     }
 
@@ -583,6 +600,7 @@
      */
     private void updateBody() {
         mBinding = true;
+        if (!isAdded()) return;
 
         final Context context = getActivity();
         final String currentTab = mTabHost.getCurrentTabTag();
@@ -636,25 +654,14 @@
             throw new IllegalStateException("unknown tab: " + currentTab);
         }
 
-        try {
-            // load stats for current template
-            mHistory = mStatsService.getHistoryForNetwork(
-                    mTemplate, FIELD_RX_BYTES | FIELD_TX_BYTES);
-        } catch (RemoteException e) {
-            // since we can't do much without policy or history, and we don't
-            // want to leave with half-baked UI, we bail hard.
-            throw new RuntimeException("problem reading network policy or stats", e);
-        }
+        // kick off loader for network history
+        // TODO: consider chaining two loaders together instead of reloading
+        // network history when showing app detail.
+        getLoaderManager().restartLoader(LOADER_CHART_DATA,
+                ChartDataLoader.buildArgs(mTemplate, mAppDetailUids), mChartDataCallbacks);
 
-        // bind chart to historical stats
-        mChart.bindNetworkStats(mHistory);
-
-        // only update policy when switching tabs
-        updatePolicy(tabChanged);
-        updateAppDetail();
-
-        // force scroll to top of body
-        mListView.smoothScrollToPosition(0);
+        // detail mode can change visible menus, invalidate
+        getActivity().invalidateOptionsMenu();
 
         mBinding = false;
     }
@@ -683,10 +690,6 @@
             mAppDetail.setVisibility(View.GONE);
             mCycleAdapter.setChangeVisible(true);
 
-            mDetailHistory = null;
-            mDetailHistoryDefault = null;
-            mDetailHistoryForeground = null;
-
             // hide detail stats when not in detail mode
             mChart.bindDetailNetworkStats(null);
             return;
@@ -697,7 +700,7 @@
 
         // show icon and all labels appearing under this app
         final int primaryUid = getAppDetailPrimaryUid();
-        final UidDetail detail = resolveDetailForUid(context, primaryUid);
+        final UidDetail detail = mUidDetailProvider.getUidDetail(primaryUid, true);
         mAppIcon.setImageDrawable(detail.icon);
 
         mAppTitles.removeAllViews();
@@ -725,7 +728,6 @@
             mAppSettings.setEnabled(false);
         }
 
-        updateDetailHistory();
         updateDetailData();
 
         if (NetworkPolicyManager.isUidValidForPolicy(context, primaryUid)
@@ -743,54 +745,6 @@
         }
     }
 
-    /**
-     * Update {@link #mDetailHistory} and related values based on
-     * {@link #mAppDetailUids}.
-     */
-    private void updateDetailHistory() {
-        try {
-            mDetailHistoryDefault = null;
-            mDetailHistoryForeground = null;
-
-            // load stats for current uid and template
-            for (int uid : mAppDetailUids) {
-                mDetailHistoryDefault = collectHistoryForUid(
-                        uid, SET_DEFAULT, mDetailHistoryDefault);
-                mDetailHistoryForeground = collectHistoryForUid(
-                        uid, SET_FOREGROUND, mDetailHistoryForeground);
-            }
-        } catch (RemoteException e) {
-            // since we can't do much without history, and we don't want to
-            // leave with half-baked UI, we bail hard.
-            throw new RuntimeException("problem reading network stats", e);
-        }
-
-        mDetailHistory = new NetworkStatsHistory(mDetailHistoryForeground.getBucketDuration());
-        mDetailHistory.recordEntireHistory(mDetailHistoryDefault);
-        mDetailHistory.recordEntireHistory(mDetailHistoryForeground);
-
-        // bind chart to historical stats
-        mChart.bindDetailNetworkStats(mDetailHistory);
-    }
-
-    /**
-     * Collect {@link NetworkStatsHistory} for the requested UID, combining with
-     * an existing {@link NetworkStatsHistory} if provided.
-     */
-    private NetworkStatsHistory collectHistoryForUid(
-            int uid, int set, NetworkStatsHistory existing)
-            throws RemoteException {
-        final NetworkStatsHistory history = mStatsService.getHistoryForUid(
-                mTemplate, uid, set, TAG_NONE, FIELD_RX_BYTES | FIELD_TX_BYTES);
-
-        if (existing != null) {
-            existing.recordEntireHistory(history);
-            return existing;
-        } else {
-            return history;
-        }
-    }
-
     private void setPolicyCycleDay(int cycleDay) {
         if (LOGD) Log.d(TAG, "setPolicyCycleDay()");
         mPolicyEditor.setPolicyCycleDay(mTemplate, cycleDay);
@@ -956,21 +910,22 @@
         mCycleAdapter.clear();
 
         final Context context = mCycleSpinner.getContext();
-        long historyStart = mHistory.getStart();
-        long historyEnd = mHistory.getEnd();
 
-        if (historyStart == Long.MAX_VALUE || historyEnd == Long.MIN_VALUE) {
-            historyStart = System.currentTimeMillis();
-            historyEnd = System.currentTimeMillis();
+        long historyStart = Long.MAX_VALUE;
+        long historyEnd = Long.MIN_VALUE;
+        if (mChartData != null) {
+            historyStart = mChartData.network.getStart();
+            historyEnd = mChartData.network.getEnd();
         }
 
+        if (historyStart == Long.MAX_VALUE) historyStart = System.currentTimeMillis();
+        if (historyEnd == Long.MIN_VALUE) historyEnd = System.currentTimeMillis();
+
         boolean hasCycles = false;
         if (policy != null) {
             // find the next cycle boundary
             long cycleEnd = computeNextCycleBoundary(historyEnd, policy);
 
-            int guardCount = 0;
-
             // walk backwards, generating all valid cycle ranges
             while (cycleEnd > historyStart) {
                 final long cycleStart = computeLastCycleBoundary(cycleEnd, policy);
@@ -979,12 +934,6 @@
                 mCycleAdapter.add(new CycleItem(context, cycleStart, cycleEnd));
                 cycleEnd = cycleStart;
                 hasCycles = true;
-
-                // TODO: remove this guard once we have better testing
-                if (guardCount++ > 50) {
-                    Log.wtf(TAG, "stuck generating ranges for historyStart=" + historyStart
-                            + ", historyEnd=" + historyEnd + " and policy=" + policy);
-                }
             }
 
             // one last cycle entry to modify policy cycle day
@@ -1075,7 +1024,7 @@
         public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
             final Context context = view.getContext();
             final AppUsageItem app = (AppUsageItem) parent.getItemAtPosition(position);
-            final UidDetail detail = resolveDetailForUid(context, app.uids[0]);
+            final UidDetail detail = mUidDetailProvider.getUidDetail(app.uids[0], true);
             AppDetailsFragment.show(DataUsageSummary.this, app.uids, detail.label);
         }
     };
@@ -1128,11 +1077,11 @@
         final Context context = getActivity();
 
         NetworkStatsHistory.Entry entry = null;
-        if (isAppDetailMode() && mDetailHistory != null) {
+        if (isAppDetailMode() && mChartData != null && mChartData.detail != null) {
             // bind foreground/background to piechart and labels
-            entry = mDetailHistoryDefault.getValues(start, end, now, entry);
+            entry = mChartData.detailDefault.getValues(start, end, now, entry);
             final long defaultBytes = entry.rxBytes + entry.txBytes;
-            entry = mDetailHistoryForeground.getValues(start, end, now, entry);
+            entry = mChartData.detailForeground.getValues(start, end, now, entry);
             final long foregroundBytes = entry.rxBytes + entry.txBytes;
 
             mAppPieChart.setOriginAngle(175);
@@ -1147,17 +1096,18 @@
             mAppForeground.setText(Formatter.formatFileSize(context, foregroundBytes));
 
             // and finally leave with summary data for label below
-            entry = mDetailHistory.getValues(start, end, now, null);
+            entry = mChartData.detail.getValues(start, end, now, null);
 
             getLoaderManager().destroyLoader(LOADER_SUMMARY);
 
         } else {
-            entry = mHistory.getValues(start, end, now, null);
+            if (mChartData != null) {
+                entry = mChartData.network.getValues(start, end, now, null);
+            }
 
             // kick off loader for detailed stats
-            // TODO: delay loader until animation is finished
             getLoaderManager().restartLoader(LOADER_SUMMARY,
-                    SummaryForAllUidLoader.buildArgs(mTemplate, start, end), mSummaryForAllUid);
+                    SummaryForAllUidLoader.buildArgs(mTemplate, start, end), mSummaryCallbacks);
         }
 
         final long totalBytes = entry != null ? entry.rxBytes + entry.txBytes : 0;
@@ -1168,7 +1118,38 @@
                 getString(R.string.data_usage_total_during_range, totalPhrase, rangePhrase));
     }
 
-    private final LoaderCallbacks<NetworkStats> mSummaryForAllUid = new LoaderCallbacks<
+    private final LoaderCallbacks<ChartData> mChartDataCallbacks = new LoaderCallbacks<
+            ChartData>() {
+        /** {@inheritDoc} */
+        public Loader<ChartData> onCreateLoader(int id, Bundle args) {
+            return new ChartDataLoader(getActivity(), mStatsService, args);
+        }
+
+        /** {@inheritDoc} */
+        public void onLoadFinished(Loader<ChartData> loader, ChartData data) {
+            mChartData = data;
+            mChart.bindNetworkStats(mChartData.network);
+            mChart.bindDetailNetworkStats(mChartData.detail);
+
+            // calcuate policy cycles based on available data
+            updatePolicy(true);
+            updateAppDetail();
+
+            // force scroll to top of body when showing detail
+            if (mChartData.detail != null) {
+                mListView.smoothScrollToPosition(0);
+            }
+        }
+
+        /** {@inheritDoc} */
+        public void onLoaderReset(Loader<ChartData> loader) {
+            mChartData = null;
+            mChart.bindNetworkStats(null);
+            mChart.bindDetailNetworkStats(null);
+        }
+    };
+
+    private final LoaderCallbacks<NetworkStats> mSummaryCallbacks = new LoaderCallbacks<
             NetworkStats>() {
         /** {@inheritDoc} */
         public Loader<NetworkStats> onCreateLoader(int id, Bundle args) {
@@ -1337,9 +1318,17 @@
      * Adapter of applications, sorted by total usage descending.
      */
     public static class DataUsageAdapter extends BaseAdapter {
+        private final UidDetailProvider mProvider;
+        private final int mInsetSide;
+
         private ArrayList<AppUsageItem> mItems = Lists.newArrayList();
         private long mLargest;
 
+        public DataUsageAdapter(UidDetailProvider provider, int insetSide) {
+            mProvider = checkNotNull(provider);
+            mInsetSide = insetSide;
+        }
+
         /**
          * Bind the given {@link NetworkStats}, or {@code null} to clear list.
          */
@@ -1401,21 +1390,22 @@
             if (convertView == null) {
                 convertView = LayoutInflater.from(parent.getContext()).inflate(
                         R.layout.data_usage_item, parent, false);
+
+                if (mInsetSide > 0) {
+                    convertView.setPadding(mInsetSide, 0, mInsetSide, 0);
+                }
             }
 
             final Context context = parent.getContext();
 
-            final ImageView icon = (ImageView) convertView.findViewById(android.R.id.icon);
-            final TextView title = (TextView) convertView.findViewById(android.R.id.title);
             final TextView text1 = (TextView) convertView.findViewById(android.R.id.text1);
             final ProgressBar progress = (ProgressBar) convertView.findViewById(
                     android.R.id.progress);
 
+            // kick off async load of app details
             final AppUsageItem item = mItems.get(position);
-            final UidDetail detail = resolveDetailForUid(context, item.uids[0]);
+            UidDetailTask.bindView(mProvider, item, convertView);
 
-            icon.setImageDrawable(detail.icon);
-            title.setText(detail.label);
             text1.setText(Formatter.formatFileSize(context, item.total));
 
             final int percentTotal = mLargest != 0 ? (int) (item.total * 100 / mLargest) : 0;
@@ -1745,66 +1735,64 @@
         }
     }
 
-    public static class UidDetail {
-        public CharSequence label;
-        public CharSequence[] detailLabels;
-        public Drawable icon;
-    }
-
     /**
-     * Resolve best descriptive label for the given UID.
+     * Background task that loads {@link UidDetail}, binding to
+     * {@link DataUsageAdapter} row item when finished.
      */
-    public static UidDetail resolveDetailForUid(Context context, int uid) {
-        final Resources res = context.getResources();
-        final PackageManager pm = context.getPackageManager();
+    private static class UidDetailTask extends AsyncTask<Void, Void, UidDetail> {
+        private final UidDetailProvider mProvider;
+        private final AppUsageItem mItem;
+        private final View mTarget;
 
-        final UidDetail detail = new UidDetail();
-        detail.label = pm.getNameForUid(uid);
-        detail.icon = pm.getDefaultActivityIcon();
-
-        // handle special case labels
-        switch (uid) {
-            case android.os.Process.SYSTEM_UID:
-                detail.label = res.getString(R.string.process_kernel_label);
-                detail.icon = pm.getDefaultActivityIcon();
-                return detail;
-            case TrafficStats.UID_REMOVED:
-                detail.label = res.getString(R.string.data_usage_uninstalled_apps);
-                detail.icon = pm.getDefaultActivityIcon();
-                return detail;
+        private UidDetailTask(UidDetailProvider provider, AppUsageItem item, View target) {
+            mProvider = checkNotNull(provider);
+            mItem = checkNotNull(item);
+            mTarget = checkNotNull(target);
         }
 
-        // 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);
-                detail.label = info.loadLabel(pm).toString();
-                detail.icon = info.loadIcon(pm);
-            } else if (length > 1) {
-                detail.detailLabels = new CharSequence[length];
-                for (int i = 0; i < length; i++) {
-                    final String packageName = packageNames[i];
-                    final PackageInfo packageInfo = pm.getPackageInfo(packageName, 0);
-                    final ApplicationInfo appInfo = pm.getApplicationInfo(packageName, 0);
-
-                    detail.detailLabels[i] = appInfo.loadLabel(pm).toString();
-                    if (packageInfo.sharedUserLabel != 0) {
-                        detail.label = pm.getText(packageName, packageInfo.sharedUserLabel,
-                                packageInfo.applicationInfo).toString();
-                        detail.icon = appInfo.loadIcon(pm);
-                    }
-                }
+        public static void bindView(
+                UidDetailProvider provider, AppUsageItem item, View target) {
+            final UidDetailTask existing = (UidDetailTask) target.getTag();
+            if (existing != null) {
+                existing.cancel(false);
             }
-        } catch (NameNotFoundException e) {
+
+            final UidDetail cachedDetail = provider.getUidDetail(item.uids[0], false);
+            if (cachedDetail != null) {
+                bindView(cachedDetail, target);
+            } else {
+                target.setTag(new UidDetailTask(provider, item, target).executeOnExecutor(
+                        AsyncTask.THREAD_POOL_EXECUTOR));
+            }
         }
 
-        if (TextUtils.isEmpty(detail.label)) {
-            detail.label = Integer.toString(uid);
+        private static void bindView(UidDetail detail, View target) {
+            final ImageView icon = (ImageView) target.findViewById(android.R.id.icon);
+            final TextView title = (TextView) target.findViewById(android.R.id.title);
+
+            if (detail != null) {
+                icon.setImageDrawable(detail.icon);
+                title.setText(detail.label);
+            } else {
+                icon.setImageDrawable(null);
+                title.setText(null);
+            }
         }
-        return detail;
+
+        @Override
+        protected void onPreExecute() {
+            bindView(null, mTarget);
+        }
+
+        @Override
+        protected UidDetail doInBackground(Void... params) {
+            return mProvider.getUidDetail(mItem.uids[0], true);
+        }
+
+        @Override
+        protected void onPostExecute(UidDetail result) {
+            bindView(result, mTarget);
+        }
     }
 
     /**
@@ -1827,6 +1815,9 @@
      * Test if device has a mobile 4G data radio.
      */
     private static boolean hasMobile4gRadio(Context context) {
+        if (!NetworkPolicyEditor.ENABLE_SPLIT_POLICIES) {
+            return false;
+        }
         if (TEST_RADIOS) {
             return SystemProperties.get(TEST_RADIOS_PROP).contains("4g");
         }
diff --git a/src/com/android/settings/SoundSettings.java b/src/com/android/settings/SoundSettings.java
index d1f8247..c200c63 100644
--- a/src/com/android/settings/SoundSettings.java
+++ b/src/com/android/settings/SoundSettings.java
@@ -191,7 +191,8 @@
         mNotificationPulse = (CheckBoxPreference)
                 mSoundSettings.findPreference(KEY_NOTIFICATION_PULSE);
         if (mNotificationPulse != null
-                && getResources().getBoolean(R.bool.has_intrusive_led) == false) {
+                && getResources().getBoolean(
+                        com.android.internal.R.bool.config_intrusiveNotificationLed) == false) {
             mSoundSettings.removePreference(mNotificationPulse);
         } else {
             try {
diff --git a/src/com/android/settings/inputmethod/InputMethodPreference.java b/src/com/android/settings/inputmethod/InputMethodPreference.java
index f490fd2..5cc7ad0 100644
--- a/src/com/android/settings/inputmethod/InputMethodPreference.java
+++ b/src/com/android/settings/inputmethod/InputMethodPreference.java
@@ -97,7 +97,19 @@
                 });
         mInputMethodSettingsButton = (ImageView)view.findViewById(R.id.inputmethod_settings);
         mTitleText = (TextView)view.findViewById(android.R.id.title);
+
+        final boolean hasSubtypes = mImi.getSubtypeCount() > 1;
+        final String imiId = mImi.getId();
         mSummaryText = (TextView)view.findViewById(android.R.id.summary);
+        mSummaryText.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View arg0) {
+                final Bundle bundle = new Bundle();
+                bundle.putString(Settings.EXTRA_INPUT_METHOD_ID, imiId);
+                startFragment(mFragment, InputMethodAndSubtypeEnabler.class.getName(),
+                        0, bundle);
+            }
+        });
         if (mSettingsIntent != null) {
             mInputMethodSettingsButton.setOnClickListener(
                     new OnClickListener() {
@@ -113,8 +125,6 @@
                         }
                     });
         }
-        final boolean hasSubtypes = mImi.getSubtypeCount() > 1;
-        final String imiId = mImi.getId();
         if (hasSubtypes) {
             final OnLongClickListener listener = new OnLongClickListener() {
                 @Override
@@ -142,8 +152,8 @@
     }
 
     private void enableSettingsButton() {
+        final boolean checked = isChecked();
         if (mInputMethodSettingsButton != null) {
-            final boolean checked = isChecked();
             mInputMethodSettingsButton.setEnabled(checked);
             mInputMethodSettingsButton.setClickable(checked);
             mInputMethodSettingsButton.setFocusable(checked);
@@ -155,7 +165,8 @@
             mTitleText.setEnabled(true);
         }
         if (mSummaryText != null) {
-            mSummaryText.setEnabled(true);
+            mSummaryText.setEnabled(checked);
+            mSummaryText.setClickable(checked);
         }
     }
 
diff --git a/src/com/android/settings/net/ChartData.java b/src/com/android/settings/net/ChartData.java
new file mode 100644
index 0000000..0b8969e
--- /dev/null
+++ b/src/com/android/settings/net/ChartData.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2011 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.net;
+
+import android.net.NetworkStatsHistory;
+
+public class ChartData {
+    public NetworkStatsHistory network;
+
+    public NetworkStatsHistory detail;
+    public NetworkStatsHistory detailDefault;
+    public NetworkStatsHistory detailForeground;
+}
diff --git a/src/com/android/settings/net/ChartDataLoader.java b/src/com/android/settings/net/ChartDataLoader.java
new file mode 100644
index 0000000..09e6e3b
--- /dev/null
+++ b/src/com/android/settings/net/ChartDataLoader.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2011 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.net;
+
+import static android.net.NetworkStats.SET_DEFAULT;
+import static android.net.NetworkStats.SET_FOREGROUND;
+import static android.net.NetworkStats.TAG_NONE;
+import static android.net.NetworkStatsHistory.FIELD_RX_BYTES;
+import static android.net.NetworkStatsHistory.FIELD_TX_BYTES;
+
+import android.content.AsyncTaskLoader;
+import android.content.Context;
+import android.net.INetworkStatsService;
+import android.net.NetworkStatsHistory;
+import android.net.NetworkTemplate;
+import android.os.Bundle;
+import android.os.RemoteException;
+
+/**
+ * Loader for historical chart data for both network and UID details.
+ */
+public class ChartDataLoader extends AsyncTaskLoader<ChartData> {
+    private static final String KEY_TEMPLATE = "template";
+    private static final String KEY_UIDS = "uids";
+    private static final String KEY_FIELDS = "fields";
+
+    private final INetworkStatsService mStatsService;
+    private final Bundle mArgs;
+
+    public static Bundle buildArgs(NetworkTemplate template, int[] uids) {
+        return buildArgs(template, uids, FIELD_RX_BYTES | FIELD_TX_BYTES);
+    }
+
+    public static Bundle buildArgs(NetworkTemplate template, int[] uids, int fields) {
+        final Bundle args = new Bundle();
+        args.putParcelable(KEY_TEMPLATE, template);
+        args.putIntArray(KEY_UIDS, uids);
+        args.putInt(KEY_FIELDS, fields);
+        return args;
+    }
+
+    public ChartDataLoader(Context context, INetworkStatsService statsService, Bundle args) {
+        super(context);
+        mStatsService = statsService;
+        mArgs = args;
+    }
+
+    @Override
+    protected void onStartLoading() {
+        super.onStartLoading();
+        forceLoad();
+    }
+
+    @Override
+    public ChartData loadInBackground() {
+        final NetworkTemplate template = mArgs.getParcelable(KEY_TEMPLATE);
+        final int[] uids = mArgs.getIntArray(KEY_UIDS);
+        final int fields = mArgs.getInt(KEY_FIELDS);
+
+        try {
+            return loadInBackground(template, uids, fields);
+        } catch (RemoteException e) {
+            // since we can't do much without history, and we don't want to
+            // leave with half-baked UI, we bail hard.
+            throw new RuntimeException("problem reading network stats", e);
+        }
+    }
+
+    private ChartData loadInBackground(NetworkTemplate template, int[] uids, int fields)
+            throws RemoteException {
+        final ChartData data = new ChartData();
+        data.network = mStatsService.getHistoryForNetwork(template, fields);
+
+        if (uids != null) {
+            data.detailDefault = null;
+            data.detailForeground = null;
+
+            // load stats for current uid and template
+            for (int uid : uids) {
+                data.detailDefault = collectHistoryForUid(
+                        template, uid, SET_DEFAULT, data.detailDefault);
+                data.detailForeground = collectHistoryForUid(
+                        template, uid, SET_FOREGROUND, data.detailForeground);
+            }
+
+            data.detail = new NetworkStatsHistory(data.detailForeground.getBucketDuration());
+            data.detail.recordEntireHistory(data.detailDefault);
+            data.detail.recordEntireHistory(data.detailForeground);
+        }
+
+        return data;
+    }
+
+    @Override
+    protected void onStopLoading() {
+        super.onStopLoading();
+        cancelLoad();
+    }
+
+    @Override
+    protected void onReset() {
+        super.onReset();
+        cancelLoad();
+    }
+
+    /**
+     * Collect {@link NetworkStatsHistory} for the requested UID, combining with
+     * an existing {@link NetworkStatsHistory} if provided.
+     */
+    private NetworkStatsHistory collectHistoryForUid(
+            NetworkTemplate template, int uid, int set, NetworkStatsHistory existing)
+            throws RemoteException {
+        final NetworkStatsHistory history = mStatsService.getHistoryForUid(
+                template, uid, set, TAG_NONE, FIELD_RX_BYTES | FIELD_TX_BYTES);
+
+        if (existing != null) {
+            existing.recordEntireHistory(history);
+            return existing;
+        } else {
+            return history;
+        }
+    }
+}
diff --git a/src/com/android/settings/net/NetworkPolicyEditor.java b/src/com/android/settings/net/NetworkPolicyEditor.java
index 161e6ee..bb5a2c3 100644
--- a/src/com/android/settings/net/NetworkPolicyEditor.java
+++ b/src/com/android/settings/net/NetworkPolicyEditor.java
@@ -36,8 +36,10 @@
 
 import com.android.internal.util.Objects;
 import com.google.android.collect.Lists;
+import com.google.android.collect.Sets;
 
 import java.util.ArrayList;
+import java.util.HashSet;
 
 /**
  * Utility class to modify list of {@link NetworkPolicy}. Specifically knows
@@ -46,6 +48,8 @@
 public class NetworkPolicyEditor {
     // TODO: be more robust when missing policies from service
 
+    public static final boolean ENABLE_SPLIT_POLICIES = false;
+
     private INetworkPolicyManager mPolicyService;
     private ArrayList<NetworkPolicy> mPolicies = Lists.newArrayList();
 
@@ -83,6 +87,11 @@
             mPolicies.add(policy);
         }
 
+        // force combine any split policies when disabled
+        if (!ENABLE_SPLIT_POLICIES) {
+            modified |= forceMobilePolicyCombined();
+        }
+
         // when we cleaned policies above, write back changes
         if (modified) writeAsync();
     }
@@ -161,6 +170,22 @@
         writeAsync();
     }
 
+    /**
+     * Remove any split {@link NetworkPolicy}.
+     */
+    private boolean forceMobilePolicyCombined() {
+        final HashSet<String> subscriberIds = Sets.newHashSet();
+        for (NetworkPolicy policy : mPolicies) {
+            subscriberIds.add(policy.template.getSubscriberId());
+        }
+
+        boolean modified = false;
+        for (String subscriberId : subscriberIds) {
+            modified |= setMobilePolicySplitInternal(subscriberId, false);
+        }
+        return modified;
+    }
+
     public boolean isMobilePolicySplit(String subscriberId) {
         boolean has3g = false;
         boolean has4g = false;
@@ -181,6 +206,18 @@
     }
 
     public void setMobilePolicySplit(String subscriberId, boolean split) {
+        if (setMobilePolicySplitInternal(subscriberId, split)) {
+            writeAsync();
+        }
+    }
+
+    /**
+     * Mutate {@link NetworkPolicy} for given subscriber, combining or splitting
+     * the policy as requested.
+     *
+     * @return {@code true} when any {@link NetworkPolicy} was mutated.
+     */
+    private boolean setMobilePolicySplitInternal(String subscriberId, boolean split) {
         final boolean beforeSplit = isMobilePolicySplit(subscriberId);
 
         final NetworkTemplate template3g = buildTemplateMobile3gLower(subscriberId);
@@ -189,7 +226,7 @@
 
         if (split == beforeSplit) {
             // already in requested state; skip
-            return;
+            return false;
 
         } else if (beforeSplit && !split) {
             // combine, picking most restrictive policy
@@ -203,7 +240,7 @@
             mPolicies.add(
                     new NetworkPolicy(templateAll, restrictive.cycleDay, restrictive.warningBytes,
                             restrictive.limitBytes, SNOOZE_NEVER));
-            writeAsync();
+            return true;
 
         } else if (!beforeSplit && split) {
             // duplicate existing policy into two rules
@@ -215,8 +252,9 @@
             mPolicies.add(
                     new NetworkPolicy(template4g, policyAll.cycleDay, policyAll.warningBytes,
                             policyAll.limitBytes, SNOOZE_NEVER));
-            writeAsync();
-
+            return true;
+        } else {
+            return false;
         }
     }
 }
diff --git a/src/com/android/settings/net/UidDetail.java b/src/com/android/settings/net/UidDetail.java
new file mode 100644
index 0000000..fd44d47
--- /dev/null
+++ b/src/com/android/settings/net/UidDetail.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2011 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.net;
+
+import android.graphics.drawable.Drawable;
+
+public class UidDetail {
+    public CharSequence label;
+    public CharSequence[] detailLabels;
+    public Drawable icon;
+}
diff --git a/src/com/android/settings/net/UidDetailProvider.java b/src/com/android/settings/net/UidDetailProvider.java
new file mode 100644
index 0000000..9eac801
--- /dev/null
+++ b/src/com/android/settings/net/UidDetailProvider.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2011 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.net;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.Resources;
+import android.net.TrafficStats;
+import android.text.TextUtils;
+import android.util.SparseArray;
+
+import com.android.settings.R;
+
+public class UidDetailProvider {
+    private final Context mContext;
+    private final SparseArray<UidDetail> mUidDetailCache;
+
+    public UidDetailProvider(Context context) {
+        mContext = context.getApplicationContext();
+        mUidDetailCache = new SparseArray<UidDetail>();
+    }
+
+    public void clearCache() {
+        mUidDetailCache.clear();
+    }
+
+    /**
+     * Resolve best descriptive label for the given UID.
+     */
+    public UidDetail getUidDetail(int uid, boolean blocking) {
+        final UidDetail cached = mUidDetailCache.get(uid);
+        if (cached != null) {
+            return cached;
+        } else if (!blocking) {
+            return null;
+        }
+
+        final Resources res = mContext.getResources();
+        final PackageManager pm = mContext.getPackageManager();
+
+        final UidDetail detail = new UidDetail();
+        detail.label = pm.getNameForUid(uid);
+        detail.icon = pm.getDefaultActivityIcon();
+
+        // handle special case labels
+        switch (uid) {
+            case android.os.Process.SYSTEM_UID:
+                detail.label = res.getString(R.string.process_kernel_label);
+                detail.icon = pm.getDefaultActivityIcon();
+                mUidDetailCache.put(uid, detail);
+                return detail;
+            case TrafficStats.UID_REMOVED:
+                detail.label = res.getString(R.string.data_usage_uninstalled_apps);
+                detail.icon = pm.getDefaultActivityIcon();
+                mUidDetailCache.put(uid, detail);
+                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);
+                detail.label = info.loadLabel(pm).toString();
+                detail.icon = info.loadIcon(pm);
+            } else if (length > 1) {
+                detail.detailLabels = new CharSequence[length];
+                for (int i = 0; i < length; i++) {
+                    final String packageName = packageNames[i];
+                    final PackageInfo packageInfo = pm.getPackageInfo(packageName, 0);
+                    final ApplicationInfo appInfo = pm.getApplicationInfo(packageName, 0);
+
+                    detail.detailLabels[i] = appInfo.loadLabel(pm).toString();
+                    if (packageInfo.sharedUserLabel != 0) {
+                        detail.label = pm.getText(packageName, packageInfo.sharedUserLabel,
+                                packageInfo.applicationInfo).toString();
+                        detail.icon = appInfo.loadIcon(pm);
+                    }
+                }
+            }
+        } catch (NameNotFoundException e) {
+        }
+
+        if (TextUtils.isEmpty(detail.label)) {
+            detail.label = Integer.toString(uid);
+        }
+
+        mUidDetailCache.put(uid, detail);
+        return detail;
+    }
+}
diff --git a/src/com/android/settings/tts/TextToSpeechSettings.java b/src/com/android/settings/tts/TextToSpeechSettings.java
index e8255bf..517eade 100644
--- a/src/com/android/settings/tts/TextToSpeechSettings.java
+++ b/src/com/android/settings/tts/TextToSpeechSettings.java
@@ -198,9 +198,13 @@
                     currentEngine);
             if (localeString != null) {
                 final String[] locale = TtsEngines.parseLocalePref(localeString);
+                final Locale newLocale = new Locale(locale[0], locale[1], locale[2]);
+                final Locale engineLocale = mTts.getLanguage();
 
-                if (DBG) Log.d(TAG, "Loading language ahead of sample check : " + locale);
-                mTts.setLanguage(new Locale(locale[0], locale[1], locale[2]));
+                if (!newLocale.equals(engineLocale)) {
+                    if (DBG) Log.d(TAG, "Loading language ahead of sample check : " + locale);
+                    mTts.setLanguage(newLocale);
+                }
             }
         }
     }
diff --git a/src/com/android/settings/widget/ChartAxis.java b/src/com/android/settings/widget/ChartAxis.java
index d3d499c..515fe66 100644
--- a/src/com/android/settings/widget/ChartAxis.java
+++ b/src/com/android/settings/widget/ChartAxis.java
@@ -26,9 +26,9 @@
 public interface ChartAxis {
 
     /** Set range of raw values this axis should cover. */
-    public void setBounds(long min, long max);
+    public boolean setBounds(long min, long max);
     /** Set range of screen points this axis should cover. */
-    public void setSize(float size);
+    public boolean setSize(float size);
 
     /** Convert raw value into screen point. */
     public float convertToPoint(long value);
diff --git a/src/com/android/settings/widget/ChartDataUsageView.java b/src/com/android/settings/widget/ChartDataUsageView.java
index cb9c8d7..e831cc1 100644
--- a/src/com/android/settings/widget/ChartDataUsageView.java
+++ b/src/com/android/settings/widget/ChartDataUsageView.java
@@ -30,6 +30,7 @@
 import android.view.MotionEvent;
 import android.view.View;
 
+import com.android.internal.util.Objects;
 import com.android.settings.R;
 import com.android.settings.widget.ChartSweepView.OnSweepListener;
 
@@ -214,7 +215,8 @@
 
         // always show known data and policy lines
         final long maxSweep = Math.max(mSweepWarning.getValue(), mSweepLimit.getValue());
-        final long maxVisible = Math.max(mSeries.getMaxVisible(), maxSweep) * 12 / 10;
+        final long maxSeries = Math.max(mSeries.getMaxVisible(), mDetailSeries.getMaxVisible());
+        final long maxVisible = Math.max(maxSeries, maxSweep) * 12 / 10;
         final long maxDefault = Math.max(maxVisible, 2 * GB_IN_BYTES);
         newMax = Math.max(maxDefault, newMax);
 
@@ -222,12 +224,14 @@
         if (newMax != mVertMax) {
             mVertMax = newMax;
 
-            mVert.setBounds(0L, newMax);
+            final boolean changed = mVert.setBounds(0L, newMax);
             mSweepWarning.setValidRange(0L, newMax);
             mSweepLimit.setValidRange(0L, newMax);
 
-            mSeries.generatePath();
-            mDetailSeries.generatePath();
+            if (changed) {
+                mSeries.invalidatePath();
+                mDetailSeries.invalidatePath();
+            }
 
             mGrid.invalidate();
 
@@ -263,6 +267,10 @@
             interestLine = mSweepLimit.getValue();
         }
 
+        if (interestLine < 0) {
+            interestLine = Long.MAX_VALUE;
+        }
+
         final boolean estimateVisible = (maxEstimate >= interestLine * 7 / 10);
         mSeries.setEstimateVisible(estimateVisible);
     }
@@ -354,8 +362,10 @@
      * last "week" of available data, without triggering listener events.
      */
     public void setVisibleRange(long visibleStart, long visibleEnd) {
-        mHoriz.setBounds(visibleStart, visibleEnd);
+        final boolean changed = mHoriz.setBounds(visibleStart, visibleEnd);
         mGrid.setBounds(visibleStart, visibleEnd);
+        mSeries.setBounds(visibleStart, visibleEnd);
+        mDetailSeries.setBounds(visibleStart, visibleEnd);
 
         final long validStart = Math.max(visibleStart, getStatsStart());
         final long validEnd = Math.min(visibleEnd, getStatsEnd());
@@ -378,7 +388,10 @@
         mSweepRight.setValue(sweepMax);
 
         requestLayout();
-        mSeries.generatePath();
+        if (changed) {
+            mSeries.invalidatePath();
+            mDetailSeries.invalidatePath();
+        }
 
         updateVertAxisBounds(null);
         updateEstimateVisible();
@@ -410,15 +423,30 @@
             setBounds(currentTime - DateUtils.DAY_IN_MILLIS * 30, currentTime);
         }
 
-        /** {@inheritDoc} */
-        public void setBounds(long min, long max) {
-            mMin = min;
-            mMax = max;
+        @Override
+        public int hashCode() {
+            return Objects.hashCode(mMin, mMax, mSize);
         }
 
         /** {@inheritDoc} */
-        public void setSize(float size) {
-            this.mSize = size;
+        public boolean setBounds(long min, long max) {
+            if (mMin != min || mMax != max) {
+                mMin = min;
+                mMax = max;
+                return true;
+            } else {
+                return false;
+            }
+        }
+
+        /** {@inheritDoc} */
+        public boolean setSize(float size) {
+            if (mSize != size) {
+                mSize = size;
+                return true;
+            } else {
+                return false;
+            }
         }
 
         /** {@inheritDoc} */
@@ -461,15 +489,30 @@
         private long mMax;
         private float mSize;
 
-        /** {@inheritDoc} */
-        public void setBounds(long min, long max) {
-            mMin = min;
-            mMax = max;
+        @Override
+        public int hashCode() {
+            return Objects.hashCode(mMin, mMax, mSize);
         }
 
         /** {@inheritDoc} */
-        public void setSize(float size) {
-            mSize = size;
+        public boolean setBounds(long min, long max) {
+            if (mMin != min || mMax != max) {
+                mMin = min;
+                mMax = max;
+                return true;
+            } else {
+                return false;
+            }
+        }
+
+        /** {@inheritDoc} */
+        public boolean setSize(float size) {
+            if (mSize != size) {
+                mSize = size;
+                return true;
+            } else {
+                return false;
+            }
         }
 
         /** {@inheritDoc} */
diff --git a/src/com/android/settings/widget/ChartNetworkSeriesView.java b/src/com/android/settings/widget/ChartNetworkSeriesView.java
index f0ccc1b..c4f45b0 100644
--- a/src/com/android/settings/widget/ChartNetworkSeriesView.java
+++ b/src/com/android/settings/widget/ChartNetworkSeriesView.java
@@ -58,12 +58,16 @@
     private Path mPathFill;
     private Path mPathEstimate;
 
+    private long mStart;
+    private long mEnd;
+
     private long mPrimaryLeft;
     private long mPrimaryRight;
 
     /** Series will be extended to reach this end time. */
     private long mEndTime = Long.MIN_VALUE;
 
+    private boolean mPathValid = false;
     private boolean mEstimateVisible = false;
 
     private long mMax;
@@ -130,13 +134,15 @@
 
     public void bindNetworkStats(NetworkStatsHistory stats) {
         mStats = stats;
-
-        mPathStroke.reset();
-        mPathFill.reset();
-        mPathEstimate.reset();
+        invalidatePath();
         invalidate();
     }
 
+    public void setBounds(long start, long end) {
+        mStart = start;
+        mEnd = end;
+    }
+
     /**
      * Set the range to paint with {@link #mPaintFill}, leaving the remaining
      * area to be painted with {@link #mPaintFillSecondary}.
@@ -147,26 +153,27 @@
         invalidate();
     }
 
-    @Override
-    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
-        generatePath();
+    public void invalidatePath() {
+        mPathValid = false;
+        mMax = 0;
+        invalidate();
     }
 
     /**
      * Erase any existing {@link Path} and generate series outline based on
      * currently bound {@link NetworkStatsHistory} data.
      */
-    public void generatePath() {
+    private void generatePath() {
         if (LOGD) Log.d(TAG, "generatePath()");
 
         mMax = 0;
         mPathStroke.reset();
         mPathFill.reset();
         mPathEstimate.reset();
+        mPathValid = true;
 
         // bail when not enough stats to render
         if (mStats == null || mStats.size() < 2) {
-            invalidate();
             return;
         }
 
@@ -185,7 +192,10 @@
         long totalData = 0;
 
         NetworkStatsHistory.Entry entry = null;
-        for (int i = 0; i < mStats.size(); i++) {
+
+        final int start = mStats.getIndexBefore(mStart);
+        final int end = mStats.getIndexAfter(mEnd);
+        for (int i = start; i <= end; i++) {
             entry = mStats.getValues(i, entry);
 
             lastTime = entry.bucketStart + entry.bucketDuration;
@@ -206,9 +216,6 @@
                 totalData += entry.rxBytes + entry.txBytes;
             }
 
-            // skip if beyond view
-            if (x > width) break;
-
             lastX = x;
             lastY = y;
         }
@@ -284,13 +291,24 @@
     }
 
     public long getMaxVisible() {
-        return mEstimateVisible ? mMaxEstimate : mMax;
+        final long maxVisible = mEstimateVisible ? mMaxEstimate : mMax;
+        if (maxVisible <= 0 && mStats != null) {
+            // haven't generated path yet; fall back to raw data
+            final NetworkStatsHistory.Entry entry = mStats.getValues(mStart, mEnd, null);
+            return entry.rxBytes + entry.txBytes;
+        } else {
+            return maxVisible;
+        }
     }
 
     @Override
     protected void onDraw(Canvas canvas) {
         int save;
 
+        if (!mPathValid) {
+            generatePath();
+        }
+
         final float primaryLeftPoint = mHoriz.convertToPoint(mPrimaryLeft);
         final float primaryRightPoint = mHoriz.convertToPoint(mPrimaryRight);
 
diff --git a/src/com/android/settings/widget/ChartSweepView.java b/src/com/android/settings/widget/ChartSweepView.java
index 7b6d887..2190588 100644
--- a/src/com/android/settings/widget/ChartSweepView.java
+++ b/src/com/android/settings/widget/ChartSweepView.java
@@ -76,6 +76,8 @@
     private ChartSweepView mValidAfterDynamic;
     private ChartSweepView mValidBeforeDynamic;
 
+    private float mLabelOffset;
+
     private Paint mOutlinePaint = new Paint();
 
     public static final int HORIZONTAL = 0;
@@ -230,12 +232,44 @@
     private void invalidateLabel() {
         if (mLabelTemplate != null && mAxis != null) {
             mLabelValue = mAxis.buildLabel(getResources(), mLabelTemplate, mValue);
+            invalidateLabelOffset();
             invalidate();
         } else {
             mLabelValue = mValue;
         }
     }
 
+    /**
+     * When overlapping with neighbor, split difference and push label.
+     */
+    public void invalidateLabelOffset() {
+        float margin;
+        float labelOffset = 0;
+        if (mFollowAxis == VERTICAL) {
+            if (mValidAfterDynamic != null) {
+                margin = getLabelTop(mValidAfterDynamic) - getLabelBottom(this);
+                if (margin < 0) {
+                    labelOffset = margin / 2;
+                }
+            } else if (mValidBeforeDynamic != null) {
+                margin = getLabelTop(this) - getLabelBottom(mValidBeforeDynamic);
+                if (margin < 0) {
+                    labelOffset = -margin / 2;
+                }
+            }
+        } else {
+            // TODO: implement horizontal labels
+        }
+
+        // when offsetting label, neighbor probably needs to offset too
+        if (labelOffset != mLabelOffset) {
+            mLabelOffset = labelOffset;
+            invalidate();
+            if (mValidAfterDynamic != null) mValidAfterDynamic.invalidateLabelOffset();
+            if (mValidBeforeDynamic != null) mValidBeforeDynamic.invalidateLabelOffset();
+        }
+    }
+
     @Override
     public void jumpDrawablesToCurrentState() {
         super.jumpDrawablesToCurrentState();
@@ -567,6 +601,12 @@
     }
 
     @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        super.onLayout(changed, left, top, right, bottom);
+        invalidateLabelOffset();
+    }
+
+    @Override
     protected void onDraw(Canvas canvas) {
         final int width = getWidth();
         final int height = getHeight();
@@ -575,36 +615,11 @@
             canvas.drawRect(0, 0, width, height, mOutlinePaint);
         }
 
-        // when overlapping with neighbor, split difference and push label
-        float margin;
-        float labelOffset = 0;
-        if (mFollowAxis == VERTICAL) {
-            if (mValidAfterDynamic != null) {
-                margin = getLabelTop(mValidAfterDynamic) - getLabelBottom(this);
-                if (margin < 0) {
-                    labelOffset = margin / 2;
-                }
-            } else if (mValidBeforeDynamic != null) {
-                margin = getLabelTop(this) - getLabelBottom(mValidBeforeDynamic);
-                if (margin < 0) {
-                    labelOffset = -margin / 2;
-                }
-            }
-        } else {
-            // TODO: implement horizontal labels
-        }
-
-        // when offsetting label, neighbor probably needs to offset too
-        if (labelOffset != 0) {
-            if (mValidAfterDynamic != null) mValidAfterDynamic.invalidate();
-            if (mValidBeforeDynamic != null) mValidBeforeDynamic.invalidate();
-        }
-
         final int labelSize;
         if (isEnabled() && mLabelLayout != null) {
             final int count = canvas.save();
             {
-                canvas.translate(mContentOffset.left, mContentOffset.top + labelOffset);
+                canvas.translate(mContentOffset.left, mContentOffset.top + mLabelOffset);
                 mLabelLayout.draw(canvas);
             }
             canvas.restoreToCount(count);
diff --git a/src/com/android/settings/widget/InvertedChartAxis.java b/src/com/android/settings/widget/InvertedChartAxis.java
index 7dcc78a..2d820d9 100644
--- a/src/com/android/settings/widget/InvertedChartAxis.java
+++ b/src/com/android/settings/widget/InvertedChartAxis.java
@@ -31,14 +31,14 @@
     }
 
     /** {@inheritDoc} */
-    public void setBounds(long min, long max) {
-        mWrapped.setBounds(min, max);
+    public boolean setBounds(long min, long max) {
+        return mWrapped.setBounds(min, max);
     }
 
     /** {@inheritDoc} */
-    public void setSize(float size) {
+    public boolean setSize(float size) {
         mSize = size;
-        mWrapped.setSize(size);
+        return mWrapped.setSize(size);
     }
 
     /** {@inheritDoc} */
diff --git a/src/com/android/settings/widget/PieChartView.java b/src/com/android/settings/widget/PieChartView.java
index 85d45a2..6765733 100644
--- a/src/com/android/settings/widget/PieChartView.java
+++ b/src/com/android/settings/widget/PieChartView.java
@@ -40,7 +40,7 @@
  */
 public class PieChartView extends View {
     public static final String TAG = "PieChartView";
-    public static final boolean LOGD = true;
+    public static final boolean LOGD = false;
 
     private ArrayList<Slice> mSlices = Lists.newArrayList();