Merge "Downgrade a WTF to a W" into udc-dev am: 878b61b063

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/23450796

Change-Id: Ie09bf6c9cc6a65d73e11841ac6c8849efedd286b
Signed-off-by: Automerger Merge Worker <[email protected]>
diff --git a/core/res/res/layout/shutdown_dialog.xml b/core/res/res/layout/shutdown_dialog.xml
index ec67aa8..726c255 100644
--- a/core/res/res/layout/shutdown_dialog.xml
+++ b/core/res/res/layout/shutdown_dialog.xml
@@ -40,7 +40,7 @@
         android:fontFamily="@string/config_headlineFontFamily"/>
 
     <TextView
-        android:id="@+id/text2"
+        android:id="@id/text2"
         android:layout_width="wrap_content"
         android:layout_height="32sp"
         android:text="@string/shutdown_progress"
diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml
index fd74185..8420934 100644
--- a/core/res/res/values/config_telephony.xml
+++ b/core/res/res/values/config_telephony.xml
@@ -167,9 +167,4 @@
          true, routing from the android emergency number database will be ignored. -->
     <bool name="ignore_emergency_number_routing_from_db">false</bool>
     <java-symbol type="bool" name="ignore_emergency_number_routing_from_db" />
-
-    <!-- Whether "Virtual DSDA", i.e. in-call IMS connectivity can be provided on both subs with
-         only single logical modem, by using its data connection in addition to cellular IMS. -->
-    <bool name="config_enable_virtual_dsda">false</bool>
-    <java-symbol type="bool" name="config_enable_virtual_dsda" />
 </resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 6ab671a..d70c283 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -5021,6 +5021,7 @@
   <java-symbol type="bool" name="config_batteryStatsResetOnUnplugHighBatteryLevel" />
   <java-symbol type="bool" name="config_batteryStatsResetOnUnplugAfterSignificantCharge" />
 
+
   <java-symbol name="materialColorOnSecondaryFixedVariant" type="attr"/>
   <java-symbol name="materialColorOnTertiaryFixedVariant" type="attr"/>
   <java-symbol name="materialColorSurfaceContainerLowest" type="attr"/>
diff --git a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/DynamicSummary.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/DynamicSummary.java
index c9d9b57..5b7899b 100644
--- a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/DynamicSummary.java
+++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/DynamicSummary.java
@@ -16,7 +16,7 @@
 
 package com.android.settingslib.drawer;
 
-/** Interface for {@link SwitchController} whose instances support dynamic summary */
+/** Interface for {@link EntryController} whose instances support dynamic summary */
 public interface DynamicSummary {
     /** @return the dynamic summary text */
     String getDynamicSummary();
diff --git a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/DynamicTitle.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/DynamicTitle.java
index af711dd..cb15773 100644
--- a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/DynamicTitle.java
+++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/DynamicTitle.java
@@ -16,7 +16,7 @@
 
 package com.android.settingslib.drawer;
 
-/** Interface for {@link SwitchController} whose instances support dynamic title */
+/** Interface for {@link EntryController} whose instances support dynamic title */
 public interface DynamicTitle {
     /** @return the dynamic title text */
     String getDynamicTitle();
diff --git a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/EntriesProvider.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/EntriesProvider.java
new file mode 100644
index 0000000..1c14c0a
--- /dev/null
+++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/EntriesProvider.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2023 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.settingslib.drawer;
+
+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_TITLE;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.pm.ProviderInfo;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * An abstract class for injecting entries to Settings.
+ */
+public abstract class EntriesProvider extends ContentProvider {
+    private static final String TAG = "EntriesProvider";
+
+    public static final String METHOD_GET_ENTRY_DATA = "getEntryData";
+    public static final String METHOD_GET_PROVIDER_ICON = "getProviderIcon";
+    public static final String METHOD_GET_DYNAMIC_TITLE = "getDynamicTitle";
+    public static final String METHOD_GET_DYNAMIC_SUMMARY = "getDynamicSummary";
+    public static final String METHOD_IS_CHECKED = "isChecked";
+    public static final String METHOD_ON_CHECKED_CHANGED = "onCheckedChanged";
+
+    /**
+     * @deprecated use {@link #METHOD_GET_ENTRY_DATA} instead.
+     */
+    @Deprecated
+    public static final String METHOD_GET_SWITCH_DATA = "getSwitchData";
+
+    public static final String EXTRA_ENTRY_DATA = "entry_data";
+    public static final String EXTRA_SWITCH_CHECKED_STATE = "checked_state";
+    public static final String EXTRA_SWITCH_SET_CHECKED_ERROR = "set_checked_error";
+    public static final String EXTRA_SWITCH_SET_CHECKED_ERROR_MESSAGE = "set_checked_error_message";
+
+    /**
+     * @deprecated use {@link #EXTRA_ENTRY_DATA} instead.
+     */
+    @Deprecated
+    public static final String EXTRA_SWITCH_DATA = "switch_data";
+
+    private String mAuthority;
+    private final Map<String, EntryController> mControllerMap = new LinkedHashMap<>();
+    private final List<Bundle> mEntryDataList = new ArrayList<>();
+
+    /**
+     * Get a list of {@link EntryController} for this provider.
+     */
+    protected abstract List<? extends EntryController> createEntryControllers();
+
+    protected EntryController getController(String key) {
+        return mControllerMap.get(key);
+    }
+
+    @Override
+    public void attachInfo(Context context, ProviderInfo info) {
+        mAuthority = info.authority;
+        Log.i(TAG, mAuthority);
+        super.attachInfo(context, info);
+    }
+
+    @Override
+    public boolean onCreate() {
+        final List<? extends EntryController> controllers = createEntryControllers();
+        if (controllers == null || controllers.isEmpty()) {
+            throw new IllegalArgumentException();
+        }
+
+        for (EntryController controller : controllers) {
+            final String key = controller.getKey();
+            if (TextUtils.isEmpty(key)) {
+                throw new NullPointerException("Entry key cannot be null: "
+                        + controller.getClass().getSimpleName());
+            } else if (mControllerMap.containsKey(key)) {
+                throw new IllegalArgumentException("Entry key " + key + " is duplicated by: "
+                        + controller.getClass().getSimpleName());
+            }
+
+            controller.setAuthority(mAuthority);
+            mControllerMap.put(key, controller);
+            if (!(controller instanceof PrimarySwitchController)) {
+                mEntryDataList.add(controller.getBundle());
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public Bundle call(String method, String uriString, Bundle extras) {
+        final Bundle bundle = new Bundle();
+        final String key = extras != null
+                ? extras.getString(META_DATA_PREFERENCE_KEYHINT)
+                : null;
+        if (TextUtils.isEmpty(key)) {
+            switch (method) {
+                case METHOD_GET_ENTRY_DATA:
+                    bundle.putParcelableList(EXTRA_ENTRY_DATA, mEntryDataList);
+                    return bundle;
+                case METHOD_GET_SWITCH_DATA:
+                    bundle.putParcelableList(EXTRA_SWITCH_DATA, mEntryDataList);
+                    return bundle;
+                default:
+                    return null;
+            }
+        }
+
+        final EntryController controller = mControllerMap.get(key);
+        if (controller == null) {
+            return null;
+        }
+
+        switch (method) {
+            case METHOD_GET_ENTRY_DATA:
+            case METHOD_GET_SWITCH_DATA:
+                if (!(controller instanceof PrimarySwitchController)) {
+                    return controller.getBundle();
+                }
+                break;
+            case METHOD_GET_PROVIDER_ICON:
+                if (controller instanceof ProviderIcon) {
+                    return ((ProviderIcon) controller).getProviderIcon();
+                }
+                break;
+            case METHOD_GET_DYNAMIC_TITLE:
+                if (controller instanceof DynamicTitle) {
+                    bundle.putString(META_DATA_PREFERENCE_TITLE,
+                            ((DynamicTitle) controller).getDynamicTitle());
+                    return bundle;
+                }
+                break;
+            case METHOD_GET_DYNAMIC_SUMMARY:
+                if (controller instanceof DynamicSummary) {
+                    bundle.putString(META_DATA_PREFERENCE_SUMMARY,
+                            ((DynamicSummary) controller).getDynamicSummary());
+                    return bundle;
+                }
+                break;
+            case METHOD_IS_CHECKED:
+                if (controller instanceof ProviderSwitch) {
+                    bundle.putBoolean(EXTRA_SWITCH_CHECKED_STATE,
+                            ((ProviderSwitch) controller).isSwitchChecked());
+                    return bundle;
+                }
+                break;
+            case METHOD_ON_CHECKED_CHANGED:
+                if (controller instanceof ProviderSwitch) {
+                    return onSwitchCheckedChanged(extras.getBoolean(EXTRA_SWITCH_CHECKED_STATE),
+                            (ProviderSwitch) controller);
+                }
+                break;
+        }
+        return null;
+    }
+
+    private Bundle onSwitchCheckedChanged(boolean checked, ProviderSwitch controller) {
+        final boolean success = controller.onSwitchCheckedChanged(checked);
+        final Bundle bundle = new Bundle();
+        bundle.putBoolean(EXTRA_SWITCH_SET_CHECKED_ERROR, !success);
+        if (success) {
+            if (controller instanceof DynamicSummary) {
+                ((EntryController) controller).notifySummaryChanged(getContext());
+            }
+        } else {
+            bundle.putString(EXTRA_SWITCH_SET_CHECKED_ERROR_MESSAGE,
+                    controller.getSwitchErrorMessage(checked));
+        }
+        return bundle;
+    }
+
+    @Override
+    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+            String sortOrder) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getType(Uri uri) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Uri insert(Uri uri, ContentValues values) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public int delete(Uri uri, String selection, String[] selectionArgs) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+        throw new UnsupportedOperationException();
+    }
+}
+
diff --git a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/EntryController.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/EntryController.java
new file mode 100644
index 0000000..5d6e6a3
--- /dev/null
+++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/EntryController.java
@@ -0,0 +1,246 @@
+/*
+ * Copyright (C) 2023 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.settingslib.drawer;
+
+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.TileUtils.EXTRA_CATEGORY_KEY;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_KEY_ORDER;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON_BACKGROUND_ARGB;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON_BACKGROUND_HINT;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON_TINTABLE;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON_URI;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_KEYHINT;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_PENDING_INTENT;
+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;
+
+import android.app.PendingIntent;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.net.Uri;
+import android.os.Bundle;
+
+import androidx.annotation.DrawableRes;
+import androidx.annotation.NonNull;
+import androidx.annotation.StringRes;
+
+/**
+ * A controller that manages events for switch.
+ */
+public abstract class EntryController {
+
+    private String mAuthority;
+
+    /**
+     * Returns the key for this switch.
+     */
+    public abstract String getKey();
+
+    /**
+     * Returns the {@link MetaData} for this switch.
+     */
+    protected abstract MetaData getMetaData();
+
+    /**
+     * Notify registered observers that title was updated and attempt to sync changes.
+     */
+    public void notifyTitleChanged(Context context) {
+        if (this instanceof DynamicTitle) {
+            notifyChanged(context, METHOD_GET_DYNAMIC_TITLE);
+        }
+    }
+
+    /**
+     * Notify registered observers that summary was updated and attempt to sync changes.
+     */
+    public void notifySummaryChanged(Context context) {
+        if (this instanceof DynamicSummary) {
+            notifyChanged(context, METHOD_GET_DYNAMIC_SUMMARY);
+        }
+    }
+
+    void setAuthority(String authority) {
+        mAuthority = authority;
+    }
+
+    Bundle getBundle() {
+        final MetaData metaData = getMetaData();
+        if (metaData == null) {
+            throw new NullPointerException("Should not return null in getMetaData()");
+        }
+
+        final Bundle bundle = metaData.build();
+        final String uriString = new Uri.Builder()
+                .scheme(ContentResolver.SCHEME_CONTENT)
+                .authority(mAuthority)
+                .build()
+                .toString();
+        bundle.putString(META_DATA_PREFERENCE_KEYHINT, getKey());
+        if (this instanceof ProviderIcon) {
+            bundle.putString(META_DATA_PREFERENCE_ICON_URI, uriString);
+        }
+        if (this instanceof DynamicTitle) {
+            bundle.putString(META_DATA_PREFERENCE_TITLE_URI, uriString);
+        }
+        if (this instanceof DynamicSummary) {
+            bundle.putString(META_DATA_PREFERENCE_SUMMARY_URI, uriString);
+        }
+        if (this instanceof ProviderSwitch) {
+            bundle.putString(META_DATA_PREFERENCE_SWITCH_URI, uriString);
+        }
+        return bundle;
+    }
+
+    private void notifyChanged(Context context, String method) {
+        final Uri uri = TileUtils.buildUri(mAuthority, method, getKey());
+        context.getContentResolver().notifyChange(uri, null);
+    }
+
+    /**
+     * Collects all meta data of the item.
+     */
+    protected static class MetaData {
+        private String mCategory;
+        private int mOrder;
+        @DrawableRes
+        private int mIcon;
+        private int mIconBackgroundHint;
+        private int mIconBackgroundArgb;
+        private Boolean mIconTintable;
+        @StringRes
+        private int mTitleId;
+        private String mTitle;
+        @StringRes
+        private int mSummaryId;
+        private String mSummary;
+        private PendingIntent mPendingIntent;
+
+        /**
+         * @param category the category of the switch. This value must be from {@link CategoryKey}.
+         */
+        public MetaData(@NonNull String category) {
+            mCategory = category;
+        }
+
+        /**
+         * Set the order of the item that should be displayed on screen. Bigger value items displays
+         * closer on top.
+         */
+        public MetaData setOrder(int order) {
+            mOrder = order;
+            return this;
+        }
+
+        /** Set the icon that should be displayed for the item. */
+        public MetaData setIcon(@DrawableRes int icon) {
+            mIcon = icon;
+            return this;
+        }
+
+        /** Set the icon background color. The value may or may not be used by Settings app. */
+        public MetaData setIconBackgoundHint(int hint) {
+            mIconBackgroundHint = hint;
+            return this;
+        }
+
+        /** Set the icon background color as raw ARGB. */
+        public MetaData setIconBackgoundArgb(int argb) {
+            mIconBackgroundArgb = argb;
+            return this;
+        }
+
+        /** Specify whether the icon is tintable. */
+        public MetaData setIconTintable(boolean tintable) {
+            mIconTintable = tintable;
+            return this;
+        }
+
+        /** Set the title that should be displayed for the item. */
+        public MetaData setTitle(@StringRes int id) {
+            mTitleId = id;
+            return this;
+        }
+
+        /** Set the title that should be displayed for the item. */
+        public MetaData setTitle(String title) {
+            mTitle = title;
+            return this;
+        }
+
+        /** Set the summary text that should be displayed for the item. */
+        public MetaData setSummary(@StringRes int id) {
+            mSummaryId = id;
+            return this;
+        }
+
+        /** Set the summary text that should be displayed for the item. */
+        public MetaData setSummary(String summary) {
+            mSummary = summary;
+            return this;
+        }
+
+        public MetaData setPendingIntent(PendingIntent pendingIntent) {
+            mPendingIntent = pendingIntent;
+            return this;
+        }
+
+        protected Bundle build() {
+            final Bundle bundle = new Bundle();
+            bundle.putString(EXTRA_CATEGORY_KEY, mCategory);
+
+            if (mOrder != 0) {
+                bundle.putInt(META_DATA_KEY_ORDER, mOrder);
+            }
+
+            if (mIcon != 0) {
+                bundle.putInt(META_DATA_PREFERENCE_ICON, mIcon);
+            }
+            if (mIconBackgroundHint != 0) {
+                bundle.putInt(META_DATA_PREFERENCE_ICON_BACKGROUND_HINT, mIconBackgroundHint);
+            }
+            if (mIconBackgroundArgb != 0) {
+                bundle.putInt(META_DATA_PREFERENCE_ICON_BACKGROUND_ARGB, mIconBackgroundArgb);
+            }
+            if (mIconTintable != null) {
+                bundle.putBoolean(META_DATA_PREFERENCE_ICON_TINTABLE, mIconTintable);
+            }
+
+            if (mTitleId != 0) {
+                bundle.putInt(META_DATA_PREFERENCE_TITLE, mTitleId);
+            } else if (mTitle != null) {
+                bundle.putString(META_DATA_PREFERENCE_TITLE, mTitle);
+            }
+
+            if (mSummaryId != 0) {
+                bundle.putInt(META_DATA_PREFERENCE_SUMMARY, mSummaryId);
+            } else if (mSummary != null) {
+                bundle.putString(META_DATA_PREFERENCE_SUMMARY, mSummary);
+            }
+
+            if (mPendingIntent != null) {
+                bundle.putParcelable(META_DATA_PREFERENCE_PENDING_INTENT, mPendingIntent);
+            }
+
+            return bundle;
+        }
+    }
+}
diff --git a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/ProviderIcon.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/ProviderIcon.java
index 2945d5c1..3aa6fcb 100644
--- a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/ProviderIcon.java
+++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/ProviderIcon.java
@@ -19,7 +19,7 @@
 import android.os.Bundle;
 
 /**
- *  Interface for {@link SwitchController} whose instances support icon provided from the content
+ *  Interface for {@link EntryController} whose instances support icon provided from the content
  *  provider
  */
 public interface ProviderIcon {
diff --git a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/ProviderSwitch.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/ProviderSwitch.java
new file mode 100644
index 0000000..47eb31c
--- /dev/null
+++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/ProviderSwitch.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2023 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.settingslib.drawer;
+
+/**
+ *  Interface for {@link EntryController} whose instances support switch widget provided from the
+ *  content provider
+ */
+public interface ProviderSwitch {
+    /**
+     * Returns the checked state of this switch.
+     */
+    boolean isSwitchChecked();
+
+    /**
+     * Called when the checked state of this switch is changed.
+     *
+     * @return true if the checked state was successfully changed, otherwise false
+     */
+    boolean onSwitchCheckedChanged(boolean checked);
+
+    /**
+     * Returns the error message which will be toasted when {@link #onSwitchCheckedChanged} returns
+     * false.
+     */
+    String getSwitchErrorMessage(boolean attemptedChecked);
+}
diff --git a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/ProviderTile.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/ProviderTile.java
index 54da585..b775e93 100644
--- a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/ProviderTile.java
+++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/ProviderTile.java
@@ -75,7 +75,7 @@
             if (infoList != null && !infoList.isEmpty()) {
                 final ProviderInfo providerInfo = infoList.get(0).providerInfo;
                 mComponentInfo = providerInfo;
-                setMetaData(TileUtils.getSwitchDataFromProvider(context, providerInfo.authority,
+                setMetaData(TileUtils.getEntryDataFromProvider(context, providerInfo.authority,
                         mKey));
             } else {
                 Log.e(TAG, "Cannot find package info for "
diff --git a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/SwitchController.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/SwitchController.java
index 23669b2..a1a4e58 100644
--- a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/SwitchController.java
+++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/SwitchController.java
@@ -16,38 +16,16 @@
 
 package com.android.settingslib.drawer;
 
-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_IS_CHECKED;
-import static com.android.settingslib.drawer.TileUtils.EXTRA_CATEGORY_KEY;
-import static com.android.settingslib.drawer.TileUtils.META_DATA_KEY_ORDER;
-import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON;
-import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON_BACKGROUND_ARGB;
-import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON_BACKGROUND_HINT;
-import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON_TINTABLE;
-import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON_URI;
-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_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;
-
-import android.content.ContentResolver;
-import android.content.Context;
-import android.net.Uri;
-import android.os.Bundle;
-
-import androidx.annotation.DrawableRes;
 import androidx.annotation.NonNull;
-import androidx.annotation.StringRes;
 
 /**
  * A controller that manages events for switch.
+ *
+ * @deprecated Use {@link EntriesProvider} with {@link ProviderSwitch} instead.
  */
-public abstract class SwitchController {
+@Deprecated
+public abstract class SwitchController extends EntryController implements ProviderSwitch {
 
-    private String mAuthority;
 
     /**
      * Returns the key for this switch.
@@ -55,11 +33,6 @@
     public abstract String getSwitchKey();
 
     /**
-     * Returns the {@link MetaData} for this switch.
-     */
-    protected abstract MetaData getMetaData();
-
-    /**
      * Returns the checked state of this switch.
      */
     protected abstract boolean isChecked();
@@ -76,181 +49,41 @@
      */
     protected abstract String getErrorMessage(boolean attemptedChecked);
 
-    /**
-     * Notify registered observers that title was updated and attempt to sync changes.
-     */
-    public void notifyTitleChanged(Context context) {
-        if (this instanceof DynamicTitle) {
-            notifyChanged(context, METHOD_GET_DYNAMIC_TITLE);
-        }
+    @Override
+    public String getKey() {
+        return getSwitchKey();
+    }
+
+    @Override
+    public boolean isSwitchChecked() {
+        return isChecked();
+    }
+
+    @Override
+    public boolean onSwitchCheckedChanged(boolean checked) {
+        return onCheckedChanged(checked);
+    }
+
+    @Override
+    public String getSwitchErrorMessage(boolean attemptedChecked) {
+        return getErrorMessage(attemptedChecked);
     }
 
     /**
-     * Notify registered observers that summary was updated and attempt to sync changes.
+     * Same as {@link EntryController.MetaData}, for backwards compatibility purpose.
+     *
+     * @deprecated Use {@link EntryController.MetaData} instead.
      */
-    public void notifySummaryChanged(Context context) {
-        if (this instanceof DynamicSummary) {
-            notifyChanged(context, METHOD_GET_DYNAMIC_SUMMARY);
-        }
-    }
-
-    /**
-     * Notify registered observers that checked state was updated and attempt to sync changes.
-     */
-    public void notifyCheckedChanged(Context context) {
-        notifyChanged(context, METHOD_IS_CHECKED);
-    }
-
-    void setAuthority(String authority) {
-        mAuthority = authority;
-    }
-
-    Bundle getBundle() {
-        final MetaData metaData = getMetaData();
-        if (metaData == null) {
-            throw new NullPointerException("Should not return null in getMetaData()");
-        }
-
-        final Bundle bundle = metaData.build();
-        final String uriString = new Uri.Builder()
-                .scheme(ContentResolver.SCHEME_CONTENT)
-                .authority(mAuthority)
-                .build()
-                .toString();
-        bundle.putString(META_DATA_PREFERENCE_KEYHINT, getSwitchKey());
-        bundle.putString(META_DATA_PREFERENCE_SWITCH_URI, uriString);
-        if (this instanceof ProviderIcon) {
-            bundle.putString(META_DATA_PREFERENCE_ICON_URI, uriString);
-        }
-        if (this instanceof DynamicTitle) {
-            bundle.putString(META_DATA_PREFERENCE_TITLE_URI, uriString);
-        }
-        if (this instanceof DynamicSummary) {
-            bundle.putString(META_DATA_PREFERENCE_SUMMARY_URI, uriString);
-        }
-        return bundle;
-    }
-
-    private void notifyChanged(Context context, String method) {
-        final Uri uri = TileUtils.buildUri(mAuthority, method, getSwitchKey());
-        context.getContentResolver().notifyChange(uri, null);
-    }
-
-    /**
-     * Collects all meta data of the item.
-     */
-    protected static class MetaData {
-        private String mCategory;
-        private int mOrder;
-        @DrawableRes
-        private int mIcon;
-        private int mIconBackgroundHint;
-        private int mIconBackgroundArgb;
-        private Boolean mIconTintable;
-        @StringRes
-        private int mTitleId;
-        private String mTitle;
-        @StringRes
-        private int mSummaryId;
-        private String mSummary;
-
+    @Deprecated
+    protected static class MetaData extends EntryController.MetaData {
         /**
          * @param category the category of the switch. This value must be from {@link CategoryKey}.
+         *
+         * @deprecated Use {@link EntryController.MetaData} instead.
          */
+        @Deprecated
         public MetaData(@NonNull String category) {
-            mCategory = category;
-        }
-
-        /**
-         * Set the order of the item that should be displayed on screen. Bigger value items displays
-         * closer on top.
-         */
-        public MetaData setOrder(int order) {
-            mOrder = order;
-            return this;
-        }
-
-        /** Set the icon that should be displayed for the item. */
-        public MetaData setIcon(@DrawableRes int icon) {
-            mIcon = icon;
-            return this;
-        }
-
-        /** Set the icon background color. The value may or may not be used by Settings app. */
-        public MetaData setIconBackgoundHint(int hint) {
-            mIconBackgroundHint = hint;
-            return this;
-        }
-
-        /** Set the icon background color as raw ARGB. */
-        public MetaData setIconBackgoundArgb(int argb) {
-            mIconBackgroundArgb = argb;
-            return this;
-        }
-
-        /** Specify whether the icon is tintable. */
-        public MetaData setIconTintable(boolean tintable) {
-            mIconTintable = tintable;
-            return this;
-        }
-
-        /** Set the title that should be displayed for the item. */
-        public MetaData setTitle(@StringRes int id) {
-            mTitleId = id;
-            return this;
-        }
-
-        /** Set the title that should be displayed for the item. */
-        public MetaData setTitle(String title) {
-            mTitle = title;
-            return this;
-        }
-
-        /** Set the summary text that should be displayed for the item. */
-        public MetaData setSummary(@StringRes int id) {
-            mSummaryId = id;
-            return this;
-        }
-
-        /** Set the summary text that should be displayed for the item. */
-        public MetaData setSummary(String summary) {
-            mSummary = summary;
-            return this;
-        }
-
-        private Bundle build() {
-            final Bundle bundle = new Bundle();
-            bundle.putString(EXTRA_CATEGORY_KEY, mCategory);
-
-            if (mOrder != 0) {
-                bundle.putInt(META_DATA_KEY_ORDER, mOrder);
-            }
-
-            if (mIcon != 0) {
-                bundle.putInt(META_DATA_PREFERENCE_ICON, mIcon);
-            }
-            if (mIconBackgroundHint != 0) {
-                bundle.putInt(META_DATA_PREFERENCE_ICON_BACKGROUND_HINT, mIconBackgroundHint);
-            }
-            if (mIconBackgroundArgb != 0) {
-                bundle.putInt(META_DATA_PREFERENCE_ICON_BACKGROUND_ARGB, mIconBackgroundArgb);
-            }
-            if (mIconTintable != null) {
-                bundle.putBoolean(META_DATA_PREFERENCE_ICON_TINTABLE, mIconTintable);
-            }
-
-            if (mTitleId != 0) {
-                bundle.putInt(META_DATA_PREFERENCE_TITLE, mTitleId);
-            } else if (mTitle != null) {
-                bundle.putString(META_DATA_PREFERENCE_TITLE, mTitle);
-            }
-
-            if (mSummaryId != 0) {
-                bundle.putInt(META_DATA_PREFERENCE_SUMMARY, mSummaryId);
-            } else if (mSummary != null) {
-                bundle.putString(META_DATA_PREFERENCE_SUMMARY, mSummary);
-            }
-            return bundle;
+            super(category);
         }
     }
 }
diff --git a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/SwitchesProvider.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/SwitchesProvider.java
index f2b3e30..ad00ced 100644
--- a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/SwitchesProvider.java
+++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/SwitchesProvider.java
@@ -16,46 +16,15 @@
 
 package com.android.settingslib.drawer;
 
-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_TITLE;
-
-import android.content.ContentProvider;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.pm.ProviderInfo;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Bundle;
-import android.text.TextUtils;
-import android.util.Log;
-
-import java.util.ArrayList;
-import java.util.LinkedHashMap;
 import java.util.List;
-import java.util.Map;
 
 /**
  * An abstract class for injecting switches to Settings.
+ *
+ * @deprecated Use {@link EntriesProvider} instead.
  */
-public abstract class SwitchesProvider extends ContentProvider {
-    private static final String TAG = "SwitchesProvider";
-
-    public static final String METHOD_GET_SWITCH_DATA = "getSwitchData";
-    public static final String METHOD_GET_PROVIDER_ICON = "getProviderIcon";
-    public static final String METHOD_GET_DYNAMIC_TITLE = "getDynamicTitle";
-    public static final String METHOD_GET_DYNAMIC_SUMMARY = "getDynamicSummary";
-    public static final String METHOD_IS_CHECKED = "isChecked";
-    public static final String METHOD_ON_CHECKED_CHANGED = "onCheckedChanged";
-
-    public static final String EXTRA_SWITCH_DATA = "switch_data";
-    public static final String EXTRA_SWITCH_CHECKED_STATE = "checked_state";
-    public static final String EXTRA_SWITCH_SET_CHECKED_ERROR = "set_checked_error";
-    public static final String EXTRA_SWITCH_SET_CHECKED_ERROR_MESSAGE = "set_checked_error_message";
-
-    private String mAuthority;
-    private final Map<String, SwitchController> mControllerMap = new LinkedHashMap<>();
-    private final List<Bundle> mSwitchDataList = new ArrayList<>();
+@Deprecated
+public abstract class SwitchesProvider extends EntriesProvider {
 
     /**
      * Get a list of {@link SwitchController} for this provider.
@@ -63,129 +32,7 @@
     protected abstract List<SwitchController> createSwitchControllers();
 
     @Override
-    public void attachInfo(Context context, ProviderInfo info) {
-        mAuthority = info.authority;
-        Log.i(TAG, mAuthority);
-        super.attachInfo(context, info);
-    }
-
-    @Override
-    public boolean onCreate() {
-        final List<SwitchController> controllers = createSwitchControllers();
-        if (controllers == null || controllers.isEmpty()) {
-            throw new IllegalArgumentException();
-        }
-
-        controllers.forEach(controller -> {
-            final String key = controller.getSwitchKey();
-            if (TextUtils.isEmpty(key)) {
-                throw new NullPointerException("Switch key cannot be null: "
-                        + controller.getClass().getSimpleName());
-            } else if (mControllerMap.containsKey(key)) {
-                throw new IllegalArgumentException("Switch key " + key + " is duplicated by: "
-                        + controller.getClass().getSimpleName());
-            }
-
-            controller.setAuthority(mAuthority);
-            mControllerMap.put(key, controller);
-            if (!(controller instanceof PrimarySwitchController)) {
-                mSwitchDataList.add(controller.getBundle());
-            }
-        });
-        return true;
-    }
-
-    @Override
-    public Bundle call(String method, String uriString, Bundle extras) {
-        final Bundle bundle = new Bundle();
-        final String key = extras != null
-                ? extras.getString(META_DATA_PREFERENCE_KEYHINT)
-                : null;
-        if (TextUtils.isEmpty(key)) {
-            if (METHOD_GET_SWITCH_DATA.equals(method)) {
-                bundle.putParcelableList(EXTRA_SWITCH_DATA, mSwitchDataList);
-                return bundle;
-            }
-            return null;
-        }
-
-        final SwitchController controller = mControllerMap.get(key);
-        if (controller == null) {
-            return null;
-        }
-
-        switch (method) {
-            case METHOD_GET_SWITCH_DATA:
-                if (!(controller instanceof PrimarySwitchController)) {
-                    return controller.getBundle();
-                }
-                break;
-            case METHOD_GET_PROVIDER_ICON:
-                if (controller instanceof ProviderIcon) {
-                    return ((ProviderIcon) controller).getProviderIcon();
-                }
-                break;
-            case METHOD_GET_DYNAMIC_TITLE:
-                if (controller instanceof DynamicTitle) {
-                    bundle.putString(META_DATA_PREFERENCE_TITLE,
-                            ((DynamicTitle) controller).getDynamicTitle());
-                    return bundle;
-                }
-                break;
-            case METHOD_GET_DYNAMIC_SUMMARY:
-                if (controller instanceof DynamicSummary) {
-                    bundle.putString(META_DATA_PREFERENCE_SUMMARY,
-                            ((DynamicSummary) controller).getDynamicSummary());
-                    return bundle;
-                }
-                break;
-            case METHOD_IS_CHECKED:
-                bundle.putBoolean(EXTRA_SWITCH_CHECKED_STATE, controller.isChecked());
-                return bundle;
-            case METHOD_ON_CHECKED_CHANGED:
-                return onCheckedChanged(extras.getBoolean(EXTRA_SWITCH_CHECKED_STATE), controller);
-        }
-        return null;
-    }
-
-    private Bundle onCheckedChanged(boolean checked, SwitchController controller) {
-        final boolean success = controller.onCheckedChanged(checked);
-        final Bundle bundle = new Bundle();
-        bundle.putBoolean(EXTRA_SWITCH_SET_CHECKED_ERROR, !success);
-        if (success) {
-            if (controller instanceof DynamicSummary) {
-                controller.notifySummaryChanged(getContext());
-            }
-        } else {
-            bundle.putString(EXTRA_SWITCH_SET_CHECKED_ERROR_MESSAGE,
-                    controller.getErrorMessage(checked));
-        }
-        return bundle;
-    }
-
-    @Override
-    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
-            String sortOrder) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public String getType(Uri uri) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public Uri insert(Uri uri, ContentValues values) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public int delete(Uri uri, String selection, String[] selectionArgs) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
-        throw new UnsupportedOperationException();
+    protected List<? extends EntryController> createEntryControllers() {
+        return createSwitchControllers();
     }
 }
diff --git a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java
index a0c8ac4..00dd8cc 100644
--- a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java
+++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java
@@ -19,6 +19,7 @@
 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_NEW_TASK;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_GROUP_KEY;
 import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON;
 import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_KEYHINT;
 import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SUMMARY;
@@ -29,6 +30,7 @@
 import static com.android.settingslib.drawer.TileUtils.PROFILE_ALL;
 import static com.android.settingslib.drawer.TileUtils.PROFILE_PRIMARY;
 
+import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ComponentInfo;
@@ -47,6 +49,7 @@
 
 import java.util.ArrayList;
 import java.util.Comparator;
+import java.util.HashMap;
 
 /**
  * Description of a single dashboard tile that the user can select.
@@ -60,6 +63,8 @@
      */
     public ArrayList<UserHandle> userHandle = new ArrayList<>();
 
+    public HashMap<UserHandle, PendingIntent> pendingIntentMap = new HashMap<>();
+
     @VisibleForTesting
     long mLastUpdateTime;
     private final String mComponentPackage;
@@ -186,6 +191,13 @@
     }
 
     /**
+     * Check whether tile has a pending intent.
+     */
+    public boolean hasPendingIntent() {
+        return !pendingIntentMap.isEmpty();
+    }
+
+    /**
      * Title of the tile that is shown to the user.
      */
     public CharSequence getTitle(Context context) {
@@ -395,6 +407,76 @@
         return TextUtils.equals(profile, PROFILE_PRIMARY);
     }
 
+    /**
+     * Returns whether the tile belongs to another group / category.
+     */
+    public boolean hasGroupKey() {
+        return mMetaData != null
+                && !TextUtils.isEmpty(mMetaData.getString(META_DATA_PREFERENCE_GROUP_KEY));
+    }
+
+    /**
+     * Returns the group / category key this tile belongs to.
+     */
+    public String getGroupKey() {
+        return (mMetaData == null) ? null : mMetaData.getString(META_DATA_PREFERENCE_GROUP_KEY);
+    }
+
+    /**
+     * The type of the tile.
+     */
+    public enum Type {
+        /**
+         * A preference that can be tapped on to open a new page.
+         */
+        ACTION,
+
+        /**
+         * A preference that can be tapped on to open an external app.
+         */
+        EXTERNAL_ACTION,
+
+        /**
+         * A preference that shows an on / off switch that can be toggled by the user.
+         */
+        SWITCH,
+
+        /**
+         * A preference with both an on / off switch, and a tappable area that can perform an
+         * action.
+         */
+        SWITCH_WITH_ACTION,
+
+        /**
+         * A preference category with a title that can be used to group multiple preferences
+         * together.
+         */
+        GROUP;
+    }
+
+    /**
+     * Returns the type of the tile.
+     *
+     * @see Type
+     */
+    public Type getType() {
+        boolean hasExternalAction = hasPendingIntent();
+        boolean hasAction = hasExternalAction || this instanceof ActivityTile;
+        boolean hasSwitch = hasSwitch();
+
+        if (hasSwitch && hasAction) {
+            return Type.SWITCH_WITH_ACTION;
+        } else if (hasSwitch) {
+            return Type.SWITCH;
+        } else if (hasExternalAction) {
+            return Type.EXTERNAL_ACTION;
+        } else if (hasAction) {
+            return Type.ACTION;
+        } else {
+            return Type.GROUP;
+        }
+    }
+
     public static final Comparator<Tile> TILE_COMPARATOR =
             (lhs, rhs) -> rhs.getOrder() - lhs.getOrder();
 }
diff --git a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java
index acc0087..e46db75 100644
--- a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java
+++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java
@@ -113,6 +113,12 @@
     public static final String META_DATA_PREFERENCE_KEYHINT = "com.android.settings.keyhint";
 
     /**
+     * Name of the meta-data item that can be set in the AndroidManifest.xml or in the content
+     * provider to specify the key of a group / category where this preference belongs to.
+     */
+    public static final String META_DATA_PREFERENCE_GROUP_KEY = "com.android.settings.group_key";
+
+    /**
      * Order of the item that should be displayed on screen. Bigger value items displays closer on
      * top.
      */
@@ -202,6 +208,13 @@
             "com.android.settings.switch_uri";
 
     /**
+     * Name of the meta-data item that can be set from the content provider providing the intent
+     * that will be executed when the user taps on the preference.
+     */
+    public static final String META_DATA_PREFERENCE_PENDING_INTENT =
+            "com.android.settings.pending_intent";
+
+    /**
      * Value for {@link #META_DATA_KEY_PROFILE}. When the device has a managed profile,
      * the app will always be run in the primary profile.
      *
@@ -331,12 +344,12 @@
                 continue;
             }
             final ProviderInfo providerInfo = resolved.providerInfo;
-            final List<Bundle> switchData = getSwitchDataFromProvider(context,
+            final List<Bundle> entryData = getEntryDataFromProvider(context,
                     providerInfo.authority);
-            if (switchData == null || switchData.isEmpty()) {
+            if (entryData == null || entryData.isEmpty()) {
                 continue;
             }
-            for (Bundle metaData : switchData) {
+            for (Bundle metaData : entryData) {
                 loadTile(user, addedCache, defaultCategory, outTiles, intent, metaData,
                         providerInfo);
             }
@@ -386,27 +399,43 @@
         if (!tile.userHandle.contains(user)) {
             tile.userHandle.add(user);
         }
+        if (metaData.containsKey(META_DATA_PREFERENCE_PENDING_INTENT)) {
+            tile.pendingIntentMap.put(
+                    user, metaData.getParcelable(META_DATA_PREFERENCE_PENDING_INTENT));
+        }
         if (!outTiles.contains(tile)) {
             outTiles.add(tile);
         }
     }
 
-    /** Returns the switch data of the key specified from the provider */
+    /** Returns the entry data of the key specified from the provider */
     // TODO(b/144732809): rearrange methods by access level modifiers
-    static Bundle getSwitchDataFromProvider(Context context, String authority, String key) {
+    static Bundle getEntryDataFromProvider(Context context, String authority, String key) {
         final Map<String, IContentProvider> providerMap = new ArrayMap<>();
-        final Uri uri = buildUri(authority, SwitchesProvider.METHOD_GET_SWITCH_DATA, key);
-        return getBundleFromUri(context, uri, providerMap, null /* bundle */);
+        final Uri uri = buildUri(authority, EntriesProvider.METHOD_GET_ENTRY_DATA, key);
+        Bundle result = getBundleFromUri(context, uri, providerMap, null /* bundle */);
+        if (result == null) {
+            Uri fallbackUri = buildUri(authority, EntriesProvider.METHOD_GET_SWITCH_DATA, key);
+            result = getBundleFromUri(context, fallbackUri, providerMap, null /* bundle */);
+        }
+        return result;
     }
 
-    /** Returns all switch data from the provider */
-    private static List<Bundle> getSwitchDataFromProvider(Context context, String authority) {
+    /** Returns all entry data from the provider */
+    private static List<Bundle> getEntryDataFromProvider(Context context, String authority) {
         final Map<String, IContentProvider> providerMap = new ArrayMap<>();
-        final Uri uri = buildUri(authority, SwitchesProvider.METHOD_GET_SWITCH_DATA);
+        final Uri uri = buildUri(authority, EntriesProvider.METHOD_GET_ENTRY_DATA);
         final Bundle result = getBundleFromUri(context, uri, providerMap, null /* bundle */);
-        return result != null
-                ? result.getParcelableArrayList(SwitchesProvider.EXTRA_SWITCH_DATA)
-                : null;
+        if (result != null) {
+            return result.getParcelableArrayList(EntriesProvider.EXTRA_ENTRY_DATA);
+        } else {
+            Uri fallbackUri = buildUri(authority, EntriesProvider.METHOD_GET_SWITCH_DATA);
+            Bundle fallbackResult =
+                    getBundleFromUri(context, fallbackUri, providerMap, null /* bundle */);
+            return fallbackResult != null
+                    ? fallbackResult.getParcelableArrayList(EntriesProvider.EXTRA_SWITCH_DATA)
+                    : null;
+        }
     }
 
     /**
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/MetricsFeatureProvider.java b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/MetricsFeatureProvider.java
index 09abc39..9ee8a32 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/MetricsFeatureProvider.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/MetricsFeatureProvider.java
@@ -209,8 +209,7 @@
         }
         final ComponentName cn = intent.getComponent();
         final String key = cn != null ? cn.flattenToString() : intent.getAction();
-        return logSettingsTileClick(key + (isWorkProfile ? "/work" : "/personal"),
-                sourceMetricsCategory);
+        return logSettingsTileClickWithProfile(key, sourceMetricsCategory, isWorkProfile);
     }
 
     /**
@@ -226,4 +225,20 @@
         clicked(sourceMetricsCategory, logKey);
         return true;
     }
+
+    /**
+     * Logs an event when the setting key is clicked with a specific profile from Profile select
+     * dialog.
+     *
+     * @return true if the key is loggable, otherwise false
+     */
+    public boolean logSettingsTileClickWithProfile(String logKey, int sourceMetricsCategory,
+            boolean isWorkProfile) {
+        if (TextUtils.isEmpty(logKey)) {
+            // Not loggable
+            return false;
+        }
+        clicked(sourceMetricsCategory, logKey + (isWorkProfile ? "/work" : "/personal"));
+        return true;
+    }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/MetricsFeatureProviderTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/MetricsFeatureProviderTest.java
index 3352d86..dd8d54a 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/MetricsFeatureProviderTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/MetricsFeatureProviderTest.java
@@ -203,4 +203,24 @@
         assertThat(loggable).isFalse();
         verifyNoMoreInteractions(mLogWriter);
     }
+
+    @Test
+    public void logSettingsTileClickWithProfile_isPersonalProfile_shouldTagPersonal() {
+        final String key = "abc";
+        final boolean loggable = mProvider.logSettingsTileClickWithProfile(key,
+                MetricsEvent.SETTINGS_GESTURES, false);
+
+        assertThat(loggable).isTrue();
+        verify(mLogWriter).clicked(MetricsEvent.SETTINGS_GESTURES, "abc/personal");
+    }
+
+    @Test
+    public void logSettingsTileClickWithProfile_isWorkProfile_shouldTagWork() {
+        final String key = "abc";
+        final boolean loggable = mProvider.logSettingsTileClickWithProfile(key,
+                MetricsEvent.SETTINGS_GESTURES, true);
+
+        assertThat(loggable).isTrue();
+        verify(mLogWriter).clicked(MetricsEvent.SETTINGS_GESTURES, "abc/work");
+    }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/ActivityTileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/ActivityTileTest.java
index aa6b0bf..4d2b1ae 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/ActivityTileTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/ActivityTileTest.java
@@ -17,19 +17,23 @@
 
 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_GROUP_KEY;
 import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON;
 import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON_URI;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SWITCH_URI;
 import static com.android.settingslib.drawer.TileUtils.PROFILE_ALL;
 import static com.android.settingslib.drawer.TileUtils.PROFILE_PRIMARY;
 
 import static com.google.common.truth.Truth.assertThat;
 
+import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.ResolveInfo;
 import android.os.Bundle;
+import android.os.UserHandle;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -191,4 +195,65 @@
 
         assertThat(tile.getTitle(RuntimeEnvironment.application)).isNull();
     }
+
+    @Test
+    public void hasPendingIntent_empty_returnsFalse() {
+        final Tile tile = new ActivityTile(mActivityInfo, "category");
+
+        assertThat(tile.hasPendingIntent()).isFalse();
+    }
+
+    @Test
+    public void hasPendingIntent_notEmpty_returnsTrue() {
+        final Tile tile = new ActivityTile(mActivityInfo, "category");
+        tile.pendingIntentMap.put(
+                UserHandle.CURRENT, PendingIntent.getActivity(mContext, 0, new Intent(), 0));
+
+        assertThat(tile.hasPendingIntent()).isTrue();
+    }
+
+    @Test
+    public void hasGroupKey_empty_returnsFalse() {
+        final Tile tile = new ActivityTile(mActivityInfo, "category");
+
+        assertThat(tile.hasGroupKey()).isFalse();
+    }
+
+    @Test
+    public void hasGroupKey_notEmpty_returnsTrue() {
+        mActivityInfo.metaData.putString(META_DATA_PREFERENCE_GROUP_KEY, "test_key");
+        final Tile tile = new ActivityTile(mActivityInfo, "category");
+
+        assertThat(tile.hasGroupKey()).isTrue();
+    }
+
+    @Test
+    public void getGroupKey_empty_returnsNull() {
+        final Tile tile = new ActivityTile(mActivityInfo, "category");
+
+        assertThat(tile.getGroupKey()).isNull();
+    }
+
+    @Test
+    public void getGroupKey_notEmpty_returnsValue() {
+        mActivityInfo.metaData.putString(META_DATA_PREFERENCE_GROUP_KEY, "test_key");
+        final Tile tile = new ActivityTile(mActivityInfo, "category");
+
+        assertThat(tile.getGroupKey()).isEqualTo("test_key");
+    }
+
+    @Test
+    public void getType_withoutSwitch_returnsAction() {
+        final Tile tile = new ActivityTile(mActivityInfo, "category");
+
+        assertThat(tile.getType()).isEqualTo(Tile.Type.ACTION);
+    }
+
+    @Test
+    public void getType_withSwitch_returnsSwitchWithAction() {
+        mActivityInfo.metaData.putString(META_DATA_PREFERENCE_SWITCH_URI, "test://testabc/");
+        final Tile tile = new ActivityTile(mActivityInfo, "category");
+
+        assertThat(tile.getType()).isEqualTo(Tile.Type.SWITCH_WITH_ACTION);
+    }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/EntriesProviderTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/EntriesProviderTest.java
new file mode 100644
index 0000000..a248330
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/EntriesProviderTest.java
@@ -0,0 +1,472 @@
+/*
+ * Copyright (C) 2023 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.settingslib.drawer;
+
+import static com.android.settingslib.drawer.EntriesProvider.EXTRA_ENTRY_DATA;
+import static com.android.settingslib.drawer.EntriesProvider.EXTRA_SWITCH_CHECKED_STATE;
+import static com.android.settingslib.drawer.EntriesProvider.EXTRA_SWITCH_DATA;
+import static com.android.settingslib.drawer.EntriesProvider.EXTRA_SWITCH_SET_CHECKED_ERROR;
+import static com.android.settingslib.drawer.EntriesProvider.EXTRA_SWITCH_SET_CHECKED_ERROR_MESSAGE;
+import static com.android.settingslib.drawer.EntriesProvider.METHOD_GET_DYNAMIC_SUMMARY;
+import static com.android.settingslib.drawer.EntriesProvider.METHOD_GET_DYNAMIC_TITLE;
+import static com.android.settingslib.drawer.EntriesProvider.METHOD_GET_ENTRY_DATA;
+import static com.android.settingslib.drawer.EntriesProvider.METHOD_GET_PROVIDER_ICON;
+import static com.android.settingslib.drawer.EntriesProvider.METHOD_GET_SWITCH_DATA;
+import static com.android.settingslib.drawer.EntriesProvider.METHOD_IS_CHECKED;
+import static com.android.settingslib.drawer.EntriesProvider.METHOD_ON_CHECKED_CHANGED;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_KEYHINT;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_PENDING_INTENT;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SUMMARY;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_TITLE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ProviderInfo;
+import android.os.Bundle;
+
+import com.android.settingslib.drawer.EntryController.MetaData;
+import com.android.settingslib.drawer.PrimarySwitchControllerTest.TestPrimarySwitchController;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(RobolectricTestRunner.class)
+public class EntriesProviderTest {
+
+    @Rule
+    public final ExpectedException thrown = ExpectedException.none();
+
+    private Context mContext;
+    private ProviderInfo mProviderInfo;
+
+    private TestEntriesProvider mEntriesProvider;
+
+    @Before
+    public void setUp() {
+        mContext = RuntimeEnvironment.application;
+        mEntriesProvider = new TestEntriesProvider();
+        mProviderInfo = new ProviderInfo();
+        mProviderInfo.authority = "auth";
+    }
+
+    @Test
+    public void attachInfo_noController_shouldThrowIllegalArgumentException() {
+        thrown.expect(IllegalArgumentException.class);
+
+        mEntriesProvider.attachInfo(mContext, mProviderInfo);
+    }
+
+    @Test
+    public void attachInfo_NoKeyInController_shouldThrowNullPointerException() {
+        thrown.expect(NullPointerException.class);
+        final TestEntryController controller = new TestEntryController();
+        mEntriesProvider.addController(controller);
+
+        mEntriesProvider.attachInfo(mContext, mProviderInfo);
+    }
+
+    @Test
+    public void attachInfo_NoMetaDataInController_shouldThrowNullPointerException() {
+        thrown.expect(NullPointerException.class);
+        final TestEntryController controller = new TestEntryController();
+        controller.setKey("123");
+        mEntriesProvider.addController(controller);
+
+        mEntriesProvider.attachInfo(mContext, mProviderInfo);
+    }
+
+    @Test
+    public void attachInfo_duplicateKey_shouldThrowIllegalArgumentException() {
+        thrown.expect(IllegalArgumentException.class);
+        final TestEntryController controller1 = new TestEntryController();
+        final TestEntryController controller2 = new TestEntryController();
+        controller1.setKey("123");
+        controller2.setKey("123");
+        controller1.setMetaData(new MetaData("category"));
+        controller2.setMetaData(new MetaData("category"));
+        mEntriesProvider.addController(controller1);
+        mEntriesProvider.addController(controller2);
+
+        mEntriesProvider.attachInfo(mContext, mProviderInfo);
+    }
+
+    @Test
+    public void attachInfo_hasDifferentControllers_shouldNotThrowException() {
+        final TestEntryController controller1 = new TestEntryController();
+        final TestEntryController controller2 = new TestEntryController();
+        controller1.setKey("123");
+        controller2.setKey("456");
+        controller1.setMetaData(new MetaData("category"));
+        controller2.setMetaData(new MetaData("category"));
+        mEntriesProvider.addController(controller1);
+        mEntriesProvider.addController(controller2);
+
+        mEntriesProvider.attachInfo(mContext, mProviderInfo);
+    }
+
+    @Test
+    public void getEntryData_shouldNotReturnPrimarySwitchData() {
+        final EntryController controller = new TestPrimarySwitchController("123");
+        mEntriesProvider.addController(controller);
+        mEntriesProvider.attachInfo(mContext, mProviderInfo);
+
+        final Bundle switchData = mEntriesProvider.call(METHOD_GET_ENTRY_DATA, "uri",
+                null /* extras*/);
+
+        final ArrayList<Bundle> dataList = switchData.getParcelableArrayList(EXTRA_ENTRY_DATA);
+        assertThat(dataList).isEmpty();
+    }
+
+    @Test
+    public void getEntryData_shouldReturnDataList() {
+        final TestEntryController controller = new TestEntryController();
+        final PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+        controller.setKey("123");
+        controller.setMetaData(new MetaData("category").setPendingIntent(pendingIntent));
+        mEntriesProvider.addController(controller);
+        mEntriesProvider.attachInfo(mContext, mProviderInfo);
+
+        final Bundle entryData = mEntriesProvider.call(METHOD_GET_ENTRY_DATA, "uri",
+                null /* extras*/);
+
+        final ArrayList<Bundle> dataList = entryData.getParcelableArrayList(EXTRA_ENTRY_DATA);
+        assertThat(dataList).hasSize(1);
+        assertThat(dataList.get(0).getString(META_DATA_PREFERENCE_KEYHINT)).isEqualTo("123");
+        assertThat(dataList.get(0).getParcelable(META_DATA_PREFERENCE_PENDING_INTENT,
+                PendingIntent.class))
+                .isEqualTo(pendingIntent);
+    }
+
+    @Test
+    public void getSwitchData_shouldReturnDataList() {
+        final TestEntryController controller = new TestEntryController();
+        final PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+        controller.setKey("123");
+        controller.setMetaData(new MetaData("category").setPendingIntent(pendingIntent));
+        mEntriesProvider.addController(controller);
+        mEntriesProvider.attachInfo(mContext, mProviderInfo);
+
+        final Bundle entryData = mEntriesProvider.call(METHOD_GET_SWITCH_DATA, "uri",
+                null /* extras*/);
+
+        final ArrayList<Bundle> dataList = entryData.getParcelableArrayList(EXTRA_SWITCH_DATA);
+        assertThat(dataList).hasSize(1);
+        assertThat(dataList.get(0).getString(META_DATA_PREFERENCE_KEYHINT)).isEqualTo("123");
+        assertThat(dataList.get(0).getParcelable(META_DATA_PREFERENCE_PENDING_INTENT,
+                PendingIntent.class))
+                .isEqualTo(pendingIntent);
+    }
+
+    @Test
+    public void getEntryDataByKey_shouldReturnData() {
+        final Bundle extras = new Bundle();
+        extras.putString(META_DATA_PREFERENCE_KEYHINT, "123");
+        final TestEntryController controller = new TestEntryController();
+        controller.setKey("123");
+        controller.setMetaData(new MetaData("category"));
+        mEntriesProvider.addController(controller);
+        mEntriesProvider.attachInfo(mContext, mProviderInfo);
+
+        final Bundle entryData = mEntriesProvider.call(METHOD_GET_ENTRY_DATA, "uri", extras);
+
+        assertThat(entryData.getString(META_DATA_PREFERENCE_KEYHINT)).isEqualTo("123");
+    }
+
+    @Test
+    public void getSwitchDataByKey_shouldReturnData() {
+        final Bundle extras = new Bundle();
+        extras.putString(META_DATA_PREFERENCE_KEYHINT, "123");
+        final TestEntryController controller = new TestEntryController();
+        controller.setKey("123");
+        controller.setMetaData(new MetaData("category"));
+        mEntriesProvider.addController(controller);
+        mEntriesProvider.attachInfo(mContext, mProviderInfo);
+
+        final Bundle entryData = mEntriesProvider.call(METHOD_GET_SWITCH_DATA, "uri", extras);
+
+        assertThat(entryData.getString(META_DATA_PREFERENCE_KEYHINT)).isEqualTo("123");
+    }
+
+    @Test
+    public void isSwitchChecked_shouldReturnCheckedState() {
+        final Bundle extras = new Bundle();
+        extras.putString(META_DATA_PREFERENCE_KEYHINT, "123");
+        final TestSwitchController controller = new TestSwitchController();
+        controller.setKey("123");
+        controller.setMetaData(new MetaData("category"));
+        mEntriesProvider.addController(controller);
+        mEntriesProvider.attachInfo(mContext, mProviderInfo);
+
+        controller.setSwitchChecked(true);
+        Bundle result = mEntriesProvider.call(METHOD_IS_CHECKED, "uri", extras);
+
+        assertThat(result.getBoolean(EXTRA_SWITCH_CHECKED_STATE)).isTrue();
+
+        controller.setSwitchChecked(false);
+        result = mEntriesProvider.call(METHOD_IS_CHECKED, "uri", extras);
+
+        assertThat(result.getBoolean(EXTRA_SWITCH_CHECKED_STATE)).isFalse();
+    }
+
+    @Test
+    public void getProviderIcon_noImplementInterface_shouldReturnNull() {
+        final Bundle extras = new Bundle();
+        extras.putString(META_DATA_PREFERENCE_KEYHINT, "123");
+        final TestEntryController controller = new TestEntryController();
+        controller.setKey("123");
+        controller.setMetaData(new MetaData("category"));
+        mEntriesProvider.addController(controller);
+        mEntriesProvider.attachInfo(mContext, mProviderInfo);
+
+        final Bundle iconBundle = mEntriesProvider.call(METHOD_GET_PROVIDER_ICON, "uri", extras);
+
+        assertThat(iconBundle).isNull();
+    }
+
+    @Test
+    public void getProviderIcon_implementInterface_shouldReturnIcon() {
+        final Bundle extras = new Bundle();
+        extras.putString(META_DATA_PREFERENCE_KEYHINT, "123");
+        final TestEntryController controller = new TestDynamicController();
+        controller.setKey("123");
+        controller.setMetaData(new MetaData("category"));
+        mEntriesProvider.addController(controller);
+        mEntriesProvider.attachInfo(mContext, mProviderInfo);
+
+        final Bundle iconBundle = mEntriesProvider.call(METHOD_GET_PROVIDER_ICON, "uri", extras);
+
+        assertThat(iconBundle).isEqualTo(TestDynamicController.ICON_BUNDLE);
+    }
+
+    @Test
+    public void getDynamicTitle_noImplementInterface_shouldReturnNull() {
+        final Bundle extras = new Bundle();
+        extras.putString(META_DATA_PREFERENCE_KEYHINT, "123");
+        final TestEntryController controller = new TestEntryController();
+        controller.setKey("123");
+        controller.setMetaData(new MetaData("category"));
+        mEntriesProvider.addController(controller);
+        mEntriesProvider.attachInfo(mContext, mProviderInfo);
+
+        final Bundle result = mEntriesProvider.call(METHOD_GET_DYNAMIC_TITLE, "uri", extras);
+
+        assertThat(result).isNull();
+    }
+
+    @Test
+    public void getDynamicTitle_implementInterface_shouldReturnTitle() {
+        final Bundle extras = new Bundle();
+        extras.putString(META_DATA_PREFERENCE_KEYHINT, "123");
+        final TestEntryController controller = new TestDynamicController();
+        controller.setKey("123");
+        controller.setMetaData(new MetaData("category"));
+        mEntriesProvider.addController(controller);
+        mEntriesProvider.attachInfo(mContext, mProviderInfo);
+
+        final Bundle result = mEntriesProvider.call(METHOD_GET_DYNAMIC_TITLE, "uri", extras);
+
+        assertThat(result.getString(META_DATA_PREFERENCE_TITLE))
+                .isEqualTo(TestDynamicController.TITLE);
+    }
+
+    @Test
+    public void getDynamicSummary_noImplementInterface_shouldReturnNull() {
+        final Bundle extras = new Bundle();
+        extras.putString(META_DATA_PREFERENCE_KEYHINT, "123");
+        final TestEntryController controller = new TestEntryController();
+        controller.setKey("123");
+        controller.setMetaData(new MetaData("category"));
+        mEntriesProvider.addController(controller);
+        mEntriesProvider.attachInfo(mContext, mProviderInfo);
+
+        final Bundle result = mEntriesProvider.call(METHOD_GET_DYNAMIC_SUMMARY, "uri", extras);
+
+        assertThat(result).isNull();
+    }
+
+    @Test
+    public void getDynamicSummary_implementInterface_shouldReturnSummary() {
+        final Bundle extras = new Bundle();
+        extras.putString(META_DATA_PREFERENCE_KEYHINT, "123");
+        final TestEntryController controller = new TestDynamicController();
+        controller.setKey("123");
+        controller.setMetaData(new MetaData("category"));
+        mEntriesProvider.addController(controller);
+        mEntriesProvider.attachInfo(mContext, mProviderInfo);
+
+        final Bundle result = mEntriesProvider.call(METHOD_GET_DYNAMIC_SUMMARY, "uri", extras);
+
+        assertThat(result.getString(META_DATA_PREFERENCE_SUMMARY))
+                .isEqualTo(TestDynamicController.SUMMARY);
+    }
+
+    @Test
+    public void onSwitchCheckedChangedSuccess_shouldReturnNoError() {
+        final Bundle extras = new Bundle();
+        extras.putString(META_DATA_PREFERENCE_KEYHINT, "123");
+        final TestSwitchController controller = new TestSwitchController();
+        controller.setKey("123");
+        controller.setMetaData(new MetaData("category"));
+        mEntriesProvider.addController(controller);
+        mEntriesProvider.attachInfo(mContext, mProviderInfo);
+
+        final Bundle result = mEntriesProvider.call(METHOD_ON_CHECKED_CHANGED, "uri", extras);
+
+        assertThat(result.getBoolean(EXTRA_SWITCH_SET_CHECKED_ERROR)).isFalse();
+    }
+
+    @Test
+    public void onSwitchCheckedChangedFailed_shouldReturnErrorMessage() {
+        final Bundle extras = new Bundle();
+        extras.putString(META_DATA_PREFERENCE_KEYHINT, "123");
+        final TestSwitchController controller = new TestSwitchController();
+        controller.setKey("123");
+        controller.setMetaData(new MetaData("category"));
+        controller.setSwitchErrorMessage("error");
+        mEntriesProvider.addController(controller);
+        mEntriesProvider.attachInfo(mContext, mProviderInfo);
+
+        final Bundle result = mEntriesProvider.call(METHOD_ON_CHECKED_CHANGED, "uri", extras);
+
+        assertThat(result.getBoolean(EXTRA_SWITCH_SET_CHECKED_ERROR)).isTrue();
+        assertThat(result.getString(EXTRA_SWITCH_SET_CHECKED_ERROR_MESSAGE)).isEqualTo("error");
+    }
+
+    private static class TestEntriesProvider extends EntriesProvider {
+
+        private List<EntryController> mControllers;
+
+        @Override
+        protected List<EntryController> createEntryControllers() {
+            return mControllers;
+        }
+
+        void addController(EntryController controller) {
+            if (mControllers == null) {
+                mControllers = new ArrayList<>();
+            }
+            mControllers.add(controller);
+        }
+    }
+
+    private static class TestEntryController extends EntryController {
+
+        private String mKey;
+        private MetaData mMetaData;
+
+        @Override
+        public String getKey() {
+            return mKey;
+        }
+
+        @Override
+        protected MetaData getMetaData() {
+            return mMetaData;
+        }
+
+        void setKey(String key) {
+            mKey = key;
+        }
+
+        void setMetaData(MetaData metaData) {
+            mMetaData = metaData;
+        }
+    }
+
+    private static class TestSwitchController extends EntryController implements ProviderSwitch {
+
+        private String mKey;
+        private MetaData mMetaData;
+        private boolean mChecked;
+        private String mErrorMsg;
+
+        @Override
+        public String getKey() {
+            return mKey;
+        }
+
+        @Override
+        protected MetaData getMetaData() {
+            return mMetaData;
+        }
+
+        @Override
+        public boolean isSwitchChecked() {
+            return mChecked;
+        }
+
+        @Override
+        public boolean onSwitchCheckedChanged(boolean checked) {
+            return mErrorMsg == null ? true : false;
+        }
+
+        @Override
+        public String getSwitchErrorMessage(boolean attemptedChecked) {
+            return mErrorMsg;
+        }
+
+        void setKey(String key) {
+            mKey = key;
+        }
+
+        void setMetaData(MetaData metaData) {
+            mMetaData = metaData;
+        }
+
+        void setSwitchChecked(boolean checked) {
+            mChecked = checked;
+        }
+
+        void setSwitchErrorMessage(String errorMsg) {
+            mErrorMsg = errorMsg;
+        }
+    }
+
+    private static class TestDynamicController extends TestEntryController
+            implements ProviderIcon, DynamicTitle, DynamicSummary {
+
+        static final String TITLE = "title";
+        static final String SUMMARY = "summary";
+        static final Bundle ICON_BUNDLE = new Bundle();
+
+        @Override
+        public Bundle getProviderIcon() {
+            return ICON_BUNDLE;
+        }
+
+        @Override
+        public String getDynamicTitle() {
+            return TITLE;
+        }
+
+        @Override
+        public String getDynamicSummary() {
+            return SUMMARY;
+        }
+    }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/ProviderTileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/ProviderTileTest.java
index abfb407..80f9efb8 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/ProviderTileTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/ProviderTileTest.java
@@ -17,20 +17,24 @@
 
 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_GROUP_KEY;
 import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON;
 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.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;
 
 import static com.google.common.truth.Truth.assertThat;
 
+import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.ProviderInfo;
 import android.content.pm.ResolveInfo;
 import android.os.Bundle;
+import android.os.UserHandle;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -173,13 +177,93 @@
         assertThat(tile.mLastUpdateTime).isNotEqualTo(staleTimeStamp);
     }
 
+    @Test
+    public void hasPendingIntent_empty_returnsFalse() {
+        final Tile tile = new ProviderTile(mProviderInfo, "category", mMetaData);
+
+        assertThat(tile.hasPendingIntent()).isFalse();
+    }
+
+    @Test
+    public void hasPendingIntent_notEmpty_returnsTrue() {
+        final Tile tile = new ProviderTile(mProviderInfo, "category", mMetaData);
+        tile.pendingIntentMap.put(
+                UserHandle.CURRENT, PendingIntent.getActivity(mContext, 0, new Intent(), 0));
+
+        assertThat(tile.hasPendingIntent()).isTrue();
+    }
+
+    @Test
+    public void hasGroupKey_empty_returnsFalse() {
+        final Tile tile = new ProviderTile(mProviderInfo, "category", mMetaData);
+
+        assertThat(tile.hasGroupKey()).isFalse();
+    }
+
+    @Test
+    public void hasGroupKey_notEmpty_returnsTrue() {
+        mMetaData.putString(META_DATA_PREFERENCE_GROUP_KEY, "test_key");
+        final Tile tile = new ProviderTile(mProviderInfo, "category", mMetaData);
+
+        assertThat(tile.hasGroupKey()).isTrue();
+    }
+
+    @Test
+    public void getGroupKey_empty_returnsNull() {
+        final Tile tile = new ProviderTile(mProviderInfo, "category", mMetaData);
+
+        assertThat(tile.getGroupKey()).isNull();
+    }
+
+    @Test
+    public void getGroupKey_notEmpty_returnsValue() {
+        mMetaData.putString(META_DATA_PREFERENCE_GROUP_KEY, "test_key");
+        final Tile tile = new ProviderTile(mProviderInfo, "category", mMetaData);
+
+        assertThat(tile.getGroupKey()).isEqualTo("test_key");
+    }
+
+    @Test
+    public void getType_withSwitch_returnsSwitch() {
+        mMetaData.putString(META_DATA_PREFERENCE_SWITCH_URI, "test://testabc/");
+        final Tile tile = new ProviderTile(mProviderInfo, "category", mMetaData);
+
+        assertThat(tile.getType()).isEqualTo(Tile.Type.SWITCH);
+    }
+
+    @Test
+    public void getType_withSwitchAndPendingIntent_returnsSwitchWithAction() {
+        mMetaData.putString(META_DATA_PREFERENCE_SWITCH_URI, "test://testabc/");
+        final Tile tile = new ProviderTile(mProviderInfo, "category", mMetaData);
+        tile.pendingIntentMap.put(
+                UserHandle.CURRENT, PendingIntent.getActivity(mContext, 0, new Intent(), 0));
+
+        assertThat(tile.getType()).isEqualTo(Tile.Type.SWITCH_WITH_ACTION);
+    }
+
+    @Test
+    public void getType_withPendingIntent_returnsExternalAction() {
+        final Tile tile = new ProviderTile(mProviderInfo, "category", mMetaData);
+        tile.pendingIntentMap.put(
+                UserHandle.CURRENT, PendingIntent.getActivity(mContext, 0, new Intent(), 0));
+
+        assertThat(tile.getType()).isEqualTo(Tile.Type.EXTERNAL_ACTION);
+    }
+
+    @Test
+    public void getType_withoutSwitchAndPendingIntent_returnsGroup() {
+        final Tile tile = new ProviderTile(mProviderInfo, "category", mMetaData);
+
+        assertThat(tile.getType()).isEqualTo(Tile.Type.GROUP);
+    }
+
     @Implements(TileUtils.class)
     private static class ShadowTileUtils {
 
         private static Bundle sMetaData;
 
         @Implementation
-        protected static Bundle getSwitchDataFromProvider(Context context, String authority,
+        protected static Bundle getEntryDataFromProvider(Context context, String authority,
                 String key) {
             return sMetaData;
         }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java
index 906e06e..2086466 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java
@@ -21,6 +21,7 @@
 import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON;
 import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON_URI;
 import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_KEYHINT;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_PENDING_INTENT;
 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.PROFILE_ALL;
@@ -40,6 +41,7 @@
 import static org.robolectric.RuntimeEnvironment.application;
 
 import android.app.ActivityManager;
+import android.app.PendingIntent;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
@@ -350,6 +352,53 @@
         assertThat(outTiles).isEmpty();
     }
 
+    @Test
+    public void loadTilesForAction_multipleUserProfiles_updatesUserHandle() {
+        Map<Pair<String, String>, Tile> addedCache = new ArrayMap<>();
+        List<Tile> outTiles = new ArrayList<>();
+        List<ResolveInfo> info = new ArrayList<>();
+        ResolveInfo resolveInfo = newInfo(true, null /* category */, null, URI_GET_ICON,
+                URI_GET_SUMMARY, null, 123, PROFILE_ALL);
+        info.add(resolveInfo);
+
+        when(mPackageManager.queryIntentActivitiesAsUser(any(Intent.class), anyInt(), anyInt()))
+                .thenReturn(info);
+
+        TileUtils.loadTilesForAction(mContext, UserHandle.CURRENT, IA_SETTINGS_ACTION,
+                addedCache, null /* defaultCategory */, outTiles, false /* requiresSettings */);
+        TileUtils.loadTilesForAction(mContext, new UserHandle(10), IA_SETTINGS_ACTION,
+                addedCache, null /* defaultCategory */, outTiles, false /* requiresSettings */);
+
+        assertThat(outTiles).hasSize(1);
+        assertThat(outTiles.get(0).userHandle)
+                .containsExactly(UserHandle.CURRENT, new UserHandle(10));
+    }
+
+    @Test
+    public void loadTilesForAction_withPendingIntent_updatesPendingIntentMap() {
+        Map<Pair<String, String>, Tile> addedCache = new ArrayMap<>();
+        List<Tile> outTiles = new ArrayList<>();
+        List<ResolveInfo> info = new ArrayList<>();
+        ResolveInfo resolveInfo = newInfo(true, null /* category */, null, URI_GET_ICON,
+                URI_GET_SUMMARY, null, 123, PROFILE_ALL);
+        PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+        resolveInfo.activityInfo.metaData
+                .putParcelable(META_DATA_PREFERENCE_PENDING_INTENT, pendingIntent);
+        info.add(resolveInfo);
+
+        when(mPackageManager.queryIntentActivitiesAsUser(any(Intent.class), anyInt(), anyInt()))
+                .thenReturn(info);
+
+        TileUtils.loadTilesForAction(mContext, UserHandle.CURRENT, IA_SETTINGS_ACTION,
+                addedCache, null /* defaultCategory */, outTiles, false /* requiresSettings */);
+        TileUtils.loadTilesForAction(mContext, new UserHandle(10), IA_SETTINGS_ACTION,
+                addedCache, null /* defaultCategory */, outTiles, false /* requiresSettings */);
+
+        assertThat(outTiles).hasSize(1);
+        assertThat(outTiles.get(0).pendingIntentMap).containsExactly(
+                UserHandle.CURRENT, pendingIntent, new UserHandle(10), pendingIntent);
+    }
+
     public static ResolveInfo newInfo(boolean systemApp, String category) {
         return newInfo(systemApp, category, null);
     }
@@ -424,7 +473,7 @@
         private static Bundle sMetaData;
 
         @Implementation
-        protected static List<Bundle> getSwitchDataFromProvider(Context context, String authority) {
+        protected static List<Bundle> getEntryDataFromProvider(Context context, String authority) {
             return Arrays.asList(sMetaData);
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSysUIComponent.java
index a431a59..a90980f 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSysUIComponent.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.dagger;
 
+import com.android.systemui.globalactions.ShutdownUiModule;
 import com.android.systemui.keyguard.CustomizationProvider;
 import com.android.systemui.statusbar.NotificationInsetsModule;
 import com.android.systemui.statusbar.QsFrameTranslateModule;
@@ -31,6 +32,7 @@
         DependencyProvider.class,
         NotificationInsetsModule.class,
         QsFrameTranslateModule.class,
+        ShutdownUiModule.class,
         SystemUIBinder.class,
         SystemUIModule.class,
         SystemUICoreStartableModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index d6c0829..498e597 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -221,7 +221,7 @@
     // TODO(b/267722622): Tracking Bug
     @JvmField
     val WALLPAPER_PICKER_UI_FOR_AIWP =
-            unreleasedFlag(
+            releasedFlag(
                     229,
                     "wallpaper_picker_ui_for_aiwp"
             )
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
index 290bf0d..c5027cc 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
@@ -15,27 +15,12 @@
 package com.android.systemui.globalactions;
 
 import static android.app.StatusBarManager.DISABLE2_GLOBAL_ACTIONS;
-import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
 
-import android.annotation.Nullable;
-import android.annotation.StringRes;
-import android.app.Dialog;
 import android.content.Context;
-import android.os.PowerManager;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.Window;
-import android.view.WindowManager;
-import android.widget.ProgressBar;
-import android.widget.TextView;
 
-import com.android.internal.R;
-import com.android.settingslib.Utils;
 import com.android.systemui.plugins.GlobalActions;
-import com.android.systemui.scrim.ScrimDrawable;
 import com.android.systemui.statusbar.BlurUtils;
 import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.phone.ScrimController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
@@ -50,12 +35,14 @@
     private final CommandQueue mCommandQueue;
     private final GlobalActionsDialogLite mGlobalActionsDialog;
     private boolean mDisabled;
+    private ShutdownUi mShutdownUi;
 
     @Inject
     public GlobalActionsImpl(Context context, CommandQueue commandQueue,
             GlobalActionsDialogLite globalActionsDialog, BlurUtils blurUtils,
             KeyguardStateController keyguardStateController,
-            DeviceProvisionedController deviceProvisionedController) {
+            DeviceProvisionedController deviceProvisionedController,
+            ShutdownUi shutdownUi) {
         mContext = context;
         mGlobalActionsDialog = globalActionsDialog;
         mKeyguardStateController = keyguardStateController;
@@ -63,6 +50,7 @@
         mCommandQueue = commandQueue;
         mBlurUtils = blurUtils;
         mCommandQueue.addCallback(this);
+        mShutdownUi = shutdownUi;
     }
 
     @Override
@@ -80,103 +68,8 @@
 
     @Override
     public void showShutdownUi(boolean isReboot, String reason) {
-        ScrimDrawable background = new ScrimDrawable();
-
-        final Dialog d = new Dialog(mContext,
-                com.android.systemui.R.style.Theme_SystemUI_Dialog_GlobalActions);
-
-        d.setOnShowListener(dialog -> {
-            if (mBlurUtils.supportsBlursOnWindows()) {
-                int backgroundAlpha = (int) (ScrimController.BUSY_SCRIM_ALPHA * 255);
-                background.setAlpha(backgroundAlpha);
-                mBlurUtils.applyBlur(d.getWindow().getDecorView().getViewRootImpl(),
-                        (int) mBlurUtils.blurRadiusOfRatio(1), backgroundAlpha == 255);
-            } else {
-                float backgroundAlpha = mContext.getResources().getFloat(
-                        com.android.systemui.R.dimen.shutdown_scrim_behind_alpha);
-                background.setAlpha((int) (backgroundAlpha * 255));
-            }
-        });
-
-        // Window initialization
-        Window window = d.getWindow();
-        window.requestFeature(Window.FEATURE_NO_TITLE);
-        window.getAttributes().systemUiVisibility |= View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
-                | View.SYSTEM_UI_FLAG_LAYOUT_STABLE
-                | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
-        // Inflate the decor view, so the attributes below are not overwritten by the theme.
-        window.getDecorView();
-        window.getAttributes().width = ViewGroup.LayoutParams.MATCH_PARENT;
-        window.getAttributes().height = ViewGroup.LayoutParams.MATCH_PARENT;
-        window.getAttributes().layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
-        window.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY);
-        window.getAttributes().setFitInsetsTypes(0 /* types */);
-        window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
-        window.addFlags(
-                WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
-                        | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
-                        | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR
-                        | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
-                        | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
-                        | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
-        window.setBackgroundDrawable(background);
-        window.setWindowAnimations(com.android.systemui.R.style.Animation_ShutdownUi);
-
-        d.setContentView(R.layout.shutdown_dialog);
-        d.setCancelable(false);
-
-        int color;
-        if (mBlurUtils.supportsBlursOnWindows()) {
-            color = Utils.getColorAttrDefaultColor(mContext,
-                    com.android.systemui.R.attr.wallpaperTextColor);
-        } else {
-            color = mContext.getResources().getColor(
-                    com.android.systemui.R.color.global_actions_shutdown_ui_text);
-        }
-
-        ProgressBar bar = d.findViewById(R.id.progress);
-        bar.getIndeterminateDrawable().setTint(color);
-
-        TextView reasonView = d.findViewById(R.id.text1);
-        TextView messageView = d.findViewById(R.id.text2);
-
-        reasonView.setTextColor(color);
-        messageView.setTextColor(color);
-
-        messageView.setText(getRebootMessage(isReboot, reason));
-        String rebootReasonMessage = getReasonMessage(reason);
-        if (rebootReasonMessage != null) {
-            reasonView.setVisibility(View.VISIBLE);
-            reasonView.setText(rebootReasonMessage);
-        }
-
-        d.show();
+        mShutdownUi.showShutdownUi(isReboot, reason);
     }
-
-    @StringRes
-    private int getRebootMessage(boolean isReboot, @Nullable String reason) {
-        if (reason != null && reason.startsWith(PowerManager.REBOOT_RECOVERY_UPDATE)) {
-            return R.string.reboot_to_update_reboot;
-        } else if (reason != null && reason.equals(PowerManager.REBOOT_RECOVERY)) {
-            return R.string.reboot_to_reset_message;
-        } else if (isReboot) {
-            return R.string.reboot_to_reset_message;
-        } else {
-            return R.string.shutdown_progress;
-        }
-    }
-
-    @Nullable
-    private String getReasonMessage(@Nullable String reason) {
-        if (reason != null && reason.startsWith(PowerManager.REBOOT_RECOVERY_UPDATE)) {
-            return mContext.getString(R.string.reboot_to_update_title);
-        } else if (reason != null && reason.equals(PowerManager.REBOOT_RECOVERY)) {
-            return mContext.getString(R.string.reboot_to_reset_title);
-        } else {
-            return null;
-        }
-    }
-
     @Override
     public void disable(int displayId, int state1, int state2, boolean animate) {
         final boolean disabled = (state2 & DISABLE2_GLOBAL_ACTIONS) != 0;
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/ShutdownUi.java b/packages/SystemUI/src/com/android/systemui/globalactions/ShutdownUi.java
new file mode 100644
index 0000000..8125d70
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/ShutdownUi.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2023 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.systemui.globalactions;
+
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+
+import android.annotation.Nullable;
+import android.annotation.StringRes;
+import android.app.Dialog;
+import android.content.Context;
+import android.os.PowerManager;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+
+import androidx.annotation.VisibleForTesting;
+
+import com.android.internal.R;
+import com.android.settingslib.Utils;
+import com.android.systemui.scrim.ScrimDrawable;
+import com.android.systemui.statusbar.BlurUtils;
+import com.android.systemui.statusbar.phone.ScrimController;
+
+/**
+ * Provides the UI shown during system shutdown.
+ */
+public class ShutdownUi {
+
+    private Context mContext;
+    private BlurUtils mBlurUtils;
+    public ShutdownUi(Context context, BlurUtils blurUtils) {
+        mContext = context;
+        mBlurUtils = blurUtils;
+    }
+
+    /**
+     * Display the shutdown UI.
+     * @param isReboot Whether the device will be rebooting after this shutdown.
+     * @param reason Cause for the shutdown.
+     */
+    public void showShutdownUi(boolean isReboot, String reason) {
+        ScrimDrawable background = new ScrimDrawable();
+
+        final Dialog d = new Dialog(mContext,
+                com.android.systemui.R.style.Theme_SystemUI_Dialog_GlobalActions);
+
+        d.setOnShowListener(dialog -> {
+            if (mBlurUtils.supportsBlursOnWindows()) {
+                int backgroundAlpha = (int) (ScrimController.BUSY_SCRIM_ALPHA * 255);
+                background.setAlpha(backgroundAlpha);
+                mBlurUtils.applyBlur(d.getWindow().getDecorView().getViewRootImpl(),
+                        (int) mBlurUtils.blurRadiusOfRatio(1), backgroundAlpha == 255);
+            } else {
+                float backgroundAlpha = mContext.getResources().getFloat(
+                        com.android.systemui.R.dimen.shutdown_scrim_behind_alpha);
+                background.setAlpha((int) (backgroundAlpha * 255));
+            }
+        });
+
+        // Window initialization
+        Window window = d.getWindow();
+        window.requestFeature(Window.FEATURE_NO_TITLE);
+        window.getAttributes().systemUiVisibility |= View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+                | View.SYSTEM_UI_FLAG_LAYOUT_STABLE
+                | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
+        // Inflate the decor view, so the attributes below are not overwritten by the theme.
+        window.getDecorView();
+        window.getAttributes().width = ViewGroup.LayoutParams.MATCH_PARENT;
+        window.getAttributes().height = ViewGroup.LayoutParams.MATCH_PARENT;
+        window.getAttributes().layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+        window.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY);
+        window.getAttributes().setFitInsetsTypes(0 /* types */);
+        window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
+        window.addFlags(
+                WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+                        | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+                        | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR
+                        | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
+                        | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
+                        | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
+        window.setBackgroundDrawable(background);
+        window.setWindowAnimations(com.android.systemui.R.style.Animation_ShutdownUi);
+
+        d.setContentView(getShutdownDialogContent());
+        d.setCancelable(false);
+
+        int color;
+        if (mBlurUtils.supportsBlursOnWindows()) {
+            color = Utils.getColorAttrDefaultColor(mContext,
+                    com.android.systemui.R.attr.wallpaperTextColor);
+        } else {
+            color = mContext.getResources().getColor(
+                    com.android.systemui.R.color.global_actions_shutdown_ui_text);
+        }
+
+        ProgressBar bar = d.findViewById(R.id.progress);
+        bar.getIndeterminateDrawable().setTint(color);
+
+        TextView reasonView = d.findViewById(R.id.text1);
+        TextView messageView = d.findViewById(R.id.text2);
+
+        reasonView.setTextColor(color);
+        messageView.setTextColor(color);
+
+        messageView.setText(getRebootMessage(isReboot, reason));
+        String rebootReasonMessage = getReasonMessage(reason);
+        if (rebootReasonMessage != null) {
+            reasonView.setVisibility(View.VISIBLE);
+            reasonView.setText(rebootReasonMessage);
+        }
+
+        d.show();
+    }
+
+    public int getShutdownDialogContent() {
+        return R.layout.shutdown_dialog;
+    }
+
+    @StringRes
+    @VisibleForTesting int getRebootMessage(boolean isReboot, @Nullable String reason) {
+        if (reason != null && reason.startsWith(PowerManager.REBOOT_RECOVERY_UPDATE)) {
+            return R.string.reboot_to_update_reboot;
+        } else if (reason != null && reason.equals(PowerManager.REBOOT_RECOVERY)) {
+            return R.string.reboot_to_reset_message;
+        } else if (isReboot) {
+            return R.string.reboot_to_reset_message;
+        } else {
+            return R.string.shutdown_progress;
+        }
+    }
+
+    @Nullable
+    @VisibleForTesting String getReasonMessage(@Nullable String reason) {
+        if (reason != null && reason.startsWith(PowerManager.REBOOT_RECOVERY_UPDATE)) {
+            return mContext.getString(R.string.reboot_to_update_title);
+        } else if (reason != null && reason.equals(PowerManager.REBOOT_RECOVERY)) {
+            return mContext.getString(R.string.reboot_to_reset_title);
+        } else {
+            return null;
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/ShutdownUiModule.kt b/packages/SystemUI/src/com/android/systemui/globalactions/ShutdownUiModule.kt
new file mode 100644
index 0000000..b7285da
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/ShutdownUiModule.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2023 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.systemui.globalactions
+
+import android.content.Context
+import com.android.systemui.statusbar.BlurUtils
+import dagger.Module
+import dagger.Provides
+
+/** Provides the UI shown during system shutdown. */
+@Module
+class ShutdownUiModule {
+    /** Shutdown UI provider. */
+    @Provides
+    fun provideShutdownUi(context: Context?, blurUtils: BlurUtils?): ShutdownUi {
+        return ShutdownUi(context, blurUtils)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponent.java b/packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponent.java
index 640adcc..5e489b0 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponent.java
@@ -20,15 +20,14 @@
 import com.android.systemui.dagger.DependencyProvider;
 import com.android.systemui.dagger.SysUIComponent;
 import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.SystemUIBinder;
 import com.android.systemui.dagger.SystemUIModule;
+import com.android.systemui.globalactions.ShutdownUiModule;
+import com.android.systemui.keyguard.dagger.KeyguardModule;
+import com.android.systemui.recents.RecentsModule;
 import com.android.systemui.statusbar.dagger.CentralSurfacesDependenciesModule;
 import com.android.systemui.statusbar.notification.dagger.NotificationsModule;
 import com.android.systemui.statusbar.notification.row.NotificationRowModule;
 
-import com.android.systemui.keyguard.dagger.KeyguardModule;
-import com.android.systemui.recents.RecentsModule;
-
 import dagger.Subcomponent;
 
 /**
@@ -43,6 +42,7 @@
         NotificationRowModule.class,
         NotificationsModule.class,
         RecentsModule.class,
+        ShutdownUiModule.class,
         SystemUIModule.class,
         TvSystemUIBinder.class,
         TVSystemUICoreStartableModule.class,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/ShutdownUiTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/ShutdownUiTest.java
new file mode 100644
index 0000000..9d9b263
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/ShutdownUiTest.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2023 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.systemui.globalactions;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNull;
+
+import android.os.PowerManager;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.BlurUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class ShutdownUiTest extends SysuiTestCase {
+
+    ShutdownUi mShutdownUi;
+    @Mock
+    BlurUtils mBlurUtils;
+
+    @Before
+    public void setUp() throws Exception {
+        mShutdownUi = new ShutdownUi(getContext(), mBlurUtils);
+    }
+
+    @Test
+    public void getRebootMessage_update() {
+        int messageId = mShutdownUi.getRebootMessage(true, PowerManager.REBOOT_RECOVERY_UPDATE);
+        assertEquals(messageId, R.string.reboot_to_update_reboot);
+    }
+
+    @Test
+    public void getRebootMessage_rebootDefault() {
+        int messageId = mShutdownUi.getRebootMessage(true, "anything-else");
+        assertEquals(messageId, R.string.reboot_to_reset_message);
+    }
+
+    @Test
+    public void getRebootMessage_shutdown() {
+        int messageId = mShutdownUi.getRebootMessage(false, "anything-else");
+        assertEquals(messageId, R.string.shutdown_progress);
+    }
+
+    @Test
+    public void getReasonMessage_update() {
+        String message = mShutdownUi.getReasonMessage(PowerManager.REBOOT_RECOVERY_UPDATE);
+        assertEquals(message, mContext.getString(R.string.reboot_to_update_title));
+    }
+
+    @Test
+    public void getReasonMessage_rebootDefault() {
+        String message = mShutdownUi.getReasonMessage(PowerManager.REBOOT_RECOVERY);
+        assertEquals(message, mContext.getString(R.string.reboot_to_reset_title));
+    }
+
+    @Test
+    public void getRebootMessage_defaultToNone() {
+        String message = mShutdownUi.getReasonMessage("anything-else");
+        assertNull(message);
+    }
+}