Merge "Allows user to skip fingerprint during setup"
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 637e710..c40328e 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -1546,7 +1546,7 @@
         <activity
             android:name=".Settings$FingerprintSuggestionActivity"
             android:label="@string/security_settings_fingerprint_preference_title"
-            android:icon="@drawable/ic_fingerprint">
+            android:icon="@drawable/ic_suggestion_fingerprint">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="com.android.settings.suggested.category.SETTINGS_ONLY" />
@@ -1626,7 +1626,7 @@
         </activity>
 
         <activity android:name=".Settings$FingerprintEnrollSuggestionActivity"
-            android:icon="@drawable/ic_settings_security">
+            android:icon="@drawable/ic_suggestion_fingerprint">
             <intent-filter android:priority="2">
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="com.android.settings.suggested.category.LOCK_SCREEN" />
@@ -1638,7 +1638,7 @@
             <meta-data android:name="com.android.settings.title"
                 android:resource="@string/suggested_lock_settings_title" />
             <meta-data android:name="com.android.settings.summary"
-                android:resource="@string/suggested_lock_settings_summary" />
+                android:resource="@string/suggested_fingerprint_lock_settings_summary" />
         </activity>
 
         <activity android:name="ChooseLockGeneric$InternalActivity" android:exported="false"
diff --git a/res/drawable/ic_suggestion_fingerprint.xml b/res/drawable/ic_suggestion_fingerprint.xml
new file mode 100644
index 0000000..feebd94
--- /dev/null
+++ b/res/drawable/ic_suggestion_fingerprint.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2016 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.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="32dp"
+        android:height="32dp"
+        android:tint="?android:attr/colorAccent"
+        android:viewportWidth="32.0"
+        android:viewportHeight="32.0">
+    <path
+        android:fillColor="#ffffff"
+        android:pathData="M23.7,5.9c-0.1,0.0 -0.2,0.0 -0.3,-0.1C21.0,4.5 18.6,3.9 16.0,3.9c-2.5,0.0 -4.6,0.6 -6.9,1.9C8.8,6.0 8.3,5.9 8.1,5.5C7.9,5.2 8.0,4.7 8.4,4.5c2.5,-1.4 4.9,-2.1 7.7,-2.1c2.8,0.0 5.4,0.7 8.0,2.1c0.4,0.2 0.5,0.6 0.3,1.0C24.2,5.7 24.0,5.9 23.7,5.9z"/>
+    <path
+        android:fillColor="#ffffff"
+        android:pathData="M5.3,13.2c-0.1,0.0 -0.3,0.0 -0.4,-0.1c-0.3,-0.2 -0.4,-0.7 -0.2,-1.0c1.3,-1.9 2.9,-3.4 4.9,-4.5c4.1,-2.2 9.3,-2.2 13.4,0.0c1.9,1.1 3.6,2.5 4.9,4.4c0.2,0.3 0.1,0.8 -0.2,1.0c-0.3,0.2 -0.8,0.1 -1.0,-0.2c-1.2,-1.7 -2.6,-3.0 -4.3,-4.0c-3.7,-2.0 -8.3,-2.0 -12.0,0.0c-1.7,0.9 -3.2,2.3 -4.3,4.0C5.7,13.1 5.5,13.2 5.3,13.2z"/>
+    <path
+        android:fillColor="#ffffff"
+        android:pathData="M13.3,29.6c-0.2,0.0 -0.4,-0.1 -0.5,-0.2c-1.1,-1.2 -1.7,-2.0 -2.6,-3.6c-0.9,-1.7 -1.4,-3.7 -1.4,-5.9c0.0,-4.1 3.3,-7.4 7.4,-7.4c4.1,0.0 7.4,3.3 7.4,7.4c0.0,0.4 -0.3,0.7 -0.7,0.7s-0.7,-0.3 -0.7,-0.7c0.0,-3.3 -2.7,-5.9 -5.9,-5.9c-3.3,0.0 -5.9,2.7 -5.9,5.9c0.0,2.0 0.4,3.8 1.2,5.2c0.8,1.6 1.4,2.2 2.4,3.3c0.3,0.3 0.3,0.8 0.0,1.0C13.7,29.5 13.5,29.6 13.3,29.6z"/>
+    <path
+        android:fillColor="#ffffff"
+        android:pathData="M22.6,27.1c-1.6,0.0 -2.9,-0.4 -4.1,-1.2c-1.9,-1.4 -3.1,-3.6 -3.1,-6.0c0.0,-0.4 0.3,-0.7 0.7,-0.7s0.7,0.3 0.7,0.7c0.0,1.9 0.9,3.7 2.5,4.8c0.9,0.6 1.9,1.0 3.2,1.0c0.3,0.0 0.8,0.0 1.3,-0.1c0.4,-0.1 0.8,0.2 0.8,0.6c0.1,0.4 -0.2,0.8 -0.6,0.8C23.4,27.1 22.8,27.1 22.6,27.1z"/>
+    <path
+        android:fillColor="#ffffff"
+        android:pathData="M20.0,29.9c-0.1,0.0 -0.1,0.0 -0.2,0.0c-2.1,-0.6 -3.4,-1.4 -4.8,-2.9c-1.8,-1.9 -2.8,-4.4 -2.8,-7.1c0.0,-2.2 1.8,-4.1 4.1,-4.1c2.2,0.0 4.1,1.8 4.1,4.1c0.0,1.4 1.2,2.6 2.6,2.6c1.4,0.0 2.6,-1.2 2.6,-2.6c0.0,-5.1 -4.2,-9.3 -9.3,-9.3c-3.6,0.0 -6.9,2.1 -8.4,5.4C7.3,17.1 7.0,18.4 7.0,19.8c0.0,1.1 0.1,2.7 0.9,4.9c0.1,0.4 -0.1,0.8 -0.4,0.9c-0.4,0.1 -0.8,-0.1 -0.9,-0.4c-0.6,-1.8 -0.9,-3.6 -0.9,-5.4c0.0,-1.6 0.3,-3.1 0.9,-4.4c1.7,-3.8 5.6,-6.3 9.8,-6.3c5.9,0.0 10.7,4.8 10.7,10.7c0.0,2.2 -1.8,4.1 -4.1,4.1s-4.0,-1.8 -4.0,-4.1c0.0,-1.4 -1.2,-2.6 -2.6,-2.6c-1.4,0.0 -2.6,1.2 -2.6,2.6c0.0,2.3 0.9,4.5 2.4,6.1c1.2,1.3 2.4,2.0 4.2,2.5c0.4,0.1 0.6,0.5 0.5,0.9C20.6,29.7 20.3,29.9 20.0,29.9z"/>
+</vector>
+
diff --git a/res/layout/font_size_activity.xml b/res/layout/font_size_activity.xml
index ec064fa..479a5f5 100644
--- a/res/layout/font_size_activity.xml
+++ b/res/layout/font_size_activity.xml
@@ -70,7 +70,7 @@
                 android:focusable="true"
                 android:contentDescription="@string/font_size_make_smaller_desc" />
 
-            <SeekBar
+            <com.android.settings.widget.LabeledSeekBar
                 android:id="@+id/seek_bar"
                 android:layout_width="0dp"
                 android:layout_height="48dp"
diff --git a/res/layout/locale_order_list.xml b/res/layout/locale_order_list.xml
index 97ba9a3..e210c65 100644
--- a/res/layout/locale_order_list.xml
+++ b/res/layout/locale_order_list.xml
@@ -25,20 +25,19 @@
         android:id="@+id/dragList"
         android:layout_width="match_parent"
         android:layout_height="0dp"
-        android:layout_weight="1"/>
+        android:layout_weight="1"
+        android:scrollbars="vertical"/>
 
-    <TextView
+    <Button
         android:id="@+id/add_language"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:clickable="true"
-        android:contextClickable="true"
         android:drawableStart="@drawable/ic_add_24dp"
         android:drawablePadding="12dp"
-        android:gravity="center_vertical"
-        android:measureWithLargestChild="false"
-        android:padding="16dp"
+        android:minHeight="64dp"
+        android:textAlignment="textStart"
         android:text="@string/add_a_language"
+        style="@style/Base.Widget.AppCompat.Button.Borderless"
         android:textAppearance="?android:attr/textAppearanceListItem"/>
 
 </LinearLayout>
diff --git a/res/layout/printer_dropdown_item.xml b/res/layout/printer_dropdown_item.xml
index 0063477..1209aa6 100644
--- a/res/layout/printer_dropdown_item.xml
+++ b/res/layout/printer_dropdown_item.xml
@@ -15,31 +15,33 @@
 -->
 
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
+    android:layout_width="fill_parent"
     android:layout_height="wrap_content"
-    android:minHeight="48dip"
-    android:gravity="center_vertical"
     android:paddingStart="?android:attr/listPreferredItemPaddingStart"
     android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
-    android:background="?android:attr/selectableItemBackground">
+    android:minHeight="?android:attr/listPreferredItemHeight"
+    android:orientation="horizontal"
+    android:gravity="start|center_vertical">
 
     <ImageView
         android:id="@+id/icon"
-        android:layout_width="48dip"
-        android:layout_height="48dip"
+        android:layout_width="40dip"
+        android:layout_height="40dip"
         android:layout_gravity="center_vertical"
-        android:layout_marginEnd="8dip"
+        android:layout_marginTop="8dip"
+        android:layout_marginBottom="8dip"
         android:duplicateParentState="true"
         android:contentDescription="@null"
-        android:visibility="gone">
+        android:visibility="invisible">
     </ImageView>
 
-    <LinearLayout
-        android:layout_width="wrap_content"
+    <RelativeLayout
+        android:layout_width="0dip"
+        android:layout_weight="1"
         android:layout_height="wrap_content"
-        android:layout_marginTop="6dip"
-        android:layout_marginBottom="6dip"
-        android:orientation="vertical">
+        android:layout_gravity="center_vertical"
+        android:layout_marginStart="16dip"
+        android:duplicateParentState="true">
 
         <TextView
             android:id="@+id/title"
@@ -47,24 +49,45 @@
             android:layout_height="wrap_content"
             android:textAppearance="?android:attr/textAppearanceMedium"
             android:singleLine="true"
-            android:ellipsize="marquee"
+            android:ellipsize="end"
+            android:textIsSelectable="false"
+            android:layout_alignParentTop="true"
+            android:layout_alignParentStart="true"
             android:fadingEdge="horizontal"
+            android:textAlignment="viewStart"
             android:textColor="?android:attr/textColorPrimary"
-            android:textIsSelectable="false">
+            android:duplicateParentState="true">
         </TextView>
 
         <TextView
             android:id="@+id/subtitle"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
+            android:layout_below="@id/title"
+            android:layout_alignParentStart="true"
             android:textAppearance="?android:attr/textAppearanceSmall"
             android:singleLine="true"
             android:ellipsize="end"
             android:textIsSelectable="false"
             android:visibility="gone"
-            android:textColor="?android:attr/textColorSecondary">
+            android:textColor="?android:attr/textColorSecondary"
+            android:textAlignment="viewStart"
+            android:duplicateParentState="true">
         </TextView>
 
-    </LinearLayout>
+    </RelativeLayout>
+
+    <ImageView
+        android:id="@+id/more_info"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_vertical"
+        android:paddingLeft="16dip"
+        android:contentDescription="@string/printer_info_desc"
+        android:src="@drawable/ic_info"
+        android:tint="?android:attr/colorControlNormal"
+        android:tintMode="src_in"
+        android:visibility="gone">
+    </ImageView>
 
 </LinearLayout>
diff --git a/res/layout/screen_zoom_activity.xml b/res/layout/screen_zoom_activity.xml
index 51fea69..47c6b19 100644
--- a/res/layout/screen_zoom_activity.xml
+++ b/res/layout/screen_zoom_activity.xml
@@ -69,7 +69,7 @@
                 android:focusable="true"
                 android:contentDescription="@string/screen_zoom_make_smaller_desc" />
 
-            <SeekBar
+            <com.android.settings.widget.LabeledSeekBar
                 android:id="@+id/seek_bar"
                 android:layout_width="0dp"
                 android:layout_height="48dp"
diff --git a/res/layout/wifi_dialog.xml b/res/layout/wifi_dialog.xml
index c5e554d..05c33d7 100644
--- a/res/layout/wifi_dialog.xml
+++ b/res/layout/wifi_dialog.xml
@@ -157,6 +157,24 @@
                             android:text="@string/wifi_do_not_validate_eap_server_warning" />
                 </LinearLayout>
 
+                <LinearLayout android:id="@+id/l_domain"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    style="@style/wifi_item" >
+                    <TextView
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content"
+                            style="@style/wifi_item_label"
+                            android:text="@string/wifi_eap_domain" />
+
+                    <EditText android:id="@+id/domain"
+                            android:layout_width="match_parent"
+                            android:layout_height="wrap_content"
+                            style="@style/wifi_item_edit_content"
+                            android:singleLine="true"
+                            android:inputType="textNoSuggestions" />
+                </LinearLayout>
+
                 <LinearLayout android:id="@+id/l_user_cert"
                         android:layout_width="match_parent"
                         android:layout_height="wrap_content"
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 438afa9..58815c6 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -966,6 +966,9 @@
     <!-- Summary for suggested actions for screen lock -->
     <string name="suggested_lock_settings_summary">Protect your device</string>
 
+    <!-- Summary for suggested actions for settings up a fingerprint lock -->
+    <string name="suggested_fingerprint_lock_settings_summary">Unlock with your fingerprint</string>
+
     <!--  Title for security picker to choose the unlock method: None/Pattern/PIN/Password [CHAR LIMIT=22] -->
     <string name="lock_settings_picker_title">Choose screen lock</string>
 
@@ -1613,6 +1616,8 @@
     <string name="please_select_phase2">Phase 2 authentication</string>
     <!-- Label for the EAP CA certificate of the network -->
     <string name="wifi_eap_ca_cert">CA certificate</string>
+    <!-- Label for the domain name that the EAP CA certificate(s) can be used to validate. -->
+    <string name="wifi_eap_domain">Domain</string>
     <!-- Label for the EAP user certificate of the network -->
     <string name="wifi_eap_user_cert">User certificate</string>
     <!-- Label for the EAP identity of the network -->
@@ -3547,7 +3552,7 @@
     <!-- Toast that settings for an application is failed to open. -->
     <string name="failed_to_open_app_settings_toast">Failed to open settings for <xliff:g id="spell_application_name">%1$s</xliff:g></string>
 
-    <!-- Title for the 'keyboard and input methods' preference category. [CHAR LIMIT=35] -->
+    <!-- Title for the 'keyboard and input methods' preference category. [CHAR LIMIT=45] -->
     <string name="keyboard_and_input_methods_category">Keyboard and input methods</string>
     <!-- Title for the 'virtual keyboard' preference sub-screen. [CHAR LIMIT=35] -->
     <string name="virtual_keyboard_category">Virtual keyboard</string>
@@ -4034,6 +4039,9 @@
     <!-- Utterance to announce that the search box is hidden. This is spoken to a blind user. [CHAR LIMIT=none] -->
     <string name="print_search_box_hidden_utterance">Search box hidden</string>
 
+    <!-- Description of printer info icon. [CHAR LIMIT=50] -->
+    <string name="printer_info_desc">More information about this printer</string>
+
     <!-- App Fuel Gauge strings -->
     <skip />
 
@@ -6326,7 +6334,9 @@
    <!-- App notification summary with notifications enabled [CHAR LIMIT=40] -->
    <string name="notifications_enabled">Normal</string>
    <!-- App notification summary with notifications disabled [CHAR LIMIT=40] -->
-   <string name="notifications_disabled">Block</string>
+   <string name="notifications_disabled">Fully Blocked</string>
+   <!-- App notification summary with notifications disabled [CHAR LIMIT=40] -->
+   <string name="notifications_partially_disabled">Partially Blocked</string>
    <!-- App notification summary with 2 items [CHAR LIMIT=15] -->
    <string name="notifications_two_items"><xliff:g id="notif_state" example="Priority">%1$s</xliff:g> / <xliff:g id="notif_state" example="Priority">%2$s</xliff:g></string>
    <!-- App notification summary with 3 items [CHAR LIMIT=15] -->
diff --git a/res/xml/suggestion_ordering.xml b/res/xml/suggestion_ordering.xml
index 55f7803..1eeafba 100644
--- a/res/xml/suggestion_ordering.xml
+++ b/res/xml/suggestion_ordering.xml
@@ -17,9 +17,9 @@
 <optional-steps>
     <step category="com.android.settings.suggested.category.LOCK_SCREEN" />
     <step category="com.android.settings.suggested.category.EMAIL" />
-    <step category="com.android.settings.suggested.category.PAYMENT" />
     <step category="com.android.settings.suggested.category.PARTNER_ACCOUNT"
         multiple="true" />
+    <step category="com.android.settings.suggested.category.HOTWORD" />
     <step category="com.android.settings.suggested.category.DEFAULT"
         multiple="true" />
     <step category="com.android.settings.suggested.category.SETTINGS_ONLY"
diff --git a/src/com/android/settings/ChooseLockGeneric.java b/src/com/android/settings/ChooseLockGeneric.java
index 03a3fef..42ae602 100644
--- a/src/com/android/settings/ChooseLockGeneric.java
+++ b/src/com/android/settings/ChooseLockGeneric.java
@@ -240,6 +240,7 @@
             if (UserManager.get(getActivity()).isAdminUser()
                     && mUserId == UserHandle.myUserId()
                     && LockPatternUtils.isDeviceEncryptionEnabled()
+                    && !LockPatternUtils.isFileEncryptionEnabled()
                     && !dpm.getDoNotAskCredentialsOnBoot()) {
                 mEncryptionRequestQuality = quality;
                 mEncryptionRequestDisabled = disabled;
diff --git a/src/com/android/settings/ConfirmDeviceCredentialActivity.java b/src/com/android/settings/ConfirmDeviceCredentialActivity.java
index 28c0515..16d0685 100644
--- a/src/com/android/settings/ConfirmDeviceCredentialActivity.java
+++ b/src/com/android/settings/ConfirmDeviceCredentialActivity.java
@@ -19,6 +19,8 @@
 
 import android.app.Activity;
 import android.app.KeyguardManager;
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
 import android.content.Intent;
 import android.os.Binder;
 import android.os.Bundle;
@@ -72,6 +74,11 @@
                 Log.e(TAG, "Invalid intent extra", se);
             }
         }
+        // if the client app did not hand in a title and we are about to show the work challenge,
+        // check whether there is a policy setting the organization name and use that as title
+        if ((title == null) && Utils.isManagedProfile(UserManager.get(this), userId)) {
+            title = getTitleFromOrganizationName(userId);
+        }
         ChooseLockSettingsHelper helper = new ChooseLockSettingsHelper(this);
         if (!helper.launchConfirmationActivity(0 /* request code */, null /* title */, title,
                 details, false /* returnCredentials */, true /* isExternal */, userId)) {
@@ -84,4 +91,10 @@
     private boolean isInternalActivity() {
         return this instanceof ConfirmDeviceCredentialActivity.InternalActivity;
     }
+
+    private String getTitleFromOrganizationName(int userId) {
+        DevicePolicyManager dpm = (DevicePolicyManager) getSystemService(
+                Context.DEVICE_POLICY_SERVICE);
+        return (dpm != null) ? dpm.getOrganizationNameForUser(userId) : null;
+    }
 }
diff --git a/src/com/android/settings/PreviewSeekBarPreferenceFragment.java b/src/com/android/settings/PreviewSeekBarPreferenceFragment.java
index ea8b55a..ad32c2d 100644
--- a/src/com/android/settings/PreviewSeekBarPreferenceFragment.java
+++ b/src/com/android/settings/PreviewSeekBarPreferenceFragment.java
@@ -28,6 +28,7 @@
 import android.widget.SeekBar.OnSeekBarChangeListener;
 import android.widget.TextView;
 import com.android.settings.widget.DotsPageIndicator;
+import com.android.settings.widget.LabeledSeekBar;
 
 
 /**
@@ -97,7 +98,8 @@
         // seek bar.
         final int max = Math.max(1, mEntries.length - 1);
 
-        final SeekBar seekBar = (SeekBar) content.findViewById(R.id.seek_bar);
+        final LabeledSeekBar seekBar = (LabeledSeekBar) content.findViewById(R.id.seek_bar);
+        seekBar.setLabels(mEntries);
         seekBar.setMax(max);
         seekBar.setProgress(mInitialIndex);
         seekBar.setOnSeekBarChangeListener(new onPreviewSeekBarChangeListener());
diff --git a/src/com/android/settings/TetherService.java b/src/com/android/settings/TetherService.java
index d4944ef..1d68e90 100644
--- a/src/com/android/settings/TetherService.java
+++ b/src/com/android/settings/TetherService.java
@@ -37,6 +37,7 @@
 import android.util.ArrayMap;
 import android.util.Log;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.settingslib.TetherUtil;
 
 import java.util.ArrayList;
@@ -46,7 +47,8 @@
     private static final String TAG = "TetherService";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
-    private static final String EXTRA_RESULT = "EntitlementResult";
+    @VisibleForTesting
+    public static final String EXTRA_RESULT = "EntitlementResult";
 
     // Activity results to match the activity provision protocol.
     // Default to something not ok.
@@ -295,7 +297,7 @@
         @Override
         public void onReceive(Context context, Intent intent) {
             if (DEBUG) Log.d(TAG, "Got provision result " + intent);
-            String provisionResponse = context.getResources().getString(
+            String provisionResponse = getResources().getString(
                     com.android.internal.R.string.config_mobile_hotspot_provision_response);
 
             if (provisionResponse.equals(intent.getAction())) {
diff --git a/src/com/android/settings/applications/AppStateNotificationBridge.java b/src/com/android/settings/applications/AppStateNotificationBridge.java
index 6d057b3..c7b78af 100644
--- a/src/com/android/settings/applications/AppStateNotificationBridge.java
+++ b/src/com/android/settings/applications/AppStateNotificationBridge.java
@@ -63,7 +63,11 @@
 
         @Override
         public boolean filterApp(AppEntry info) {
-            return info.extraInfo != null && ((AppRow) info.extraInfo).banned;
+            if (info == null) {
+                return false;
+            }
+            AppRow row = (AppRow) info.extraInfo;
+            return row.banned || row.bannedTopics;
         }
     };
 }
diff --git a/src/com/android/settings/applications/InstalledAppDetails.java b/src/com/android/settings/applications/InstalledAppDetails.java
index f31fa72..83159e3 100755
--- a/src/com/android/settings/applications/InstalledAppDetails.java
+++ b/src/com/android/settings/applications/InstalledAppDetails.java
@@ -821,6 +821,8 @@
     public static CharSequence getNotificationSummary(AppRow appRow, Context context) {
         if (appRow.banned) {
             return context.getString(R.string.notifications_disabled);
+        } else if (appRow.bannedTopics) {
+            return context.getString(R.string.notifications_partially_disabled);
         }
         return context.getString(R.string.notifications_enabled);
     }
diff --git a/src/com/android/settings/datausage/DataUsageMeteredSettings.java b/src/com/android/settings/datausage/DataUsageMeteredSettings.java
index 25e1a07..eb43d47 100644
--- a/src/com/android/settings/datausage/DataUsageMeteredSettings.java
+++ b/src/com/android/settings/datausage/DataUsageMeteredSettings.java
@@ -14,6 +14,7 @@
 
 package com.android.settings.datausage;
 
+import android.app.backup.BackupManager;
 import android.content.Context;
 import android.content.res.Resources;
 import android.net.NetworkPolicy;
@@ -150,6 +151,8 @@
             super.notifyChanged();
             if (!mBinding) {
                 mPolicyEditor.setPolicyMetered(mTemplate, isChecked());
+                // Stage the backup of the SettingsProvider package which backs this up
+                BackupManager.dataChanged("com.android.providers.settings");
             }
         }
     }
diff --git a/src/com/android/settings/notification/NotificationBackend.java b/src/com/android/settings/notification/NotificationBackend.java
index 292d247..8dd6a4c 100644
--- a/src/com/android/settings/notification/NotificationBackend.java
+++ b/src/com/android/settings/notification/NotificationBackend.java
@@ -54,6 +54,7 @@
         row.appImportance = getImportance(row.pkg, row.uid, null);
         row.appBypassDnd = getBypassZenMode(row.pkg, row.uid, null);
         row.appSensitive = getSensitive(row.pkg, row.uid, null);
+        row.bannedTopics = hasBannedTopics(row.pkg, row.uid);
         return row;
     }
 
@@ -170,6 +171,15 @@
         }
     }
 
+    public boolean hasBannedTopics(String pkg, int uid) {
+        try {
+            return sINM.hasBannedTopics(pkg, uid);
+        } catch (Exception e) {
+            Log.w(TAG, "Error calling NoMan", e);
+            return false;
+        }
+    }
+
     static class Row {
         public String section;
     }
@@ -186,6 +196,7 @@
         public int appImportance;
         public boolean appBypassDnd;
         public boolean appSensitive;
+        public boolean bannedTopics;
     }
 
     public static class TopicRow extends AppRow {
diff --git a/src/com/android/settings/print/PrintServiceSettingsFragment.java b/src/com/android/settings/print/PrintServiceSettingsFragment.java
index 2adfb17..a9dba1e 100644
--- a/src/com/android/settings/print/PrintServiceSettingsFragment.java
+++ b/src/com/android/settings/print/PrintServiceSettingsFragment.java
@@ -40,11 +40,13 @@
 import android.print.PrinterInfo;
 import android.text.TextUtils;
 import android.util.Log;
+import android.util.TypedValue;
 import android.view.Menu;
 import android.view.MenuInflater;
 import android.view.MenuItem;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.View.OnClickListener;
 import android.view.accessibility.AccessibilityManager;
 import android.widget.AdapterView;
 import android.widget.BaseAdapter;
@@ -529,6 +531,17 @@
             return position;
         }
 
+        /**
+         * Checks if a printer can be used for printing
+         *
+         * @param position The position of the printer in the list
+         * @return true iff the printer can be used for printing.
+         */
+        public boolean isActionable(int position) {
+            PrinterInfo printer = (PrinterInfo) getItem(position);
+            return printer.getStatus() != PrinterInfo.STATUS_UNAVAILABLE;
+        }
+
         @Override
         public View getView(int position, View convertView, ViewGroup parent) {
             if (convertView == null) {
@@ -536,7 +549,9 @@
                         R.layout.printer_dropdown_item, parent, false);
             }
 
-            PrinterInfo printer = (PrinterInfo) getItem(position);
+            convertView.setEnabled(isActionable(position));
+
+            final PrinterInfo printer = (PrinterInfo) getItem(position);
             CharSequence title = printer.getName();
             CharSequence subtitle = printer.getDescription();
             Drawable icon = printer.loadIcon(getActivity());
@@ -553,10 +568,36 @@
                 subtitleView.setVisibility(View.GONE);
             }
 
+            ImageView moreInfoView = (ImageView) convertView.findViewById(R.id.more_info);
+            if (printer.getInfoIntent() != null) {
+                moreInfoView.setVisibility(View.VISIBLE);
+                moreInfoView.setOnClickListener(new OnClickListener() {
+                    @Override
+                    public void onClick(View v) {
+                        try {
+                            getActivity().startIntentSender(
+                                    printer.getInfoIntent().getIntentSender(), null, 0, 0, 0);
+                        } catch (SendIntentException e) {
+                            Log.e(LOG_TAG, "Could not execute pending info intent: %s", e);
+                        }
+                    }
+                });
+            } else {
+                moreInfoView.setVisibility(View.GONE);
+            }
+
             ImageView iconView = (ImageView) convertView.findViewById(R.id.icon);
             if (icon != null) {
-                iconView.setImageDrawable(icon);
                 iconView.setVisibility(View.VISIBLE);
+                if (!isActionable(position)) {
+                    icon.mutate();
+
+                    TypedValue value = new TypedValue();
+                    getActivity().getTheme().resolveAttribute(android.R.attr.disabledAlpha, value,
+                            true);
+                    icon.setAlpha((int)(value.getFloat() * 255));
+                }
+                iconView.setImageDrawable(icon);
             } else {
                 iconView.setVisibility(View.GONE);
             }
diff --git a/src/com/android/settings/vpn2/VpnSettings.java b/src/com/android/settings/vpn2/VpnSettings.java
index cd24be8..4cbb3df 100644
--- a/src/com/android/settings/vpn2/VpnSettings.java
+++ b/src/com/android/settings/vpn2/VpnSettings.java
@@ -223,6 +223,8 @@
         final List<LegacyVpnInfo> connectedLegacyVpns = getConnectedLegacyVpns();
         final List<AppVpnInfo> connectedAppVpns = getConnectedAppVpns();
 
+        final Set<Integer> readOnlyUsers = getReadOnlyUserProfiles();
+
         // Refresh the PreferenceGroup which lists VPNs
         getActivity().runOnUiThread(new Runnable() {
             @Override
@@ -233,11 +235,13 @@
                 for (VpnProfile profile : vpnProfiles) {
                     ConfigPreference p = findOrCreatePreference(profile);
                     p.setState(ConfigPreference.STATE_NONE);
+                    p.setEnabled(!readOnlyUsers.contains(UserHandle.myUserId()));
                     updates.add(p);
                 }
                 for (AppVpnInfo app : vpnApps) {
                     AppPreference p = findOrCreatePreference(app);
                     p.setState(AppPreference.STATE_DISCONNECTED);
+                    p.setEnabled(!readOnlyUsers.contains(app.userId));
                     updates.add(p);
                 }
 
@@ -417,6 +421,19 @@
         return connections;
     }
 
+    @WorkerThread
+    private Set<Integer> getReadOnlyUserProfiles() {
+        Set<Integer> result = new ArraySet<>();
+        for (UserHandle profile : mUserManager.getUserProfiles()) {
+            final int profileId = profile.getIdentifier();
+            if (mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_VPN, profile)
+                    || mConnectivityManager.getAlwaysOnVpnPackageForUser(profileId) != null) {
+                result.add(profileId);
+            }
+        }
+        return result;
+    }
+
     static List<AppVpnInfo> getVpnApps(Context context, boolean includeProfiles) {
         List<AppVpnInfo> result = Lists.newArrayList();
 
diff --git a/src/com/android/settings/widget/LabeledSeekBar.java b/src/com/android/settings/widget/LabeledSeekBar.java
new file mode 100644
index 0000000..9463bf7
--- /dev/null
+++ b/src/com/android/settings/widget/LabeledSeekBar.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2016 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.widget;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.support.v4.view.ViewCompat;
+import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
+import android.support.v4.widget.ExploreByTouchHelper;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.accessibility.AccessibilityEvent;
+import android.widget.RadioButton;
+import android.widget.RadioGroup;
+import android.widget.SeekBar;
+
+import java.util.List;
+
+/**
+ * LabeledSeekBar represent a seek bar assigned with labeled, discrete values.
+ * It pretends to be a group of radio button for AccessibilityServices, in order to adjust the
+ * behavior of these services to keep the mental model of the visual discrete SeekBar.
+ */
+public class LabeledSeekBar extends SeekBar {
+
+    private class LabeledSeekBarExploreByTouchHelper extends ExploreByTouchHelper {
+
+        public LabeledSeekBarExploreByTouchHelper(View forView) {
+            super(forView);
+        }
+
+        @Override
+        protected int getVirtualViewAt(float x, float y) {
+            return getVirtualViewIdIndexFromX(x);
+        }
+
+        @Override
+        protected void getVisibleVirtualViews(List<Integer> list) {
+            for (int i = 0, c = LabeledSeekBar.this.getMax(); i <= c; ++i) {
+                list.add(i);
+            }
+        }
+
+        @Override
+        protected boolean onPerformActionForVirtualView(int virtualViewId, int action,
+                Bundle arguments) {
+            if (virtualViewId == ExploreByTouchHelper.HOST_ID) {
+                // Do nothing
+                return false;
+            }
+
+            switch (action) {
+                case AccessibilityNodeInfoCompat.ACTION_CLICK:
+                    LabeledSeekBar.this.setProgress(virtualViewId);
+                    sendEventForVirtualView(virtualViewId, AccessibilityEvent.TYPE_VIEW_CLICKED);
+                    return true;
+                default:
+                    return false;
+            }
+        }
+
+        @Override
+        protected void onPopulateNodeForVirtualView(
+                int virtualViewId, AccessibilityNodeInfoCompat node) {
+            node.setClassName(RadioButton.class.getName());
+            node.setBoundsInParent(getBoundsInParentFromVirtualViewId(virtualViewId));
+            node.addAction(AccessibilityNodeInfoCompat.ACTION_CLICK);
+            node.setContentDescription(mLabels[virtualViewId]);
+            node.setClickable(true);
+            node.setCheckable(true);
+            node.setChecked(virtualViewId == LabeledSeekBar.this.getProgress());
+        }
+
+        @Override
+        protected void onPopulateEventForVirtualView(int virtualViewId, AccessibilityEvent event) {
+            event.setClassName(RadioButton.class.getName());
+            event.setContentDescription(mLabels[virtualViewId]);
+            event.setChecked(virtualViewId == LabeledSeekBar.this.getProgress());
+        }
+
+        @Override
+        protected void onPopulateNodeForHost(AccessibilityNodeInfoCompat node) {
+            node.setClassName(RadioGroup.class.getName());
+        }
+
+        @Override
+        protected void onPopulateEventForHost(AccessibilityEvent event) {
+            event.setClassName(RadioGroup.class.getName());
+        }
+
+        private int getHalfVirtualViewWidth() {
+            final int width = LabeledSeekBar.this.getWidth();
+            final int barWidth = width - LabeledSeekBar.this.getPaddingStart()
+                    - LabeledSeekBar.this.getPaddingEnd();
+            return Math.max(0, barWidth / (LabeledSeekBar.this.getMax() * 2));
+        }
+
+        private int getVirtualViewIdIndexFromX(float x) {
+            final int posBase = Math.max(0,
+                    ((int) x - LabeledSeekBar.this.getPaddingStart()) / getHalfVirtualViewWidth());
+            return (posBase + 1) / 2;
+        }
+
+        private Rect getBoundsInParentFromVirtualViewId(int virtualViewId) {
+            int left = (virtualViewId * 2 - 1) * getHalfVirtualViewWidth()
+                    + LabeledSeekBar.this.getPaddingStart();
+            int right = (virtualViewId * 2 + 1) * getHalfVirtualViewWidth()
+                    + LabeledSeekBar.this.getPaddingStart();
+
+            // Edge case
+            left = virtualViewId == 0 ? 0 : left;
+            right = virtualViewId == LabeledSeekBar.this.getMax()
+                    ? LabeledSeekBar.this.getWidth() : right;
+
+            final Rect r = new Rect();
+            r.set(left, 0, right, LabeledSeekBar.this.getHeight());
+            return r;
+        }
+    }
+
+    private String[] mLabels;
+
+    private ExploreByTouchHelper mAccessHelper;
+
+    public LabeledSeekBar(Context context, AttributeSet attrs) {
+        this(context, attrs, com.android.internal.R.attr.seekBarStyle);
+    }
+
+    public LabeledSeekBar(Context context, AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    public LabeledSeekBar(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    @Override
+    public synchronized void setProgress(int progress) {
+        if (mAccessHelper != null) {
+            mAccessHelper.invalidateRoot();
+        }
+
+        super.setProgress(progress);
+    }
+
+    public void setLabels(String[] labels) {
+        mLabels = labels;
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        mAccessHelper = new LabeledSeekBarExploreByTouchHelper(this);
+        ViewCompat.setAccessibilityDelegate(this, mAccessHelper);
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        ViewCompat.setAccessibilityDelegate(this, null);
+        mAccessHelper = null;
+        super.onDetachedFromWindow();
+    }
+
+    @Override
+    protected boolean dispatchHoverEvent(MotionEvent event) {
+        if (mAccessHelper != null && mAccessHelper.dispatchHoverEvent(event)) {
+            return true;
+        }
+
+        return super.dispatchHoverEvent(event);
+    }
+}
diff --git a/src/com/android/settings/wifi/WifiConfigController.java b/src/com/android/settings/wifi/WifiConfigController.java
index 3bb1473..b3b284b 100644
--- a/src/com/android/settings/wifi/WifiConfigController.java
+++ b/src/com/android/settings/wifi/WifiConfigController.java
@@ -127,6 +127,7 @@
     private Spinner mSecuritySpinner;
     private Spinner mEapMethodSpinner;
     private Spinner mEapCaCertSpinner;
+    private TextView mEapDomainView;
     private Spinner mPhase2Spinner;
     // Associated with mPhase2Spinner, one of mPhase2FullAdapter or mPhase2PeapAdapter
     private ArrayAdapter<String> mPhase2Adapter;
@@ -414,7 +415,7 @@
         if (mEapCaCertSpinner != null
                 && mView.findViewById(R.id.l_ca_cert).getVisibility() != View.GONE
                 && ((String) mEapCaCertSpinner.getSelectedItem())
-                       .equals(mDoNotValidateEapServerString)) {
+                        .equals(mDoNotValidateEapServerString)) {
             // Display warning if user chooses not to validate the EAP server with a user-supplied
             // CA certificate in an EAP network configuration.
             mView.findViewById(R.id.no_ca_cert_warning).setVisibility(View.VISIBLE);
@@ -514,17 +515,22 @@
                     // Note: |caCert| should not be able to take the value |unspecifiedCert|,
                     // since we prevent such configurations from being saved.
                     config.enterpriseConfig.setCaCertificateAliases(null);
-                } else if (caCert.equals(mMultipleCertSetString)) {
-                    if (mAccessPoint != null) {
-                        if (!mAccessPoint.isSaved()) {
-                            Log.e(TAG, "Multiple certs can only be set when editing saved network");
-                        }
-                        config.enterpriseConfig.setCaCertificateAliases(
-                                mAccessPoint.getConfig().enterpriseConfig
-                                        .getCaCertificateAliases());
-                    }
                 } else {
-                    config.enterpriseConfig.setCaCertificateAliases(new String[] {caCert});
+                    config.enterpriseConfig.setDomainSuffixMatch(
+                            mEapDomainView.getText().toString());
+                    if (caCert.equals(mMultipleCertSetString)) {
+                        if (mAccessPoint != null) {
+                            if (!mAccessPoint.isSaved()) {
+                                Log.e(TAG, "Multiple certs can only be set "
+                                        + "when editing saved network");
+                            }
+                            config.enterpriseConfig.setCaCertificateAliases(
+                                    mAccessPoint.getConfig().enterpriseConfig
+                                            .getCaCertificateAliases());
+                        }
+                    } else {
+                        config.enterpriseConfig.setCaCertificateAliases(new String[] {caCert});
+                    }
                 }
 
                 String clientCert = (String) mEapUserCertSpinner.getSelectedItem();
@@ -738,6 +744,7 @@
             mPhase2Spinner = (Spinner) mView.findViewById(R.id.phase2);
             mEapCaCertSpinner = (Spinner) mView.findViewById(R.id.ca_cert);
             mEapCaCertSpinner.setOnItemSelectedListener(this);
+            mEapDomainView = (TextView) mView.findViewById(R.id.domain);
             mEapUserCertSpinner = (Spinner) mView.findViewById(R.id.user_cert);
             mEapUserCertSpinner.setOnItemSelectedListener(this);
             mEapIdentityView = (TextView) mView.findViewById(R.id.identity);
@@ -787,6 +794,7 @@
                             Credentials.CA_CERTIFICATE, true, mDoNotValidateEapServerString);
                     mEapCaCertSpinner.setSelection(MULTIPLE_CERT_SET_INDEX);
                 }
+                mEapDomainView.setText(enterpriseConfig.getDomainSuffixMatch());
                 setSelection(mEapUserCertSpinner, enterpriseConfig.getClientCertificateAlias());
                 mEapIdentityView.setText(enterpriseConfig.getIdentity());
                 mEapAnonymousView.setText(enterpriseConfig.getAnonymousIdentity());
@@ -811,6 +819,7 @@
      * EAP-TLS valid fields include
      *   user_cert
      *   ca_cert
+     *   domain
      *   identity
      * EAP-TTLS valid fields include
      *   phase2: PAP, MSCHAP, MSCHAPV2, GTC
@@ -823,6 +832,7 @@
         // Common defaults
         mView.findViewById(R.id.l_method).setVisibility(View.VISIBLE);
         mView.findViewById(R.id.l_identity).setVisibility(View.VISIBLE);
+        mView.findViewById(R.id.l_domain).setVisibility(View.VISIBLE);
 
         // Defaults for most of the EAP methods and over-riden by
         // by certain EAP methods
@@ -835,6 +845,7 @@
             case WIFI_EAP_METHOD_PWD:
                 setPhase2Invisible();
                 setCaCertInvisible();
+                setDomainInvisible();
                 setAnonymousIdentInvisible();
                 setUserCertInvisible();
                 break;
@@ -870,11 +881,22 @@
                 setPhase2Invisible();
                 setAnonymousIdentInvisible();
                 setCaCertInvisible();
+                setDomainInvisible();
                 setUserCertInvisible();
                 setPasswordInvisible();
                 setIdentityInvisible();
                 break;
         }
+
+        if (mView.findViewById(R.id.l_ca_cert).getVisibility() != View.GONE) {
+            String eapCertSelection = (String) mEapCaCertSpinner.getSelectedItem();
+            if (eapCertSelection.equals(mDoNotValidateEapServerString)
+                    || eapCertSelection.equals(mUnspecifiedCertString)) {
+                // Domain suffix matching is not relevant if the user hasn't chosen a CA
+                // certificate yet, or chooses not to validate the EAP server.
+                setDomainInvisible();
+            }
+        }
     }
 
     private void setIdentityInvisible() {
@@ -892,6 +914,11 @@
         mEapCaCertSpinner.setSelection(UNSPECIFIED_CERT_INDEX);
     }
 
+    private void setDomainInvisible() {
+        mView.findViewById(R.id.l_domain).setVisibility(View.GONE);
+        mEapDomainView.setText("");
+    }
+
     private void setUserCertInvisible() {
         mView.findViewById(R.id.l_user_cert).setVisibility(View.GONE);
         mEapUserCertSpinner.setSelection(UNSPECIFIED_CERT_INDEX);
@@ -1120,7 +1147,7 @@
         if (parent == mSecuritySpinner) {
             mAccessPointSecurity = position;
             showSecurityFields();
-        } else if (parent == mEapMethodSpinner) {
+        } else if (parent == mEapMethodSpinner || parent == mEapCaCertSpinner) {
             showSecurityFields();
         } else if (parent == mProxySettingsSpinner) {
             showProxyFields();
diff --git a/tests/unit/src/com/android/settings/TetherServiceTest.java b/tests/unit/src/com/android/settings/TetherServiceTest.java
new file mode 100644
index 0000000..09c6119
--- /dev/null
+++ b/tests/unit/src/com/android/settings/TetherServiceTest.java
@@ -0,0 +1,358 @@
+/*
+ * Copyright (C) 2016 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;
+
+import static org.junit.Assert.*;
+import static org.mockito.Matchers.*;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static android.net.ConnectivityManager.EXTRA_ADD_TETHER_TYPE;
+import static android.net.ConnectivityManager.EXTRA_PROVISION_CALLBACK;
+import static android.net.ConnectivityManager.EXTRA_REM_TETHER_TYPE;
+import static android.net.ConnectivityManager.EXTRA_RUN_PROVISION;
+import static android.net.ConnectivityManager.EXTRA_SET_ALARM;
+import static android.net.ConnectivityManager.TETHERING_BLUETOOTH;
+import static android.net.ConnectivityManager.TETHERING_INVALID;
+import static android.net.ConnectivityManager.TETHERING_USB;
+import static android.net.ConnectivityManager.TETHERING_WIFI;
+import static android.net.ConnectivityManager.TETHER_ERROR_NO_ERROR;
+import static android.net.ConnectivityManager.TETHER_ERROR_PROVISION_FAILED;
+
+import android.app.Activity;
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.Editor;
+import android.content.res.Resources;
+import android.net.ConnectivityManager;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiManager;
+import android.os.Bundle;
+import android.os.ResultReceiver;
+import android.os.SystemClock;
+import android.test.ServiceTestCase;
+import android.test.mock.MockResources;
+import android.util.Log;
+
+import com.android.settings.TetherService;
+
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.lang.ref.WeakReference;
+
+public class TetherServiceTest extends ServiceTestCase<TetherService> {
+
+    private static final String TAG = "TetherServiceTest";
+    private static final String TEST_RESPONSE_ACTION = "testProvisioningResponseAction";
+    private static final String TEST_NO_UI_ACTION = "testNoUiProvisioningRequestAction";
+    private static final int BOGUS_RECEIVER_RESULT = -5;
+    private static final int TEST_CHECK_PERIOD = 100;
+    private static final int MS_PER_HOUR = 60 * 60 * 1000;
+    private static final int SHORT_TIMEOUT = 100;
+    private static final int PROVISION_TIMEOUT = 1000;
+
+    private TetherService mService;
+    private MockResources mResources;
+    int mLastReceiverResultCode = BOGUS_RECEIVER_RESULT;
+    private int mLastTetherRequestType = TETHERING_INVALID;
+    private int mProvisionResponse = BOGUS_RECEIVER_RESULT;
+    private ProvisionReceiver mProvisionReceiver;
+    private Receiver mResultReceiver;
+
+    @Mock private AlarmManager mAlarmManager;
+    @Mock private ConnectivityManager mConnectivityManager;
+    @Mock private WifiManager mWifiManager;
+    @Mock private SharedPreferences mPrefs;
+    @Mock private Editor mPrefEditor;
+    @Captor private ArgumentCaptor<PendingIntent> mPiCaptor;
+    @Captor private ArgumentCaptor<String> mStoredTypes;
+
+    public TetherServiceTest() {
+        super(TetherService.class);
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        MockitoAnnotations.initMocks(this);
+
+        mResources = new MockResources();
+        mContext = new TestContextWrapper(getContext());
+        setContext(mContext);
+
+        mResultReceiver = new Receiver(this);
+        mLastReceiverResultCode = BOGUS_RECEIVER_RESULT;
+        mProvisionResponse = Activity.RESULT_OK;
+        mProvisionReceiver = new ProvisionReceiver();
+        IntentFilter filter = new IntentFilter(TEST_NO_UI_ACTION);
+        filter.addCategory(Intent.CATEGORY_DEFAULT);
+        mContext.registerReceiver(mProvisionReceiver, filter);
+
+        final String CURRENT_TYPES = "currentTethers";
+        when(mPrefs.getString(CURRENT_TYPES, "")).thenReturn("");
+        when(mPrefs.edit()).thenReturn(mPrefEditor);
+        when(mPrefEditor.putString(eq(CURRENT_TYPES), mStoredTypes.capture())).thenReturn(
+                mPrefEditor);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        mContext.unregisterReceiver(mProvisionReceiver);
+        super.tearDown();
+    }
+
+    private void cancelAllProvisioning() {
+        int[] types = new int[]{TETHERING_BLUETOOTH, TETHERING_WIFI, TETHERING_USB};
+        for (int type : types) {
+            Intent intent = new Intent();
+            intent.putExtra(EXTRA_REM_TETHER_TYPE, type);
+            startService(intent);
+        }
+    }
+
+    public void testStartForProvision() {
+        runProvisioningForType(TETHERING_WIFI);
+
+        assertTrue(waitForProvisionRequest(TETHERING_WIFI));
+        assertTrue(waitForProvisionResponse(TETHER_ERROR_NO_ERROR));
+    }
+
+    public void testScheduleRechecks() {
+        Intent intent = new Intent();
+        intent.putExtra(EXTRA_ADD_TETHER_TYPE, TETHERING_WIFI);
+        intent.putExtra(EXTRA_SET_ALARM, true);
+        startService(intent);
+
+        long period = TEST_CHECK_PERIOD * MS_PER_HOUR;
+        verify(mAlarmManager).setRepeating(eq(AlarmManager.ELAPSED_REALTIME), anyLong(),
+                eq(period), mPiCaptor.capture());
+        PendingIntent pi = mPiCaptor.getValue();
+        assertEquals(TetherService.class.getName(), pi.getIntent().getComponent().getClassName());
+    }
+
+    public void testStartMultiple() {
+        runProvisioningForType(TETHERING_WIFI);
+
+        assertTrue(waitForProvisionRequest(TETHERING_WIFI));
+        assertTrue(waitForProvisionResponse(TETHER_ERROR_NO_ERROR));
+
+        runProvisioningForType(TETHERING_USB);
+
+        assertTrue(waitForProvisionRequest(TETHERING_USB));
+        assertTrue(waitForProvisionResponse(TETHER_ERROR_NO_ERROR));
+
+        runProvisioningForType(TETHERING_BLUETOOTH);
+
+        assertTrue(waitForProvisionRequest(TETHERING_BLUETOOTH));
+        assertTrue(waitForProvisionResponse(TETHER_ERROR_NO_ERROR));
+    }
+
+    public void testPersistTypes() {
+        runProvisioningForType(TETHERING_WIFI);
+
+        waitForProvisionRequest(TETHERING_WIFI);
+        waitForProvisionResponse(TETHER_ERROR_NO_ERROR);
+
+        runProvisioningForType(TETHERING_BLUETOOTH);
+
+        waitForProvisionRequest(TETHERING_BLUETOOTH);
+        waitForProvisionResponse(TETHER_ERROR_NO_ERROR);
+
+        shutdownService();
+        assertEquals(TETHERING_WIFI + "," + TETHERING_BLUETOOTH, mStoredTypes.getValue());
+    }
+
+    public void testFailureStopsTethering_Wifi() {
+        mProvisionResponse = Activity.RESULT_CANCELED;
+
+        runProvisioningForType(TETHERING_WIFI);
+
+        assertTrue(waitForProvisionRequest(TETHERING_WIFI));
+        assertTrue(waitForProvisionResponse(TETHER_ERROR_PROVISION_FAILED));
+
+        verify(mWifiManager).setWifiApEnabled(isNull(WifiConfiguration.class), eq(false));
+    }
+
+    public void testFailureStopsTethering_Usb() {
+        mProvisionResponse = Activity.RESULT_CANCELED;
+
+        runProvisioningForType(TETHERING_USB);
+
+        assertTrue(waitForProvisionRequest(TETHERING_USB));
+        assertTrue(waitForProvisionResponse(TETHER_ERROR_PROVISION_FAILED));
+
+        verify(mConnectivityManager).setUsbTethering(eq(false));
+    }
+
+    public void testCancelAlarm() {
+        runProvisioningForType(TETHERING_WIFI);
+
+        assertTrue(waitForProvisionRequest(TETHERING_WIFI));
+        assertTrue(waitForProvisionResponse(TETHER_ERROR_NO_ERROR));
+
+        Intent intent = new Intent();
+        intent.putExtra(EXTRA_REM_TETHER_TYPE, TETHERING_WIFI);
+        startService(intent);
+
+        verify(mAlarmManager).cancel(mPiCaptor.capture());
+        PendingIntent pi = mPiCaptor.getValue();
+        assertEquals(TetherService.class.getName(), pi.getIntent().getComponent().getClassName());
+    }
+
+    private void runProvisioningForType(int type) {
+        Intent intent = new Intent();
+        intent.putExtra(EXTRA_ADD_TETHER_TYPE, type);
+        intent.putExtra(EXTRA_RUN_PROVISION, true);
+        intent.putExtra(EXTRA_PROVISION_CALLBACK, mResultReceiver);
+        startService(intent);
+    }
+
+    private boolean waitForProvisionRequest(int expectedType) {
+        long startTime = SystemClock.uptimeMillis();
+        while (true) {
+            if (mLastTetherRequestType == expectedType) {
+                mLastTetherRequestType = -1;
+                return true;
+            }
+            if ((SystemClock.uptimeMillis() - startTime) > PROVISION_TIMEOUT) {
+                Log.v(TAG, String.format(
+                        "waitForProvisionRequest timeout: expected=%d, actual=%d",
+                        expectedType, mLastTetherRequestType));
+                return false;
+            }
+            SystemClock.sleep(SHORT_TIMEOUT);
+        }
+    }
+
+    private boolean waitForProvisionResponse(int expectedValue) {
+        long startTime = SystemClock.uptimeMillis();
+        while (true) {
+            if (mLastReceiverResultCode == expectedValue) {
+                mLastReceiverResultCode = BOGUS_RECEIVER_RESULT;
+                return true;
+            }
+            if ((SystemClock.uptimeMillis() - startTime) > PROVISION_TIMEOUT) {
+                Log.v(TAG, String.format(
+                        "waitForProvisionResponse timeout: expected=%d, actual=%d",
+                        expectedValue, mLastReceiverResultCode));
+                return false;
+            }
+            SystemClock.sleep(SHORT_TIMEOUT);
+        }
+    }
+
+    private static class MockResources extends android.test.mock.MockResources {
+        @Override
+        public int getInteger(int id) {
+            switch(id) {
+                case com.android.internal.R.integer.config_mobile_hotspot_provision_check_period:
+                    return TEST_CHECK_PERIOD;
+                default:
+                    return 0;
+            }
+        }
+
+        @Override
+        public String getString(int id) {
+            switch(id) {
+                case com.android.internal.R.string.config_mobile_hotspot_provision_response:
+                    return TEST_RESPONSE_ACTION;
+                case com.android.internal.R.string.config_mobile_hotspot_provision_app_no_ui:
+                    return TEST_NO_UI_ACTION;
+                default:
+                    return null;
+            }
+        }
+    }
+
+    private class TestContextWrapper extends ContextWrapper {
+
+        public TestContextWrapper(Context base) {
+            super(base);
+        }
+
+        @Override
+        public Resources getResources() {
+            return mResources;
+        }
+
+        @Override
+        public SharedPreferences getSharedPreferences(String name, int mode) {
+            // Stub out prefs to control the persisted tether type list.
+            if (name == "tetherPrefs") {
+                return mPrefs;
+            }
+            return super.getSharedPreferences(name, mode);
+        }
+
+        @Override
+        public Object getSystemService(String name) {
+            if (ALARM_SERVICE.equals(name)) {
+                return mAlarmManager;
+            } else if (CONNECTIVITY_SERVICE.equals(name)) {
+                return mConnectivityManager;
+            } else if (WIFI_SERVICE.equals(name)) {
+                return mWifiManager;
+            }
+
+            return super.getSystemService(name);
+        }
+    }
+
+    private static final class Receiver extends ResultReceiver {
+        final WeakReference<TetherServiceTest> mTest;
+
+        Receiver(TetherServiceTest test) {
+            super(null);
+            mTest = new WeakReference<TetherServiceTest>(test);
+        }
+
+        @Override
+        protected void onReceiveResult(int resultCode, Bundle resultData) {
+            TetherServiceTest test = mTest.get();
+            if (test != null) {
+                test.mLastReceiverResultCode = resultCode;
+            }
+        }
+    };
+
+    /**
+     * Stubs out the provisioning app receiver.
+     */
+    private class ProvisionReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            mLastTetherRequestType = intent.getIntExtra("TETHER_TYPE", TETHERING_INVALID);
+            sendResponse(mProvisionResponse, context);
+        }
+
+        private void sendResponse(int response, Context context) {
+            Intent responseIntent = new Intent(TEST_RESPONSE_ACTION);
+            responseIntent.putExtra(TetherService.EXTRA_RESULT, response);
+            context.sendBroadcast(
+                    responseIntent, android.Manifest.permission.CONNECTIVITY_INTERNAL);
+        }
+    }
+}