Merge "Cache Device Motion Prediction Availability"
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 317e753..7cf437f 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -3021,7 +3021,11 @@
 
         public void destroy() {
             try {
-                mArray.close();
+                // If this process is the system server process, mArray is the same object as
+                // the memory int array kept inside SetingsProvider, so skipping the close()
+                if (!Settings.isInSystemServer()) {
+                    mArray.close();
+                }
             } catch (IOException e) {
                 Log.e(TAG, "Error closing backing array", e);
             }
@@ -3190,9 +3194,7 @@
         @UnsupportedAppUsage
         public String getStringForUser(ContentResolver cr, String name, final int userHandle) {
             final boolean isSelf = (userHandle == UserHandle.myUserId());
-            int currentGeneration = -1;
             boolean needsGenerationTracker = false;
-
             if (isSelf) {
                 synchronized (NameValueCache.this) {
                     final GenerationTracker generationTracker = mGenerationTrackers.get(name);
@@ -3204,27 +3206,31 @@
                                         + " in package:" + cr.getPackageName()
                                         + " and user:" + userHandle);
                             }
+                            // When a generation number changes, remove cached value, remove the old
+                            // generation tracker and request a new one
                             mValues.remove(name);
+                            generationTracker.destroy();
+                            mGenerationTrackers.remove(name);
                         } else if (mValues.containsKey(name)) {
                             if (DEBUG) {
                                 Log.i(TAG, "Cache hit for setting:" + name);
                             }
                             return mValues.get(name);
                         }
-                        currentGeneration = generationTracker.getCurrentGeneration();
-                    } else {
-                        needsGenerationTracker = true;
                     }
                 }
+                if (DEBUG) {
+                    Log.i(TAG, "Cache miss for setting:" + name + " for user:"
+                            + userHandle);
+                }
+                // Generation tracker doesn't exist or the value isn't cached
+                needsGenerationTracker = true;
             } else {
                 if (DEBUG || LOCAL_LOGV) {
                     Log.v(TAG, "get setting for user " + userHandle
                             + " by user " + UserHandle.myUserId() + " so skipping cache");
                 }
             }
-            if (DEBUG) {
-                Log.i(TAG, "Cache miss for setting:" + name + " for user:" + userHandle);
-            }
 
             // Check if the target settings key is readable. Reject if the caller is not system and
             // is trying to access a settings key defined in the Settings.Secure, Settings.System or
@@ -3324,11 +3330,10 @@
                                         mGenerationTrackers.put(name, new GenerationTracker(name,
                                                 array, index, generation,
                                                 mGenerationTrackerErrorHandler));
-                                        currentGeneration = generation;
                                     }
                                 }
-                                if (mGenerationTrackers.get(name) != null && currentGeneration
-                                        == mGenerationTrackers.get(name).getCurrentGeneration()) {
+                                if (mGenerationTrackers.get(name) != null
+                                        && !mGenerationTrackers.get(name).isGenerationChanged()) {
                                     if (DEBUG) {
                                         Log.i(TAG, "Updating cache for setting:" + name);
                                     }
@@ -3374,8 +3379,8 @@
 
                 String value = c.moveToNext() ? c.getString(0) : null;
                 synchronized (NameValueCache.this) {
-                    if (mGenerationTrackers.get(name) != null && currentGeneration
-                            == mGenerationTrackers.get(name).getCurrentGeneration()) {
+                    if (mGenerationTrackers.get(name) != null
+                            && !mGenerationTrackers.get(name).isGenerationChanged()) {
                         if (DEBUG) {
                             Log.i(TAG, "Updating cache for setting:" + name + " using query");
                         }
diff --git a/core/tests/coretests/src/android/content/AbstractCrossUserContentResolverTest.java b/core/tests/coretests/src/android/content/AbstractCrossUserContentResolverTest.java
index 90bb627..92b1c04 100644
--- a/core/tests/coretests/src/android/content/AbstractCrossUserContentResolverTest.java
+++ b/core/tests/coretests/src/android/content/AbstractCrossUserContentResolverTest.java
@@ -18,6 +18,7 @@
 
 import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeTrue;
+import org.junit.Ignore;
 
 import android.app.ActivityManager;
 import android.app.activity.LocalProvider;
@@ -123,6 +124,7 @@
      * Register an observer for an URI in another user and verify that it receives
      * onChange callback when data at the URI changes.
      */
+    @Ignore("b/272733874")
     @Test
     public void testRegisterContentObserver() throws Exception {
         Context crossUserContext = null;
@@ -152,6 +154,7 @@
      * Register an observer for an URI in the current user and verify that another user can
      * notify changes for this URI.
      */
+    @Ignore("b/272733874")
     @Test
     public void testNotifyChange() throws Exception {
         final CountDownLatch notifyLatch = new CountDownLatch(1);
diff --git a/core/tests/coretests/src/android/provider/NameValueCacheTest.java b/core/tests/coretests/src/android/provider/NameValueCacheTest.java
index b6fc137..54a3817 100644
--- a/core/tests/coretests/src/android/provider/NameValueCacheTest.java
+++ b/core/tests/coretests/src/android/provider/NameValueCacheTest.java
@@ -28,6 +28,7 @@
 import android.content.ContentProvider;
 import android.content.IContentProvider;
 import android.os.Bundle;
+import android.os.Parcel;
 import android.platform.test.annotations.Presubmit;
 import android.test.mock.MockContentResolver;
 import android.util.MemoryIntArray;
@@ -91,7 +92,7 @@
         mConfigsCacheGenerationStore = new MemoryIntArray(2);
         mConfigsCacheGenerationStore.set(0, 123);
         mConfigsCacheGenerationStore.set(1, 456);
-        mSettingsCacheGenerationStore = new MemoryIntArray(2);
+        mSettingsCacheGenerationStore = new MemoryIntArray(3);
         mSettingsCacheGenerationStore.set(0, 234);
         mSettingsCacheGenerationStore.set(1, 567);
         mConfigsStorage = new HashMap<>();
@@ -163,6 +164,10 @@
                     Bundle incomingBundle = invocationOnMock.getArgument(4);
                     String key = invocationOnMock.getArgument(3);
                     String value = incomingBundle.getString(Settings.NameValueTable.VALUE);
+                    boolean newSetting = false;
+                    if (!mSettingsStorage.containsKey(key)) {
+                        newSetting = true;
+                    }
                     mSettingsStorage.put(key, value);
                     int currentGeneration;
                     // Different settings have different generation codes
@@ -173,12 +178,18 @@
                         currentGeneration = mSettingsCacheGenerationStore.get(1);
                         mSettingsCacheGenerationStore.set(1, ++currentGeneration);
                     }
+                    if (newSetting) {
+                        // Tracking the generation of all unset settings.
+                        // Increment when a new setting is inserted
+                        currentGeneration = mSettingsCacheGenerationStore.get(2);
+                        mSettingsCacheGenerationStore.set(2, ++currentGeneration);
+                    }
                     return null;
                 });
 
         // Returns the value corresponding to a setting, or null if the setting
-        // doesn't have a value stored for it. Returns the generation key if the value isn't null
-        // and the caller asked for the generation key.
+        // doesn't have a value stored for it. Returns the generation key
+        // if the caller asked for the generation key.
         when(mMockIContentProvider.call(any(), eq(Settings.Secure.CONTENT_URI.getAuthority()),
                 eq(Settings.CALL_METHOD_GET_SECURE), any(), any(Bundle.class))).thenAnswer(
                 invocationOnMock -> {
@@ -189,14 +200,26 @@
                     Bundle bundle = new Bundle();
                     bundle.putSerializable(Settings.NameValueTable.VALUE, value);
 
-                    if (value != null && incomingBundle.containsKey(
+                    if (incomingBundle.containsKey(
                             Settings.CALL_METHOD_TRACK_GENERATION_KEY)) {
-                        int index = key.equals(SETTING) ? 0 : 1;
+                        int index;
+                        if (value != null) {
+                            index = key.equals(SETTING) ? 0 : 1;
+                        } else {
+                            // special index for unset settings
+                            index = 2;
+                        }
+                        // Manually make a copy of the memory int array to mimic sending it over IPC
+                        Parcel p = Parcel.obtain();
+                        mSettingsCacheGenerationStore.writeToParcel(p, 0);
+                        p.setDataPosition(0);
+                        MemoryIntArray parcelArray = MemoryIntArray.CREATOR.createFromParcel(p);
                         bundle.putParcelable(Settings.CALL_METHOD_TRACK_GENERATION_KEY,
-                                mSettingsCacheGenerationStore);
+                                parcelArray);
                         bundle.putInt(Settings.CALL_METHOD_GENERATION_INDEX_KEY, index);
                         bundle.putInt(Settings.CALL_METHOD_GENERATION_KEY,
                                 mSettingsCacheGenerationStore.get(index));
+                        p.recycle();
                     }
                     return bundle;
                 });
@@ -206,6 +229,8 @@
     public void cleanUp() throws IOException {
         mConfigsStorage.clear();
         mSettingsStorage.clear();
+        mSettingsCacheGenerationStore.close();
+        mConfigsCacheGenerationStore.close();
     }
 
     @Test
@@ -361,16 +386,38 @@
     }
 
     @Test
-    public void testCaching_nullSetting() throws Exception {
+    public void testCaching_unsetSetting() throws Exception {
         String returnedValue = Settings.Secure.getString(mMockContentResolver, SETTING);
         verify(mMockIContentProvider, times(1)).call(any(), any(),
                 eq(Settings.CALL_METHOD_GET_SECURE), any(), any(Bundle.class));
         assertThat(returnedValue).isNull();
 
         String cachedValue = Settings.Secure.getString(mMockContentResolver, SETTING);
-        // Empty list won't be cached
+        // The first unset setting's generation number is cached
+        verifyNoMoreInteractions(mMockIContentProvider);
+        assertThat(cachedValue).isNull();
+
+        String returnedValue2 = Settings.Secure.getString(mMockContentResolver, SETTING2);
         verify(mMockIContentProvider, times(2)).call(any(), any(),
                 eq(Settings.CALL_METHOD_GET_SECURE), any(), any(Bundle.class));
-        assertThat(cachedValue).isNull();
+        assertThat(returnedValue2).isNull();
+
+        String cachedValue2 = Settings.Secure.getString(mMockContentResolver, SETTING);
+        // The second unset setting's generation number is cached
+        verifyNoMoreInteractions(mMockIContentProvider);
+        assertThat(cachedValue2).isNull();
+
+        Settings.Secure.putString(mMockContentResolver, SETTING, "a");
+        // The generation for unset settings should have changed
+        returnedValue2 = Settings.Secure.getString(mMockContentResolver, SETTING2);
+        verify(mMockIContentProvider, times(3)).call(any(), any(),
+                eq(Settings.CALL_METHOD_GET_SECURE), any(), any(Bundle.class));
+        assertThat(returnedValue2).isNull();
+
+        // The generation tracker for the first setting should have change because it's set now
+        returnedValue = Settings.Secure.getString(mMockContentResolver, SETTING);
+        verify(mMockIContentProvider, times(4)).call(any(), any(),
+                eq(Settings.CALL_METHOD_GET_SECURE), any(), any(Bundle.class));
+        assertThat(returnedValue).isEqualTo("a");
     }
 }
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java b/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java
index 7f3b0ff..db6cc1a 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java
@@ -16,6 +16,7 @@
 
 package com.android.providers.settings;
 
+import android.annotation.NonNull;
 import android.os.Bundle;
 import android.provider.Settings;
 import android.util.ArrayMap;
@@ -59,6 +60,10 @@
     // Maximum size of an individual backing store
     static final int MAX_BACKING_STORE_SIZE = MemoryIntArray.getMaxSize();
 
+    // Use an empty string to track the generation number of all non-predefined, unset settings
+    // The generation number is only increased when a new non-predefined setting is inserted
+    private static final String DEFAULT_MAP_KEY_FOR_UNSET_SETTINGS = "";
+
     public GenerationRegistry(Object lock) {
         mLock = lock;
     }
@@ -72,6 +77,10 @@
                 (SettingsState.getTypeFromKey(key) == SettingsState.SETTINGS_TYPE_CONFIG);
         // Only store the prefix if the mutated setting is a config
         final String indexMapKey = isConfig ? (name.split("/")[0] + "/") : name;
+        incrementGenerationInternal(key, indexMapKey);
+    }
+
+    private void incrementGenerationInternal(int key, @NonNull String indexMapKey) {
         synchronized (mLock) {
             final MemoryIntArray backingStore = getBackingStoreLocked(key,
                     /* createIfNotExist= */ false);
@@ -87,7 +96,8 @@
                 final int generation = backingStore.get(index) + 1;
                 backingStore.set(index, generation);
                 if (DEBUG) {
-                    Slog.i(LOG_TAG, "Incremented generation for setting:" + indexMapKey
+                    Slog.i(LOG_TAG, "Incremented generation for "
+                            + (indexMapKey.isEmpty() ? "unset settings" : "setting:" + indexMapKey)
                             + " key:" + SettingsState.keyToString(key)
                             + " at index:" + index);
                 }
@@ -98,6 +108,18 @@
         }
     }
 
+    // A new, non-predefined setting has been inserted, increment the tracking number for all unset
+    // settings
+    public void incrementGenerationForUnsetSettings(int key) {
+        final boolean isConfig =
+                (SettingsState.getTypeFromKey(key) == SettingsState.SETTINGS_TYPE_CONFIG);
+        if (isConfig) {
+            // No need to track new settings for configs
+            return;
+        }
+        incrementGenerationInternal(key, DEFAULT_MAP_KEY_FOR_UNSET_SETTINGS);
+    }
+
     /**
      *  Return the backing store's reference, the index and the current generation number
      *  of a cached setting. If it was not in the backing store, first create the entry in it before
@@ -124,8 +146,8 @@
                 bundle.putInt(Settings.CALL_METHOD_GENERATION_KEY,
                         backingStore.get(index));
                 if (DEBUG) {
-                    Slog.i(LOG_TAG, "Exported index:" + index
-                            + " for setting:" + indexMapKey
+                    Slog.i(LOG_TAG, "Exported index:" + index + " for "
+                            + (indexMapKey.isEmpty() ? "unset settings" : "setting:" + indexMapKey)
                             + " key:" + SettingsState.keyToString(key));
                 }
             } catch (IOException e) {
@@ -135,6 +157,10 @@
         }
     }
 
+    public void addGenerationDataForUnsetSettings(Bundle bundle, int key) {
+        addGenerationData(bundle, key, /* indexMapKey= */ DEFAULT_MAP_KEY_FOR_UNSET_SETTINGS);
+    }
+
     public void onUserRemoved(int userId) {
         final int secureKey = SettingsState.makeKey(
                 SettingsState.SETTINGS_TYPE_SECURE, userId);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 832c1b9..e6408bf 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -2378,11 +2378,15 @@
         result.putString(Settings.NameValueTable.VALUE,
                 (setting != null && !setting.isNull()) ? setting.getValue() : null);
 
-        if ((setting != null && !setting.isNull()) || isSettingPreDefined(name, type)) {
-            // Don't track generation for non-existent settings unless the name is predefined
-            synchronized (mLock) {
+        synchronized (mLock) {
+            if ((setting != null && !setting.isNull()) || isSettingPreDefined(name, type)) {
+                // Individual generation tracking for predefined settings even if they are unset
                 mSettingsRegistry.mGenerationRegistry.addGenerationData(result,
                         SettingsState.makeKey(type, userId), name);
+            } else {
+                // All non-predefined, unset settings are tracked using the same generation number
+                mSettingsRegistry.mGenerationRegistry.addGenerationDataForUnsetSettings(result,
+                        SettingsState.makeKey(type, userId));
             }
         }
         return result;
@@ -2396,7 +2400,8 @@
         } else if (type == SETTINGS_TYPE_SYSTEM) {
             return sAllSystemSettings.contains(name);
         } else {
-            return false;
+            // Consider all config settings predefined because they are used by system apps only
+            return type == SETTINGS_TYPE_CONFIG;
         }
     }
 
@@ -2405,14 +2410,13 @@
         Bundle result = new Bundle();
         result.putSerializable(Settings.NameValueTable.VALUE, keyValues);
         if (trackingGeneration) {
-            // Track generation even if the namespace is empty because this is for system apps
             synchronized (mLock) {
+                // Track generation even if namespace is empty because this is for system apps only
                 mSettingsRegistry.mGenerationRegistry.addGenerationData(result,
-                        mSettingsRegistry.getSettingsLocked(SETTINGS_TYPE_CONFIG,
-                                UserHandle.USER_SYSTEM).mKey, prefix);
+                        SettingsState.makeKey(SETTINGS_TYPE_CONFIG, UserHandle.USER_SYSTEM),
+                        prefix);
             }
         }
-
         return result;
     }
 
@@ -3103,10 +3107,15 @@
             final int key = makeKey(type, userId);
 
             boolean success = false;
+            boolean wasUnsetNonPredefinedSetting = false;
             SettingsState settingsState = peekSettingsStateLocked(key);
             if (settingsState != null) {
+                if (!isSettingPreDefined(name, type) && !settingsState.hasSetting(name)) {
+                    wasUnsetNonPredefinedSetting = true;
+                }
                 success = settingsState.insertSettingLocked(name, value,
-                        tag, makeDefault, forceNonSystemPackage, packageName, overrideableByRestore);
+                        tag, makeDefault, forceNonSystemPackage, packageName,
+                        overrideableByRestore);
             }
 
             if (success && criticalSettings != null && criticalSettings.contains(name)) {
@@ -3115,6 +3124,11 @@
 
             if (forceNotify || success) {
                 notifyForSettingsChange(key, name);
+                if (wasUnsetNonPredefinedSetting) {
+                    // Increment the generation number for all non-predefined, unset settings,
+                    // because a new non-predefined setting has been inserted
+                    mGenerationRegistry.incrementGenerationForUnsetSettings(key);
+                }
             }
             if (success) {
                 logSettingChanged(userId, name, type, CHANGE_TYPE_INSERT);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index c388826..4d8705f 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -759,6 +759,12 @@
         mPackageToMemoryUsage.put(packageName, newSize);
     }
 
+    public boolean hasSetting(String name) {
+        synchronized (mLock) {
+            return hasSettingLocked(name);
+        }
+    }
+
     @GuardedBy("mLock")
     private boolean hasSettingLocked(String name) {
         return mSettings.indexOfKey(name) >= 0;
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/GenerationRegistryTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/GenerationRegistryTest.java
index d34fe694..6ec8146 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/GenerationRegistryTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/GenerationRegistryTest.java
@@ -151,6 +151,26 @@
         checkBundle(b, 0, 1, false);
     }
 
+    @Test
+    public void testUnsetSettings() throws IOException {
+        final GenerationRegistry generationRegistry = new GenerationRegistry(new Object());
+        final int secureKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_SECURE, 0);
+        final String testSecureSetting = "test_secure_setting";
+        Bundle b = new Bundle();
+        generationRegistry.addGenerationData(b, secureKey, testSecureSetting);
+        checkBundle(b, 0, 1, false);
+        generationRegistry.addGenerationDataForUnsetSettings(b, secureKey);
+        checkBundle(b, 1, 1, false);
+        generationRegistry.addGenerationDataForUnsetSettings(b, secureKey);
+        // Test that unset settings always have the same index
+        checkBundle(b, 1, 1, false);
+        generationRegistry.incrementGenerationForUnsetSettings(secureKey);
+        // Test that the generation number of the unset settings have increased
+        generationRegistry.addGenerationDataForUnsetSettings(b, secureKey);
+        checkBundle(b, 1, 2, false);
+    }
+
+
     private void checkBundle(Bundle b, int expectedIndex, int expectedGeneration, boolean isNull)
             throws IOException {
         final MemoryIntArray array = getArray(b);
diff --git a/packages/SystemUI/docs/clock-plugins.md b/packages/SystemUI/docs/clock-plugins.md
index 5e4f6c7..2226d79 100644
--- a/packages/SystemUI/docs/clock-plugins.md
+++ b/packages/SystemUI/docs/clock-plugins.md
@@ -1,35 +1,92 @@
 # Clock Plugins
 
-## Introduction
-
 The clock appearing on the lock screen and always on display (AOD) can be
-customized via the ClockPlugin plugin interface.
+customized via the ClockProviderPlugin plugin interface.
+
+## Lock screen integration
+The lockscreen code has two main components, a [clock customization library](../customization), and
+the SystemUI [lockscreen host code](../src/com/android/keyguard). The customization library contains
+the default clock, and some support code for managing clocks and picking the correct one to render.
+It is used by both SystemUI for rendering and ThemePicker for selecting clocks. The SystemUI host is
+responsible for maintaining the view within the hierarchy and propagating events to the rendered
+clock controller.
+
+### Clock Library Code
+[ClockProvider and ClockController](../plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt)
+serve as the interface between the lockscreen (or other host application) and the clock that is
+being rendered. Implementing these interfaces is the primary integration point for rendering clocks
+in SystemUI. Many of the methods have an empty default implementation and are optional for
+implementations if the related event is not interesting to your use case.
+
+[DefaultClockProvider](../customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt) and
+[DefaultClockController](../customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt)
+implement these interfaces for the default lockscreen clock. They handle relevant events from the
+lockscreen to update and control the small and large clock view as appropriate.
+[AnimatableClockView](../customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt)
+is the view that DefaultClockController uses to render both the small and large clock.
+AnimatableClockView has moved location within the repo, but is largely unchanged from previous
+versions of android.
+
+The [ClockRegistry](../customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt)
+determines which clock should be shown, and handles creating them. It does this by maintaining a
+list of [ClockProviders](../plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt) and
+delegating work to them as appropriate. The DefaultClockProvider is compiled in so that it is
+guaranteed to be available, and additional ClockProviders are loaded at runtime via
+[PluginManager](../plugin_core/src/com/android/systemui/plugins/PluginManager.java).
+
+[ClockPlugin](../plugin/src/com/android/systemui/plugins/ClockPlugin.java) is deprecated and no
+longer used by keyguard to render clocks. The host code has been disabled but most of it is still
+present in the source tree, although it will likely be removed in a later patch.
+
+### Lockscreen Host
+[ClockEventController](../src/com/android/keyguard/ClockEventController.kt) propagates events from
+SystemUI event dispatchers to the clock controllers. It maintains a set of event listeners, but
+otherwise attempts to do as little work as possible. It does maintain some state where necessary.
+
+[KeyguardClockSwitchController](../src/com/android/keyguard/KeyguardClockSwitchController.java) is
+the primary controller for the [KeyguardClockSwitch](../src/com/android/keyguard/KeyguardClockSwitch.java),
+which serves as the view parent within SystemUI. Together they ensure the correct clock (either
+large or small) is shown, handle animation between clock sizes, and control some sizing/layout
+parameters for the clocks.
+
+### Creating a custom clock
+In order to create a custom clock, a partner must:
+ - Write an implementation of ClockProviderPlugin and the subinterfaces relevant to your use-case.
+ - Build this into a seperate plugin apk, and deploy that apk to the device.
+    - Alternatively, it could be compiled directly into the customization lib like DefaultClockProvider.
+ - PluginManager should automatically notify ClockRegistry of your plugin apk when it arrives on
+      device. ClockRegistry will print info logs when it successfully loads a plugin.
+ - Set the clock either in ThemePicker or through adb:
+      `adb shell settings put secure lock_screen_custom_clock_face '''{\"clockId\":\"ID\"}'''`
+ - SystemUI should immediately load and render the new clock if it is available.
+
+### Picker integration
+Picker logic for choosing between clocks is available to our partners as part of the ThemePicker.
+The clock picking UI will be enabled by default if there is more than 1 clock provided, otherwise
+it will be hidden from the UI.
 
 ## System Health
 
-Clocks are high risk for battery consumption and screen burn-in because they
-modify the UI of AOD.
+Clocks are high risk for battery consumption and screen burn-in because they modify the UI of AOD.
 
-To reduce battery consumption, it is recommended to
-target a maximum on-pixel-ratio (OPR) of 5%. Clocks that are composed of
-large blocks of color that cause the OPR to exceed 5% should be avoided.
+To reduce battery consumption, it is recommended to target a maximum on-pixel-ratio (OPR) of 10%.
+Clocks that are composed of large blocks of color that cause the OPR to exceed 10% should be
+avoided, but this target will differ depending on the device hardware.
 
-To prevent screen burn-in, clocks should not be composed of large solid
-blocks of color, and the clock should be moved around the screen to
-distribute the on pixels across a large number of pixels. Software
-burn-in testing is a good starting point to assess the pixel shifting
-(clock movement) scheme and shape of the clock.
+To prevent screen burn-in, clocks should not be composed of large solid blocks of color, and the
+clock should be moved around the screen to distribute the on pixels across a large number of pixels.
+Software burn-in testing is a good starting point to assess the pixel shifting (clock movement)
+scheme and shape of the clock. SystemUI currently treats all clocks the same in this regard using
+[KeyguardClockPositionAlgorithm](../src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java)
 
 ### Software Burn-In Test
 
-The goal is to look for bright spots in the luminosity average over a period of
-time. It is difficult to define a threshold where burn-in will occur. It is,
-therefore, recommended to compare against an element on AOD that is known not
-to cause problems.
+The goal is to look for bright spots in the luminosity average over a period of time. It is
+difficult to define a threshold where burn-in will occur. It is, therefore, recommended to compare
+against an element on AOD that is known not to cause problems.
 
-For clock face that contain color, it is recommended to use an all white
-version of the face. Since white has the highest luminosity, this version of
-the clock face represents the worst case scenario.
+For clock face that contain color, it is recommended to use an all white version of the face. Since
+white has the highest luminosity, this version of the clock face represents the worst case scenario.
 
 To start, generate a sequence of screenshots for each minute over a 12 hr interval.
 
@@ -87,6 +144,5 @@
   main(sys.argv[1])
 ```
 
-Look for bright spots in the luminosity average. If bright spots are found,
-action should be taken to change the shape of the clock face or increase the
-amount of pixel shifting.
+Look for bright spots in the luminosity average. If bright spots are found, action should be taken
+to change the shape of the clock face or increase the amount of pixel shifting.
diff --git a/packages/SystemUI/docs/dagger.md b/packages/SystemUI/docs/dagger.md
index 9b4c21e..4a6240b 100644
--- a/packages/SystemUI/docs/dagger.md
+++ b/packages/SystemUI/docs/dagger.md
@@ -108,20 +108,13 @@
 
 ### Using injection with Fragments
 
-Fragments are created as part of the FragmentManager, so they need to be
-setup so the manager knows how to create them. To do that, add a method
-to com.android.systemui.fragments.FragmentService$FragmentCreator that
-returns your fragment class. That is all that is required, once the method
-exists, FragmentService will automatically pick it up and use injection
-whenever your fragment needs to be created.
+Fragments are created as part of the FragmentManager, so injectable Fragments need to be registered
+so the manager knows how to create them. This is done via
+[FragmentService#addFragmentInstantiationProvider](../src/com/android/systemui/fragments/FragmentService.java).
+Pass it the class of your fragment and a `Provider` for your fragment at some time before your
+Fragment is accessed.
 
-```java
-public interface FragmentCreator {
-    NavigationBarFragment createNavigationBar();
-}
-```
-
-If you need to create your fragment (i.e. for the add or replace transaction),
+When you need to create your fragment (i.e. for the add or replace transaction),
 then the FragmentHostManager can do this for you.
 
 ```java
diff --git a/packages/SystemUI/docs/plugin_hooks.md b/packages/SystemUI/docs/plugin_hooks.md
index cde5094..6ce7ee0 100644
--- a/packages/SystemUI/docs/plugin_hooks.md
+++ b/packages/SystemUI/docs/plugin_hooks.md
@@ -1,7 +1,6 @@
 # Plugin hooks
 ### Action: com.android.systemui.action.PLUGIN_OVERLAY
-Expected interface: [OverlayPlugin](/frameworks/base/packages/SystemUI/plugin/src/com/android
-/systemui/plugins/OverlayPlugin.java)
+Expected interface: [OverlayPlugin](/frameworks/base/packages/SystemUI/plugin/src/com/android/systemui/plugins/OverlayPlugin.java)
 
 Use: Allows plugin access to the status bar and nav bar window for whatever nefarious purposes you can imagine.
 
@@ -52,10 +51,10 @@
 
 Use: Control over swipes/input for notification views, can be used to control what happens when you swipe/long-press
 
-### Action: com.android.systemui.action.PLUGIN_CLOCK
-Expected interface: [ClockPlugin](/frameworks/base/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockPlugin.java)
+### Action: com.android.systemui.action.PLUGIN_CLOCK_PROVIDER
+Expected interface: [ClockProviderPlugin](/frameworks/base/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt)
 
-Use: Allows replacement of the keyguard main clock.
+Use: Allows replacement of the keyguard main clock. See [additional Documentation](./clock-plugins.md).
 
 ### Action: com.android.systemui.action.PLUGIN_TOAST
 Expected interface: [ToastPlugin](/frameworks/base/packages/SystemUI/plugin/src/com/android/systemui/plugins/ToastPlugin.java)
diff --git a/packages/SystemUI/proguard.flags b/packages/SystemUI/proguard.flags
index 10bb00c..65f5a14 100644
--- a/packages/SystemUI/proguard.flags
+++ b/packages/SystemUI/proguard.flags
@@ -1,156 +1,13 @@
-# Preserve line number information for debugging stack traces.
--keepattributes SourceFile,LineNumberTable
+-include proguard_common.flags
 
-# Preserve relationship information that can impact simple class naming.
--keepattributes EnclosingMethod,InnerClasses
-
--keep class com.android.systemui.recents.OverviewProxyRecentsImpl
--keep class com.android.systemui.statusbar.car.CarStatusBar
--keep class com.android.systemui.statusbar.phone.CentralSurfaces
 -keep class com.android.systemui.statusbar.tv.TvStatusBar
--keep class ** extends com.android.systemui.SystemUIInitializer {
-    *;
-}
--keep class * extends com.android.systemui.CoreStartable
--keep class * implements com.android.systemui.CoreStartable$Injector
-
-# Needed for builds to properly initialize KeyFrames from xml scene
--keepclassmembers class * extends androidx.constraintlayout.motion.widget.Key {
-  public <init>();
-}
-
-# Needed to ensure callback field references are kept in their respective
-# owning classes when the downstream callback registrars only store weak refs.
-# TODO(b/264686688): Handle these cases with more targeted annotations.
--keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** {
-  private com.android.keyguard.KeyguardUpdateMonitorCallback *;
-  private com.android.systemui.privacy.PrivacyConfig$Callback *;
-  private com.android.systemui.privacy.PrivacyItemController$Callback *;
-  private com.android.systemui.settings.UserTracker$Callback *;
-  private com.android.systemui.statusbar.phone.StatusBarWindowCallback *;
-  private com.android.systemui.util.service.Observer$Callback *;
-  private com.android.systemui.util.service.ObservableServiceConnection$Callback *;
-}
-# Note that these rules are temporary companions to the above rules, required
-# for cases like Kotlin where fields with anonymous types use the anonymous type
-# rather than the supertype.
--if class * extends com.android.keyguard.KeyguardUpdateMonitorCallback
--keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** {
-  <1> *;
-}
--if class * extends com.android.systemui.privacy.PrivacyConfig$Callback
--keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** {
-  <1> *;
-}
--if class * extends com.android.systemui.privacy.PrivacyItemController$Callback
--keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** {
-  <1> *;
-}
--if class * extends com.android.systemui.settings.UserTracker$Callback
--keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** {
-  <1> *;
-}
--if class * extends com.android.systemui.statusbar.phone.StatusBarWindowCallback
--keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** {
-  <1> *;
-}
--if class * extends com.android.systemui.util.service.Observer$Callback
--keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** {
-  <1> *;
-}
--if class * extends com.android.systemui.util.service.ObservableServiceConnection$Callback
--keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** {
-  <1> *;
-}
-
--keepclasseswithmembers class * {
-    public <init>(android.content.Context, android.util.AttributeSet);
-}
-
--keep class ** extends androidx.preference.PreferenceFragment
--keep class com.android.systemui.tuner.*
-
-# The plugins subpackage acts as a shared library that might be referenced in
-# dynamically-loaded plugin APKs.
--keep class com.android.systemui.plugins.** {
-    *;
-}
--keep class com.android.systemui.fragments.FragmentService$FragmentCreator {
-    *;
-}
--keep class androidx.core.app.CoreComponentFactory
-
--keep public class * extends com.android.systemui.CoreStartable {
-    public <init>(android.content.Context);
-}
-
-# Keep the wm shell lib
--keep class com.android.wm.shell.*
-# Keep the protolog group methods that are called by the generated code
--keepclassmembers class com.android.wm.shell.protolog.ShellProtoLogGroup {
+-keep class com.android.systemui.SystemUIInitializerImpl {
     *;
 }
 
--keep,allowoptimization,allowaccessmodification class com.android.systemui.dagger.GlobalRootComponent { !synthetic *; }
--keep,allowoptimization,allowaccessmodification class com.android.systemui.dagger.GlobalRootComponent$SysUIComponentImpl { !synthetic *; }
--keep,allowoptimization,allowaccessmodification class com.android.systemui.dagger.Dagger** { !synthetic *; }
--keep,allowoptimization,allowaccessmodification class com.android.systemui.tv.Dagger** { !synthetic *; }
-
-# Prevent optimization or access modification of any referenced code that may
-# conflict with code in the bootclasspath.
-# TODO(b/222468116): Resolve such collisions in the build system.
--keepnames class android.**.nano.** { *; }
--keepnames class com.android.**.nano.** { *; }
--keepnames class com.android.internal.protolog.** { *; }
--keepnames class android.hardware.common.** { *; }
-
-# Allows proguard to make private and protected methods and fields public as
-# part of optimization. This lets proguard inline trivial getter/setter methods.
--allowaccessmodification
-
-# Removes runtime checks added through Kotlin to JVM code genereration to
-# avoid linear growth as more Kotlin code is converted / added to the codebase.
-# These checks are generally applied to Java platform types (values returned
-# from Java code that don't have nullness annotations), but we remove them to
-# avoid code size increases.
-#
-# See also https://kotlinlang.org/docs/reference/java-interop.html
-#
-# TODO(b/199941987): Consider standardizing these rules in a central place as
-# Kotlin gains adoption with other platform targets.
--assumenosideeffects class kotlin.jvm.internal.Intrinsics {
-    # Remove check for method parameters being null
-    static void checkParameterIsNotNull(java.lang.Object, java.lang.String);
-
-    # When a Java platform type is returned and passed to Kotlin NonNull method,
-    # remove the null check
-    static void checkExpressionValueIsNotNull(java.lang.Object, java.lang.String);
-    static void checkNotNullExpressionValue(java.lang.Object, java.lang.String);
-
-    # Remove check that final value returned from method is null, if passing
-    # back Java platform type.
-    static void checkReturnedValueIsNotNull(java.lang.Object, java.lang.String, java.lang.String);
-    static void checkReturnedValueIsNotNull(java.lang.Object, java.lang.String);
-
-    # Null check for accessing a field from a parent class written in Java.
-    static void checkFieldIsNotNull(java.lang.Object, java.lang.String, java.lang.String);
-    static void checkFieldIsNotNull(java.lang.Object, java.lang.String);
-
-    # Removes code generated from !! operator which converts Nullable type to
-    # NonNull type. These would throw an NPE immediate after on access.
-    static void checkNotNull(java.lang.Object, java.lang.String);
-    static void checkNotNullParameter(java.lang.Object, java.lang.String);
-
-    # Removes lateinit var check being used before being set. Check is applied
-    # on every field access without this.
-    static void throwUninitializedPropertyAccessException(java.lang.String);
+-keep class com.android.systemui.TVSystemUIInitializer {
+    *;
 }
-# Strip verbose logs.
--assumenosideeffects class android.util.Log {
-  static *** v(...);
-  static *** isLoggable(...);
-}
--assumenosideeffects class android.util.Slog {
-  static *** v(...);
-}
--maximumremovedandroidloglevel 2
+
+-keep,allowoptimization,allowaccessmodification class com.android.systemui.dagger.DaggerReferenceGlobalRootComponent** { !synthetic *; }
+-keep,allowoptimization,allowaccessmodification class com.android.systemui.tv.DaggerTvGlobalRootComponent** { !synthetic *; }
\ No newline at end of file
diff --git a/packages/SystemUI/proguard_common.flags b/packages/SystemUI/proguard_common.flags
new file mode 100644
index 0000000..1d008cf
--- /dev/null
+++ b/packages/SystemUI/proguard_common.flags
@@ -0,0 +1,141 @@
+# Preserve line number information for debugging stack traces.
+-keepattributes SourceFile,LineNumberTable
+
+-keep class com.android.systemui.VendorServices
+
+# the `#inject` methods are accessed via reflection to work on ContentProviders
+-keepclassmembers class * extends com.android.systemui.dagger.SysUIComponent { void inject(***); }
+
+# Needed for builds to properly initialize KeyFrames from xml scene
+-keepclassmembers class * extends androidx.constraintlayout.motion.widget.Key {
+  public <init>();
+}
+
+# Needed to ensure callback field references are kept in their respective
+# owning classes when the downstream callback registrars only store weak refs.
+# TODO(b/264686688): Handle these cases with more targeted annotations.
+-keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** {
+  private com.android.keyguard.KeyguardUpdateMonitorCallback *;
+  private com.android.systemui.privacy.PrivacyConfig$Callback *;
+  private com.android.systemui.privacy.PrivacyItemController$Callback *;
+  private com.android.systemui.settings.UserTracker$Callback *;
+  private com.android.systemui.statusbar.phone.StatusBarWindowCallback *;
+  private com.android.systemui.util.service.Observer$Callback *;
+  private com.android.systemui.util.service.ObservableServiceConnection$Callback *;
+}
+# Note that these rules are temporary companions to the above rules, required
+# for cases like Kotlin where fields with anonymous types use the anonymous type
+# rather than the supertype.
+-if class * extends com.android.keyguard.KeyguardUpdateMonitorCallback
+-keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** {
+  <1> *;
+}
+-if class * extends com.android.systemui.privacy.PrivacyConfig$Callback
+-keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** {
+  <1> *;
+}
+-if class * extends com.android.systemui.privacy.PrivacyItemController$Callback
+-keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** {
+  <1> *;
+}
+-if class * extends com.android.systemui.settings.UserTracker$Callback
+-keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** {
+  <1> *;
+}
+-if class * extends com.android.systemui.statusbar.phone.StatusBarWindowCallback
+-keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** {
+  <1> *;
+}
+-if class * extends com.android.systemui.util.service.Observer$Callback
+-keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** {
+  <1> *;
+}
+-if class * extends com.android.systemui.util.service.ObservableServiceConnection$Callback
+-keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** {
+  <1> *;
+}
+
+-keepclasseswithmembers class * {
+    public <init>(android.content.Context, android.util.AttributeSet);
+}
+
+-keep class ** extends androidx.preference.PreferenceFragment
+-keep class com.android.systemui.tuner.*
+
+# The plugins subpackage acts as a shared library that might be referenced in
+# dynamically-loaded plugin APKs.
+-keep class com.android.systemui.plugins.** {
+    *;
+}
+-keep class com.android.systemui.fragments.FragmentService$FragmentCreator {
+    *;
+}
+-keep class androidx.core.app.CoreComponentFactory
+
+# Keep the wm shell lib
+-keep class com.android.wm.shell.*
+# Keep the protolog group methods that are called by the generated code
+-keepclassmembers class com.android.wm.shell.protolog.ShellProtoLogGroup {
+    *;
+}
+
+# Prevent optimization or access modification of any referenced code that may
+# conflict with code in the bootclasspath.
+# TODO(b/222468116): Resolve such collisions in the build system.
+-keepnames class android.**.nano.** { *; }
+-keepnames class com.android.**.nano.** { *; }
+-keepnames class com.android.internal.protolog.** { *; }
+-keepnames class android.hardware.common.** { *; }
+
+# Allows proguard to make private and protected methods and fields public as
+# part of optimization. This lets proguard inline trivial getter/setter methods.
+-allowaccessmodification
+
+# Removes runtime checks added through Kotlin to JVM code genereration to
+# avoid linear growth as more Kotlin code is converted / added to the codebase.
+# These checks are generally applied to Java platform types (values returned
+# from Java code that don't have nullness annotations), but we remove them to
+# avoid code size increases.
+#
+# See also https://kotlinlang.org/docs/reference/java-interop.html
+#
+# TODO(b/199941987): Consider standardizing these rules in a central place as
+# Kotlin gains adoption with other platform targets.
+-assumenosideeffects class kotlin.jvm.internal.Intrinsics {
+    # Remove check for method parameters being null
+    static void checkParameterIsNotNull(java.lang.Object, java.lang.String);
+
+    # When a Java platform type is returned and passed to Kotlin NonNull method,
+    # remove the null check
+    static void checkExpressionValueIsNotNull(java.lang.Object, java.lang.String);
+    static void checkNotNullExpressionValue(java.lang.Object, java.lang.String);
+
+    # Remove check that final value returned from method is null, if passing
+    # back Java platform type.
+    static void checkReturnedValueIsNotNull(java.lang.Object, java.lang.String, java.lang.String);
+    static void checkReturnedValueIsNotNull(java.lang.Object, java.lang.String);
+
+    # Null check for accessing a field from a parent class written in Java.
+    static void checkFieldIsNotNull(java.lang.Object, java.lang.String, java.lang.String);
+    static void checkFieldIsNotNull(java.lang.Object, java.lang.String);
+
+    # Removes code generated from !! operator which converts Nullable type to
+    # NonNull type. These would throw an NPE immediate after on access.
+    static void checkNotNull(java.lang.Object, java.lang.String);
+    static void checkNotNullParameter(java.lang.Object, java.lang.String);
+
+    # Removes lateinit var check being used before being set. Check is applied
+    # on every field access without this.
+    static void throwUninitializedPropertyAccessException(java.lang.String);
+}
+
+
+# Strip verbose logs.
+-assumenosideeffects class android.util.Log {
+  static *** v(...);
+  static *** isLoggable(...);
+}
+-assumenosideeffects class android.util.Slog {
+  static *** v(...);
+}
+-maximumremovedandroidloglevel 2
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 63a4fd2..3b38870 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -47,7 +47,6 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.FlagsModule;
-import com.android.systemui.fragments.FragmentService;
 import com.android.systemui.keyboard.KeyboardModule;
 import com.android.systemui.keyguard.data.BouncerViewModule;
 import com.android.systemui.log.dagger.LogModule;
@@ -63,6 +62,7 @@
 import com.android.systemui.qrcodescanner.dagger.QRCodeScannerModule;
 import com.android.systemui.qs.FgsManagerController;
 import com.android.systemui.qs.FgsManagerControllerImpl;
+import com.android.systemui.qs.QSFragmentStartableModule;
 import com.android.systemui.qs.footer.dagger.FooterActionsModule;
 import com.android.systemui.recents.Recents;
 import com.android.systemui.screenrecord.ScreenRecordModule;
@@ -116,16 +116,16 @@
 import com.android.systemui.wmshell.BubblesManager;
 import com.android.wm.shell.bubbles.Bubbles;
 
-import java.util.Optional;
-import java.util.concurrent.Executor;
-
-import javax.inject.Named;
-
 import dagger.Binds;
 import dagger.BindsOptionalOf;
 import dagger.Module;
 import dagger.Provides;
 
+import java.util.Optional;
+import java.util.concurrent.Executor;
+
+import javax.inject.Named;
+
 /**
  * A dagger module for injecting components of System UI that are required by System UI.
  *
@@ -165,6 +165,7 @@
             PolicyModule.class,
             PrivacyModule.class,
             QRCodeScannerModule.class,
+            QSFragmentStartableModule.class,
             ScreenshotModule.class,
             SensorModule.class,
             SecurityRepositoryModule.class,
@@ -194,8 +195,7 @@
             DozeComponent.class,
             ExpandableNotificationRowComponent.class,
             KeyguardBouncerComponent.class,
-            NotificationShelfComponent.class,
-            FragmentService.FragmentCreator.class
+            NotificationShelfComponent.class
         })
 public abstract class SystemUIModule {
 
diff --git a/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java b/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java
index 6a27ee7..81a5206 100644
--- a/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java
+++ b/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java
@@ -39,16 +39,17 @@
 import com.android.systemui.plugins.Plugin;
 import com.android.systemui.util.leak.LeakDetector;
 
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.lang.reflect.InvocationTargetException;
-import java.util.ArrayList;
-import java.util.HashMap;
-
 import dagger.assisted.Assisted;
 import dagger.assisted.AssistedFactory;
 import dagger.assisted.AssistedInject;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import javax.inject.Provider;
+
 public class FragmentHostManager {
 
     private final Handler mHandler = new Handler(Looper.getMainLooper());
@@ -322,25 +323,17 @@
             return instantiateWithInjections(context, className, arguments);
         }
 
-        private Fragment instantiateWithInjections(
-                Context context, String className, Bundle args) {
-            FragmentService.FragmentInstantiationInfo fragmentInstantiationInfo =
+        private Fragment instantiateWithInjections(Context context, String className, Bundle args) {
+            Provider<? extends Fragment> fragmentProvider =
                     mManager.getInjectionMap().get(className);
-            if (fragmentInstantiationInfo != null) {
-                try {
-                    Fragment f = (Fragment) fragmentInstantiationInfo
-                            .mMethod
-                            .invoke(fragmentInstantiationInfo.mDaggerComponent);
-                    // Setup the args, taken from Fragment#instantiate.
-                    if (args != null) {
-                        args.setClassLoader(f.getClass().getClassLoader());
-                        f.setArguments(args);
-                    }
-                    return f;
-                } catch (IllegalAccessException | InvocationTargetException e) {
-                    throw new Fragment.InstantiationException("Unable to instantiate " + className,
-                            e);
+            if (fragmentProvider != null) {
+                Fragment f = fragmentProvider.get();
+                // Setup the args, taken from Fragment#instantiate.
+                if (args != null) {
+                    args.setClassLoader(f.getClass().getClassLoader());
+                    f.setArguments(args);
                 }
+                return f;
             }
             return Fragment.instantiate(context, className, args);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/fragments/FragmentService.java b/packages/SystemUI/src/com/android/systemui/fragments/FragmentService.java
index d302b13a..a75c056 100644
--- a/packages/SystemUI/src/com/android/systemui/fragments/FragmentService.java
+++ b/packages/SystemUI/src/com/android/systemui/fragments/FragmentService.java
@@ -24,16 +24,12 @@
 import com.android.systemui.Dumpable;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.qs.QSFragment;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 
 import java.io.PrintWriter;
-import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
 
 import javax.inject.Inject;
-
-import dagger.Subcomponent;
+import javax.inject.Provider;
 
 /**
  * Holds a map of root views to FragmentHostStates and generates them as needed.
@@ -49,9 +45,9 @@
      * A map with the means to create fragments via Dagger injection.
      *
      * key: the fragment class name.
-     * value: see {@link FragmentInstantiationInfo}.
+     * value: A {@link Provider} for the Fragment
      */
-    private final ArrayMap<String, FragmentInstantiationInfo> mInjectionMap = new ArrayMap<>();
+    private final ArrayMap<String, Provider<? extends Fragment>> mInjectionMap = new ArrayMap<>();
     private final Handler mHandler = new Handler();
     private final FragmentHostManager.Factory mFragmentHostManagerFactory;
 
@@ -67,38 +63,31 @@
 
     @Inject
     public FragmentService(
-            FragmentCreator.Factory fragmentCreatorFactory,
             FragmentHostManager.Factory fragmentHostManagerFactory,
             ConfigurationController configurationController,
             DumpManager dumpManager) {
         mFragmentHostManagerFactory = fragmentHostManagerFactory;
-        addFragmentInstantiationProvider(fragmentCreatorFactory.build());
         configurationController.addCallback(mConfigurationListener);
 
-        dumpManager.registerDumpable(getClass().getSimpleName(), this);
+        dumpManager.registerNormalDumpable(this);
     }
 
-    ArrayMap<String, FragmentInstantiationInfo> getInjectionMap() {
+    ArrayMap<String, Provider<? extends Fragment>> getInjectionMap() {
         return mInjectionMap;
     }
 
     /**
      * Adds a new Dagger component object that provides method(s) to create fragments via injection.
      */
-    public void addFragmentInstantiationProvider(Object daggerComponent) {
-        for (Method method : daggerComponent.getClass().getDeclaredMethods()) {
-            if (Fragment.class.isAssignableFrom(method.getReturnType())
-                    && (method.getModifiers() & Modifier.PUBLIC) != 0) {
-                String fragmentName = method.getReturnType().getName();
-                if (mInjectionMap.containsKey(fragmentName)) {
-                    Log.w(TAG, "Fragment " + fragmentName + " is already provided by different"
-                            + " Dagger component; Not adding method");
-                    continue;
-                }
-                mInjectionMap.put(
-                        fragmentName, new FragmentInstantiationInfo(method, daggerComponent));
-            }
+    public void addFragmentInstantiationProvider(
+            Class<?> fragmentCls, Provider<? extends Fragment> provider) {
+        String fragmentName = fragmentCls.getName();
+        if (mInjectionMap.containsKey(fragmentName)) {
+            Log.w(TAG, "Fragment " + fragmentName + " is already provided by different"
+                    + " Dagger component; Not adding method");
+            return;
         }
+        mInjectionMap.put(fragmentName, provider);
     }
 
     public FragmentHostManager getFragmentHostManager(View view) {
@@ -132,22 +121,6 @@
         }
     }
 
-    /**
-     * The subcomponent of dagger that holds all fragments that need injection.
-     */
-    @Subcomponent
-    public interface FragmentCreator {
-        /** Factory for creating a FragmentCreator. */
-        @Subcomponent.Factory
-        interface Factory {
-            FragmentCreator build();
-        }
-        /**
-         * Inject a QSFragment.
-         */
-        QSFragment createQSFragment();
-    }
-
     private class FragmentHostState {
         private final View mView;
 
@@ -170,16 +143,4 @@
             mFragmentHostManager.onConfigurationChanged(newConfig);
         }
     }
-
-    /** An object containing the information needed to instantiate a fragment. */
-    static class FragmentInstantiationInfo {
-        /** The method that returns a newly-created fragment of the given class. */
-        final Method mMethod;
-        /** The Dagger component that the method should be invoked on. */
-        final Object mDaggerComponent;
-        FragmentInstantiationInfo(Method method, Object daggerComponent) {
-            this.mMethod = method;
-            this.mDaggerComponent = daggerComponent;
-        }
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragmentStartable.kt b/packages/SystemUI/src/com/android/systemui/qs/QSFragmentStartable.kt
new file mode 100644
index 0000000..253560b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragmentStartable.kt
@@ -0,0 +1,47 @@
+/*
+ * 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.qs
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.fragments.FragmentService
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+import javax.inject.Inject
+import javax.inject.Provider
+
+@SysUISingleton
+class QSFragmentStartable
+@Inject
+constructor(
+    private val fragmentService: FragmentService,
+    private val qsFragmentProvider: Provider<QSFragment>
+) : CoreStartable {
+    override fun start() {
+        fragmentService.addFragmentInstantiationProvider(QSFragment::class.java, qsFragmentProvider)
+    }
+}
+
+@Module
+interface QSFragmentStartableModule {
+    @Binds
+    @IntoMap
+    @ClassKey(QSFragmentStartable::class)
+    fun bindsQSFragmentStartable(startable: QSFragmentStartable): CoreStartable
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 00519b9..209c377 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -228,6 +228,7 @@
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
 import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent;
 import com.android.systemui.statusbar.phone.dagger.StatusBarPhoneModule;
+import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment;
 import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.BrightnessMirrorController;
@@ -1613,7 +1614,9 @@
             }
         }
         mCentralSurfacesComponent = mCentralSurfacesComponentFactory.create();
-        mFragmentService.addFragmentInstantiationProvider(mCentralSurfacesComponent);
+        mFragmentService.addFragmentInstantiationProvider(
+                CollapsedStatusBarFragment.class,
+                mCentralSurfacesComponent::createCollapsedStatusBarFragment);
 
         mNotificationShadeWindowView = mCentralSurfacesComponent.getNotificationShadeWindowView();
         mNotificationShadeWindowViewController = mCentralSurfacesComponent
diff --git a/packages/SystemUI/tests/src/com/android/systemui/fragments/FragmentServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/fragments/FragmentServiceTest.kt
index a2dc1eb..4ba1bc6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/fragments/FragmentServiceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/fragments/FragmentServiceTest.kt
@@ -5,7 +5,6 @@
 import android.test.suitebuilder.annotation.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
-import com.android.systemui.qs.QSFragment
 import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
@@ -13,9 +12,7 @@
 
 @SmallTest
 class FragmentServiceTest : SysuiTestCase() {
-    private val fragmentCreator = TestFragmentCreator()
-    private val fragmenetHostManagerFactory: FragmentHostManager.Factory = mock()
-    private val fragmentCreatorFactory = FragmentService.FragmentCreator.Factory { fragmentCreator }
+    private val fragmentHostManagerFactory: FragmentHostManager.Factory = mock()
 
     private lateinit var fragmentService: FragmentService
 
@@ -25,65 +22,29 @@
             Looper.prepare()
         }
 
-        fragmentService =
-            FragmentService(
-                fragmentCreatorFactory,
-                fragmenetHostManagerFactory,
-                mock(),
-                DumpManager()
-            )
-    }
-
-    @Test
-    fun constructor_addsFragmentCreatorMethodsToMap() {
-        val map = fragmentService.injectionMap
-        assertThat(map).hasSize(2)
-        assertThat(map.keys).contains(QSFragment::class.java.name)
-        assertThat(map.keys).contains(TestFragmentInCreator::class.java.name)
+        fragmentService = FragmentService(fragmentHostManagerFactory, mock(), DumpManager())
     }
 
     @Test
     fun addFragmentInstantiationProvider_objectHasNoFragmentMethods_nothingAdded() {
-        fragmentService.addFragmentInstantiationProvider(Object())
+        fragmentService.addFragmentInstantiationProvider(TestFragment::class.java) {
+            TestFragment()
+        }
 
-        assertThat(fragmentService.injectionMap).hasSize(2)
-    }
-
-    @Test
-    fun addFragmentInstantiationProvider_objectHasFragmentMethods_methodsAdded() {
-        fragmentService.addFragmentInstantiationProvider(
-            @Suppress("unused")
-            object : Any() {
-                fun createTestFragment2() = TestFragment2()
-                fun createTestFragment3() = TestFragment3()
-            }
-        )
-
-        val map = fragmentService.injectionMap
-        assertThat(map).hasSize(4)
-        assertThat(map.keys).contains(TestFragment2::class.java.name)
-        assertThat(map.keys).contains(TestFragment3::class.java.name)
+        assertThat(fragmentService.injectionMap).hasSize(1)
     }
 
     @Test
     fun addFragmentInstantiationProvider_objectFragmentMethodsAlreadyProvided_nothingAdded() {
-        fragmentService.addFragmentInstantiationProvider(
-            @Suppress("unused")
-            object : Any() {
-                fun createTestFragment() = TestFragmentInCreator()
-            }
-        )
+        fragmentService.addFragmentInstantiationProvider(TestFragment::class.java) {
+            TestFragment()
+        }
+        fragmentService.addFragmentInstantiationProvider(TestFragment::class.java) {
+            TestFragment()
+        }
 
-        assertThat(fragmentService.injectionMap).hasSize(2)
+        assertThat(fragmentService.injectionMap).hasSize(1)
     }
 
-    class TestFragmentCreator : FragmentService.FragmentCreator {
-        override fun createQSFragment(): QSFragment = mock()
-        @Suppress("unused")
-        fun createTestFragment(): TestFragmentInCreator = TestFragmentInCreator()
-    }
-
-    class TestFragmentInCreator : Fragment()
-    class TestFragment2 : Fragment()
-    class TestFragment3 : Fragment()
+    class TestFragment : Fragment()
 }
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 0b22f36..c5008fa 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -2067,7 +2067,9 @@
                     final boolean isOldTypeShortFgs = r.isShortFgs();
                     final boolean isNewTypeShortFgs =
                             foregroundServiceType == FOREGROUND_SERVICE_TYPE_SHORT_SERVICE;
-                    final boolean isOldTypeShortFgsAndTimedOut = r.shouldTriggerShortFgsTimeout();
+                    final long nowUptime = SystemClock.uptimeMillis();
+                    final boolean isOldTypeShortFgsAndTimedOut =
+                            r.shouldTriggerShortFgsTimeout(nowUptime);
 
                     // If true, we skip the BFSL check.
                     boolean bypassBfslCheck = false;
@@ -3225,9 +3227,11 @@
 
     void onShortFgsTimeout(ServiceRecord sr) {
         synchronized (mAm) {
-            if (!sr.shouldTriggerShortFgsTimeout()) {
+            final long nowUptime = SystemClock.uptimeMillis();
+            if (!sr.shouldTriggerShortFgsTimeout(nowUptime)) {
                 if (DEBUG_SHORT_SERVICE) {
-                    Slog.d(TAG_SERVICE, "[STALE] Short FGS timed out: " + sr);
+                    Slog.d(TAG_SERVICE, "[STALE] Short FGS timed out: " + sr
+                            + " " + sr.getShortFgsTimedEventDescription(nowUptime));
                 }
                 return;
             }
@@ -3261,7 +3265,8 @@
             if (sr == null) {
                 return false;
             }
-            return sr.shouldTriggerShortFgsTimeout();
+            final long nowUptime = SystemClock.uptimeMillis();
+            return sr.shouldTriggerShortFgsTimeout(nowUptime);
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
@@ -3269,9 +3274,11 @@
 
     void onShortFgsProcstateTimeout(ServiceRecord sr) {
         synchronized (mAm) {
-            if (!sr.shouldDemoteShortFgsProcState()) {
+            final long nowUptime = SystemClock.uptimeMillis();
+            if (!sr.shouldDemoteShortFgsProcState(nowUptime)) {
                 if (DEBUG_SHORT_SERVICE) {
-                    Slog.d(TAG_SERVICE, "[STALE] Short FGS procstate demotion: " + sr);
+                    Slog.d(TAG_SERVICE, "[STALE] Short FGS procstate demotion: " + sr
+                            + " " + sr.getShortFgsTimedEventDescription(nowUptime));
                 }
                 return;
             }
@@ -3292,9 +3299,11 @@
         synchronized (mAm) {
             tr.mLatencyTracker.waitingOnAMSLockEnded();
 
-            if (!sr.shouldTriggerShortFgsAnr()) {
+            final long nowUptime = SystemClock.uptimeMillis();
+            if (!sr.shouldTriggerShortFgsAnr(nowUptime)) {
                 if (DEBUG_SHORT_SERVICE) {
-                    Slog.d(TAG_SERVICE, "[STALE] Short FGS ANR'ed: " + sr);
+                    Slog.d(TAG_SERVICE, "[STALE] Short FGS ANR'ed: " + sr
+                            + " " + sr.getShortFgsTimedEventDescription(nowUptime));
                 }
                 return;
             }
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index 6180117..663121e 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -392,6 +392,15 @@
             return mStartTime + ams.mConstants.mShortFgsTimeoutDuration
                     + ams.mConstants.mShortFgsAnrExtraWaitDuration;
         }
+
+        String getDescription() {
+            return "sfc=" + this.mStartForegroundCount
+                    + " sid=" + this.mStartId
+                    + " stime=" + this.mStartTime
+                    + " tt=" + this.getTimeoutTime()
+                    + " dt=" + this.getProcStateDemoteTime()
+                    + " at=" + this.getAnrTime();
+        }
     }
 
     /**
@@ -1413,10 +1422,7 @@
         this.mShortFgsInfo = null;
     }
 
-    /**
-     * @return true if it's a short FGS that's still up and running, and should be timed out.
-     */
-    public boolean shouldTriggerShortFgsTimeout() {
+    private boolean shouldTriggerShortFgsTimedEvent(long targetTime, long nowUptime) {
         if (!isAppAlive()) {
             return false;
         }
@@ -1424,36 +1430,48 @@
                 || !mShortFgsInfo.isCurrent()) {
             return false;
         }
-        return mShortFgsInfo.getTimeoutTime() <= SystemClock.uptimeMillis();
+        return targetTime <= nowUptime;
+    }
+
+    /**
+     * @return true if it's a short FGS that's still up and running, and should be timed out.
+     */
+    public boolean shouldTriggerShortFgsTimeout(long nowUptime) {
+        return shouldTriggerShortFgsTimedEvent(
+                (mShortFgsInfo == null ? 0 : mShortFgsInfo.getTimeoutTime()),
+                nowUptime);
     }
 
     /**
      * @return true if it's a short FGS's procstate should be demoted.
      */
-    public boolean shouldDemoteShortFgsProcState() {
-        if (!isAppAlive()) {
-            return false;
-        }
-        if (!this.startRequested || !isShortFgs() || mShortFgsInfo == null
-                || !mShortFgsInfo.isCurrent()) {
-            return false;
-        }
-        return mShortFgsInfo.getProcStateDemoteTime() <= SystemClock.uptimeMillis();
+    public boolean shouldDemoteShortFgsProcState(long nowUptime) {
+        return shouldTriggerShortFgsTimedEvent(
+                (mShortFgsInfo == null ? 0 : mShortFgsInfo.getProcStateDemoteTime()),
+                nowUptime);
     }
 
     /**
      * @return true if it's a short FGS that's still up and running, and should be declared
      * an ANR.
      */
-    public boolean shouldTriggerShortFgsAnr() {
-        if (!isAppAlive()) {
-            return false;
-        }
-        if (!this.startRequested || !isShortFgs() || mShortFgsInfo == null
-                || !mShortFgsInfo.isCurrent()) {
-            return false;
-        }
-        return mShortFgsInfo.getAnrTime() <= SystemClock.uptimeMillis();
+    public boolean shouldTriggerShortFgsAnr(long nowUptime) {
+        return shouldTriggerShortFgsTimedEvent(
+                (mShortFgsInfo == null ? 0 : mShortFgsInfo.getAnrTime()),
+                nowUptime);
+    }
+
+    /**
+     * Human readable description about short-FGS internal states.
+     */
+    public String getShortFgsTimedEventDescription(long nowUptime) {
+        return "aa=" + isAppAlive()
+                + " sreq=" + this.startRequested
+                + " isfg=" + this.isForeground
+                + " type=" + Integer.toHexString(this.foregroundServiceType)
+                + " sfc=" + this.mStartForegroundCount
+                + " now=" + nowUptime
+                + " " + (mShortFgsInfo == null ? "" : mShortFgsInfo.getDescription());
     }
 
     private boolean isAppAlive() {
diff --git a/services/core/java/com/android/server/am/TEST_MAPPING b/services/core/java/com/android/server/am/TEST_MAPPING
index d4bcd9e..97d4879 100644
--- a/services/core/java/com/android/server/am/TEST_MAPPING
+++ b/services/core/java/com/android/server/am/TEST_MAPPING
@@ -3,9 +3,11 @@
     {
       "name": "CtsAppTestCases",
       "options": [
-        {
-          "include-filter": "android.app.cts.TaskDescriptionTest"
-        },
+        { "include-filter": "android.app.cts.TaskDescriptionTest" },
+        { "include-filter": "android.app.cts.ActivityManagerTest" },
+        { "include-filter": "android.app.cts.ActivityManagerProcessStateTest" },
+        { "include-filter": "android.app.cts.ServiceTest" },
+        { "include-filter": "android.app.cts.ActivityManagerFgsBgStartTest" },
         {
           "include-annotation": "android.platform.test.annotations.Presubmit"
         },
@@ -14,7 +16,8 @@
         },
         {
           "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
+        },
+        { "exclude-annotation": "org.junit.Ignore" }
       ]
     },
     {
@@ -148,6 +151,16 @@
           "include-filter": "android.appsecurity.cts.AppDataIsolationTests"
         }
       ]
+    },
+    {
+      "name": "CtsAppTestCases",
+      "options": [
+        { "include-filter": "android.app.cts.TaskDescriptionTest" },
+        { "include-filter": "android.app.cts.ActivityManagerTest" },
+        { "include-filter": "android.app.cts.ActivityManagerProcessStateTest" },
+        { "include-filter": "android.app.cts.ServiceTest" },
+        { "include-filter": "android.app.cts.ActivityManagerFgsBgStartTest" }
+      ]
     }
   ]
 }