[Settings] Support pure switch of inline toggle of Settings Injection v2

Bug: 132808482
Test: robotest
Change-Id: Ib24614fb46fe990925edad721e3b7d5d032854fc
diff --git a/src/com/android/settings/SettingsActivity.java b/src/com/android/settings/SettingsActivity.java
index e459ab8..70536c0 100644
--- a/src/com/android/settings/SettingsActivity.java
+++ b/src/com/android/settings/SettingsActivity.java
@@ -583,12 +583,7 @@
         // Generally the items that are will be changing from these updates will
         // not be in the top list of tiles, so run it in the background and the
         // SettingsBaseActivity will pick up on the updates automatically.
-        AsyncTask.execute(new Runnable() {
-            @Override
-            public void run() {
-                doUpdateTilesList();
-            }
-        });
+        AsyncTask.execute(() -> doUpdateTilesList());
     }
 
     private void doUpdateTilesList() {
@@ -648,7 +643,6 @@
                 || somethingChanged;
 
         if (UserHandle.MU_ENABLED && !isAdmin) {
-
             // When on restricted users, disable all extra categories (but only the settings ones).
             final List<DashboardCategory> categories = mDashboardFeatureProvider.getAllCategories();
             synchronized (categories) {
diff --git a/src/com/android/settings/dashboard/CategoryManager.java b/src/com/android/settings/dashboard/CategoryManager.java
index 5cc75c8..525b6f8 100644
--- a/src/com/android/settings/dashboard/CategoryManager.java
+++ b/src/com/android/settings/dashboard/CategoryManager.java
@@ -27,6 +27,7 @@
 import com.android.settingslib.applications.InterestingConfigChanges;
 import com.android.settingslib.drawer.CategoryKey;
 import com.android.settingslib.drawer.DashboardCategory;
+import com.android.settingslib.drawer.ProviderTile;
 import com.android.settingslib.drawer.Tile;
 import com.android.settingslib.drawer.TileUtils;
 
@@ -189,21 +190,31 @@
 
     /**
      * Filter out duplicate tiles from category. Duplicate tiles are the ones pointing to the
-     * same intent.
+     * same intent for ActivityTile, and also the ones having the same description for ProviderTile.
      */
     @VisibleForTesting
     synchronized void filterDuplicateTiles(Map<String, DashboardCategory> categoryByKeyMap) {
         for (Entry<String, DashboardCategory> categoryEntry : categoryByKeyMap.entrySet()) {
             final DashboardCategory category = categoryEntry.getValue();
             final int count = category.getTilesCount();
+            final Set<String> descriptions = new ArraySet<>();
             final Set<ComponentName> components = new ArraySet<>();
             for (int i = count - 1; i >= 0; i--) {
                 final Tile tile = category.getTile(i);
-                final ComponentName tileComponent = tile.getIntent().getComponent();
-                if (components.contains(tileComponent)) {
-                    category.removeTile(i);
+                if (tile instanceof ProviderTile) {
+                    final String desc = tile.getDescription();
+                    if (descriptions.contains(desc)) {
+                        category.removeTile(i);
+                    } else {
+                        descriptions.add(desc);
+                    }
                 } else {
-                    components.add(tileComponent);
+                    final ComponentName tileComponent = tile.getIntent().getComponent();
+                    if (components.contains(tileComponent)) {
+                        category.removeTile(i);
+                    } else {
+                        components.add(tileComponent);
+                    }
                 }
             }
         }
diff --git a/src/com/android/settings/dashboard/DashboardFeatureProvider.java b/src/com/android/settings/dashboard/DashboardFeatureProvider.java
index 9f562a0..8c872f0 100644
--- a/src/com/android/settings/dashboard/DashboardFeatureProvider.java
+++ b/src/com/android/settings/dashboard/DashboardFeatureProvider.java
@@ -44,19 +44,22 @@
     String getDashboardKeyForTile(Tile tile);
 
     /**
-     * Binds preference to data provided by tile.
+     * Binds preference to data provided by tile and gets dynamic data observers.
      *
      * @param activity If tile contains intent to launch, it will be launched from this activity
-     * @param forceRoundedIcon Whether or not injected tiles from other packages should be forced to rounded icon.
+     * @param forceRoundedIcon Whether or not injected tiles from other packages should be forced to
+     * rounded icon.
      * @param sourceMetricsCategory The context (source) from which an action is performed
      * @param pref The preference to bind data
      * @param tile The binding data
      * @param key They key for preference. If null, we will generate one from tile data
      * @param baseOrder The order offset value. When binding, pref's order is determined by
      * both this value and tile's own priority.
+     * @return The list of dynamic data observers
      */
-    void bindPreferenceToTile(FragmentActivity activity, boolean forceRoundedIcon,
-            int sourceMetricsCategory, Preference pref, Tile tile, String key, int baseOrder);
+    List<DynamicDataObserver> bindPreferenceToTileAndGetObservers(FragmentActivity activity,
+            boolean forceRoundedIcon, int sourceMetricsCategory, Preference pref, Tile tile,
+            String key, int baseOrder);
 
     /**
      * Opens a tile to its destination intent.
diff --git a/src/com/android/settings/dashboard/DashboardFeatureProviderImpl.java b/src/com/android/settings/dashboard/DashboardFeatureProviderImpl.java
index 2cfcbe6..4e541cb 100644
--- a/src/com/android/settings/dashboard/DashboardFeatureProviderImpl.java
+++ b/src/com/android/settings/dashboard/DashboardFeatureProviderImpl.java
@@ -18,9 +18,18 @@
 
 import static android.content.Intent.EXTRA_USER;
 
+import static com.android.settingslib.drawer.SwitchesProvider.EXTRA_SWITCH_CHECKED_STATE;
+import static com.android.settingslib.drawer.SwitchesProvider.EXTRA_SWITCH_SET_CHECKED_ERROR;
+import static com.android.settingslib.drawer.SwitchesProvider.EXTRA_SWITCH_SET_CHECKED_ERROR_MESSAGE;
+import static com.android.settingslib.drawer.SwitchesProvider.METHOD_GET_DYNAMIC_SUMMARY;
+import static com.android.settingslib.drawer.SwitchesProvider.METHOD_GET_DYNAMIC_TITLE;
+import static com.android.settingslib.drawer.SwitchesProvider.METHOD_GET_PROVIDER_ICON;
+import static com.android.settingslib.drawer.SwitchesProvider.METHOD_IS_CHECKED;
+import static com.android.settingslib.drawer.SwitchesProvider.METHOD_ON_CHECKED_CHANGED;
 import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON_URI;
 import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SUMMARY;
 import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SUMMARY_URI;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SWITCH_URI;
 import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_TITLE;
 import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_TITLE_URI;
 
@@ -40,22 +49,26 @@
 import android.util.ArrayMap;
 import android.util.Log;
 import android.util.Pair;
+import android.widget.Toast;
 
 import androidx.annotation.VisibleForTesting;
 import androidx.fragment.app.FragmentActivity;
 import androidx.preference.Preference;
+import androidx.preference.SwitchPreference;
 
 import com.android.settings.R;
 import com.android.settings.SettingsActivity;
 import com.android.settings.dashboard.profileselector.ProfileSelectDialog;
 import com.android.settings.overlay.FeatureFactory;
 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
+import com.android.settingslib.drawer.ActivityTile;
 import com.android.settingslib.drawer.DashboardCategory;
 import com.android.settingslib.drawer.Tile;
 import com.android.settingslib.drawer.TileUtils;
 import com.android.settingslib.utils.ThreadUtils;
 import com.android.settingslib.widget.AdaptiveIcon;
 
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 
@@ -106,40 +119,54 @@
     }
 
     @Override
-    public void bindPreferenceToTile(FragmentActivity activity, boolean forceRoundedIcon,
-            int sourceMetricsCategory, Preference pref, Tile tile, String key, int baseOrder) {
+    public List<DynamicDataObserver> bindPreferenceToTileAndGetObservers(FragmentActivity activity,
+            boolean forceRoundedIcon, int sourceMetricsCategory, Preference pref, Tile tile,
+            String key, int baseOrder) {
         if (pref == null) {
-            return;
+            return null;
         }
         if (!TextUtils.isEmpty(key)) {
             pref.setKey(key);
         } else {
             pref.setKey(getDashboardKeyForTile(tile));
         }
-        bindTitle(pref, tile);
-        bindSummary(pref, tile);
+        final List<DynamicDataObserver> outObservers = new ArrayList<>();
+        DynamicDataObserver observer = bindTitleAndGetObserver(pref, tile);
+        if (observer != null) {
+            outObservers.add(observer);
+        }
+        observer = bindSummaryAndGetObserver(pref, tile);
+        if (observer != null) {
+            outObservers.add(observer);
+        }
+        observer = bindSwitchAndGetObserver(pref, tile);
+        if (observer != null) {
+            outObservers.add(observer);
+        }
         bindIcon(pref, tile, forceRoundedIcon);
 
-        final Bundle metadata = tile.getMetaData();
-        String clsName = null;
-        String action = null;
-        if (metadata != null) {
-            clsName = metadata.getString(SettingsActivity.META_DATA_KEY_FRAGMENT_CLASS);
-            action = metadata.getString(META_DATA_KEY_INTENT_ACTION);
-        }
-        if (!TextUtils.isEmpty(clsName)) {
-            pref.setFragment(clsName);
-        } else {
-            final Intent intent = new Intent(tile.getIntent());
-            intent.putExtra(MetricsFeatureProvider.EXTRA_SOURCE_METRICS_CATEGORY,
-                    sourceMetricsCategory);
-            if (action != null) {
-                intent.setAction(action);
+        if (tile instanceof ActivityTile) {
+            final Bundle metadata = tile.getMetaData();
+            String clsName = null;
+            String action = null;
+            if (metadata != null) {
+                clsName = metadata.getString(SettingsActivity.META_DATA_KEY_FRAGMENT_CLASS);
+                action = metadata.getString(META_DATA_KEY_INTENT_ACTION);
             }
-            pref.setOnPreferenceClickListener(preference -> {
-                launchIntentOrSelectProfile(activity, tile, intent, sourceMetricsCategory);
-                return true;
-            });
+            if (!TextUtils.isEmpty(clsName)) {
+                pref.setFragment(clsName);
+            } else {
+                final Intent intent = new Intent(tile.getIntent());
+                intent.putExtra(MetricsFeatureProvider.EXTRA_SOURCE_METRICS_CATEGORY,
+                        sourceMetricsCategory);
+                if (action != null) {
+                    intent.setAction(action);
+                }
+                pref.setOnPreferenceClickListener(preference -> {
+                    launchIntentOrSelectProfile(activity, tile, intent, sourceMetricsCategory);
+                    return true;
+                });
+            }
         }
 
         if (tile.hasOrder()) {
@@ -153,6 +180,7 @@
                 pref.setOrder(order + baseOrder);
             }
         }
+        return outObservers.isEmpty() ? null : outObservers;
     }
 
     @Override
@@ -170,11 +198,35 @@
         launchIntentOrSelectProfile(activity, tile, intent, SettingsEnums.DASHBOARD_SUMMARY);
     }
 
-    private void bindTitle(Preference preference, Tile tile) {
+    private DynamicDataObserver createDynamicDataObserver(String method, Uri uri, Preference pref) {
+        return new DynamicDataObserver() {
+            @Override
+            public Uri getUri() {
+                return uri;
+            }
+
+            @Override
+            public void onDataChanged() {
+                switch (method) {
+                    case METHOD_GET_DYNAMIC_TITLE:
+                        refreshTitle(uri, pref);
+                        break;
+                    case METHOD_GET_DYNAMIC_SUMMARY:
+                        refreshSummary(uri, pref);
+                        break;
+                    case METHOD_IS_CHECKED:
+                        refreshSwitch(uri, pref);
+                        break;
+                }
+            }
+        };
+    }
+
+    private DynamicDataObserver bindTitleAndGetObserver(Preference preference, Tile tile) {
         final CharSequence title = tile.getTitle(mContext.getApplicationContext());
         if (title != null) {
             preference.setTitle(title);
-            return;
+            return null;
         }
         if (tile.getMetaData() != null && tile.getMetaData().containsKey(
                 META_DATA_PREFERENCE_TITLE_URI)) {
@@ -182,9 +234,12 @@
             // to avoid preference height change.
             preference.setTitle(R.string.summary_placeholder);
 
-            final Uri uri = TileUtils.getCompleteUri(tile, META_DATA_PREFERENCE_TITLE_URI);
+            final Uri uri = TileUtils.getCompleteUri(tile, META_DATA_PREFERENCE_TITLE_URI,
+                    METHOD_GET_DYNAMIC_TITLE);
             refreshTitle(uri, preference);
+            return createDynamicDataObserver(METHOD_GET_DYNAMIC_TITLE, uri, preference);
         }
+        return null;
     }
 
     private void refreshTitle(Uri uri, Preference preference) {
@@ -196,7 +251,7 @@
         });
     }
 
-    private void bindSummary(Preference preference, Tile tile) {
+    private DynamicDataObserver bindSummaryAndGetObserver(Preference preference, Tile tile) {
         final CharSequence summary = tile.getSummary(mContext);
         if (summary != null) {
             preference.setSummary(summary);
@@ -206,11 +261,14 @@
             // to avoid preference height change.
             preference.setSummary(R.string.summary_placeholder);
 
-            final Uri uri = TileUtils.getCompleteUri(tile, META_DATA_PREFERENCE_SUMMARY_URI);
+            final Uri uri = TileUtils.getCompleteUri(tile, META_DATA_PREFERENCE_SUMMARY_URI,
+                    METHOD_GET_DYNAMIC_SUMMARY);
             refreshSummary(uri, preference);
+            return createDynamicDataObserver(METHOD_GET_DYNAMIC_SUMMARY, uri, preference);
         } else {
             preference.setSummary(R.string.summary_placeholder);
         }
+        return null;
     }
 
     private void refreshSummary(Uri uri, Preference preference) {
@@ -222,6 +280,70 @@
         });
     }
 
+    private DynamicDataObserver bindSwitchAndGetObserver(Preference preference, Tile tile) {
+        if (!tile.hasSwitch()) {
+            return null;
+        }
+
+        final Uri onCheckedChangedUri = TileUtils.getCompleteUri(tile,
+                META_DATA_PREFERENCE_SWITCH_URI, METHOD_ON_CHECKED_CHANGED);
+        preference.setOnPreferenceChangeListener((pref, newValue) -> {
+            onCheckedChanged(onCheckedChangedUri, pref, (boolean) newValue);
+            return true;
+        });
+
+        final Uri isCheckedUri = TileUtils.getCompleteUri(tile, META_DATA_PREFERENCE_SWITCH_URI,
+                METHOD_IS_CHECKED);
+        setSwitchEnabled(preference, false);
+        refreshSwitch(isCheckedUri, preference);
+        return createDynamicDataObserver(METHOD_IS_CHECKED, isCheckedUri, preference);
+    }
+
+    private void onCheckedChanged(Uri uri, Preference pref, boolean checked) {
+        setSwitchEnabled(pref, false);
+        ThreadUtils.postOnBackgroundThread(() -> {
+            final Map<String, IContentProvider> providerMap = new ArrayMap<>();
+            final Bundle result = TileUtils.putBooleanToUri(mContext, uri, providerMap,
+                    EXTRA_SWITCH_CHECKED_STATE, checked);
+
+            ThreadUtils.postOnMainThread(() -> {
+                setSwitchEnabled(pref, true);
+                final boolean error = result.getBoolean(EXTRA_SWITCH_SET_CHECKED_ERROR);
+                if (!error) {
+                    return;
+                }
+
+                setSwitchChecked(pref, !checked);
+                final String errorMsg = result.getString(EXTRA_SWITCH_SET_CHECKED_ERROR_MESSAGE);
+                if (!TextUtils.isEmpty(errorMsg)) {
+                    Toast.makeText(mContext, errorMsg, Toast.LENGTH_SHORT).show();
+                }
+            });
+        });
+    }
+
+    private void refreshSwitch(Uri uri, Preference preference) {
+        ThreadUtils.postOnBackgroundThread(() -> {
+            final Map<String, IContentProvider> providerMap = new ArrayMap<>();
+            final boolean checked = TileUtils.getBooleanFromUri(mContext, uri, providerMap,
+                    EXTRA_SWITCH_CHECKED_STATE);
+            ThreadUtils.postOnMainThread(() -> {
+                setSwitchChecked(preference, checked);
+                setSwitchEnabled(preference, true);
+            });
+        });
+    }
+
+    private void setSwitchChecked(Preference pref, boolean checked) {
+        if (pref instanceof SwitchPreference) {
+            ((SwitchPreference) pref).setChecked(checked);
+        }
+    }
+
+    private void setSwitchEnabled(Preference pref, boolean enabled) {
+        pref.setEnabled(enabled);
+    }
+
     @VisibleForTesting
     void bindIcon(Preference preference, Tile tile, boolean forceRoundedIcon) {
         // Use preference context instead here when get icon from Tile, as we are using the context
@@ -246,7 +368,8 @@
                     packageName = intent.getComponent().getPackageName();
                 }
                 final Map<String, IContentProvider> providerMap = new ArrayMap<>();
-                final Uri uri = TileUtils.getCompleteUri(tile, META_DATA_PREFERENCE_ICON_URI);
+                final Uri uri = TileUtils.getCompleteUri(tile, META_DATA_PREFERENCE_ICON_URI,
+                        METHOD_GET_PROVIDER_ICON);
                 final Pair<String, Integer> iconInfo = TileUtils.getIconFromUri(
                         mContext, packageName, uri, providerMap);
                 if (iconInfo == null) {
diff --git a/src/com/android/settings/dashboard/DashboardFragment.java b/src/com/android/settings/dashboard/DashboardFragment.java
index 3575b72..30d6df3 100644
--- a/src/com/android/settings/dashboard/DashboardFragment.java
+++ b/src/com/android/settings/dashboard/DashboardFragment.java
@@ -17,11 +17,11 @@
 
 import android.app.Activity;
 import android.app.settings.SettingsEnums;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.os.Bundle;
 import android.text.TextUtils;
 import android.util.ArrayMap;
-import android.util.ArraySet;
 import android.util.Log;
 
 import androidx.annotation.CallSuper;
@@ -30,6 +30,7 @@
 import androidx.preference.PreferenceGroup;
 import androidx.preference.PreferenceManager;
 import androidx.preference.PreferenceScreen;
+import androidx.preference.SwitchPreference;
 
 import com.android.settings.R;
 import com.android.settings.SettingsPreferenceFragment;
@@ -41,6 +42,7 @@
 import com.android.settingslib.core.lifecycle.Lifecycle;
 import com.android.settingslib.core.lifecycle.LifecycleObserver;
 import com.android.settingslib.drawer.DashboardCategory;
+import com.android.settingslib.drawer.ProviderTile;
 import com.android.settingslib.drawer.Tile;
 import com.android.settingslib.search.Indexable;
 
@@ -49,7 +51,7 @@
 import java.util.Collection;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
+import java.util.Objects;
 
 /**
  * Base fragment for dashboard style UI containing a list of static and dynamic setting items.
@@ -60,9 +62,11 @@
         BasePreferenceController.UiBlockListener {
     private static final String TAG = "DashboardFragment";
 
+    @VisibleForTesting
+    final ArrayMap<String, List<DynamicDataObserver>> mDashboardTilePrefKeys = new ArrayMap<>();
     private final Map<Class, List<AbstractPreferenceController>> mPreferenceControllers =
             new ArrayMap<>();
-    private final Set<String> mDashboardTilePrefKeys = new ArraySet<>();
+    private final List<DynamicDataObserver> mRegisteredObservers = new ArrayList<>();
     private final List<AbstractPreferenceController> mControllers = new ArrayList<>();
 
     private DashboardFeatureProvider mDashboardFeatureProvider;
@@ -171,6 +175,15 @@
             mListeningToCategoryChange = true;
             ((SettingsBaseActivity) activity).addCategoryListener(this);
         }
+        final ContentResolver resolver = getContentResolver();
+        mDashboardTilePrefKeys.values().stream()
+                .filter(Objects::nonNull)
+                .flatMap(List::stream)
+                .forEach(observer -> {
+                    if (!mRegisteredObservers.contains(observer)) {
+                        registerDynamicDataObserver(resolver, observer);
+                    }
+                });
     }
 
     @Override
@@ -200,6 +213,7 @@
     @Override
     public void onStop() {
         super.onStop();
+        unregisterDynamicDataObservers(new ArrayList<>(mRegisteredObservers));
         if (mListeningToCategoryChange) {
             final Activity activity = getActivity();
             if (activity instanceof SettingsBaseActivity) {
@@ -325,7 +339,7 @@
      * Refresh all preference items, including both static prefs from xml, and dynamic items from
      * DashboardCategory.
      */
-    private void refreshAllPreferences(final String TAG) {
+    private void refreshAllPreferences(final String tag) {
         final PreferenceScreen screen = getPreferenceScreen();
         // First remove old preferences.
         if (screen != null) {
@@ -336,11 +350,11 @@
         // Add resource based tiles.
         displayResourceTiles();
 
-        refreshDashboardTiles(TAG);
+        refreshDashboardTiles(tag);
 
         final Activity activity = getActivity();
         if (activity != null) {
-            Log.d(TAG, "All preferences added, reporting fully drawn");
+            Log.d(tag, "All preferences added, reporting fully drawn");
             activity.reportFullyDrawn();
         }
 
@@ -371,59 +385,62 @@
     /**
      * Refresh preference items backed by DashboardCategory.
      */
-    @VisibleForTesting
-    void refreshDashboardTiles(final String TAG) {
+    private void refreshDashboardTiles(final String tag) {
         final PreferenceScreen screen = getPreferenceScreen();
 
         final DashboardCategory category =
                 mDashboardFeatureProvider.getTilesForCategory(getCategoryKey());
         if (category == null) {
-            Log.d(TAG, "NO dashboard tiles for " + TAG);
+            Log.d(tag, "NO dashboard tiles for " + tag);
             return;
         }
         final List<Tile> tiles = category.getTiles();
         if (tiles == null) {
-            Log.d(TAG, "tile list is empty, skipping category " + category.key);
+            Log.d(tag, "tile list is empty, skipping category " + category.key);
             return;
         }
         // Create a list to track which tiles are to be removed.
-        final List<String> remove = new ArrayList<>(mDashboardTilePrefKeys);
+        final Map<String, List<DynamicDataObserver>> remove = new ArrayMap(mDashboardTilePrefKeys);
 
         // Install dashboard tiles.
         final boolean forceRoundedIcons = shouldForceRoundedIcon();
         for (Tile tile : tiles) {
             final String key = mDashboardFeatureProvider.getDashboardKeyForTile(tile);
             if (TextUtils.isEmpty(key)) {
-                Log.d(TAG, "tile does not contain a key, skipping " + tile);
+                Log.d(tag, "tile does not contain a key, skipping " + tile);
                 continue;
             }
             if (!displayTile(tile)) {
                 continue;
             }
-            if (mDashboardTilePrefKeys.contains(key)) {
+            if (mDashboardTilePrefKeys.containsKey(key)) {
                 // Have the key already, will rebind.
                 final Preference preference = screen.findPreference(key);
-                mDashboardFeatureProvider.bindPreferenceToTile(getActivity(), forceRoundedIcons,
-                        getMetricsCategory(), preference, tile, key,
+                mDashboardFeatureProvider.bindPreferenceToTileAndGetObservers(getActivity(),
+                        forceRoundedIcons, getMetricsCategory(), preference, tile, key,
                         mPlaceholderPreferenceController.getOrder());
             } else {
                 // Don't have this key, add it.
-                final Preference pref = new Preference(getPrefContext());
-                mDashboardFeatureProvider.bindPreferenceToTile(getActivity(), forceRoundedIcons,
-                        getMetricsCategory(), pref, tile, key,
-                        mPlaceholderPreferenceController.getOrder());
+                final Preference pref = createPreference(tile);
+                final List<DynamicDataObserver> observers =
+                        mDashboardFeatureProvider.bindPreferenceToTileAndGetObservers(getActivity(),
+                                forceRoundedIcons, getMetricsCategory(), pref, tile, key,
+                                mPlaceholderPreferenceController.getOrder());
                 screen.addPreference(pref);
-                mDashboardTilePrefKeys.add(key);
+                registerDynamicDataObservers(observers);
+                mDashboardTilePrefKeys.put(key, observers);
             }
             remove.remove(key);
         }
         // Finally remove tiles that are gone.
-        for (String key : remove) {
+        for (Map.Entry<String, List<DynamicDataObserver>> entry : remove.entrySet()) {
+            final String key = entry.getKey();
             mDashboardTilePrefKeys.remove(key);
             final Preference preference = screen.findPreference(key);
             if (preference != null) {
                 screen.removePreference(preference);
             }
+            unregisterDynamicDataObservers(entry.getValue());
         }
     }
 
@@ -431,4 +448,41 @@
     public void onBlockerWorkFinished(BasePreferenceController controller) {
         mBlockerController.countDown(controller.getPreferenceKey());
     }
+
+    @VisibleForTesting
+    Preference createPreference(Tile tile) {
+        return tile instanceof ProviderTile
+                ? new SwitchPreference(getPrefContext())
+                : new Preference(getPrefContext());
+    }
+
+    @VisibleForTesting
+    void registerDynamicDataObservers(List<DynamicDataObserver> observers) {
+        if (observers == null || observers.isEmpty()) {
+            return;
+        }
+        final ContentResolver resolver = getContentResolver();
+        observers.forEach(observer -> registerDynamicDataObserver(resolver, observer));
+    }
+
+    private void registerDynamicDataObserver(ContentResolver resolver,
+            DynamicDataObserver observer) {
+        Log.d(TAG, "register observer: @" + Integer.toHexString(observer.hashCode())
+                + ", uri: " + observer.getUri());
+        resolver.registerContentObserver(observer.getUri(), false, observer);
+        mRegisteredObservers.add(observer);
+    }
+
+    private void unregisterDynamicDataObservers(List<DynamicDataObserver> observers) {
+        if (observers == null || observers.isEmpty()) {
+            return;
+        }
+        final ContentResolver resolver = getContentResolver();
+        observers.forEach(observer -> {
+            Log.d(TAG, "unregister observer: @" + Integer.toHexString(observer.hashCode())
+                    + ", uri: " + observer.getUri());
+            mRegisteredObservers.remove(observer);
+            resolver.unregisterContentObserver(observer);
+        });
+    }
 }
diff --git a/src/com/android/settings/dashboard/DynamicDataObserver.java b/src/com/android/settings/dashboard/DynamicDataObserver.java
new file mode 100644
index 0000000..f5299be
--- /dev/null
+++ b/src/com/android/settings/dashboard/DynamicDataObserver.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2019 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.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+
+/**
+ * Observer for updating injected dynamic data.
+ */
+public abstract class DynamicDataObserver extends ContentObserver {
+
+    protected DynamicDataObserver() {
+        super(new Handler(Looper.getMainLooper()));
+    }
+
+    /** Returns the uri of the callback. */
+    public abstract Uri getUri();
+
+    /** Called when data changes. */
+    public abstract void onDataChanged();
+
+    @Override
+    public void onChange(boolean selfChange) {
+        onDataChanged();
+    }
+}
diff --git a/tests/robotests/res/values-mcc999/config.xml b/tests/robotests/res/values-mcc999/config.xml
index 0dec65f..679062b 100644
--- a/tests/robotests/res/values-mcc999/config.xml
+++ b/tests/robotests/res/values-mcc999/config.xml
@@ -92,5 +92,6 @@
     <!-- List containing the injected tile keys which are suppressed. -->
     <string-array name="config_suppress_injected_tile_keys" translatable="false">
         <item>injected_tile_key</item>
+        <item>injected_tile_key2</item>
     </string-array>
 </resources>
diff --git a/tests/robotests/src/com/android/settings/accounts/AccountDetailDashboardFragmentTest.java b/tests/robotests/src/com/android/settings/accounts/AccountDetailDashboardFragmentTest.java
index 03c5de3..a827284 100644
--- a/tests/robotests/src/com/android/settings/accounts/AccountDetailDashboardFragmentTest.java
+++ b/tests/robotests/src/com/android/settings/accounts/AccountDetailDashboardFragmentTest.java
@@ -151,9 +151,9 @@
 
         final FragmentActivity activity = Robolectric.setupActivity(FragmentActivity.class);
         final Preference preference = new Preference(mContext);
-        dashboardFeatureProvider.bindPreferenceToTile(activity, false /* forceRoundedIcon */,
-                MetricsProto.MetricsEvent.DASHBOARD_SUMMARY, preference, tile, null /* key */,
-                Preference.DEFAULT_ORDER);
+        dashboardFeatureProvider.bindPreferenceToTileAndGetObservers(activity,
+                false /* forceRoundedIcon */, MetricsProto.MetricsEvent.DASHBOARD_SUMMARY,
+                preference, tile, null /* key */, Preference.DEFAULT_ORDER);
 
         assertThat(preference.getKey()).isEqualTo(tile.getKey(mContext));
         preference.performClick();
diff --git a/tests/robotests/src/com/android/settings/dashboard/CategoryManagerTest.java b/tests/robotests/src/com/android/settings/dashboard/CategoryManagerTest.java
index 1efaed9..64eaad5 100644
--- a/tests/robotests/src/com/android/settings/dashboard/CategoryManagerTest.java
+++ b/tests/robotests/src/com/android/settings/dashboard/CategoryManagerTest.java
@@ -18,18 +18,21 @@
 
 import static com.android.settingslib.drawer.CategoryKey.CATEGORY_HOMEPAGE;
 import static com.android.settingslib.drawer.TileUtils.META_DATA_KEY_ORDER;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_KEYHINT;
 
 import static com.google.common.truth.Truth.assertThat;
 
 import android.content.Context;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.ProviderInfo;
 import android.os.Bundle;
 import android.util.Pair;
 
 import com.android.settingslib.drawer.ActivityTile;
 import com.android.settingslib.drawer.CategoryKey;
 import com.android.settingslib.drawer.DashboardCategory;
+import com.android.settingslib.drawer.ProviderTile;
 import com.android.settingslib.drawer.Tile;
 
 import org.junit.Before;
@@ -132,25 +135,9 @@
         // Create some fake tiles that are not sorted.
         final String testPackage = "com.android.test";
         final DashboardCategory category = new DashboardCategory(CATEGORY_HOMEPAGE);
-        final ActivityInfo activityInfo1 = new ActivityInfo();
-        activityInfo1.metaData = new Bundle();
-        activityInfo1.metaData.putInt(META_DATA_KEY_ORDER, 100);
-        activityInfo1.packageName = testPackage;
-        activityInfo1.name = "class1";
-        final ActivityInfo activityInfo2 = new ActivityInfo();
-        activityInfo2.metaData = new Bundle();
-        activityInfo2.metaData.putInt(META_DATA_KEY_ORDER, 50);
-        activityInfo2.packageName = testPackage;
-        activityInfo2.name = "class2";
-        final ActivityInfo activityInfo3 = new ActivityInfo();
-        activityInfo3.metaData = new Bundle();
-        activityInfo3.metaData.putInt(META_DATA_KEY_ORDER, 200);
-        activityInfo3.packageName = testPackage;
-        activityInfo3.name = "class3";
-        final Tile tile1 = new ActivityTile(activityInfo1, category.key);
-        final Tile tile2 = new ActivityTile(activityInfo2, category.key);
-        final Tile tile3 = new ActivityTile(activityInfo3, category.key);
-
+        final Tile tile1 = createActivityTile(category.key, testPackage, "class1", 100);
+        final Tile tile2 = createActivityTile(category.key, testPackage, "class2", 50);
+        final Tile tile3 = createActivityTile(category.key, testPackage, "class3", 200);
         category.addTile(tile1);
         category.addTile(tile2);
         category.addTile(tile3);
@@ -171,25 +158,9 @@
         final String testPackage1 = "com.android.test1";
         final String testPackage2 = "com.android.test2";
         final DashboardCategory category = new DashboardCategory(CATEGORY_HOMEPAGE);
-        final ActivityInfo activityInfo1 = new ActivityInfo();
-        activityInfo1.metaData = new Bundle();
-        activityInfo1.metaData.putInt(META_DATA_KEY_ORDER, 100);
-        activityInfo1.packageName = testPackage2;
-        activityInfo1.name = "class1";
-        final ActivityInfo activityInfo2 = new ActivityInfo();
-        activityInfo2.metaData = new Bundle();
-        activityInfo2.metaData.putInt(META_DATA_KEY_ORDER, 100);
-        activityInfo2.packageName = testPackage1;
-        activityInfo2.name = "class2";
-        final ActivityInfo activityInfo3 = new ActivityInfo();
-        activityInfo3.metaData = new Bundle();
-        activityInfo3.metaData.putInt(META_DATA_KEY_ORDER, 50);
-        activityInfo3.packageName = testPackage1;
-        activityInfo3.name = "class3";
-
-        final Tile tile1 = new ActivityTile(activityInfo1, category.key);
-        final Tile tile2 = new ActivityTile(activityInfo2, category.key);
-        final Tile tile3 = new ActivityTile(activityInfo3, category.key);
+        final Tile tile1 = createActivityTile(category.key, testPackage2, "class1", 100);
+        final Tile tile2 = createActivityTile(category.key, testPackage1, "class2", 100);
+        final Tile tile3 = createActivityTile(category.key, testPackage1, "class3", 50);
         category.addTile(tile1);
         category.addTile(tile2);
         category.addTile(tile3);
@@ -209,24 +180,9 @@
         // Create some fake tiles that are not sorted.
         final String testPackage = mContext.getPackageName();
         final DashboardCategory category = new DashboardCategory(CATEGORY_HOMEPAGE);
-        final ActivityInfo activityInfo1 = new ActivityInfo();
-        activityInfo1.packageName = testPackage;
-        activityInfo1.name = "class1";
-        activityInfo1.metaData = new Bundle();
-        activityInfo1.metaData.putInt(META_DATA_KEY_ORDER, 100);
-        final ActivityInfo activityInfo2 = new ActivityInfo();
-        activityInfo2.packageName = testPackage;
-        activityInfo2.name = "class2";
-        activityInfo2.metaData = new Bundle();
-        activityInfo2.metaData.putInt(META_DATA_KEY_ORDER, 100);
-        final ActivityInfo activityInfo3 = new ActivityInfo();
-        activityInfo3.packageName = testPackage;
-        activityInfo3.name = "class3";
-        activityInfo3.metaData = new Bundle();
-        activityInfo3.metaData.putInt(META_DATA_KEY_ORDER, 50);
-        final Tile tile1 = new ActivityTile(activityInfo1, category.key);
-        final Tile tile2 = new ActivityTile(activityInfo2, category.key);
-        final Tile tile3 = new ActivityTile(activityInfo3, category.key);
+        final Tile tile1 = createActivityTile(category.key, testPackage, "class1", 100);
+        final Tile tile2 = createActivityTile(category.key, testPackage, "class2", 100);
+        final Tile tile3 = createActivityTile(category.key, testPackage, "class3", 50);
         category.addTile(tile1);
         category.addTile(tile2);
         category.addTile(tile3);
@@ -247,32 +203,10 @@
         final String testPackage = mContext.getPackageName();
         final String testPackage2 = "com.google.test2";
         final DashboardCategory category = new DashboardCategory(CATEGORY_HOMEPAGE);
-
-        final ActivityInfo activityInfo1 = new ActivityInfo();
-        activityInfo1.packageName = testPackage;
-        activityInfo1.name = "class1";
-        activityInfo1.metaData = new Bundle();
-        activityInfo1.metaData.putInt(META_DATA_KEY_ORDER, 2);
-        final ActivityInfo activityInfo2 = new ActivityInfo();
-        activityInfo2.packageName = testPackage;
-        activityInfo2.name = "class2";
-        activityInfo2.metaData = new Bundle();
-        activityInfo2.metaData.putInt(META_DATA_KEY_ORDER, 1);
-        final ActivityInfo activityInfo3 = new ActivityInfo();
-        activityInfo3.packageName = testPackage2;
-        activityInfo3.name = "class0";
-        activityInfo3.metaData = new Bundle();
-        activityInfo3.metaData.putInt(META_DATA_KEY_ORDER, 0);
-        final ActivityInfo activityInfo4 = new ActivityInfo();
-        activityInfo4.packageName = testPackage;
-        activityInfo4.name = "class3";
-        activityInfo4.metaData = new Bundle();
-        activityInfo4.metaData.putInt(META_DATA_KEY_ORDER, -1);
-
-        final Tile tile1 = new ActivityTile(activityInfo1, category.key);
-        final Tile tile2 = new ActivityTile(activityInfo2, category.key);
-        final Tile tile4 = new ActivityTile(activityInfo4, category.key);
-        final Tile tile3 = new ActivityTile(activityInfo3, category.key);
+        final Tile tile1 = createActivityTile(category.key, testPackage, "class1", 2);
+        final Tile tile2 = createActivityTile(category.key, testPackage, "class2", 1);
+        final Tile tile3 = createActivityTile(category.key, testPackage2, "class0", 0);
+        final Tile tile4 = createActivityTile(category.key, testPackage, "class3", -1);
         category.addTile(tile1);
         category.addTile(tile2);
         category.addTile(tile3);
@@ -296,24 +230,9 @@
         final String testPackage2 = "com.google.test2";
         final String testPackage3 = "com.abcde.test3";
         final DashboardCategory category = new DashboardCategory(CATEGORY_HOMEPAGE);
-        final ActivityInfo activityInfo1 = new ActivityInfo();
-        activityInfo1.packageName = testPackage2;
-        activityInfo1.name = "class1";
-        activityInfo1.metaData = new Bundle();
-        activityInfo1.metaData.putInt(META_DATA_KEY_ORDER, 1);
-        final ActivityInfo activityInfo2 = new ActivityInfo();
-        activityInfo2.packageName = testPackage;
-        activityInfo2.name = "class2";
-        activityInfo2.metaData = new Bundle();
-        activityInfo2.metaData.putInt(META_DATA_KEY_ORDER, 1);
-        final ActivityInfo activityInfo3 = new ActivityInfo();
-        activityInfo3.packageName = testPackage3;
-        activityInfo3.name = "class3";
-        activityInfo3.metaData = new Bundle();
-        activityInfo3.metaData.putInt(META_DATA_KEY_ORDER, 1);
-        final Tile tile1 = new ActivityTile(activityInfo1, category.key);
-        final Tile tile2 = new ActivityTile(activityInfo2, category.key);
-        final Tile tile3 = new ActivityTile(activityInfo3, category.key);
+        final Tile tile1 = createActivityTile(category.key, testPackage2, "class1", 1);
+        final Tile tile2 = createActivityTile(category.key, testPackage, "class2", 1);
+        final Tile tile3 = createActivityTile(category.key, testPackage3, "class3", 1);
         category.addTile(tile1);
         category.addTile(tile2);
         category.addTile(tile3);
@@ -333,58 +252,36 @@
         // Create some unique tiles
         final String testPackage = mContext.getPackageName();
         final DashboardCategory category = new DashboardCategory(CATEGORY_HOMEPAGE);
-        final ActivityInfo activityInfo1 = new ActivityInfo();
-        activityInfo1.packageName = testPackage;
-        activityInfo1.name = "class1";
-        activityInfo1.metaData = new Bundle();
-        activityInfo1.metaData.putInt(META_DATA_KEY_ORDER, 100);
-        final ActivityInfo activityInfo2 = new ActivityInfo();
-        activityInfo2.packageName = testPackage;
-        activityInfo2.name = "class2";
-        activityInfo2.metaData = new Bundle();
-        activityInfo2.metaData.putInt(META_DATA_KEY_ORDER, 100);
-        final ActivityInfo activityInfo3 = new ActivityInfo();
-        activityInfo3.packageName = testPackage;
-        activityInfo3.name = "class3";
-        activityInfo3.metaData = new Bundle();
-        activityInfo3.metaData.putInt(META_DATA_KEY_ORDER, 50);
-        final Tile tile1 = new ActivityTile(activityInfo1, category.key);
-        final Tile tile2 = new ActivityTile(activityInfo2, category.key);
-        final Tile tile3 = new ActivityTile(activityInfo3, category.key);
+        final Tile tile1 = createActivityTile(category.key, testPackage, "class1", 100);
+        final Tile tile2 = createActivityTile(category.key, testPackage, "class2", 100);
+        final Tile tile3 = createActivityTile(category.key, testPackage, "class3", 50);
+        final Tile tile4 = createProviderTile(category.key, testPackage, "class1", "authority1",
+                "key1", 100);
+        final Tile tile5 = createProviderTile(category.key, testPackage, "class1", "authority2",
+                "key2", 100);
+        final Tile tile6 = createProviderTile(category.key, testPackage, "class1", "authority1",
+                "key2", 50);
         category.addTile(tile1);
         category.addTile(tile2);
         category.addTile(tile3);
+        category.addTile(tile4);
+        category.addTile(tile5);
+        category.addTile(tile6);
         mCategoryByKeyMap.put(CATEGORY_HOMEPAGE, category);
 
         mCategoryManager.filterDuplicateTiles(mCategoryByKeyMap);
 
-        assertThat(category.getTilesCount()).isEqualTo(3);
+        assertThat(category.getTilesCount()).isEqualTo(6);
     }
 
     @Test
-    public void filterTiles_hasDuplicate_shouldOnlyKeepUniqueTiles() {
+    public void filterTiles_hasDuplicateActivityTiles_shouldOnlyKeepUniqueTiles() {
         // Create tiles pointing to same intent.
         final String testPackage = mContext.getPackageName();
         final DashboardCategory category = new DashboardCategory(CATEGORY_HOMEPAGE);
-        final ActivityInfo activityInfo1 = new ActivityInfo();
-        activityInfo1.packageName = testPackage;
-        activityInfo1.name = "class1";
-        activityInfo1.metaData = new Bundle();
-        activityInfo1.metaData.putInt(META_DATA_KEY_ORDER, 100);
-        final ActivityInfo activityInfo2 = new ActivityInfo();
-        activityInfo2.packageName = testPackage;
-        activityInfo2.name = "class1";
-        activityInfo2.metaData = new Bundle();
-        activityInfo2.metaData.putInt(META_DATA_KEY_ORDER, 100);
-        final ActivityInfo activityInfo3 = new ActivityInfo();
-        activityInfo3.packageName = testPackage;
-        activityInfo3.name = "class1";
-        activityInfo3.metaData = new Bundle();
-        activityInfo3.metaData.putInt(META_DATA_KEY_ORDER, 50);
-
-        final Tile tile1 = new ActivityTile(activityInfo1, category.key);
-        final Tile tile2 = new ActivityTile(activityInfo2, category.key);
-        final Tile tile3 = new ActivityTile(activityInfo3, category.key);
+        final Tile tile1 = createActivityTile(category.key, testPackage, "class1", 100);
+        final Tile tile2 = createActivityTile(category.key, testPackage, "class1", 100);
+        final Tile tile3 = createActivityTile(category.key, testPackage, "class1", 50);
         category.addTile(tile1);
         category.addTile(tile2);
         category.addTile(tile3);
@@ -394,4 +291,47 @@
 
         assertThat(category.getTilesCount()).isEqualTo(1);
     }
+
+    @Test
+    public void filterTiles_hasDuplicateProviderTiles_shouldOnlyKeepUniqueTiles() {
+        // Create tiles pointing to same authority and key.
+        final String testPackage = mContext.getPackageName();
+        final DashboardCategory category = new DashboardCategory(CATEGORY_HOMEPAGE);
+        final Tile tile1 = createProviderTile(category.key, testPackage, "class1", "authority1",
+                "key1", 100);
+        final Tile tile2 = createProviderTile(category.key, testPackage, "class2", "authority1",
+                "key1", 100);
+        final Tile tile3 = createProviderTile(category.key, testPackage, "class3", "authority1",
+                "key1", 50);
+        category.addTile(tile1);
+        category.addTile(tile2);
+        category.addTile(tile3);
+        mCategoryByKeyMap.put(CATEGORY_HOMEPAGE, category);
+
+        mCategoryManager.filterDuplicateTiles(mCategoryByKeyMap);
+
+        assertThat(category.getTilesCount()).isEqualTo(1);
+    }
+
+    private Tile createActivityTile(String categoryKey, String packageName, String className,
+            int order) {
+        final ActivityInfo activityInfo = new ActivityInfo();
+        activityInfo.packageName = packageName;
+        activityInfo.name = className;
+        activityInfo.metaData = new Bundle();
+        activityInfo.metaData.putInt(META_DATA_KEY_ORDER, order);
+        return new ActivityTile(activityInfo, categoryKey);
+    }
+
+    private Tile createProviderTile(String categoryKey, String packageName, String className,
+            String authority, String key, int order) {
+        final ProviderInfo providerInfo = new ProviderInfo();
+        final Bundle metaData = new Bundle();
+        providerInfo.packageName = packageName;
+        providerInfo.name = className;
+        providerInfo.authority = authority;
+        metaData.putString(META_DATA_PREFERENCE_KEYHINT, key);
+        metaData.putInt(META_DATA_KEY_ORDER, order);
+        return new ProviderTile(providerInfo, categoryKey, metaData);
+    }
 }
diff --git a/tests/robotests/src/com/android/settings/dashboard/DashboardFeatureProviderImplTest.java b/tests/robotests/src/com/android/settings/dashboard/DashboardFeatureProviderImplTest.java
index 7dafd07..14fd284 100644
--- a/tests/robotests/src/com/android/settings/dashboard/DashboardFeatureProviderImplTest.java
+++ b/tests/robotests/src/com/android/settings/dashboard/DashboardFeatureProviderImplTest.java
@@ -18,10 +18,12 @@
 
 import static android.content.Intent.EXTRA_USER;
 
+import static com.android.settingslib.drawer.SwitchesProvider.EXTRA_SWITCH_SET_CHECKED_ERROR;
 import static com.android.settingslib.drawer.TileUtils.META_DATA_KEY_ORDER;
 import static com.android.settingslib.drawer.TileUtils.META_DATA_KEY_PROFILE;
 import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_KEYHINT;
 import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SUMMARY;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SWITCH_URI;
 import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_TITLE;
 import static com.android.settingslib.drawer.TileUtils.PROFILE_ALL;
 import static com.android.settingslib.drawer.TileUtils.PROFILE_PRIMARY;
@@ -43,6 +45,7 @@
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.ProviderInfo;
 import android.content.pm.ResolveInfo;
 import android.graphics.Bitmap;
 import android.graphics.drawable.Icon;
@@ -52,6 +55,7 @@
 
 import androidx.fragment.app.FragmentActivity;
 import androidx.preference.Preference;
+import androidx.preference.SwitchPreference;
 
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.settings.R;
@@ -62,6 +66,7 @@
 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
 import com.android.settingslib.drawer.ActivityTile;
 import com.android.settingslib.drawer.CategoryKey;
+import com.android.settingslib.drawer.ProviderTile;
 import com.android.settingslib.drawer.Tile;
 import com.android.settingslib.drawer.TileUtils;
 
@@ -81,11 +86,15 @@
 import org.robolectric.util.ReflectionHelpers;
 
 import java.util.ArrayList;
+import java.util.List;
 
 @RunWith(RobolectricTestRunner.class)
 @Config(shadows = ShadowUserManager.class)
 public class DashboardFeatureProviderImplTest {
 
+    private static final String KEY = "key";
+    private static final String SWITCH_URI = "content://com.android.settings/tile_switch";
+
     @Mock(answer = Answers.RETURNS_DEEP_STUBS)
     private FragmentActivity mActivity;
     @Mock(answer = Answers.RETURNS_DEEP_STUBS)
@@ -96,6 +105,8 @@
 
     private Context mContext;
     private ActivityInfo mActivityInfo;
+    private ProviderInfo mProviderInfo;
+    private Bundle mSwitchMetaData;
     private DashboardFeatureProviderImpl mImpl;
     private boolean mForceRoundedIcon;
 
@@ -112,6 +123,17 @@
         mActivityInfo.metaData.putInt(META_DATA_PREFERENCE_TITLE, R.string.settings_label);
         mActivityInfo.metaData.putInt(META_DATA_PREFERENCE_SUMMARY,
                 R.string.about_settings_summary);
+
+        mProviderInfo = new ProviderInfo();
+        mProviderInfo.packageName = mContext.getPackageName();
+        mProviderInfo.name = "provider";
+        mProviderInfo.authority = "com.android.settings";
+        mSwitchMetaData = new Bundle();
+        mSwitchMetaData.putInt(META_DATA_PREFERENCE_TITLE, R.string.settings_label);
+        mSwitchMetaData.putInt(META_DATA_PREFERENCE_SUMMARY, R.string.about_settings_summary);
+        mSwitchMetaData.putString(META_DATA_PREFERENCE_KEYHINT, KEY);
+        mSwitchMetaData.putString(META_DATA_PREFERENCE_SWITCH_URI, SWITCH_URI);
+
         doReturn(mPackageManager).when(mContext).getPackageManager();
         when(mPackageManager.resolveActivity(any(Intent.class), anyInt()))
                 .thenReturn(new ResolveInfo());
@@ -132,8 +154,8 @@
         doReturn(Icon.createWithBitmap(Bitmap.createBitmap(1, 1, Bitmap.Config.RGB_565)))
                 .when(tile).getIcon(any(Context.class));
         mActivityInfo.metaData.putString(SettingsActivity.META_DATA_KEY_FRAGMENT_CLASS, "HI");
-        mImpl.bindPreferenceToTile(mActivity, mForceRoundedIcon, MetricsEvent.SETTINGS_GESTURES,
-                preference, tile, "123", Preference.DEFAULT_ORDER);
+        mImpl.bindPreferenceToTileAndGetObservers(mActivity, mForceRoundedIcon,
+                MetricsEvent.SETTINGS_GESTURES, preference, tile, "123", Preference.DEFAULT_ORDER);
 
         assertThat(preference.getTitle()).isEqualTo(mContext.getText(R.string.settings_label));
         assertThat(preference.getSummary())
@@ -145,13 +167,34 @@
     }
 
     @Test
+    public void bindPreference_shouldBindAllSwitchData() {
+        final Preference preference = new SwitchPreference(RuntimeEnvironment.application);
+        final Tile tile = spy(new ProviderTile(mProviderInfo, CategoryKey.CATEGORY_HOMEPAGE,
+                mSwitchMetaData));
+        mSwitchMetaData.putInt(META_DATA_KEY_ORDER, 10);
+        doReturn(Icon.createWithBitmap(Bitmap.createBitmap(1, 1, Bitmap.Config.RGB_565)))
+                .when(tile).getIcon(any(Context.class));
+        final List<DynamicDataObserver> observers = mImpl.bindPreferenceToTileAndGetObservers(
+                mActivity, mForceRoundedIcon, MetricsEvent.SETTINGS_GESTURES, preference, tile,
+                null /* key*/, Preference.DEFAULT_ORDER);
+
+        assertThat(preference.getTitle()).isEqualTo(mContext.getText(R.string.settings_label));
+        assertThat(preference.getSummary())
+                .isEqualTo(mContext.getText(R.string.about_settings_summary));
+        assertThat(preference.getKey()).isEqualTo(KEY);
+        assertThat(preference.getIcon()).isNotNull();
+        assertThat(preference.getOrder()).isEqualTo(tile.getOrder());
+        assertThat(observers.get(0).getUri().toString()).isEqualTo(SWITCH_URI);
+    }
+
+    @Test
     public void bindPreference_noFragmentMetadata_shouldBindIntent() {
         final Preference preference = new Preference(RuntimeEnvironment.application);
         mActivityInfo.metaData.putInt(META_DATA_KEY_ORDER, 10);
         final Tile tile = new ActivityTile(mActivityInfo, CategoryKey.CATEGORY_HOMEPAGE);
 
-        mImpl.bindPreferenceToTile(mActivity, mForceRoundedIcon, MetricsEvent.SETTINGS_GESTURES,
-                preference, tile, "123", Preference.DEFAULT_ORDER);
+        mImpl.bindPreferenceToTileAndGetObservers(mActivity, mForceRoundedIcon,
+                MetricsEvent.SETTINGS_GESTURES, preference, tile, "123", Preference.DEFAULT_ORDER);
 
         assertThat(preference.getFragment()).isNull();
         assertThat(preference.getOnPreferenceClickListener()).isNotNull();
@@ -166,8 +209,8 @@
         tile.userHandle.add(mock(UserHandle.class));
         tile.userHandle.add(mock(UserHandle.class));
 
-        mImpl.bindPreferenceToTile(mActivity, mForceRoundedIcon, MetricsEvent.SETTINGS_GESTURES,
-                preference, tile, "123", Preference.DEFAULT_ORDER);
+        mImpl.bindPreferenceToTileAndGetObservers(mActivity, mForceRoundedIcon,
+                MetricsEvent.SETTINGS_GESTURES, preference, tile, "123", Preference.DEFAULT_ORDER);
         preference.getOnPreferenceClickListener().onPreferenceClick(null);
 
         verify(mActivity).getSupportFragmentManager();
@@ -183,8 +226,8 @@
         when(mActivity.getSystemService(Context.USER_SERVICE))
                 .thenReturn(mUserManager);
 
-        mImpl.bindPreferenceToTile(mActivity, mForceRoundedIcon, MetricsEvent.SETTINGS_GESTURES,
-                preference, tile, "123", Preference.DEFAULT_ORDER);
+        mImpl.bindPreferenceToTileAndGetObservers(mActivity, mForceRoundedIcon,
+                MetricsEvent.SETTINGS_GESTURES, preference, tile, "123", Preference.DEFAULT_ORDER);
         preference.getOnPreferenceClickListener().onPreferenceClick(null);
 
         verify(mFeatureFactory.metricsFeatureProvider).logDashboardStartIntent(
@@ -203,8 +246,8 @@
         tile.userHandle = new ArrayList<>();
         tile.userHandle.add(mock(UserHandle.class));
 
-        mImpl.bindPreferenceToTile(mActivity, mForceRoundedIcon, MetricsEvent.SETTINGS_GESTURES,
-                preference, tile, "123", Preference.DEFAULT_ORDER);
+        mImpl.bindPreferenceToTileAndGetObservers(mActivity, mForceRoundedIcon,
+                MetricsEvent.SETTINGS_GESTURES, preference, tile, "123", Preference.DEFAULT_ORDER);
         preference.getOnPreferenceClickListener().onPreferenceClick(null);
         verify(mFeatureFactory.metricsFeatureProvider).logDashboardStartIntent(
                 any(Context.class),
@@ -217,8 +260,8 @@
     @Test
     public void bindPreference_nullPreference_shouldIgnore() {
         final Tile tile = mock(Tile.class);
-        mImpl.bindPreferenceToTile(mActivity, mForceRoundedIcon, MetricsEvent.VIEW_UNKNOWN,
-                null, tile, "123", Preference.DEFAULT_ORDER);
+        mImpl.bindPreferenceToTileAndGetObservers(mActivity, mForceRoundedIcon,
+                MetricsEvent.VIEW_UNKNOWN, null, tile, "123", Preference.DEFAULT_ORDER);
 
         verifyZeroInteractions(tile);
     }
@@ -227,8 +270,9 @@
     public void bindPreference_withNullKeyNullPriority_shouldGenerateKeyAndPriority() {
         final Preference preference = new Preference(RuntimeEnvironment.application);
         final Tile tile = new ActivityTile(mActivityInfo, CategoryKey.CATEGORY_HOMEPAGE);
-        mImpl.bindPreferenceToTile(mActivity, mForceRoundedIcon, MetricsEvent.VIEW_UNKNOWN,
-                preference, tile, null /*key */, Preference.DEFAULT_ORDER);
+        mImpl.bindPreferenceToTileAndGetObservers(mActivity, mForceRoundedIcon,
+                MetricsEvent.VIEW_UNKNOWN, preference, tile, null /*key */,
+                Preference.DEFAULT_ORDER);
 
         assertThat(preference.getKey()).isNotNull();
         assertThat(preference.getOrder()).isEqualTo(Preference.DEFAULT_ORDER);
@@ -241,8 +285,9 @@
 
         final Tile tile = new ActivityTile(mActivityInfo, CategoryKey.CATEGORY_HOMEPAGE);
 
-        mImpl.bindPreferenceToTile(mActivity, mForceRoundedIcon, MetricsEvent.VIEW_UNKNOWN,
-                preference, tile, null /*key */, Preference.DEFAULT_ORDER);
+        mImpl.bindPreferenceToTileAndGetObservers(mActivity, mForceRoundedIcon,
+                MetricsEvent.VIEW_UNKNOWN, preference, tile, null /*key */,
+                Preference.DEFAULT_ORDER);
 
         assertThat(preference.getSummary())
                 .isEqualTo(RuntimeEnvironment.application.getString(R.string.summary_placeholder));
@@ -250,30 +295,99 @@
 
     @Test
     @Config(shadows = {ShadowTileUtils.class})
-    public void bindPreference_hasSummaryUri_shouldLoadSummaryFromContentProvider() {
+    public void bindPreference_hasSummaryUri_shouldLoadSummaryFromContentProviderAndHaveObserver() {
         final Preference preference = new Preference(RuntimeEnvironment.application);
         final Tile tile = new ActivityTile(mActivityInfo, CategoryKey.CATEGORY_HOMEPAGE);
-        mActivityInfo.metaData.putString(TileUtils.META_DATA_PREFERENCE_SUMMARY_URI,
-                "content://com.android.settings/tile_summary");
+        final String uriString = "content://com.android.settings/tile_summary";
+        mActivityInfo.metaData.putString(TileUtils.META_DATA_PREFERENCE_SUMMARY_URI, uriString);
 
-        mImpl.bindPreferenceToTile(mActivity, mForceRoundedIcon, MetricsEvent.VIEW_UNKNOWN,
-                preference, tile, null /*key */, Preference.DEFAULT_ORDER);
+        final List<DynamicDataObserver> observers = mImpl.bindPreferenceToTileAndGetObservers(
+                mActivity, mForceRoundedIcon, MetricsEvent.VIEW_UNKNOWN, preference, tile,
+                null /*key */, Preference.DEFAULT_ORDER);
 
         assertThat(preference.getSummary()).isEqualTo(ShadowTileUtils.MOCK_SUMMARY);
+        assertThat(observers.get(0).getUri().toString()).isEqualTo(uriString);
     }
 
     @Test
     @Config(shadows = {ShadowTileUtils.class})
-    public void bindPreference_hasTitleUri_shouldLoadFromContentProvider() {
+    public void bindPreference_hasTitleUri_shouldLoadFromContentProviderAndHaveObserver() {
         final Preference preference = new Preference(RuntimeEnvironment.application);
         final Tile tile = new ActivityTile(mActivityInfo, CategoryKey.CATEGORY_HOMEPAGE);
-        mActivityInfo.metaData.putString(TileUtils.META_DATA_PREFERENCE_TITLE_URI,
-                "content://com.android.settings/tile_title");
+        final String uriString =  "content://com.android.settings/tile_title";
+        mActivityInfo.metaData.putString(TileUtils.META_DATA_PREFERENCE_TITLE_URI, uriString);
 
-        mImpl.bindPreferenceToTile(mActivity, mForceRoundedIcon, MetricsEvent.VIEW_UNKNOWN,
-                preference, tile, null /*key */, Preference.DEFAULT_ORDER);
+        final List<DynamicDataObserver> observers = mImpl.bindPreferenceToTileAndGetObservers(
+                mActivity, mForceRoundedIcon, MetricsEvent.VIEW_UNKNOWN, preference, tile,
+                null /*key */, Preference.DEFAULT_ORDER);
 
         assertThat(preference.getTitle()).isEqualTo(ShadowTileUtils.MOCK_SUMMARY);
+        assertThat(observers.get(0).getUri().toString()).isEqualTo(uriString);
+    }
+
+    @Test
+    @Config(shadows = {ShadowTileUtils.class})
+    public void bindPreference_onCheckedChanged_shouldPutStateToContentProvider() {
+        final SwitchPreference preference = new SwitchPreference(RuntimeEnvironment.application);
+        final Tile tile = new ProviderTile(mProviderInfo, CategoryKey.CATEGORY_HOMEPAGE,
+                mSwitchMetaData);
+        final Bundle bundle = new Bundle();
+        bundle.putBoolean(EXTRA_SWITCH_SET_CHECKED_ERROR, false);
+        ShadowTileUtils.setResultBundle(bundle);
+        mImpl.bindPreferenceToTileAndGetObservers(mActivity, mForceRoundedIcon,
+                MetricsEvent.VIEW_UNKNOWN, preference, tile, null /*key */,
+                Preference.DEFAULT_ORDER);
+
+        preference.callChangeListener(false);
+
+        assertThat(ShadowTileUtils.getProviderChecked()).isFalse();
+
+        preference.callChangeListener(true);
+
+        assertThat(ShadowTileUtils.getProviderChecked()).isTrue();
+    }
+
+    @Test
+    @Config(shadows = {ShadowTileUtils.class})
+    public void bindPreference_onCheckedChangedError_shouldRevertCheckedState() {
+        final SwitchPreference preference = new SwitchPreference(RuntimeEnvironment.application);
+        final Tile tile = new ProviderTile(mProviderInfo, CategoryKey.CATEGORY_HOMEPAGE,
+                mSwitchMetaData);
+        final Bundle bundle = new Bundle();
+        bundle.putBoolean(EXTRA_SWITCH_SET_CHECKED_ERROR, true);
+        ShadowTileUtils.setResultBundle(bundle);
+        mImpl.bindPreferenceToTileAndGetObservers(mActivity, mForceRoundedIcon,
+                MetricsEvent.VIEW_UNKNOWN, preference, tile, null /*key */,
+                Preference.DEFAULT_ORDER);
+
+        preference.callChangeListener(true);
+
+        assertThat(preference.isChecked()).isFalse();
+
+        preference.callChangeListener(false);
+
+        assertThat(preference.isChecked()).isTrue();
+    }
+
+    @Test
+    @Config(shadows = {ShadowTileUtils.class})
+    public void bindPreference_callbackOnChanged_shouldLoadFromContentProvider() {
+        final SwitchPreference preference = new SwitchPreference(RuntimeEnvironment.application);
+        final Tile tile = new ProviderTile(mProviderInfo, CategoryKey.CATEGORY_HOMEPAGE,
+                mSwitchMetaData);
+        final List<DynamicDataObserver> observers = mImpl.bindPreferenceToTileAndGetObservers(
+                mActivity, mForceRoundedIcon, MetricsEvent.VIEW_UNKNOWN, preference, tile,
+                null /*key */, Preference.DEFAULT_ORDER);
+
+        ShadowTileUtils.setProviderChecked(false);
+        observers.get(0).onDataChanged();
+
+        assertThat(preference.isChecked()).isFalse();
+
+        ShadowTileUtils.setProviderChecked(true);
+        observers.get(0).onDataChanged();
+
+        assertThat(preference.isChecked()).isTrue();
     }
 
     @Test
@@ -281,8 +395,9 @@
         final Preference preference = new Preference(RuntimeEnvironment.application);
         mActivityInfo.metaData.putString(META_DATA_PREFERENCE_KEYHINT, "key");
         final Tile tile = new ActivityTile(mActivityInfo, CategoryKey.CATEGORY_HOMEPAGE);
-        mImpl.bindPreferenceToTile(mActivity, mForceRoundedIcon, MetricsEvent.VIEW_UNKNOWN,
-                preference, tile, null /* key */, Preference.DEFAULT_ORDER);
+        mImpl.bindPreferenceToTileAndGetObservers(mActivity, mForceRoundedIcon,
+                MetricsEvent.VIEW_UNKNOWN, preference, tile, null /* key */,
+                Preference.DEFAULT_ORDER);
 
         assertThat(preference.getKey()).isEqualTo(tile.getKey(mContext));
     }
@@ -308,8 +423,8 @@
         mActivityInfo.metaData.putInt(META_DATA_KEY_ORDER, 10);
         final Tile tile = new ActivityTile(mActivityInfo, CategoryKey.CATEGORY_HOMEPAGE);
 
-        mImpl.bindPreferenceToTile(mActivity, mForceRoundedIcon, MetricsEvent.VIEW_UNKNOWN,
-                preference, tile, "123", baseOrder);
+        mImpl.bindPreferenceToTileAndGetObservers(mActivity, mForceRoundedIcon,
+                MetricsEvent.VIEW_UNKNOWN, preference, tile, "123", baseOrder);
 
         assertThat(preference.getOrder()).isEqualTo(tile.getOrder() + baseOrder);
     }
@@ -321,8 +436,8 @@
         mActivityInfo.metaData.putInt(META_DATA_KEY_ORDER, 10);
         final Tile tile = new ActivityTile(mActivityInfo, CategoryKey.CATEGORY_HOMEPAGE);
         mActivityInfo.metaData.putInt(META_DATA_KEY_ORDER, testOrder);
-        mImpl.bindPreferenceToTile(mActivity, mForceRoundedIcon, MetricsEvent.VIEW_UNKNOWN,
-                preference, tile, "123", Preference.DEFAULT_ORDER);
+        mImpl.bindPreferenceToTileAndGetObservers(mActivity, mForceRoundedIcon,
+                MetricsEvent.VIEW_UNKNOWN, preference, tile, "123", Preference.DEFAULT_ORDER);
 
         assertThat(preference.getOrder()).isEqualTo(testOrder);
     }
@@ -333,8 +448,8 @@
         final Tile tile = new ActivityTile(mActivityInfo, CategoryKey.CATEGORY_HOMEPAGE);
         mActivityInfo.metaData.putString(META_DATA_KEY_ORDER, "hello");
 
-        mImpl.bindPreferenceToTile(mActivity, mForceRoundedIcon, MetricsEvent.VIEW_UNKNOWN,
-                preference, tile, "123", Preference.DEFAULT_ORDER);
+        mImpl.bindPreferenceToTileAndGetObservers(mActivity, mForceRoundedIcon,
+                MetricsEvent.VIEW_UNKNOWN, preference, tile, "123", Preference.DEFAULT_ORDER);
 
         assertThat(preference.getOrder()).isEqualTo(Preference.DEFAULT_ORDER);
     }
@@ -347,8 +462,8 @@
         mActivityInfo.metaData.putString(META_DATA_PREFERENCE_KEYHINT, "key");
         mActivityInfo.metaData.putString("com.android.settings.intent.action", "TestAction");
         tile.userHandle = null;
-        mImpl.bindPreferenceToTile(activity, mForceRoundedIcon, MetricsEvent.SETTINGS_GESTURES,
-                preference, tile, "123", Preference.DEFAULT_ORDER);
+        mImpl.bindPreferenceToTileAndGetObservers(activity, mForceRoundedIcon,
+                MetricsEvent.SETTINGS_GESTURES, preference, tile, "123", Preference.DEFAULT_ORDER);
         preference.performClick();
         ShadowActivity shadowActivity = Shadows.shadowOf(activity);
 
@@ -371,8 +486,8 @@
         mActivityInfo.metaData.putString("com.android.settings.intent.action", "TestAction");
         tile.userHandle = null;
 
-        mImpl.bindPreferenceToTile(activity, mForceRoundedIcon, MetricsEvent.SETTINGS_GESTURES,
-                preference, tile, "123", Preference.DEFAULT_ORDER);
+        mImpl.bindPreferenceToTileAndGetObservers(activity, mForceRoundedIcon,
+                MetricsEvent.SETTINGS_GESTURES, preference, tile, "123", Preference.DEFAULT_ORDER);
         preference.performClick();
 
         final ShadowActivity.IntentForResult launchIntent =
diff --git a/tests/robotests/src/com/android/settings/dashboard/DashboardFragmentTest.java b/tests/robotests/src/com/android/settings/dashboard/DashboardFragmentTest.java
index 954a872..146b37a 100644
--- a/tests/robotests/src/com/android/settings/dashboard/DashboardFragmentTest.java
+++ b/tests/robotests/src/com/android/settings/dashboard/DashboardFragmentTest.java
@@ -17,9 +17,11 @@
 
 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.DASHBOARD_CONTAINER;
 import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_KEYHINT;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SWITCH_URI;
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -29,14 +31,18 @@
 import static org.mockito.Mockito.when;
 
 import android.app.settings.SettingsEnums;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.pm.ActivityInfo;
+import android.content.pm.ProviderInfo;
 import android.net.Uri;
 import android.os.Bundle;
 
 import androidx.preference.Preference;
+import androidx.preference.PreferenceFragmentCompat;
 import androidx.preference.PreferenceManager;
 import androidx.preference.PreferenceScreen;
+import androidx.preference.SwitchPreference;
 
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.settings.core.PreferenceControllerMixin;
@@ -47,7 +53,7 @@
 import com.android.settingslib.core.instrumentation.VisibilityLoggerMixin;
 import com.android.settingslib.drawer.ActivityTile;
 import com.android.settingslib.drawer.DashboardCategory;
-import com.android.settingslib.drawer.Tile;
+import com.android.settingslib.drawer.ProviderTile;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -57,6 +63,8 @@
 import org.robolectric.RobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
 import org.robolectric.annotation.Config;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
 import org.robolectric.util.ReflectionHelpers;
 
 import java.util.ArrayList;
@@ -70,24 +78,37 @@
 
     @Mock
     private FakeFeatureFactory mFakeFeatureFactory;
-    private ActivityInfo mActivityInfo;
     private DashboardCategory mDashboardCategory;
     private Context mContext;
     private TestFragment mTestFragment;
     private List<AbstractPreferenceController> mControllers;
+    private ActivityTile mActivityTile;
+    private ProviderTile mProviderTile;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         mContext = spy(RuntimeEnvironment.application);
-        mActivityInfo = new ActivityInfo();
-        mActivityInfo.packageName = "pkg";
-        mActivityInfo.name = "class";
-        mActivityInfo.metaData = new Bundle();
-        mActivityInfo.metaData.putString(META_DATA_PREFERENCE_KEYHINT, "injected_tile_key");
+        final ActivityInfo activityInfo = new ActivityInfo();
+        activityInfo.packageName = "pkg";
+        activityInfo.name = "class";
+        activityInfo.metaData = new Bundle();
+        activityInfo.metaData.putString(META_DATA_PREFERENCE_KEYHINT, "injected_tile_key");
         mFakeFeatureFactory = FakeFeatureFactory.setupForTest();
         mDashboardCategory = new DashboardCategory("key");
-        mDashboardCategory.addTile(new ActivityTile(mActivityInfo, mDashboardCategory.key));
+        mActivityTile = new ActivityTile(activityInfo, mDashboardCategory.key);
+        mDashboardCategory.addTile(mActivityTile);
+
+        final ProviderInfo providerInfo = new ProviderInfo();
+        providerInfo.packageName = "pkg";
+        providerInfo.name = "provider";
+        providerInfo.authority = "authority";
+        final Bundle metaData = new Bundle();
+        metaData.putString(META_DATA_PREFERENCE_KEYHINT, "injected_tile_key2");
+        metaData.putString(META_DATA_PREFERENCE_SWITCH_URI, "uri");
+        mProviderTile = new ProviderTile(providerInfo, mDashboardCategory.key, metaData);
+        mDashboardCategory.addTile(mProviderTile);
+
         mTestFragment = new TestFragment(RuntimeEnvironment.application);
         when(mFakeFeatureFactory.dashboardFeatureProvider
                 .getTilesForCategory(nullable(String.class)))
@@ -127,11 +148,14 @@
                 .getTilesForCategory(nullable(String.class)))
                 .thenReturn(mDashboardCategory);
         when(mFakeFeatureFactory.dashboardFeatureProvider
-                .getDashboardKeyForTile(nullable(Tile.class)))
+                .getDashboardKeyForTile(any(ActivityTile.class)))
                 .thenReturn("test_key");
+        when(mFakeFeatureFactory.dashboardFeatureProvider
+                .getDashboardKeyForTile(any(ProviderTile.class)))
+                .thenReturn("test_key2");
         mTestFragment.onCreatePreferences(new Bundle(), "rootKey");
 
-        verify(mTestFragment.mScreen).addPreference(nullable(Preference.class));
+        verify(mTestFragment.mScreen, times(2)).addPreference(nullable(Preference.class));
     }
 
     @Test
@@ -156,8 +180,11 @@
                 .getTilesForCategory(nullable(String.class)))
                 .thenReturn(mDashboardCategory);
         when(mFakeFeatureFactory.dashboardFeatureProvider
-                .getDashboardKeyForTile(nullable(Tile.class)))
+                .getDashboardKeyForTile(any(ActivityTile.class)))
                 .thenReturn("test_key");
+        when(mFakeFeatureFactory.dashboardFeatureProvider
+                .getDashboardKeyForTile(any(ProviderTile.class)))
+                .thenReturn("test_key2");
         mTestFragment.onCreatePreferences(new Bundle(), "rootKey");
 
         verify(mTestFragment.mScreen, never()).addPreference(nullable(Preference.class));
@@ -172,6 +199,29 @@
     }
 
     @Test
+    @Config(shadows = ShadowPreferenceFragmentCompat.class)
+    public void onStart_shouldRegisterDynamicDataObservers() {
+        final DynamicDataObserver observer = new TestDynamicDataObserver();
+        mTestFragment.mDashboardTilePrefKeys.put("key", Arrays.asList(observer));
+
+        mTestFragment.onStart();
+
+        verify(mTestFragment.getContentResolver()).registerContentObserver(observer.getUri(), false,
+                observer);
+    }
+
+    @Test
+    @Config(shadows = ShadowPreferenceFragmentCompat.class)
+    public void onStop_shouldUnregisterDynamicDataObservers() {
+        final DynamicDataObserver observer = new TestDynamicDataObserver();
+        mTestFragment.registerDynamicDataObservers(Arrays.asList(observer));
+
+        mTestFragment.onStop();
+
+        verify(mTestFragment.getContentResolver()).unregisterContentObserver(observer);
+    }
+
+    @Test
     public void updateState_skipUnavailablePrefs() {
         final List<AbstractPreferenceController> preferenceControllers = mTestFragment.mControllers;
         final AbstractPreferenceController mockController1 =
@@ -266,7 +316,14 @@
         assertThat(mTestFragment.mBlockerController).isNull();
     }
 
-    public static class TestPreferenceController extends AbstractPreferenceController
+    @Test
+    public void createPreference_isProviderTile_returnSwitchPreference() {
+        final Preference pref = mTestFragment.createPreference(mProviderTile);
+
+        assertThat(pref).isInstanceOf(SwitchPreference.class);
+    }
+
+    private static class TestPreferenceController extends AbstractPreferenceController
             implements PreferenceControllerMixin {
 
         private TestPreferenceController(Context context) {
@@ -293,18 +350,20 @@
         }
     }
 
-    public static class TestFragment extends DashboardFragment {
+    private static class TestFragment extends DashboardFragment {
+
+        public final PreferenceScreen mScreen;
 
         private final PreferenceManager mPreferenceManager;
         private final Context mContext;
         private final List<AbstractPreferenceController> mControllers;
-
-        public final PreferenceScreen mScreen;
+        private final ContentResolver mContentResolver;
 
         public TestFragment(Context context) {
             mContext = context;
             mPreferenceManager = mock(PreferenceManager.class);
             mScreen = mock(PreferenceScreen.class);
+            mContentResolver = mock(ContentResolver.class);
             mControllers = new ArrayList<>();
 
             when(mPreferenceManager.getContext()).thenReturn(mContext);
@@ -346,5 +405,36 @@
         public PreferenceManager getPreferenceManager() {
             return mPreferenceManager;
         }
+
+        @Override
+        protected ContentResolver getContentResolver() {
+            return mContentResolver;
+        }
+    }
+
+    private static class TestDynamicDataObserver extends DynamicDataObserver {
+
+        @Override
+        public Uri getUri() {
+            return Uri.parse("content://abc");
+        }
+
+        @Override
+        public void onDataChanged() {
+        }
+    }
+
+    @Implements(PreferenceFragmentCompat.class)
+    public static class ShadowPreferenceFragmentCompat {
+
+        @Implementation
+        public void onStart() {
+            // do nothing
+        }
+
+        @Implementation
+        public void onStop() {
+            // do nothing
+        }
     }
 }
diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowTileUtils.java b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowTileUtils.java
index e46d72f..e1b815e 100644
--- a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowTileUtils.java
+++ b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowTileUtils.java
@@ -19,6 +19,7 @@
 import android.content.Context;
 import android.content.IContentProvider;
 import android.net.Uri;
+import android.os.Bundle;
 import android.util.Pair;
 
 import com.android.settings.R;
@@ -35,6 +36,9 @@
 
     public static final String MOCK_SUMMARY = "summary";
 
+    private static boolean sChecked;
+    private static Bundle sResult;
+
     @Implementation
     protected static String getTextFromUri(Context context, Uri uri,
             Map<String, IContentProvider> providerMap, String key) {
@@ -46,4 +50,29 @@
             Uri uri, Map<String, IContentProvider> providerMap) {
         return Pair.create(RuntimeEnvironment.application.getPackageName(), R.drawable.ic_settings_accent);
     }
+
+    @Implementation
+    public static boolean getBooleanFromUri(Context context, Uri uri,
+            Map<String, IContentProvider> providerMap, String key) {
+        return sChecked;
+    }
+
+    @Implementation
+    public static Bundle putBooleanToUri(Context context, Uri uri,
+            Map<String, IContentProvider> providerMap, String key, boolean value) {
+        sChecked = value;
+        return sResult;
+    }
+
+    public static boolean getProviderChecked() {
+        return sChecked;
+    }
+
+    public static void setProviderChecked(boolean value) {
+        sChecked = value;
+    }
+
+    public static void setResultBundle(Bundle result) {
+        sResult = result;
+    }
 }