Merge "Use consistent formatter to convert byte->MB->GB."
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 6698814..a68b2a7 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -2564,6 +2564,20 @@
                        android:value="com.android.settings.applications.VrListenerSettings" />
         </activity>
 
+        <activity android:name="Settings$PictureInPictureSettingsActivity"
+            android:label="@string/picture_in_picture_title"
+            android:taskAffinity="">
+            <intent-filter android:priority="1">
+                <action android:name="android.settings.PICTURE_IN_PICTURE_SETTINGS" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+            <meta-data android:name="com.android.settings.FRAGMENT_CLASS"
+                android:value="com.android.settings.applications.PictureInPictureSettings" />
+        </activity>
 
         <activity android:name="Settings$ZenAccessSettingsActivity"
                   android:label="@string/manage_zen_access_title"
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 8f5bbf1..7a3be0c 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -6233,6 +6233,9 @@
     <!-- [CHAR LIMIT=100] Notification Importance slider: unset importance level description -->
     <string name="notification_importance_none">Not set</string>
 
+    <!-- [CHAR LIMIT=100] Notification Importance slider: unspecified importance level description -->
+    <string name="notification_importance_unspecified">Let the app decide</string>
+
     <!-- [CHAR LIMIT=100] Notification Importance slider: blocked importance level description -->
     <string name="notification_importance_blocked">Never show notifications</string>
 
@@ -6321,6 +6324,21 @@
          flicker while in VR mode. -->
     <string name="display_vr_pref_off">Reduce flicker</string>
 
+    <!-- Special access > Title for managing Picture-in-picture settings. [CHAR LIMIT=50] -->
+    <string name="picture_in_picture_title">Picture-in-picture</string>
+
+    <!-- Special access > Picture-in-picture > Text to display when the list is empty. [CHAR LIMIT=NONE] -->
+    <string name="picture_in_picture_empty_text">No installed apps support Picture-in-picture</string>
+
+    <!-- Special access > Picture-in-picture > Additional keywords to search for. [CHAR LIMIT=NONE] -->
+    <string name="picture_in_picture_keywords">pip picture in</string>
+
+    <!-- Apps > App Details > Advanced section string title. [CHAR LIMIT=NONE] -->
+    <string name="picture_in_picture_app_detail_title">Picture-in-picture</string>
+
+    <!-- Apps > App Details > Advanced section string description. [CHAR LIMIT=NONE] -->
+    <string name="picture_in_picture_app_detail_summary">Permit entering picture-in-picture when leaving app</string>
+
     <!-- Sound & notification > Advanced section: Title for managing Do Not Disturb access option. [CHAR LIMIT=40] -->
     <string name="manage_zen_access_title">Do Not Disturb access</string>
 
@@ -7641,6 +7659,8 @@
     <string name="notification_log_details_importance">importance</string>
     <!-- Notification log debug tool: header: notification importance explanation -->
     <string name="notification_log_details_explanation">explanation</string>
+    <!-- Notification log debug tool: header: notification importance -->
+    <string name="notification_log_details_badge">can show badge</string>
     <!-- Notification log debug tool: header: notification contentIntent field -->
     <string name="notification_log_details_content_intent">intent</string>
     <!-- Notification log debug tool: header: notification deleteIntent field -->
diff --git a/res/xml/app_notification_settings.xml b/res/xml/app_notification_settings.xml
index 82660dc..7d51fa8 100644
--- a/res/xml/app_notification_settings.xml
+++ b/res/xml/app_notification_settings.xml
@@ -23,6 +23,15 @@
         android:key="block"
         android:title="@string/app_notification_block_title"
         android:summary="@string/app_notification_block_summary"
+        android:order="1"
+        settings:useAdditionalSummary="true"
+        settings:restrictedSwitchSummary="@string/enabled_by_admin" />
+
+    <!-- Show badge -->
+    <com.android.settingslib.RestrictedSwitchPreference
+        android:key="badge"
+        android:title="@string/notification_badge_title"
+        android:summary="@string/notification_badge_summary"
         android:order="2"
         settings:useAdditionalSummary="true"
         settings:restrictedSwitchSummary="@string/enabled_by_admin" />
diff --git a/res/xml/channel_notification_settings.xml b/res/xml/channel_notification_settings.xml
index e1d6d55..4af9fe8 100644
--- a/res/xml/channel_notification_settings.xml
+++ b/res/xml/channel_notification_settings.xml
@@ -27,54 +27,55 @@
         settings:useAdditionalSummary="true"
         settings:restrictedSwitchSummary="@string/enabled_by_admin" />
 
+    <!-- Importance -->
+    <com.android.settings.notification.RestrictedDropDownPreference
+        android:key="importance"
+        android:title="@string/notification_importance_title"
+        android:order="2"/>
+
+    <!-- Default ringtone -->
+    <com.android.settings.notification.DefaultNotificationTonePreference
+        android:key="ringtone"
+        android:title="@string/notification_ringtone_title"
+        android:dialogTitle="@string/notification_ringtone_title"
+        android:order="3"
+        android:ringtoneType="notification" />
+
+    <!-- Vibration -->
+    <com.android.settingslib.RestrictedSwitchPreference
+        android:key="vibrate"
+        android:title="@string/notification_vibrate_title"
+        android:order="4"
+        settings:useAdditionalSummary="true" />
+
     <!-- Show badge -->
     <com.android.settingslib.RestrictedSwitchPreference
         android:key="badge"
         android:title="@string/notification_badge_title"
         android:summary="@string/notification_badge_summary"
-        android:order="3"
+        android:order="5"
         settings:useAdditionalSummary="true"
         settings:restrictedSwitchSummary="@string/enabled_by_admin" />
 
-    <!-- Importance -->
-    <com.android.settings.notification.RestrictedDropDownPreference
-            android:key="importance"
-            android:title="@string/notification_importance_title"
-            android:order="4"/>
+    <!-- Lights -->
+    <com.android.settingslib.RestrictedSwitchPreference
+        android:key="lights"
+        android:title="@string/notification_show_lights_title"
+        android:order="6"
+        settings:useAdditionalSummary="true" />
 
     <!-- Visibility Override -->
     <com.android.settings.notification.RestrictedDropDownPreference
             android:key="visibility_override"
             android:title="@string/app_notification_visibility_override_title"
-            android:order="5" />
+            android:order="7" />
 
     <!-- Bypass DND -->
     <com.android.settingslib.RestrictedSwitchPreference
             android:key="bypass_dnd"
             android:title="@string/app_notification_override_dnd_title"
             android:summary="@string/app_notification_override_dnd_summary"
-            android:order="6"
-            settings:useAdditionalSummary="true" />
-
-    <!-- Lights -->
-    <com.android.settingslib.RestrictedSwitchPreference
-            android:key="lights"
-            android:title="@string/notification_show_lights_title"
-            android:order="7"
-            settings:useAdditionalSummary="true" />
-
-    <!-- Vibration -->
-    <com.android.settingslib.RestrictedSwitchPreference
-            android:key="vibrate"
-            android:title="@string/notification_vibrate_title"
             android:order="8"
             settings:useAdditionalSummary="true" />
 
-    <!-- Default ringtone -->
-    <com.android.settings.notification.DefaultNotificationTonePreference
-            android:key="ringtone"
-            android:title="@string/notification_ringtone_title"
-            android:dialogTitle="@string/notification_ringtone_title"
-            android:order="9"
-            android:ringtoneType="notification" />
 </PreferenceScreen>
diff --git a/res/xml/special_access.xml b/res/xml/special_access.xml
index 7d85195..8bf5c56 100644
--- a/res/xml/special_access.xml
+++ b/res/xml/special_access.xml
@@ -75,6 +75,11 @@
         android:fragment="com.android.settings.notification.NotificationAccessSettings" />
 
     <Preference
+        android:key="picture_in_picture"
+        android:title="@string/picture_in_picture_title"
+        android:fragment="com.android.settings.applications.PictureInPictureSettings"
+        settings:keywords="@string/picture_in_picture_keywords" />
+    <Preference
         android:key="premium_sms"
         android:title="@string/premium_sms_access"
         android:fragment="com.android.settings.applications.PremiumSmsAccess" />
diff --git a/src/com/android/settings/BackupSettingsActivity.java b/src/com/android/settings/BackupSettingsActivity.java
index a4cc4b7..c0456b2 100644
--- a/src/com/android/settings/BackupSettingsActivity.java
+++ b/src/com/android/settings/BackupSettingsActivity.java
@@ -17,19 +17,13 @@
 package com.android.settings;
 
 import android.app.Activity;
-import android.app.backup.BackupManager;
-import android.app.backup.IBackupManager;
-import android.content.Context;
+import android.content.ComponentName;
 import android.content.Intent;
+import android.content.pm.PackageManager;
 import android.os.Bundle;
-import android.os.ServiceManager;
-import android.os.UserHandle;
-import android.text.TextUtils;
 import android.util.Log;
 
-import com.android.settings.R;
-
-import java.net.URISyntaxException;
+import com.android.settings.Settings.PrivacySettingsActivity;
 
 /**
  * A trampoline activity used to launch the configured Backup activity.
@@ -42,30 +36,24 @@
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        String backup = getResources().getString(R.string.config_backup_settings_intent);
-        if (!TextUtils.isEmpty(backup)) {
-            try {
-                Intent intent = Intent.parseUri(backup, 0);
-                if (intent.resolveActivity(getPackageManager()) != null) {
-                    // use startActivityForResult to let the activity check the caller signature
-                    IBackupManager bmgr = IBackupManager.Stub.asInterface(
-                            ServiceManager.getService(Context.BACKUP_SERVICE));
-                    boolean backupOkay;
-                    try {
-                        backupOkay = bmgr.isBackupServiceActive(UserHandle.myUserId());
-                    } catch (Exception e) {
-                        // things go wrong talking to the backup system => ignore and
-                        // pass the default 'false' as the "backup is a thing?" state.
-                        backupOkay = false;
-                    }
-                    intent.putExtra(BackupManager.EXTRA_BACKUP_SERVICES_AVAILABLE, backupOkay);
-                    startActivityForResult(intent, -1);
-                } else {
-                    Log.e(TAG, "Backup component not found!");
-                }
-            } catch (URISyntaxException e) {
-                Log.e(TAG, "Invalid backup component URI!", e);
+
+        BackupSettingsHelper backupHelper = new BackupSettingsHelper();
+        if (backupHelper.isIntentProvidedByTransport(getPackageManager())) {
+            Intent intent = backupHelper.getIntentForBackupSettings();
+            if (intent != null) {
+                // use startActivityForResult to let the activity check the caller signature
+                startActivityForResult(intent, -1);
             }
+        } else {
+            // This should never happen, because isIntentProvidedByTransport() is called before
+            // starting this activity.
+            Log.e(TAG, "Backup transport has not provided an intent"
+                    + " or the component for the intent is not found!");
+            getPackageManager().setComponentEnabledSetting(
+                    new ComponentName(getPackageName(), PrivacySettingsActivity.class.getName()),
+                    PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
+                    PackageManager.DONT_KILL_APP);
+            startActivityForResult(new Intent(this, PrivacySettingsActivity.class), -1);
         }
         finish();
     }
diff --git a/src/com/android/settings/BackupSettingsHelper.java b/src/com/android/settings/BackupSettingsHelper.java
new file mode 100644
index 0000000..37c0971
--- /dev/null
+++ b/src/com/android/settings/BackupSettingsHelper.java
@@ -0,0 +1,103 @@
+/*
+ * 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.app.backup.BackupManager;
+import android.app.backup.BackupTransport;
+import android.app.backup.IBackupManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Helper class for {@link BackupSettingsActivity} that interacts with {@link IBackupManager}.
+ */
+public class BackupSettingsHelper {
+    private static final String TAG = "BackupSettingsHelper";
+
+    private IBackupManager mBackupManager = IBackupManager.Stub.asInterface(
+            ServiceManager.getService(Context.BACKUP_SERVICE));
+
+
+    /**
+     * Gets the intent from Backup transport and adds the extra depending on whether the user has
+     * rights to see backup settings.
+     *
+     * @return Intent to launch Backup settings provided by the Backup transport.
+     */
+    public Intent getIntentForBackupSettings() {
+        Intent intent = getIntentFromBackupTransport();
+        if (intent != null) {
+            intent.putExtra(BackupManager.EXTRA_BACKUP_SERVICES_AVAILABLE, isBackupServiceActive());
+        }
+        return intent;
+    }
+
+
+    /**
+     * Checks if the transport provided the intent to launch the backup settings and if that
+     * intent resolves to an activity.
+     */
+    public boolean isIntentProvidedByTransport(PackageManager packageManager) {
+        Intent intent = getIntentFromBackupTransport();
+        return intent != null && intent.resolveActivity(packageManager) != null;
+    }
+
+    /**
+     * Gets an intent to launch the backup settings from the current transport using
+     * {@link BackupTransport#dataManagementIntent()} API.
+     *
+     * @return intent provided by transport or null if no intent was provided.
+     */
+    private Intent getIntentFromBackupTransport() {
+        try {
+            Intent intent =
+                    mBackupManager.getDataManagementIntent(mBackupManager.getCurrentTransport());
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                if (intent != null) {
+                    Log.d(TAG, "Parsed intent from backup transport: " + intent.toString());
+                } else {
+                    Log.d(TAG, "Received a null intent from backup transport");
+                }
+            }
+            return intent;
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error getting data management intent", e);
+        }
+        return null;
+    }
+
+    /** Checks if backup service is enabled for this user. */
+    private boolean isBackupServiceActive() {
+        boolean backupOkay;
+        try {
+            backupOkay = mBackupManager.isBackupServiceActive(UserHandle.myUserId());
+        } catch (Exception e) {
+            // things go wrong talking to the backup system => ignore and
+            // pass the default 'false' as the "backup is a thing?" state.
+            backupOkay = false;
+        }
+        return backupOkay;
+    }
+}
diff --git a/src/com/android/settings/Settings.java b/src/com/android/settings/Settings.java
index a393436..66e5b33 100644
--- a/src/com/android/settings/Settings.java
+++ b/src/com/android/settings/Settings.java
@@ -105,6 +105,7 @@
     public static class UserSettingsActivity extends SettingsActivity { /* empty */ }
     public static class NotificationAccessSettingsActivity extends SettingsActivity { /* empty */ }
     public static class VrListenersSettingsActivity extends SettingsActivity { /* empty */ }
+    public static class PictureInPictureSettingsActivity extends SettingsActivity { /* empty */ }
     public static class ZenAccessSettingsActivity extends SettingsActivity { /* empty */ }
     public static class ConditionProviderSettingsActivity extends SettingsActivity { /* empty */ }
     public static class UsbSettingsActivity extends SettingsActivity { /* empty */ }
diff --git a/src/com/android/settings/SettingsActivity.java b/src/com/android/settings/SettingsActivity.java
index c01e4c4..8bb6834 100644
--- a/src/com/android/settings/SettingsActivity.java
+++ b/src/com/android/settings/SettingsActivity.java
@@ -33,6 +33,8 @@
 import android.content.res.Configuration;
 import android.nfc.NfcAdapter;
 import android.os.AsyncTask;
+import android.os.Build;
+import android.os.Build.VERSION;
 import android.os.Bundle;
 import android.os.UserHandle;
 import android.os.UserManager;
@@ -965,29 +967,27 @@
             }
         }
 
-        String backupIntent = getResources().getString(R.string.config_backup_settings_intent);
-        boolean useDefaultBackup = TextUtils.isEmpty(backupIntent);
+        // Check if the backup transport has provided an intent to launch the backup settings.
+        BackupSettingsHelper backupHelper = new BackupSettingsHelper();
+        boolean useDefaultBackup = !backupHelper.isIntentProvidedByTransport(getPackageManager());
+        if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
+            Log.v(LOG_TAG, "Enabling default backup settings page: " + useDefaultBackup);
+        }
+
         setTileEnabled(new ComponentName(packageName,
                 Settings.PrivacySettingsActivity.class.getName()), useDefaultBackup, isAdmin);
         setTileEnabled(new ComponentName(packageName,
                         "com.android.settings.PrivacyDashboardAlias"),
                 useDefaultBackup, isAdmin);
 
-        boolean hasBackupActivity = false;
-        if (!useDefaultBackup) {
-            try {
-                Intent intent = Intent.parseUri(backupIntent, 0);
-                hasBackupActivity = !getPackageManager().queryIntentActivities(intent, 0).isEmpty();
-            } catch (URISyntaxException e) {
-                Log.e(LOG_TAG, "Invalid backup intent URI!", e);
-            }
-        }
-
         // Enable/disable BackupSettingsActivity and its alias.
+        if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
+            Log.v(LOG_TAG, "Enabling transport provided backup settings: " + !useDefaultBackup);
+        }
         setTileEnabled(new ComponentName(packageName,
-                BackupSettingsActivity.class.getName()), hasBackupActivity, isAdmin);
+                BackupSettingsActivity.class.getName()), !useDefaultBackup, isAdmin);
         setTileEnabled(new ComponentName(packageName,
-                "com.android.settings.BackupResetDashboardAlias"), hasBackupActivity, isAdmin);
+                "com.android.settings.BackupResetDashboardAlias"), !useDefaultBackup, isAdmin);
 
         setTileEnabled(new ComponentName(packageName,
                 Settings.EnterprisePrivacySettingsActivity.class.getName()),
diff --git a/src/com/android/settings/applications/ActivityInfoWrapper.java b/src/com/android/settings/applications/ActivityInfoWrapper.java
new file mode 100644
index 0000000..c6920ca
--- /dev/null
+++ b/src/com/android/settings/applications/ActivityInfoWrapper.java
@@ -0,0 +1,30 @@
+/*
+ * 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;
+
+/**
+ * This interface replicates a subset of the android.content.pm.ActivityInfo. The interface
+ * exists so that we can use a thin wrapper around the ActivityInfo in production code and a mock in
+ * tests.
+ */
+public interface ActivityInfoWrapper {
+
+    /**
+     * Returns the resizeMode of the activity.
+     */
+    int getResizeMode();
+}
diff --git a/src/com/android/settings/applications/ActivityInfoWrapperImpl.java b/src/com/android/settings/applications/ActivityInfoWrapperImpl.java
new file mode 100644
index 0000000..e7a20bc
--- /dev/null
+++ b/src/com/android/settings/applications/ActivityInfoWrapperImpl.java
@@ -0,0 +1,33 @@
+/*
+ * 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.pm.ActivityInfo;
+
+public class ActivityInfoWrapperImpl implements ActivityInfoWrapper {
+
+    private final ActivityInfo mInfo;
+
+    public ActivityInfoWrapperImpl(ActivityInfo info) {
+        mInfo = info;
+    }
+
+    @Override
+    public int getResizeMode() {
+        return mInfo.resizeMode;
+    }
+}
diff --git a/src/com/android/settings/applications/InstalledAppDetails.java b/src/com/android/settings/applications/InstalledAppDetails.java
index 8bde62a..873c5fd 100755
--- a/src/com/android/settings/applications/InstalledAppDetails.java
+++ b/src/com/android/settings/applications/InstalledAppDetails.java
@@ -51,6 +51,7 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.support.annotation.VisibleForTesting;
+import android.support.v14.preference.SwitchPreference;
 import android.support.v7.preference.Preference;
 import android.support.v7.preference.Preference.OnPreferenceClickListener;
 import android.support.v7.preference.PreferenceCategory;
@@ -934,9 +935,23 @@
                     AdvancedAppSettings.class, "default_sms_app", R.string.sms_application_title,
                     R.string.configure_apps));
         }
+
+        // Get the package info with the activities
+        PackageInfo packageInfoWithActivities = null;
+        try {
+            packageInfoWithActivities = mPm.getPackageInfoAsUser(mPackageName,
+                    PackageManager.GET_ACTIVITIES, UserHandle.myUserId());
+        } catch (NameNotFoundException e) {
+            Log.e(TAG, "Exception while retrieving the package info of " + mPackageName, e);
+        }
+
         boolean hasDrawOverOtherApps = hasPermission(permission.SYSTEM_ALERT_WINDOW);
         boolean hasWriteSettings = hasPermission(permission.WRITE_SETTINGS);
-        if (hasDrawOverOtherApps || hasWriteSettings) {
+        boolean hasPictureInPictureActivities = (packageInfoWithActivities != null) &&
+                PictureInPictureSettings.checkPackageHasPictureInPictureActivities(
+                        packageInfoWithActivities.packageName,
+                        packageInfoWithActivities.activities);
+        if (hasDrawOverOtherApps || hasWriteSettings || hasPictureInPictureActivities) {
             PreferenceCategory category = new PreferenceCategory(getPrefContext());
             category.setTitle(R.string.advanced_apps);
             screen.addPreference(category);
@@ -969,6 +984,23 @@
                 });
                 category.addPreference(pref);
             }
+            if (hasPictureInPictureActivities) {
+                final SwitchPreference pref = new SwitchPreference(getPrefContext());
+                pref.setPersistent(false);
+                pref.setTitle(R.string.picture_in_picture_app_detail_title);
+                pref.setSummary(R.string.picture_in_picture_app_detail_summary);
+                pref.setChecked(PictureInPictureSettings.getEnterPipOnHideStateForPackage(
+                        getContext(), mPackageInfo.applicationInfo.uid, mPackageName));
+                pref.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
+                    @Override
+                    public boolean onPreferenceChange(Preference preference, Object newValue) {
+                        PictureInPictureSettings.setEnterPipOnHideStateForPackage(getContext(),
+                                mPackageInfo.applicationInfo.uid, mPackageName, (Boolean) newValue);
+                        return true;
+                    }
+                });
+                category.addPreference(pref);
+            }
         }
 
         addAppInstallerInfoPref(screen);
diff --git a/src/com/android/settings/applications/PictureInPictureSettings.java b/src/com/android/settings/applications/PictureInPictureSettings.java
new file mode 100644
index 0000000..a17c894
--- /dev/null
+++ b/src/com/android/settings/applications/PictureInPictureSettings.java
@@ -0,0 +1,201 @@
+/*
+ * 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 static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.AppOpsManager.MODE_ERRORED;
+import static android.app.AppOpsManager.OP_ENTER_PICTURE_IN_PICTURE_ON_HIDE;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE_AND_PIPABLE;
+import static android.content.pm.PackageManager.GET_ACTIVITIES;
+
+import android.annotation.Nullable;
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageItemInfo;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.support.v14.preference.SwitchPreference;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceScreen;
+import android.util.ArrayMap;
+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.notification.EmptyTextSettings;
+import com.android.settings.overlay.FeatureFactory;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class PictureInPictureSettings extends EmptyTextSettings {
+
+    private static final String TAG = PictureInPictureSettings.class.getSimpleName();
+    @VisibleForTesting
+    static final List<String> IGNORE_PACKAGE_LIST = new ArrayList<>();
+    static {
+        IGNORE_PACKAGE_LIST.add("com.android.systemui");
+    }
+
+    private Context mContext;
+    private PackageManager mPackageManager;
+
+    /**
+     * @return true if the package has any activities that declare that they support
+     *         picture-in-picture.
+     */
+    static boolean checkPackageHasPictureInPictureActivities(String packageName,
+            ActivityInfo[] activities) {
+        ActivityInfoWrapper[] wrappedActivities = null;
+        if (activities != null) {
+            wrappedActivities = new ActivityInfoWrapper[activities.length];
+            for (int i = 0; i < activities.length; i++) {
+                wrappedActivities[i] = new ActivityInfoWrapperImpl(activities[i]);
+            }
+        }
+        return checkPackageHasPictureInPictureActivities(packageName, wrappedActivities);
+    }
+
+    /**
+     * @return true if the package has any activities that declare that they support
+     *         picture-in-picture.
+     */
+    @VisibleForTesting
+    static boolean checkPackageHasPictureInPictureActivities(String packageName,
+            ActivityInfoWrapper[] activities) {
+        // Skip if it's in the ignored list
+        if (IGNORE_PACKAGE_LIST.contains(packageName)) {
+            return false;
+        }
+
+        // Iterate through all the activities and check if it is resizeable and supports
+        // picture-in-picture
+        if (activities != null) {
+            for (int i = activities.length - 1; i >= 0; i--) {
+                if (activities[i].getResizeMode() == RESIZE_MODE_RESIZEABLE_AND_PIPABLE) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Sets whether the app associated with the given {@param packageName} is allowed to enter
+     * picture-in-picture when it is hidden.
+     */
+    static void setEnterPipOnHideStateForPackage(Context context, int uid, String packageName,
+            boolean value) {
+        final AppOpsManager appOps = context.getSystemService(AppOpsManager.class);
+        final int newMode = value ? MODE_ALLOWED : MODE_ERRORED;
+        appOps.setMode(OP_ENTER_PICTURE_IN_PICTURE_ON_HIDE,
+                uid, packageName, newMode);
+    }
+
+    /**
+     * @return whether the app associated with the given {@param packageName} is allowed to enter
+     *         picture-in-picture when it is hidden.
+     */
+    static boolean getEnterPipOnHideStateForPackage(Context context, int uid, String packageName) {
+        final AppOpsManager appOps = context.getSystemService(AppOpsManager.class);
+        return appOps.checkOpNoThrow(OP_ENTER_PICTURE_IN_PICTURE_ON_HIDE,
+                uid, packageName) == MODE_ALLOWED;
+    }
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+
+        mContext = getActivity();
+        mPackageManager = mContext.getPackageManager();
+        setPreferenceScreen(getPreferenceManager().createPreferenceScreen(mContext));
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+
+        // Clear the prefs
+        final PreferenceScreen screen = getPreferenceScreen();
+        screen.removeAll();
+
+        // Fetch the set of applications which have at least one activity that declare that they
+        // support picture-in-picture
+        final ArrayMap<String, Boolean> packageToState = new ArrayMap<>();
+        final ArrayList<ApplicationInfo> pipApps = new ArrayList<>();
+        final List<PackageInfo> installedPackages = mPackageManager.getInstalledPackagesAsUser(
+                GET_ACTIVITIES, UserHandle.myUserId());
+        for (PackageInfo packageInfo : installedPackages) {
+            if (checkPackageHasPictureInPictureActivities(packageInfo.packageName,
+                    packageInfo.activities)) {
+                final String packageName = packageInfo.applicationInfo.packageName;
+                final boolean state = getEnterPipOnHideStateForPackage(mContext,
+                        packageInfo.applicationInfo.uid, packageName);
+                pipApps.add(packageInfo.applicationInfo);
+                packageToState.put(packageName, state);
+            }
+        }
+        Collections.sort(pipApps, new PackageItemInfo.DisplayNameComparator(mPackageManager));
+
+        // Rebuild the list of prefs
+        final Context prefContext = getPrefContext();
+        for (final ApplicationInfo appInfo : pipApps) {
+            final String packageName = appInfo.packageName;
+            final CharSequence label = appInfo.loadLabel(mPackageManager);
+            final SwitchPreference pref = new SwitchPreference(prefContext);
+            pref.setPersistent(false);
+            pref.setIcon(appInfo.loadIcon(mPackageManager));
+            pref.setTitle(label);
+            pref.setChecked(packageToState.get(packageName));
+            pref.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
+                @Override
+                public boolean onPreferenceChange(Preference preference, Object newValue) {
+                    logSpecialPermissionChange((Boolean) newValue, packageName);
+                    setEnterPipOnHideStateForPackage(mContext, appInfo.uid, packageName,
+                            (Boolean) newValue);
+                    return true;
+                }
+            });
+            screen.addPreference(pref);
+        }
+    }
+
+    @Override
+    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
+        super.onViewCreated(view, savedInstanceState);
+        setEmptyText(R.string.picture_in_picture_empty_text);
+    }
+
+    @Override
+    public int getMetricsCategory() {
+        return MetricsEvent.SETTINGS_MANAGE_PICTURE_IN_PICTURE;
+    }
+
+    @VisibleForTesting
+    void logSpecialPermissionChange(boolean newState, String packageName) {
+        int logCategory = newState
+                ? MetricsEvent.APP_PICTURE_IN_PICTURE_ON_HIDE_ALLOW
+                : MetricsEvent.APP_PICTURE_IN_PICTURE_ON_HIDE_DENY;
+        FeatureFactory.getFactory(getContext())
+                .getMetricsFeatureProvider().action(getContext(), logCategory, packageName);
+    }
+}
diff --git a/src/com/android/settings/core/gateway/SettingsGateway.java b/src/com/android/settings/core/gateway/SettingsGateway.java
index 6b08af8..76132ef 100644
--- a/src/com/android/settings/core/gateway/SettingsGateway.java
+++ b/src/com/android/settings/core/gateway/SettingsGateway.java
@@ -57,6 +57,7 @@
 import com.android.settings.applications.ManageAssist;
 import com.android.settings.applications.ManageDomainUrls;
 import com.android.settings.applications.NotificationApps;
+import com.android.settings.applications.PictureInPictureSettings;
 import com.android.settings.applications.ProcessStatsSummary;
 import com.android.settings.applications.ProcessStatsUi;
 import com.android.settings.applications.UsageAccessDetails;
@@ -225,6 +226,7 @@
             AdvancedAppSettings.class.getName(),
             WallpaperTypeSettings.class.getName(),
             VrListenerSettings.class.getName(),
+            PictureInPictureSettings.class.getName(),
             ManagedProfileSettings.class.getName(),
             ChooseAccountActivity.class.getName(),
             IccLockSettings.class.getName(),
diff --git a/src/com/android/settings/dashboard/DashboardFragmentRegistry.java b/src/com/android/settings/dashboard/DashboardFragmentRegistry.java
new file mode 100644
index 0000000..54d4fd0
--- /dev/null
+++ b/src/com/android/settings/dashboard/DashboardFragmentRegistry.java
@@ -0,0 +1,93 @@
+/*
+ * 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.dashboard;
+
+import android.util.ArrayMap;
+
+import com.android.settings.DevelopmentSettings;
+import com.android.settings.DisplaySettings;
+import com.android.settings.SecuritySettings;
+import com.android.settings.accounts.UserAndAccountDashboardFragment;
+import com.android.settings.applications.AdvancedAppSettings;
+import com.android.settings.applications.AppAndNotificationDashboardFragment;
+import com.android.settings.connecteddevice.ConnectedDeviceDashboardFragment;
+import com.android.settings.deviceinfo.StorageDashboardFragment;
+import com.android.settings.inputmethod.InputAndGestureSettings;
+import com.android.settings.inputmethod.InputMethodAndLanguageSettings;
+import com.android.settings.network.NetworkDashboardFragment;
+import com.android.settings.notification.SoundSettings;
+import com.android.settings.system.SystemDashboardFragment;
+import com.android.settingslib.drawer.CategoryKey;
+
+import java.util.Map;
+
+/**
+ * A registry to keep track of which page hosts which category.
+ * TODO: Remove DashboardFragment#getCategoryKey() and just use this registry instead.
+ */
+public class DashboardFragmentRegistry {
+
+    /**
+     * Map from parent fragment to category key. The parent fragment hosts child with
+     * category_key.
+     */
+    public static final Map<String, String> PARENT_TO_CATEGORY_KEY_MAP;
+
+    /**
+     * Map from category_key to parent. This is a helper to look up which fragment hosts the
+     * category_key.
+     */
+    public static final Map<String, String> CATEGORY_KEY_TO_PARENT_MAP;
+
+    static {
+        PARENT_TO_CATEGORY_KEY_MAP = new ArrayMap<>();
+        PARENT_TO_CATEGORY_KEY_MAP.put(
+                NetworkDashboardFragment.class.getName(), CategoryKey.CATEGORY_NETWORK);
+        PARENT_TO_CATEGORY_KEY_MAP.put(ConnectedDeviceDashboardFragment.class.getName(),
+                CategoryKey.CATEGORY_DEVICE);
+        PARENT_TO_CATEGORY_KEY_MAP.put(AppAndNotificationDashboardFragment.class.getName(),
+                CategoryKey.CATEGORY_APPS);
+        PARENT_TO_CATEGORY_KEY_MAP.put(AdvancedAppSettings.class.getName(),
+                CategoryKey.CATEGORY_APPS_DEFAULT);
+        PARENT_TO_CATEGORY_KEY_MAP.put(DisplaySettings.class.getName(),
+                CategoryKey.CATEGORY_DISPLAY);
+        PARENT_TO_CATEGORY_KEY_MAP.put(SoundSettings.class.getName(),
+                CategoryKey.CATEGORY_SOUND);
+        PARENT_TO_CATEGORY_KEY_MAP.put(StorageDashboardFragment.class.getName(),
+                CategoryKey.CATEGORY_STORAGE);
+        PARENT_TO_CATEGORY_KEY_MAP.put(SecuritySettings.class.getName(),
+                CategoryKey.CATEGORY_SECURITY);
+        PARENT_TO_CATEGORY_KEY_MAP.put(UserAndAccountDashboardFragment.class.getName(),
+                CategoryKey.CATEGORY_ACCOUNT);
+        PARENT_TO_CATEGORY_KEY_MAP.put(UserAndAccountDashboardFragment.class.getName(),
+                CategoryKey.CATEGORY_ACCOUNT);
+        PARENT_TO_CATEGORY_KEY_MAP.put(
+                SystemDashboardFragment.class.getName(), CategoryKey.CATEGORY_SYSTEM);
+        PARENT_TO_CATEGORY_KEY_MAP.put(
+                InputAndGestureSettings.class.getName(), CategoryKey.CATEGORY_SYSTEM_INPUT);
+        PARENT_TO_CATEGORY_KEY_MAP.put(InputMethodAndLanguageSettings.class.getName(),
+                CategoryKey.CATEGORY_SYSTEM_LANGUAGE);
+        PARENT_TO_CATEGORY_KEY_MAP.put(DevelopmentSettings.class.getName(),
+                CategoryKey.CATEGORY_SYSTEM_DEVELOPMENT);
+
+        CATEGORY_KEY_TO_PARENT_MAP = new ArrayMap<>(PARENT_TO_CATEGORY_KEY_MAP.size());
+
+        for (Map.Entry<String, String> parentToKey : PARENT_TO_CATEGORY_KEY_MAP.entrySet()) {
+            CATEGORY_KEY_TO_PARENT_MAP.put(parentToKey.getValue(), parentToKey.getKey());
+        }
+    }
+}
diff --git a/src/com/android/settings/dashboard/SiteMapManager.java b/src/com/android/settings/dashboard/SiteMapManager.java
new file mode 100644
index 0000000..3144398
--- /dev/null
+++ b/src/com/android/settings/dashboard/SiteMapManager.java
@@ -0,0 +1,212 @@
+/*
+ * 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.dashboard;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.support.annotation.VisibleForTesting;
+import android.support.annotation.WorkerThread;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.settings.SettingsActivity;
+import com.android.settings.overlay.FeatureFactory;
+import com.android.settings.search.IndexDatabaseHelper;
+import com.android.settings.search.IndexDatabaseHelper.IndexColumns;
+import com.android.settings.search.IndexDatabaseHelper.SiteMapColumns;
+import com.android.settingslib.drawer.DashboardCategory;
+import com.android.settingslib.drawer.Tile;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static com.android.settings.dashboard.DashboardFragmentRegistry.CATEGORY_KEY_TO_PARENT_MAP;
+
+/**
+ * A manager class that maintains a "site map" and look up breadcrumb for a certain page on demand.
+ * <p/>
+ * The methods on this class can only be called on a background thread.
+ */
+public class SiteMapManager {
+
+    private static final String TAG = "SiteMapManager";
+    private static final boolean DEBUG_TIMING = false;
+
+    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+    public static final String[] SITE_MAP_COLUMNS = {
+            SiteMapColumns.PARENT_CLASS,
+            SiteMapColumns.PARENT_TITLE,
+            SiteMapColumns.CHILD_CLASS,
+            SiteMapColumns.CHILD_TITLE
+    };
+
+    private static final String[] CLASS_TO_SCREEN_TITLE_COLUMNS = {
+            IndexColumns.CLASS_NAME,
+            IndexColumns.SCREEN_TITLE,
+    };
+
+    private final List<SiteMapPair> mPairs = new ArrayList<>();
+
+    private boolean mInitialized;
+
+    /**
+     * Given a fragment class name and its screen title, build a breadcrumb from Settings root to
+     * this screen.
+     * <p/>
+     * Not all screens have a full breadcrumb path leading up to root, it's because either some
+     * page in the breadcrumb path is not indexed, or it's only reachable via search.
+     */
+    @WorkerThread
+    public synchronized List<String> buildBreadCrumb(Context context, String clazz,
+            String screenTitle) {
+        init(context);
+        final long startTime = System.currentTimeMillis();
+        final List<String> breadcrumbs = new ArrayList<>();
+        if (!mInitialized) {
+            Log.w(TAG, "SiteMap is not initialized yet, skipping");
+            return breadcrumbs;
+        }
+        breadcrumbs.add(screenTitle);
+        String currentClass = clazz;
+        String currentTitle = screenTitle;
+        // Look up current page's parent, if found add it to breadcrumb string list, and repeat.
+        while (true) {
+            final SiteMapPair pair = lookUpParent(currentClass, currentTitle);
+            if (pair == null) {
+                if (DEBUG_TIMING) {
+                    Log.d(TAG, "BreadCrumb timing: " + (System.currentTimeMillis() - startTime));
+                }
+                return breadcrumbs;
+            }
+            breadcrumbs.add(0, pair.parentTitle);
+            currentClass = pair.parentClass;
+            currentTitle = pair.parentTitle;
+        }
+    }
+
+    /**
+     * Initialize a list of {@link SiteMapPair}s. Each pair knows about a single parent-child
+     * page relationship.
+     *
+     * We get the knowledge of such mPairs from 2 sources:
+     * 1. Static indexing time: we know which page(s) a parent can open by parsing its pref xml.
+     * 2. IA: We know from {@link DashboardFeatureProvider} which page can be dynamically
+     * injected to where.
+     */
+    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+    @WorkerThread
+    synchronized void init(Context context) {
+        if (mInitialized) {
+            // Make sure only init once.
+            return;
+        }
+        final long startTime = System.currentTimeMillis();
+        // First load site map from static index table.
+        final Context appContext = context.getApplicationContext();
+        final SQLiteDatabase db = IndexDatabaseHelper.getInstance(appContext).getReadableDatabase();
+        Cursor sitemap = db.query(IndexDatabaseHelper.Tables.TABLE_SITE_MAP, SITE_MAP_COLUMNS, null,
+                null, null, null, null);
+        while (sitemap.moveToNext()) {
+            final SiteMapPair pair = new SiteMapPair(
+                    sitemap.getString(sitemap.getColumnIndex(SiteMapColumns.PARENT_CLASS)),
+                    sitemap.getString(sitemap.getColumnIndex(SiteMapColumns.PARENT_TITLE)),
+                    sitemap.getString(sitemap.getColumnIndex(SiteMapColumns.CHILD_CLASS)),
+                    sitemap.getString(sitemap.getColumnIndex(SiteMapColumns.CHILD_TITLE)));
+            mPairs.add(pair);
+        }
+        sitemap.close();
+
+        // Then prepare a local map that contains class name -> screen title mapping. This is needed
+        // to figure out the display name for any fragment if it's injected dynamically through IA.
+        final Map<String, String> classToTitleMap = new HashMap<>();
+        final Cursor titleQuery = db.query(IndexDatabaseHelper.Tables.TABLE_PREFS_INDEX,
+                CLASS_TO_SCREEN_TITLE_COLUMNS, null, null, null, null, null);
+        while (titleQuery.moveToNext()) {
+            classToTitleMap.put(
+                    titleQuery.getString(titleQuery.getColumnIndex(IndexColumns.CLASS_NAME)),
+                    titleQuery.getString(titleQuery.getColumnIndex(IndexColumns.SCREEN_TITLE)));
+        }
+        titleQuery.close();
+
+        // Loop through all IA categories and pages and build additional SiteMapPairs
+        List<DashboardCategory> categories = FeatureFactory.getFactory(context)
+                .getDashboardFeatureProvider(context).getAllCategories();
+
+        for (DashboardCategory category : categories) {
+            // Find the category key first.
+            final String parentClass = CATEGORY_KEY_TO_PARENT_MAP.get(category.key);
+            if (parentClass == null) {
+                continue;
+            }
+            // Use the key to look up parent (which page hosts this key)
+            final String parentName = classToTitleMap.get(parentClass);
+            if (parentName == null) {
+                continue;
+            }
+            // Build parent-child mPairs for all children listed under this key.
+            for (Tile tile : category.tiles) {
+                final String childTitle = tile.title.toString();
+                String childClass = null;
+                if (tile.metaData != null) {
+                    childClass = tile.metaData.getString(
+                            SettingsActivity.META_DATA_KEY_FRAGMENT_CLASS);
+                }
+                if (childClass == null) {
+                    continue;
+                }
+                mPairs.add(new SiteMapPair(parentClass, parentName, childClass, childTitle));
+            }
+        }
+        // Done.
+        mInitialized = true;
+        if (DEBUG_TIMING) {
+            Log.d(TAG, "Init timing: " + (System.currentTimeMillis() - startTime));
+        }
+    }
+
+    @WorkerThread
+    private SiteMapPair lookUpParent(String clazz, String title) {
+        for (SiteMapPair pair : mPairs) {
+            if (TextUtils.equals(pair.childClass, clazz)
+                    && TextUtils.equals(title, pair.childTitle)) {
+                return pair;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Data model for a parent-child page pair.
+     */
+    private static class SiteMapPair {
+        public final String parentClass;
+        public final String parentTitle;
+        public final String childClass;
+        public final String childTitle;
+
+        public SiteMapPair(String parentClass, String parentTitle, String childClass,
+                String childTitle) {
+            this.parentClass = parentClass;
+            this.parentTitle = parentTitle;
+            this.childClass = childClass;
+            this.childTitle = childTitle;
+        }
+    }
+}
diff --git a/src/com/android/settings/deviceinfo/StorageDashboardFragment.java b/src/com/android/settings/deviceinfo/StorageDashboardFragment.java
index d05d088..8fabd8d 100644
--- a/src/com/android/settings/deviceinfo/StorageDashboardFragment.java
+++ b/src/com/android/settings/deviceinfo/StorageDashboardFragment.java
@@ -21,9 +21,6 @@
 import android.os.storage.StorageManager;
 import android.os.storage.VolumeInfo;
 import android.provider.SearchIndexableResource;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
 
 import com.android.internal.logging.nano.MetricsProto;
 import com.android.settings.R;
@@ -62,7 +59,8 @@
 
         // Initialize the storage sizes that we can quickly calc.
         StorageManager sm = context.getSystemService(StorageManager.class);
-        String volumeId = getArguments().getString(VolumeInfo.EXTRA_VOLUME_ID);
+        String volumeId = getArguments().getString(VolumeInfo.EXTRA_VOLUME_ID,
+                VolumeInfo.ID_PRIVATE_INTERNAL);
         mVolume = sm.findVolumeById(volumeId);
         if (!isVolumeValid()) {
             getActivity().finish();
diff --git a/src/com/android/settings/notification/AppNotificationSettings.java b/src/com/android/settings/notification/AppNotificationSettings.java
index b740b92..d4cd6f8 100644
--- a/src/com/android/settings/notification/AppNotificationSettings.java
+++ b/src/com/android/settings/notification/AppNotificationSettings.java
@@ -19,6 +19,7 @@
 import android.app.Activity;
 import android.app.Notification;
 import android.app.NotificationChannel;
+import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
@@ -35,11 +36,13 @@
 import com.android.settings.Utils;
 import com.android.settings.applications.AppHeaderController;
 import com.android.settings.applications.AppInfoBase;
+import com.android.settings.core.PreferenceController;
 import com.android.settings.dashboard.DashboardFeatureProvider;
 import com.android.settings.notification.NotificationBackend.AppRow;
 import com.android.settings.overlay.FeatureFactory;
 import com.android.settingslib.RestrictedPreference;
 import com.android.settingslib.RestrictedSwitchPreference;
+import com.android.settingslib.drawer.CategoryKey;
 
 import java.text.Collator;
 import java.util.Collections;
@@ -88,10 +91,12 @@
         addPreferencesFromResource(R.xml.app_notification_settings);
 
         mBlock = (RestrictedSwitchPreference) getPreferenceScreen().findPreference(KEY_BLOCK);
+        mBadge = (RestrictedSwitchPreference) getPreferenceScreen().findPreference(KEY_BADGE);
         mChannels = (PreferenceCategory) findPreference(KEY_CHANNELS);
 
         if (mPkgInfo != null) {
-            setupBlock(mAppRow.systemApp, mAppRow.banned);
+            setupBlock();
+            setupBadge();
             // load settings intent
             ArrayMap<String, AppRow> rows = new ArrayMap<String, AppRow>();
             rows.put(mAppRow.pkg, mAppRow);
@@ -113,6 +118,7 @@
                     if (channel.isDeleted()) {
                         channelPref.setTitle(
                                 getString(R.string.deleted_channel_name, channel.getName()));
+                        channelPref.setEnabled(false);
                     } else {
                         Bundle channelArgs = new Bundle();
                         channelArgs.putInt(AppInfoBase.ARG_PACKAGE_UID, mUid);
@@ -153,16 +159,27 @@
             finish();
             return;
         }
-        if (mBlock != null) {
-            mBlock.setDisabledByAdmin(mSuspendedAppsAdmin);
-        }
     }
 
-    private void setupBlock(boolean notBlockable, boolean banned) {
-        if (notBlockable) {
+    private void setupBadge() {
+        mBadge.setDisabledByAdmin(mSuspendedAppsAdmin);
+        mBadge.setChecked(mAppRow.showBadge);
+        mBadge.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
+            @Override
+            public boolean onPreferenceChange(Preference preference, Object newValue) {
+                final boolean value = (Boolean) newValue;
+                mBackend.setShowBadge(mPkg, mUid, value);
+                return true;
+            }
+        });
+    }
+
+    private void setupBlock() {
+        if (mAppRow.systemApp) {
             setVisible(mBlock, false);
         } else {
-            mBlock.setChecked(banned);
+            mBlock.setDisabledByAdmin(mSuspendedAppsAdmin);
+            mBlock.setChecked(mAppRow.banned);
             mBlock.setOnPreferenceChangeListener(
                     new Preference.OnPreferenceChangeListener() {
                         @Override
@@ -180,6 +197,7 @@
 
     private void updateDependents(boolean banned) {
         setVisible(mChannels, !(mChannelList.isEmpty() || banned));
+        setVisible(mBadge, !banned);
     }
 
     private List<ResolveInfo> queryNotificationConfigActivities() {
diff --git a/src/com/android/settings/notification/ChannelNotificationSettings.java b/src/com/android/settings/notification/ChannelNotificationSettings.java
index e73feb5..2749e03 100644
--- a/src/com/android/settings/notification/ChannelNotificationSettings.java
+++ b/src/com/android/settings/notification/ChannelNotificationSettings.java
@@ -26,6 +26,7 @@
 import android.app.NotificationChannel;
 import android.app.NotificationManager;
 import android.app.admin.DevicePolicyManager;
+import android.content.Context;
 import android.content.Intent;
 import android.content.pm.UserInfo;
 import android.net.Uri;
@@ -47,15 +48,17 @@
 import com.android.settingslib.RestrictedSwitchPreference;
 
 import java.util.ArrayList;
+import java.util.List;
 
 public class ChannelNotificationSettings extends NotificationSettingsBase {
+    private static final String TAG = "ChannelSettings";
+
     protected static final String KEY_BYPASS_DND = "bypass_dnd";
     protected static final String KEY_VISIBILITY_OVERRIDE = "visibility_override";
     protected static final String KEY_IMPORTANCE = "importance";
     protected static final String KEY_LIGHTS = "lights";
     protected static final String KEY_VIBRATE = "vibrate";
     protected static final String KEY_RINGTONE = "ringtone";
-    protected static final String KEY_BADGE = "badge";
 
     protected RestrictedSwitchPreference mLights;
     protected RestrictedSwitchPreference mVibrate;
@@ -114,8 +117,8 @@
                     .getApplicationFeatureProvider(activity)
                     .newAppHeaderController(this /* fragment */, null /* appHeader */)
                     .setIcon(mAppRow.icon)
-                    .setLabel(mAppRow.label)
-                    .setSummary(mChannel.getName())
+                    .setLabel(mChannel.getName())
+                    .setSummary(mAppRow.label)
                     .setPackageName(mAppRow.pkg)
                     .setUid(mAppRow.uid)
                     .setButtonActions(AppHeaderController.ActionType.ACTION_APP_INFO,
@@ -138,8 +141,6 @@
         mImportance.setDisabledByAdmin(mSuspendedAppsAdmin);
         mPriority.setDisabledByAdmin(mSuspendedAppsAdmin);
         mVisibilityOverride.setDisabledByAdmin(mSuspendedAppsAdmin);
-        mBlock.setDisabledByAdmin(mSuspendedAppsAdmin);
-        mBadge.setDisabledByAdmin(mSuspendedAppsAdmin);
     }
 
     private void setupLights() {
@@ -204,6 +205,7 @@
             }
         });
         mBadge.setDisabledByAdmin(mSuspendedAppsAdmin);
+        mBadge.setEnabled(mAppRow.showBadge);
         mBadge.setChecked(mChannel.canShowBadge());
         mBadge.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
             @Override
@@ -218,15 +220,20 @@
 
         mImportance.setDisabledByAdmin(mSuspendedAppsAdmin);
         final int numImportances = IMPORTANCE_HIGH - IMPORTANCE_MIN + 1;
-        String[] summaries = new String[numImportances];
-        String[] values = new String[numImportances];
+        List<String> summaries = new ArrayList<>();
+        List<String> values = new ArrayList<>();;
         for (int i = 0; i < numImportances; i++) {
             int importance = i + 1;
-            summaries[i] = getSummary(importance);
-            values[i] = String.valueOf(importance);
+            summaries.add(getSummary(importance));
+            values.add(String.valueOf(importance));
         }
-        mImportance.setEntryValues(values);
-        mImportance.setEntries(summaries);
+        if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(mChannel.getId())) {
+            // Add option to reset to letting the app decide
+            summaries.add(getSummary(NotificationManager.IMPORTANCE_UNSPECIFIED));
+            values.add(String.valueOf(NotificationManager.IMPORTANCE_UNSPECIFIED));
+        }
+        mImportance.setEntryValues(values.toArray(new String[0]));
+        mImportance.setEntries(summaries.toArray(new String[0]));
         mImportance.setValue(String.valueOf(mChannel.getImportance()));
         mImportance.setSummary("%s");
 
@@ -245,6 +252,8 @@
 
     private String getSummary(int importance) {
         switch (importance) {
+            case NotificationManager.IMPORTANCE_UNSPECIFIED:
+                return getContext().getString(R.string.notification_importance_unspecified);
             case NotificationManager.IMPORTANCE_NONE:
                 return getContext().getString(R.string.notification_importance_blocked);
             case NotificationManager.IMPORTANCE_MIN:
diff --git a/src/com/android/settings/notification/NotificationBackend.java b/src/com/android/settings/notification/NotificationBackend.java
index 692e1f6..124579f 100644
--- a/src/com/android/settings/notification/NotificationBackend.java
+++ b/src/com/android/settings/notification/NotificationBackend.java
@@ -26,6 +26,7 @@
 import android.content.pm.ParceledListSlice;
 import android.graphics.drawable.Drawable;
 import android.os.ServiceManager;
+import android.os.UserHandle;
 import android.util.Log;
 
 import com.android.settingslib.Utils;
@@ -48,6 +49,8 @@
         }
         row.icon = app.loadIcon(pm);
         row.banned = getNotificationsBanned(row.pkg, row.uid);
+        row.showBadge = canShowBadge(row.pkg, row.uid);
+        row.userId = UserHandle.getUserId(row.uid);
         return row;
     }
 
@@ -87,6 +90,25 @@
         }
     }
 
+    public boolean canShowBadge(String pkg, int uid) {
+        try {
+            return sINM.canShowBadge(pkg, uid);
+        } catch (Exception e) {
+            Log.w(TAG, "Error calling NoMan", e);
+            return false;
+        }
+    }
+
+    public boolean setShowBadge(String pkg, int uid, boolean showBadge) {
+        try {
+            sINM.setShowBadge(pkg, uid, showBadge);
+            return true;
+        } catch (Exception e) {
+            Log.w(TAG, "Error calling NoMan", e);
+            return false;
+        }
+    }
+
     public NotificationChannel getChannel(String pkg, int uid, String channelId) {
         if (channelId == null) {
             return null;
@@ -129,6 +151,8 @@
         public boolean banned;
         public boolean first;  // first app in section
         public boolean systemApp;
+        public boolean showBadge;
+        public int userId;
     }
 
     public static class ChannelRow extends AppRow {
diff --git a/src/com/android/settings/notification/NotificationSettingsBase.java b/src/com/android/settings/notification/NotificationSettingsBase.java
index f3e4390..1535269 100644
--- a/src/com/android/settings/notification/NotificationSettingsBase.java
+++ b/src/com/android/settings/notification/NotificationSettingsBase.java
@@ -16,36 +16,28 @@
 
 package com.android.settings.notification;
 
-import com.android.internal.widget.LockPatternUtils;
 import com.android.settings.R;
 import com.android.settings.SettingsPreferenceFragment;
 import com.android.settings.applications.AppInfoBase;
 import com.android.settingslib.RestrictedLockUtils;
 import com.android.settingslib.RestrictedSwitchPreference;
 
-import android.app.Notification;
 import android.app.NotificationChannel;
 import android.app.NotificationManager;
-import android.app.admin.DevicePolicyManager;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.UserInfo;
 import android.os.Bundle;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
-import android.service.notification.NotificationListenerService.Ranking;
 import android.support.v7.preference.Preference;
 import android.text.TextUtils;
 import android.util.Log;
 import android.widget.Toast;
 
-import java.util.ArrayList;
-
-import static com.android.settings.notification.RestrictedDropDownPreference.RestrictedItem;
 import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
 
 abstract public class NotificationSettingsBase extends SettingsPreferenceFragment {
@@ -55,6 +47,7 @@
     protected static final String ARG_CHANNEL = "channel";
 
     protected static final String KEY_BLOCK = "block";
+    protected static final String KEY_BADGE = "badge";
 
     protected PackageManager mPm;
     protected UserManager mUm;
@@ -145,6 +138,7 @@
         mSuspendedAppsAdmin = RestrictedLockUtils.checkIfApplicationIsSuspended(
                 mContext, mPkg, mUserId);
         mBlock.setDisabledByAdmin(mSuspendedAppsAdmin);
+        mBadge.setDisabledByAdmin(mSuspendedAppsAdmin);
     }
 
     protected void setVisible(Preference p, boolean visible) {
diff --git a/src/com/android/settings/notification/NotificationStation.java b/src/com/android/settings/notification/NotificationStation.java
index 04897e4..50bcd95 100644
--- a/src/com/android/settings/notification/NotificationStation.java
+++ b/src/com/android/settings/notification/NotificationStation.java
@@ -56,7 +56,7 @@
 public class NotificationStation extends SettingsPreferenceFragment {
     private static final String TAG = NotificationStation.class.getSimpleName();
 
-    private static final boolean DEBUG = false;
+    private static final boolean DEBUG = true;
     private static final boolean DUMP_EXTRAS = true;
     private static final boolean DUMP_PARCEL = true;
     private Handler mHandler;
@@ -362,6 +362,11 @@
                                         .append(delim)
                                         .append(rank.getImportanceExplanation());
                             }
+                            sb.append("\n")
+                                    .append(bold(getString(
+                                            R.string.notification_log_details_badge)))
+                                    .append(delim)
+                                    .append(Boolean.toString(rank.canShowBadge()));
                         } else {
                             if (mRanking == null) {
                                 sb.append("\n")
diff --git a/src/com/android/settings/search/IndexDatabaseHelper.java b/src/com/android/settings/search/IndexDatabaseHelper.java
index 8de6c54..60378c2 100644
--- a/src/com/android/settings/search/IndexDatabaseHelper.java
+++ b/src/com/android/settings/search/IndexDatabaseHelper.java
@@ -28,12 +28,13 @@
     private static final String TAG = "IndexDatabaseHelper";
 
     private static final String DATABASE_NAME = "search_index.db";
-    private static final int DATABASE_VERSION = 116;
+    private static final int DATABASE_VERSION = 117;
 
     private static final String INDEX = "index";
 
     public interface Tables {
         String TABLE_PREFS_INDEX = "prefs_index";
+        String TABLE_SITE_MAP = "site_map";
         String TABLE_META_INDEX = "meta_index";
         String TABLE_SAVED_QUERIES = "saved_queries";
     }
@@ -72,6 +73,14 @@
         String TIME_STAMP = "timestamp";
     }
 
+    public interface SiteMapColumns {
+        String DOCID = "docid";
+        String PARENT_CLASS = "parent_class";
+        String CHILD_CLASS = "child_class";
+        String PARENT_TITLE = "parent_title";
+        String CHILD_TITLE = "child_title";
+    }
+
     private static final String CREATE_INDEX_TABLE =
             "CREATE VIRTUAL TABLE " + Tables.TABLE_PREFS_INDEX + " USING fts4" +
                     "(" +
@@ -132,6 +141,17 @@
                     SavedQueriesColumns.TIME_STAMP + " INTEGER" +
                     ")";
 
+    private static final String CREATE_SITE_MAP_TABLE =
+            "CREATE VIRTUAL TABLE " + Tables.TABLE_SITE_MAP + " USING fts4" +
+                    "(" +
+                    SiteMapColumns.PARENT_CLASS +
+                    ", " +
+                    SiteMapColumns.CHILD_CLASS +
+                    ", " +
+                    SiteMapColumns.PARENT_TITLE +
+                    ", " +
+                    SiteMapColumns.CHILD_TITLE +
+                    ")";
     private static final String INSERT_BUILD_VERSION =
             "INSERT INTO " + Tables.TABLE_META_INDEX +
                     " VALUES ('" + Build.VERSION.INCREMENTAL + "');";
@@ -164,6 +184,7 @@
         db.execSQL(CREATE_INDEX_TABLE);
         db.execSQL(CREATE_META_TABLE);
         db.execSQL(CREATE_SAVED_QUERIES_TABLE);
+        db.execSQL(CREATE_SITE_MAP_TABLE);
         db.execSQL(INSERT_BUILD_VERSION);
         Log.i(TAG, "Bootstrapped database");
     }
@@ -241,5 +262,6 @@
         db.execSQL("DROP TABLE IF EXISTS " + Tables.TABLE_META_INDEX);
         db.execSQL("DROP TABLE IF EXISTS " + Tables.TABLE_PREFS_INDEX);
         db.execSQL("DROP TABLE IF EXISTS " + Tables.TABLE_SAVED_QUERIES);
+        db.execSQL("DROP TABLE IF EXISTS " + Tables.TABLE_SITE_MAP);
     }
 }
diff --git a/src/com/android/settings/search2/CursorToSearchResultConverter.java b/src/com/android/settings/search2/CursorToSearchResultConverter.java
index 948397f..6549c18 100644
--- a/src/com/android/settings/search2/CursorToSearchResultConverter.java
+++ b/src/com/android/settings/search2/CursorToSearchResultConverter.java
@@ -28,8 +28,10 @@
 import android.os.Bundle;
 import android.text.TextUtils;
 import android.util.Log;
+
 import com.android.settings.SettingsActivity;
 import com.android.settings.Utils;
+import com.android.settings.dashboard.SiteMapManager;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -39,18 +41,20 @@
 import java.util.Map;
 import java.util.Set;
 
-import static com.android.settings.search2.DatabaseResultLoader.COLUMN_INDEX_ID;
-import static com.android.settings.search2.DatabaseResultLoader.COLUMN_INDEX_INTENT_ACTION_TARGET_CLASS;
-import static com.android.settings.search2.DatabaseResultLoader.COLUMN_INDEX_SCREEN_TITLE;
-import static com.android.settings.search2.DatabaseResultLoader.COLUMN_INDEX_TITLE;
-import static com.android.settings.search2.DatabaseResultLoader.COLUMN_INDEX_SUMMARY_ON;
 import static com.android.settings.search2.DatabaseResultLoader.COLUMN_INDEX_CLASS_NAME;
 import static com.android.settings.search2.DatabaseResultLoader.COLUMN_INDEX_ICON;
+import static com.android.settings.search2.DatabaseResultLoader.COLUMN_INDEX_ID;
 import static com.android.settings.search2.DatabaseResultLoader.COLUMN_INDEX_INTENT_ACTION;
-import static com.android.settings.search2.DatabaseResultLoader.COLUMN_INDEX_INTENT_ACTION_TARGET_PACKAGE;
+import static com.android.settings.search2.DatabaseResultLoader
+        .COLUMN_INDEX_INTENT_ACTION_TARGET_CLASS;
+import static com.android.settings.search2.DatabaseResultLoader
+        .COLUMN_INDEX_INTENT_ACTION_TARGET_PACKAGE;
 import static com.android.settings.search2.DatabaseResultLoader.COLUMN_INDEX_KEY;
-import static com.android.settings.search2.DatabaseResultLoader.COLUMN_INDEX_PAYLOAD_TYPE;
 import static com.android.settings.search2.DatabaseResultLoader.COLUMN_INDEX_PAYLOAD;
+import static com.android.settings.search2.DatabaseResultLoader.COLUMN_INDEX_PAYLOAD_TYPE;
+import static com.android.settings.search2.DatabaseResultLoader.COLUMN_INDEX_SCREEN_TITLE;
+import static com.android.settings.search2.DatabaseResultLoader.COLUMN_INDEX_SUMMARY_ON;
+import static com.android.settings.search2.DatabaseResultLoader.COLUMN_INDEX_TITLE;
 
 /**
  * Controller to Build search results from {@link Cursor} Objects.
@@ -78,7 +82,8 @@
         mQueryText = queryText;
     }
 
-    public List<SearchResult> convertCursor(Cursor cursorResults, int baseRank) {
+    public List<SearchResult> convertCursor(SiteMapManager sitemapManager,
+            Cursor cursorResults, int baseRank) {
         if (cursorResults == null) {
             return null;
         }
@@ -86,8 +91,8 @@
         final List<SearchResult> results = new ArrayList<>();
 
         while (cursorResults.moveToNext()) {
-            SearchResult result = buildSingleSearchResultFromCursor(contextMap, cursorResults,
-                    baseRank);
+            SearchResult result = buildSingleSearchResultFromCursor(sitemapManager,
+                    contextMap, cursorResults, baseRank);
             if (result != null) {
                 results.add(result);
             }
@@ -96,8 +101,8 @@
         return results;
     }
 
-    private SearchResult buildSingleSearchResultFromCursor(Map<String, Context> contextMap,
-            Cursor cursor, int baseRank) {
+    private SearchResult buildSingleSearchResultFromCursor(SiteMapManager sitemapManager,
+            Map<String, Context> contextMap, Cursor cursor, int baseRank) {
         final String docId = cursor.getString(COLUMN_INDEX_ID);
         /* Make sure that this result has not yet been added as a result. Checking the docID
            covers the case of multiple queries matching the same row, but we need to also to check
@@ -128,7 +133,7 @@
             return null;
         }
 
-        final List<String> breadcrumbs = getBreadcrumbs(cursor);
+        final List<String> breadcrumbs = getBreadcrumbs(sitemapManager, cursor);
         final int rank = getRank(breadcrumbs, baseRank);
 
         final SearchResult.Builder builder = new SearchResult.Builder();
@@ -210,12 +215,11 @@
         return null;
     }
 
-    private List<String> getBreadcrumbs(Cursor cursor) {
-        final List<String> breadcrumbs = new ArrayList<>();
+    private List<String> getBreadcrumbs(SiteMapManager siteMapManager, Cursor cursor) {
         final String screenTitle = cursor.getString(COLUMN_INDEX_SCREEN_TITLE);
-        if (!TextUtils.isEmpty(screenTitle)) {
-            breadcrumbs.add(screenTitle);
-        }
+        final String screenClass = cursor.getString(COLUMN_INDEX_CLASS_NAME);
+        final List<String> breadcrumbs = siteMapManager.buildBreadCrumb(mContext, screenClass,
+                screenTitle);
         return breadcrumbs;
     }
 
diff --git a/src/com/android/settings/search2/DatabaseIndexingManager.java b/src/com/android/settings/search2/DatabaseIndexingManager.java
index 073e202..d7c8746 100644
--- a/src/com/android/settings/search2/DatabaseIndexingManager.java
+++ b/src/com/android/settings/search2/DatabaseIndexingManager.java
@@ -34,7 +34,6 @@
 import android.provider.SearchIndexableResource;
 import android.provider.SearchIndexablesContract;
 import android.text.TextUtils;
-import android.util.ArrayMap;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.util.Xml;
@@ -517,10 +516,7 @@
                 nonIndexableKeys.addAll(resNonIndxableKeys);
             }
 
-            indexFromResource(sir.context, database, localeStr,
-                    sir.xmlResId, sir.className, sir.iconResId, sir.rank,
-                    sir.intentAction, sir.intentTargetPackage, sir.intentTargetClass,
-                    nonIndexableKeys);
+            indexFromResource(database, localeStr, sir, nonIndexableKeys);
         } else {
             if (TextUtils.isEmpty(sir.className)) {
                 Log.w(LOG_TAG, "Cannot index an empty Search Provider name!");
@@ -544,20 +540,17 @@
                     nonIndexableKeys.addAll(providerNonIndexableKeys);
                 }
 
-                indexFromProvider(mContext, database, localeStr, provider, sir.className,
-                        sir.iconResId, sir.rank, sir.enabled, nonIndexableKeys);
+                indexFromProvider(database, localeStr, provider, sir, nonIndexableKeys);
             }
         }
     }
 
-    private void indexFromResource(Context context, SQLiteDatabase database, String localeStr,
-            int xmlResId, String fragmentName, int iconResId, int rank,
-            String intentAction, String intentTargetPackage, String intentTargetClass,
-            List<String> nonIndexableKeys) {
-
+    private void indexFromResource(SQLiteDatabase database, String localeStr,
+            SearchIndexableResource sir, List<String> nonIndexableKeys) {
+        final Context context = sir.context;
         XmlResourceParser parser = null;
         try {
-            parser = context.getResources().getXml(xmlResId);
+            parser = context.getResources().getXml(sir.xmlResId);
 
             int type;
             while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
@@ -582,39 +575,47 @@
             String title;
             String summary;
             String keywords;
+            String childFragment;
             ResultPayload payload;
+            boolean enabled;
+            final String fragmentName = sir.className;
+            final int iconResId = sir.iconResId;
+            final int rank = sir.rank;
+            final String intentAction = sir.intentAction;
+            final String intentTargetPackage = sir.intentTargetPackage;
+            final String intentTargetClass = sir.intentTargetClass;
 
-            ArrayMap<String, PreferenceController> controllerUriMap = null;
+            Map<String, PreferenceController> controllerUriMap = null;
 
             if (fragmentName != null) {
-                controllerUriMap = (ArrayMap) DatabaseIndexingUtils
+                controllerUriMap = DatabaseIndexingUtils
                         .getPreferenceControllerUriMap(fragmentName, context);
             }
 
             // Insert rows for the main PreferenceScreen node. Rewrite the data for removing
             // hyphens.
-            if (!nonIndexableKeys.contains(key)) {
-                title = XmlParserUtils.getDataTitle(context, attrs);
-                summary = XmlParserUtils.getDataSummary(context, attrs);
-                keywords = XmlParserUtils.getDataKeywords(context, attrs);
 
-                DatabaseRow.Builder builder = new DatabaseRow.Builder();
-                builder.setLocale(localeStr)
-                        .setEntries(null)
-                        .setClassName(fragmentName)
-                        .setScreenTitle(screenTitle)
-                        .setIconResId(iconResId)
-                        .setRank(rank)
-                        .setIntentAction(intentAction)
-                        .setIntentTargetPackage(intentTargetPackage)
-                        .setIntentTargetClass(intentTargetClass)
-                        .setEnabled(true)
-                        .setKey(key)
-                        .setUserId(-1 /* default user id */);
+            title = XmlParserUtils.getDataTitle(context, attrs);
+            summary = XmlParserUtils.getDataSummary(context, attrs);
+            keywords = XmlParserUtils.getDataKeywords(context, attrs);
+            enabled = !nonIndexableKeys.contains(key);
 
-                updateOneRowWithFilteredData(database, builder, title, summary,
-                        null /* summary off */, keywords);
-            }
+            DatabaseRow.Builder builder = new DatabaseRow.Builder();
+            builder.setLocale(localeStr)
+                    .setEntries(null)
+                    .setClassName(fragmentName)
+                    .setScreenTitle(screenTitle)
+                    .setIconResId(iconResId)
+                    .setRank(rank)
+                    .setIntentAction(intentAction)
+                    .setIntentTargetPackage(intentTargetPackage)
+                    .setIntentTargetClass(intentTargetClass)
+                    .setEnabled(enabled)
+                    .setKey(key)
+                    .setUserId(-1 /* default user id */);
+
+            updateOneRowWithFilteredData(database, builder, title, summary,
+                    null /* summary off */, keywords);
 
             while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
                     && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
@@ -625,14 +626,11 @@
                 nodeName = parser.getName();
 
                 key = XmlParserUtils.getDataKey(context, attrs);
-                if (nonIndexableKeys.contains(key)) {
-                    continue;
-                }
-
+                enabled = ! nonIndexableKeys.contains(key);
                 title = XmlParserUtils.getDataTitle(context, attrs);
                 keywords = XmlParserUtils.getDataKeywords(context, attrs);
 
-                DatabaseRow.Builder builder = new DatabaseRow.Builder();
+                builder = new DatabaseRow.Builder();
                 builder.setLocale(localeStr)
                         .setClassName(fragmentName)
                         .setScreenTitle(screenTitle)
@@ -641,7 +639,7 @@
                         .setIntentAction(intentAction)
                         .setIntentTargetPackage(intentTargetPackage)
                         .setIntentTargetClass(intentTargetClass)
-                        .setEnabled(true)
+                        .setEnabled(enabled)
                         .setKey(key)
                         .setUserId(-1 /* default user id */);
 
@@ -655,8 +653,10 @@
                     }
 
                     payload = DatabaseIndexingUtils.getPayloadFromUriMap(controllerUriMap, key);
+                    childFragment = XmlParserUtils.getDataChildFragment(context, attrs);
 
                     builder.setEntries(entries)
+                            .setChildClassName(childFragment)
                             .setPayload(payload);
 
                     // Insert rows for the child nodes of PreferenceScreen
@@ -683,16 +683,21 @@
         }
     }
 
-    private void indexFromProvider(Context context, SQLiteDatabase database, String localeStr,
-            Indexable.SearchIndexProvider provider, String className, int iconResId, int rank,
-            boolean enabled, List<String> nonIndexableKeys) {
+    private void indexFromProvider(SQLiteDatabase database, String localeStr,
+            Indexable.SearchIndexProvider provider, SearchIndexableResource sir,
+            List<String> nonIndexableKeys) {
+
+        final String className = sir.className;
+        final int iconResId = sir.iconResId;
+        final int rank = sir.rank;
 
         if (provider == null) {
             Log.w(LOG_TAG, "Cannot find provider: " + className);
             return;
         }
 
-        final List<SearchIndexableRaw> rawList = provider.getRawDataToIndex(context, enabled);
+        final List<SearchIndexableRaw> rawList = provider.getRawDataToIndex(mContext,
+                true /* enabled */);
 
         if (rawList != null) {
 
@@ -704,10 +709,7 @@
                 if (!raw.locale.toString().equalsIgnoreCase(localeStr)) {
                     continue;
                 }
-
-                if (nonIndexableKeys.contains(raw.key)) {
-                    continue;
-                }
+                boolean enabled = !nonIndexableKeys.contains(raw.key);
 
                 DatabaseRow.Builder builder = new DatabaseRow.Builder();
                 builder.setLocale(localeStr)
@@ -719,7 +721,7 @@
                         .setIntentAction(raw.intentAction)
                         .setIntentTargetPackage(raw.intentTargetPackage)
                         .setIntentTargetClass(raw.intentTargetClass)
-                        .setEnabled(raw.enabled)
+                        .setEnabled(enabled)
                         .setKey(raw.key)
                         .setUserId(raw.userId);
 
@@ -729,7 +731,7 @@
         }
 
         final List<SearchIndexableResource> resList =
-                provider.getXmlResourcesToIndex(context, enabled);
+                provider.getXmlResourcesToIndex(mContext, true);
         if (resList != null) {
             final int resSize = resList.size();
             for (int i = 0; i < resSize; i++) {
@@ -740,15 +742,10 @@
                     continue;
                 }
 
-                final int itemIconResId = (item.iconResId == 0) ? iconResId : item.iconResId;
-                final int itemRank = (item.rank == 0) ? rank : item.rank;
-                String itemClassName = (TextUtils.isEmpty(item.className))
-                        ? className : item.className;
+                item.iconResId = (item.iconResId == 0) ? iconResId : item.iconResId;
+                item.className = (TextUtils.isEmpty(item.className)) ? className : item.className;
 
-                indexFromResource(context, database, localeStr,
-                        item.xmlResId, itemClassName, itemIconResId, itemRank,
-                        item.intentAction, item.intentTargetPackage,
-                        item.intentTargetClass, nonIndexableKeys);
+                indexFromResource(database, localeStr, item, nonIndexableKeys);
             }
         }
     }
@@ -811,6 +808,18 @@
         values.put(IndexDatabaseHelper.IndexColumns.PAYLOAD, row.payload);
 
         database.replaceOrThrow(IndexDatabaseHelper.Tables.TABLE_PREFS_INDEX, null, values);
+
+        if (!TextUtils.isEmpty(row.className) && !TextUtils.isEmpty(row.childClassName)) {
+            ContentValues siteMapPair = new ContentValues();
+            final int pairDocId = Objects.hash(row.className, row.childClassName);
+            siteMapPair.put(IndexDatabaseHelper.SiteMapColumns.DOCID, pairDocId);
+            siteMapPair.put(IndexDatabaseHelper.SiteMapColumns.PARENT_CLASS, row.className);
+            siteMapPair.put(IndexDatabaseHelper.SiteMapColumns.PARENT_TITLE, row.screenTitle);
+            siteMapPair.put(IndexDatabaseHelper.SiteMapColumns.CHILD_CLASS, row.childClassName);
+            siteMapPair.put(IndexDatabaseHelper.SiteMapColumns.CHILD_TITLE, row.updatedTitle);
+
+            database.replaceOrThrow(IndexDatabaseHelper.Tables.TABLE_SITE_MAP, null, siteMapPair);
+        }
     }
 
     /**
@@ -950,6 +959,7 @@
         public final String normalizedSummaryOff;
         public final String entries;
         public final String className;
+        public final String childClassName;
         public final String screenTitle;
         public final int iconResId;
         public final int rank;
@@ -973,6 +983,7 @@
             normalizedSummaryOff = builder.mNormalizedSummaryOff;
             entries = builder.mEntries;
             className = builder.mClassName;
+            childClassName = builder.mChildClassName;
             screenTitle = builder.mScreenTitle;
             iconResId = builder.mIconResId;
             rank = builder.mRank;
@@ -1008,6 +1019,7 @@
             private String mNormalizedSummaryOff;
             private String mEntries;
             private String mClassName;
+            private String mChildClassName;
             private String mScreenTitle;
             private int mIconResId;
             private int mRank;
@@ -1067,6 +1079,11 @@
                 return this;
             }
 
+            public Builder setChildClassName(String childClassName) {
+                mChildClassName = childClassName;
+                return this;
+            }
+
             public Builder setScreenTitle(String screenTitle) {
                 mScreenTitle = screenTitle;
                 return this;
@@ -1142,4 +1159,4 @@
             }
         }
     }
-}
+}
\ No newline at end of file
diff --git a/src/com/android/settings/search2/DatabaseIndexingUtils.java b/src/com/android/settings/search2/DatabaseIndexingUtils.java
index 9fdf732..bd06ef3 100644
--- a/src/com/android/settings/search2/DatabaseIndexingUtils.java
+++ b/src/com/android/settings/search2/DatabaseIndexingUtils.java
@@ -105,7 +105,7 @@
      * @return The Payload from the {@link PreferenceController} specified by the key, if it exists.
      * Otherwise null.
      */
-    public static ResultPayload getPayloadFromUriMap(ArrayMap<String, PreferenceController> uriMap,
+    public static ResultPayload getPayloadFromUriMap(Map<String, PreferenceController> uriMap,
             String key) {
         if (uriMap == null) {
             return null;
diff --git a/src/com/android/settings/search2/DatabaseResultLoader.java b/src/com/android/settings/search2/DatabaseResultLoader.java
index f7acd25..0bbded2 100644
--- a/src/com/android/settings/search2/DatabaseResultLoader.java
+++ b/src/com/android/settings/search2/DatabaseResultLoader.java
@@ -20,6 +20,8 @@
 import android.database.Cursor;
 import android.database.sqlite.SQLiteDatabase;
 
+import com.android.settings.dashboard.SiteMapManager;
+import com.android.settings.overlay.FeatureFactory;
 import com.android.settings.search.IndexDatabaseHelper;
 import com.android.settings.utils.AsyncLoader;
 
@@ -35,11 +37,6 @@
  */
 public class DatabaseResultLoader extends AsyncLoader<List<SearchResult>> {
     private static final String LOG = "DatabaseResultLoader";
-    private final String mQueryText;
-
-    protected final SQLiteDatabase mDatabase;
-
-    private final CursorToSearchResultConverter mConverter;
 
     /* These indices are used to match the columns of the this loader's SELECT statement.
      These are not necessarily the same order nor similar coverage as the schema defined in
@@ -99,8 +96,15 @@
      */
     private static final int[] BASE_RANKS = {1, 4, 7};
 
+    private final String mQueryText;
+    private final SQLiteDatabase mDatabase;
+    private final CursorToSearchResultConverter mConverter;
+    private final SiteMapManager mSiteMapManager;
+
     public DatabaseResultLoader(Context context, String queryText) {
         super(context);
+        mSiteMapManager = FeatureFactory.getFactory(context)
+                .getSearchFeatureProvider().getSiteMapManager();
         mDatabase = IndexDatabaseHelper.getInstance(context).getReadableDatabase();
         mQueryText = cleanQuery(queryText);
         mConverter = new CursorToSearchResultConverter(context, mQueryText);
@@ -125,7 +129,6 @@
         secondaryResults = query(MATCH_COLUMNS_SECONDARY, BASE_RANKS[1]);
         tertiaryResults = query(MATCH_COLUMNS_TERTIARY, BASE_RANKS[2]);
 
-
         final List<SearchResult> results = new ArrayList<>(primaryResults.size()
                 + secondaryResults.size()
                 + tertiaryResults.size());
@@ -144,7 +147,7 @@
 
         final Cursor resultCursor = mDatabase.query(TABLE_PREFS_INDEX, SELECT_COLUMNS, whereClause,
                 selection, null, null, null);
-        return mConverter.convertCursor(resultCursor, baseRank);
+        return mConverter.convertCursor(mSiteMapManager, resultCursor, baseRank);
     }
 
     @Override
@@ -155,6 +158,7 @@
 
     /**
      * A generic method to make the query suitable for searching the database.
+     *
      * @return the cleaned query string
      */
     private static String cleanQuery(String query) {
@@ -162,7 +166,7 @@
     }
 
     private static String buildWhereClause(String[] matchColumns) {
-        StringBuilder sb = new StringBuilder(" ");
+        StringBuilder sb = new StringBuilder(" (");
         final int count = matchColumns.length;
         for (int n = 0; n < count; n++) {
             sb.append(matchColumns[n]);
@@ -171,6 +175,7 @@
                 sb.append(" OR ");
             }
         }
+        sb.append(") AND enabled = 1");
         return sb.toString();
     }
 }
diff --git a/src/com/android/settings/search2/InstalledAppResultLoader.java b/src/com/android/settings/search2/InstalledAppResultLoader.java
index 17bb157..0b828a3 100644
--- a/src/com/android/settings/search2/InstalledAppResultLoader.java
+++ b/src/com/android/settings/search2/InstalledAppResultLoader.java
@@ -29,7 +29,10 @@
 import android.text.TextUtils;
 
 import com.android.settings.R;
+import com.android.settings.applications.ManageApplications;
 import com.android.settings.applications.PackageManagerWrapper;
+import com.android.settings.dashboard.SiteMapManager;
+import com.android.settings.overlay.FeatureFactory;
 import com.android.settings.utils.AsyncLoader;
 
 import java.util.ArrayList;
@@ -46,7 +49,8 @@
     private static final Intent LAUNCHER_PROBE = new Intent(Intent.ACTION_MAIN)
             .addCategory(Intent.CATEGORY_LAUNCHER);
 
-    private final List<String> mBreadcrumb;
+    private List<String> mBreadcrumb;
+    private SiteMapManager mSiteMapManager;
     private final String mQuery;
     private final UserManager mUserManager;
     private final PackageManagerWrapper mPackageManager;
@@ -55,9 +59,8 @@
     public InstalledAppResultLoader(Context context, PackageManagerWrapper pmWrapper,
             String query) {
         super(context);
-        mBreadcrumb = new ArrayList<>();
-        mBreadcrumb.add(context.getString(R.string.app_and_notification_dashboard_title));
-        mBreadcrumb.add(context.getString(R.string.applications_settings));
+        mSiteMapManager = FeatureFactory.getFactory(context)
+                .getSearchFeatureProvider().getSiteMapManager();
         mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
         mPackageManager = pmWrapper;
         mQuery = query;
@@ -92,7 +95,7 @@
                 builder.addIcon(info.loadIcon(pm))
                         .addTitle(info.loadLabel(pm))
                         .addRank(wordDiff)
-                        .addBreadcrumbs(mBreadcrumb)
+                        .addBreadcrumbs(getBreadCrumb())
                         .addPayload(new IntentPayload(intent));
                 results.add(builder.build());
             }
@@ -162,4 +165,14 @@
         // to infinity.
         return valueText.length - queryTokens.length;
     }
+
+    private List<String> getBreadCrumb() {
+        if (mBreadcrumb == null || mBreadcrumb.isEmpty()) {
+            final Context context = getContext();
+            mBreadcrumb = mSiteMapManager.buildBreadCrumb(
+                    context, ManageApplications.class.getName(),
+                    context.getString(R.string.applications_settings));
+        }
+        return mBreadcrumb;
+    }
 }
diff --git a/src/com/android/settings/search2/SearchFeatureProvider.java b/src/com/android/settings/search2/SearchFeatureProvider.java
index 91a1444..a9be5a1 100644
--- a/src/com/android/settings/search2/SearchFeatureProvider.java
+++ b/src/com/android/settings/search2/SearchFeatureProvider.java
@@ -19,6 +19,8 @@
 import android.content.Context;
 import android.view.Menu;
 
+import com.android.settings.dashboard.SiteMapManager;
+
 /**
  * FeatureProvider for Settings Search
  */
@@ -58,6 +60,11 @@
     DatabaseIndexingManager getIndexingManager(Context context);
 
     /**
+     * Returns the manager for looking up breadcrumbs.
+     */
+    SiteMapManager getSiteMapManager();
+
+    /**
      * Updates the Settings indexes
      */
     void updateIndex(Context context);
diff --git a/src/com/android/settings/search2/SearchFeatureProviderImpl.java b/src/com/android/settings/search2/SearchFeatureProviderImpl.java
index 5d62412..b575b15 100644
--- a/src/com/android/settings/search2/SearchFeatureProviderImpl.java
+++ b/src/com/android/settings/search2/SearchFeatureProviderImpl.java
@@ -25,6 +25,7 @@
 
 import com.android.settings.R;
 import com.android.settings.applications.PackageManagerWrapperImpl;
+import com.android.settings.dashboard.SiteMapManager;
 import com.android.settings.search.Index;
 
 /**
@@ -35,6 +36,7 @@
     private static final String TAG = "SearchFeatureProvider";
 
     private DatabaseIndexingManager mDatabaseIndexingManager;
+    private SiteMapManager mSiteMapManager;
 
     @Override
     public boolean isEnabled(Context context) {
@@ -86,6 +88,13 @@
         return mDatabaseIndexingManager;
     }
 
+    public SiteMapManager getSiteMapManager() {
+        if (mSiteMapManager == null) {
+            mSiteMapManager = new SiteMapManager();
+        }
+        return mSiteMapManager;
+    }
+
     @Override
     public void updateIndex(Context context) {
         long indexStartTime = System.currentTimeMillis();
diff --git a/src/com/android/settings/search2/XmlParserUtils.java b/src/com/android/settings/search2/XmlParserUtils.java
index 748d4b0..90b1c1f 100644
--- a/src/com/android/settings/search2/XmlParserUtils.java
+++ b/src/com/android/settings/search2/XmlParserUtils.java
@@ -24,9 +24,6 @@
 
 import com.android.settings.R;
 
-import java.text.Normalizer;
-import java.util.regex.Pattern;
-
 /**
  * Utility class to parse elements of XML preferences
  */
@@ -74,6 +71,14 @@
         return getData(context, attrs, R.styleable.Preference, R.styleable.Preference_keywords);
     }
 
+    /**
+     * Returns the fragment name if this preference launches a child fragment.
+     */
+    public static String getDataChildFragment(Context context, AttributeSet attrs) {
+        return getData(context, attrs, R.styleable.Preference,
+                R.styleable.Preference_android_fragment);
+    }
+
     private static String getData(Context context, AttributeSet set, int[] attrs, int resId) {
         final TypedArray sa = context.obtainStyledAttributes(set, attrs);
         final TypedValue tv = sa.peekValue(resId);
diff --git a/tests/robotests/assets/grandfather_not_implementing_indexable b/tests/robotests/assets/grandfather_not_implementing_indexable
index a178596..d774779 100644
--- a/tests/robotests/assets/grandfather_not_implementing_indexable
+++ b/tests/robotests/assets/grandfather_not_implementing_indexable
@@ -91,3 +91,4 @@
 com.android.settings.localepicker.LocaleListEditor
 com.android.settings.qstile.DevelopmentTileConfigActivity$DevelopmentTileConfigFragment
 com.android.settings.applications.ExternalSourcesDetails
+com.android.settings.applications.PictureInPictureSettings
diff --git a/tests/robotests/src/com/android/settings/BackupSettingsHelperTest.java b/tests/robotests/src/com/android/settings/BackupSettingsHelperTest.java
new file mode 100644
index 0000000..b14debc
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/BackupSettingsHelperTest.java
@@ -0,0 +1,188 @@
+/*
+ * 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.app.backup.BackupManager;
+import android.app.backup.IBackupManager;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+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.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+
+
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION,
+        shadows = {BackupSettingsHelperTest.ShadowBackupManagerStub.class})
+public class BackupSettingsHelperTest {
+
+    private BackupSettingsHelper mBackupSettingsHelper;
+
+    @Mock
+    private static IBackupManager mBackupManager;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        when(mBackupManager.getCurrentTransport()).thenReturn("test_transport");
+        mBackupSettingsHelper = new BackupSettingsHelper();
+    }
+
+    @Test
+    public void testGetIntentFromBackupTransport() throws Exception {
+        Intent intent = new Intent();
+
+        when(mBackupManager.getDataManagementIntent(anyString())).thenReturn(intent);
+
+        Intent backupIntent = mBackupSettingsHelper.getIntentForBackupSettings();
+
+        verify(mBackupManager).getDataManagementIntent(anyString());
+    }
+
+    @Test
+    public void testGetIntentFromBackupTransport_WithIntent() throws Exception {
+        Intent intent = mock(Intent.class);
+
+        when(mBackupManager.getDataManagementIntent(anyString())).thenReturn(intent);
+
+        Intent backupIntent = mBackupSettingsHelper.getIntentForBackupSettings();
+
+        assertThat(backupIntent).isEqualTo(intent);
+    }
+
+    @Test
+    public void testGetIntentFromBackupTransport_WithNullIntent() throws Exception {
+        when(mBackupManager.getDataManagementIntent(anyString())).thenReturn(null);
+
+        Intent backupIntent = mBackupSettingsHelper.getIntentForBackupSettings();
+
+        assertThat(backupIntent).isNull();
+    }
+
+    @Test
+    public void testGetIntentFromBackupTransport_RemoteException() throws Exception {
+        when(mBackupManager.getDataManagementIntent(anyString())).thenThrow(new RemoteException());
+
+        Intent backupIntent = mBackupSettingsHelper.getIntentForBackupSettings();
+
+        assertThat(backupIntent).isNull();
+    }
+
+    @Test
+    public void testGetIntentFromBackupTransport_BackupEnabled() throws Exception {
+        Intent intent = new Intent("test_intent");
+
+        when(mBackupManager.getDataManagementIntent(anyString())).thenReturn(intent);
+        when(mBackupManager.isBackupServiceActive(anyInt())).thenReturn(true);
+
+        Intent backupIntent = mBackupSettingsHelper.getIntentForBackupSettings();
+
+        assertThat(backupIntent.getExtras().get(BackupManager.EXTRA_BACKUP_SERVICES_AVAILABLE))
+                .isEqualTo(true);
+    }
+
+    @Test
+    public void testGetIntentFromBackupTransport_BackupDisabled() throws Exception {
+        Intent intent = new Intent("test_intent");
+
+        when(mBackupManager.getDataManagementIntent(anyString())).thenReturn(intent);
+        when(mBackupManager.isBackupServiceActive(anyInt())).thenReturn(false);
+
+        Intent backupIntent = mBackupSettingsHelper.getIntentForBackupSettings();
+
+        assertThat(backupIntent.getExtras().get(BackupManager.EXTRA_BACKUP_SERVICES_AVAILABLE))
+                .isEqualTo(false);
+    }
+
+    @Test
+    public void testGetIntentFromBackupTransport_BackupStatusException() throws Exception {
+        Intent intent = new Intent("test_intent");
+
+        when(mBackupManager.getDataManagementIntent(anyString())).thenReturn(intent);
+        when(mBackupManager.isBackupServiceActive(anyInt())).thenThrow(new RemoteException());
+
+        Intent backupIntent = mBackupSettingsHelper.getIntentForBackupSettings();
+
+        assertThat(backupIntent.getExtras().get(BackupManager.EXTRA_BACKUP_SERVICES_AVAILABLE))
+                .isEqualTo(false);
+    }
+
+    @Test
+    public void testIsIntentProvidedByTransport_WithNullIntent() throws Exception {
+        when(mBackupManager.getDataManagementIntent(anyString())).thenReturn(null);
+
+        PackageManager packageManager = mock(PackageManager.class);
+
+        boolean isIntentProvided = mBackupSettingsHelper.isIntentProvidedByTransport(packageManager);
+
+        assertThat(isIntentProvided).isFalse();
+    }
+
+    @Test
+    public void testIsIntentProvidedByTransport_WithInvalidIntent() throws Exception {
+        Intent intent = mock(Intent.class);
+
+        when(mBackupManager.getDataManagementIntent(anyString())).thenReturn(intent);
+
+        PackageManager packageManager = mock(PackageManager.class);
+        when(intent.resolveActivity(packageManager)).thenReturn(null);
+
+        boolean isIntentProvided = mBackupSettingsHelper.isIntentProvidedByTransport(packageManager);
+
+        assertThat(isIntentProvided).isFalse();
+    }
+
+    @Test
+    public void testIsIntentProvidedByTransport_WithIntent() throws Exception {
+        Intent intent = mock(Intent.class);
+
+        when(mBackupManager.getDataManagementIntent(anyString())).thenReturn(intent);
+
+        PackageManager packageManager = mock(PackageManager.class);
+        when(intent.resolveActivity(packageManager)).thenReturn(mock(ComponentName.class));
+
+        boolean isIntentProvided = mBackupSettingsHelper.isIntentProvidedByTransport(packageManager);
+
+        assertThat(isIntentProvided).isTrue();
+    }
+
+    @Implements(IBackupManager.Stub.class)
+    public static class ShadowBackupManagerStub {
+        @Implementation
+        public static IBackupManager asInterface(IBinder iBinder) {
+            return mBackupManager;
+        }
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/applications/PictureInPictureSettingsTest.java b/tests/robotests/src/com/android/settings/applications/PictureInPictureSettingsTest.java
new file mode 100644
index 0000000..daed00d
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/applications/PictureInPictureSettingsTest.java
@@ -0,0 +1,132 @@
+/*
+ * 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 android.content.pm.ActivityInfo;
+import android.content.pm.PackageInfo;
+
+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.util.ReflectionHelpers;
+
+import java.lang.reflect.Field;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.argThat;
+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;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class PictureInPictureSettingsTest {
+
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private Context mContext;
+
+    private FakeFeatureFactory mFeatureFactory;
+    private PictureInPictureSettings mFragment;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        FakeFeatureFactory.setupForTest(mContext);
+        mFeatureFactory = (FakeFeatureFactory) FakeFeatureFactory.getFactory(mContext);
+        mFragment = new PictureInPictureSettings();
+    }
+
+    @Test
+    public void testIgnoredApp() {
+        for (String ignoredPackage : mFragment.IGNORE_PACKAGE_LIST) {
+            assertThat(checkPackageHasPictureInPictureActivities(ignoredPackage, true))
+                            .isFalse();
+        }
+    }
+
+    @Test
+    public void testNonPippableApp() {
+        assertThat(checkPackageHasPictureInPictureActivities("com.android.dummypackage")).isFalse();
+        assertThat(checkPackageHasPictureInPictureActivities("com.android.dummypackage",
+                false, false, false)).isFalse();
+    }
+
+    @Test
+    public void testPippableApp() {
+        assertThat(checkPackageHasPictureInPictureActivities("com.android.dummypackage",
+                true)).isTrue();
+        assertThat(checkPackageHasPictureInPictureActivities("com.android.dummypackage",
+                false, true)).isTrue();
+        assertThat(checkPackageHasPictureInPictureActivities("com.android.dummypackage",
+                true, false)).isTrue();
+    }
+
+    @Test
+    public void logSpecialPermissionChange() {
+        mFragment.logSpecialPermissionChange(true, "app");
+        verify(mFeatureFactory.metricsFeatureProvider).action(any(Context.class),
+                eq(MetricsProto.MetricsEvent.APP_PICTURE_IN_PICTURE_ON_HIDE_ALLOW), eq("app"));
+
+        mFragment.logSpecialPermissionChange(false, "app");
+        verify(mFeatureFactory.metricsFeatureProvider).action(any(Context.class),
+                eq(MetricsProto.MetricsEvent.APP_PICTURE_IN_PICTURE_ON_HIDE_DENY), eq("app"));
+    }
+
+    private boolean checkPackageHasPictureInPictureActivities(String packageName,
+            boolean... resizeableActivityState) {
+        ActivityInfoWrapper[] activities = null;
+        if (resizeableActivityState.length > 0) {
+            activities = new ActivityInfoWrapper[resizeableActivityState.length];
+            for (int i = 0; i < activities.length; i++) {
+                activities[i] = new MockActivityInfo(resizeableActivityState[i]
+                        ? ActivityInfo.RESIZE_MODE_RESIZEABLE_AND_PIPABLE
+                        : ActivityInfo.RESIZE_MODE_UNRESIZEABLE);
+            }
+        }
+        return PictureInPictureSettings.checkPackageHasPictureInPictureActivities(packageName,
+                activities);
+    }
+
+    private class MockActivityInfo implements ActivityInfoWrapper {
+
+        private int mResizeMode;
+
+        public MockActivityInfo(int resizeMode) {
+            mResizeMode = resizeMode;
+        }
+
+        @Override
+        public int getResizeMode() {
+            return mResizeMode;
+        }
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/search/DatabaseIndexingManagerTest.java b/tests/robotests/src/com/android/settings/search/DatabaseIndexingManagerTest.java
index e55dc10..e89f009 100644
--- a/tests/robotests/src/com/android/settings/search/DatabaseIndexingManagerTest.java
+++ b/tests/robotests/src/com/android/settings/search/DatabaseIndexingManagerTest.java
@@ -25,6 +25,7 @@
 import com.android.settings.R;
 import com.android.settings.SettingsRobolectricTestRunner;
 import com.android.settings.TestConfig;
+import com.android.settings.search.IndexDatabaseHelper.SiteMapColumns;
 import com.android.settings.search2.DatabaseIndexingManager;
 import com.android.settings.testutils.DatabaseTestUtils;
 
@@ -40,7 +41,9 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Locale;
+import java.util.Map;
 
+import static com.android.settings.dashboard.SiteMapManager.SITE_MAP_COLUMNS;
 import static com.google.common.truth.Truth.assertThat;
 import static org.mockito.Mockito.spy;
 
@@ -49,7 +52,7 @@
 public class DatabaseIndexingManagerTest {
     private final String localeStr = "en_US";
 
-    private final int rank = 42;
+    private final int rank = 8;
     private final String title = "title\u2011title";
     private final String updatedTitle = "title-title";
     private final String normalizedTitle = "titletitle";
@@ -57,7 +60,7 @@
     private final String updatedSummaryOn = "summary-on";
     private final String normalizedSummaryOn = "summaryon";
     private final String summaryOff = "summary\u2011off";
-    private final String updatedSummaryOff ="summary-off";
+    private final String updatedSummaryOff = "summary-off";
     private final String normalizedSummaryOff = "summaryoff";
     private final String entries = "entries";
     private final String keywords = "keywords, keywordss, keywordsss";
@@ -94,7 +97,7 @@
         Cursor dbCursor = mDb.query("prefs_index", null, null, null, null, null, null);
         List<String> columnNames = new ArrayList<>(Arrays.asList(dbCursor.getColumnNames()));
         // Note that docid is not included.
-        List<String> expColumnNames = new ArrayList<>(Arrays.asList(new String[ ]{
+        List<String> expColumnNames = new ArrayList<>(Arrays.asList(new String[]{
                 "locale",
                 "data_rank",
                 "data_title",
@@ -211,6 +214,20 @@
     }
 
     @Test
+    public void testAddResourceWithNIKs_RowsInsertedDisabled() {
+        SearchIndexableResource resource = getFakeResource(R.xml.gesture_settings);
+        // Only add 2 of 6 items to be disabled.
+        String[] keys = {"gesture_double_tap_power", "gesture_swipe_down_fingerprint"};
+        Map<String, List<String>> niks = getNonIndexableKeys(keys);
+        mManager.indexOneSearchIndexableData(mDb, localeStr, resource, niks);
+
+        Cursor cursor = mDb.rawQuery("SELECT * FROM prefs_index WHERE enabled = 0", null);
+        assertThat(cursor.getCount()).isEqualTo(2);
+        cursor = mDb.rawQuery("SELECT * FROM prefs_index WHERE enabled = 1", null);
+        assertThat(cursor.getCount()).isEqualTo(4);
+    }
+
+    @Test
     public void testAddResourceHeader_RowsMatch() {
         SearchIndexableResource resource = getFakeResource(R.xml.application_settings);
         mManager.indexOneSearchIndexableData(mDb, localeStr, resource,
@@ -264,6 +281,29 @@
     }
 
     @Test
+    public void testAddResourceWithChildFragment_shouldUpdateSiteMapDb() {
+        SearchIndexableResource resource = getFakeResource(R.xml.network_and_internet);
+        mManager.indexOneSearchIndexableData(mDb, localeStr, resource,
+                new HashMap<>());
+        Cursor query = mDb.query(IndexDatabaseHelper.Tables.TABLE_SITE_MAP, SITE_MAP_COLUMNS,
+                null, null, null, null, null);
+        query.moveToPosition(-1);
+        int count = 0;
+        while (query.moveToNext()) {
+            count++;
+            assertThat(query.getString(query.getColumnIndex(SiteMapColumns.PARENT_CLASS)))
+                    .isEqualTo(className);
+            assertThat(query.getString(query.getColumnIndex(SiteMapColumns.PARENT_TITLE)))
+                    .isEqualTo(mContext.getString(R.string.network_dashboard_title));
+            assertThat(query.getString(query.getColumnIndex(SiteMapColumns.CHILD_CLASS)))
+                    .isNotEmpty();
+            assertThat(query.getString(query.getColumnIndex(SiteMapColumns.CHILD_TITLE)))
+                    .isNotEmpty();
+        }
+        assertThat(count).isEqualTo(5);
+    }
+
+    @Test
     public void testAddResourceCustomSetting_RowsMatch() {
         SearchIndexableResource resource = getFakeResource(R.xml.gesture_settings);
         mManager.indexOneSearchIndexableData(mDb, localeStr, resource,
@@ -393,16 +433,18 @@
         // Normalized Title
         assertThat(cursor.getString(3)).isEqualTo("preferred install location");
         // Summary On
-        assertThat(cursor.getString(4)).isEqualTo("Change the preferred installation location for new apps");
+        assertThat(cursor.getString(4)).isEqualTo(
+                "Change the preferred installation location for new apps");
         // Summary On Normalized
-        assertThat(cursor.getString(5)).isEqualTo("change the preferred installation location for new apps");
+        assertThat(cursor.getString(5)).isEqualTo(
+                "change the preferred installation location for new apps");
         // Summary Off - only on for checkbox preferences
         assertThat(cursor.getString(6)).isEmpty();
         // Summary off normalized - only on for checkbox preferences
         assertThat(cursor.getString(7)).isEmpty();
         // Entries - only on for list preferences
         assertThat(cursor.getString(8)).isEqualTo("Internal device storage|Removable SD card|" +
-                        "Let the system decide|");
+                "Let the system decide|");
         // Keywords
         assertThat(cursor.getString(9)).isEmpty();
         // Screen Title
@@ -502,21 +544,19 @@
 
     @Test
     public void testResourceProvider_ResourceRowInserted() {
-        SearchIndexableResource resource = getFakeResource(R.xml.gesture_settings);
-        resource.xmlResId = 0;
+        SearchIndexableResource resource = getFakeResource(0);
         resource.className = "com.android.settings.LegalSettings";
 
         mManager.indexOneSearchIndexableData(mDb, localeStr, resource,
                 new HashMap<>());
         Cursor cursor = mDb.rawQuery("SELECT * FROM prefs_index", null);
-        assertThat(cursor.getCount()).isEqualTo(2);
+        assertThat(cursor.getCount()).isEqualTo(6);
     }
 
     @Test
     public void testResourceProvider_ResourceRowMatches() {
-        SearchIndexableResource resource = getFakeResource(R.xml.gesture_settings);
-        resource.xmlResId = 0;
-        resource.className = "com.android.settings.LegalSettings";
+        SearchIndexableResource resource = getFakeResource(0);
+        resource.className = "com.android.settings.display.ScreenZoomSettings";
 
         mManager.indexOneSearchIndexableData(mDb, localeStr, resource,
                 new HashMap<>());
@@ -528,9 +568,9 @@
         // Data Rank
         assertThat(cursor.getInt(1)).isEqualTo(rank);
         // Data Title
-        assertThat(cursor.getString(2)).isEqualTo("Legal information");
+        assertThat(cursor.getString(2)).isEqualTo("Display size");
         // Normalized Title
-        assertThat(cursor.getString(3)).isEqualTo("legal information");
+        assertThat(cursor.getString(3)).isEqualTo("display size");
         // Summary On
         assertThat(cursor.getString(4)).isEmpty();
         // Summary On Normalized
@@ -542,12 +582,13 @@
         // Entries - only on for list preferences
         assertThat(cursor.getString(8)).isNull();
         // Keywords
-        assertThat(cursor.getString(9)).isEmpty();
+        assertThat(cursor.getString(9)).isEqualTo(
+                "display density screen zoom scale scaling");
         // Screen Title
-        assertThat(cursor.getString(10)).isEqualTo("Legal information");
+        assertThat(cursor.getString(10)).isEqualTo("Display size");
         // Class Name
         assertThat(cursor.getString(11))
-                .isEqualTo("com.android.settings.LegalSettings");
+                .isEqualTo("com.android.settings.display.ScreenZoomSettings");
         // Icon
         assertThat(cursor.getInt(12)).isEqualTo(iconResId);
         // Intent Action
@@ -568,6 +609,20 @@
         assertThat(cursor.getBlob(20)).isNull();
     }
 
+    @Test
+    public void testResourceProvider_DisabledResourceRowsInserted() {
+        SearchIndexableResource resource = getFakeResource(0);
+        resource.className = "com.android.settings.LegalSettings";
+
+        mManager.indexOneSearchIndexableData(mDb, localeStr, resource,
+                new HashMap<String, List<String>>());
+
+        Cursor cursor = mDb.rawQuery("SELECT * FROM prefs_index WHERE enabled = 1", null);
+        assertThat(cursor.getCount()).isEqualTo(2);
+        cursor = mDb.rawQuery("SELECT * FROM prefs_index WHERE enabled = 0", null);
+        assertThat(cursor.getCount()).isEqualTo(4);
+    }
+
     // Util functions
 
     private SearchIndexableRaw getFakeRaw() {
@@ -609,4 +664,11 @@
         sir.enabled = enabled;
         return sir;
     }
-}
+
+    private Map<String, List<String>> getNonIndexableKeys(String[] keys) {
+        Map<String, List<String>> niks = new HashMap<>();
+        List<String> keysList = new ArrayList<>(Arrays.asList(keys));
+        niks.put(packageName, keysList);
+        return niks;
+    }
+}
\ No newline at end of file
diff --git a/tests/robotests/src/com/android/settings/search/DatabaseResultLoaderTest.java b/tests/robotests/src/com/android/settings/search/DatabaseResultLoaderTest.java
index 2b29a16..31e6e6c 100644
--- a/tests/robotests/src/com/android/settings/search/DatabaseResultLoaderTest.java
+++ b/tests/robotests/src/com/android/settings/search/DatabaseResultLoaderTest.java
@@ -23,31 +23,51 @@
 
 import com.android.settings.SettingsRobolectricTestRunner;
 import com.android.settings.TestConfig;
+import com.android.settings.dashboard.SiteMapManager;
 import com.android.settings.search2.DatabaseIndexingUtils;
 import com.android.settings.search2.DatabaseResultLoader;
 import com.android.settings.testutils.DatabaseTestUtils;
+import com.android.settings.testutils.FakeFeatureFactory;
 
 import org.junit.After;
 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.RuntimeEnvironment;
 import org.robolectric.annotation.Config;
 
 import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.times;
+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 DatabaseResultLoaderTest {
-    private Context mContext;
 
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private Context mMockContext;
+    @Mock
+    private SiteMapManager mSiteMapManager;
+    private Context mContext;
     private DatabaseResultLoader loader;
 
     SQLiteDatabase mDb;
 
     @Before
     public void setUp() {
+        MockitoAnnotations.initMocks(this);
         mContext = RuntimeEnvironment.application;
+        FakeFeatureFactory.setupForTest(mMockContext);
+        FakeFeatureFactory factory =
+                (FakeFeatureFactory) FakeFeatureFactory.getFactory(mMockContext);
+        when(factory.searchFeatureProvider.getSiteMapManager())
+                .thenReturn(mSiteMapManager);
         mDb = IndexDatabaseHelper.getInstance(mContext).getWritableDatabase();
         setUpDb();
     }
@@ -60,25 +80,26 @@
     @Test
     public void testMatchTitle() {
         loader = new DatabaseResultLoader(mContext, "title");
-        assertThat(loader.loadInBackground().size()).isEqualTo(3);
+        assertThat(loader.loadInBackground().size()).isEqualTo(2);
+        verify(mSiteMapManager, times(2)).buildBreadCrumb(eq(mContext), anyString(), anyString());
     }
 
     @Test
     public void testMatchSummary() {
         loader = new DatabaseResultLoader(mContext, "summary");
-        assertThat(loader.loadInBackground().size()).isEqualTo(3);
+        assertThat(loader.loadInBackground().size()).isEqualTo(2);
     }
 
     @Test
     public void testMatchKeywords() {
         loader = new DatabaseResultLoader(mContext, "keywords");
-        assertThat(loader.loadInBackground().size()).isEqualTo(3);
+        assertThat(loader.loadInBackground().size()).isEqualTo(2);
     }
 
     @Test
     public void testMatchEntries() {
         loader = new DatabaseResultLoader(mContext, "entries");
-        assertThat(loader.loadInBackground().size()).isEqualTo(3);
+        assertThat(loader.loadInBackground().size()).isEqualTo(2);
     }
 
     @Test
@@ -146,7 +167,7 @@
         values.put(IndexDatabaseHelper.IndexColumns.INTENT_TARGET_PACKAGE, "");
         values.put(IndexDatabaseHelper.IndexColumns.INTENT_TARGET_CLASS, "");
         values.put(IndexDatabaseHelper.IndexColumns.ICON, "");
-        values.put(IndexDatabaseHelper.IndexColumns.ENABLED, "");
+        values.put(IndexDatabaseHelper.IndexColumns.ENABLED, true);
         values.put(IndexDatabaseHelper.IndexColumns.DATA_KEY_REF, "gesture_double_tap_power");
         values.put(IndexDatabaseHelper.IndexColumns.USER_ID, 0);
         values.put(IndexDatabaseHelper.IndexColumns.PAYLOAD_TYPE, 0);
@@ -175,7 +196,7 @@
         values.put(IndexDatabaseHelper.IndexColumns.INTENT_TARGET_PACKAGE, "");
         values.put(IndexDatabaseHelper.IndexColumns.INTENT_TARGET_CLASS, "");
         values.put(IndexDatabaseHelper.IndexColumns.ICON, "");
-        values.put(IndexDatabaseHelper.IndexColumns.ENABLED, "");
+        values.put(IndexDatabaseHelper.IndexColumns.ENABLED, true);
         values.put(IndexDatabaseHelper.IndexColumns.DATA_KEY_REF, "gesture_double_tap_power");
         values.put(IndexDatabaseHelper.IndexColumns.USER_ID, 0);
         values.put(IndexDatabaseHelper.IndexColumns.PAYLOAD_TYPE, 0);
@@ -202,7 +223,7 @@
         values.put(IndexDatabaseHelper.IndexColumns.INTENT_TARGET_PACKAGE, "");
         values.put(IndexDatabaseHelper.IndexColumns.INTENT_TARGET_CLASS, "");
         values.put(IndexDatabaseHelper.IndexColumns.ICON, "");
-        values.put(IndexDatabaseHelper.IndexColumns.ENABLED, "");
+        values.put(IndexDatabaseHelper.IndexColumns.ENABLED, true);
         values.put(IndexDatabaseHelper.IndexColumns.DATA_KEY_REF, "gesture_double_tap_power");
         values.put(IndexDatabaseHelper.IndexColumns.USER_ID, 0);
         values.put(IndexDatabaseHelper.IndexColumns.PAYLOAD_TYPE, 0);
@@ -228,7 +249,7 @@
         values.put(IndexDatabaseHelper.IndexColumns.INTENT_TARGET_PACKAGE, "");
         values.put(IndexDatabaseHelper.IndexColumns.INTENT_TARGET_CLASS, "");
         values.put(IndexDatabaseHelper.IndexColumns.ICON, "");
-        values.put(IndexDatabaseHelper.IndexColumns.ENABLED, "");
+        values.put(IndexDatabaseHelper.IndexColumns.ENABLED, false);
         values.put(IndexDatabaseHelper.IndexColumns.DATA_KEY_REF, "gesture_double_tap_power");
         values.put(IndexDatabaseHelper.IndexColumns.USER_ID, 0);
         values.put(IndexDatabaseHelper.IndexColumns.PAYLOAD_TYPE, 0);
diff --git a/tests/robotests/src/com/android/settings/search/SearchFeatureProviderImplTest.java b/tests/robotests/src/com/android/settings/search/SearchFeatureProviderImplTest.java
index c0b1b3d..c314728 100644
--- a/tests/robotests/src/com/android/settings/search/SearchFeatureProviderImplTest.java
+++ b/tests/robotests/src/com/android/settings/search/SearchFeatureProviderImplTest.java
@@ -19,17 +19,13 @@
 
 import android.app.Activity;
 import android.content.Context;
-import android.content.res.Configuration;
 import android.view.Menu;
 
 import com.android.settings.SettingsRobolectricTestRunner;
 import com.android.settings.TestConfig;
-import com.android.settings.overlay.FeatureFactory;
-import com.android.settings.search2.DatabaseIndexingManager;
+import com.android.settings.dashboard.SiteMapManager;
 import com.android.settings.search2.SearchFeatureProviderImpl;
 
-import com.android.settings.testutils.FakeFeatureFactory;
-import com.android.settingslib.drawer.DashboardCategory;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -77,6 +73,14 @@
     }
 
     @Test
+    public void getSiteMapManager_shouldCacheInstanec() {
+        final SiteMapManager manager1 = mProvider.getSiteMapManager();
+        final SiteMapManager manager2 = mProvider.getSiteMapManager();
+
+        assertThat(manager1).isSameAs(manager2);
+    }
+
+    @Test
     public void testUpdateIndexNewSearch_UsesDatabaseIndexingManager() {
         mProvider = spy(new SearchFeatureProviderImpl());
         when(mProvider.isEnabled(mActivity)).thenReturn(true);
diff --git a/tests/robotests/src/com/android/settings/search2/CursorToSearchResultConverterTest.java b/tests/robotests/src/com/android/settings/search2/CursorToSearchResultConverterTest.java
index 6ad7501..d69ba3e 100644
--- a/tests/robotests/src/com/android/settings/search2/CursorToSearchResultConverterTest.java
+++ b/tests/robotests/src/com/android/settings/search2/CursorToSearchResultConverterTest.java
@@ -28,12 +28,16 @@
 import com.android.settings.SettingsRobolectricTestRunner;
 import com.android.settings.SubSettings;
 import com.android.settings.TestConfig;
+import com.android.settings.dashboard.SiteMapManager;
 import com.android.settings.gestures.GestureSettings;
 import com.android.settings.search2.ResultPayload.PayloadType;
 
 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;
 
@@ -57,11 +61,14 @@
     private static final int BASE_RANK = 1;
     private static final int EXAMPLES = 3;
 
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private SiteMapManager mSiteMapManager;
     private Drawable mDrawable;
     private CursorToSearchResultConverter mConverter;
 
     @Before
     public void setUp() {
+        MockitoAnnotations.initMocks(this);
         Context context = Robolectric.buildActivity(Activity.class).get();
         mDrawable = context.getDrawable(ICON);
         mConverter = new CursorToSearchResultConverter(context, QUERY);
@@ -69,19 +76,21 @@
 
     @Test
     public void testParseNullResults_ReturnsNull() {
-        List<SearchResult> results = mConverter.convertCursor(null, BASE_RANK);
+        List<SearchResult> results = mConverter.convertCursor(mSiteMapManager, null, BASE_RANK);
         assertThat(results).isNull();
     }
 
     @Test
     public void testParseCursor_NotNull() {
-        List<SearchResult> results = mConverter.convertCursor(getDummyCursor(), BASE_RANK);
+        List<SearchResult> results = mConverter.convertCursor(
+                mSiteMapManager, getDummyCursor(), BASE_RANK);
         assertThat(results).isNotNull();
     }
 
     @Test
     public void testParseCursor_MatchesRank() {
-        List<SearchResult> results = mConverter.convertCursor(getDummyCursor(), BASE_RANK);
+        List<SearchResult> results = mConverter.convertCursor(
+                mSiteMapManager, getDummyCursor(), BASE_RANK);
         for (int i = 0; i < EXAMPLES; i++) {
             assertThat(results.get(i).rank).isEqualTo(BASE_RANK);
         }
@@ -89,7 +98,8 @@
 
     @Test
     public void testParseCursor_MatchesTitle() {
-        List<SearchResult> results = mConverter.convertCursor(getDummyCursor(), BASE_RANK);
+        List<SearchResult> results = mConverter.convertCursor(
+                mSiteMapManager, getDummyCursor(), BASE_RANK);
         for (int i = 0; i < EXAMPLES; i++) {
             assertThat(results.get(i).title).isEqualTo(TITLES[i]);
         }
@@ -97,7 +107,8 @@
 
     @Test
     public void testParseCursor_MatchesSummary() {
-        List<SearchResult> results = mConverter.convertCursor(getDummyCursor(), BASE_RANK);
+        List<SearchResult> results = mConverter.convertCursor(
+                mSiteMapManager, getDummyCursor(), BASE_RANK);
         for (int i = 0; i < EXAMPLES; i++) {
             assertThat(results.get(i).summary).isEqualTo(SUMMARY);
         }
@@ -105,7 +116,8 @@
 
     @Test
     public void testParseCursor_MatchesIcon() {
-        List<SearchResult> results = mConverter.convertCursor(getDummyCursor(), BASE_RANK);
+        List<SearchResult> results = mConverter.convertCursor(
+                mSiteMapManager, getDummyCursor(), BASE_RANK);
         for (int i = 0; i < EXAMPLES; i++) {
             Drawable resultDrawable = results.get(i).icon;
             assertThat(resultDrawable).isNotNull();
@@ -116,7 +128,7 @@
     @Test
     public void testParseCursor_NoIcon() {
         List<SearchResult> results = mConverter.convertCursor(
-                getDummyCursor(false /* hasIcon */), BASE_RANK);
+                mSiteMapManager, getDummyCursor(false /* hasIcon */), BASE_RANK);
         for (int i = 0; i < EXAMPLES; i++) {
             Drawable resultDrawable = results.get(i).icon;
             assertThat(resultDrawable).isNull();
@@ -125,7 +137,8 @@
 
     @Test
     public void testParseCursor_MatchesPayloadType() {
-        List<SearchResult> results = mConverter.convertCursor(getDummyCursor(), BASE_RANK);
+        List<SearchResult> results = mConverter.convertCursor(
+                mSiteMapManager, getDummyCursor(), BASE_RANK);
         ResultPayload payload;
         for (int i = 0; i < EXAMPLES; i++) {
             payload = results.get(i).payload;
@@ -152,7 +165,7 @@
                 0,       // Payload Type
                 null     // Payload
         });
-        List<SearchResult> results = mConverter.convertCursor(cursor, BASE_RANK);
+        List<SearchResult> results = mConverter.convertCursor(mSiteMapManager, cursor, BASE_RANK);
         IntentPayload payload = (IntentPayload) results.get(0).payload;
         Intent intent = payload.intent;
         assertThat(intent.getComponent().getClassName()).isEqualTo(SubSettings.class.getName());
@@ -160,7 +173,8 @@
 
     @Test
     public void testParseCursor_MatchesIntentPayload() {
-        List<SearchResult> results = mConverter.convertCursor(getDummyCursor(), BASE_RANK);
+        List<SearchResult> results = mConverter.convertCursor(
+                mSiteMapManager, getDummyCursor(), BASE_RANK);
         IntentPayload payload;
         for (int i = 0; i < EXAMPLES; i++) {
             payload = (IntentPayload) results.get(i).payload;
@@ -187,7 +201,7 @@
                 PayloadType.INTENT,    // Payload Type
                 null // Payload
         });
-        List<SearchResult> results = mConverter.convertCursor(cursor, BASE_RANK);
+        List<SearchResult> results = mConverter.convertCursor(mSiteMapManager, cursor, BASE_RANK);
         IntentPayload payload = (IntentPayload) results.get(0).payload;
         Intent intent = payload.intent;
 
@@ -222,7 +236,7 @@
                 type,    // Payload Type
                 ResultPayloadUtils.marshall(payload) // Payload
         });
-        List<SearchResult> results = mConverter.convertCursor(cursor, BASE_RANK);
+        List<SearchResult> results = mConverter.convertCursor(mSiteMapManager, cursor, BASE_RANK);
         InlineSwitchPayload newPayload = (InlineSwitchPayload) results.get(0).payload;
 
         assertThat(newPayload.settingsUri).isEqualTo(uri);
diff --git a/tests/robotests/src/com/android/settings/search2/InstalledAppResultLoaderTest.java b/tests/robotests/src/com/android/settings/search2/InstalledAppResultLoaderTest.java
index 4f62a9e..e6397e1 100644
--- a/tests/robotests/src/com/android/settings/search2/InstalledAppResultLoaderTest.java
+++ b/tests/robotests/src/com/android/settings/search2/InstalledAppResultLoaderTest.java
@@ -22,10 +22,13 @@
 import android.content.pm.UserInfo;
 import android.os.UserManager;
 
+import com.android.settings.R;
 import com.android.settings.SettingsRobolectricTestRunner;
 import com.android.settings.TestConfig;
 import com.android.settings.applications.PackageManagerWrapper;
+import com.android.settings.dashboard.SiteMapManager;
 import com.android.settings.testutils.ApplicationTestUtils;
+import com.android.settings.testutils.FakeFeatureFactory;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -44,7 +47,12 @@
 import static com.google.common.truth.Truth.assertThat;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 @RunWith(SettingsRobolectricTestRunner.class)
@@ -57,16 +65,24 @@
     private PackageManagerWrapper mPackageManagerWrapper;
     @Mock
     private UserManager mUserManager;
+    @Mock
+    private SiteMapManager mSiteMapManager;
 
     private InstalledAppResultLoader mLoader;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
+        FakeFeatureFactory.setupForTest(mContext);
+        FakeFeatureFactory factory = (FakeFeatureFactory) FakeFeatureFactory.getFactory(mContext);
+        when(factory.searchFeatureProvider.getSiteMapManager())
+                .thenReturn(mSiteMapManager);
         final List<UserInfo> infos = new ArrayList<>();
         infos.add(new UserInfo(1, "user 1", 0));
         when(mUserManager.getProfiles(anyInt())).thenReturn(infos);
         when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
+        when(mContext.getString(R.string.applications_settings))
+                .thenReturn("app");
         when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt()))
                 .thenReturn(Arrays.asList(
                         ApplicationTestUtils.buildInfo(0 /* uid */, "app1", FLAG_SYSTEM,
@@ -94,9 +110,14 @@
     public void query_matchingQuery_shouldReturnNonSystemApps() {
         final String query = "app";
 
-        mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query);
+        mLoader = spy(new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query));
+        when(mLoader.getContext()).thenReturn(mContext);
+        when(mSiteMapManager.buildBreadCrumb(eq(mContext), anyString(), anyString()))
+                .thenReturn(Arrays.asList(new String[]{"123"}));
 
         assertThat(mLoader.loadInBackground().size()).isEqualTo(2);
+        verify(mSiteMapManager)
+                .buildBreadCrumb(eq(mContext), anyString(), anyString());
     }
 
     @Test
@@ -107,9 +128,12 @@
                                 0 /* targetSdkVersion */)));
         final String query = "app";
 
-        mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query);
+        mLoader = spy(new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query));
+        when(mLoader.getContext()).thenReturn(mContext);
 
         assertThat(mLoader.loadInBackground().size()).isEqualTo(1);
+        verify(mSiteMapManager)
+                .buildBreadCrumb(eq(mContext), anyString(), anyString());
     }
 
     @Test
@@ -132,7 +156,7 @@
     }
 
     @Test
-    public void query_matchingQuery_shouldNOtReturnSystemAppIfNotLaunchable() {
+    public void query_matchingQuery_shouldNotReturnSystemAppIfNotLaunchable() {
         when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt()))
                 .thenReturn(Arrays.asList(
                         ApplicationTestUtils.buildInfo(0 /* uid */, "app1", FLAG_SYSTEM,
@@ -146,6 +170,8 @@
         mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query);
 
         assertThat(mLoader.loadInBackground()).isEmpty();
+        verify(mSiteMapManager, never())
+                .buildBreadCrumb(eq(mContext), anyString(), anyString());
     }
 
     @Test
diff --git a/tests/robotests/src/com/android/settings/search2/SiteMapManagerTest.java b/tests/robotests/src/com/android/settings/search2/SiteMapManagerTest.java
new file mode 100644
index 0000000..b8ac8fe
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/search2/SiteMapManagerTest.java
@@ -0,0 +1,145 @@
+/*
+ * 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.search2;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.sqlite.SQLiteDatabase;
+import android.os.Bundle;
+
+import com.android.settings.SettingsActivity;
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settings.dashboard.SiteMapManager;
+import com.android.settings.search.IndexDatabaseHelper;
+import com.android.settings.search.IndexDatabaseHelper.SiteMapColumns;
+import com.android.settings.system.SystemDashboardFragment;
+import com.android.settings.testutils.DatabaseTestUtils;
+import com.android.settings.testutils.FakeFeatureFactory;
+import com.android.settingslib.drawer.CategoryKey;
+import com.android.settingslib.drawer.DashboardCategory;
+import com.android.settingslib.drawer.Tile;
+
+import org.junit.After;
+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.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+import java.util.Arrays;
+import java.util.List;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.when;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class SiteMapManagerTest {
+
+    private static final int STATIC_DB_DEPTH = 4;
+    private static final String CLASS_PREFIX = "class_";
+    private static final String TITLE_PREFIX = "title_";
+
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private Context mMockContext;
+    private Context mContext;
+    private SQLiteDatabase mDb;
+    private SiteMapManager mSiteMapManager;
+    private FakeFeatureFactory mFeatureFactory;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        FakeFeatureFactory.setupForTest(mMockContext);
+        mFeatureFactory = (FakeFeatureFactory) FakeFeatureFactory.getFactory(mMockContext);
+
+        mContext = RuntimeEnvironment.application;
+        mDb = IndexDatabaseHelper.getInstance(mContext).getWritableDatabase();
+        buildDb();
+        mSiteMapManager = new SiteMapManager();
+    }
+
+    @After
+    public void cleanUp() {
+        DatabaseTestUtils.clearDb();
+    }
+
+    @Test
+    public void buildBreadCrumb_onlyFromSiteMapDb_breadcrumbShouldLinkUp() {
+        List<String> breadcrumb = mSiteMapManager.buildBreadCrumb(mContext,
+                CLASS_PREFIX + 0, TITLE_PREFIX + 0);
+        assertThat(breadcrumb.size()).isEqualTo(STATIC_DB_DEPTH + 1);
+        for (int i = 0; i < STATIC_DB_DEPTH; i++) {
+            assertThat(breadcrumb.get(i)).isEqualTo(TITLE_PREFIX + (STATIC_DB_DEPTH - i));
+        }
+    }
+
+    @Test
+    public void buildBreadCrumb_fromSiteMapDbAndDashboardProvider_breadcrumbShouldLinkUp() {
+        final String iaClass = SystemDashboardFragment.class.getName();
+        final String iaTitle = "ia_title";
+
+        ContentValues index = new ContentValues();
+        index.put(IndexDatabaseHelper.IndexColumns.CLASS_NAME, iaClass);
+        index.put(IndexDatabaseHelper.IndexColumns.SCREEN_TITLE, iaTitle);
+        mDb.replaceOrThrow(IndexDatabaseHelper.Tables.TABLE_PREFS_INDEX, null, index);
+
+        final DashboardCategory category = new DashboardCategory();
+        category.key = CategoryKey.CATEGORY_SYSTEM;
+        category.tiles.add(new Tile());
+        category.tiles.get(0).title = TITLE_PREFIX + STATIC_DB_DEPTH;
+        category.tiles.get(0).metaData = new Bundle();
+        category.tiles.get(0).metaData.putString(SettingsActivity.META_DATA_KEY_FRAGMENT_CLASS,
+                CLASS_PREFIX + STATIC_DB_DEPTH);
+        when(mFeatureFactory.dashboardFeatureProvider.getAllCategories())
+                .thenReturn(Arrays.asList(category));
+
+        final List<String> breadcrumb = mSiteMapManager.buildBreadCrumb(mContext,
+                CLASS_PREFIX + 0, TITLE_PREFIX + 0);
+
+        assertThat(breadcrumb.size()).isEqualTo(STATIC_DB_DEPTH + 2);
+        assertThat(breadcrumb.get(0))
+                .isEqualTo(iaTitle);
+    }
+
+    @Test
+    public void buildBreadCrumb_classNotIndexed_shouldNotHaveBreadCrumb() {
+        final String title = "wrong_title";
+
+        final List<String> breadcrumb = mSiteMapManager.buildBreadCrumb(mContext,
+                "wrong_class", title);
+
+        assertThat(breadcrumb.size()).isEqualTo(1);
+        assertThat(breadcrumb.get(0)).isEqualTo(title);
+    }
+
+    private void buildDb() {
+        for (int i = 0; i < STATIC_DB_DEPTH; i++) {
+            final ContentValues siteMapPair = new ContentValues();
+            siteMapPair.put(SiteMapColumns.DOCID, i);
+            siteMapPair.put(SiteMapColumns.PARENT_CLASS, CLASS_PREFIX + (i + 1));
+            siteMapPair.put(SiteMapColumns.PARENT_TITLE, TITLE_PREFIX + (i + 1));
+            siteMapPair.put(SiteMapColumns.CHILD_CLASS, CLASS_PREFIX + i);
+            siteMapPair.put(SiteMapColumns.CHILD_TITLE, TITLE_PREFIX + i);
+            mDb.replaceOrThrow(IndexDatabaseHelper.Tables.TABLE_SITE_MAP, null, siteMapPair);
+        }
+    }
+}