Merge "Import translations. DO NOT MERGE"
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 5038177..54ed1df 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -3986,12 +3986,6 @@
     <string name="accessibility_screen_magnification_summary"><b>To zoom</b>, quickly tap the screen 3 times with one finger.\n<ul><li>Drag 2 or more fingers to scroll</li>\n<li>Pinch 2 or more fingers together or apart to adjust zoom</li></ul>\n\n<b>To zoom temporarily</b>, quickly tap the screen 3 times and hold down your finger on the third tap.\n<ul><li>Drag your finger to move around the screen</li>\n<li>Lift your finger to zoom out</li></ul>\n\nYou can\'t zoom in on the keyboard and navigation bar.</string>
     <!-- Title for the preference to enable the global geture that turns on accessibility. [CHAR LIMIT=35] -->
     <string name="accessibility_global_gesture_preference_title">Accessibility shortcut</string>
-    <!-- Summary for the preference to enable the global geture that turns on accessibility (on state). [CHAR LIMIT=60] -->
-    <string name="accessibility_global_gesture_preference_summary_on">On</string>
-    <!-- Summary for the preference screen to enable the global geture that turns on accessibility (off state). [CHAR LIMIT=35] -->
-    <string name="accessibility_global_gesture_preference_summary_off">Off</string>
-    <!--  Description for the preference screen to enable the global geture taht turns on accessibility. [CHAR LIMIT=none] -->
-    <string name="accessibility_global_gesture_preference_description">When this feature is turned on, you can quickly activate accessibility features in two steps:\n\nStep 1: Press and hold the power button until you hear a sound or feel a vibration.\n\nStep 2: Touch and hold two fingers until you hear audio confirmation.\n\nIf the device has multiple users, using this shortcut on the lock screen temporarily enables accessibility until the device is unlocked.</string>
     <!-- Title for the accessibility preference to high contrast text. [CHAR LIMIT=35] -->
     <string name="accessibility_toggle_high_text_contrast_preference_title">High contrast text</string>
     <!-- Title for the accessibility preference to auto update screen magnification. [CHAR LIMIT=35] -->
@@ -6165,6 +6159,11 @@
     <!-- Work Sound: Message for dialog shown when enabling sync with personal sounds. [CHAR LIMIT=none] -->
     <string name="work_sync_dialog_message">Your current work profile sounds will be replaced with your personal profile sounds</string>
 
+    <!-- Ringtones preference category: Title for the Ringotnes preference categories. [CHAR LIMIT=none] -->
+    <string name="ringtones_category_preference_title">Ringtones</string>
+
+    <!-- Other sounds and vibrations preference category: Title for the Other sounds and vibrations preference categories. [CHAR LIMIT=none] -->
+    <string name="other_sound_category_preference_title">Other sounds and vibrations</string>
 
     <!-- Configure Notifications Settings title. [CHAR LIMIT=30] -->
     <string name="configure_notification_settings">Notification preferences</string>
@@ -8007,6 +8006,26 @@
     <string name="enterprise_privacy_security_logs">Your most recent security log</string>
     <!-- Label indicating that the date at which the admin last took a particular action was "never" (i.e. the admin never took the action so far). -->
     <string name="enterprise_privacy_never">Never</string>
+    <!-- Label indicating how many apps were installed on the device by the admin. [CHAR LIMIT=NONE] -->
+    <plurals name="enterprise_privacy_number_enterprise_installed_packages">
+        <item quantity="one"><xliff:g id="count">%d</xliff:g> app installed by your admin</item>
+        <item quantity="other"><xliff:g id="count">%d</xliff:g> apps installed by your admin</item>
+    </plurals>
+    <!-- Label indicating how many apps were granted permission to access the device's location by the admin. [CHAR LIMIT=NONE] -->
+    <plurals name="enterprise_privacy_number_location_access_packages">
+        <item quantity="one"><xliff:g id="count">%d</xliff:g> app allowed access to your location by your admin</item>
+        <item quantity="other"><xliff:g id="count">%d</xliff:g> apps allowed access to your location by your admin</item>
+    </plurals>
+    <!-- Label indicating how many apps were granted permission to access the microphone by the admin. [CHAR LIMIT=NONE] -->
+    <plurals name="enterprise_privacy_number_microphone_access_packages">
+        <item quantity="one"><xliff:g id="count">%d</xliff:g> app allowed access to your microphone by your admin</item>
+        <item quantity="other"><xliff:g id="count">%d</xliff:g> apps allowed access to your microphone by your admin</item>
+    </plurals>
+    <!-- Label indicating how many apps were granted permission to access the camera by the admin. [CHAR LIMIT=NONE] -->
+    <plurals name="enterprise_privacy_number_camera_access_packages">
+        <item quantity="one"><xliff:g id="count">%d</xliff:g> app allowed access to your camera by your admin</item>
+        <item quantity="other"><xliff:g id="count">%d</xliff:g> apps allowed access to your camera by your admin</item>
+    </plurals>
     <!-- Label explaining that an always-on VPN was set by the admin for the entire device. [CHAR LIMIT=NONE] -->
     <string name="enterprise_privacy_always_on_vpn_device">Always-on VPN turned on</string>
     <!-- Label explaining that an always-on VPN was set by the admin in the personal profile. [CHAR LIMIT=NONE] -->
@@ -8015,11 +8034,6 @@
     <string name="enterprise_privacy_always_on_vpn_work">Always-on VPN turned on in your work profile</string>
     <!-- Label explaining that a global HTTP proxy was set by the admin. [CHAR LIMIT=NONE] -->
     <string name="enterprise_privacy_global_http_proxy">Global HTTP proxy set</string>
-    <!-- Label indicating how many apps were installed on the device by the admin. [CHAR LIMIT=NONE] -->
-    <plurals name="enterprise_privacy_number_enterprise_installed_packages">
-        <item quantity="one"><xliff:g id="count">%d</xliff:g> app installed by your admin</item>
-        <item quantity="other"><xliff:g id="count">%d</xliff:g> apps installed by your admin</item>
-    </plurals>
 
     <!-- Preference label for the Photos & Videos storage section. [CHAR LIMIT=50] -->
     <string name="storage_photos_videos">Photos &amp; Videos</string>
diff --git a/res/xml/accessibility_settings.xml b/res/xml/accessibility_settings.xml
index 8bb54c6..fb9e171 100644
--- a/res/xml/accessibility_settings.xml
+++ b/res/xml/accessibility_settings.xml
@@ -81,9 +81,8 @@
                 android:summary="@string/accessibility_toggle_master_mono_summary"
                 android:persistent="false"/>
 
-        <Preference
-                android:fragment="com.android.settings.accessibility.ToggleGlobalGesturePreferenceFragment"
-                android:key="enable_global_gesture_preference_screen"
+        <ListPreference
+                android:key="accessibility_shortcut_preference"
                 android:title="@string/accessibility_global_gesture_preference_title"/>
 
         <Preference
diff --git a/res/xml/enterprise_privacy_settings.xml b/res/xml/enterprise_privacy_settings.xml
index 9fed938..4bb2a8c 100644
--- a/res/xml/enterprise_privacy_settings.xml
+++ b/res/xml/enterprise_privacy_settings.xml
@@ -60,6 +60,22 @@
 
     <PreferenceCategory android:title="@string/enterprise_privacy_exposure_changes_category">
         <com.android.settings.DividerPreference
+                android:key="number_enterprise_installed_packages"
+                settings:allowDividerBelow="true"
+                settings:multiLine="true"/>
+        <com.android.settings.DividerPreference
+                android:key="enterprise_privacy_number_location_access_packages"
+                settings:allowDividerBelow="true"
+                settings:multiLine="true"/>
+        <com.android.settings.DividerPreference
+                android:key="enterprise_privacy_number_microphone_access_packages"
+                settings:allowDividerBelow="true"
+                settings:multiLine="true"/>
+        <com.android.settings.DividerPreference
+                android:key="enterprise_privacy_number_camera_access_packages"
+                settings:allowDividerBelow="true"
+                settings:multiLine="true"/>
+        <com.android.settings.DividerPreference
                 android:key="always_on_vpn_primary_user"
                 settings:allowDividerBelow="true"
                 settings:multiLine="true"/>
@@ -73,10 +89,6 @@
                 android:title="@string/enterprise_privacy_global_http_proxy"
                 settings:allowDividerBelow="true"
                 settings:multiLine="true"/>
-        <com.android.settings.DividerPreference
-                android:key="number_enterprise_installed_packages"
-                settings:allowDividerBelow="true"
-                settings:multiLine="true"/>
     </PreferenceCategory>
 
     <PreferenceCategory android:title="@string/enterprise_privacy_device_access_category">
diff --git a/res/xml/ia_sound_settings.xml b/res/xml/ia_sound_settings.xml
new file mode 100644
index 0000000..e63db0d
--- /dev/null
+++ b/res/xml/ia_sound_settings.xml
@@ -0,0 +1,152 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 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.
+-->
+
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+                  xmlns:settings="http://schemas.android.com/apk/res/com.android.settings"
+          android:title="@string/sound_settings"
+          android:key="sound_settings"
+          settings:keywords="@string/keywords_sounds">
+
+        <!-- Media volume -->
+        <com.android.settings.notification.VolumeSeekBarPreference
+                android:key="media_volume"
+                android:icon="@*android:drawable/ic_audio_media"
+                android:title="@string/media_volume_option_title" />
+
+        <!-- Alarm volume -->
+        <com.android.settings.notification.VolumeSeekBarPreference
+                android:key="alarm_volume"
+                android:icon="@*android:drawable/ic_audio_alarm"
+                android:title="@string/alarm_volume_option_title" />
+
+        <!-- Ring volume -->
+        <com.android.settings.notification.VolumeSeekBarPreference
+                android:key="ring_volume"
+                android:icon="@*android:drawable/ic_audio_ring_notif"
+                android:title="@string/ring_volume_option_title" />
+
+        <!-- Notification volume -->
+        <com.android.settings.notification.VolumeSeekBarPreference
+                android:key="notification_volume"
+                android:icon="@*android:drawable/ic_audio_ring_notif"
+                android:title="@string/notification_volume_option_title" />
+
+        <!-- Also vibrate for calls -->
+        <SwitchPreference
+                android:key="vibrate_when_ringing"
+                android:title="@string/vibrate_when_ringing_title" />
+
+
+        <!-- Interruptions -->
+        <com.android.settingslib.RestrictedPreference
+                android:key="zen_mode"
+                android:title="@string/zen_mode_settings_title"
+                settings:useAdminDisabledSummary="true"
+                settings:keywords="@string/keywords_sounds_and_notifications_interruptions"
+                android:fragment="com.android.settings.notification.ZenModeSettings" />
+
+        <PreferenceCategory
+          android:key="ringtones_preferecence_category"
+          android:title="@string/ringtones_category_preference_title" />
+
+        <!-- Phone ringtone -->
+        <com.android.settings.DefaultRingtonePreference
+                android:key="ringtone"
+                android:title="@string/ringtone_title"
+                android:dialogTitle="@string/ringtone_title"
+                android:summary="@string/ringtone_summary"
+                android:ringtoneType="ringtone" />
+
+        <!-- Default notification ringtone -->
+        <com.android.settings.DefaultRingtonePreference
+                android:key="notification_ringtone"
+                android:title="@string/notification_ringtone_title"
+                android:dialogTitle="@string/notification_ringtone_title"
+                android:summary="@string/ringtone_summary"
+                android:ringtoneType="notification" />
+
+        <!-- Default alarm ringtone -->
+        <com.android.settings.DefaultRingtonePreference
+                android:key="alarm_ringtone"
+                android:title="@string/alarm_ringtone_title"
+                android:dialogTitle="@string/alarm_ringtone_title"
+                android:summary="@string/ringtone_summary"
+                android:persistent="false"
+                android:ringtoneType="alarm" />
+
+        <!-- Other sounds -->
+        <PreferenceCategory
+          android:key="other_sound_preferecence_category"
+          android:title="@string/other_sound_category_preference_title" />
+
+        <!-- Dial pad tones -->
+        <SwitchPreference
+          android:key="dial_pad_tones"
+          android:title="@string/dial_pad_tones_title" />
+
+        <!-- Screen locking sounds -->
+        <SwitchPreference
+          android:key="screen_locking_sounds"
+          android:title="@string/screen_locking_sounds_title" />
+
+        <!-- Charging sounds -->
+        <SwitchPreference
+          android:key="charging_sounds"
+          android:title="@string/charging_sounds_title" />
+
+        <!-- Docking sounds -->
+        <SwitchPreference
+          android:key="docking_sounds"
+          android:title="@string/docking_sounds_title" />
+
+        <!-- Touch sounds -->
+        <SwitchPreference
+          android:key="touch_sounds"
+          android:title="@string/touch_sounds_title" />
+
+        <!-- Vibrate on touch -->
+        <SwitchPreference
+          android:key="vibrate_on_touch"
+          android:title="@string/vibrate_on_touch_title" />
+
+        <!-- Dock speaker plays -->
+        <DropDownPreference
+          android:key="dock_audio_media"
+          android:title="@string/dock_audio_media_title"
+          android:summary="%s" />
+
+        <!-- Boot sounds -->
+        <SwitchPreference
+          android:key="boot_sounds"
+          android:title="@string/boot_sounds_title" />
+
+        <!-- Emergency tone -->
+        <DropDownPreference
+          android:key="emergency_tone"
+          android:title="@string/emergency_tone_title"
+          android:summary="%s" />
+
+        <com.android.settingslib.RestrictedPreference
+          android:key="cell_broadcast_settings"
+          android:title="@string/cell_broadcast_settings"
+          settings:useAdminDisabledSummary="true">
+                <intent
+                  android:action="android.intent.action.MAIN"
+                  android:targetPackage="com.android.cellbroadcastreceiver"
+                  android:targetClass="com.android.cellbroadcastreceiver.CellBroadcastSettings" />
+        </com.android.settingslib.RestrictedPreference>
+
+</PreferenceScreen>
diff --git a/src/com/android/settings/DevelopmentSettings.java b/src/com/android/settings/DevelopmentSettings.java
index 27ff937..8184905 100644
--- a/src/com/android/settings/DevelopmentSettings.java
+++ b/src/com/android/settings/DevelopmentSettings.java
@@ -1779,49 +1779,50 @@
 
     private void initBluetoothConfigurationValues() {
         String[] values;
-        String[] titles;
+        String[] summaries;
         int index;
 
         // Init the Codec Type - Default
         values = getResources().getStringArray(R.array.bluetooth_a2dp_codec_values);
-        titles = getResources().getStringArray(R.array.bluetooth_a2dp_codec_titles);
+        summaries = getResources().getStringArray(R.array.bluetooth_a2dp_codec_summaries);
         index = 0;
         mBluetoothSelectA2dpCodec.setValue(values[index]);
-        mBluetoothSelectA2dpCodec.setSummary(titles[index]);
+        mBluetoothSelectA2dpCodec.setSummary(summaries[index]);
 
         // Init the Sample Rate - Default
         values = getResources().getStringArray(R.array.bluetooth_a2dp_codec_sample_rate_values);
-        titles = getResources().getStringArray(R.array.bluetooth_a2dp_codec_sample_rate_titles);
+        summaries = getResources().getStringArray(R.array.bluetooth_a2dp_codec_sample_rate_summaries);
         index = 0;
         mBluetoothSelectA2dpSampleRate.setValue(values[index]);
-        mBluetoothSelectA2dpSampleRate.setSummary(titles[index]);
+        mBluetoothSelectA2dpSampleRate.setSummary(summaries[index]);
 
         // Init the Bits Per Sample - Default
         values = getResources().getStringArray(R.array.bluetooth_a2dp_codec_bits_per_sample_values);
-        titles = getResources().getStringArray(R.array.bluetooth_a2dp_codec_bits_per_sample_titles);
+        summaries = getResources().getStringArray(R.array.bluetooth_a2dp_codec_bits_per_sample_summaries);
         index = 0;
         mBluetoothSelectA2dpBitsPerSample.setValue(values[index]);
-        mBluetoothSelectA2dpBitsPerSample.setSummary(titles[index]);
+        mBluetoothSelectA2dpBitsPerSample.setSummary(summaries[index]);
 
         // Init the Channel Mode - Default
         values = getResources().getStringArray(R.array.bluetooth_a2dp_codec_channel_mode_values);
-        titles = getResources().getStringArray(R.array.bluetooth_a2dp_codec_channel_mode_titles);
+        summaries = getResources().getStringArray(R.array.bluetooth_a2dp_codec_channel_mode_summaries);
         index = 0;
         mBluetoothSelectA2dpChannelMode.setValue(values[index]);
-        mBluetoothSelectA2dpChannelMode.setSummary(titles[index]);
+        mBluetoothSelectA2dpChannelMode.setSummary(summaries[index]);
 
         // Init the LDAC Playback Quality - High
         values = getResources().getStringArray(R.array.bluetooth_a2dp_codec_ldac_playback_quality_values);
-        titles = getResources().getStringArray(R.array.bluetooth_a2dp_codec_ldac_playback_quality_titles);
+        summaries = getResources().getStringArray(R.array.bluetooth_a2dp_codec_ldac_playback_quality_summaries);
         index = 0;
         mBluetoothSelectA2dpLdacPlaybackQuality.setValue(values[index]);
-        mBluetoothSelectA2dpLdacPlaybackQuality.setSummary(titles[index]);
+        mBluetoothSelectA2dpLdacPlaybackQuality.setSummary(summaries[index]);
     }
 
     private void updateBluetoothA2dpConfigurationValues() {
         int index;
-        String[] titles;
+        String[] summaries;
         BluetoothCodecConfig codecConfig = null;
+        String streaming;
 
         synchronized (mBluetoothA2dpLock) {
             if (mBluetoothA2dp != null) {
@@ -1851,8 +1852,9 @@
             break;
         }
         if (index >= 0) {
-            titles = getResources().getStringArray(R.array.bluetooth_a2dp_codec_titles);
-            mBluetoothSelectA2dpCodec.setSummary("Streaming: " + titles[index]);
+            summaries = getResources().getStringArray(R.array.bluetooth_a2dp_codec_summaries);
+            streaming = getResources().getString(R.string.bluetooth_select_a2dp_codec_streaming_label, summaries[index]);
+            mBluetoothSelectA2dpCodec.setSummary(streaming);
         }
 
         // Update the Sample Rate
@@ -1877,8 +1879,9 @@
             break;
         }
         if (index >= 0) {
-            titles = getResources().getStringArray(R.array.bluetooth_a2dp_codec_sample_rate_titles);
-            mBluetoothSelectA2dpSampleRate.setSummary("Streaming: " + titles[index]);
+            summaries = getResources().getStringArray(R.array.bluetooth_a2dp_codec_sample_rate_summaries);
+            streaming = getResources().getString(R.string.bluetooth_select_a2dp_codec_streaming_label, summaries[index]);
+             mBluetoothSelectA2dpSampleRate.setSummary(streaming);
         }
 
         // Update the Bits Per Sample
@@ -1898,8 +1901,9 @@
             break;
         }
         if (index >= 0) {
-            titles = getResources().getStringArray(R.array.bluetooth_a2dp_codec_bits_per_sample_titles);
-            mBluetoothSelectA2dpBitsPerSample.setSummary("Streaming: " + titles[index]);
+            summaries = getResources().getStringArray(R.array.bluetooth_a2dp_codec_bits_per_sample_summaries);
+            streaming = getResources().getString(R.string.bluetooth_select_a2dp_codec_streaming_label, summaries[index]);
+            mBluetoothSelectA2dpBitsPerSample.setSummary(streaming);
         }
 
         // Update the Channel Mode
@@ -1916,34 +1920,41 @@
             break;
         }
         if (index >= 0) {
-            titles = getResources().getStringArray(R.array.bluetooth_a2dp_codec_channel_mode_titles);
-            mBluetoothSelectA2dpChannelMode.setSummary("Streaming: " + titles[index]);
+            summaries = getResources().getStringArray(R.array.bluetooth_a2dp_codec_channel_mode_summaries);
+            streaming = getResources().getString(R.string.bluetooth_select_a2dp_codec_streaming_label, summaries[index]);
+             mBluetoothSelectA2dpChannelMode.setSummary(streaming);
         }
 
         // Update the LDAC Playback Quality
-        index = -1;
-        switch ((int)codecConfig.getCodecSpecific1()) {
-        case 1000:
-            index = 0;
-            break;
-        case 1001:
-            index = 1;
-            break;
-        case 1002:
-            index = 2;
+        // The actual values are 0, 1, 2 - those are extracted
+        // as mod-10 remainders of a larger value.
+        // The reason is because within BluetoothCodecConfig we cannot use
+        // a codec-specific value of zero.
+        index = (int)codecConfig.getCodecSpecific1();
+        if (index > 0) {
+            index %= 10;
+        } else {
+            index = -1;
+        }
+        switch (index) {
+        case 0:
+        case 1:
+        case 2:
             break;
         default:
+            index = -1;
             break;
         }
         if (index >= 0) {
-            titles = getResources().getStringArray(R.array.bluetooth_a2dp_codec_ldac_playback_quality_titles);
-            mBluetoothSelectA2dpLdacPlaybackQuality.setSummary("Streaming: " + titles[index]);
+            summaries = getResources().getStringArray(R.array.bluetooth_a2dp_codec_ldac_playback_quality_summaries);
+            streaming = getResources().getString(R.string.bluetooth_select_a2dp_codec_streaming_label, summaries[index]);
+            mBluetoothSelectA2dpLdacPlaybackQuality.setSummary(streaming);
         }
     }
 
     private void writeBluetoothConfigurationOption(Preference preference,
                                                    Object newValue) {
-        String[] titles;
+        String[] summaries;
         int index;
         int codecTypeValue = BluetoothCodecConfig.SOURCE_CODEC_TYPE_INVALID;
         int codecPriorityValue = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
@@ -1961,8 +1972,8 @@
             codecType = newValue.toString();
             index = mBluetoothSelectA2dpCodec.findIndexOfValue(newValue.toString());
             if (index >= 0) {
-                titles = getResources().getStringArray(R.array.bluetooth_a2dp_codec_titles);
-                mBluetoothSelectA2dpCodec.setSummary(titles[index]);
+                summaries = getResources().getStringArray(R.array.bluetooth_a2dp_codec_summaries);
+                mBluetoothSelectA2dpCodec.setSummary(summaries[index]);
             }
         }
         index = mBluetoothSelectA2dpCodec.findIndexOfValue(codecType);
@@ -2015,8 +2026,8 @@
             sampleRate = newValue.toString();
             index = mBluetoothSelectA2dpSampleRate.findIndexOfValue(newValue.toString());
             if (index >= 0) {
-                titles = getResources().getStringArray(R.array.bluetooth_a2dp_codec_sample_rate_titles);
-                mBluetoothSelectA2dpSampleRate.setSummary(titles[index]);
+                summaries = getResources().getStringArray(R.array.bluetooth_a2dp_codec_sample_rate_summaries);
+                mBluetoothSelectA2dpSampleRate.setSummary(summaries[index]);
             }
         }
         index = mBluetoothSelectA2dpSampleRate.findIndexOfValue(sampleRate);
@@ -2046,8 +2057,8 @@
             bitsPerSample = newValue.toString();
             index = mBluetoothSelectA2dpBitsPerSample.findIndexOfValue(newValue.toString());
             if (index >= 0) {
-                titles = getResources().getStringArray(R.array.bluetooth_a2dp_codec_bits_per_sample_titles);
-                mBluetoothSelectA2dpBitsPerSample.setSummary(titles[index]);
+                summaries = getResources().getStringArray(R.array.bluetooth_a2dp_codec_bits_per_sample_summaries);
+                mBluetoothSelectA2dpBitsPerSample.setSummary(summaries[index]);
             }
         }
         index = mBluetoothSelectA2dpBitsPerSample.findIndexOfValue(bitsPerSample);
@@ -2074,8 +2085,8 @@
             channelMode = newValue.toString();
             index = mBluetoothSelectA2dpChannelMode.findIndexOfValue(newValue.toString());
             if (index >= 0) {
-                titles = getResources().getStringArray(R.array.bluetooth_a2dp_codec_channel_mode_titles);
-                mBluetoothSelectA2dpChannelMode.setSummary(titles[index]);
+                summaries = getResources().getStringArray(R.array.bluetooth_a2dp_codec_channel_mode_summaries);
+                mBluetoothSelectA2dpChannelMode.setSummary(summaries[index]);
             }
         }
         index = mBluetoothSelectA2dpChannelMode.findIndexOfValue(channelMode);
@@ -2099,20 +2110,16 @@
             ldacPlaybackQuality = newValue.toString();
             index = mBluetoothSelectA2dpLdacPlaybackQuality.findIndexOfValue(newValue.toString());
             if (index >= 0) {
-                titles = getResources().getStringArray(R.array.bluetooth_a2dp_codec_ldac_playback_quality_titles);
-                mBluetoothSelectA2dpLdacPlaybackQuality.setSummary(titles[index]);
+                summaries = getResources().getStringArray(R.array.bluetooth_a2dp_codec_ldac_playback_quality_summaries);
+                mBluetoothSelectA2dpLdacPlaybackQuality.setSummary(summaries[index]);
             }
         }
         index = mBluetoothSelectA2dpLdacPlaybackQuality.findIndexOfValue(ldacPlaybackQuality);
         switch (index) {
         case 0:
-            codecSpecific1Value = 1000;
-            break;
         case 1:
-            codecSpecific1Value = 1001;
-            break;
         case 2:
-            codecSpecific1Value = 1002;
+            codecSpecific1Value = 1000 + index;
             break;
         default:
             break;
diff --git a/src/com/android/settings/DeviceAdminAdd.java b/src/com/android/settings/DeviceAdminAdd.java
index 14d42c0..d6a072c 100644
--- a/src/com/android/settings/DeviceAdminAdd.java
+++ b/src/com/android/settings/DeviceAdminAdd.java
@@ -58,6 +58,9 @@
 import android.widget.ImageView;
 import android.widget.TextView;
 
+import com.android.internal.logging.nano.MetricsProto;
+import com.android.settings.core.instrumentation.MetricsFeatureProvider;
+import com.android.settings.overlay.FeatureFactory;
 import com.android.settings.users.UserDialogs;
 
 import org.xmlpull.v1.XmlPullParserException;
@@ -395,6 +398,7 @@
 
     void addAndFinish() {
         try {
+            logSpecialPermissionChange(true, mDeviceAdmin.getComponent().getPackageName());
             mDPM.setActiveAdmin(mDeviceAdmin.getComponent(), mRefreshing);
             EventLog.writeEvent(EventLogTags.EXP_DET_DEVICE_ADMIN_ACTIVATED_BY_USER,
                 mDeviceAdmin.getActivityInfo().applicationInfo.uid);
@@ -429,6 +433,7 @@
                 ActivityManager.getService().resumeAppSwitches();
             } catch (RemoteException e) {
             }
+            logSpecialPermissionChange(false, mDeviceAdmin.getComponent().getPackageName());
             mDPM.removeActiveAdmin(mDeviceAdmin.getComponent());
             finish();
         } else {
@@ -444,6 +449,12 @@
         }
     }
 
+    void logSpecialPermissionChange(boolean allow, String packageName) {
+        int logCategory = allow ? MetricsProto.MetricsEvent.APP_SPECIAL_PERMISSION_ADMIN_ALLOW :
+                MetricsProto.MetricsEvent.APP_SPECIAL_PERMISSION_ADMIN_DENY;
+        FeatureFactory.getFactory(this).getMetricsFeatureProvider().action(this, logCategory, packageName);
+    }
+
     @Override
     protected void onResume() {
         super.onResume();
diff --git a/src/com/android/settings/accessibility/AccessibilityServiceWarning.java b/src/com/android/settings/accessibility/AccessibilityServiceWarning.java
new file mode 100644
index 0000000..2d33902
--- /dev/null
+++ b/src/com/android/settings/accessibility/AccessibilityServiceWarning.java
@@ -0,0 +1,157 @@
+/*
+ * 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.accessibility;
+
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.storage.StorageManager;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.android.settings.R;
+
+import java.util.List;
+
+/**
+ * Utility class for creating the dialog that asks users for explicit permission to grant
+ * all of the requested capabilities to an accessibility service before the service is enabled
+ */
+public class AccessibilityServiceWarning {
+    public static Dialog createCapabilitiesDialog(Activity parentActivity,
+            AccessibilityServiceInfo info, DialogInterface.OnClickListener listener) {
+        final AlertDialog ad = new AlertDialog.Builder(parentActivity)
+                .setTitle(parentActivity.getString(R.string.enable_service_title,
+                        info.getResolveInfo().loadLabel(parentActivity.getPackageManager())))
+                .setView(createEnableDialogContentView(parentActivity, info))
+                .setCancelable(true)
+                .setPositiveButton(android.R.string.ok, listener)
+                .setNegativeButton(android.R.string.cancel, listener)
+                .create();
+
+        final View.OnTouchListener filterTouchListener = (View v, MotionEvent event) -> {
+            // Filter obscured touches by consuming them.
+            if ((event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0) {
+                if (event.getAction() == MotionEvent.ACTION_UP) {
+                    Toast.makeText(v.getContext(), R.string.touch_filtered_warning,
+                            Toast.LENGTH_SHORT).show();
+                }
+                return true;
+            }
+            return false;
+        };
+
+        ad.create();
+        ad.getButton(AlertDialog.BUTTON_POSITIVE).setOnTouchListener(filterTouchListener);
+        return ad;
+    }
+
+    /**
+     * Return whether the device is encrypted with legacy full disk encryption. Newer devices
+     * should be using File Based Encryption.
+     *
+     * @return true if device is encrypted
+     */
+    private static boolean isFullDiskEncrypted() {
+        return StorageManager.isNonDefaultBlockEncrypted();
+    }
+
+    private static View createEnableDialogContentView(Activity parentActivity,
+            AccessibilityServiceInfo info) {
+        LayoutInflater inflater = (LayoutInflater) parentActivity.getSystemService(
+                Context.LAYOUT_INFLATER_SERVICE);
+
+        View content = inflater.inflate(R.layout.enable_accessibility_service_dialog_content,
+                null);
+
+        TextView encryptionWarningView = (TextView) content.findViewById(
+                R.id.encryption_warning);
+        if (isFullDiskEncrypted()) {
+            String text = parentActivity.getString(R.string.enable_service_encryption_warning,
+                    info.getResolveInfo().loadLabel(parentActivity.getPackageManager()));
+            encryptionWarningView.setText(text);
+            encryptionWarningView.setVisibility(View.VISIBLE);
+        } else {
+            encryptionWarningView.setVisibility(View.GONE);
+        }
+
+        TextView capabilitiesHeaderView = (TextView) content.findViewById(
+                R.id.capabilities_header);
+        capabilitiesHeaderView.setText(parentActivity.getString(R.string.capabilities_list_title,
+                info.getResolveInfo().loadLabel(parentActivity.getPackageManager())));
+
+        LinearLayout capabilitiesView = (LinearLayout) content.findViewById(R.id.capabilities);
+
+        // This capability is implicit for all services.
+        View capabilityView = inflater.inflate(
+                com.android.internal.R.layout.app_permission_item_old, null);
+
+        ImageView imageView = (ImageView) capabilityView.findViewById(
+                com.android.internal.R.id.perm_icon);
+        imageView.setImageDrawable(parentActivity.getDrawable(
+                com.android.internal.R.drawable.ic_text_dot));
+
+        TextView labelView = (TextView) capabilityView.findViewById(
+                com.android.internal.R.id.permission_group);
+        labelView.setText(parentActivity.getString(
+                R.string.capability_title_receiveAccessibilityEvents));
+
+        TextView descriptionView = (TextView) capabilityView.findViewById(
+                com.android.internal.R.id.permission_list);
+        descriptionView.setText(
+                parentActivity.getString(R.string.capability_desc_receiveAccessibilityEvents));
+
+        List<AccessibilityServiceInfo.CapabilityInfo> capabilities =
+                info.getCapabilityInfos();
+
+        capabilitiesView.addView(capabilityView);
+
+        // Service-specific capabilities.
+        final int capabilityCount = capabilities.size();
+        for (int i = 0; i < capabilityCount; i++) {
+            AccessibilityServiceInfo.CapabilityInfo capability = capabilities.get(i);
+
+            capabilityView = inflater.inflate(
+                    com.android.internal.R.layout.app_permission_item_old, null);
+
+            imageView = (ImageView) capabilityView.findViewById(
+                    com.android.internal.R.id.perm_icon);
+            imageView.setImageDrawable(parentActivity.getDrawable(
+                    com.android.internal.R.drawable.ic_text_dot));
+
+            labelView = (TextView) capabilityView.findViewById(
+                    com.android.internal.R.id.permission_group);
+            labelView.setText(parentActivity.getString(capability.titleResId));
+
+            descriptionView = (TextView) capabilityView.findViewById(
+                    com.android.internal.R.id.permission_list);
+            descriptionView.setText(parentActivity.getString(capability.descResId));
+
+            capabilitiesView.addView(capabilityView);
+        }
+
+        return content;
+    }
+}
diff --git a/src/com/android/settings/accessibility/AccessibilitySettings.java b/src/com/android/settings/accessibility/AccessibilitySettings.java
index af5b5cd..dc57bdd 100644
--- a/src/com/android/settings/accessibility/AccessibilitySettings.java
+++ b/src/com/android/settings/accessibility/AccessibilitySettings.java
@@ -17,9 +17,11 @@
 package com.android.settings.accessibility;
 
 import android.accessibilityservice.AccessibilityServiceInfo;
+import android.app.Dialog;
 import android.app.admin.DevicePolicyManager;
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.DialogInterface;
 import android.content.pm.PackageManager;
 import android.content.pm.ServiceInfo;
 import android.content.res.Resources;
@@ -87,8 +89,8 @@
             "toggle_master_mono";
     private static final String SELECT_LONG_PRESS_TIMEOUT_PREFERENCE =
             "select_long_press_timeout_preference";
-    private static final String ENABLE_ACCESSIBILITY_GESTURE_PREFERENCE_SCREEN =
-            "enable_global_gesture_preference_screen";
+    private static final String ACCESSIBILITY_SHORTCUT_PREFERENCE =
+            "accessibility_shortcut_preference";
     private static final String CAPTIONING_PREFERENCE_SCREEN =
             "captioning_preference_screen";
     private static final String DISPLAY_MAGNIFICATION_PREFERENCE_SCREEN =
@@ -115,6 +117,9 @@
     // presentation.
     private static final long DELAY_UPDATE_SERVICES_MILLIS = 1000;
 
+    // ID for dialog that confirms shortcut capabilities
+    private static final int DIALOG_ID_ADD_SHORTCUT_WARNING = 1;
+
     // Auxiliary members.
     static final Set<ComponentName> sInstalledServices = new HashSet<>();
 
@@ -188,7 +193,7 @@
     private Preference mDisplayMagnificationPreferenceScreen;
     private Preference mFontSizePreferenceScreen;
     private Preference mAutoclickPreferenceScreen;
-    private Preference mGlobalGesturePreferenceScreen;
+    private ListPreference mAccessibilityShortcutPreference;
     private Preference mDisplayDaltonizerPreferenceScreen;
     private SwitchPreference mToggleInversionPreference;
 
@@ -247,6 +252,9 @@
         } else if (mToggleInversionPreference == preference) {
             handleToggleInversionPreferenceChange((Boolean) newValue);
             return true;
+        } else if (mAccessibilityShortcutPreference == preference) {
+            handleAccessibilityShortcutPreferenceChange((String) newValue);
+            return true;
         }
         return false;
     }
@@ -263,6 +271,58 @@
                 Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED, (checked ? 1 : 0));
     }
 
+    private void handleAccessibilityShortcutPreferenceChange(String serviceComponentName) {
+        // When assigning a service to the shortcut the user must explicitly agree to the same
+        // capabilities that are present if the service were being enabled.
+        // No need if clearing the setting or the service is already enabled.
+        if (TextUtils.isEmpty(serviceComponentName)
+                || AccessibilityUtils.getEnabledServicesFromSettings(getActivity())
+                        .contains(ComponentName.unflattenFromString(serviceComponentName))) {
+            Settings.Secure.putString(getContentResolver(),
+                    Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, serviceComponentName);
+            updateAccessibilityShortcut();
+            return;
+        }
+        if (!serviceComponentName.equals(mAccessibilityShortcutPreference.getValue())) {
+            showDialog(DIALOG_ID_ADD_SHORTCUT_WARNING);
+        }
+    }
+
+    @Override
+    public Dialog onCreateDialog(int dialogId) {
+        switch (dialogId) {
+            case DIALOG_ID_ADD_SHORTCUT_WARNING: {
+                DialogInterface.OnClickListener listener =
+                        (DialogInterface dialogInterface, int buttonId) -> {
+                            if (buttonId == DialogInterface.BUTTON_POSITIVE) {
+                                Settings.Secure.putString(getContentResolver(),
+                                        Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE,
+                                        mAccessibilityShortcutPreference.getValue());
+                            }
+                            updateAccessibilityShortcut();
+                        };
+                AccessibilityServiceInfo info = AccessibilityManager.getInstance(getActivity())
+                        .getInstalledServiceInfoWithComponentName(
+                                ComponentName.unflattenFromString(
+                                        mAccessibilityShortcutPreference.getValue()));
+                if (info == null) {
+                    return null;
+                }
+                return AccessibilityServiceWarning
+                        .createCapabilitiesDialog(getActivity(), info, listener);
+            }
+            default: {
+                throw new IllegalArgumentException();
+            }
+        }
+    }
+
+    @Override
+    public int getDialogMetricsCategory(int dialogId) {
+        // The only dialog is the one that confirms the properties for the accessibility shortcut
+        return MetricsEvent.ACCESSIBILITY_TOGGLE_GLOBAL_GESTURE;
+    }
+
     @Override
     public boolean onPreferenceTreeClick(Preference preference) {
         if (mToggleHighTextContrastPreference == preference) {
@@ -283,9 +343,6 @@
         } else if (mToggleMasterMonoPreference == preference) {
             handleToggleMasterMonoPreferenceClick();
             return true;
-        } else if (mGlobalGesturePreferenceScreen == preference) {
-            handleToggleEnableAccessibilityGesturePreferenceClick();
-            return true;
         } else if (mDisplayMagnificationPreferenceScreen == preference) {
             handleDisplayMagnificationPreferenceScreenClick();
             return true;
@@ -329,17 +386,6 @@
                 mToggleMasterMonoPreference.isChecked() ? 1 : 0, UserHandle.USER_CURRENT);
     }
 
-    private void handleToggleEnableAccessibilityGesturePreferenceClick() {
-        Bundle extras = mGlobalGesturePreferenceScreen.getExtras();
-        extras.putString(EXTRA_TITLE, getString(
-                R.string.accessibility_global_gesture_preference_title));
-        extras.putString(EXTRA_SUMMARY, getString(
-                R.string.accessibility_global_gesture_preference_description));
-        extras.putBoolean(EXTRA_CHECKED, Settings.Global.getInt(getContentResolver(),
-                Settings.Global.ENABLE_ACCESSIBILITY_GLOBAL_GESTURE_ENABLED, 0) == 1);
-        super.onPreferenceTreeClick(mGlobalGesturePreferenceScreen);
-    }
-
     private void handleDisplayMagnificationPreferenceScreenClick() {
         Bundle extras = mDisplayMagnificationPreferenceScreen.getExtras();
         extras.putString(EXTRA_TITLE, getString(
@@ -422,18 +468,10 @@
         // Display color adjustments.
         mDisplayDaltonizerPreferenceScreen = findPreference(DISPLAY_DALTONIZER_PREFERENCE_SCREEN);
 
-        // Global gesture.
-        mGlobalGesturePreferenceScreen = findPreference(
-                ENABLE_ACCESSIBILITY_GESTURE_PREFERENCE_SCREEN);
-        final int longPressOnPowerBehavior = getActivity().getResources().getInteger(
-                com.android.internal.R.integer.config_longPressOnPowerBehavior);
-        final int LONG_PRESS_POWER_GLOBAL_ACTIONS = 1;
-        if (!KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_POWER)
-                || longPressOnPowerBehavior != LONG_PRESS_POWER_GLOBAL_ACTIONS) {
-            // Remove accessibility shortcut if power key is not present
-            // nor long press power does not show global actions menu.
-            mSystemsCategory.removePreference(mGlobalGesturePreferenceScreen);
-        }
+        // Accessibility shortcut
+        mAccessibilityShortcutPreference =
+                (ListPreference) findPreference(ACCESSIBILITY_SHORTCUT_PREFERENCE);
+        mAccessibilityShortcutPreference.setOnPreferenceChangeListener(this);
     }
 
     private void updateAllPreferences() {
@@ -598,16 +636,7 @@
 
         updateAutoclickSummary(mAutoclickPreferenceScreen);
 
-        // Global gesture
-        final boolean globalGestureEnabled = Settings.Global.getInt(getContentResolver(),
-                Settings.Global.ENABLE_ACCESSIBILITY_GLOBAL_GESTURE_ENABLED, 0) == 1;
-        if (globalGestureEnabled) {
-            mGlobalGesturePreferenceScreen.setSummary(
-                    R.string.accessibility_global_gesture_preference_summary_on);
-        } else {
-            mGlobalGesturePreferenceScreen.setSummary(
-                    R.string.accessibility_global_gesture_preference_summary_off);
-        }
+        updateAccessibilityShortcut();
     }
 
     private void updateFeatureSummary(String prefKey, Preference pref) {
@@ -656,6 +685,37 @@
         mToggleMasterMonoPreference.setChecked(masterMono);
     }
 
+    private void updateAccessibilityShortcut() {
+        String currentShortcutNameString =
+                AccessibilityUtils.getShortcutTargetServiceComponentNameString(getActivity(),
+                        UserHandle.myUserId());
+        final PackageManager pm = getPackageManager();
+        final AccessibilityManager accessibilityManager = getActivity()
+                .getSystemService(AccessibilityManager.class);
+        final List<AccessibilityServiceInfo> installedServices =
+                accessibilityManager.getInstalledAccessibilityServiceList();
+        final int numInstalledServices = installedServices.size();
+
+        CharSequence[] entries = new CharSequence[numInstalledServices + 1];
+        CharSequence[] entryValues = new CharSequence[numInstalledServices + 1];
+        int currentSettingIndex = numInstalledServices;
+        for (int i = 0; i < numInstalledServices; i++) {
+            AccessibilityServiceInfo installedService = installedServices.get(i);
+            entries[i] = installedService.getResolveInfo().loadLabel(pm);
+            entryValues[i] = installedService.getComponentName().flattenToShortString();
+            if (installedService.getId().equals(currentShortcutNameString)) {
+                currentSettingIndex = i;
+            }
+        }
+        entries[numInstalledServices] =
+                getString(com.android.internal.R.string.disable_accessibility_shortcut);
+        entryValues[numInstalledServices] = "";
+        mAccessibilityShortcutPreference.setEntryValues(entryValues);
+        mAccessibilityShortcutPreference.setEntries(entries);
+        mAccessibilityShortcutPreference.setSummary(entries[currentSettingIndex]);
+        mAccessibilityShortcutPreference.setValueIndex(currentSettingIndex);
+    }
+
     public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
             new BaseSearchIndexProvider() {
         @Override
@@ -663,8 +723,8 @@
             List<SearchIndexableRaw> indexables = new ArrayList<SearchIndexableRaw>();
 
             PackageManager packageManager = context.getPackageManager();
-            AccessibilityManager accessibilityManager = (AccessibilityManager)
-                    context.getSystemService(Context.ACCESSIBILITY_SERVICE);
+            AccessibilityManager accessibilityManager =
+                    context.getSystemService(AccessibilityManager.class);
 
             String screenTitle = context.getResources().getString(
                     R.string.accessibility_services_title);
diff --git a/src/com/android/settings/accessibility/ToggleAccessibilityServicePreferenceFragment.java b/src/com/android/settings/accessibility/ToggleAccessibilityServicePreferenceFragment.java
index 3bd450b..9c01a5f 100644
--- a/src/com/android/settings/accessibility/ToggleAccessibilityServicePreferenceFragment.java
+++ b/src/com/android/settings/accessibility/ToggleAccessibilityServicePreferenceFragment.java
@@ -146,39 +146,13 @@
         switch (dialogId) {
             case DIALOG_ID_ENABLE_WARNING: {
                 mShownDialogId = DIALOG_ID_ENABLE_WARNING;
-
                 final AccessibilityServiceInfo info = getAccessibilityServiceInfo();
                 if (info == null) {
                     return null;
                 }
 
-                final AlertDialog ad = new AlertDialog.Builder(getActivity())
-                        .setTitle(getString(R.string.enable_service_title,
-                                info.getResolveInfo().loadLabel(getPackageManager())))
-                        .setView(createEnableDialogContentView(info))
-                        .setCancelable(true)
-                        .setPositiveButton(android.R.string.ok, this)
-                        .setNegativeButton(android.R.string.cancel, this)
-                        .create();
-
-                final View.OnTouchListener filterTouchListener = new View.OnTouchListener() {
-                    @Override
-                    public boolean onTouch(View v, MotionEvent event) {
-                        // Filter obscured touches by consuming them.
-                        if ((event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0) {
-                            if (event.getAction() == MotionEvent.ACTION_UP) {
-                                Toast.makeText(v.getContext(), R.string.touch_filtered_warning,
-                                        Toast.LENGTH_SHORT).show();
-                            }
-                            return true;
-                        }
-                        return false;
-                    }
-                };
-
-                ad.create();
-                ad.getButton(AlertDialog.BUTTON_POSITIVE).setOnTouchListener(filterTouchListener);
-                return ad;
+                return AccessibilityServiceWarning
+                        .createCapabilitiesDialog(getActivity(), info, this);
             }
             case DIALOG_ID_DISABLE_WARNING: {
                 mShownDialogId = DIALOG_ID_DISABLE_WARNING;
@@ -227,80 +201,6 @@
         return StorageManager.isNonDefaultBlockEncrypted();
     }
 
-    private View createEnableDialogContentView(AccessibilityServiceInfo info) {
-        LayoutInflater inflater = (LayoutInflater) getSystemService(
-                Context.LAYOUT_INFLATER_SERVICE);
-
-        View content = inflater.inflate(R.layout.enable_accessibility_service_dialog_content,
-                null);
-
-        TextView encryptionWarningView = (TextView) content.findViewById(
-                R.id.encryption_warning);
-        if (isFullDiskEncrypted()) {
-            String text = getString(R.string.enable_service_encryption_warning,
-                    info.getResolveInfo().loadLabel(getPackageManager()));
-            encryptionWarningView.setText(text);
-            encryptionWarningView.setVisibility(View.VISIBLE);
-        } else {
-            encryptionWarningView.setVisibility(View.GONE);
-        }
-
-        TextView capabilitiesHeaderView = (TextView) content.findViewById(
-                R.id.capabilities_header);
-        capabilitiesHeaderView.setText(getString(R.string.capabilities_list_title,
-                info.getResolveInfo().loadLabel(getPackageManager())));
-
-        LinearLayout capabilitiesView = (LinearLayout) content.findViewById(R.id.capabilities);
-
-        // This capability is implicit for all services.
-        View capabilityView = inflater.inflate(
-                com.android.internal.R.layout.app_permission_item_old, null);
-
-        ImageView imageView = (ImageView) capabilityView.findViewById(
-                com.android.internal.R.id.perm_icon);
-        imageView.setImageDrawable(getActivity().getDrawable(
-                com.android.internal.R.drawable.ic_text_dot));
-
-        TextView labelView = (TextView) capabilityView.findViewById(
-                com.android.internal.R.id.permission_group);
-        labelView.setText(getString(R.string.capability_title_receiveAccessibilityEvents));
-
-        TextView descriptionView = (TextView) capabilityView.findViewById(
-                com.android.internal.R.id.permission_list);
-        descriptionView.setText(getString(R.string.capability_desc_receiveAccessibilityEvents));
-
-        List<AccessibilityServiceInfo.CapabilityInfo> capabilities =
-                info.getCapabilityInfos();
-
-        capabilitiesView.addView(capabilityView);
-
-        // Service specific capabilities.
-        final int capabilityCount = capabilities.size();
-        for (int i = 0; i < capabilityCount; i++) {
-            AccessibilityServiceInfo.CapabilityInfo capability = capabilities.get(i);
-
-            capabilityView = inflater.inflate(
-                    com.android.internal.R.layout.app_permission_item_old, null);
-
-            imageView = (ImageView) capabilityView.findViewById(
-                    com.android.internal.R.id.perm_icon);
-            imageView.setImageDrawable(getActivity().getDrawable(
-                    com.android.internal.R.drawable.ic_text_dot));
-
-            labelView = (TextView) capabilityView.findViewById(
-                    com.android.internal.R.id.permission_group);
-            labelView.setText(getString(capability.titleResId));
-
-            descriptionView = (TextView) capabilityView.findViewById(
-                    com.android.internal.R.id.permission_list);
-            descriptionView.setText(getString(capability.descResId));
-
-            capabilitiesView.addView(capabilityView);
-        }
-
-        return content;
-    }
-
     @Override
     public void onActivityResult(int requestCode, int resultCode, Intent data) {
         if (requestCode == ACTIVITY_REQUEST_CONFIRM_CREDENTIAL_FOR_WEAKER_ENCRYPTION) {
diff --git a/src/com/android/settings/accessibility/ToggleGlobalGesturePreferenceFragment.java b/src/com/android/settings/accessibility/ToggleGlobalGesturePreferenceFragment.java
deleted file mode 100644
index 5d95d7e..0000000
--- a/src/com/android/settings/accessibility/ToggleGlobalGesturePreferenceFragment.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2013 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.accessibility;
-
-import android.provider.Settings;
-
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.settings.widget.ToggleSwitch;
-import com.android.settings.widget.ToggleSwitch.OnBeforeCheckedChangeListener;
-
-public class ToggleGlobalGesturePreferenceFragment
-        extends ToggleFeaturePreferenceFragment {
-    @Override
-    protected void onPreferenceToggled(String preferenceKey, boolean enabled) {
-        Settings.Global.putInt(getContentResolver(),
-                Settings.Global.ENABLE_ACCESSIBILITY_GLOBAL_GESTURE_ENABLED, enabled ? 1 : 0);
-    }
-
-    @Override
-    protected void onInstallSwitchBarToggleSwitch() {
-        super.onInstallSwitchBarToggleSwitch();
-        mToggleSwitch.setOnBeforeCheckedChangeListener(new OnBeforeCheckedChangeListener() {
-                @Override
-            public boolean onBeforeCheckedChanged(ToggleSwitch toggleSwitch, boolean checked) {
-                mSwitchBar.setCheckedInternal(checked);
-                getArguments().putBoolean(AccessibilitySettings.EXTRA_CHECKED, checked);
-                onPreferenceToggled(mPreferenceKey, checked);
-                return false;
-            }
-        });
-    }
-
-    @Override
-    public int getMetricsCategory() {
-        return MetricsEvent.ACCESSIBILITY_TOGGLE_GLOBAL_GESTURE;
-    }
-}
diff --git a/src/com/android/settings/applications/AppWithAdminGrantedPermissionsCounter.java b/src/com/android/settings/applications/AppWithAdminGrantedPermissionsCounter.java
new file mode 100644
index 0000000..331d384
--- /dev/null
+++ b/src/com/android/settings/applications/AppWithAdminGrantedPermissionsCounter.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2017 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.applications;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
+import android.os.Build;
+import android.os.RemoteException;
+import android.os.UserHandle;
+
+import com.android.settings.enterprise.DevicePolicyManagerWrapper;
+
+/**
+ * Counts installed apps across all users that have been granted one or more specific permissions by
+ * the admin.
+ */
+public abstract class AppWithAdminGrantedPermissionsCounter extends AppCounter {
+
+    private final String[] mPermissions;
+    private final PackageManagerWrapper mPackageManager;
+    private final IPackageManager mPackageManagerService;
+    private final DevicePolicyManagerWrapper mDevicePolicyManager;
+
+    public AppWithAdminGrantedPermissionsCounter(Context context, String[] permissions,
+            PackageManagerWrapper packageManager, IPackageManager packageManagerService,
+            DevicePolicyManagerWrapper devicePolicyManager) {
+        super(context, packageManager);
+        mPermissions = permissions;
+        mPackageManager = packageManager;
+        mPackageManagerService = packageManagerService;
+        mDevicePolicyManager = devicePolicyManager;
+    }
+
+    @Override
+    protected boolean includeInCount(ApplicationInfo info) {
+        if (info.targetSdkVersion >= Build.VERSION_CODES.M) {
+            // The app uses run-time permissions. Check whether one or more of the permissions were
+            // granted by enterprise policy.
+            for (final String permission : mPermissions) {
+                if (mDevicePolicyManager.getPermissionGrantState(null /* admin */, info.packageName,
+                        permission) == DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        // The app uses install-time permissions. Check whether the app requested one or more of the
+        // permissions and was installed by enterprise policy, implicitly granting permissions.
+        if (mPackageManager.getInstallReason(info.packageName,
+                new UserHandle(UserHandle.getUserId(info.uid)))
+                        != PackageManager.INSTALL_REASON_POLICY) {
+            return false;
+        }
+        try {
+            for (final String permission : mPermissions) {
+                if (mPackageManagerService.checkUidPermission(permission, info.uid)
+                        == PackageManager.PERMISSION_GRANTED) {
+                    return true;
+                }
+            }
+        } catch (RemoteException exception) {
+        }
+        return false;
+    }
+}
diff --git a/src/com/android/settings/applications/ApplicationFeatureProvider.java b/src/com/android/settings/applications/ApplicationFeatureProvider.java
index 1db33a6..101ae91 100644
--- a/src/com/android/settings/applications/ApplicationFeatureProvider.java
+++ b/src/com/android/settings/applications/ApplicationFeatureProvider.java
@@ -35,17 +35,29 @@
      * Asynchronously calculates the total number of apps installed on the device, across all users
      * and managed profiles.
      *
-     * @param installReason Only consider packages with this install reason; may be any install
-     *         reason defined in {@link android.content.pm.PackageManager} or
-     *         {@link #IGNORE_INSTALL_REASON} to count all packages, irrespective of install reason.
+     * @param installReason Only consider apps with this install reason; may be any install reason
+     *         defined in {@link android.content.pm.PackageManager} or
+     *         {@link #IGNORE_INSTALL_REASON} to count all apps, irrespective of install reason.
      * @param callback The callback to invoke with the result
      */
-    void calculateNumberOfInstalledApps(int installReason, NumberOfInstalledAppsCallback callback);
+    void calculateNumberOfInstalledApps(int installReason, NumberOfAppsCallback callback);
 
     /**
-     * Callback that receives the total number of packages installed on the device.
+     * Asynchronously calculates the total number of apps installed on the device, across all users
+     * and managed profiles, that have been granted one or more of the given permissions by the
+     * admin.
+     *
+     * @param permissions Only consider apps that have been granted one or more of these permissions
+     *        by the admin, either at run-time or install-time
+     * @param callback The callback to invoke with the result
      */
-    interface NumberOfInstalledAppsCallback {
-        void onNumberOfInstalledAppsResult(int num);
+    void calculateNumberOfAppsWithAdminGrantedPermissions(String[] permissions,
+            NumberOfAppsCallback callback);
+
+    /**
+     * Callback that receives the number of packages installed on the device.
+     */
+    interface NumberOfAppsCallback {
+        void onNumberOfAppsResult(int num);
     }
 }
diff --git a/src/com/android/settings/applications/ApplicationFeatureProviderImpl.java b/src/com/android/settings/applications/ApplicationFeatureProviderImpl.java
index 902008c..ff7f0f0 100644
--- a/src/com/android/settings/applications/ApplicationFeatureProviderImpl.java
+++ b/src/com/android/settings/applications/ApplicationFeatureProviderImpl.java
@@ -18,21 +18,29 @@
 
 import android.app.Fragment;
 import android.content.Context;
+import android.content.pm.IPackageManager;
 import android.content.pm.UserInfo;
 import android.os.UserManager;
 import android.view.View;
 
+import com.android.settings.enterprise.DevicePolicyManagerWrapper;
+
 import java.util.List;
 
 public class ApplicationFeatureProviderImpl implements ApplicationFeatureProvider {
 
     private final Context mContext;
     private final PackageManagerWrapper mPm;
+    private final IPackageManager mPms;
+    private final DevicePolicyManagerWrapper mDpm;
     private final UserManager mUm;
 
-    public ApplicationFeatureProviderImpl(Context context, PackageManagerWrapper pm) {
+    public ApplicationFeatureProviderImpl(Context context, PackageManagerWrapper pm,
+            IPackageManager pms, DevicePolicyManagerWrapper dpm) {
         mContext = context.getApplicationContext();
         mPm = pm;
+        mPms = pms;
+        mDpm = dpm;
         mUm = UserManager.get(mContext);
     }
 
@@ -42,22 +50,51 @@
     }
 
     @Override
-    public void calculateNumberOfInstalledApps(int installReason,
-            NumberOfInstalledAppsCallback callback) {
-        new AllUserInstalledAppCounter(installReason, callback).execute();
+    public void calculateNumberOfInstalledApps(int installReason, NumberOfAppsCallback callback) {
+        new AllUserInstalledAppCounter(mContext, installReason, mPm, callback).execute();
     }
 
-    private class AllUserInstalledAppCounter extends InstalledAppCounter {
-        private NumberOfInstalledAppsCallback mCallback;
+    @Override
+    public void calculateNumberOfAppsWithAdminGrantedPermissions(String[] permissions,
+            NumberOfAppsCallback callback) {
+        new AllUserAppWithAdminGrantedPermissionsCounter(mContext, permissions, mPm, mPms, mDpm,
+                callback).execute();
+    }
 
-        AllUserInstalledAppCounter(int installReason, NumberOfInstalledAppsCallback callback) {
-            super(mContext, installReason, ApplicationFeatureProviderImpl.this.mPm);
+    private static class AllUserInstalledAppCounter extends InstalledAppCounter {
+        private NumberOfAppsCallback mCallback;
+
+        AllUserInstalledAppCounter(Context context, int installReason,
+                PackageManagerWrapper packageManager, NumberOfAppsCallback callback) {
+            super(context, installReason, packageManager);
             mCallback = callback;
         }
 
         @Override
         protected void onCountComplete(int num) {
-            mCallback.onNumberOfInstalledAppsResult(num);
+            mCallback.onNumberOfAppsResult(num);
+        }
+
+        @Override
+        protected List<UserInfo> getUsersToCount() {
+            return mUm.getUsers(true /* excludeDying */);
+        }
+    }
+
+    private static class AllUserAppWithAdminGrantedPermissionsCounter extends
+            AppWithAdminGrantedPermissionsCounter {
+        private NumberOfAppsCallback mCallback;
+
+        AllUserAppWithAdminGrantedPermissionsCounter(Context context, String[] permissions,
+                PackageManagerWrapper packageManager, IPackageManager packageManagerService,
+                DevicePolicyManagerWrapper devicePolicyManager, NumberOfAppsCallback callback) {
+            super(context, permissions, packageManager, packageManagerService, devicePolicyManager);
+            mCallback = callback;
+        }
+
+        @Override
+        protected void onCountComplete(int num) {
+            mCallback.onNumberOfAppsResult(num);
         }
 
         @Override
diff --git a/src/com/android/settings/applications/DrawOverlayDetails.java b/src/com/android/settings/applications/DrawOverlayDetails.java
index dfaa95f..39b8919 100644
--- a/src/com/android/settings/applications/DrawOverlayDetails.java
+++ b/src/com/android/settings/applications/DrawOverlayDetails.java
@@ -31,10 +31,12 @@
 import android.support.v7.preference.Preference.OnPreferenceClickListener;
 import android.util.Log;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.settings.R;
 import com.android.settings.applications.AppStateAppOpsBridge.PermissionState;
 import com.android.settings.applications.AppStateOverlayBridge.OverlayState;
+import com.android.settings.overlay.FeatureFactory;
 import com.android.settingslib.applications.ApplicationsState.AppEntry;
 
 public class DrawOverlayDetails extends AppInfoWithHeader implements OnPreferenceChangeListener,
@@ -121,11 +123,20 @@
     }
 
     private void setCanDrawOverlay(boolean newState) {
+        logSpecialPermissionChange(newState, mPackageName);
         mAppOpsManager.setMode(AppOpsManager.OP_SYSTEM_ALERT_WINDOW,
                 mPackageInfo.applicationInfo.uid, mPackageName, newState
                 ? AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_ERRORED);
     }
 
+    @VisibleForTesting
+    void logSpecialPermissionChange(boolean newState, String packageName) {
+        int logCategory = newState ? MetricsEvent.APP_SPECIAL_PERMISSION_APPDRAW_ALLOW
+                : MetricsEvent.APP_SPECIAL_PERMISSION_APPDRAW_DENY;
+        FeatureFactory.getFactory(getContext())
+                .getMetricsFeatureProvider().action(getContext(), logCategory, packageName);
+    }
+
     private boolean canDrawOverlay(String pkgName) {
         int result = mAppOpsManager.noteOpNoThrow(AppOpsManager.OP_SYSTEM_ALERT_WINDOW,
                 mPackageInfo.applicationInfo.uid, pkgName);
diff --git a/src/com/android/settings/applications/PremiumSmsAccess.java b/src/com/android/settings/applications/PremiumSmsAccess.java
index fa97537..2b0942f 100644
--- a/src/com/android/settings/applications/PremiumSmsAccess.java
+++ b/src/com/android/settings/applications/PremiumSmsAccess.java
@@ -24,13 +24,17 @@
 import android.support.v7.preference.PreferenceScreen;
 import android.support.v7.preference.PreferenceViewHolder;
 import android.view.View;
+
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.nano.MetricsProto;
 import com.android.internal.telephony.SmsUsageMonitor;
 import com.android.settings.DividerPreference;
 import com.android.settings.R;
 import com.android.settings.applications.AppStateBaseBridge.Callback;
 import com.android.settings.applications.AppStateSmsPremBridge.SmsState;
+import com.android.settings.core.instrumentation.MetricsFeatureProvider;
 import com.android.settings.notification.EmptyTextSettings;
+import com.android.settings.overlay.FeatureFactory;
 import com.android.settingslib.applications.ApplicationsState;
 import com.android.settingslib.applications.ApplicationsState.AppEntry;
 import com.android.settingslib.applications.ApplicationsState.Callbacks;
@@ -81,11 +85,33 @@
     @Override
     public boolean onPreferenceChange(Preference preference, Object newValue) {
         PremiumSmsPreference pref = (PremiumSmsPreference) preference;
-        mSmsBackend.setSmsState(pref.mAppEntry.info.packageName,
-                Integer.parseInt((String) newValue));
+        int smsState = Integer.parseInt((String) newValue);
+        logSpecialPermissionChange(smsState, pref.mAppEntry.info.packageName);
+        mSmsBackend.setSmsState(pref.mAppEntry.info.packageName, smsState);
         return true;
     }
 
+    @VisibleForTesting
+    void logSpecialPermissionChange(int smsState, String packageName) {
+        int category = SmsUsageMonitor.PREMIUM_SMS_PERMISSION_UNKNOWN;
+        switch (smsState) {
+            case SmsUsageMonitor.PREMIUM_SMS_PERMISSION_ASK_USER:
+                category = MetricsProto.MetricsEvent.APP_SPECIAL_PERMISSION_PREMIUM_SMS_ASK;
+                break;
+            case SmsUsageMonitor.PREMIUM_SMS_PERMISSION_NEVER_ALLOW:
+                category = MetricsProto.MetricsEvent.APP_SPECIAL_PERMISSION_PREMIUM_SMS_DENY;
+                break;
+            case SmsUsageMonitor.PREMIUM_SMS_PERMISSION_ALWAYS_ALLOW:
+                category = MetricsProto.MetricsEvent.
+                        APP_SPECIAL_PERMISSION_PREMIUM_SMS_ALWAYS_ALLOW;
+                break;
+        }
+        if (category != SmsUsageMonitor.PREMIUM_SMS_PERMISSION_UNKNOWN) {
+            FeatureFactory.getFactory(getContext()).getMetricsFeatureProvider().action(
+                    getContext(), category, packageName);
+        }
+    }
+
     private void updatePrefs(ArrayList<AppEntry> apps) {
         if (apps == null) return;
         setEmptyText(R.string.premium_sms_none);
diff --git a/src/com/android/settings/applications/UsageAccessDetails.java b/src/com/android/settings/applications/UsageAccessDetails.java
index 2fa0253..e40ae37 100644
--- a/src/com/android/settings/applications/UsageAccessDetails.java
+++ b/src/com/android/settings/applications/UsageAccessDetails.java
@@ -33,9 +33,12 @@
 import android.support.v7.preference.Preference.OnPreferenceClickListener;
 import android.util.Log;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.settings.R;
 import com.android.settings.applications.AppStateUsageBridge.UsageState;
+import com.android.settings.core.instrumentation.MetricsFeatureProvider;
+import com.android.settings.overlay.FeatureFactory;
 
 public class UsageAccessDetails extends AppInfoWithHeader implements OnPreferenceChangeListener,
         OnPreferenceClickListener {
@@ -119,10 +122,19 @@
     }
 
     private void setHasAccess(boolean newState) {
+        logSpecialPermissionChange(newState, mPackageName);
         mAppOpsManager.setMode(AppOpsManager.OP_GET_USAGE_STATS, mPackageInfo.applicationInfo.uid,
                 mPackageName, newState ? AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_IGNORED);
     }
 
+    @VisibleForTesting
+    void logSpecialPermissionChange(boolean newState, String packageName) {
+        int logCategory = newState ? MetricsEvent.APP_SPECIAL_PERMISSION_USAGE_VIEW_ALLOW
+                : MetricsEvent.APP_SPECIAL_PERMISSION_USAGE_VIEW_DENY;
+        FeatureFactory.getFactory(getContext()).getMetricsFeatureProvider().action(getContext(),
+                logCategory, packageName);
+    }
+
     @Override
     protected boolean refreshUi() {
         mUsageState = mUsageBridge.getUsageInfo(mPackageName,
diff --git a/src/com/android/settings/applications/VrListenerSettings.java b/src/com/android/settings/applications/VrListenerSettings.java
index 08d1367..99340b1 100644
--- a/src/com/android/settings/applications/VrListenerSettings.java
+++ b/src/com/android/settings/applications/VrListenerSettings.java
@@ -15,11 +15,14 @@
  */
 package com.android.settings.applications;
 
+import android.content.ComponentName;
 import android.provider.Settings;
 import android.service.vr.VrListenerService;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.settings.R;
+import com.android.settings.overlay.FeatureFactory;
 import com.android.settings.utils.ManagedServiceSettings;
 
 public class VrListenerSettings extends ManagedServiceSettings {
@@ -48,4 +51,18 @@
     public int getMetricsCategory() {
         return MetricsEvent.VR_MANAGE_LISTENERS;
     }
+
+    @Override
+    protected boolean setEnabled(ComponentName service, String title, boolean enable) {
+        logSpecialPermissionChange(enable, service.getPackageName());
+        return super.setEnabled(service, title, enable);
+    }
+
+    @VisibleForTesting
+    void logSpecialPermissionChange(boolean enable, String packageName) {
+        int logCategory = enable ? MetricsEvent.APP_SPECIAL_PERMISSION_VRHELPER_ALLOW
+                : MetricsEvent.APP_SPECIAL_PERMISSION_VRHELPER_DENY;
+        FeatureFactory.getFactory(getContext()).getMetricsFeatureProvider().action(getContext(),
+                logCategory, packageName);
+    }
 }
diff --git a/src/com/android/settings/applications/WriteSettingsDetails.java b/src/com/android/settings/applications/WriteSettingsDetails.java
index 9f9016d..aea05b3 100644
--- a/src/com/android/settings/applications/WriteSettingsDetails.java
+++ b/src/com/android/settings/applications/WriteSettingsDetails.java
@@ -35,6 +35,7 @@
 import com.android.settings.R;
 import com.android.settings.applications.AppStateAppOpsBridge.PermissionState;
 import com.android.settings.applications.AppStateWriteSettingsBridge.WriteSettingsState;
+import com.android.settings.overlay.FeatureFactory;
 import com.android.settingslib.applications.ApplicationsState.AppEntry;
 
 import java.util.List;
@@ -117,11 +118,19 @@
     }
 
     private void setCanWriteSettings(boolean newState) {
+        logSpecialPermissionChange(newState, mPackageName);
         mAppOpsManager.setMode(AppOpsManager.OP_WRITE_SETTINGS,
                 mPackageInfo.applicationInfo.uid, mPackageName, newState
                 ? AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_ERRORED);
     }
 
+    void logSpecialPermissionChange(boolean newState, String packageName) {
+        int logCategory = newState ? MetricsEvent.APP_SPECIAL_PERMISSION_SETTINGS_CHANGE_ALLOW
+                : MetricsEvent.APP_SPECIAL_PERMISSION_SETTINGS_CHANGE_DENY;
+        FeatureFactory.getFactory(getContext()).getMetricsFeatureProvider().action(getContext(),
+                logCategory, packageName);
+    }
+
     private boolean canWriteSettings(String pkgName) {
         int result = mAppOpsManager.noteOpNoThrow(AppOpsManager.OP_WRITE_SETTINGS,
                 mPackageInfo.applicationInfo.uid, pkgName);
diff --git a/src/com/android/settings/dashboard/DashboardFragment.java b/src/com/android/settings/dashboard/DashboardFragment.java
index 056efc7..de86bd5 100644
--- a/src/com/android/settings/dashboard/DashboardFragment.java
+++ b/src/com/android/settings/dashboard/DashboardFragment.java
@@ -299,15 +299,15 @@
         if (mSummaryLoader != null) {
             mSummaryLoader.release();
         }
-        final Activity activity = getActivity();
-        mSummaryLoader = new SummaryLoader(activity, getCategoryKey());
+        final Context context = getContext();
+        mSummaryLoader = new SummaryLoader(getActivity(), getCategoryKey());
         mSummaryLoader.setSummaryConsumer(this);
-        final TypedArray a = activity.obtainStyledAttributes(new int[] {
+        final TypedArray a = context.obtainStyledAttributes(new int[] {
             mDashboardFeatureProvider.isEnabled() ? android.R.attr.colorControlNormal
                 : android.R.attr.colorAccent});
-        final int tintColor = a.getColor(0, activity.getColor(android.R.color.white));
+        final int tintColor = a.getColor(0, context.getColor(android.R.color.white));
         a.recycle();
-        final String pkgName = activity.getPackageName();
+        final String pkgName = context.getPackageName();
         // Install dashboard tiles.
         for (Tile tile : tiles) {
             final String key = mDashboardFeatureProvider.getDashboardKeyForTile(tile);
@@ -318,7 +318,8 @@
             if (!displayTile(tile)) {
                 continue;
             }
-            if (!pkgName.equals(tile.intent.getComponent().getPackageName())) {
+            if (pkgName != null && tile.intent != null
+                && !pkgName.equals(tile.intent.getComponent().getPackageName())) {
                 // If this drawable is coming from outside Settings, tint it to match the color.
                 tile.icon.setTint(tintColor);
             }
@@ -326,12 +327,12 @@
                 // Have the key already, will rebind.
                 final Preference preference = mProgressiveDisclosureMixin.findPreference(
                         screen, key);
-                mDashboardFeatureProvider.bindPreferenceToTile(activity, preference, tile, key,
+                mDashboardFeatureProvider.bindPreferenceToTile(getActivity(), preference, tile, key,
                         mPlaceholderPreferenceController.getOrder());
             } else {
                 // Don't have this key, add it.
                 final Preference pref = new Preference(getPrefContext());
-                mDashboardFeatureProvider.bindPreferenceToTile(activity, pref, tile, key,
+                mDashboardFeatureProvider.bindPreferenceToTile(getActivity(), pref, tile, key,
                         mPlaceholderPreferenceController.getOrder());
                 mProgressiveDisclosureMixin.addPreference(screen, pref);
                 mDashboardTilePrefKeys.add(key);
diff --git a/src/com/android/settings/datausage/UnrestrictedDataAccess.java b/src/com/android/settings/datausage/UnrestrictedDataAccess.java
index 0839114..58a34b9 100644
--- a/src/com/android/settings/datausage/UnrestrictedDataAccess.java
+++ b/src/com/android/settings/datausage/UnrestrictedDataAccess.java
@@ -18,7 +18,6 @@
 import android.content.Context;
 import android.os.Bundle;
 import android.os.UserHandle;
-import android.support.annotation.VisibleForTesting;
 import android.support.v14.preference.SwitchPreference;
 import android.support.v7.preference.Preference;
 import android.support.v7.preference.PreferenceViewHolder;
@@ -27,12 +26,14 @@
 import android.view.MenuItem;
 import android.view.View;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.settings.R;
 import com.android.settings.SettingsPreferenceFragment;
 import com.android.settings.applications.AppStateBaseBridge;
 import com.android.settings.applications.InstalledAppDetails;
 import com.android.settings.datausage.AppStateDataUsageBridge.DataUsageState;
+import com.android.settings.overlay.FeatureFactory;
 import com.android.settingslib.applications.ApplicationsState;
 import com.android.settingslib.applications.ApplicationsState.AppEntry;
 import com.android.settingslib.applications.ApplicationsState.AppFilter;
@@ -215,6 +216,7 @@
         if (preference instanceof AccessPreference) {
             AccessPreference accessPreference = (AccessPreference) preference;
             boolean whitelisted = newValue == Boolean.TRUE;
+            logSpecialPermissionChange(whitelisted, accessPreference.mEntry.info.packageName);
             mDataSaverBackend.setIsWhitelisted(accessPreference.mEntry.info.uid,
                     accessPreference.mEntry.info.packageName, whitelisted);
             accessPreference.mState.isDataSaverWhitelisted = whitelisted;
@@ -224,6 +226,14 @@
     }
 
     @VisibleForTesting
+    void logSpecialPermissionChange(boolean whitelisted, String packageName) {
+        int logCategory = whitelisted ? MetricsEvent.APP_SPECIAL_PERMISSION_UNL_DATA_ALLOW
+                : MetricsEvent.APP_SPECIAL_PERMISSION_UNL_DATA_DENY;
+        FeatureFactory.getFactory(getContext()).getMetricsFeatureProvider().action(getContext(),
+                logCategory, packageName);
+    }
+
+    @VisibleForTesting
     boolean shouldAddPreference(AppEntry app) {
         return app != null && UserHandle.isApp(app.info.uid);
     }
diff --git a/src/com/android/settings/enterprise/AdminActionPreferenceControllerBase.java b/src/com/android/settings/enterprise/AdminActionPreferenceControllerBase.java
index 99ff62e..ef03cfb 100644
--- a/src/com/android/settings/enterprise/AdminActionPreferenceControllerBase.java
+++ b/src/com/android/settings/enterprise/AdminActionPreferenceControllerBase.java
@@ -11,6 +11,7 @@
  * KIND, either express or implied. See the License for the specific language governing
  * permissions and limitations under the License.
  */
+
 package com.android.settings.enterprise;
 
 import android.content.Context;
diff --git a/src/com/android/settings/enterprise/AdminGrantedCameraPermissionPreferenceController.java b/src/com/android/settings/enterprise/AdminGrantedCameraPermissionPreferenceController.java
new file mode 100644
index 0000000..a2137ff
--- /dev/null
+++ b/src/com/android/settings/enterprise/AdminGrantedCameraPermissionPreferenceController.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2017 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.enterprise;
+
+import android.Manifest;
+import android.content.Context;
+
+import com.android.settings.R;
+
+public class AdminGrantedCameraPermissionPreferenceController extends
+        AdminGrantedPermissionsPreferenceControllerBase {
+
+    private static final String KEY_ENTERPRISE_PRIVACY_NUMBER_CAMERA_ACCESS_PACKAGES
+            = "enterprise_privacy_number_camera_access_packages";
+
+    public AdminGrantedCameraPermissionPreferenceController(Context context) {
+        super(context, new String[] {Manifest.permission.CAMERA},
+                R.plurals.enterprise_privacy_number_camera_access_packages);
+    }
+
+    @Override
+    public String getPreferenceKey() {
+        return KEY_ENTERPRISE_PRIVACY_NUMBER_CAMERA_ACCESS_PACKAGES;
+    }
+}
diff --git a/src/com/android/settings/enterprise/AdminGrantedLocationPermissionsPreferenceController.java b/src/com/android/settings/enterprise/AdminGrantedLocationPermissionsPreferenceController.java
new file mode 100644
index 0000000..0453b53
--- /dev/null
+++ b/src/com/android/settings/enterprise/AdminGrantedLocationPermissionsPreferenceController.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2017 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.enterprise;
+
+import android.Manifest;
+import android.content.Context;
+
+import com.android.settings.R;
+
+public class AdminGrantedLocationPermissionsPreferenceController extends
+        AdminGrantedPermissionsPreferenceControllerBase {
+
+    private static final String KEY_ENTERPRISE_PRIVACY_NUMBER_LOCATION_ACCESS_PACKAGES
+            = "enterprise_privacy_number_location_access_packages";
+
+    public AdminGrantedLocationPermissionsPreferenceController(Context context) {
+        super(context, new String[] {Manifest.permission.ACCESS_COARSE_LOCATION,
+                Manifest.permission.ACCESS_FINE_LOCATION},
+                R.plurals.enterprise_privacy_number_location_access_packages);
+    }
+
+    @Override
+    public String getPreferenceKey() {
+        return KEY_ENTERPRISE_PRIVACY_NUMBER_LOCATION_ACCESS_PACKAGES;
+    }
+}
diff --git a/src/com/android/settings/enterprise/AdminGrantedMicrophonePermissionPreferenceController.java b/src/com/android/settings/enterprise/AdminGrantedMicrophonePermissionPreferenceController.java
new file mode 100644
index 0000000..3adde92
--- /dev/null
+++ b/src/com/android/settings/enterprise/AdminGrantedMicrophonePermissionPreferenceController.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2017 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.enterprise;
+
+import android.Manifest;
+import android.content.Context;
+
+import com.android.settings.R;
+
+public class AdminGrantedMicrophonePermissionPreferenceController extends
+        AdminGrantedPermissionsPreferenceControllerBase {
+
+    private static final String KEY_ENTERPRISE_PRIVACY_NUMBER_MICROPHONE_ACCESS_PACKAGES
+            = "enterprise_privacy_number_microphone_access_packages";
+
+    public AdminGrantedMicrophonePermissionPreferenceController(Context context) {
+        super(context, new String[] {Manifest.permission.RECORD_AUDIO},
+                R.plurals.enterprise_privacy_number_microphone_access_packages);
+    }
+
+    @Override
+    public String getPreferenceKey() {
+        return KEY_ENTERPRISE_PRIVACY_NUMBER_MICROPHONE_ACCESS_PACKAGES;
+    }
+}
diff --git a/src/com/android/settings/enterprise/AdminGrantedPermissionsPreferenceControllerBase.java b/src/com/android/settings/enterprise/AdminGrantedPermissionsPreferenceControllerBase.java
new file mode 100644
index 0000000..2ca5451
--- /dev/null
+++ b/src/com/android/settings/enterprise/AdminGrantedPermissionsPreferenceControllerBase.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2017 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.enterprise;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.support.v7.preference.Preference;
+
+import com.android.settings.applications.ApplicationFeatureProvider;
+import com.android.settings.core.PreferenceController;
+import com.android.settings.overlay.FeatureFactory;
+
+public abstract class AdminGrantedPermissionsPreferenceControllerBase extends PreferenceController {
+
+    private final String[] mPermissions;
+    private final int mStringResourceId;
+    private final ApplicationFeatureProvider mFeatureProvider;
+
+    public AdminGrantedPermissionsPreferenceControllerBase(Context context, String[] permissions,
+            int stringResourceId) {
+        super(context);
+        mPermissions = permissions;
+        mStringResourceId = stringResourceId;
+        mFeatureProvider = FeatureFactory.getFactory(context)
+                .getApplicationFeatureProvider(context);
+    }
+
+    @Override
+    public void updateState(Preference preference) {
+        mFeatureProvider.calculateNumberOfAppsWithAdminGrantedPermissions(mPermissions,
+                (num) -> {
+                    if (num == 0) {
+                        preference.setVisible(false);
+                    } else {
+                        preference.setVisible(true);
+                        preference.setTitle(mContext.getResources().getQuantityString(
+                                mStringResourceId, num, num));
+                    }
+                });
+    }
+
+    @Override
+    public boolean isAvailable() {
+        return true;
+    }
+}
diff --git a/src/com/android/settings/enterprise/DevicePolicyManagerWrapper.java b/src/com/android/settings/enterprise/DevicePolicyManagerWrapper.java
index d65292f..a23448e 100644
--- a/src/com/android/settings/enterprise/DevicePolicyManagerWrapper.java
+++ b/src/com/android/settings/enterprise/DevicePolicyManagerWrapper.java
@@ -17,6 +17,7 @@
 package com.android.settings.enterprise;
 
 import android.content.ComponentName;
+import android.support.annotation.Nullable;
 
 /**
  * This interface replicates a subset of the android.app.admin.DevicePolicyManager (DPM). The
@@ -33,6 +34,14 @@
     ComponentName getDeviceOwnerComponentOnAnyUser();
 
     /**
+     * Calls {@code DevicePolicyManager.getPermissionGrantState()}.
+     *
+     * @see android.app.admin.DevicePolicyManager#getPermissionGrantState
+     */
+    int getPermissionGrantState(@Nullable ComponentName admin, String packageName,
+            String permission);
+
+    /**
      * Calls {@code DevicePolicyManager.getLastSecurityLogRetrievalTime()}.
      *
      * @see android.app.admin.DevicePolicyManager#getLastSecurityLogRetrievalTime
diff --git a/src/com/android/settings/enterprise/DevicePolicyManagerWrapperImpl.java b/src/com/android/settings/enterprise/DevicePolicyManagerWrapperImpl.java
index 710e5ac..d122ec6 100644
--- a/src/com/android/settings/enterprise/DevicePolicyManagerWrapperImpl.java
+++ b/src/com/android/settings/enterprise/DevicePolicyManagerWrapperImpl.java
@@ -18,6 +18,7 @@
 
 import android.app.admin.DevicePolicyManager;
 import android.content.ComponentName;
+import android.support.annotation.Nullable;
 
 public class DevicePolicyManagerWrapperImpl implements DevicePolicyManagerWrapper {
     private final DevicePolicyManager mDpm;
@@ -32,6 +33,12 @@
     }
 
     @Override
+    public int getPermissionGrantState(@Nullable ComponentName admin, String packageName,
+            String permission) {
+        return mDpm.getPermissionGrantState(admin, packageName, permission);
+    }
+
+    @Override
     public long getLastSecurityLogRetrievalTime() {
         return mDpm.getLastSecurityLogRetrievalTime();
     }
diff --git a/src/com/android/settings/enterprise/EnterprisePrivacySettings.java b/src/com/android/settings/enterprise/EnterprisePrivacySettings.java
index 2cd2862..208bf0c 100644
--- a/src/com/android/settings/enterprise/EnterprisePrivacySettings.java
+++ b/src/com/android/settings/enterprise/EnterprisePrivacySettings.java
@@ -19,8 +19,8 @@
 import android.content.Context;
 import android.provider.SearchIndexableResource;
 
-import com.android.settings.R;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.settings.R;
 import com.android.settings.core.PreferenceController;
 import com.android.settings.dashboard.DashboardFragment;
 import com.android.settings.search.BaseSearchIndexProvider;
@@ -61,10 +61,13 @@
         controllers.add(new NetworkLogsPreferenceController(context));
         controllers.add(new BugReportsPreferenceController(context));
         controllers.add(new SecurityLogsPreferenceController(context));
+        controllers.add(new EnterpriseInstalledPackagesPreferenceController(context));
+        controllers.add(new AdminGrantedLocationPermissionsPreferenceController(context));
+        controllers.add(new AdminGrantedMicrophonePermissionPreferenceController(context));
+        controllers.add(new AdminGrantedCameraPermissionPreferenceController(context));
         controllers.add(new AlwaysOnVpnPrimaryUserPreferenceController(context));
         controllers.add(new AlwaysOnVpnManagedProfilePreferenceController(context));
         controllers.add(new GlobalHttpProxyPreferenceController(context));
-        controllers.add(new EnterpriseInstalledPackagesPreferenceController(context));
         return controllers;
     }
 
diff --git a/src/com/android/settings/fingerprint/SetupFingerprintEnrollIntroduction.java b/src/com/android/settings/fingerprint/SetupFingerprintEnrollIntroduction.java
index 546fc0e..3951aff 100644
--- a/src/com/android/settings/fingerprint/SetupFingerprintEnrollIntroduction.java
+++ b/src/com/android/settings/fingerprint/SetupFingerprintEnrollIntroduction.java
@@ -16,6 +16,7 @@
 
 package com.android.settings.fingerprint;
 
+import android.app.KeyguardManager;
 import android.content.Intent;
 import android.content.res.Resources;
 import android.os.UserHandle;
@@ -83,9 +84,17 @@
 
     @Override
     protected void onCancelButtonClick() {
-        SetupSkipDialog dialog = SetupSkipDialog.newInstance(
-                getIntent().getBooleanExtra(SetupSkipDialog.EXTRA_FRP_SUPPORTED, false));
-        dialog.show(getFragmentManager());
+        KeyguardManager keyguardManager = getSystemService(KeyguardManager.class);
+        if (keyguardManager.isKeyguardSecure()) {
+            // If the keyguard is already set up securely (maybe the user added a backup screen
+            // lock and skipped fingerprint), return RESULT_SKIP directly.
+            setResult(RESULT_SKIP);
+            finish();
+        } else {
+            SetupSkipDialog dialog = SetupSkipDialog.newInstance(
+                    getIntent().getBooleanExtra(SetupSkipDialog.EXTRA_FRP_SUPPORTED, false));
+            dialog.show(getFragmentManager());
+        }
     }
 
     @Override
diff --git a/src/com/android/settings/fuelgauge/HighPowerDetail.java b/src/com/android/settings/fuelgauge/HighPowerDetail.java
index a59dc78..296f973 100644
--- a/src/com/android/settings/fuelgauge/HighPowerDetail.java
+++ b/src/com/android/settings/fuelgauge/HighPowerDetail.java
@@ -30,10 +30,12 @@
 import android.widget.Checkable;
 import android.widget.TextView;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.nano.MetricsProto;
 import com.android.settings.R;
 import com.android.settings.applications.AppInfoBase;
 import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
+import com.android.settings.overlay.FeatureFactory;
 import com.android.settingslib.applications.ApplicationsState.AppEntry;
 
 public class HighPowerDetail extends InstrumentedDialogFragment implements OnClickListener,
@@ -125,6 +127,7 @@
             boolean newValue = mIsEnabled;
             boolean oldValue = mBackend.isWhitelisted(mPackageName);
             if (newValue != oldValue) {
+                logSpecialPermissionChange(newValue, mPackageName, getContext());
                 if (newValue) {
                     mBackend.addApp(mPackageName);
                 } else {
@@ -134,6 +137,14 @@
         }
     }
 
+    @VisibleForTesting
+    static void logSpecialPermissionChange(boolean whitelist, String packageName, Context context) {
+        int logCategory = whitelist ? MetricsProto.MetricsEvent.APP_SPECIAL_PERMISSION_BATTERY_DENY
+                : MetricsProto.MetricsEvent.APP_SPECIAL_PERMISSION_BATTERY_ALLOW;
+        FeatureFactory.getFactory(context).getMetricsFeatureProvider().action(context, logCategory,
+                packageName);
+    }
+
     @Override
     public void onDismiss(DialogInterface dialog) {
         super.onDismiss(dialog);
diff --git a/src/com/android/settings/notification/BootSoundPreferenceController.java b/src/com/android/settings/notification/BootSoundPreferenceController.java
new file mode 100644
index 0000000..b644ee9
--- /dev/null
+++ b/src/com/android/settings/notification/BootSoundPreferenceController.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2017 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.notification;
+
+import android.content.Context;
+import android.os.SystemProperties;
+import android.support.annotation.VisibleForTesting;
+import android.support.v14.preference.SwitchPreference;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceScreen;
+import com.android.settings.core.PreferenceController;
+
+public class BootSoundPreferenceController extends PreferenceController {
+
+    // Boot Sounds needs to be a system property so it can be accessed during boot.
+    private static final String KEY_BOOT_SOUNDS = "boot_sounds";
+    @VisibleForTesting
+    static final String PROPERTY_BOOT_SOUNDS = "persist.sys.bootanim.play_sound";
+
+    public BootSoundPreferenceController(Context context) {
+        super(context);
+    }
+
+    @Override
+    public void displayPreference(PreferenceScreen screen) {
+        super.displayPreference(screen);
+        if (isAvailable()) {
+            SwitchPreference preference = (SwitchPreference) screen.findPreference(KEY_BOOT_SOUNDS);
+            preference.setChecked(SystemProperties.getBoolean(PROPERTY_BOOT_SOUNDS, true));
+        }
+    }
+
+    @Override
+    public boolean handlePreferenceTreeClick(Preference preference) {
+        if (KEY_BOOT_SOUNDS.equals(preference.getKey())) {
+            SwitchPreference switchPreference = (SwitchPreference) preference;
+            SystemProperties.set(PROPERTY_BOOT_SOUNDS, switchPreference.isChecked() ? "1" : "0");
+        }
+        return false;
+    }
+
+    @Override
+    public String getPreferenceKey() {
+        return KEY_BOOT_SOUNDS;
+    }
+
+    @Override
+    public boolean isAvailable() {
+        return mContext.getResources().getBoolean(com.android.settings.R.bool.has_boot_sounds);
+    }
+
+}
\ No newline at end of file
diff --git a/src/com/android/settings/notification/ChargingSoundPreferenceController.java b/src/com/android/settings/notification/ChargingSoundPreferenceController.java
new file mode 100644
index 0000000..1114b4a
--- /dev/null
+++ b/src/com/android/settings/notification/ChargingSoundPreferenceController.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2017 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.notification;
+
+import static com.android.settings.notification.SettingPref.TYPE_GLOBAL;
+
+import android.content.Context;
+
+import android.provider.Settings.Global;
+import com.android.settings.SettingsPreferenceFragment;
+import com.android.settings.core.lifecycle.Lifecycle;
+
+public class ChargingSoundPreferenceController extends SettingPrefController {
+
+    private static final String KEY_CHARGING_SOUNDS = "charging_sounds";
+
+    public ChargingSoundPreferenceController(Context context, SettingsPreferenceFragment parent,
+            Lifecycle lifecycle) {
+        super(context, parent, lifecycle);
+        mPreference = new SettingPref(
+            TYPE_GLOBAL, KEY_CHARGING_SOUNDS, Global.CHARGING_SOUNDS_ENABLED, DEFAULT_ON);
+
+    }
+
+}
diff --git a/src/com/android/settings/notification/DialPadTonePreferenceController.java b/src/com/android/settings/notification/DialPadTonePreferenceController.java
new file mode 100644
index 0000000..08e1a7d
--- /dev/null
+++ b/src/com/android/settings/notification/DialPadTonePreferenceController.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2017 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.notification;
+
+import static com.android.settings.notification.SettingPref.TYPE_SYSTEM;
+
+import android.content.Context;
+
+import android.provider.Settings.System;
+import com.android.settings.SettingsPreferenceFragment;
+import com.android.settings.Utils;
+import com.android.settings.core.lifecycle.Lifecycle;
+
+public class DialPadTonePreferenceController extends SettingPrefController {
+
+    private static final String KEY_DIAL_PAD_TONES = "dial_pad_tones";
+
+    public DialPadTonePreferenceController(Context context, SettingsPreferenceFragment parent,
+            Lifecycle lifecycle) {
+        super(context, parent, lifecycle);
+        mPreference = new SettingPref(
+            TYPE_SYSTEM, KEY_DIAL_PAD_TONES, System.DTMF_TONE_WHEN_DIALING, DEFAULT_ON) {
+            @Override
+            public boolean isApplicable(Context context) {
+                return Utils.isVoiceCapable(context);
+            }
+        };
+    }
+
+}
diff --git a/src/com/android/settings/notification/DockAudioMediaPreferenceController.java b/src/com/android/settings/notification/DockAudioMediaPreferenceController.java
new file mode 100644
index 0000000..20c20b4
--- /dev/null
+++ b/src/com/android/settings/notification/DockAudioMediaPreferenceController.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2017 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.notification;
+
+import static com.android.settings.notification.SettingPref.TYPE_GLOBAL;
+
+import android.content.Context;
+
+import android.content.res.Resources;
+import android.provider.Settings.Global;
+import android.telephony.TelephonyManager;
+import com.android.settings.SettingsPreferenceFragment;
+import com.android.settings.core.lifecycle.Lifecycle;
+
+public class DockAudioMediaPreferenceController extends SettingPrefController {
+
+    private static final String KEY_DOCK_AUDIO_MEDIA = "dock_audio_media";
+
+    private static final int DOCK_AUDIO_MEDIA_DISABLED = 0;
+    private static final int DOCK_AUDIO_MEDIA_ENABLED = 1;
+    private static final int DEFAULT_DOCK_AUDIO_MEDIA = DOCK_AUDIO_MEDIA_DISABLED;
+
+    public DockAudioMediaPreferenceController(Context context, SettingsPreferenceFragment parent,
+            Lifecycle lifecycle) {
+        super(context, parent, lifecycle);
+        mPreference = new SettingPref(
+            TYPE_GLOBAL, KEY_DOCK_AUDIO_MEDIA, Global.DOCK_AUDIO_MEDIA_ENABLED,
+            DEFAULT_DOCK_AUDIO_MEDIA, DOCK_AUDIO_MEDIA_DISABLED, DOCK_AUDIO_MEDIA_ENABLED) {
+            @Override
+            public boolean isApplicable(Context context) {
+                return context.getResources().getBoolean(
+                    com.android.settings.R.bool.has_dock_settings);
+            }
+
+            @Override
+            protected String getCaption(Resources res, int value) {
+                switch(value) {
+                    case DOCK_AUDIO_MEDIA_DISABLED:
+                        return res.getString(
+                            com.android.settings.R.string.dock_audio_media_disabled);
+                    case DOCK_AUDIO_MEDIA_ENABLED:
+                        return res.getString(
+                            com.android.settings.R.string.dock_audio_media_enabled);
+                    default:
+                        throw new IllegalArgumentException();
+                }
+            }
+        };
+    }
+}
diff --git a/src/com/android/settings/notification/DockingSoundPreferenceController.java b/src/com/android/settings/notification/DockingSoundPreferenceController.java
new file mode 100644
index 0000000..ee277f0
--- /dev/null
+++ b/src/com/android/settings/notification/DockingSoundPreferenceController.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2017 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.notification;
+
+import static com.android.settings.notification.SettingPref.TYPE_GLOBAL;
+
+import android.content.Context;
+import android.provider.Settings.Global;
+import com.android.settings.R;
+import com.android.settings.SettingsPreferenceFragment;
+import com.android.settings.core.lifecycle.Lifecycle;
+
+public class DockingSoundPreferenceController extends SettingPrefController {
+
+    private static final String KEY_DOCKING_SOUNDS = "docking_sounds";
+
+    public DockingSoundPreferenceController(Context context, SettingsPreferenceFragment parent,
+            Lifecycle lifecycle) {
+        super(context, parent, lifecycle);
+        mPreference = new SettingPref(
+            TYPE_GLOBAL, KEY_DOCKING_SOUNDS, Global.DOCK_SOUNDS_ENABLED, DEFAULT_ON) {
+            @Override
+            public boolean isApplicable(Context context) {
+                return context.getResources().getBoolean(R.bool.has_dock_settings);
+            }
+        };
+    }
+
+}
diff --git a/src/com/android/settings/notification/EmergencyTonePreferenceController.java b/src/com/android/settings/notification/EmergencyTonePreferenceController.java
new file mode 100644
index 0000000..bc21f44
--- /dev/null
+++ b/src/com/android/settings/notification/EmergencyTonePreferenceController.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2017 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.notification;
+
+import static com.android.settings.notification.SettingPref.TYPE_GLOBAL;
+
+import android.content.Context;
+
+import android.content.res.Resources;
+import android.provider.Settings.Global;
+import android.telephony.TelephonyManager;
+import com.android.settings.R;
+import com.android.settings.SettingsPreferenceFragment;
+import com.android.settings.core.lifecycle.Lifecycle;
+
+public class EmergencyTonePreferenceController extends SettingPrefController {
+
+    private static final String KEY_EMERGENCY_TONE = "emergency_tone";
+    private static final int EMERGENCY_TONE_SILENT = 0;
+    private static final int EMERGENCY_TONE_ALERT = 1;
+    private static final int EMERGENCY_TONE_VIBRATE = 2;
+    private static final int DEFAULT_EMERGENCY_TONE = EMERGENCY_TONE_SILENT;
+
+    public EmergencyTonePreferenceController(Context context, SettingsPreferenceFragment parent,
+            Lifecycle lifecycle) {
+        super(context, parent, lifecycle);
+        mPreference = new SettingPref(
+            TYPE_GLOBAL, KEY_EMERGENCY_TONE, Global.EMERGENCY_TONE, DEFAULT_EMERGENCY_TONE,
+            EMERGENCY_TONE_ALERT, EMERGENCY_TONE_VIBRATE, EMERGENCY_TONE_SILENT) {
+
+            @Override
+            public boolean isApplicable(Context context) {
+                final TelephonyManager telephony =
+                    (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+                return telephony != null
+                    && telephony.getCurrentPhoneType() == TelephonyManager.PHONE_TYPE_CDMA;
+            }
+
+            @Override
+            protected String getCaption(Resources res, int value) {
+                switch(value) {
+                    case EMERGENCY_TONE_SILENT:
+                        return res.getString(R.string.emergency_tone_silent);
+                    case EMERGENCY_TONE_ALERT:
+                        return res.getString(R.string.emergency_tone_alert);
+                    case EMERGENCY_TONE_VIBRATE:
+                        return res.getString(R.string.emergency_tone_vibrate);
+                    default:
+                        throw new IllegalArgumentException();
+                }
+            }
+        };
+    }
+
+}
diff --git a/src/com/android/settings/notification/NotificationAccessSettings.java b/src/com/android/settings/notification/NotificationAccessSettings.java
index b032358..2cd728c 100644
--- a/src/com/android/settings/notification/NotificationAccessSettings.java
+++ b/src/com/android/settings/notification/NotificationAccessSettings.java
@@ -29,9 +29,12 @@
 import android.service.notification.NotificationListenerService;
 import android.util.Log;
 
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.settings.R;
 import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
+import com.android.settings.overlay.FeatureFactory;
 import com.android.settings.utils.ManagedServiceSettings;
 
 public class NotificationAccessSettings extends ManagedServiceSettings {
@@ -68,6 +71,7 @@
     }
 
     protected boolean setEnabled(ComponentName service, String title, boolean enable) {
+        logSpecialPermissionChange(enable, service.getPackageName());
         if (!enable) {
             if (!mServiceListing.isEnabled(service)) {
                 return true; // already disabled
@@ -82,6 +86,14 @@
         }
     }
 
+    @VisibleForTesting
+    void logSpecialPermissionChange(boolean enable, String packageName) {
+        int logCategory = enable ? MetricsEvent.APP_SPECIAL_PERMISSION_NOTIVIEW_ALLOW
+                : MetricsEvent.APP_SPECIAL_PERMISSION_NOTIVIEW_DENY;
+        FeatureFactory.getFactory(getContext()).getMetricsFeatureProvider().action(getContext(),
+                logCategory, packageName);
+    }
+
     private static void disable(final Context context, final NotificationAccessSettings parent,
             final ComponentName cn) {
         parent.mServiceListing.setEnabled(cn, false);
diff --git a/src/com/android/settings/notification/OtherSoundSettings.java b/src/com/android/settings/notification/OtherSoundSettings.java
index cf47cb5..15e7f8c 100644
--- a/src/com/android/settings/notification/OtherSoundSettings.java
+++ b/src/com/android/settings/notification/OtherSoundSettings.java
@@ -16,177 +16,26 @@
 
 package com.android.settings.notification;
 
-import android.content.ContentResolver;
 import android.content.Context;
-import android.content.res.Resources;
-import android.database.ContentObserver;
-import android.media.AudioManager;
-import android.net.Uri;
-import android.os.AsyncTask;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.SystemProperties;
-import android.os.Vibrator;
 import android.provider.SearchIndexableResource;
-import android.provider.Settings.Global;
-import android.provider.Settings.System;
-import android.support.v14.preference.SwitchPreference;
-import android.support.v7.preference.Preference;
-import android.telephony.TelephonyManager;
 
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.settings.R;
-import com.android.settings.SettingsPreferenceFragment;
-import com.android.settings.Utils;
+import com.android.settings.core.PreferenceController;
+import com.android.settings.core.lifecycle.Lifecycle;
+import com.android.settings.dashboard.DashboardFragment;
 import com.android.settings.search.BaseSearchIndexProvider;
-import com.android.settings.search.Indexable;
 
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 
-import static com.android.settings.notification.SettingPref.TYPE_GLOBAL;
-import static com.android.settings.notification.SettingPref.TYPE_SYSTEM;
-
-public class OtherSoundSettings extends SettingsPreferenceFragment implements Indexable {
+/* This class has been deprecated  Modifications to Other Sounds settings should be made in
+   {@link SoundSettings } instead. */
+@Deprecated
+public class OtherSoundSettings extends DashboardFragment {
     private static final String TAG = "OtherSoundSettings";
 
-    private static final int DEFAULT_ON = 1;
-
-    private static final int EMERGENCY_TONE_SILENT = 0;
-    private static final int EMERGENCY_TONE_ALERT = 1;
-    private static final int EMERGENCY_TONE_VIBRATE = 2;
-    private static final int DEFAULT_EMERGENCY_TONE = EMERGENCY_TONE_SILENT;
-
-    private static final int DOCK_AUDIO_MEDIA_DISABLED = 0;
-    private static final int DOCK_AUDIO_MEDIA_ENABLED = 1;
-    private static final int DEFAULT_DOCK_AUDIO_MEDIA = DOCK_AUDIO_MEDIA_DISABLED;
-
-    private static final String KEY_DIAL_PAD_TONES = "dial_pad_tones";
-    private static final String KEY_SCREEN_LOCKING_SOUNDS = "screen_locking_sounds";
-    private static final String KEY_CHARGING_SOUNDS = "charging_sounds";
-    private static final String KEY_DOCKING_SOUNDS = "docking_sounds";
-    private static final String KEY_TOUCH_SOUNDS = "touch_sounds";
-    private static final String KEY_VIBRATE_ON_TOUCH = "vibrate_on_touch";
-    private static final String KEY_DOCK_AUDIO_MEDIA = "dock_audio_media";
-    private static final String KEY_EMERGENCY_TONE = "emergency_tone";
-
-    // Boot Sounds needs to be a system property so it can be accessed during boot.
-    private static final String KEY_BOOT_SOUNDS = "boot_sounds";
-    private static final String PROPERTY_BOOT_SOUNDS = "persist.sys.bootanim.play_sound";
-
-    private static final SettingPref PREF_DIAL_PAD_TONES = new SettingPref(
-            TYPE_SYSTEM, KEY_DIAL_PAD_TONES, System.DTMF_TONE_WHEN_DIALING, DEFAULT_ON) {
-        @Override
-        public boolean isApplicable(Context context) {
-            return Utils.isVoiceCapable(context);
-        }
-    };
-
-    private static final SettingPref PREF_SCREEN_LOCKING_SOUNDS = new SettingPref(
-            TYPE_SYSTEM, KEY_SCREEN_LOCKING_SOUNDS, System.LOCKSCREEN_SOUNDS_ENABLED, DEFAULT_ON);
-
-    private static final SettingPref PREF_CHARGING_SOUNDS = new SettingPref(
-            TYPE_GLOBAL, KEY_CHARGING_SOUNDS, Global.CHARGING_SOUNDS_ENABLED, DEFAULT_ON);
-
-    private static final SettingPref PREF_DOCKING_SOUNDS = new SettingPref(
-            TYPE_GLOBAL, KEY_DOCKING_SOUNDS, Global.DOCK_SOUNDS_ENABLED, DEFAULT_ON) {
-        @Override
-        public boolean isApplicable(Context context) {
-            return hasDockSettings(context);
-        }
-    };
-
-    private static final SettingPref PREF_TOUCH_SOUNDS = new SettingPref(
-            TYPE_SYSTEM, KEY_TOUCH_SOUNDS, System.SOUND_EFFECTS_ENABLED, DEFAULT_ON) {
-        @Override
-        protected boolean setSetting(final Context context, final int value) {
-            AsyncTask.execute(new Runnable() {
-                @Override
-                public void run() {
-                    final AudioManager am =
-                            (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
-                    if (value != 0) {
-                        am.loadSoundEffects();
-                    } else {
-                        am.unloadSoundEffects();
-                    }
-                }
-            });
-            return super.setSetting(context, value);
-        }
-    };
-
-    private static final SettingPref PREF_VIBRATE_ON_TOUCH = new SettingPref(
-            TYPE_SYSTEM, KEY_VIBRATE_ON_TOUCH, System.HAPTIC_FEEDBACK_ENABLED, DEFAULT_ON) {
-        @Override
-        public boolean isApplicable(Context context) {
-            return hasHaptic(context);
-        }
-    };
-
-    private static final SettingPref PREF_DOCK_AUDIO_MEDIA = new SettingPref(
-            TYPE_GLOBAL, KEY_DOCK_AUDIO_MEDIA, Global.DOCK_AUDIO_MEDIA_ENABLED,
-            DEFAULT_DOCK_AUDIO_MEDIA, DOCK_AUDIO_MEDIA_DISABLED, DOCK_AUDIO_MEDIA_ENABLED) {
-        @Override
-        public boolean isApplicable(Context context) {
-            return hasDockSettings(context);
-        }
-
-        @Override
-        protected String getCaption(Resources res, int value) {
-            switch(value) {
-                case DOCK_AUDIO_MEDIA_DISABLED:
-                    return res.getString(R.string.dock_audio_media_disabled);
-                case DOCK_AUDIO_MEDIA_ENABLED:
-                    return res.getString(R.string.dock_audio_media_enabled);
-                default:
-                    throw new IllegalArgumentException();
-            }
-        }
-    };
-
-    private static final SettingPref PREF_EMERGENCY_TONE = new SettingPref(
-            TYPE_GLOBAL, KEY_EMERGENCY_TONE, Global.EMERGENCY_TONE, DEFAULT_EMERGENCY_TONE,
-            EMERGENCY_TONE_ALERT, EMERGENCY_TONE_VIBRATE, EMERGENCY_TONE_SILENT) {
-        @Override
-        public boolean isApplicable(Context context) {
-            final int activePhoneType = TelephonyManager.getDefault().getCurrentPhoneType();
-            return activePhoneType == TelephonyManager.PHONE_TYPE_CDMA;
-        }
-
-        @Override
-        protected String getCaption(Resources res, int value) {
-            switch(value) {
-                case EMERGENCY_TONE_SILENT:
-                    return res.getString(R.string.emergency_tone_silent);
-                case EMERGENCY_TONE_ALERT:
-                    return res.getString(R.string.emergency_tone_alert);
-                case EMERGENCY_TONE_VIBRATE:
-                    return res.getString(R.string.emergency_tone_vibrate);
-                default:
-                    throw new IllegalArgumentException();
-            }
-        }
-    };
-
-    private static final SettingPref[] PREFS = {
-        PREF_DIAL_PAD_TONES,
-        PREF_SCREEN_LOCKING_SOUNDS,
-        PREF_CHARGING_SOUNDS,
-        PREF_DOCKING_SOUNDS,
-        PREF_TOUCH_SOUNDS,
-        PREF_VIBRATE_ON_TOUCH,
-        PREF_DOCK_AUDIO_MEDIA,
-        PREF_EMERGENCY_TONE,
-    };
-
-    private SwitchPreference mBootSounds;
-
-    private final SettingsObserver mSettingsObserver = new SettingsObserver();
-
-    private Context mContext;
-
     @Override
     public int getMetricsCategory() {
         return MetricsEvent.NOTIFICATION_OTHER_SOUND;
@@ -198,84 +47,34 @@
     }
 
     @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        addPreferencesFromResource(R.xml.other_sound_settings);
-
-        mContext = getActivity();
-
-        for (SettingPref pref : PREFS) {
-            pref.init(this);
-        }
-
-        if (mContext.getResources().getBoolean(R.bool.has_boot_sounds)) {
-            mBootSounds = (SwitchPreference) findPreference(KEY_BOOT_SOUNDS);
-            mBootSounds.setChecked(SystemProperties.getBoolean(PROPERTY_BOOT_SOUNDS, true));
-        } else {
-            removePreference(KEY_BOOT_SOUNDS);
-        }
+    protected String getCategoryKey() {
+        return null;
     }
 
     @Override
-    public void onResume() {
-        super.onResume();
-        mSettingsObserver.register(true);
+    protected String getLogTag() {
+        return TAG;
     }
 
     @Override
-    public void onPause() {
-        super.onPause();
-        mSettingsObserver.register(false);
+    protected int getPreferenceScreenResId() {
+        return R.xml.other_sound_settings;
     }
 
     @Override
-    public boolean onPreferenceTreeClick(Preference preference) {
-        if (mBootSounds != null && preference == mBootSounds) {
-            SystemProperties.set(PROPERTY_BOOT_SOUNDS, mBootSounds.isChecked() ? "1" : "0");
-            return false;
-        } else {
-            return super.onPreferenceTreeClick(preference);
-        }
-    }
-
-    private static boolean hasDockSettings(Context context) {
-        return context.getResources().getBoolean(R.bool.has_dock_settings);
-    }
-
-    private static boolean hasHaptic(Context context) {
-        final Vibrator vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
-        return vibrator != null && vibrator.hasVibrator();
-    }
-
-    // === Callbacks ===
-
-    private final class SettingsObserver extends ContentObserver {
-        public SettingsObserver() {
-            super(new Handler());
-        }
-
-        public void register(boolean register) {
-            final ContentResolver cr = getContentResolver();
-            if (register) {
-                for (SettingPref pref : PREFS) {
-                    cr.registerContentObserver(pref.getUri(), false, this);
-                }
-            } else {
-                cr.unregisterContentObserver(this);
-            }
-        }
-
-        @Override
-        public void onChange(boolean selfChange, Uri uri) {
-            super.onChange(selfChange, uri);
-            for (SettingPref pref : PREFS) {
-                if (pref.getUri().equals(uri)) {
-                    pref.update(mContext);
-                    return;
-                }
-            }
-        }
+    protected List<PreferenceController> getPreferenceControllers(Context context) {
+        final List<PreferenceController> controllers = new ArrayList<>();
+        Lifecycle lifecycle = getLifecycle();
+        controllers.add(new DialPadTonePreferenceController(context, this, lifecycle));
+        controllers.add(new ScreenLockSoundPreferenceController(context, this, lifecycle));
+        controllers.add(new ChargingSoundPreferenceController(context, this, lifecycle));
+        controllers.add(new DockingSoundPreferenceController(context, this, lifecycle));
+        controllers.add(new TouchSoundPreferenceController(context, this, lifecycle));
+        controllers.add(new VibrateOnTouchPreferenceController(context, this, lifecycle));
+        controllers.add(new DockAudioMediaPreferenceController(context, this, lifecycle));
+        controllers.add(new BootSoundPreferenceController(context));
+        controllers.add(new EmergencyTonePreferenceController(context, this, lifecycle));
+        return controllers;
     }
 
     // === Indexing ===
@@ -292,11 +91,23 @@
 
         public List<String> getNonIndexableKeys(Context context) {
             final ArrayList<String> rt = new ArrayList<String>();
-            for (SettingPref pref : PREFS) {
-                if (!pref.isApplicable(context)) {
-                    rt.add(pref.getKey());
-                }
-            }
+            new DialPadTonePreferenceController(context, null /* SettingsPreferenceFragment */,
+                null /* Lifecycle */).updateNonIndexableKeys(rt);
+            new ScreenLockSoundPreferenceController(context, null /* SettingsPreferenceFragment */,
+                null /* Lifecycle */).updateNonIndexableKeys(rt);
+            new ChargingSoundPreferenceController(context, null /* SettingsPreferenceFragment */,
+                null /* Lifecycle */).updateNonIndexableKeys(rt);
+            new DockingSoundPreferenceController(context, null /* SettingsPreferenceFragment */,
+                null /* Lifecycle */).updateNonIndexableKeys(rt);
+            new TouchSoundPreferenceController(context, null /* SettingsPreferenceFragment */,
+                null /* Lifecycle */).updateNonIndexableKeys(rt);
+            new VibrateOnTouchPreferenceController(context, null /* SettingsPreferenceFragment */,
+                null /* Lifecycle */).updateNonIndexableKeys(rt);
+            new DockAudioMediaPreferenceController(context, null /* SettingsPreferenceFragment */,
+                null /* Lifecycle */).updateNonIndexableKeys(rt);
+            new BootSoundPreferenceController(context).updateNonIndexableKeys(rt);
+            new EmergencyTonePreferenceController(context, null /* SettingsPreferenceFragment */,
+                null /* Lifecycle */).updateNonIndexableKeys(rt);
             return rt;
         }
     };
diff --git a/src/com/android/settings/notification/ScreenLockSoundPreferenceController.java b/src/com/android/settings/notification/ScreenLockSoundPreferenceController.java
new file mode 100644
index 0000000..f9905c0
--- /dev/null
+++ b/src/com/android/settings/notification/ScreenLockSoundPreferenceController.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2017 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.notification;
+
+import static com.android.settings.notification.SettingPref.TYPE_SYSTEM;
+
+import android.content.Context;
+
+import android.provider.Settings.System;
+import com.android.settings.SettingsPreferenceFragment;
+import com.android.settings.core.lifecycle.Lifecycle;
+
+public class ScreenLockSoundPreferenceController extends SettingPrefController {
+
+    private static final String KEY_SCREEN_LOCKING_SOUNDS = "screen_locking_sounds";
+
+    public ScreenLockSoundPreferenceController(Context context, SettingsPreferenceFragment parent,
+            Lifecycle lifecycle) {
+        super(context, parent, lifecycle);
+        mPreference = new SettingPref(
+            TYPE_SYSTEM, KEY_SCREEN_LOCKING_SOUNDS, System.LOCKSCREEN_SOUNDS_ENABLED, DEFAULT_ON);
+    }
+
+}
diff --git a/src/com/android/settings/notification/SettingPrefController.java b/src/com/android/settings/notification/SettingPrefController.java
new file mode 100644
index 0000000..d126fc4
--- /dev/null
+++ b/src/com/android/settings/notification/SettingPrefController.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2017 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.notification;
+
+import android.content.ContentResolver;
+import android.content.Context;
+
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceScreen;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.settings.SettingsPreferenceFragment;
+import com.android.settings.core.PreferenceController;
+import com.android.settings.core.lifecycle.Lifecycle;
+import com.android.settings.core.lifecycle.LifecycleObserver;
+import com.android.settings.core.lifecycle.events.OnPause;
+import com.android.settings.core.lifecycle.events.OnResume;
+import java.util.List;
+
+public abstract class SettingPrefController extends PreferenceController implements
+    LifecycleObserver, OnResume, OnPause {
+
+    protected static final int DEFAULT_ON = 1;
+
+    private SettingsPreferenceFragment mParent;
+    protected SettingsObserver mSettingsObserver;
+    protected SettingPref mPreference;
+
+    public SettingPrefController(Context context, SettingsPreferenceFragment parent,
+            Lifecycle lifecycle) {
+        super(context);
+        mParent = parent;
+        if (lifecycle != null) {
+            lifecycle.addObserver(this);
+        }
+    }
+
+    @Override
+    public void displayPreference(PreferenceScreen screen) {
+        mPreference.init(mParent);
+        if (isAvailable()) {
+            mSettingsObserver = new SettingsObserver();
+        }
+    }
+
+    @Override
+    public String getPreferenceKey() {
+        return mPreference.getKey();
+    }
+
+    @Override
+    public boolean isAvailable() {
+        return mPreference.isApplicable(mContext);
+    }
+
+    @Override
+    public void updateNonIndexableKeys(List<String> keys) {
+        if (!mPreference.isApplicable(mContext)) {
+            keys.add(mPreference.getKey());
+        }
+    }
+
+    @Override
+    public void updateState(Preference preference) {
+        mPreference.update(mContext);
+    }
+
+    @Override
+    public void onResume() {
+        if (mSettingsObserver != null) {
+            mSettingsObserver.register(true /* register */);
+        }
+    }
+
+    @Override
+    public void onPause() {
+        if (mSettingsObserver != null) {
+            mSettingsObserver.register(false /* register */);
+        }
+    }
+
+    @VisibleForTesting
+    final class SettingsObserver extends ContentObserver {
+        public SettingsObserver() {
+            super(new Handler());
+        }
+
+        public void register(boolean register) {
+            final ContentResolver cr = mContext.getContentResolver();
+            if (register) {
+                cr.registerContentObserver(mPreference.getUri(), false, this);
+            } else {
+                cr.unregisterContentObserver(this);
+            }
+        }
+
+        @Override
+        public void onChange(boolean selfChange, Uri uri) {
+            super.onChange(selfChange, uri);
+            if (mPreference.getUri().equals(uri)) {
+                mPreference.update(mContext);
+            }
+        }
+    }
+
+}
diff --git a/src/com/android/settings/notification/SoundSettings.java b/src/com/android/settings/notification/SoundSettings.java
index f156a84..a55278d 100644
--- a/src/com/android/settings/notification/SoundSettings.java
+++ b/src/com/android/settings/notification/SoundSettings.java
@@ -41,6 +41,7 @@
 import com.android.settings.core.lifecycle.Lifecycle;
 import com.android.settings.dashboard.DashboardFragment;
 import com.android.settings.dashboard.SummaryLoader;
+import com.android.settings.overlay.FeatureFactory;
 import com.android.settings.search.BaseSearchIndexProvider;
 import com.android.settingslib.drawer.CategoryKey;
 import java.text.NumberFormat;
@@ -113,14 +114,17 @@
 
     @Override
     protected int getPreferenceScreenResId() {
-        return R.xml.sound_settings;
+        return mDashboardFeatureProvider.isEnabled()
+            ? R.xml.ia_sound_settings : R.xml.sound_settings;
     }
 
     @Override
     protected List<PreferenceController> getPreferenceControllers(Context context) {
         final List<PreferenceController> controllers = new ArrayList<>();
         Lifecycle lifecycle = getLifecycle();
-        controllers.add(new CastPreferenceController(context));
+        if (!mDashboardFeatureProvider.isEnabled()) {
+            controllers.add(new CastPreferenceController(context));
+        }
         controllers.add(new ZenModePreferenceController(context));
         controllers.add(new EmergencyBroadcastPreferenceController(context));
         controllers.add(new VibrateWhenRingPreferenceController(context));
@@ -141,6 +145,19 @@
         mWorkSoundController = new WorkSoundPreferenceController(context, this, getLifecycle());
         controllers.add(mWorkSoundController);
 
+        // === Other Sound Settings ===
+        if (mDashboardFeatureProvider.isEnabled()) {
+            controllers.add(new DialPadTonePreferenceController(context, this, lifecycle));
+            controllers.add(new ScreenLockSoundPreferenceController(context, this, lifecycle));
+            controllers.add(new ChargingSoundPreferenceController(context, this, lifecycle));
+            controllers.add(new DockingSoundPreferenceController(context, this, lifecycle));
+            controllers.add(new TouchSoundPreferenceController(context, this, lifecycle));
+            controllers.add(new VibrateOnTouchPreferenceController(context, this, lifecycle));
+            controllers.add(new DockAudioMediaPreferenceController(context, this, lifecycle));
+            controllers.add(new BootSoundPreferenceController(context));
+            controllers.add(new EmergencyTonePreferenceController(context, this, lifecycle));
+        }
+
         return controllers;
     }
 
@@ -297,10 +314,38 @@
                 context, null /* Callback */, null /* Lifecycle */).updateNonIndexableKeys(rt);
             new RingVolumePreferenceController(
                 context, null /* Callback */, null /* Lifecycle */).updateNonIndexableKeys(rt);
-            new CastPreferenceController(context).updateNonIndexableKeys(rt);
             new PhoneRingtonePreferenceController(context).updateNonIndexableKeys(rt);
             new VibrateWhenRingPreferenceController(context).updateNonIndexableKeys(rt);
             new EmergencyBroadcastPreferenceController(context).updateNonIndexableKeys(rt);
+            if (FeatureFactory.getFactory(context).getDashboardFeatureProvider(context)
+                .isEnabled()) {
+                new DialPadTonePreferenceController(context,
+                    null /* SettingsPreferenceFragment */,
+                    null /* Lifecycle */).updateNonIndexableKeys(rt);
+                new ScreenLockSoundPreferenceController(context,
+                    null /* SettingsPreferenceFragment */,
+                    null /* Lifecycle */).updateNonIndexableKeys(rt);
+                new ChargingSoundPreferenceController(context,
+                    null /* SettingsPreferenceFragment */,
+                    null /* Lifecycle */).updateNonIndexableKeys(rt);
+                new DockingSoundPreferenceController(context,
+                    null /* SettingsPreferenceFragment */,
+                    null /* Lifecycle */).updateNonIndexableKeys(rt);
+                new TouchSoundPreferenceController(context, null /* SettingsPreferenceFragment */,
+                    null /* Lifecycle */).updateNonIndexableKeys(rt);
+                new VibrateOnTouchPreferenceController(context,
+                    null /* SettingsPreferenceFragment */,
+                    null /* Lifecycle */).updateNonIndexableKeys(rt);
+                new DockAudioMediaPreferenceController(context,
+                    null /* SettingsPreferenceFragment */,
+                    null /* Lifecycle */).updateNonIndexableKeys(rt);
+                new BootSoundPreferenceController(context).updateNonIndexableKeys(rt);
+                new EmergencyTonePreferenceController(context,
+                    null /* SettingsPreferenceFragment */,
+                    null /* Lifecycle */).updateNonIndexableKeys(rt);
+            } else {
+                new CastPreferenceController(context).updateNonIndexableKeys(rt);
+            }
 
             return rt;
         }
diff --git a/src/com/android/settings/notification/TouchSoundPreferenceController.java b/src/com/android/settings/notification/TouchSoundPreferenceController.java
new file mode 100644
index 0000000..4ca5ea0
--- /dev/null
+++ b/src/com/android/settings/notification/TouchSoundPreferenceController.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2017 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.notification;
+
+import static com.android.settings.notification.SettingPref.TYPE_SYSTEM;
+
+import android.content.Context;
+
+import android.media.AudioManager;
+import android.os.AsyncTask;
+import android.provider.Settings.System;
+import com.android.settings.SettingsPreferenceFragment;
+import com.android.settings.core.lifecycle.Lifecycle;
+
+public class TouchSoundPreferenceController extends SettingPrefController {
+
+    private static final String KEY_TOUCH_SOUNDS = "touch_sounds";
+
+    public TouchSoundPreferenceController(Context context, SettingsPreferenceFragment parent,
+            Lifecycle lifecycle) {
+        super(context, parent, lifecycle);
+        mPreference = new SettingPref(
+            TYPE_SYSTEM, KEY_TOUCH_SOUNDS, System.SOUND_EFFECTS_ENABLED, DEFAULT_ON) {
+            @Override
+            protected boolean setSetting(final Context context, final int value) {
+                AsyncTask.execute(new Runnable() {
+                    @Override
+                    public void run() {
+                        final AudioManager am =
+                            (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+                        if (value != 0) {
+                            am.loadSoundEffects();
+                        } else {
+                            am.unloadSoundEffects();
+                        }
+                    }
+                });
+                return super.setSetting(context, value);
+            }
+        };
+    }
+}
diff --git a/src/com/android/settings/notification/VibrateOnTouchPreferenceController.java b/src/com/android/settings/notification/VibrateOnTouchPreferenceController.java
new file mode 100644
index 0000000..8fd938e
--- /dev/null
+++ b/src/com/android/settings/notification/VibrateOnTouchPreferenceController.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2017 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.notification;
+
+import static com.android.settings.notification.SettingPref.TYPE_SYSTEM;
+
+import android.content.Context;
+import android.os.Vibrator;
+import android.provider.Settings.System;
+import com.android.settings.SettingsPreferenceFragment;
+import com.android.settings.core.lifecycle.Lifecycle;
+
+public class VibrateOnTouchPreferenceController extends SettingPrefController {
+
+    private static final String KEY_VIBRATE_ON_TOUCH = "vibrate_on_touch";
+
+    public VibrateOnTouchPreferenceController(Context context, SettingsPreferenceFragment parent,
+            Lifecycle lifecycle) {
+        super(context, parent, lifecycle);
+        mPreference = new SettingPref(
+            TYPE_SYSTEM, KEY_VIBRATE_ON_TOUCH, System.HAPTIC_FEEDBACK_ENABLED, DEFAULT_ON) {
+            @Override
+            public boolean isApplicable(Context context) {
+                return hasHaptic(context);
+            }
+        };
+
+    }
+
+    private static boolean hasHaptic(Context context) {
+        final Vibrator vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
+        return vibrator != null && vibrator.hasVibrator();
+    }
+
+}
diff --git a/src/com/android/settings/notification/ZenAccessSettings.java b/src/com/android/settings/notification/ZenAccessSettings.java
index f9b1c12..a41a733 100644
--- a/src/com/android/settings/notification/ZenAccessSettings.java
+++ b/src/com/android/settings/notification/ZenAccessSettings.java
@@ -44,9 +44,11 @@
 import android.view.View;
 import android.widget.Toast;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.settings.R;
 import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
+import com.android.settings.overlay.FeatureFactory;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -171,6 +173,7 @@
     }
 
     private static void setAccess(final Context context, final String pkg, final boolean access) {
+        logSpecialPermissionChange(access, pkg, context);
         AsyncTask.execute(new Runnable() {
             @Override
             public void run() {
@@ -180,6 +183,15 @@
         });
     }
 
+    @VisibleForTesting
+    static void logSpecialPermissionChange(boolean enable, String packageName, Context context) {
+        int logCategory = enable ? MetricsEvent.APP_SPECIAL_PERMISSION_DND_ALLOW
+                : MetricsEvent.APP_SPECIAL_PERMISSION_DND_DENY;
+        FeatureFactory.getFactory(context).getMetricsFeatureProvider().action(context,
+                logCategory, packageName);
+    }
+
+
     private static void deleteRules(final Context context, final String pkg) {
         AsyncTask.execute(new Runnable() {
             @Override
diff --git a/src/com/android/settings/overlay/FeatureFactoryImpl.java b/src/com/android/settings/overlay/FeatureFactoryImpl.java
index 060b58c..18fcaaf 100644
--- a/src/com/android/settings/overlay/FeatureFactoryImpl.java
+++ b/src/com/android/settings/overlay/FeatureFactoryImpl.java
@@ -16,6 +16,7 @@
 
 package com.android.settings.overlay;
 
+import android.app.AppGlobals;
 import android.app.admin.DevicePolicyManager;
 import android.content.Context;
 import android.net.ConnectivityManager;
@@ -85,7 +86,10 @@
     public ApplicationFeatureProvider getApplicationFeatureProvider(Context context) {
         if (mApplicationFeatureProvider == null) {
             mApplicationFeatureProvider = new ApplicationFeatureProviderImpl(context,
-                    new PackageManagerWrapperImpl(context.getPackageManager()));
+                    new PackageManagerWrapperImpl(context.getPackageManager()),
+                    AppGlobals.getPackageManager(),
+                    new DevicePolicyManagerWrapperImpl((DevicePolicyManager) context
+                            .getSystemService(Context.DEVICE_POLICY_SERVICE)));
         }
         return mApplicationFeatureProvider;
     }
diff --git a/tests/robotests/Android.mk b/tests/robotests/Android.mk
index 0a49be2..3fc034d 100644
--- a/tests/robotests/Android.mk
+++ b/tests/robotests/Android.mk
@@ -14,7 +14,8 @@
 LOCAL_JAVA_LIBRARIES := \
     junit \
     platform-robolectric-prebuilt \
-    sdk_vcurrent
+    sdk_vcurrent \
+    telephony-common
 
 LOCAL_INSTRUMENTATION_FOR := Settings
 LOCAL_MODULE := SettingsRoboTests
diff --git a/tests/robotests/src/com/android/settings/DeviceAdminAddTest.java b/tests/robotests/src/com/android/settings/DeviceAdminAddTest.java
new file mode 100644
index 0000000..42aed2b
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/DeviceAdminAddTest.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2017 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 android.content.Context;
+
+import com.android.internal.logging.nano.MetricsProto;
+import com.android.settings.testutils.FakeFeatureFactory;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.Robolectric;
+import org.robolectric.annotation.Config;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.verify;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class DeviceAdminAddTest {
+
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private Context mContext;
+    private FakeFeatureFactory mFeatureFactory;
+    private DeviceAdminAdd mDeviceAdminAdd;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        FakeFeatureFactory.setupForTest(mContext);
+        mFeatureFactory = (FakeFeatureFactory) FakeFeatureFactory.getFactory(mContext);
+        mDeviceAdminAdd = Robolectric.buildActivity(DeviceAdminAdd.class).get();
+    }
+
+    @Test
+    public void logSpecialPermissionChange() {
+        mDeviceAdminAdd.logSpecialPermissionChange(true, "app");
+        verify(mFeatureFactory.metricsFeatureProvider).action(any(Context.class),
+                eq(MetricsProto.MetricsEvent.APP_SPECIAL_PERMISSION_ADMIN_ALLOW), eq("app"));
+
+        mDeviceAdminAdd.logSpecialPermissionChange(false, "app");
+        verify(mFeatureFactory.metricsFeatureProvider).action(any(Context.class),
+                eq(MetricsProto.MetricsEvent.APP_SPECIAL_PERMISSION_ADMIN_DENY), eq("app"));
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/applications/AppWithAdminGrantedPermissionsCounterTest.java b/tests/robotests/src/com/android/settings/applications/AppWithAdminGrantedPermissionsCounterTest.java
new file mode 100644
index 0000000..9fc416d
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/applications/AppWithAdminGrantedPermissionsCounterTest.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2017 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.applications;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
+import android.os.Build;
+import android.os.UserHandle;
+
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settings.enterprise.DevicePolicyManagerWrapper;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowApplication;
+
+import java.util.Arrays;
+import java.util.List;
+
+import static com.android.settings.testutils.ApplicationTestUtils.buildInfo;
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyObject;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+/**
+ * Tests for {@link InstalledAppCounter}.
+ */
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public final class AppWithAdminGrantedPermissionsCounterTest {
+
+    private final String APP_1 = "app1";
+    private final String APP_2 = "app2";
+    private final String APP_3 = "app3";
+    private final String APP_4 = "app4";
+    private final String APP_5 = "app5";
+    private final String APP_6 = "app6";
+
+    private final int MAIN_USER_ID = 0;
+    private final int MANAGED_PROFILE_ID = 10;
+
+    private final int PER_USER_UID_RANGE = 100000;
+    private final int APP_1_UID = MAIN_USER_ID * PER_USER_UID_RANGE + 1;
+    private final int APP_2_UID = MAIN_USER_ID * PER_USER_UID_RANGE + 2;
+    private final int APP_3_UID = MAIN_USER_ID * PER_USER_UID_RANGE + 3;
+    private final int APP_4_UID = MAIN_USER_ID * PER_USER_UID_RANGE + 4;
+    private final int APP_5_UID = MAIN_USER_ID * PER_USER_UID_RANGE + 5;
+    private final int APP_6_UID = MANAGED_PROFILE_ID * PER_USER_UID_RANGE + 1;
+
+    private final String PERMISSION_1 = "some.permission.1";
+    private final String PERMISSION_2 = "some.permission.2";
+    private final String[] PERMISSIONS = {PERMISSION_1, PERMISSION_2};
+
+    @Mock private Context mContext;
+    @Mock private PackageManagerWrapper mPackageManager;
+    @Mock private IPackageManager mPackageManagerService;
+    @Mock private DevicePolicyManagerWrapper mDevicePolicyManager;
+    private List<UserInfo> mUsersToCount;
+
+    private int mAppCount = -1;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    @Test
+    public void testCountInstalledAppsAcrossAllUsers() throws Exception {
+        // There are two users.
+        mUsersToCount = Arrays.asList(
+                new UserInfo(MAIN_USER_ID, "main", UserInfo.FLAG_ADMIN),
+                new UserInfo(MANAGED_PROFILE_ID, "managed profile", 0));
+
+        // The first user has five apps installed:
+        // * app1 uses run-time permissions. It has been granted one of the permissions by the
+        //        admin. It should be counted.
+        // * app2 uses run-time permissions. It has not been granted any of the permissions by the
+        //        admin. It should not be counted.
+        // * app3 uses install-time permissions. It was installed by the admin and requested one of
+        //        the permissions. It should be counted.
+        // * app4 uses install-time permissions. It was not installed by the admin but did request
+        //        one of the permissions. It should not be counted.
+        // * app5 uses install-time permissions. It was installed by the admin but did not request
+        //        any of the permissions. It should not be counted.
+        when(mPackageManager.getInstalledApplicationsAsUser(PackageManager.GET_DISABLED_COMPONENTS
+                | PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS
+                | PackageManager.MATCH_ANY_USER,
+                MAIN_USER_ID)).thenReturn(Arrays.asList(
+                        buildInfo(APP_1_UID, APP_1, 0 /* flags */, Build.VERSION_CODES.M),
+                        buildInfo(APP_2_UID, APP_2, 0 /* flags */, Build.VERSION_CODES.M),
+                        buildInfo(APP_3_UID, APP_3, 0 /* flags */, Build.VERSION_CODES.LOLLIPOP),
+                        buildInfo(APP_4_UID, APP_4, 0 /* flags */, Build.VERSION_CODES.LOLLIPOP),
+                        buildInfo(APP_5_UID, APP_5, 0 /* flags */, Build.VERSION_CODES.LOLLIPOP)));
+
+        // Grant run-time permissions as appropriate.
+        when(mDevicePolicyManager.getPermissionGrantState(null, APP_1, PERMISSION_1))
+                .thenReturn(DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED);
+        when(mDevicePolicyManager.getPermissionGrantState(null, APP_1, PERMISSION_2))
+                .thenReturn(DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED);
+        when(mDevicePolicyManager.getPermissionGrantState(eq(null), eq(APP_2), anyObject()))
+                .thenReturn(DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED);
+        when(mDevicePolicyManager.getPermissionGrantState(eq(null), eq(APP_3), anyObject()))
+                .thenReturn(DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED);
+        when(mDevicePolicyManager.getPermissionGrantState(eq(null), eq(APP_4), anyObject()))
+                .thenReturn(DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED);
+        when(mDevicePolicyManager.getPermissionGrantState(eq(null), eq(APP_5), anyObject()))
+                .thenReturn(DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED);
+
+        // Grant install-time permissions as appropriate.
+        when(mPackageManagerService.checkUidPermission(anyObject(), eq(APP_1_UID)))
+                .thenReturn(PackageManager.PERMISSION_DENIED);
+        when(mPackageManagerService.checkUidPermission(anyObject(), eq(APP_2_UID)))
+                .thenReturn(PackageManager.PERMISSION_DENIED);
+        when(mPackageManagerService.checkUidPermission(PERMISSION_1, APP_3_UID))
+                .thenReturn(PackageManager.PERMISSION_DENIED);
+        when(mPackageManagerService.checkUidPermission(PERMISSION_2, APP_3_UID))
+                .thenReturn(PackageManager.PERMISSION_GRANTED);
+        when(mPackageManagerService.checkUidPermission(PERMISSION_1, APP_4_UID))
+                .thenReturn(PackageManager.PERMISSION_DENIED);
+        when(mPackageManagerService.checkUidPermission(PERMISSION_2, APP_4_UID))
+                .thenReturn(PackageManager.PERMISSION_GRANTED);
+        when(mPackageManagerService.checkUidPermission(anyObject(), eq(APP_5_UID)))
+                .thenReturn(PackageManager.PERMISSION_DENIED);
+
+        // app3 and app5 were installed by enterprise policy.
+        final UserHandle mainUser = new UserHandle(MAIN_USER_ID);
+        when(mPackageManager.getInstallReason(APP_1, mainUser))
+                .thenReturn(PackageManager.INSTALL_REASON_UNKNOWN);
+        when(mPackageManager.getInstallReason(APP_2, mainUser))
+                .thenReturn(PackageManager.INSTALL_REASON_UNKNOWN);
+        when(mPackageManager.getInstallReason(APP_3, mainUser))
+                .thenReturn(PackageManager.INSTALL_REASON_POLICY);
+        when(mPackageManager.getInstallReason(APP_4, mainUser))
+                .thenReturn(PackageManager.INSTALL_REASON_UNKNOWN);
+        when(mPackageManager.getInstallReason(APP_5, mainUser))
+                .thenReturn(PackageManager.INSTALL_REASON_POLICY);
+
+        // The second user has one app installed. This app uses run-time permissions. It has been
+        // granted both permissions by the admin. It should be counted.
+        when(mPackageManager.getInstalledApplicationsAsUser(PackageManager.GET_DISABLED_COMPONENTS
+                | PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS,
+                MANAGED_PROFILE_ID)).thenReturn(Arrays.asList(
+                        buildInfo(APP_6_UID, APP_6, 0 /* flags */, Build.VERSION_CODES.M)));
+
+        // Grant run-time permissions as appropriate.
+        when(mDevicePolicyManager.getPermissionGrantState(eq(null), eq(APP_6), anyObject()))
+                .thenReturn(DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED);
+
+        // Grant install-time permissions as appropriate.
+        when(mPackageManagerService.checkUidPermission(anyObject(), eq(APP_6_UID)))
+                .thenReturn(PackageManager.PERMISSION_DENIED);
+
+        // app6 was not installed by enterprise policy.
+        final UserHandle managedProfileUser = new UserHandle(MANAGED_PROFILE_ID);
+        when(mPackageManager.getInstallReason(APP_6, managedProfileUser))
+                .thenReturn(PackageManager.INSTALL_REASON_UNKNOWN);
+
+        // Count the number of all apps installed that were granted on or more permissions by the
+        // admin.  Wait for the background task to finish.
+        (new AppWithAdminGrantedPermissionsCounterTestable(PERMISSIONS)).execute();
+        ShadowApplication.runBackgroundTasks();
+
+        assertThat(mAppCount).isEqualTo(3);
+
+        // Verify that installed packages were retrieved for the users returned by
+        // InstalledAppCounterTestable.getUsersToCount() only.
+        verify(mPackageManager).getInstalledApplicationsAsUser(anyInt(), eq(MAIN_USER_ID));
+        verify(mPackageManager).getInstalledApplicationsAsUser(anyInt(),
+                eq(MANAGED_PROFILE_ID));
+        verify(mPackageManager, atLeast(0)).getInstallReason(anyObject(), anyObject());
+        verifyNoMoreInteractions(mPackageManager);
+
+    }
+
+    private class AppWithAdminGrantedPermissionsCounterTestable extends
+            AppWithAdminGrantedPermissionsCounter {
+        public AppWithAdminGrantedPermissionsCounterTestable(String[] permissions) {
+            super(mContext, permissions, mPackageManager, mPackageManagerService,
+                    mDevicePolicyManager);
+        }
+
+        @Override
+        protected void onCountComplete(int num) {
+            mAppCount = num;
+        }
+
+        @Override
+        protected List<UserInfo> getUsersToCount() {
+            return mUsersToCount;
+        }
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/applications/ApplicationFeatureProviderImplTest.java b/tests/robotests/src/com/android/settings/applications/ApplicationFeatureProviderImplTest.java
index 615d9ad..aba4a12 100644
--- a/tests/robotests/src/com/android/settings/applications/ApplicationFeatureProviderImplTest.java
+++ b/tests/robotests/src/com/android/settings/applications/ApplicationFeatureProviderImplTest.java
@@ -16,14 +16,18 @@
 
 package com.android.settings.applications;
 
+import android.app.admin.DevicePolicyManager;
 import android.content.Context;
+import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
+import android.os.Build;
 import android.os.UserHandle;
 import android.os.UserManager;
 
 import com.android.settings.SettingsRobolectricTestRunner;
 import com.android.settings.TestConfig;
+import com.android.settings.enterprise.DevicePolicyManagerWrapper;
 import com.android.settings.testutils.ApplicationTestUtils;
 import com.android.settings.testutils.shadow.ShadowUserManager;
 import org.junit.Before;
@@ -50,15 +54,25 @@
     private final int MAIN_USER_ID = 0;
     private final int MANAGED_PROFILE_ID = 10;
 
+    private final int PER_USER_UID_RANGE = 100000;
+    private final int APP_1_UID = MAIN_USER_ID * PER_USER_UID_RANGE + 1;
+    private final int APP_2_UID = MANAGED_PROFILE_ID * PER_USER_UID_RANGE + 1;
+
     private final String APP_1 = "app1";
     private final String APP_2 = "app2";
 
+    private final String PERMISSION = "some.permission";
+
     private @Mock UserManager mUserManager;
     private @Mock Context mContext;
     private @Mock PackageManagerWrapper mPackageManager;
+    @Mock private IPackageManager mPackageManagerService;
+    @Mock private DevicePolicyManagerWrapper mDevicePolicyManager;
 
     private ApplicationFeatureProvider mProvider;
 
+    private int mAppCount = -1;
+
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
@@ -66,14 +80,66 @@
         when(mContext.getApplicationContext()).thenReturn(mContext);
         when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
 
-        mProvider = new ApplicationFeatureProviderImpl(mContext, mPackageManager);
+        mProvider = new ApplicationFeatureProviderImpl(mContext, mPackageManager,
+                mPackageManagerService, mDevicePolicyManager);
     }
 
     @Test
     public void testCalculateNumberOfInstalledApps() {
-        final Integer[] numberOfInstalledApps = new Integer[1];
-        numberOfInstalledApps[0] = null;
+        setUpUsersAndInstalledApps();
 
+        when(mPackageManager.getInstallReason(APP_1, new UserHandle(MAIN_USER_ID)))
+                .thenReturn(PackageManager.INSTALL_REASON_UNKNOWN);
+        when(mPackageManager.getInstallReason(APP_2, new UserHandle(MANAGED_PROFILE_ID)))
+                .thenReturn(PackageManager.INSTALL_REASON_POLICY);
+
+        // Count all installed apps.
+        mAppCount = -1;
+        mProvider.calculateNumberOfInstalledApps(ApplicationFeatureProvider.IGNORE_INSTALL_REASON,
+                (num) -> {
+                    mAppCount = num;
+                });
+        ShadowApplication.runBackgroundTasks();
+        assertThat(mAppCount).isEqualTo(2);
+
+        // Count apps with specific install reason only.
+        mAppCount = -1;
+        mProvider.calculateNumberOfInstalledApps(PackageManager.INSTALL_REASON_POLICY,
+                (num) -> {
+                    mAppCount = num;
+                });
+        ShadowApplication.runBackgroundTasks();
+        assertThat(mAppCount).isEqualTo(1);
+    }
+
+    @Test
+    public void testCalculateNumberOfAppsWithAdminGrantedPermissions() throws Exception {
+        setUpUsersAndInstalledApps();
+
+        when(mDevicePolicyManager.getPermissionGrantState(null, APP_1, PERMISSION))
+                .thenReturn(DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED);
+        when(mDevicePolicyManager.getPermissionGrantState(null, APP_2, PERMISSION))
+                .thenReturn(DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED);
+        when(mPackageManagerService.checkUidPermission(PERMISSION, APP_1_UID))
+                .thenReturn(PackageManager.PERMISSION_DENIED);
+        when(mPackageManagerService.checkUidPermission(PERMISSION, APP_2_UID))
+                .thenReturn(PackageManager.PERMISSION_GRANTED);
+        when(mPackageManager.getInstallReason(APP_1, new UserHandle(MAIN_USER_ID)))
+                .thenReturn(PackageManager.INSTALL_REASON_UNKNOWN);
+        when(mPackageManager.getInstallReason(APP_2, new UserHandle(MANAGED_PROFILE_ID)))
+                .thenReturn(PackageManager.INSTALL_REASON_POLICY);
+
+        mAppCount = -1;
+        mProvider.calculateNumberOfAppsWithAdminGrantedPermissions(new String[] {PERMISSION},
+                (num) -> {
+                    mAppCount = num;
+                });
+        ShadowApplication.runBackgroundTasks();
+        assertThat(mAppCount).isEqualTo(2);
+
+    }
+
+    private void setUpUsersAndInstalledApps() {
         when(mUserManager.getUsers(true)).thenReturn(Arrays.asList(
                 new UserInfo(MAIN_USER_ID, "main", UserInfo.FLAG_ADMIN),
                 new UserInfo(MANAGED_PROFILE_ID, "managed profile", 0)));
@@ -82,32 +148,12 @@
                 | PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS
                 | PackageManager.MATCH_ANY_USER,
                 MAIN_USER_ID)).thenReturn(Arrays.asList(
-                        ApplicationTestUtils.buildInfo(MAIN_USER_ID, APP_1, 0 /* flags */)));
-        when(mPackageManager.getInstallReason(APP_1, new UserHandle(MAIN_USER_ID)))
-                .thenReturn(PackageManager.INSTALL_REASON_UNKNOWN);
-
+                        ApplicationTestUtils.buildInfo(APP_1_UID, APP_1, 0 /* flags */,
+                                Build.VERSION_CODES.M)));
         when(mPackageManager.getInstalledApplicationsAsUser(PackageManager.GET_DISABLED_COMPONENTS
                 | PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS,
                 MANAGED_PROFILE_ID)).thenReturn(Arrays.asList(
-                        ApplicationTestUtils.buildInfo(MANAGED_PROFILE_ID, APP_2, 0 /* flags */)));
-        when(mPackageManager.getInstallReason(APP_2, new UserHandle(MANAGED_PROFILE_ID)))
-                .thenReturn(PackageManager.INSTALL_REASON_POLICY);
-
-        // Count all installed apps.
-        mProvider.calculateNumberOfInstalledApps(ApplicationFeatureProvider.IGNORE_INSTALL_REASON,
-                (num) -> {
-                    numberOfInstalledApps[0] = num;
-                });
-        ShadowApplication.runBackgroundTasks();
-        assertThat(numberOfInstalledApps[0]).isEqualTo(2);
-
-        // Count apps with specific install reason only.
-        numberOfInstalledApps[0] = null;
-        mProvider.calculateNumberOfInstalledApps(PackageManager.INSTALL_REASON_POLICY,
-                (num) -> {
-                    numberOfInstalledApps[0] = num;
-                });
-        ShadowApplication.runBackgroundTasks();
-        assertThat(numberOfInstalledApps[0]).isEqualTo(1);
+                        ApplicationTestUtils.buildInfo(APP_2_UID, APP_2, 0 /* flags */,
+                                Build.VERSION_CODES.LOLLIPOP)));
     }
 }
diff --git a/tests/robotests/src/com/android/settings/applications/DrawOverlayDetailsTest.java b/tests/robotests/src/com/android/settings/applications/DrawOverlayDetailsTest.java
new file mode 100644
index 0000000..a5306a2
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/applications/DrawOverlayDetailsTest.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2017 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.applications;
+
+import android.content.Context;
+
+import com.android.internal.logging.nano.MetricsProto;
+import com.android.internal.telephony.SmsUsageMonitor;
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settings.testutils.FakeFeatureFactory;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowApplication;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.verify;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class DrawOverlayDetailsTest {
+
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private Context mContext;
+
+    private FakeFeatureFactory mFeatureFactory;
+    private DrawOverlayDetails mFragment;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        FakeFeatureFactory.setupForTest(mContext);
+        mFeatureFactory = (FakeFeatureFactory) FakeFeatureFactory.getFactory(mContext);
+        mFragment = new DrawOverlayDetails();
+        mFragment.onAttach(ShadowApplication.getInstance().getApplicationContext());
+    }
+
+    @Test
+    public void logSpecialPermissionChange() {
+        mFragment.logSpecialPermissionChange(true, "app");
+        verify(mFeatureFactory.metricsFeatureProvider).action(any(Context.class),
+                eq(MetricsProto.MetricsEvent.APP_SPECIAL_PERMISSION_APPDRAW_ALLOW), eq("app"));
+
+        mFragment.logSpecialPermissionChange(false, "app");
+        verify(mFeatureFactory.metricsFeatureProvider).action(any(Context.class),
+                eq(MetricsProto.MetricsEvent.APP_SPECIAL_PERMISSION_APPDRAW_DENY), eq("app"));
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/applications/InstalledAppCounterTest.java b/tests/robotests/src/com/android/settings/applications/InstalledAppCounterTest.java
index 3dc2e9b..8495603 100644
--- a/tests/robotests/src/com/android/settings/applications/InstalledAppCounterTest.java
+++ b/tests/robotests/src/com/android/settings/applications/InstalledAppCounterTest.java
@@ -71,6 +71,10 @@
     private final int MAIN_USER_ID = 0;
     private final int MANAGED_PROFILE_ID = 10;
 
+    private final int PER_USER_UID_RANGE = 100000;
+    private final int MAIN_USER_APP_UID = MAIN_USER_ID * PER_USER_UID_RANGE;
+    private final int MANAGED_PROFILE_APP_UID = MANAGED_PROFILE_ID * PER_USER_UID_RANGE;
+
     @Mock private UserManager mUserManager;
     @Mock private Context mContext;
     @Mock private PackageManagerWrapper mPackageManager;
@@ -109,10 +113,14 @@
                 | PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS
                 | PackageManager.MATCH_ANY_USER,
                 MAIN_USER_ID)).thenReturn(Arrays.asList(
-                        buildInfo(MAIN_USER_ID, APP_1, ApplicationInfo.FLAG_UPDATED_SYSTEM_APP),
-                        buildInfo(MAIN_USER_ID, APP_2, 0 /* flags */),
-                        buildInfo(MAIN_USER_ID, APP_3, ApplicationInfo.FLAG_SYSTEM),
-                        buildInfo(MAIN_USER_ID, APP_4, ApplicationInfo.FLAG_SYSTEM)));
+                        buildInfo(MAIN_USER_APP_UID, APP_1,
+                                ApplicationInfo.FLAG_UPDATED_SYSTEM_APP, 0 /* targetSdkVersion */),
+                        buildInfo(MAIN_USER_APP_UID, APP_2, 0 /* flags */,
+                                0 /* targetSdkVersion */),
+                        buildInfo(MAIN_USER_APP_UID, APP_3, ApplicationInfo.FLAG_SYSTEM,
+                                0 /* targetSdkVersion */),
+                        buildInfo(MAIN_USER_APP_UID, APP_4, ApplicationInfo.FLAG_SYSTEM,
+                                0 /* targetSdkVersion */)));
         // For system apps, InstalledAppCounter checks whether they handle the default launcher
         // intent to decide whether to include them in the count of installed apps or not.
         expectQueryIntentActivities(MAIN_USER_ID, APP_3, true /* launchable */);
@@ -129,14 +137,16 @@
         when(mPackageManager.getInstallReason(APP_4, mainUser))
                 .thenReturn(PackageManager.INSTALL_REASON_POLICY);
 
-        // The second user has four apps installed:
+        // The second user has two apps installed:
         // * app5 is a user-installed app. It should be counted.
         // * app6 is a system app that provides a launcher icon. It should be counted.
         when(mPackageManager.getInstalledApplicationsAsUser(PackageManager.GET_DISABLED_COMPONENTS
                 | PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS,
                 MANAGED_PROFILE_ID)).thenReturn(Arrays.asList(
-                        buildInfo(MANAGED_PROFILE_ID, APP_5, 0 /* flags */),
-                        buildInfo(MANAGED_PROFILE_ID, APP_6, ApplicationInfo.FLAG_SYSTEM)));
+                        buildInfo(MANAGED_PROFILE_APP_UID, APP_5, 0 /* flags */,
+                                0 /* targetSdkVersion */),
+                        buildInfo(MANAGED_PROFILE_APP_UID, APP_6, ApplicationInfo.FLAG_SYSTEM,
+                                0 /* targetSdkVersion */)));
         expectQueryIntentActivities(MANAGED_PROFILE_ID, APP_6, true /* launchable */);
 
         // app5 is installed by enterprise policy.
@@ -165,7 +175,7 @@
 
         // Count once more, considering apps installed by enterprise policy only. Wait for the
         // background task to finish.
-        mInstalledAppCount = 0;
+        mInstalledAppCount = -1;
         (new InstalledAppCounterTestable(PackageManager.INSTALL_REASON_POLICY)).execute();
         ShadowApplication.runBackgroundTasks();
 
diff --git a/tests/robotests/src/com/android/settings/applications/PremiumSmsAccessTest.java b/tests/robotests/src/com/android/settings/applications/PremiumSmsAccessTest.java
new file mode 100644
index 0000000..d9c88ff
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/applications/PremiumSmsAccessTest.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2017 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.applications;
+
+import android.content.Context;
+
+import com.android.internal.logging.nano.MetricsProto;
+import com.android.internal.telephony.SmsUsageMonitor;
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settings.testutils.FakeFeatureFactory;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowApplication;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.verify;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class PremiumSmsAccessTest {
+
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private Context mContext;
+
+    private FakeFeatureFactory mFeatureFactory;
+    private PremiumSmsAccess mFragment;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        FakeFeatureFactory.setupForTest(mContext);
+        mFeatureFactory = (FakeFeatureFactory) FakeFeatureFactory.getFactory(mContext);
+        mFragment = new PremiumSmsAccess();
+        mFragment.onAttach(ShadowApplication.getInstance().getApplicationContext());
+    }
+
+    @Test
+    public void logSpecialPermissionChange() {
+        mFragment.logSpecialPermissionChange(SmsUsageMonitor.PREMIUM_SMS_PERMISSION_ASK_USER,
+                "app");
+        verify(mFeatureFactory.metricsFeatureProvider).action(any(Context.class),
+                eq(MetricsProto.MetricsEvent.APP_SPECIAL_PERMISSION_PREMIUM_SMS_ASK), eq("app"));
+
+        mFragment.logSpecialPermissionChange(SmsUsageMonitor.PREMIUM_SMS_PERMISSION_NEVER_ALLOW,
+                "app");
+        verify(mFeatureFactory.metricsFeatureProvider).action(any(Context.class),
+                eq(MetricsProto.MetricsEvent.APP_SPECIAL_PERMISSION_PREMIUM_SMS_DENY), eq("app"));
+
+        mFragment.logSpecialPermissionChange(SmsUsageMonitor.PREMIUM_SMS_PERMISSION_ALWAYS_ALLOW,
+                "app");
+        verify(mFeatureFactory.metricsFeatureProvider).action(any(Context.class),
+                eq(MetricsProto.MetricsEvent.APP_SPECIAL_PERMISSION_PREMIUM_SMS_ALWAYS_ALLOW),
+                eq("app"));
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/applications/UsageAccessDetailsTest.java b/tests/robotests/src/com/android/settings/applications/UsageAccessDetailsTest.java
new file mode 100644
index 0000000..532a923
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/applications/UsageAccessDetailsTest.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2017 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.applications;
+
+import android.content.Context;
+
+import com.android.internal.logging.nano.MetricsProto;
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settings.testutils.FakeFeatureFactory;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowApplication;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.verify;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class UsageAccessDetailsTest {
+
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private Context mContext;
+
+    private FakeFeatureFactory mFeatureFactory;
+    private UsageAccessDetails mFragment;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        FakeFeatureFactory.setupForTest(mContext);
+        mFeatureFactory = (FakeFeatureFactory) FakeFeatureFactory.getFactory(mContext);
+        mFragment = new UsageAccessDetails();
+        mFragment.onAttach(ShadowApplication.getInstance().getApplicationContext());
+    }
+
+    @Test
+    public void logSpecialPermissionChange() {
+        mFragment.logSpecialPermissionChange(true, "app");
+        verify(mFeatureFactory.metricsFeatureProvider).action(any(Context.class),
+                eq(MetricsProto.MetricsEvent.APP_SPECIAL_PERMISSION_USAGE_VIEW_ALLOW), eq("app"));
+
+        mFragment.logSpecialPermissionChange(false, "app");
+        verify(mFeatureFactory.metricsFeatureProvider).action(any(Context.class),
+                eq(MetricsProto.MetricsEvent.APP_SPECIAL_PERMISSION_USAGE_VIEW_DENY), eq("app"));
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/applications/VrListenerSettingsTest.java b/tests/robotests/src/com/android/settings/applications/VrListenerSettingsTest.java
new file mode 100644
index 0000000..3abe3f4
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/applications/VrListenerSettingsTest.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2017 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.applications;
+
+import android.content.Context;
+
+import com.android.internal.logging.nano.MetricsProto;
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settings.testutils.FakeFeatureFactory;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.verify;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class VrListenerSettingsTest {
+
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private Context mContext;
+
+    private FakeFeatureFactory mFeatureFactory;
+    private VrListenerSettings mFragment;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        FakeFeatureFactory.setupForTest(mContext);
+        mFeatureFactory = (FakeFeatureFactory) FakeFeatureFactory.getFactory(mContext);
+        mFragment = new VrListenerSettings();
+    }
+
+    @Test
+    public void logSpecialPermissionChange() {
+        mFragment.logSpecialPermissionChange(true, "app");
+        verify(mFeatureFactory.metricsFeatureProvider).action(any(Context.class),
+                eq(MetricsProto.MetricsEvent.APP_SPECIAL_PERMISSION_VRHELPER_ALLOW), eq("app"));
+
+        mFragment.logSpecialPermissionChange(false, "app");
+        verify(mFeatureFactory.metricsFeatureProvider).action(any(Context.class),
+                eq(MetricsProto.MetricsEvent.APP_SPECIAL_PERMISSION_VRHELPER_DENY), eq("app"));
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/applications/WriteSettingsDetailsTest.java b/tests/robotests/src/com/android/settings/applications/WriteSettingsDetailsTest.java
new file mode 100644
index 0000000..a632118
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/applications/WriteSettingsDetailsTest.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2017 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.applications;
+
+import android.content.Context;
+
+import com.android.internal.logging.nano.MetricsProto;
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settings.testutils.FakeFeatureFactory;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.verify;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class WriteSettingsDetailsTest {
+
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private Context mContext;
+
+    private FakeFeatureFactory mFeatureFactory;
+    private WriteSettingsDetails mFragment;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        FakeFeatureFactory.setupForTest(mContext);
+        mFeatureFactory = (FakeFeatureFactory) FakeFeatureFactory.getFactory(mContext);
+        mFragment = new WriteSettingsDetails();
+    }
+
+    @Test
+    public void logSpecialPermissionChange() {
+        mFragment.logSpecialPermissionChange(true, "app");
+        verify(mFeatureFactory.metricsFeatureProvider).action(any(Context.class),
+                eq(MetricsProto.MetricsEvent.APP_SPECIAL_PERMISSION_SETTINGS_CHANGE_ALLOW),
+                eq("app"));
+
+        mFragment.logSpecialPermissionChange(false, "app");
+        verify(mFeatureFactory.metricsFeatureProvider).action(any(Context.class),
+                eq(MetricsProto.MetricsEvent.APP_SPECIAL_PERMISSION_SETTINGS_CHANGE_DENY),
+                eq("app"));
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/dashboard/DashboardFragmentTest.java b/tests/robotests/src/com/android/settings/dashboard/DashboardFragmentTest.java
index f477202..d479e0a 100644
--- a/tests/robotests/src/com/android/settings/dashboard/DashboardFragmentTest.java
+++ b/tests/robotests/src/com/android/settings/dashboard/DashboardFragmentTest.java
@@ -79,6 +79,7 @@
         when(mFakeFeatureFactory.dashboardFeatureProvider.getTilesForCategory(anyString()))
                 .thenReturn(mDashboardCategory);
         mTestFragment.onAttach(ShadowApplication.getInstance().getApplicationContext());
+        when(mContext.getPackageName()).thenReturn("TestPackage");
     }
 
     @Test
diff --git a/tests/robotests/src/com/android/settings/datausage/UnrestrictedDataAccessTest.java b/tests/robotests/src/com/android/settings/datausage/UnrestrictedDataAccessTest.java
index c7b8dee..d674c77 100644
--- a/tests/robotests/src/com/android/settings/datausage/UnrestrictedDataAccessTest.java
+++ b/tests/robotests/src/com/android/settings/datausage/UnrestrictedDataAccessTest.java
@@ -15,31 +15,44 @@
  */
 package com.android.settings.datausage;
 
+import com.android.internal.logging.nano.MetricsProto;
+import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.os.Process;
 import com.android.settings.SettingsRobolectricTestRunner;
 import com.android.settings.TestConfig;
+import com.android.settings.testutils.FakeFeatureFactory;
 import com.android.settingslib.applications.ApplicationsState;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Answers;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 import org.robolectric.annotation.Config;
 
 import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.verify;
 
 @RunWith(SettingsRobolectricTestRunner.class)
 @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
 public class UnrestrictedDataAccessTest {
 
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private Context mContext;
+
     @Mock
     private ApplicationsState.AppEntry mAppEntry;
     private UnrestrictedDataAccess mFragment;
+    private FakeFeatureFactory mFeatureFactory;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
+        FakeFeatureFactory.setupForTest(mContext);
+        mFeatureFactory = (FakeFeatureFactory) FakeFeatureFactory.getFactory(mContext);
         mFragment = new UnrestrictedDataAccess();
     }
 
@@ -59,4 +72,15 @@
         assertThat(mFragment.shouldAddPreference(mAppEntry)).isFalse();
     }
 
+    @Test
+    public void logSpecialPermissionChange() {
+        mFragment.logSpecialPermissionChange(true, "app");
+        verify(mFeatureFactory.metricsFeatureProvider).action(any(Context.class),
+                eq(MetricsProto.MetricsEvent.APP_SPECIAL_PERMISSION_UNL_DATA_ALLOW), eq("app"));
+
+        mFragment.logSpecialPermissionChange(false, "app");
+        verify(mFeatureFactory.metricsFeatureProvider).action(any(Context.class),
+                eq(MetricsProto.MetricsEvent.APP_SPECIAL_PERMISSION_UNL_DATA_DENY), eq("app"));
+    }
+
 }
diff --git a/tests/robotests/src/com/android/settings/enterprise/AdminGrantedCameraPermissionPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/enterprise/AdminGrantedCameraPermissionPreferenceControllerTest.java
new file mode 100644
index 0000000..de24885
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/enterprise/AdminGrantedCameraPermissionPreferenceControllerTest.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2017 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.enterprise;
+
+import android.Manifest;
+
+import com.android.settings.R;
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+/**
+ * Tests for {@link AdminGrantedCameraPermissionPreferenceController}.
+ */
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public final class AdminGrantedCameraPermissionPreferenceControllerTest extends
+        AdminGrantedPermissionsPreferenceControllerTestBase {
+
+    public AdminGrantedCameraPermissionPreferenceControllerTest() {
+        super("enterprise_privacy_number_camera_access_packages",
+                new String[] {Manifest.permission.CAMERA},
+                R.plurals.enterprise_privacy_number_camera_access_packages);
+    }
+
+    @Override
+    public void setUp() {
+        super.setUp();
+        mController = new AdminGrantedCameraPermissionPreferenceController(mContext);
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/enterprise/AdminGrantedLocationPermissionsPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/enterprise/AdminGrantedLocationPermissionsPreferenceControllerTest.java
new file mode 100644
index 0000000..1c6f91d
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/enterprise/AdminGrantedLocationPermissionsPreferenceControllerTest.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2017 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.enterprise;
+
+import android.Manifest;
+
+import com.android.settings.R;
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+/**
+ * Tests for {@link AdminGrantedLocationPermissionsPreferenceController}.
+ */
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public final class AdminGrantedLocationPermissionsPreferenceControllerTest extends
+        AdminGrantedPermissionsPreferenceControllerTestBase {
+
+    public AdminGrantedLocationPermissionsPreferenceControllerTest() {
+        super("enterprise_privacy_number_location_access_packages",
+                new String[] {Manifest.permission.ACCESS_COARSE_LOCATION,
+                        Manifest.permission.ACCESS_FINE_LOCATION},
+                R.plurals.enterprise_privacy_number_location_access_packages);
+    }
+
+    @Override
+    public void setUp() {
+        super.setUp();
+        mController = new AdminGrantedLocationPermissionsPreferenceController(mContext);
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/enterprise/AdminGrantedMicrophonePermissionPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/enterprise/AdminGrantedMicrophonePermissionPreferenceControllerTest.java
new file mode 100644
index 0000000..bcaf63f
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/enterprise/AdminGrantedMicrophonePermissionPreferenceControllerTest.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2017 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.enterprise;
+
+import android.Manifest;
+
+import com.android.settings.R;
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+/**
+ * Tests for {@link AdminGrantedMicrophonePermissionPreferenceController}.
+ */
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public final class AdminGrantedMicrophonePermissionPreferenceControllerTest extends
+        AdminGrantedPermissionsPreferenceControllerTestBase {
+
+    public AdminGrantedMicrophonePermissionPreferenceControllerTest() {
+        super("enterprise_privacy_number_microphone_access_packages",
+                new String[] {Manifest.permission.RECORD_AUDIO},
+                R.plurals.enterprise_privacy_number_microphone_access_packages);
+    }
+
+    @Override
+    public void setUp() {
+        super.setUp();
+        mController = new AdminGrantedMicrophonePermissionPreferenceController(mContext);
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/enterprise/AdminGrantedPermissionsPreferenceControllerBaseTest.java b/tests/robotests/src/com/android/settings/enterprise/AdminGrantedPermissionsPreferenceControllerBaseTest.java
new file mode 100644
index 0000000..2bebbf0
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/enterprise/AdminGrantedPermissionsPreferenceControllerBaseTest.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2017 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.enterprise;
+
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+/**
+ * Tests for {@link AdminGrantedPermissionsPreferenceControllerBase}.
+ */
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public final class AdminGrantedPermissionsPreferenceControllerBaseTest extends
+        AdminGrantedPermissionsPreferenceControllerTestBase {
+
+    public AdminGrantedPermissionsPreferenceControllerBaseTest() {
+        super(null, new String[] {"some.permission"}, 123 /* resourceStringId */);
+    }
+
+    @Override
+    public void setUp() {
+        super.setUp();
+        mController = new AdminGrantedPermissionsPreferenceControllerBaseTestable();
+    }
+
+    private class AdminGrantedPermissionsPreferenceControllerBaseTestable extends
+            AdminGrantedPermissionsPreferenceControllerBase {
+
+        AdminGrantedPermissionsPreferenceControllerBaseTestable() {
+            super(AdminGrantedPermissionsPreferenceControllerBaseTest.this.mContext, mPermissions,
+                    mStringResourceId);
+        }
+
+        @Override
+        public String getPreferenceKey() {
+            return null;
+        }
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/enterprise/AdminGrantedPermissionsPreferenceControllerTestBase.java b/tests/robotests/src/com/android/settings/enterprise/AdminGrantedPermissionsPreferenceControllerTestBase.java
new file mode 100644
index 0000000..610692d
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/enterprise/AdminGrantedPermissionsPreferenceControllerTestBase.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2017 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.enterprise;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.support.v7.preference.Preference;
+
+import com.android.settings.applications.ApplicationFeatureProvider;
+import com.android.settings.testutils.FakeFeatureFactory;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.anyObject;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.when;
+
+/**
+ * Common base for testing subclasses of {@link AdminGrantedPermissionsPreferenceControllerBase}.
+ */
+public abstract class AdminGrantedPermissionsPreferenceControllerTestBase {
+
+    protected final String mKey;
+    protected final String[] mPermissions;
+    protected final int mStringResourceId;
+
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    protected Context mContext;
+    private FakeFeatureFactory mFeatureFactory;
+
+    protected AdminGrantedPermissionsPreferenceControllerBase mController;
+
+    public AdminGrantedPermissionsPreferenceControllerTestBase(String key, String[] permissions,
+            int stringResourceId) {
+        mKey = key;
+        mPermissions = permissions;
+        mStringResourceId = stringResourceId;
+    }
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        FakeFeatureFactory.setupForTest(mContext);
+        mFeatureFactory = (FakeFeatureFactory) FakeFeatureFactory.getFactory(mContext);
+    }
+
+    private void setNumberOfPackagesWithAdminGrantedPermissions(int number) {
+        doAnswer(new Answer() {
+            public Object answer(InvocationOnMock invocation) {
+                ((ApplicationFeatureProvider.NumberOfAppsCallback)
+                        invocation.getArguments()[1]).onNumberOfAppsResult(number);
+                return null;
+            }}).when(mFeatureFactory.applicationFeatureProvider)
+                    .calculateNumberOfAppsWithAdminGrantedPermissions(eq(mPermissions),
+                            anyObject());
+    }
+
+    @Test
+    public void testUpdateState() {
+        final Preference preference = new Preference(mContext, null, 0, 0);
+        preference.setVisible(true);
+
+        setNumberOfPackagesWithAdminGrantedPermissions(20);
+        when(mContext.getResources().getQuantityString(mStringResourceId, 20, 20))
+                .thenReturn("20 packages");
+        mController.updateState(preference);
+        assertThat(preference.getTitle()).isEqualTo("20 packages");
+        assertThat(preference.isVisible()).isTrue();
+
+        setNumberOfPackagesWithAdminGrantedPermissions(0);
+        mController.updateState(preference);
+        assertThat(preference.isVisible()).isFalse();
+    }
+
+    @Test
+    public void testIsAvailable() {
+        assertThat(mController.isAvailable()).isTrue();
+    }
+
+    @Test
+    public void testHandlePreferenceTreeClick() {
+        assertThat(mController.handlePreferenceTreeClick(new Preference(mContext, null, 0, 0)))
+                .isFalse();
+    }
+
+    @Test
+    public void testGetPreferenceKey() {
+        assertThat(mController.getPreferenceKey()).isEqualTo(mKey);
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/enterprise/EnterpriseInstalledPackagesPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/enterprise/EnterpriseInstalledPackagesPreferenceControllerTest.java
index 9a46e14..3dd1fd7 100644
--- a/tests/robotests/src/com/android/settings/enterprise/EnterpriseInstalledPackagesPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/enterprise/EnterpriseInstalledPackagesPreferenceControllerTest.java
@@ -68,8 +68,8 @@
     private void setNumberOfEnterpriseInstalledPackages(int number) {
         doAnswer(new Answer() {
             public Object answer(InvocationOnMock invocation) {
-                ((ApplicationFeatureProvider.NumberOfInstalledAppsCallback)
-                        invocation.getArguments()[1]).onNumberOfInstalledAppsResult(number);
+                ((ApplicationFeatureProvider.NumberOfAppsCallback)
+                        invocation.getArguments()[1]).onNumberOfAppsResult(number);
                 return null;
             }}).when(mFeatureFactory.applicationFeatureProvider)
                     .calculateNumberOfInstalledApps(eq(PackageManager.INSTALL_REASON_POLICY),
diff --git a/tests/robotests/src/com/android/settings/enterprise/EnterprisePrivacySettingsTest.java b/tests/robotests/src/com/android/settings/enterprise/EnterprisePrivacySettingsTest.java
index 5e4e08f..de4d02e 100644
--- a/tests/robotests/src/com/android/settings/enterprise/EnterprisePrivacySettingsTest.java
+++ b/tests/robotests/src/com/android/settings/enterprise/EnterprisePrivacySettingsTest.java
@@ -73,17 +73,23 @@
         final List<PreferenceController> controllers = mSettings.getPreferenceControllers(
                 ShadowApplication.getInstance().getApplicationContext());
         assertThat(controllers).isNotNull();
-        assertThat(controllers.size()).isEqualTo(8);
+        assertThat(controllers.size()).isEqualTo(11);
         assertThat(controllers.get(0)).isInstanceOf(InstalledPackagesPreferenceController.class);
         assertThat(controllers.get(1)).isInstanceOf(NetworkLogsPreferenceController.class);
         assertThat(controllers.get(2)).isInstanceOf(BugReportsPreferenceController.class);
         assertThat(controllers.get(3)).isInstanceOf(SecurityLogsPreferenceController.class);
         assertThat(controllers.get(4)).isInstanceOf(
-                AlwaysOnVpnPrimaryUserPreferenceController.class);
-        assertThat(controllers.get(5)).isInstanceOf(
-                AlwaysOnVpnManagedProfilePreferenceController.class);
-        assertThat(controllers.get(6)).isInstanceOf(GlobalHttpProxyPreferenceController.class);
-        assertThat(controllers.get(7)).isInstanceOf(
                 EnterpriseInstalledPackagesPreferenceController.class);
+        assertThat(controllers.get(5)).isInstanceOf(
+                AdminGrantedLocationPermissionsPreferenceController.class);
+        assertThat(controllers.get(6)).isInstanceOf(
+                AdminGrantedMicrophonePermissionPreferenceController.class);
+        assertThat(controllers.get(7)).isInstanceOf(
+                AdminGrantedCameraPermissionPreferenceController.class);
+        assertThat(controllers.get(8)).isInstanceOf(
+                AlwaysOnVpnPrimaryUserPreferenceController.class);
+        assertThat(controllers.get(9)).isInstanceOf(
+                AlwaysOnVpnManagedProfilePreferenceController.class);
+        assertThat(controllers.get(10)).isInstanceOf(GlobalHttpProxyPreferenceController.class);
     }
 }
diff --git a/tests/robotests/src/com/android/settings/enterprise/InstalledPackagesPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/enterprise/InstalledPackagesPreferenceControllerTest.java
index 5a0da66..bf2c4ca 100644
--- a/tests/robotests/src/com/android/settings/enterprise/InstalledPackagesPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/enterprise/InstalledPackagesPreferenceControllerTest.java
@@ -69,8 +69,8 @@
         final Preference preference = new Preference(mContext, null, 0, 0);
         doAnswer(new Answer() {
             public Object answer(InvocationOnMock invocation) {
-                ((ApplicationFeatureProvider.NumberOfInstalledAppsCallback)
-                        invocation.getArguments()[1]).onNumberOfInstalledAppsResult(20);
+                ((ApplicationFeatureProvider.NumberOfAppsCallback)
+                        invocation.getArguments()[1]).onNumberOfAppsResult(20);
                 return null;
             }}).when(mFeatureFactory.applicationFeatureProvider)
                     .calculateNumberOfInstalledApps(
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/HighPowerDetailTest.java b/tests/robotests/src/com/android/settings/fuelgauge/HighPowerDetailTest.java
new file mode 100644
index 0000000..a60bc65
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/fuelgauge/HighPowerDetailTest.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2017 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.fuelgauge;
+
+import android.content.Context;
+
+import com.android.internal.logging.nano.MetricsProto;
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settings.testutils.FakeFeatureFactory;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.verify;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class HighPowerDetailTest {
+
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private Context mContext;
+
+    private FakeFeatureFactory mFeatureFactory;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        FakeFeatureFactory.setupForTest(mContext);
+        mFeatureFactory = (FakeFeatureFactory) FakeFeatureFactory.getFactory(mContext);
+    }
+
+    @Test
+    public void logSpecialPermissionChange() {
+        // Deny means app is whitelisted to opt out of power save restrictions
+        HighPowerDetail.logSpecialPermissionChange(true, "app", mContext);
+        verify(mFeatureFactory.metricsFeatureProvider).action(any(Context.class),
+                eq(MetricsProto.MetricsEvent.APP_SPECIAL_PERMISSION_BATTERY_DENY), eq("app"));
+
+        // Allow means app is NOT whitelisted to opt out of power save restrictions
+        HighPowerDetail.logSpecialPermissionChange(false, "app", mContext);
+        verify(mFeatureFactory.metricsFeatureProvider).action(any(Context.class),
+                eq(MetricsProto.MetricsEvent.APP_SPECIAL_PERMISSION_BATTERY_ALLOW), eq("app"));
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/notification/BootSoundPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/BootSoundPreferenceControllerTest.java
new file mode 100644
index 0000000..c547c63
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/notification/BootSoundPreferenceControllerTest.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2017 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.notification;
+
+import android.content.Context;
+import android.os.SystemProperties;
+import android.support.v14.preference.SwitchPreference;
+import android.support.v7.preference.PreferenceScreen;
+
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settings.testutils.shadow.SettingsShadowSystemProperties;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowApplication;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class BootSoundPreferenceControllerTest {
+
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private Context mContext;
+    @Mock
+    private PreferenceScreen mScreen;
+    @Mock
+    private SwitchPreference mPreference;
+
+    private BootSoundPreferenceController mController;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        SettingsShadowSystemProperties.clear();
+        when(mContext.getResources().getBoolean(com.android.settings.R.bool.has_boot_sounds))
+            .thenReturn(true);
+        mController = new BootSoundPreferenceController(mContext);
+        when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mPreference);
+        when(mPreference.getKey()).thenReturn(mController.getPreferenceKey());
+    }
+
+    @Test
+    public void isAvailable_hasBootSounds_shouldReturnTrue() {
+        when(mContext.getResources().getBoolean(
+            com.android.settings.R.bool.has_boot_sounds)).thenReturn(true);
+
+        assertThat(mController.isAvailable()).isTrue();
+    }
+
+    @Test
+    public void isAvailable_noBootSounds_shouldReturnFale() {
+        when(mContext.getResources().getBoolean(
+            com.android.settings.R.bool.has_boot_sounds)).thenReturn(false);
+
+        assertThat(mController.isAvailable()).isFalse();
+    }
+
+    @Config(shadows = {SettingsShadowSystemProperties.class})
+    @Test
+    public void displayPreference_bootSoundEnabled_shouldCheckedPreference() {
+        SettingsShadowSystemProperties.set(BootSoundPreferenceController.PROPERTY_BOOT_SOUNDS, "1");
+
+        mController.displayPreference(mScreen);
+
+        verify(mPreference).setChecked(true);
+    }
+
+    @Config(shadows = {SettingsShadowSystemProperties.class})
+    @Test
+    public void displayPreference_bootSoundDisabled_shouldUncheckedPreference() {
+        SettingsShadowSystemProperties.set(BootSoundPreferenceController.PROPERTY_BOOT_SOUNDS, "0");
+
+        mController.displayPreference(mScreen);
+
+        verify(mPreference).setChecked(false);
+    }
+
+    @Config(shadows = {SettingsShadowSystemProperties.class})
+    @Test
+    public void handlePreferenceTreeClick_preferenceChecked_shouldEnableBootSound() {
+        when(mPreference.isChecked()).thenReturn(true);
+
+        mController.handlePreferenceTreeClick(mPreference);
+
+        assertThat(SystemProperties.getBoolean(
+            BootSoundPreferenceController.PROPERTY_BOOT_SOUNDS, true)).isTrue();
+    }
+
+    @Config(shadows = {SettingsShadowSystemProperties.class})
+    @Test
+    public void handlePreferenceTreeClick_preferenceUnchecked_shouldDisableBootSound() {
+        when(mPreference.isChecked()).thenReturn(false);
+
+        mController.handlePreferenceTreeClick(mPreference);
+
+        assertThat(SystemProperties.getBoolean(
+            BootSoundPreferenceController.PROPERTY_BOOT_SOUNDS, true)).isFalse();
+    }
+
+}
diff --git a/tests/robotests/src/com/android/settings/notification/ChargingSoundPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/ChargingSoundPreferenceControllerTest.java
new file mode 100644
index 0000000..4cdf7c7
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/notification/ChargingSoundPreferenceControllerTest.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2017 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.notification;
+
+import android.app.Activity;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.support.v14.preference.SwitchPreference;
+import android.support.v7.preference.PreferenceScreen;
+import android.provider.Settings.Global;
+
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowApplication;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.when;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class ChargingSoundPreferenceControllerTest {
+
+    @Mock
+    private PreferenceScreen mScreen;
+    @Mock
+    private Activity mActivity;
+    @Mock
+    private ContentResolver mContentResolver;
+    @Mock
+    private SoundSettings mSetting;
+    @Mock
+    private Context mContext;
+
+    private ChargingSoundPreferenceController mController;
+    private SwitchPreference mPreference;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        when(mSetting.getActivity()).thenReturn(mActivity);
+        when(mActivity.getContentResolver()).thenReturn(mContentResolver);
+        mPreference = new SwitchPreference(ShadowApplication.getInstance().getApplicationContext());
+        mController = new ChargingSoundPreferenceController(mContext, mSetting, null);
+        when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mPreference);
+        doReturn(mScreen).when(mSetting).getPreferenceScreen();
+    }
+
+    @Test
+    public void isAvailable_isAlwaysTrue() {
+        assertThat(mController.isAvailable()).isTrue();
+    }
+
+    @Test
+    public void displayPreference_chargingSoundEnabled_shouldCheckedPreference() {
+        Global.putInt(mContentResolver, Global.CHARGING_SOUNDS_ENABLED, 1);
+
+        mController.displayPreference(mScreen);
+
+        assertThat(mPreference.isChecked()).isTrue();
+    }
+
+    @Test
+    public void displayPreference_chargingSoundDisabled_shouldUncheckedPreference() {
+        Global.putInt(mContentResolver, Global.CHARGING_SOUNDS_ENABLED, 0);
+
+        mController.displayPreference(mScreen);
+
+        assertThat(mPreference.isChecked()).isFalse();
+    }
+
+    @Test
+    public void onPreferenceChanged_preferenceChecked_shouldEnabledChargingSound() {
+        mController.displayPreference(mScreen);
+
+        mPreference.getOnPreferenceChangeListener().onPreferenceChange(mPreference, true);
+
+        assertThat(Global.getInt(mContentResolver, Global.CHARGING_SOUNDS_ENABLED, 1))
+            .isEqualTo(1);
+    }
+
+    @Test
+    public void onPreferenceChanged_preferenceUnchecked_shouldDisabledChargingSound() {
+        mController.displayPreference(mScreen);
+
+        mPreference.getOnPreferenceChangeListener().onPreferenceChange(mPreference, false);
+
+        assertThat(Global.getInt(mContentResolver, Global.CHARGING_SOUNDS_ENABLED, 1))
+            .isEqualTo(0);
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/notification/DialPadTonePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/DialPadTonePreferenceControllerTest.java
new file mode 100644
index 0000000..e9410e6
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/notification/DialPadTonePreferenceControllerTest.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2017 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.notification;
+
+import android.app.Activity;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.support.v14.preference.SwitchPreference;
+import android.support.v7.preference.PreferenceScreen;
+import android.provider.Settings.System;
+import android.telephony.TelephonyManager;
+
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowApplication;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.when;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class DialPadTonePreferenceControllerTest {
+
+    @Mock
+    private TelephonyManager mTelephonyManager;
+    @Mock
+    private PreferenceScreen mScreen;
+    @Mock
+    private Activity mActivity;
+    @Mock
+    private ContentResolver mContentResolver;
+    @Mock
+    private SoundSettings mSetting;
+    @Mock
+    private Context mContext;
+
+    private DialPadTonePreferenceController mController;
+    private SwitchPreference mPreference;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        when(mContext.getSystemService(Context.TELEPHONY_SERVICE)).thenReturn(mTelephonyManager);
+        when(mTelephonyManager.isVoiceCapable()).thenReturn(true);
+        when(mSetting.getActivity()).thenReturn(mActivity);
+        when(mActivity.getSystemService(Context.TELEPHONY_SERVICE)).thenReturn(mTelephonyManager);
+        when(mActivity.getContentResolver()).thenReturn(mContentResolver);
+        mPreference = new SwitchPreference(ShadowApplication.getInstance().getApplicationContext());
+        mController = new DialPadTonePreferenceController(mContext, mSetting, null);
+        when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mPreference);
+        doReturn(mScreen).when(mSetting).getPreferenceScreen();
+    }
+
+    @Test
+    public void isAvailable_voiceCapable_shouldReturnTrue() {
+        assertThat(mController.isAvailable()).isTrue();
+    }
+
+    @Test
+    public void isAvailable_notVoiceCapable_shouldReturnFalse() {
+        when(mTelephonyManager.isVoiceCapable()).thenReturn(false);
+
+        assertThat(mController.isAvailable()).isFalse();
+    }
+
+    @Test
+    public void displayPreference_dialToneEnabled_shouldCheckedPreference() {
+        System.putInt(mContentResolver, System.DTMF_TONE_WHEN_DIALING, 1);
+
+        mController.displayPreference(mScreen);
+
+        assertThat(mPreference.isChecked()).isTrue();
+    }
+
+    @Test
+    public void displayPreference_dialToneDisabled_shouldUncheckedPreference() {
+        System.putInt(mContentResolver, System.DTMF_TONE_WHEN_DIALING, 0);
+
+        mController.displayPreference(mScreen);
+
+        assertThat(mPreference.isChecked()).isFalse();
+    }
+
+    @Test
+    public void onPreferenceChanged_preferenceChecked_shouldEnabledDialTone() {
+        mController.displayPreference(mScreen);
+
+        mPreference.getOnPreferenceChangeListener().onPreferenceChange(mPreference, true);
+
+        assertThat(System.getInt(mContentResolver, System.DTMF_TONE_WHEN_DIALING, 1)).isEqualTo(1);
+    }
+
+    @Test
+    public void onPreferenceChanged_preferenceUnchecked_shouldDisabledDialTone() {
+        mController.displayPreference(mScreen);
+
+        mPreference.getOnPreferenceChangeListener().onPreferenceChange(mPreference, false);
+
+        assertThat(System.getInt(mContentResolver, System.DTMF_TONE_WHEN_DIALING, 1)).isEqualTo(0);
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/notification/DockAudioMediaPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/DockAudioMediaPreferenceControllerTest.java
new file mode 100644
index 0000000..bc6eb3a
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/notification/DockAudioMediaPreferenceControllerTest.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2017 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.notification;
+
+import android.app.Activity;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.support.v7.preference.DropDownPreference;
+import android.support.v7.preference.PreferenceScreen;
+import android.provider.Settings.Global;
+
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowApplication;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Answers.RETURNS_DEEP_STUBS;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.when;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class DockAudioMediaPreferenceControllerTest {
+
+    @Mock
+    private PreferenceScreen mScreen;
+    @Mock(answer = RETURNS_DEEP_STUBS)
+    private Activity mActivity;
+    @Mock
+    private ContentResolver mContentResolver;
+    @Mock
+    private SoundSettings mSetting;
+    @Mock(answer = RETURNS_DEEP_STUBS)
+    private Context mContext;
+
+    private DockAudioMediaPreferenceController mController;
+    private DropDownPreference mPreference;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        final Context appContext = ShadowApplication.getInstance().getApplicationContext();
+        when(mSetting.getActivity()).thenReturn(mActivity);
+        when(mActivity.getContentResolver()).thenReturn(mContentResolver);
+        when(mActivity.getResources().getBoolean(com.android.settings.R.bool.has_dock_settings))
+            .thenReturn(true);
+        when(mActivity.getResources().getString(anyInt())).thenReturn("test string");
+        mPreference = new DropDownPreference(appContext);
+        mController = new DockAudioMediaPreferenceController(mContext, mSetting, null);
+        when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mPreference);
+        doReturn(mScreen).when(mSetting).getPreferenceScreen();
+    }
+
+    @Test
+    public void isAvailable_hasDockSettings_shouldReturnTrue() {
+        when(mContext.getResources().getBoolean(com.android.settings.R.bool.has_dock_settings))
+            .thenReturn(true);
+
+        assertThat(mController.isAvailable()).isTrue();
+    }
+
+    @Test
+    public void isAvailable_noDockSettings_shouldReturnFalse() {
+        when(mContext.getResources().getBoolean(com.android.settings.R.bool.has_dock_settings))
+            .thenReturn(false);
+
+        assertThat(mController.isAvailable()).isFalse();
+    }
+
+    @Test
+    public void displayPreference_dockAudioDisabled_shouldSelectFirstItem() {
+        Global.putInt(mContentResolver, Global.DOCK_AUDIO_MEDIA_ENABLED, 0);
+
+        mController.displayPreference(mScreen);
+
+        assertThat(mPreference.getValue()).isEqualTo("0");
+    }
+
+    @Test
+    public void displayPreference_dockAudioEnabled_shouldSelectSecondItem() {
+        Global.putInt(mContentResolver, Global.DOCK_AUDIO_MEDIA_ENABLED, 1);
+
+        mController.displayPreference(mScreen);
+
+        assertThat(mPreference.getValue()).isEqualTo("1");
+    }
+
+    @Test
+    public void onPreferenceChanged_firstItemSelected_shouldDisableDockAudio() {
+        mController.displayPreference(mScreen);
+
+        mPreference.getOnPreferenceChangeListener().onPreferenceChange(mPreference, "0");
+
+        assertThat(Global.getInt(mContentResolver, Global.DOCK_AUDIO_MEDIA_ENABLED, 0))
+            .isEqualTo(0);
+    }
+
+    @Test
+    public void onPreferenceChanged_secondItemSelected_shouldEnableDockAudio() {
+        mController.displayPreference(mScreen);
+
+        mPreference.getOnPreferenceChangeListener().onPreferenceChange(mPreference, "1");
+
+        assertThat(Global.getInt(mContentResolver, Global.DOCK_AUDIO_MEDIA_ENABLED, 0))
+            .isEqualTo(1);
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/notification/DockingSoundPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/DockingSoundPreferenceControllerTest.java
new file mode 100644
index 0000000..3350fb9
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/notification/DockingSoundPreferenceControllerTest.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2017 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.notification;
+
+import android.app.Activity;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.support.v14.preference.SwitchPreference;
+import android.support.v7.preference.PreferenceScreen;
+import android.provider.Settings.Global;
+
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowApplication;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Answers.RETURNS_DEEP_STUBS;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.when;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class DockingSoundPreferenceControllerTest {
+
+    @Mock
+    private PreferenceScreen mScreen;
+    @Mock(answer = RETURNS_DEEP_STUBS)
+    private Activity mActivity;
+    @Mock
+    private ContentResolver mContentResolver;
+    @Mock
+    private SoundSettings mSetting;
+    @Mock(answer = RETURNS_DEEP_STUBS)
+    private Context mContext;
+
+    private DockingSoundPreferenceController mController;
+    private SwitchPreference mPreference;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        when(mSetting.getActivity()).thenReturn(mActivity);
+        when(mActivity.getContentResolver()).thenReturn(mContentResolver);
+        mPreference = new SwitchPreference(ShadowApplication.getInstance().getApplicationContext());
+        when(mActivity.getResources().getBoolean(com.android.settings.R.bool.has_dock_settings))
+            .thenReturn(true);
+        mController = new DockingSoundPreferenceController(mContext, mSetting, null);
+        when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mPreference);
+        doReturn(mScreen).when(mSetting).getPreferenceScreen();
+    }
+
+    @Test
+    public void isAvailable_hasDockSettings_shouldReturnTrue() {
+        when(mContext.getResources().getBoolean(com.android.settings.R.bool.has_dock_settings))
+            .thenReturn(true);
+
+        assertThat(mController.isAvailable()).isTrue();
+    }
+
+    @Test
+    public void isAvailable_noDockSettings_shouldReturnFalse() {
+        when(mContext.getResources().getBoolean(com.android.settings.R.bool.has_dock_settings))
+            .thenReturn(false);
+
+        assertThat(mController.isAvailable()).isFalse();
+    }
+
+    @Test
+    public void displayPreference_dockingSoundEnabled_shouldCheckedPreference() {
+        Global.putInt(mContentResolver, Global.DOCK_SOUNDS_ENABLED, 1);
+
+        mController.displayPreference(mScreen);
+
+        assertThat(mPreference.isChecked()).isTrue();
+    }
+
+    @Test
+    public void displayPreference_dockingSoundDisabled_shouldUncheckedPreference() {
+        Global.putInt(mContentResolver, Global.DOCK_SOUNDS_ENABLED, 0);
+
+        mController.displayPreference(mScreen);
+
+        assertThat(mPreference.isChecked()).isFalse();
+    }
+
+    @Test
+    public void onPreferenceChanged_preferenceChecked_shouldEnabledDockingSound() {
+        mController.displayPreference(mScreen);
+
+        mPreference.getOnPreferenceChangeListener().onPreferenceChange(mPreference, true);
+
+        assertThat(Global.getInt(mContentResolver, Global.DOCK_SOUNDS_ENABLED, 1)).isEqualTo(1);
+    }
+
+    @Test
+    public void onPreferenceChanged_preferenceUnchecked_shouldDisabledDockingSound() {
+        mController.displayPreference(mScreen);
+
+        mPreference.getOnPreferenceChangeListener().onPreferenceChange(mPreference, false);
+
+        assertThat(Global.getInt(mContentResolver, Global.DOCK_SOUNDS_ENABLED, 1)).isEqualTo(0);
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/notification/EmergencyTonePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/EmergencyTonePreferenceControllerTest.java
new file mode 100644
index 0000000..0124566
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/notification/EmergencyTonePreferenceControllerTest.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2017 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.notification;
+
+import android.app.Activity;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.support.v7.preference.DropDownPreference;
+import android.support.v7.preference.PreferenceScreen;
+import android.provider.Settings.Global;
+import android.telephony.TelephonyManager;
+
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowApplication;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.when;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class EmergencyTonePreferenceControllerTest {
+
+    @Mock
+    private TelephonyManager mTelephonyManager;
+    @Mock
+    private PreferenceScreen mScreen;
+    @Mock
+    private Activity mActivity;
+    @Mock
+    private ContentResolver mContentResolver;
+    @Mock
+    private SoundSettings mSetting;
+    @Mock
+    private Context mContext;
+
+    private EmergencyTonePreferenceController mController;
+    private DropDownPreference mPreference;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        final Context appContext = ShadowApplication.getInstance().getApplicationContext();
+        when(mContext.getSystemService(Context.TELEPHONY_SERVICE)).thenReturn(mTelephonyManager);
+        when(mTelephonyManager.getCurrentPhoneType()).thenReturn(TelephonyManager.PHONE_TYPE_CDMA);
+        when(mSetting.getActivity()).thenReturn(mActivity);
+        when(mActivity.getSystemService(Context.TELEPHONY_SERVICE)).thenReturn(mTelephonyManager);
+        when(mActivity.getContentResolver()).thenReturn(mContentResolver);
+        when(mActivity.getResources()).thenReturn(appContext.getResources());
+        mPreference = new DropDownPreference(appContext);
+        mController = new EmergencyTonePreferenceController(mContext, mSetting, null);
+        when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mPreference);
+        doReturn(mScreen).when(mSetting).getPreferenceScreen();
+    }
+
+    @Test
+    public void isAvailable_cdma_shouldReturnTrue() {
+        assertThat(mController.isAvailable()).isTrue();
+    }
+
+    @Test
+    public void isAvailable_notCdma_shouldReturnFalse() {
+        when(mTelephonyManager.getCurrentPhoneType()).thenReturn(TelephonyManager.PHONE_TYPE_GSM);
+
+        assertThat(mController.isAvailable()).isFalse();
+    }
+
+    @Test
+    public void displayPreference_emergencyToneOff_shouldSelectFirstItem() {
+        Global.putInt(mContentResolver, Global.EMERGENCY_TONE, 0);
+
+        mController.displayPreference(mScreen);
+
+        assertThat(mPreference.getValue()).isEqualTo("0");
+    }
+
+    @Test
+    public void displayPreference_emergencyToneAlert_shouldSelectSecondItem() {
+        Global.putInt(mContentResolver, Global.EMERGENCY_TONE, 1);
+
+        mController.displayPreference(mScreen);
+
+        assertThat(mPreference.getValue()).isEqualTo("1");
+    }
+
+    @Test
+    public void displayPreference_emergencyToneVibrate_shouldSelectThirdItem() {
+        Global.putInt(mContentResolver, Global.EMERGENCY_TONE, 2);
+
+        mController.displayPreference(mScreen);
+
+        assertThat(mPreference.getValue()).isEqualTo("2");
+    }
+
+    @Test
+    public void onPreferenceChanged_firstItemSelected_shouldSetEmergencyToneToOff() {
+        mController.displayPreference(mScreen);
+
+        mPreference.getOnPreferenceChangeListener().onPreferenceChange(mPreference, "0");
+
+        assertThat(Global.getInt(mContentResolver, Global.EMERGENCY_TONE, 0)).isEqualTo(0);
+    }
+
+    @Test
+    public void onPreferenceChanged_secondItemSelected_shouldSetEmergencyToneToAlert() {
+        mController.displayPreference(mScreen);
+
+        mPreference.getOnPreferenceChangeListener().onPreferenceChange(mPreference, "1");
+
+        assertThat(Global.getInt(mContentResolver, Global.EMERGENCY_TONE, 0)).isEqualTo(1);
+    }
+
+    @Test
+    public void onPreferenceChanged_thirdItemSelected_shouldSetEmergencyToneToVibrate() {
+        mController.displayPreference(mScreen);
+
+        mPreference.getOnPreferenceChangeListener().onPreferenceChange(mPreference, "2");
+
+        assertThat(Global.getInt(mContentResolver, Global.EMERGENCY_TONE, 0)).isEqualTo(2);
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/notification/NotificationAccessSettingsTest.java b/tests/robotests/src/com/android/settings/notification/NotificationAccessSettingsTest.java
new file mode 100644
index 0000000..b0aa856
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/notification/NotificationAccessSettingsTest.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2017 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.notification;
+
+import android.content.Context;
+
+import com.android.internal.logging.nano.MetricsProto;
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settings.testutils.FakeFeatureFactory;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.verify;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class NotificationAccessSettingsTest {
+
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private Context mContext;
+
+    private FakeFeatureFactory mFeatureFactory;
+    private NotificationAccessSettings mFragment;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        FakeFeatureFactory.setupForTest(mContext);
+        mFeatureFactory = (FakeFeatureFactory) FakeFeatureFactory.getFactory(mContext);
+        mFragment = new NotificationAccessSettings();
+    }
+
+    @Test
+    public void logSpecialPermissionChange() {
+        mFragment.logSpecialPermissionChange(true, "app");
+        verify(mFeatureFactory.metricsFeatureProvider).action(any(Context.class),
+                eq(MetricsProto.MetricsEvent.APP_SPECIAL_PERMISSION_NOTIVIEW_ALLOW),
+                eq("app"));
+
+        mFragment.logSpecialPermissionChange(false, "app");
+        verify(mFeatureFactory.metricsFeatureProvider).action(any(Context.class),
+                eq(MetricsProto.MetricsEvent.APP_SPECIAL_PERMISSION_NOTIVIEW_DENY),
+                eq("app"));
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/notification/ScreenLockSoundPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/ScreenLockSoundPreferenceControllerTest.java
new file mode 100644
index 0000000..8963a5d
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/notification/ScreenLockSoundPreferenceControllerTest.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2017 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.notification;
+
+import android.app.Activity;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.support.v14.preference.SwitchPreference;
+import android.support.v7.preference.PreferenceScreen;
+import android.provider.Settings.System;
+
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowApplication;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.when;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class ScreenLockSoundPreferenceControllerTest {
+
+    @Mock
+    private PreferenceScreen mScreen;
+    @Mock
+    private Activity mActivity;
+    @Mock
+    private ContentResolver mContentResolver;
+    @Mock
+    private SoundSettings mSetting;
+    @Mock
+    private Context mContext;
+
+    private ScreenLockSoundPreferenceController mController;
+    private SwitchPreference mPreference;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        when(mSetting.getActivity()).thenReturn(mActivity);
+        when(mActivity.getContentResolver()).thenReturn(mContentResolver);
+        mPreference = new SwitchPreference(ShadowApplication.getInstance().getApplicationContext());
+        mController = new ScreenLockSoundPreferenceController(mContext, mSetting, null);
+        when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mPreference);
+        doReturn(mScreen).when(mSetting).getPreferenceScreen();
+    }
+
+    @Test
+    public void isAvailable_isAlwaysTrue() {
+        assertThat(mController.isAvailable()).isTrue();
+    }
+
+    @Test
+    public void displayPreference_lockScreenSoundEnabled_shouldCheckedPreference() {
+        System.putInt(mContentResolver, System.LOCKSCREEN_SOUNDS_ENABLED, 1);
+
+        mController.displayPreference(mScreen);
+
+        assertThat(mPreference.isChecked()).isTrue();
+    }
+
+    @Test
+    public void displayPreference_lockScreenSoundDisabled_shouldUncheckedPreference() {
+        System.putInt(mContentResolver, System.LOCKSCREEN_SOUNDS_ENABLED, 0);
+
+        mController.displayPreference(mScreen);
+
+        assertThat(mPreference.isChecked()).isFalse();
+    }
+
+    @Test
+    public void onPreferenceChanged_preferenceChecked_shouldEnabledLockScreenSound() {
+        mController.displayPreference(mScreen);
+
+        mPreference.getOnPreferenceChangeListener().onPreferenceChange(mPreference, true);
+
+        assertThat(System.getInt(mContentResolver, System.LOCKSCREEN_SOUNDS_ENABLED, 1))
+            .isEqualTo(1);
+    }
+
+    @Test
+    public void onPreferenceChanged_preferenceUnchecked_shouldDisabledLockScreenSound() {
+        mController.displayPreference(mScreen);
+
+        mPreference.getOnPreferenceChangeListener().onPreferenceChange(mPreference, false);
+
+        assertThat(System.getInt(mContentResolver, System.LOCKSCREEN_SOUNDS_ENABLED, 1))
+            .isEqualTo(0);
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/notification/SettingPrefControllerTest.java b/tests/robotests/src/com/android/settings/notification/SettingPrefControllerTest.java
new file mode 100644
index 0000000..b36b19b
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/notification/SettingPrefControllerTest.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2017 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.notification;
+
+import android.app.Activity;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.provider.Settings.Global;
+import android.support.v7.preference.PreferenceScreen;
+
+import com.android.settings.SettingsPreferenceFragment;
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settings.core.lifecycle.Lifecycle;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowApplication;
+
+import static com.android.settings.notification.SettingPref.TYPE_GLOBAL;
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Answers.RETURNS_DEEP_STUBS;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class SettingPrefControllerTest {
+
+    @Mock
+    private PreferenceScreen mScreen;
+    @Mock
+    private OtherSoundSettings mSetting;
+    @Mock
+    private Activity mActivity;
+    @Mock
+    private ContentResolver mContentResolver;
+
+    private Context mContext;
+    private PreferenceControllerTestable mController;
+    private SettingPref mPreference;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mContext = spy(ShadowApplication.getInstance().getApplicationContext());
+        when(mContext.getContentResolver()).thenReturn(mContentResolver);
+        when(mSetting.getActivity()).thenReturn(mActivity);
+        doReturn(mScreen).when(mSetting).getPreferenceScreen();
+        mController = new PreferenceControllerTestable(mContext, mSetting, null);
+        mPreference = mController.getPref();
+    }
+
+    @Test
+    public void displayPreference_shouldInitPreference() {
+        mController.displayPreference(mScreen);
+
+        verify(mPreference).init(mSetting);
+    }
+
+    @Test
+    public void isAvailable_shouldCallisApplicable() {
+        mController.isAvailable();
+
+        verify(mPreference).isApplicable(mContext);
+    }
+
+    @Test
+    public void getPreferenceKey_shouldReturnPrefKey() {
+        assertThat(mController.getPreferenceKey()).isEqualTo(mController.KEY_TEST);
+    }
+
+    @Test
+    public void updateState_shouldUpdatePreference() {
+        mController.updateState(null);
+
+        verify(mPreference).update(mContext);
+    }
+
+    @Test
+    public void onResume_shouldRegisterContentObserver() {
+        mController.displayPreference(mScreen);
+        mController.onResume();
+
+        verify(mContentResolver).registerContentObserver(
+            Global.getUriFor("Setting1"), false, mController.getObserver());
+    }
+
+    @Test
+    public void onPause_shouldUnregisterContentObserver() {
+        mController.displayPreference(mScreen);
+        mController.onPause();
+
+        verify(mContentResolver).unregisterContentObserver(mController.getObserver());
+    }
+
+    @Test
+    public void onContentChange_shouldUpdatePreference() {
+        mController.displayPreference(mScreen);
+        mController.onResume();
+        mController.getObserver().onChange(false, Global.getUriFor("Setting1"));
+
+        verify(mPreference).update(mContext);
+    }
+
+    @Test
+    public void updateNonIndexableKeys_applicable_shouldNotUpdate() {
+        final List<String> keys = new ArrayList<>();
+
+        mController.updateNonIndexableKeys(keys);
+
+        assertThat(keys).isEmpty();
+    }
+
+    @Test
+    public void updateNonIndexableKeys_notApplicable_shouldUpdate() {
+        mController.setApplicable(false);
+        final List<String> keys = new ArrayList<>();
+
+        mController.updateNonIndexableKeys(keys);
+
+        assertThat(keys).isNotEmpty();
+    }
+
+    private class PreferenceControllerTestable extends SettingPrefController {
+
+        private static final String KEY_TEST = "key1";
+        private boolean mApplicable = true;
+
+        public PreferenceControllerTestable(Context context, SettingsPreferenceFragment parent,
+            Lifecycle lifecycle) {
+            super(context, parent, lifecycle);
+            mPreference = spy(new SettingPref(
+                TYPE_GLOBAL, KEY_TEST, "Setting1", 1) {
+                @Override
+                public boolean isApplicable(Context context) {
+                    return mApplicable;
+                }
+            });
+        }
+
+        SettingPref getPref() {
+            return mPreference;
+        }
+
+        PreferenceControllerTestable.SettingsObserver getObserver() {
+            return mSettingsObserver;
+        }
+
+        void setApplicable(boolean applicable) {
+            mApplicable = applicable;
+        }
+
+    }
+
+}
diff --git a/tests/robotests/src/com/android/settings/notification/TouchSoundPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/TouchSoundPreferenceControllerTest.java
new file mode 100644
index 0000000..f530f66
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/notification/TouchSoundPreferenceControllerTest.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2017 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.notification;
+
+import android.app.Activity;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.media.AudioManager;
+import android.support.v14.preference.SwitchPreference;
+import android.support.v7.preference.PreferenceScreen;
+import android.provider.Settings.System;
+
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.Robolectric;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowApplication;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class TouchSoundPreferenceControllerTest {
+
+    @Mock
+    private AudioManager mAudioManager;
+    @Mock
+    private PreferenceScreen mScreen;
+    @Mock
+    private Activity mActivity;
+    @Mock
+    private ContentResolver mContentResolver;
+    @Mock
+    private SoundSettings mSetting;
+    @Mock
+    private Context mContext;
+
+    private TouchSoundPreferenceController mController;
+    private SwitchPreference mPreference;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        when(mActivity.getSystemService(Context.AUDIO_SERVICE)).thenReturn(mAudioManager);
+        when(mSetting.getActivity()).thenReturn(mActivity);
+        when(mActivity.getContentResolver()).thenReturn(mContentResolver);
+        mPreference = new SwitchPreference(ShadowApplication.getInstance().getApplicationContext());
+        mController = new TouchSoundPreferenceController(mContext, mSetting, null);
+        when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mPreference);
+        doReturn(mScreen).when(mSetting).getPreferenceScreen();
+    }
+
+    @Test
+    public void isAvailable_isAlwaysTrue() {
+        assertThat(mController.isAvailable()).isTrue();
+    }
+
+    @Test
+    public void displayPreference_soundEffectEnabled_shouldCheckedPreference() {
+        System.putInt(mContentResolver, System.SOUND_EFFECTS_ENABLED, 1);
+
+        mController.displayPreference(mScreen);
+
+        assertThat(mPreference.isChecked()).isTrue();
+    }
+
+    @Test
+    public void displayPreference_soundEffectDisabled_shouldUncheckedPreference() {
+        System.putInt(mContentResolver, System.SOUND_EFFECTS_ENABLED, 0);
+
+        mController.displayPreference(mScreen);
+
+        assertThat(mPreference.isChecked()).isFalse();
+    }
+
+    @Test
+    public void onPreferenceChanged_preferenceChecked_shouldEnabledSoundEffect() {
+        mController.displayPreference(mScreen);
+
+        mPreference.getOnPreferenceChangeListener().onPreferenceChange(mPreference, true);
+
+        assertThat(System.getInt(mContentResolver, System.SOUND_EFFECTS_ENABLED, 1)).isEqualTo(1);
+    }
+
+    @Test
+    public void onPreferenceChanged_preferenceUnchecked_shouldDisabledSoundEffect() {
+        mController.displayPreference(mScreen);
+
+        mPreference.getOnPreferenceChangeListener().onPreferenceChange(mPreference, false);
+
+        assertThat(System.getInt(mContentResolver, System.SOUND_EFFECTS_ENABLED, 1)).isEqualTo(0);
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/notification/VibrateOnTouchPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/VibrateOnTouchPreferenceControllerTest.java
new file mode 100644
index 0000000..440b69e
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/notification/VibrateOnTouchPreferenceControllerTest.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2017 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.notification;
+
+import android.app.Activity;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.os.Vibrator;
+import android.support.v14.preference.SwitchPreference;
+import android.support.v7.preference.PreferenceScreen;
+import android.provider.Settings.System;
+
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowApplication;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Answers.RETURNS_DEEP_STUBS;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.when;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class VibrateOnTouchPreferenceControllerTest {
+
+    @Mock
+    private PreferenceScreen mScreen;
+    @Mock
+    private Activity mActivity;
+    @Mock
+    private ContentResolver mContentResolver;
+    @Mock
+    private SoundSettings mSetting;
+    @Mock
+    private Context mContext;
+    @Mock
+    private Vibrator mVibrator;
+
+    private VibrateOnTouchPreferenceController mController;
+    private SwitchPreference mPreference;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        when(mActivity.getSystemService(Context.VIBRATOR_SERVICE)).thenReturn(mVibrator);
+        when(mContext.getSystemService(Context.VIBRATOR_SERVICE)).thenReturn(mVibrator);
+        when(mVibrator.hasVibrator()).thenReturn(true);
+        when(mSetting.getActivity()).thenReturn(mActivity);
+        when(mActivity.getContentResolver()).thenReturn(mContentResolver);
+        mPreference = new SwitchPreference(ShadowApplication.getInstance().getApplicationContext());
+        mController = new VibrateOnTouchPreferenceController(mContext, mSetting, null);
+        when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mPreference);
+        doReturn(mScreen).when(mSetting).getPreferenceScreen();
+    }
+
+    @Test
+    public void isAvailable_hasHaptic_shouldReturnTrue() {
+
+        assertThat(mController.isAvailable()).isTrue();
+    }
+
+    @Test
+    public void isAvailable_noHaptic_shouldReturnFalse() {
+        when(mVibrator.hasVibrator()).thenReturn(false);
+
+        assertThat(mController.isAvailable()).isFalse();
+    }
+
+    @Test
+    public void displayPreference_hapticEnabled_shouldCheckedPreference() {
+        System.putInt(mContentResolver, System.HAPTIC_FEEDBACK_ENABLED, 1);
+
+        mController.displayPreference(mScreen);
+
+        assertThat(mPreference.isChecked()).isTrue();
+    }
+
+    @Test
+    public void displayPreference_hapticDisabled_shouldUncheckedPreference() {
+        System.putInt(mContentResolver, System.HAPTIC_FEEDBACK_ENABLED, 0);
+
+        mController.displayPreference(mScreen);
+
+        assertThat(mPreference.isChecked()).isFalse();
+    }
+
+    @Test
+    public void onPreferenceChanged_preferenceChecked_shouldEnabledHaptic() {
+        mController.displayPreference(mScreen);
+
+        mPreference.getOnPreferenceChangeListener().onPreferenceChange(mPreference, true);
+
+        assertThat(System.getInt(mContentResolver, System.HAPTIC_FEEDBACK_ENABLED, 1)).isEqualTo(1);
+    }
+
+    @Test
+    public void onPreferenceChanged_preferenceUnchecked_shouldDisabledHaptic() {
+        mController.displayPreference(mScreen);
+
+        mPreference.getOnPreferenceChangeListener().onPreferenceChange(mPreference, false);
+
+        assertThat(System.getInt(mContentResolver, System.HAPTIC_FEEDBACK_ENABLED, 1)).isEqualTo(0);
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/notification/ZenAccessSettingsTest.java b/tests/robotests/src/com/android/settings/notification/ZenAccessSettingsTest.java
new file mode 100644
index 0000000..854edcd
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/notification/ZenAccessSettingsTest.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2017 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.notification;
+
+import android.content.Context;
+
+import com.android.internal.logging.nano.MetricsProto;
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settings.testutils.FakeFeatureFactory;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.verify;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class ZenAccessSettingsTest {
+
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private Context mContext;
+
+    private FakeFeatureFactory mFeatureFactory;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        FakeFeatureFactory.setupForTest(mContext);
+        mFeatureFactory = (FakeFeatureFactory) FakeFeatureFactory.getFactory(mContext);
+    }
+
+    @Test
+    public void logSpecialPermissionChange() {
+        ZenAccessSettings.logSpecialPermissionChange(true, "app", mContext);
+        verify(mFeatureFactory.metricsFeatureProvider).action(any(Context.class),
+                eq(MetricsProto.MetricsEvent.APP_SPECIAL_PERMISSION_DND_ALLOW),
+                eq("app"));
+
+        ZenAccessSettings.logSpecialPermissionChange(false, "app", mContext);
+        verify(mFeatureFactory.metricsFeatureProvider).action(any(Context.class),
+                eq(MetricsProto.MetricsEvent.APP_SPECIAL_PERMISSION_DND_DENY),
+                eq("app"));
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/search2/InstalledAppResultLoaderTest.java b/tests/robotests/src/com/android/settings/search2/InstalledAppResultLoaderTest.java
index 24aa94b..8fde73d 100644
--- a/tests/robotests/src/com/android/settings/search2/InstalledAppResultLoaderTest.java
+++ b/tests/robotests/src/com/android/settings/search2/InstalledAppResultLoaderTest.java
@@ -64,11 +64,16 @@
         when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
         when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt()))
                 .thenReturn(Arrays.asList(
-                        ApplicationTestUtils.buildInfo(0 /* uid */, "app1", FLAG_SYSTEM),
-                        ApplicationTestUtils.buildInfo(0 /* uid */, "app2", FLAG_SYSTEM),
-                        ApplicationTestUtils.buildInfo(0 /* uid */, "app3", FLAG_SYSTEM),
-                        ApplicationTestUtils.buildInfo(0 /* uid */, "app4", 0 /* flags */),
-                        ApplicationTestUtils.buildInfo(0 /* uid */, "app", 0 /* flags */)));
+                        ApplicationTestUtils.buildInfo(0 /* uid */, "app1", FLAG_SYSTEM,
+                                0 /* targetSdkVersion */),
+                        ApplicationTestUtils.buildInfo(0 /* uid */, "app2", FLAG_SYSTEM,
+                                0 /* targetSdkVersion */),
+                        ApplicationTestUtils.buildInfo(0 /* uid */, "app3", FLAG_SYSTEM,
+                                0 /* targetSdkVersion */),
+                        ApplicationTestUtils.buildInfo(0 /* uid */, "app4", 0 /* flags */,
+                                0 /* targetSdkVersion */),
+                        ApplicationTestUtils.buildInfo(0 /* uid */, "app", 0 /* flags */,
+                                0 /* targetSdkVersion */)));
     }
 
     @Test
diff --git a/tests/robotests/src/com/android/settings/testutils/ApplicationTestUtils.java b/tests/robotests/src/com/android/settings/testutils/ApplicationTestUtils.java
index 8789928..352d128 100644
--- a/tests/robotests/src/com/android/settings/testutils/ApplicationTestUtils.java
+++ b/tests/robotests/src/com/android/settings/testutils/ApplicationTestUtils.java
@@ -15,7 +15,6 @@
 package com.android.settings.testutils;
 
 import android.content.pm.ApplicationInfo;
-import android.os.UserHandle;
 
 /**
  * Helper for mocking installed applications.
@@ -25,19 +24,21 @@
      * Create and populate an {@link android.content.pm.ApplicationInfo} object that describes an
      * installed app.
      *
-     * @param userId The user id that this app is installed for. Typical values are 0 for the
-     *         system user and 10, 11, 12... for secondary users.
+     * @param uid The app's uid
      * @param packageName The app's package name.
      * @param flags Flags describing the app. See {@link android.content.pm.ApplicationInfo#flags}
      *         for possible values.
+     * @param targetSdkVersion The app's target SDK version
      *
      * @see android.content.pm.ApplicationInfo
      */
-    public static ApplicationInfo buildInfo(int userId, String packageName, int flags) {
+    public static ApplicationInfo buildInfo(int uid, String packageName, int flags,
+            int targetSdkVersion) {
         final ApplicationInfo info = new ApplicationInfo();
-        info.uid = UserHandle.getUid(userId, 1);
+        info.uid = uid;
         info.packageName = packageName;
         info.flags = flags;
+        info.targetSdkVersion = targetSdkVersion;
         return info;
     }
 }
diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/SettingsShadowSystemProperties.java b/tests/robotests/src/com/android/settings/testutils/shadow/SettingsShadowSystemProperties.java
new file mode 100644
index 0000000..e2a863a
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/testutils/shadow/SettingsShadowSystemProperties.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2017 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.testutils.shadow;
+
+import android.os.SystemProperties;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.shadows.ShadowSystemProperties;
+
+/**
+ * This class provides write capability to ShadowSystemProperties.
+ */
+@Implements(SystemProperties.class)
+public class SettingsShadowSystemProperties extends ShadowSystemProperties {
+
+    private static final Map<String, String> sValues = new HashMap<>();
+
+    @Implementation
+    public static synchronized boolean getBoolean(String key, boolean def) {
+        if (sValues.containsKey(key)) {
+            String val = sValues.get(key);
+            return "y".equals(val) || "yes".equals(val) || "1".equals(val) || "true".equals(val)
+                || "on".equals(val);
+        }
+        return ShadowSystemProperties.getBoolean(key, def);
+    }
+
+    public static synchronized void set(String key, String val) {
+        sValues.put(key, val);
+    }
+
+    public static synchronized void clear() {
+        sValues.clear();
+    }
+
+}
diff --git a/tests/unit/src/com/android/settings/fingerprint/SetupFingerprintEnrollIntroductionTest.java b/tests/unit/src/com/android/settings/fingerprint/SetupFingerprintEnrollIntroductionTest.java
new file mode 100644
index 0000000..8afed18
--- /dev/null
+++ b/tests/unit/src/com/android/settings/fingerprint/SetupFingerprintEnrollIntroductionTest.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2017 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.fingerprint;
+
+
+import static org.mockito.Mockito.doReturn;
+
+import android.app.KeyguardManager;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.Intent;
+import android.test.ActivityUnitTestCase;
+import android.view.View;
+import android.widget.Button;
+
+import com.android.settings.R;
+
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+public class SetupFingerprintEnrollIntroductionTest
+        extends ActivityUnitTestCase<SetupFingerprintEnrollIntroduction> {
+
+    private TestContext mContext;
+
+    @Mock
+    private KeyguardManager mKeyguardManager;
+
+    private SetupFingerprintEnrollIntroduction mActivity;
+
+    public SetupFingerprintEnrollIntroductionTest() {
+        super(SetupFingerprintEnrollIntroduction.class);
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        MockitoAnnotations.initMocks(this);
+        mContext = new TestContext(getInstrumentation().getTargetContext());
+        setActivityContext(mContext);
+
+        getInstrumentation().runOnMainSync(() -> {
+            final Intent intent = new Intent();
+            mActivity = startActivity(intent,
+                    null /* savedInstanceState */, null /* lastNonConfigurationInstance */);
+        });
+    }
+
+    public void testKeyguardNotSecure_shouldShowSkipDialog() {
+        doReturn(false).when(mKeyguardManager).isKeyguardSecure();
+
+        getInstrumentation().runOnMainSync(() -> {
+            getInstrumentation().callActivityOnCreate(mActivity, null);
+            getInstrumentation().callActivityOnResume(mActivity);
+
+            final Button skipButton =
+                    (Button) mActivity.findViewById(R.id.fingerprint_cancel_button);
+            assertEquals(View.VISIBLE, skipButton.getVisibility());
+            skipButton.performClick();
+        });
+
+        assertFalse(isFinishCalled());
+    }
+
+    public void testKeyguardSecure_shouldNotShowSkipDialog() {
+        doReturn(true).when(mKeyguardManager).isKeyguardSecure();
+
+        getInstrumentation().runOnMainSync(() -> {
+            getInstrumentation().callActivityOnCreate(mActivity, null);
+            getInstrumentation().callActivityOnResume(mActivity);
+
+            final Button skipButton =
+                    (Button) mActivity.findViewById(R.id.fingerprint_cancel_button);
+            assertEquals(View.VISIBLE, skipButton.getVisibility());
+            skipButton.performClick();
+        });
+
+        assertTrue(isFinishCalled());
+    }
+
+    public class TestContext extends ContextWrapper {
+
+        public TestContext(Context base) {
+            super(base);
+        }
+
+        @Override
+        public Object getSystemService(String name) {
+            if (Context.KEYGUARD_SERVICE.equals(name)) {
+                return mKeyguardManager;
+            }
+            return super.getSystemService(name);
+        }
+    }
+}