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" }
+ ]
}
]
}