summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/api/test-current.txt5
-rw-r--r--core/java/android/app/Notification.java17
-rw-r--r--core/java/android/app/admin/DevicePolicyManager.java5
-rw-r--r--core/java/android/os/BatteryStats.java202
-rw-r--r--core/java/android/os/BatteryUsageStats.java2
-rwxr-xr-xcore/java/android/os/Build.java2
-rw-r--r--core/java/com/android/internal/os/BatteryStatsHistory.java355
-rw-r--r--core/java/com/android/internal/os/BatteryStatsHistoryIterator.java157
-rw-r--r--core/java/com/android/internal/os/LongArrayMultiStateCounter.java32
-rw-r--r--core/java/com/android/internal/os/MultiStateStats.java303
-rw-r--r--core/java/com/android/internal/os/PowerStats.java268
-rw-r--r--core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp12
-rw-r--r--core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java3
-rw-r--r--core/tests/coretests/src/com/android/internal/os/PowerStatsTest.java132
-rw-r--r--libs/WindowManager/Shell/res/layout/bubble_bar_manage_education.xml56
-rw-r--r--libs/WindowManager/Shell/res/values/dimen.xml14
-rw-r--r--libs/WindowManager/Shell/res/values/strings.xml5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleEducationController.kt74
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePopupViewExt.kt47
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java55
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java45
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuViewController.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleEducationViewController.kt148
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubblePopupDrawable.kt232
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubblePopupView.kt65
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java6
-rw-r--r--media/java/android/media/AudioManager.java91
-rw-r--r--media/java/android/media/IAudioService.aidl17
-rw-r--r--media/java/android/media/projection/IMediaProjectionManager.aidl1
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/Keyboards.kt3
-rw-r--r--packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/compose/KeyboardsTest.kt6
-rw-r--r--packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/theme/SettingsThemeTest.kt6
-rw-r--r--packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/AnnotatedTextTest.kt2
-rw-r--r--packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffoldTest.kt2
-rw-r--r--packages/SettingsLib/Spa/testutils/Android.bp2
-rw-r--r--packages/SettingsLib/Spa/testutils/build.gradle.kts7
-rw-r--r--packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt3
-rw-r--r--packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/ConditionalRestarter.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsClassicDebug.java (renamed from packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java)4
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsClassicRelease.java (renamed from packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java)4
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseStartable.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/FlagCommand.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/Flags.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/FlagsCommonModule.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/SystemExitRestarter.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepository.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt)303
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicReleaseTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt)58
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/flags/FlagCommandTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/wmshell/BubbleEducationControllerTest.kt118
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt40
-rw-r--r--services/companion/java/com/android/server/companion/virtual/Android.bp12
-rw-r--r--services/companion/java/com/android/server/companion/virtual/flags.aconfig8
-rw-r--r--services/core/java/com/android/server/am/BatteryStatsService.java7
-rw-r--r--services/core/java/com/android/server/audio/AudioDeviceBroker.java32
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java72
-rw-r--r--services/core/java/com/android/server/audio/FocusRequester.java11
-rw-r--r--services/core/java/com/android/server/audio/MediaFocusControl.java152
-rw-r--r--services/core/java/com/android/server/audio/PlaybackActivityMonitor.java11
-rw-r--r--services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java7
-rw-r--r--services/core/java/com/android/server/power/stats/AggregatedPowerStats.java147
-rw-r--r--services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java6
-rw-r--r--services/core/java/com/android/server/power/stats/BatteryStatsImpl.java132
-rw-r--r--services/core/java/com/android/server/power/stats/CpuAggregatedPowerStats.java (renamed from packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/MockitoHelper.kt)22
-rw-r--r--services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java8
-rw-r--r--services/core/java/com/android/server/power/stats/EnergyConsumerSnapshot.java121
-rw-r--r--services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java232
-rw-r--r--services/core/java/com/android/server/power/stats/PowerStatsAggregator.java226
-rw-r--r--services/core/java/com/android/server/wallpaper/WallpaperManagerService.java31
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java1
-rw-r--r--services/core/java/com/android/server/wm/AsyncRotationController.java50
-rw-r--r--services/core/java/com/android/server/wm/EmbeddedWindowController.java20
-rw-r--r--services/core/java/com/android/server/wm/NavBarFadeAnimationController.java2
-rw-r--r--services/core/java/com/android/server/wm/Transition.java5
-rw-r--r--services/tests/powerstatstests/Android.bp1
-rw-r--r--services/tests/powerstatstests/AndroidManifest.xml1
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java89
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java335
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsNoteTest.java8
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/EnergyConsumerSnapshotTest.java18
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java267
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsAggregatorTest.java232
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsCollectorTest.java3
-rwxr-xr-xservices/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java43
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TransitionTests.java9
-rw-r--r--tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestUtil.java2
-rw-r--r--tools/aapt2/trace/TraceBuffer.cpp14
93 files changed, 4163 insertions, 1148 deletions
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index e61c39ff2525..b1c3b4aef0f4 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1877,6 +1877,8 @@ package android.media {
public class AudioManager {
method @RequiresPermission("android.permission.QUERY_AUDIO_STATE") public int abandonAudioFocusForTest(@NonNull android.media.AudioFocusRequest, @NonNull String);
+ method @RequiresPermission("Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED") public boolean enterAudioFocusFreezeForTest(@NonNull java.util.List<java.lang.Integer>);
+ method @RequiresPermission("Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED") public boolean exitAudioFocusFreezeForTest();
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) public void forceComputeCsdOnAllDevices(boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) public void forceUseFrameworkMel(boolean);
method @NonNull @RequiresPermission(android.Manifest.permission.CALL_AUDIO_INTERCEPTION) public android.media.AudioRecord getCallDownlinkExtractionAudioRecord(@NonNull android.media.AudioFormat);
@@ -1884,6 +1886,9 @@ package android.media {
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) public float getCsd();
method @Nullable public static android.media.AudioDeviceInfo getDeviceInfoFromType(int);
method @IntRange(from=0) @RequiresPermission("android.permission.QUERY_AUDIO_STATE") public long getFadeOutDurationOnFocusLossMillis(@NonNull android.media.AudioAttributes);
+ method @NonNull @RequiresPermission("android.permission.QUERY_AUDIO_STATE") public java.util.List<java.lang.Integer> getFocusDuckedUidsForTest();
+ method @RequiresPermission("android.permission.QUERY_AUDIO_STATE") public long getFocusFadeOutDurationForTest();
+ method @RequiresPermission("android.permission.QUERY_AUDIO_STATE") public long getFocusUnmuteDelayAfterFadeOutForTest();
method @Nullable public static android.media.AudioHalVersionInfo getHalVersion();
method public static final int[] getPublicStreamTypes();
method @NonNull public java.util.List<java.lang.Integer> getReportedSurroundFormats();
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 6cad578b6d7e..bf5b428bc9b1 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -2900,11 +2900,6 @@ public class Notification implements Parcelable
}
}
- final Person person = extras.getParcelable(EXTRA_MESSAGING_PERSON, Person.class);
- if (person != null) {
- person.visitUris(visitor);
- }
-
final RemoteInputHistoryItem[] history = extras.getParcelableArray(
Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS,
RemoteInputHistoryItem.class);
@@ -2916,9 +2911,14 @@ public class Notification implements Parcelable
}
}
}
- }
- if (isStyle(MessagingStyle.class) && extras != null) {
+ // Extras for MessagingStyle. We visit them even if not isStyle(MessagingStyle), since
+ // Notification Listeners might use directly (without the isStyle check).
+ final Person person = extras.getParcelable(EXTRA_MESSAGING_PERSON, Person.class);
+ if (person != null) {
+ person.visitUris(visitor);
+ }
+
final Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES,
Parcelable.class);
if (!ArrayUtils.isEmpty(messages)) {
@@ -2938,9 +2938,8 @@ public class Notification implements Parcelable
}
visitIconUri(visitor, extras.getParcelable(EXTRA_CONVERSATION_ICON, Icon.class));
- }
- if (isStyle(CallStyle.class) & extras != null) {
+ // Extras for CallStyle (same reason for visiting without checking isStyle).
Person callPerson = extras.getParcelable(EXTRA_CALL_PERSON, Person.class);
if (callPerson != null) {
callPerson.visitUris(visitor);
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 33b8b03e3258..715edc5161b7 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -16111,11 +16111,6 @@ public class DevicePolicyManager {
* Called by a profile owner of an organization-owned managed profile to suspend personal
* apps on the device. When personal apps are suspended the device can only be used for calls.
*
- * <p>When personal apps are suspended, an ongoing notification about that is shown to the user.
- * When the user taps the notification, system invokes {@link #ACTION_CHECK_POLICY_COMPLIANCE}
- * in the profile owner package. Profile owner implementation that uses personal apps suspension
- * must handle this intent.
- *
* @param admin Which {@link DeviceAdminReceiver} this request is associated with
* @param suspended Whether personal apps should be suspended.
* @throws IllegalStateException if the profile owner doesn't have an activity that handles
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 42c56265bb4a..8482945dc1f0 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -19,6 +19,7 @@ package android.os;
import static android.os.BatteryStatsManager.NUM_WIFI_STATES;
import static android.os.BatteryStatsManager.NUM_WIFI_SUPPL_STATES;
+import android.annotation.CurrentTimeMillisLong;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -55,6 +56,7 @@ import android.view.Display;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.BatteryStatsHistoryIterator;
import com.android.internal.os.CpuScalingPolicies;
+import com.android.internal.os.PowerStats;
import com.google.android.collect.Lists;
@@ -1793,75 +1795,55 @@ public abstract class BatteryStats {
}
/**
- * Measured energy delta from the previous reading.
+ * An extension to the history item describing a proc state change for a UID.
*/
- public static final class EnergyConsumerDetails {
+ public static final class ProcessStateChange {
+ public int uid;
+ public @BatteryConsumer.ProcessState int processState;
+
+ private static final int LARGE_UID_FLAG = 0x80000000;
+ private static final int SMALL_UID_MASK = 0x00FFFFFF;
+ private static final int PROC_STATE_MASK = 0x7F000000;
+ private static final int PROC_STATE_SHIFT = Integer.numberOfTrailingZeros(PROC_STATE_MASK);
+
/**
- * Description of the energy consumer, such as CPU, DISPLAY etc
+ * Writes this object to the supplied parcel.
*/
- public static final class EnergyConsumer {
- /**
- * See android.hardware.power.stats.EnergyConsumerType
- */
- public int type;
- /**
- * Used when there are multipe energy consumers of the same type, such
- * as CPU clusters, multiple displays on foldable devices etc.
- */
- public int ordinal;
- /**
- * Human-readable name of the energy consumer, e.g. "CPU"
- */
- public String name;
- }
- public EnergyConsumer[] consumers;
- public long[] chargeUC;
-
- @Override
- public String toString() {
- final StringBuilder sb = new StringBuilder();
- for (int i = 0; i < consumers.length; i++) {
- if (chargeUC[i] == POWER_DATA_UNAVAILABLE) {
- continue;
- }
- if (sb.length() != 0) {
- sb.append(' ');
- }
- sb.append(consumers[i].name);
- sb.append('=');
- sb.append(chargeUC[i]);
+ public void writeToParcel(Parcel out) {
+ int bits = processState << PROC_STATE_SHIFT;
+ if ((uid & ~SMALL_UID_MASK) == 0) {
+ bits |= uid;
+ out.writeInt(bits);
+ } else {
+ bits |= LARGE_UID_FLAG;
+ out.writeInt(bits);
+ out.writeInt(uid);
}
- return sb.toString();
}
- }
- /**
- * CPU usage for a given UID.
- */
- public static final class CpuUsageDetails {
/**
- * Descriptions of CPU power brackets, see PowerProfile.getCpuPowerBracketDescription
+ * Reads this object from the supplied parcel.
*/
- public String[] cpuBracketDescriptions;
- public int uid;
+ public void readFromParcel(Parcel in) {
+ int bits = in.readInt();
+ processState = (bits & PROC_STATE_MASK) >>> PROC_STATE_SHIFT;
+ if (processState >= BatteryConsumer.PROCESS_STATE_COUNT) {
+ Slog.e(TAG, "Unrecognized proc state in battery history: " + processState);
+ processState = BatteryConsumer.PROCESS_STATE_UNSPECIFIED;
+ }
+ if ((bits & LARGE_UID_FLAG) == 0) {
+ uid = bits & ~PROC_STATE_MASK;
+ } else {
+ uid = in.readInt();
+ }
+ }
+
/**
- * The delta, in milliseconds, per CPU power bracket, from the previous record for the
- * same UID.
+ * String representation for inclusion in the battery history dump.
*/
- public long[] cpuUsageMs;
-
- @Override
- public String toString() {
- final StringBuilder sb = new StringBuilder();
- UserHandle.formatUid(sb, uid);
- sb.append(": ");
- for (int bracket = 0; bracket < cpuUsageMs.length; bracket++) {
- if (bracket != 0) {
- sb.append(", ");
- }
- sb.append(cpuUsageMs[bracket]);
- }
- return sb.toString();
+ public String formatForBatteryHistory() {
+ return UserHandle.formatUid(uid) + ": "
+ + BatteryConsumer.processStateToString(processState);
}
}
@@ -2008,11 +1990,11 @@ public abstract class BatteryStats {
// Non-null when there is more detailed information at this step.
public HistoryStepDetails stepDetails;
- // Non-null when there is energy consumer information
- public EnergyConsumerDetails energyConsumerDetails;
+ // Non-null when there are power stats to be written to history
+ public PowerStats powerStats;
- // Non-null when there is CPU usage information
- public CpuUsageDetails cpuUsageDetails;
+ // Non-null when there is procstate change to be written to history
+ public ProcessStateChange processStateChange;
public static final int EVENT_FLAG_START = 0x8000;
public static final int EVENT_FLAG_FINISH = 0x4000;
@@ -2110,6 +2092,7 @@ public abstract class BatteryStats {
public final HistoryTag localWakelockTag = new HistoryTag();
public final HistoryTag localWakeReasonTag = new HistoryTag();
public final HistoryTag localEventTag = new HistoryTag();
+ public final ProcessStateChange localProcessStateChange = new ProcessStateChange();
// Includes a tag's first occurrence in the parcel, so the value of the tag is written
// rather than just its index in the history tag pool.
@@ -2222,8 +2205,8 @@ public abstract class BatteryStats {
eventCode = EVENT_NONE;
eventTag = null;
tagsFirstOccurrence = false;
- energyConsumerDetails = null;
- cpuUsageDetails = null;
+ powerStats = null;
+ processStateChange = null;
}
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
@@ -2273,8 +2256,8 @@ public abstract class BatteryStats {
}
tagsFirstOccurrence = o.tagsFirstOccurrence;
currentTime = o.currentTime;
- energyConsumerDetails = o.energyConsumerDetails;
- cpuUsageDetails = o.cpuUsageDetails;
+ powerStats = o.powerStats;
+ processStateChange = o.processStateChange;
}
public boolean sameNonEvent(HistoryItem o) {
@@ -2434,8 +2417,14 @@ public abstract class BatteryStats {
* Returns a BatteryStatsHistoryIterator. Battery history will continue being writable,
* but the iterator will continue iterating over the snapshot taken at the time this method
* is called.
+ *
+ * @param startTimeMs wall-clock time to start iterating from, inclusive
+ * @param endTimeMs wall-clock time to stop iterating, exclusive.
+ * Pass 0 to indicate current time.
*/
- public abstract BatteryStatsHistoryIterator iterateBatteryStatsHistory();
+ public abstract BatteryStatsHistoryIterator iterateBatteryStatsHistory(
+ @CurrentTimeMillisLong long startTimeMs,
+ @CurrentTimeMillisLong long endTimeMs);
/**
* Returns the number of times the device has been started.
@@ -6911,25 +6900,6 @@ public abstract class BatteryStats {
private String printNextItem(HistoryItem rec, long baseTime, boolean checkin,
boolean verbose) {
StringBuilder item = new StringBuilder();
-
- if (rec.cpuUsageDetails != null
- && rec.cpuUsageDetails.cpuBracketDescriptions != null
- && checkin) {
- String[] descriptions = rec.cpuUsageDetails.cpuBracketDescriptions;
- for (int bracket = 0; bracket < descriptions.length; bracket++) {
- item.append(BATTERY_STATS_CHECKIN_VERSION);
- item.append(',');
- item.append(HISTORY_DATA);
- item.append(",0,XB,");
- item.append(descriptions.length);
- item.append(',');
- item.append(bracket);
- item.append(',');
- item.append(descriptions[bracket]);
- item.append("\n");
- }
- }
-
if (!checkin) {
item.append(" ");
TimeUtils.formatDuration(
@@ -7165,57 +7135,19 @@ public abstract class BatteryStats {
item.append("\"");
}
}
- boolean firstExtension = true;
- if (rec.energyConsumerDetails != null) {
- firstExtension = false;
+ if (rec.powerStats != null && verbose) {
if (!checkin) {
- item.append(" ext=energy:");
- item.append(rec.energyConsumerDetails);
- } else {
- item.append(",XE");
- for (int i = 0; i < rec.energyConsumerDetails.consumers.length; i++) {
- if (rec.energyConsumerDetails.chargeUC[i] != POWER_DATA_UNAVAILABLE) {
- item.append(',');
- item.append(rec.energyConsumerDetails.consumers[i].name);
- item.append('=');
- item.append(rec.energyConsumerDetails.chargeUC[i]);
- }
- }
+ item.append(
+ "\n Stats: ");
+ item.append(rec.powerStats.formatForBatteryHistory(
+ "\n "));
}
}
- if (rec.cpuUsageDetails != null) {
+ if (rec.processStateChange != null && verbose) {
if (!checkin) {
- if (!firstExtension) {
- item.append("\n ");
- }
- String[] descriptions = rec.cpuUsageDetails.cpuBracketDescriptions;
- if (descriptions != null) {
- for (int bracket = 0; bracket < descriptions.length; bracket++) {
- item.append(" ext=cpu-bracket:");
- item.append(bracket);
- item.append(":");
- item.append(descriptions[bracket]);
- item.append("\n ");
- }
- }
- item.append(" ext=cpu:");
- item.append(rec.cpuUsageDetails);
- } else {
- if (!firstExtension) {
- item.append('\n');
- item.append(BATTERY_STATS_CHECKIN_VERSION);
- item.append(',');
- item.append(HISTORY_DATA);
- item.append(",0");
- }
- item.append(",XC,");
- item.append(rec.cpuUsageDetails.uid);
- for (int i = 0; i < rec.cpuUsageDetails.cpuUsageMs.length; i++) {
- item.append(',');
- item.append(rec.cpuUsageDetails.cpuUsageMs[i]);
- }
+ item.append(" procstate: ");
+ item.append(rec.processStateChange.formatForBatteryHistory());
}
- firstExtension = false;
}
item.append("\n");
if (rec.stepDetails != null) {
@@ -7537,7 +7469,7 @@ public abstract class BatteryStats {
long baseTime = -1;
boolean printed = false;
HistoryEventTracker tracker = null;
- try (BatteryStatsHistoryIterator iterator = iterateBatteryStatsHistory()) {
+ try (BatteryStatsHistoryIterator iterator = iterateBatteryStatsHistory(0, 0)) {
HistoryItem rec;
while ((rec = iterator.next()) != null) {
try {
@@ -8460,7 +8392,7 @@ public abstract class BatteryStats {
long baseTime = -1;
boolean printed = false;
HistoryEventTracker tracker = null;
- try (BatteryStatsHistoryIterator iterator = iterateBatteryStatsHistory()) {
+ try (BatteryStatsHistoryIterator iterator = iterateBatteryStatsHistory(0, 0)) {
HistoryItem rec;
while ((rec = iterator.next()) != null) {
lastTime = rec.time;
diff --git a/core/java/android/os/BatteryUsageStats.java b/core/java/android/os/BatteryUsageStats.java
index e2c52cecc2b1..7586bf7700d9 100644
--- a/core/java/android/os/BatteryUsageStats.java
+++ b/core/java/android/os/BatteryUsageStats.java
@@ -315,7 +315,7 @@ public final class BatteryUsageStats implements Parcelable, Closeable {
throw new IllegalStateException(
"Battery history was not requested in the BatteryUsageStatsQuery");
}
- return new BatteryStatsHistoryIterator(mBatteryStatsHistory);
+ return new BatteryStatsHistoryIterator(mBatteryStatsHistory, 0, 0);
}
@Override
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index eb4717057145..509c3b88441e 100755
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -1560,7 +1560,7 @@ public class Build {
String attestProp = getString(
TextUtils.formatSimple("ro.product.%s_for_attestation", property));
return attestProp.equals(UNKNOWN)
- ? getString(TextUtils.formatSimple("ro.product.vendor.%s", property)) : UNKNOWN;
+ ? getString(TextUtils.formatSimple("ro.product.vendor.%s", property)) : attestProp;
}
private static String[] getStringList(String property, String separator) {
diff --git a/core/java/com/android/internal/os/BatteryStatsHistory.java b/core/java/com/android/internal/os/BatteryStatsHistory.java
index 79152b4b618f..d3103f1ed24b 100644
--- a/core/java/com/android/internal/os/BatteryStatsHistory.java
+++ b/core/java/com/android/internal/os/BatteryStatsHistory.java
@@ -18,11 +18,10 @@ package com.android.internal.os;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.os.BatteryConsumer;
import android.os.BatteryManager;
import android.os.BatteryStats;
import android.os.BatteryStats.BitDescription;
-import android.os.BatteryStats.CpuUsageDetails;
-import android.os.BatteryStats.EnergyConsumerDetails;
import android.os.BatteryStats.HistoryItem;
import android.os.BatteryStats.HistoryStepDetails;
import android.os.BatteryStats.HistoryTag;
@@ -42,7 +41,6 @@ import android.util.TimeUtils;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.ParseUtils;
import java.io.File;
import java.io.FileOutputStream;
@@ -83,7 +81,7 @@ public class BatteryStatsHistory {
private static final int VERSION = 209;
private static final String HISTORY_DIR = "battery-history";
- private static final String FILE_SUFFIX = ".bin";
+ private static final String FILE_SUFFIX = ".bh";
private static final int MIN_FREE_SPACE = 100 * 1024 * 1024;
// Part of initial delta int that specifies the time delta.
@@ -124,10 +122,9 @@ public class BatteryStatsHistory {
// therefore the tag value is written in the parcel
static final int TAG_FIRST_OCCURRENCE_FLAG = 0x8000;
- static final int EXTENSION_MEASURED_ENERGY_HEADER_FLAG = 0x00000001;
- static final int EXTENSION_MEASURED_ENERGY_FLAG = 0x00000002;
- static final int EXTENSION_CPU_USAGE_HEADER_FLAG = 0x00000004;
- static final int EXTENSION_CPU_USAGE_FLAG = 0x00000008;
+ static final int EXTENSION_POWER_STATS_DESCRIPTOR_FLAG = 0x00000001;
+ static final int EXTENSION_POWER_STATS_FLAG = 0x00000002;
+ static final int EXTENSION_PROCESS_STATE_CHANGE_FLAG = 0x00000004;
// For state1, trace everything except the wakelock bit (which can race with
// suspend) and the running bit (which isn't meaningful in traces).
@@ -149,10 +146,11 @@ public class BatteryStatsHistory {
* The active history file that the history buffer is backed up into.
*/
private AtomicFile mActiveFile;
+
/**
- * A list of history files with incremental indexes.
+ * A list of history files with increasing timestamps.
*/
- private final List<Integer> mFileNumbers = new ArrayList<>();
+ private final List<BatteryHistoryFile> mHistoryFiles = new ArrayList<>();
/**
* A list of small history parcels, used when BatteryStatsImpl object is created from
@@ -200,14 +198,42 @@ public class BatteryStatsHistory {
private long mTrackRunningHistoryElapsedRealtimeMs = 0;
private long mTrackRunningHistoryUptimeMs = 0;
private long mHistoryBaseTimeMs;
- private boolean mMeasuredEnergyHeaderWritten = false;
- private boolean mCpuUsageHeaderWritten = false;
- private final VarintParceler mVarintParceler = new VarintParceler();
+ private final ArraySet<PowerStats.Descriptor> mWrittenPowerStatsDescriptors = new ArraySet<>();
private byte mLastHistoryStepLevel = 0;
private boolean mMutable = true;
private final BatteryStatsHistory mWritableHistory;
private boolean mCleanupEnabled = true;
+ private static class BatteryHistoryFile implements Comparable<BatteryHistoryFile> {
+ public final long monotonicTimeMs;
+ public final AtomicFile atomicFile;
+
+ private BatteryHistoryFile(File directory, long monotonicTimeMs) {
+ this.monotonicTimeMs = monotonicTimeMs;
+ atomicFile = new AtomicFile(new File(directory, monotonicTimeMs + FILE_SUFFIX));
+ }
+
+ @Override
+ public int compareTo(BatteryHistoryFile o) {
+ return Long.compare(monotonicTimeMs, o.monotonicTimeMs);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return monotonicTimeMs == ((BatteryHistoryFile) o).monotonicTimeMs;
+ }
+
+ @Override
+ public int hashCode() {
+ return Long.hashCode(monotonicTimeMs);
+ }
+
+ @Override
+ public String toString() {
+ return atomicFile.getBaseFile().toString();
+ }
+ }
+
/**
* A delegate responsible for computing additional details for a step in battery history.
*/
@@ -317,32 +343,47 @@ public class BatteryStatsHistory {
Slog.wtf(TAG, "HistoryDir does not exist:" + mHistoryDir.getPath());
}
- final Set<Integer> dedup = new ArraySet<>();
- // scan directory, fill mFileNumbers and mActiveFile.
+ final List<File> toRemove = new ArrayList<>();
+ final Set<BatteryHistoryFile> dedup = new ArraySet<>();
mHistoryDir.listFiles((dir, name) -> {
final int b = name.lastIndexOf(FILE_SUFFIX);
if (b <= 0) {
+ toRemove.add(new File(dir, name));
return false;
}
- final int c = ParseUtils.parseInt(name.substring(0, b), -1);
- if (c != -1) {
- dedup.add(c);
- return true;
- } else {
+ try {
+ long monotonicTime = Long.parseLong(name.substring(0, b));
+ dedup.add(new BatteryHistoryFile(mHistoryDir, monotonicTime));
+ } catch (NumberFormatException e) {
+ toRemove.add(new File(dir, name));
return false;
}
+ return true;
});
if (!dedup.isEmpty()) {
- mFileNumbers.addAll(dedup);
- Collections.sort(mFileNumbers);
- setActiveFile(mFileNumbers.get(mFileNumbers.size() - 1));
- } else {
- // No file found, default to have file 0.
- mFileNumbers.add(0);
- setActiveFile(0);
+ mHistoryFiles.addAll(dedup);
+ Collections.sort(mHistoryFiles);
+ setActiveFile(mHistoryFiles.get(mHistoryFiles.size() - 1));
+ } else if (mMutable) {
+ // No file found, default to have the initial file.
+ BatteryHistoryFile name = makeBatteryHistoryFile();
+ mHistoryFiles.add(name);
+ setActiveFile(name);
+ }
+ if (!toRemove.isEmpty()) {
+ // Clear out legacy history files, which did not follow the X-Y.bin naming format.
+ BackgroundThread.getHandler().post(() -> {
+ for (File file : toRemove) {
+ file.delete();
+ }
+ });
}
}
+ private BatteryHistoryFile makeBatteryHistoryFile() {
+ return new BatteryHistoryFile(mHistoryDir, mClock.elapsedRealtime() + mHistoryBaseTimeMs);
+ }
+
public BatteryStatsHistory(int maxHistoryFiles, int maxHistoryBufferSize,
HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock) {
mMaxHistoryFiles = maxHistoryFiles;
@@ -384,8 +425,7 @@ public class BatteryStatsHistory {
mLastHistoryElapsedRealtimeMs = 0;
mTrackRunningHistoryElapsedRealtimeMs = 0;
mTrackRunningHistoryUptimeMs = 0;
- mMeasuredEnergyHeaderWritten = false;
- mCpuUsageHeaderWritten = false;
+ mWrittenPowerStatsDescriptors.clear();
mHistoryBuffer.setDataSize(0);
mHistoryBuffer.setDataPosition(0);
@@ -439,28 +479,15 @@ public class BatteryStatsHistory {
/**
* Set the active file that mHistoryBuffer is backed up into.
- *
- * @param fileNumber the history file that mHistoryBuffer is backed up into.
*/
- private void setActiveFile(int fileNumber) {
- mActiveFile = getFile(fileNumber);
+ private void setActiveFile(BatteryHistoryFile file) {
+ mActiveFile = file.atomicFile;
if (DEBUG) {
Slog.d(TAG, "activeHistoryFile:" + mActiveFile.getBaseFile().getPath());
}
}
/**
- * Create history AtomicFile from file number.
- *
- * @param num file number.
- * @return AtomicFile object.
- */
- private AtomicFile getFile(int num) {
- return new AtomicFile(
- new File(mHistoryDir, num + FILE_SUFFIX));
- }
-
- /**
* When {@link #mHistoryBuffer} reaches {@link BatteryStatsImpl.Constants#MAX_HISTORY_BUFFER},
* create next history file.
*/
@@ -470,15 +497,19 @@ public class BatteryStatsHistory {
return;
}
- if (mFileNumbers.isEmpty()) {
+ if (mHistoryFiles.isEmpty()) {
Slog.wtf(TAG, "mFileNumbers should never be empty");
return;
}
- // The last number in mFileNumbers is the highest number. The next file number is highest
- // number plus one.
- final int next = mFileNumbers.get(mFileNumbers.size() - 1) + 1;
- mFileNumbers.add(next);
+ final long start = SystemClock.uptimeMillis();
+ writeHistory();
+ if (DEBUG) {
+ Slog.d(TAG, "writeHistory took ms:" + (SystemClock.uptimeMillis() - start));
+ }
+
+ final BatteryHistoryFile next = makeBatteryHistoryFile();
+ mHistoryFiles.add(next);
setActiveFile(next);
try {
mActiveFile.getBaseFile().createNewFile();
@@ -486,6 +517,21 @@ public class BatteryStatsHistory {
Slog.e(TAG, "Could not create history file: " + mActiveFile.getBaseFile());
}
+ mHistoryBuffer.setDataSize(0);
+ mHistoryBuffer.setDataPosition(0);
+ mHistoryBuffer.setDataCapacity(mMaxHistoryBufferSize / 2);
+ mHistoryBufferLastPos = -1;
+ mHistoryLastWritten.clear();
+ mHistoryLastLastWritten.clear();
+
+ // Mark every entry in the pool with a flag indicating that the tag
+ // has not yet been encountered while writing the current history buffer.
+ for (Map.Entry<HistoryTag, Integer> entry : mHistoryTagPool.entrySet()) {
+ entry.setValue(entry.getValue() | BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG);
+ }
+
+ mWrittenPowerStatsDescriptors.clear();
+
synchronized (this) {
cleanupLocked();
}
@@ -507,17 +553,17 @@ public class BatteryStatsHistory {
// if free disk space is less than 100MB, delete oldest history file.
if (!hasFreeDiskSpace()) {
- int oldest = mFileNumbers.remove(0);
- getFile(oldest).delete();
+ BatteryHistoryFile oldest = mHistoryFiles.remove(0);
+ oldest.atomicFile.delete();
}
// if there are more history files than allowed, delete oldest history files.
// mMaxHistoryFiles comes from Constants.MAX_HISTORY_FILES and can be updated by GService
// config at run time.
- while (mFileNumbers.size() > mMaxHistoryFiles) {
- int oldest = mFileNumbers.get(0);
- getFile(oldest).delete();
- mFileNumbers.remove(0);
+ while (mHistoryFiles.size() > mMaxHistoryFiles) {
+ BatteryHistoryFile oldest = mHistoryFiles.get(0);
+ oldest.atomicFile.delete();
+ mHistoryFiles.remove(0);
}
}
@@ -537,12 +583,14 @@ public class BatteryStatsHistory {
*/
public void reset() {
if (DEBUG) Slog.i(TAG, "********** CLEARING HISTORY!");
- for (Integer i : mFileNumbers) {
- getFile(i).delete();
+ for (BatteryHistoryFile file : mHistoryFiles) {
+ file.atomicFile.delete();
}
- mFileNumbers.clear();
- mFileNumbers.add(0);
- setActiveFile(0);
+ mHistoryFiles.clear();
+
+ BatteryHistoryFile name = makeBatteryHistoryFile();
+ mHistoryFiles.add(name);
+ setActiveFile(name);
initHistoryBuffer();
}
@@ -550,9 +598,13 @@ public class BatteryStatsHistory {
/**
* Start iterating history files and history buffer.
*
- * @return always return true.
+ * @param startTimeMs monotonic time (the HistoryItem.time field) to start iterating from,
+ * inclusive
+ * @param endTimeMs monotonic time to stop iterating, exclusive.
+ * Pass 0 to indicate current time.
*/
- public BatteryStatsHistoryIterator iterate() {
+ @NonNull
+ public BatteryStatsHistoryIterator iterate(long startTimeMs, long endTimeMs) {
mCurrentFileIndex = 0;
mCurrentParcel = null;
mCurrentParcelEnd = 0;
@@ -563,7 +615,7 @@ public class BatteryStatsHistory {
mWritableHistory.setCleanupEnabledLocked(false);
}
}
- return new BatteryStatsHistoryIterator(this);
+ return new BatteryStatsHistoryIterator(this, startTimeMs, endTimeMs);
}
/**
@@ -590,7 +642,7 @@ public class BatteryStatsHistory {
* buffer
*/
@Nullable
- public Parcel getNextParcel() {
+ public Parcel getNextParcel(long startTimeMs, long endTimeMs) {
// First iterate through all records in current parcel.
if (mCurrentParcel != null) {
if (mCurrentParcel.dataPosition() < mCurrentParcelEnd) {
@@ -606,13 +658,29 @@ public class BatteryStatsHistory {
}
}
- // Try next available history file.
+ int firstFileIndex = 0;
// skip the last file because its data is in history buffer.
- while (mCurrentFileIndex < mFileNumbers.size() - 1) {
+ int lastFileIndex = mHistoryFiles.size() - 1;
+ for (int i = mHistoryFiles.size() - 1; i >= 0; i--) {
+ BatteryHistoryFile file = mHistoryFiles.get(i);
+ if (file.monotonicTimeMs >= endTimeMs) {
+ lastFileIndex = i;
+ }
+ if (file.monotonicTimeMs <= startTimeMs) {
+ firstFileIndex = i;
+ break;
+ }
+ }
+
+ if (mCurrentFileIndex < firstFileIndex) {
+ mCurrentFileIndex = firstFileIndex;
+ }
+
+ while (mCurrentFileIndex < lastFileIndex) {
mCurrentParcel = null;
mCurrentParcelEnd = 0;
final Parcel p = Parcel.obtain();
- AtomicFile file = getFile(mFileNumbers.get(mCurrentFileIndex++));
+ AtomicFile file = mHistoryFiles.get(mCurrentFileIndex++).atomicFile;
if (readFileToParcel(p, file)) {
int bufSize = p.readInt();
int curPos = p.dataPosition();
@@ -769,9 +837,9 @@ public class BatteryStatsHistory {
private void writeToParcel(Parcel out, boolean useBlobs) {
final long start = SystemClock.uptimeMillis();
- out.writeInt(mFileNumbers.size() - 1);
- for (int i = 0; i < mFileNumbers.size() - 1; i++) {
- AtomicFile file = getFile(mFileNumbers.get(i));
+ out.writeInt(mHistoryFiles.size() - 1);
+ for (int i = 0; i < mHistoryFiles.size() - 1; i++) {
+ AtomicFile file = mHistoryFiles.get(i).atomicFile;
byte[] raw = new byte[0];
try {
raw = file.readFully();
@@ -872,8 +940,12 @@ public class BatteryStatsHistory {
}
@VisibleForTesting
- public List<Integer> getFilesNumbers() {
- return mFileNumbers;
+ public List<String> getFilesNames() {
+ List<String> names = new ArrayList<>();
+ for (BatteryHistoryFile historyFile : mHistoryFiles) {
+ names.add(historyFile.atomicFile.getBaseFile().getName());
+ }
+ return names;
}
@VisibleForTesting
@@ -886,8 +958,8 @@ public class BatteryStatsHistory {
*/
public int getHistoryUsedSize() {
int ret = 0;
- for (int i = 0; i < mFileNumbers.size() - 1; i++) {
- ret += getFile(mFileNumbers.get(i)).getBaseFile().length();
+ for (int i = 0; i < mHistoryFiles.size() - 1; i++) {
+ ret += mHistoryFiles.get(i).atomicFile.getBaseFile().length();
}
ret += mHistoryBuffer.dataSize();
if (mHistoryParcels != null) {
@@ -937,7 +1009,7 @@ public class BatteryStatsHistory {
* Prepares to continue recording after restoring previous history from persistent storage.
*/
public void continueRecordingHistory() {
- if (mHistoryBuffer.dataPosition() <= 0 && mFileNumbers.size() <= 1) {
+ if (mHistoryBuffer.dataPosition() <= 0 && mHistoryFiles.size() <= 1) {
return;
}
@@ -1050,11 +1122,23 @@ public class BatteryStatsHistory {
}
/**
- * Records measured energy data.
+ * Records a PowerStats snapshot.
*/
- public void recordEnergyConsumerDetails(long elapsedRealtimeMs, long uptimeMs,
- EnergyConsumerDetails energyConsumerDetails) {
- mHistoryCur.energyConsumerDetails = energyConsumerDetails;
+ public void recordPowerStats(long elapsedRealtimeMs, long uptimeMs,
+ PowerStats powerStats) {
+ mHistoryCur.powerStats = powerStats;
+ mHistoryCur.states2 |= HistoryItem.STATE2_EXTENSIONS_FLAG;
+ writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+ }
+
+ /**
+ * Records the change of a UID's proc state.
+ */
+ public void recordProcessStateChange(long elapsedRealtimeMs, long uptimeMs,
+ int uid, @BatteryConsumer.ProcessState int processState) {
+ mHistoryCur.processStateChange = mHistoryCur.localProcessStateChange;
+ mHistoryCur.processStateChange.uid = uid;
+ mHistoryCur.processStateChange.processState = processState;
mHistoryCur.states2 |= HistoryItem.STATE2_EXTENSIONS_FLAG;
writeHistoryItem(elapsedRealtimeMs, uptimeMs);
}
@@ -1279,17 +1363,6 @@ public class BatteryStatsHistory {
}
/**
- * Records CPU usage by a specific UID. The recorded data is the delta from
- * the previous record for the same UID.
- */
- public void recordCpuUsage(long elapsedRealtimeMs, long uptimeMs,
- CpuUsageDetails cpuUsageDetails) {
- mHistoryCur.cpuUsageDetails = cpuUsageDetails;
- mHistoryCur.states2 |= HistoryItem.STATE2_EXTENSIONS_FLAG;
- writeHistoryItem(elapsedRealtimeMs, uptimeMs);
- }
-
- /**
* Writes changes to a HistoryItem state bitmap to Atrace.
*/
private void recordTraceCounters(int oldval, int newval, int mask,
@@ -1355,7 +1428,9 @@ public class BatteryStatsHistory {
mTraceLastState2 = cur.states2;
}
- if (!mHaveBatteryLevel || !mRecordingHistory) {
+ if ((!mHaveBatteryLevel || !mRecordingHistory)
+ && cur.powerStats == null
+ && cur.processStateChange == null) {
return;
}
@@ -1391,8 +1466,8 @@ public class BatteryStatsHistory {
&& mHistoryLastWritten.batteryPlugType == cur.batteryPlugType
&& mHistoryLastWritten.batteryTemperature == cur.batteryTemperature
&& mHistoryLastWritten.batteryVoltage == cur.batteryVoltage
- && mHistoryLastWritten.energyConsumerDetails == null
- && mHistoryLastWritten.cpuUsageDetails == null) {
+ && mHistoryLastWritten.powerStats == null
+ && mHistoryLastWritten.processStateChange == null) {
// We can merge this new change in with the last one. Merging is
// allowed as long as only the states have changed, and within those states
// as long as no bit has changed both between now and the last entry, as
@@ -1434,34 +1509,15 @@ public class BatteryStatsHistory {
mMaxHistoryBufferSize = 1024;
}
- //open a new history file.
- final long start = SystemClock.uptimeMillis();
- writeHistory();
- if (DEBUG) {
- Slog.d(TAG, "addHistoryBufferLocked writeHistory took ms:"
- + (SystemClock.uptimeMillis() - start));
- }
- startNextFile();
- mHistoryBuffer.setDataSize(0);
- mHistoryBuffer.setDataPosition(0);
- mHistoryBuffer.setDataCapacity(mMaxHistoryBufferSize / 2);
- mHistoryBufferLastPos = -1;
- mHistoryLastWritten.clear();
- mHistoryLastLastWritten.clear();
-
- // Mark every entry in the pool with a flag indicating that the tag
- // has not yet been encountered while writing the current history buffer.
- for (Map.Entry<HistoryTag, Integer> entry : mHistoryTagPool.entrySet()) {
- entry.setValue(entry.getValue() | BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG);
- }
- mMeasuredEnergyHeaderWritten = false;
- mCpuUsageHeaderWritten = false;
-
// Make a copy of mHistoryCur.
HistoryItem copy = new HistoryItem();
copy.setTo(cur);
+
+ startNextFile();
+
// startRecordingHistory will reset mHistoryCur.
startRecordingHistory(elapsedRealtimeMs, uptimeMs, false);
+
// Add the copy into history buffer.
writeHistoryItem(elapsedRealtimeMs, uptimeMs, copy, HistoryItem.CMD_UPDATE);
return;
@@ -1477,8 +1533,8 @@ public class BatteryStatsHistory {
copy.eventCode = HistoryItem.EVENT_NONE;
copy.eventTag = null;
copy.tagsFirstOccurrence = false;
- copy.energyConsumerDetails = null;
- copy.cpuUsageDetails = null;
+ copy.powerStats = null;
+ copy.processStateChange = null;
writeHistoryItem(elapsedRealtimeMs, uptimeMs, copy, HistoryItem.CMD_RESET);
}
writeHistoryItem(elapsedRealtimeMs, uptimeMs, cur, HistoryItem.CMD_UPDATE);
@@ -1506,8 +1562,8 @@ public class BatteryStatsHistory {
cur.eventCode = HistoryItem.EVENT_NONE;
cur.eventTag = null;
cur.tagsFirstOccurrence = false;
- cur.energyConsumerDetails = null;
- cur.cpuUsageDetails = null;
+ cur.powerStats = null;
+ cur.processStateChange = null;
if (DEBUG) {
Slog.i(TAG, "Writing history buffer: was " + mHistoryBufferLastPos
+ " now " + mHistoryBuffer.dataPosition()
@@ -1638,17 +1694,14 @@ public class BatteryStatsHistory {
if (stateIntChanged) {
firstToken |= BatteryStatsHistory.DELTA_STATE_FLAG;
}
- if (cur.energyConsumerDetails != null) {
- extensionFlags |= BatteryStatsHistory.EXTENSION_MEASURED_ENERGY_FLAG;
- if (!mMeasuredEnergyHeaderWritten) {
- extensionFlags |= BatteryStatsHistory.EXTENSION_MEASURED_ENERGY_HEADER_FLAG;
+ if (cur.powerStats != null) {
+ extensionFlags |= BatteryStatsHistory.EXTENSION_POWER_STATS_FLAG;
+ if (!mWrittenPowerStatsDescriptors.contains(cur.powerStats.descriptor)) {
+ extensionFlags |= BatteryStatsHistory.EXTENSION_POWER_STATS_DESCRIPTOR_FLAG;
}
}
- if (cur.cpuUsageDetails != null) {
- extensionFlags |= EXTENSION_CPU_USAGE_FLAG;
- if (!mCpuUsageHeaderWritten) {
- extensionFlags |= BatteryStatsHistory.EXTENSION_CPU_USAGE_HEADER_FLAG;
- }
+ if (cur.processStateChange != null) {
+ extensionFlags |= BatteryStatsHistory.EXTENSION_PROCESS_STATE_CHANGE_FLAG;
}
if (extensionFlags != 0) {
cur.states2 |= HistoryItem.STATE2_EXTENSIONS_FLAG;
@@ -1773,37 +1826,16 @@ public class BatteryStatsHistory {
dest.writeDouble(cur.wifiRailChargeMah);
if (extensionFlags != 0) {
dest.writeInt(extensionFlags);
- if (cur.energyConsumerDetails != null) {
- if (DEBUG) {
- Slog.i(TAG, "WRITE DELTA: measuredEnergyDetails=" + cur.energyConsumerDetails);
- }
- if (!mMeasuredEnergyHeaderWritten) {
- EnergyConsumerDetails.EnergyConsumer[] consumers =
- cur.energyConsumerDetails.consumers;
- dest.writeInt(consumers.length);
- for (EnergyConsumerDetails.EnergyConsumer consumer : consumers) {
- dest.writeInt(consumer.type);
- dest.writeInt(consumer.ordinal);
- dest.writeString(consumer.name);
- }
- mMeasuredEnergyHeaderWritten = true;
+ if (cur.powerStats != null) {
+ if ((extensionFlags & BatteryStatsHistory.EXTENSION_POWER_STATS_DESCRIPTOR_FLAG)
+ != 0) {
+ cur.powerStats.descriptor.writeSummaryToParcel(dest);
+ mWrittenPowerStatsDescriptors.add(cur.powerStats.descriptor);
}
- mVarintParceler.writeLongArray(dest, cur.energyConsumerDetails.chargeUC);
+ cur.powerStats.writeToParcel(dest);
}
-
- if (cur.cpuUsageDetails != null) {
- if (DEBUG) {
- Slog.i(TAG, "WRITE DELTA: cpuUsageDetails=" + cur.cpuUsageDetails);
- }
- if (!mCpuUsageHeaderWritten) {
- dest.writeInt(cur.cpuUsageDetails.cpuBracketDescriptions.length);
- for (String desc: cur.cpuUsageDetails.cpuBracketDescriptions) {
- dest.writeString(desc);
- }
- mCpuUsageHeaderWritten = true;
- }
- dest.writeInt(cur.cpuUsageDetails.uid);
- mVarintParceler.writeLongArray(dest, cur.cpuUsageDetails.cpuUsageMs);
+ if (cur.processStateChange != null) {
+ cur.processStateChange.writeToParcel(dest);
}
}
}
@@ -2054,13 +2086,15 @@ public class BatteryStatsHistory {
* fewer bytes. It is a bit more expensive than just writing the long into the parcel,
* but at scale saves a lot of storage and allows recording of longer battery history.
*/
- @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public static final class VarintParceler {
/**
* Writes an array of longs into Parcel using the varint format, see
* https://developers.google.com/protocol-buffers/docs/encoding#varints
*/
public void writeLongArray(Parcel parcel, long[] values) {
+ if (values.length == 0) {
+ return;
+ }
int out = 0;
int shift = 0;
for (long value : values) {
@@ -2092,6 +2126,9 @@ public class BatteryStatsHistory {
* Reads a long written with {@link #writeLongArray}
*/
public void readLongArray(Parcel parcel, long[] values) {
+ if (values.length == 0) {
+ return;
+ }
int in = parcel.readInt();
int available = 4;
for (int i = 0; i < values.length; i++) {
diff --git a/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java b/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java
index 4c2b2854df88..a5d2d0fc1a01 100644
--- a/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java
+++ b/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java
@@ -16,6 +16,7 @@
package com.android.internal.os;
+import android.annotation.CurrentTimeMillisLong;
import android.annotation.NonNull;
import android.os.BatteryManager;
import android.os.BatteryStats;
@@ -33,32 +34,32 @@ public class BatteryStatsHistoryIterator implements Iterator<BatteryStats.Histor
private static final boolean DEBUG = false;
private static final String TAG = "BatteryStatsHistoryItr";
private final BatteryStatsHistory mBatteryStatsHistory;
+ private final @CurrentTimeMillisLong long mStartTimeMs;
+ private final @CurrentTimeMillisLong long mEndTimeMs;
private final BatteryStats.HistoryStepDetails mReadHistoryStepDetails =
new BatteryStats.HistoryStepDetails();
private final SparseArray<BatteryStats.HistoryTag> mHistoryTags = new SparseArray<>();
- private BatteryStats.EnergyConsumerDetails mEnergyConsumerDetails;
- private BatteryStats.CpuUsageDetails mCpuUsageDetails;
- private final BatteryStatsHistory.VarintParceler mVarintParceler =
- new BatteryStatsHistory.VarintParceler();
+ private final PowerStats.DescriptorRegistry mDescriptorRegistry =
+ new PowerStats.DescriptorRegistry();
+ private BatteryStats.HistoryItem mHistoryItem = new BatteryStats.HistoryItem();
+ private boolean mNextItemReady;
- private final BatteryStats.HistoryItem mHistoryItem = new BatteryStats.HistoryItem();
-
- private static final int MAX_ENERGY_CONSUMER_COUNT = 100;
- private static final int MAX_CPU_BRACKET_COUNT = 100;
-
- public BatteryStatsHistoryIterator(@NonNull BatteryStatsHistory history) {
+ public BatteryStatsHistoryIterator(@NonNull BatteryStatsHistory history,
+ @CurrentTimeMillisLong long startTimeMs,
+ @CurrentTimeMillisLong long endTimeMs) {
mBatteryStatsHistory = history;
+ mStartTimeMs = startTimeMs;
+ mEndTimeMs = (endTimeMs != 0) ? endTimeMs : Long.MAX_VALUE;
mHistoryItem.clear();
}
@Override
public boolean hasNext() {
- Parcel p = mBatteryStatsHistory.getNextParcel();
- if (p == null) {
- close();
- return false;
+ if (!mNextItemReady) {
+ advance();
}
- return true;
+
+ return mHistoryItem != null;
}
/**
@@ -67,25 +68,45 @@ public class BatteryStatsHistoryIterator implements Iterator<BatteryStats.Histor
*/
@Override
public BatteryStats.HistoryItem next() {
- Parcel p = mBatteryStatsHistory.getNextParcel();
- if (p == null) {
- close();
- return null;
+ if (!mNextItemReady) {
+ advance();
}
+ mNextItemReady = false;
+ return mHistoryItem;
+ }
- final long lastRealtimeMs = mHistoryItem.time;
- final long lastWalltimeMs = mHistoryItem.currentTime;
- try {
- readHistoryDelta(p, mHistoryItem);
- } catch (Throwable t) {
- Slog.wtf(TAG, "Corrupted battery history", t);
- return null;
- }
- if (mHistoryItem.cmd != BatteryStats.HistoryItem.CMD_CURRENT_TIME
- && mHistoryItem.cmd != BatteryStats.HistoryItem.CMD_RESET && lastWalltimeMs != 0) {
- mHistoryItem.currentTime = lastWalltimeMs + (mHistoryItem.time - lastRealtimeMs);
+ private void advance() {
+ while (true) {
+ Parcel p = mBatteryStatsHistory.getNextParcel(mStartTimeMs, mEndTimeMs);
+ if (p == null) {
+ break;
+ }
+
+ final long lastRealtimeMs = mHistoryItem.time;
+ final long lastWalltimeMs = mHistoryItem.currentTime;
+ try {
+ readHistoryDelta(p, mHistoryItem);
+ } catch (Throwable t) {
+ Slog.wtf(TAG, "Corrupted battery history", t);
+ break;
+ }
+ if (mHistoryItem.cmd != BatteryStats.HistoryItem.CMD_CURRENT_TIME
+ && mHistoryItem.cmd != BatteryStats.HistoryItem.CMD_RESET
+ && lastWalltimeMs != 0) {
+ mHistoryItem.currentTime = lastWalltimeMs + (mHistoryItem.time - lastRealtimeMs);
+ }
+ if (mEndTimeMs != 0 && mHistoryItem.currentTime >= mEndTimeMs) {
+ break;
+ }
+ if (mHistoryItem.currentTime >= mStartTimeMs) {
+ mNextItemReady = true;
+ return;
+ }
}
- return mHistoryItem;
+
+ mHistoryItem = null;
+ mNextItemReady = true;
+ close();
}
private void readHistoryDelta(Parcel src, BatteryStats.HistoryItem cur) {
@@ -229,74 +250,24 @@ public class BatteryStatsHistoryIterator implements Iterator<BatteryStats.Histor
cur.wifiRailChargeMah = src.readDouble();
if ((cur.states2 & BatteryStats.HistoryItem.STATE2_EXTENSIONS_FLAG) != 0) {
final int extensionFlags = src.readInt();
- if ((extensionFlags & BatteryStatsHistory.EXTENSION_MEASURED_ENERGY_HEADER_FLAG) != 0) {
- if (mEnergyConsumerDetails == null) {
- mEnergyConsumerDetails = new BatteryStats.EnergyConsumerDetails();
- }
-
- final int consumerCount = src.readInt();
- if (consumerCount > MAX_ENERGY_CONSUMER_COUNT) {
- // Check to avoid a heap explosion in case the parcel is corrupted
- throw new IllegalStateException(
- "EnergyConsumer count too high: " + consumerCount
- + ". Max = " + MAX_ENERGY_CONSUMER_COUNT);
- }
- mEnergyConsumerDetails.consumers =
- new BatteryStats.EnergyConsumerDetails.EnergyConsumer[consumerCount];
- mEnergyConsumerDetails.chargeUC = new long[consumerCount];
- for (int i = 0; i < consumerCount; i++) {
- BatteryStats.EnergyConsumerDetails.EnergyConsumer consumer =
- new BatteryStats.EnergyConsumerDetails.EnergyConsumer();
- consumer.type = src.readInt();
- consumer.ordinal = src.readInt();
- consumer.name = src.readString();
- mEnergyConsumerDetails.consumers[i] = consumer;
- }
+ if ((extensionFlags & BatteryStatsHistory.EXTENSION_POWER_STATS_DESCRIPTOR_FLAG) != 0) {
+ PowerStats.Descriptor descriptor = PowerStats.Descriptor.readSummaryFromParcel(src);
+ mDescriptorRegistry.register(descriptor);
}
-
- if ((extensionFlags & BatteryStatsHistory.EXTENSION_MEASURED_ENERGY_FLAG) != 0) {
- if (mEnergyConsumerDetails == null) {
- throw new IllegalStateException("MeasuredEnergyDetails without a header");
- }
-
- mVarintParceler.readLongArray(src, mEnergyConsumerDetails.chargeUC);
- cur.energyConsumerDetails = mEnergyConsumerDetails;
+ if ((extensionFlags & BatteryStatsHistory.EXTENSION_POWER_STATS_FLAG) != 0) {
+ cur.powerStats = PowerStats.readFromParcel(src, mDescriptorRegistry);
} else {
- cur.energyConsumerDetails = null;
- }
-
- if ((extensionFlags & BatteryStatsHistory.EXTENSION_CPU_USAGE_HEADER_FLAG) != 0) {
- mCpuUsageDetails = new BatteryStats.CpuUsageDetails();
- final int cpuBracketCount = src.readInt();
- if (cpuBracketCount > MAX_CPU_BRACKET_COUNT) {
- // Check to avoid a heap explosion in case the parcel is corrupted
- throw new IllegalStateException("Too many CPU brackets: " + cpuBracketCount
- + ". Max = " + MAX_CPU_BRACKET_COUNT);
- }
- mCpuUsageDetails.cpuBracketDescriptions = new String[cpuBracketCount];
- for (int i = 0; i < cpuBracketCount; i++) {
- mCpuUsageDetails.cpuBracketDescriptions[i] = src.readString();
- }
- mCpuUsageDetails.cpuUsageMs =
- new long[mCpuUsageDetails.cpuBracketDescriptions.length];
- } else if (mCpuUsageDetails != null) {
- mCpuUsageDetails.cpuBracketDescriptions = null;
+ cur.powerStats = null;
}
-
- if ((extensionFlags & BatteryStatsHistory.EXTENSION_CPU_USAGE_FLAG) != 0) {
- if (mCpuUsageDetails == null) {
- throw new IllegalStateException("CpuUsageDetails without a header");
- }
-
- mCpuUsageDetails.uid = src.readInt();
- mVarintParceler.readLongArray(src, mCpuUsageDetails.cpuUsageMs);
- cur.cpuUsageDetails = mCpuUsageDetails;
+ if ((extensionFlags & BatteryStatsHistory.EXTENSION_PROCESS_STATE_CHANGE_FLAG) != 0) {
+ cur.processStateChange = cur.localProcessStateChange;
+ cur.processStateChange.readFromParcel(src);
} else {
- cur.cpuUsageDetails = null;
+ cur.processStateChange = null;
}
} else {
- cur.energyConsumerDetails = null;
- cur.cpuUsageDetails = null;
+ cur.powerStats = null;
+ cur.processStateChange = null;
}
}
diff --git a/core/java/com/android/internal/os/LongArrayMultiStateCounter.java b/core/java/com/android/internal/os/LongArrayMultiStateCounter.java
index 664aeee6e299..5ea6ba86da71 100644
--- a/core/java/com/android/internal/os/LongArrayMultiStateCounter.java
+++ b/core/java/com/android/internal/os/LongArrayMultiStateCounter.java
@@ -195,6 +195,34 @@ public final class LongArrayMultiStateCounter implements Parcelable {
* is distributed among the state according to the time the object spent in those states
* since the previous call to updateValues.
*/
+ public void updateValues(long[] values, long timestampMs) {
+ LongArrayContainer container = sTmpArrayContainer.getAndSet(null);
+ if (container == null || container.mLength != values.length) {
+ container = new LongArrayContainer(values.length);
+ }
+ container.setValues(values);
+ updateValues(container, timestampMs);
+ sTmpArrayContainer.set(container);
+ }
+
+ /**
+ * Adds the supplied values to the current accumulated values in the counter.
+ */
+ public void incrementValues(long[] values, long timestampMs) {
+ LongArrayContainer container = sTmpArrayContainer.getAndSet(null);
+ if (container == null || container.mLength != values.length) {
+ container = new LongArrayContainer(values.length);
+ }
+ container.setValues(values);
+ native_incrementValues(mNativeObject, container.mNativeObject, timestampMs);
+ sTmpArrayContainer.set(container);
+ }
+
+ /**
+ * Sets the new values. The delta between the previously set values and these values
+ * is distributed among the state according to the time the object spent in those states
+ * since the previous call to updateValues.
+ */
public void updateValues(LongArrayContainer longArrayContainer, long timestampMs) {
if (longArrayContainer.mLength != mLength) {
throw new IllegalArgumentException(
@@ -293,6 +321,10 @@ public final class LongArrayMultiStateCounter implements Parcelable {
long longArrayContainerNativeObject, long timestampMs);
@CriticalNative
+ private static native void native_incrementValues(long nativeObject,
+ long longArrayContainerNativeObject, long timestampMs);
+
+ @CriticalNative
private static native void native_addCounts(long nativeObject,
long longArrayContainerNativeObject);
diff --git a/core/java/com/android/internal/os/MultiStateStats.java b/core/java/com/android/internal/os/MultiStateStats.java
new file mode 100644
index 000000000000..f971849987dd
--- /dev/null
+++ b/core/java/com/android/internal/os/MultiStateStats.java
@@ -0,0 +1,303 @@
+/*
+ * 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.internal.os;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
+
+import java.io.PrintWriter;
+import java.util.Arrays;
+
+/**
+ * Maintains multidimensional multi-state stats. States could be something like on-battery (0,1),
+ * screen-on (0,1), process state etc. Dimensions refer to the metrics themselves, e.g.
+ * CPU residency, Network packet counts etc. All metrics must be represented as <code>long</code>
+ * values;
+ */
+public class MultiStateStats {
+ /**
+ * A set of states, e.g. on-battery, screen-on, procstate. The state values are integers
+ * from 0 to States.mLabels.length
+ */
+ public static class States {
+ final boolean mTracked;
+ final String[] mLabels;
+
+ public States(boolean tracked, String... labels) {
+ this.mTracked = tracked;
+ this.mLabels = labels;
+ }
+
+ public boolean isTracked() {
+ return mTracked;
+ }
+ }
+
+ /**
+ * Factory for MultiStateStats containers. All generated containers retain their connection
+ * to the Factory and the corresponding configuration.
+ */
+ public static class Factory {
+ private static final int INVALID_SERIAL_STATE = -1;
+ final int mDimensionCount;
+ final States[] mStates;
+ /*
+ * The LongArrayMultiStateCounter object that is used for accumulation of per-state
+ * stats thinks of "state" as a simple 0-based index. This Factory object's job is to
+ * map a combination of individual states (e.g. on-battery, process state etc) to
+ * such a simple index.
+ *
+ * This task is performed in two steps:
+ * 1) We define "composite state" as an integer that combines all constituent States
+ * into one integer as bit fields. This gives us a convenient mechanism for updating a
+ * single constituent State at a time. We maintain an array of bit field masks
+ * corresponding to each constituent State.
+ *
+ * 2) We map composite states to "serial states", i.e. simple integer indexes, taking
+ * into account which constituent states are configured as tracked. If a state is not
+ * tracked, there is no need to maintain separate counts for its values, thus
+ * all values of that constituent state can be mapped to the same serial state.
+ */
+ private final int[] mStateBitFieldMasks;
+ private final short[] mStateBitFieldShifts;
+ final int[] mCompositeToSerialState;
+ final int mSerialStateCount;
+
+ public Factory(int dimensionCount, States... states) {
+ mDimensionCount = dimensionCount;
+ mStates = states;
+
+ int serialStateCount = 1;
+ for (States state : mStates) {
+ if (state.mTracked) {
+ serialStateCount *= state.mLabels.length;
+ }
+ }
+ mSerialStateCount = serialStateCount;
+
+ mStateBitFieldMasks = new int[mStates.length];
+ mStateBitFieldShifts = new short[mStates.length];
+
+ int shift = 0;
+ for (int i = 0; i < mStates.length; i++) {
+ mStateBitFieldShifts[i] = (short) shift;
+ if (mStates[i].mLabels.length < 2) {
+ throw new IllegalArgumentException("Invalid state: " + Arrays.toString(
+ mStates[i].mLabels) + ". Should have at least two values.");
+ }
+ int max = mStates[i].mLabels.length - 1;
+ int bitcount = Integer.SIZE - Integer.numberOfLeadingZeros(max);
+ mStateBitFieldMasks[i] = ((1 << bitcount) - 1) << shift;
+ shift = shift + bitcount;
+ }
+
+ if (shift >= Integer.SIZE - 1) {
+ throw new IllegalArgumentException("Too many states: " + shift
+ + " bits are required to represent the composite state, but only "
+ + (Integer.SIZE - 1) + " are available");
+ }
+
+ // Create a mask that filters out all non tracked states
+ int trackedMask = 0xFFFFFFFF;
+ for (int state = 0; state < mStates.length; state++) {
+ if (!mStates[state].mTracked) {
+ trackedMask &= ~mStateBitFieldMasks[state];
+ }
+ }
+
+ mCompositeToSerialState = new int[1 << shift];
+ Arrays.fill(mCompositeToSerialState, INVALID_SERIAL_STATE);
+
+ int nextSerialState = 0;
+ for (int composite = 0; composite < mCompositeToSerialState.length; composite++) {
+ if (!isValidCompositeState(composite)) continue;
+
+ // Values of an untracked State map to different composite states, but must map to
+ // the same serial state. Achieve that by computing a "base composite", which
+ // is equivalent to the current composite, but has 0 for all untracked States.
+ // See if the base composite already has a serial state assigned. If so, just use
+ // the same one for the current composite.
+ int baseComposite = composite & trackedMask;
+ if (mCompositeToSerialState[baseComposite] != INVALID_SERIAL_STATE) {
+ mCompositeToSerialState[composite] = mCompositeToSerialState[baseComposite];
+ } else {
+ mCompositeToSerialState[composite] = nextSerialState++;
+ }
+ }
+ }
+
+ private boolean isValidCompositeState(int composite) {
+ for (int stateIndex = 0; stateIndex < mStates.length; stateIndex++) {
+ int state = extractStateFromComposite(composite, stateIndex);
+ if (state >= mStates[stateIndex].mLabels.length) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private int extractStateFromComposite(int compositeState, int stateIndex) {
+ return (compositeState & mStateBitFieldMasks[stateIndex])
+ >>> mStateBitFieldShifts[stateIndex];
+ }
+
+ private int setStateInComposite(int baseCompositeState, int stateIndex, int value) {
+ return (baseCompositeState & ~mStateBitFieldMasks[stateIndex])
+ | (value << mStateBitFieldShifts[stateIndex]);
+ }
+
+ /**
+ * Allocates a new stats container using this Factory's configuration.
+ */
+ public MultiStateStats create() {
+ return new MultiStateStats(this, mDimensionCount);
+ }
+
+ /**
+ * Returns the total number of composite states handled by this container. For example,
+ * if there are two states: on-battery (0,1) and screen-on (0,1), both tracked, then the
+ * serial state count will be 2 * 2 = 4
+ */
+ @VisibleForTesting
+ public int getSerialStateCount() {
+ return mSerialStateCount;
+ }
+
+ /**
+ * Returns the integer index used by this container to represent the supplied composite
+ * state.
+ */
+ @VisibleForTesting
+ public int getSerialState(int[] states) {
+ Preconditions.checkArgument(states.length == mStates.length);
+ int compositeState = 0;
+ for (int i = 0; i < states.length; i++) {
+ compositeState = setStateInComposite(compositeState, i, states[i]);
+ }
+ int serialState = mCompositeToSerialState[compositeState];
+ if (serialState == INVALID_SERIAL_STATE) {
+ throw new IllegalArgumentException("State values out of bounds: "
+ + Arrays.toString(states));
+ }
+ return serialState;
+ }
+ }
+
+ private final Factory mFactory;
+ private final LongArrayMultiStateCounter mCounter;
+ private int mCompositeState;
+ private boolean mTracking;
+
+ public MultiStateStats(Factory factory, int dimensionCount) {
+ this.mFactory = factory;
+ mCounter = new LongArrayMultiStateCounter(factory.mSerialStateCount, dimensionCount);
+ }
+
+ /**
+ * Updates the current composite state by changing one of the States supplied to the Factory
+ * constructor.
+ *
+ * @param stateIndex Corresponds to the index of the States supplied to the Factory constructor
+ * @param state The new value of the state (e.g. 0 or 1 for "on-battery")
+ * @param timestampMs The time when the state change occurred
+ */
+ public void setState(int stateIndex, int state, long timestampMs) {
+ if (!mTracking) {
+ mCounter.updateValues(new long[mCounter.getArrayLength()], timestampMs);
+ mTracking = true;
+ }
+ mCompositeState = mFactory.setStateInComposite(mCompositeState, stateIndex, state);
+ mCounter.setState(mFactory.mCompositeToSerialState[mCompositeState], timestampMs);
+ }
+
+ /**
+ * Adds the delta to the metrics. The number of values must correspond to the dimension count
+ * supplied to the Factory constructor
+ */
+ public void increment(long[] values, long timestampMs) {
+ mCounter.incrementValues(values, timestampMs);
+ mTracking = true;
+ }
+
+ /**
+ * Returns accumulated stats for the specified composite state.
+ */
+ public void getStats(long[] outValues, int[] states) {
+ mCounter.getCounts(outValues, mFactory.getSerialState(states));
+ }
+
+ /**
+ * Resets the counters.
+ */
+ public void reset() {
+ mCounter.reset();
+ mTracking = false;
+ }
+
+ @Override
+ public String toString() {
+ return mCounter.toString();
+ }
+
+ /**
+ * Prints the accumulated stats, one line of every combination of states that has data.
+ */
+ public void dump(PrintWriter pw) {
+ long[] tmpArray = new long[mCounter.getArrayLength()];
+ dumpAllStates(pw, new int[mFactory.mStates.length], 0, tmpArray);
+ }
+
+ private void dumpAllStates(PrintWriter pw, int[] states, int stateIndex, long[] values) {
+ if (stateIndex < states.length) {
+ if (!mFactory.mStates[stateIndex].mTracked) {
+ dumpAllStates(pw, states, stateIndex + 1, values);
+ return;
+ }
+
+ for (int i = 0; i < mFactory.mStates[stateIndex].mLabels.length; i++) {
+ states[stateIndex] = i;
+ dumpAllStates(pw, states, stateIndex + 1, values);
+ }
+ return;
+ }
+
+ mCounter.getCounts(values, mFactory.getSerialState(states));
+ boolean nonZero = false;
+ for (long value : values) {
+ if (value != 0) {
+ nonZero = true;
+ break;
+ }
+ }
+ if (!nonZero) {
+ return;
+ }
+
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < states.length; i++) {
+ if (mFactory.mStates[i].mTracked) {
+ if (sb.length() != 0) {
+ sb.append(" ");
+ }
+ sb.append(mFactory.mStates[i].mLabels[states[i]]);
+ }
+ }
+ sb.append(" ");
+ sb.append(Arrays.toString(values));
+ pw.println(sb);
+ }
+}
diff --git a/core/java/com/android/internal/os/PowerStats.java b/core/java/com/android/internal/os/PowerStats.java
index 1169552e165d..8f66d1f9365c 100644
--- a/core/java/com/android/internal/os/PowerStats.java
+++ b/core/java/com/android/internal/os/PowerStats.java
@@ -16,21 +16,190 @@
package com.android.internal.os;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.os.BatteryConsumer;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.PersistableBundle;
+import android.os.UserHandle;
import android.util.IndentingPrintWriter;
+import android.util.Log;
import android.util.SparseArray;
import java.util.Arrays;
+import java.util.Objects;
/**
* Container for power stats, acquired by various PowerStatsCollector classes. See subclasses for
* details.
*/
public final class PowerStats {
+ private static final String TAG = "PowerStats";
+
+ private static final BatteryStatsHistory.VarintParceler VARINT_PARCELER =
+ new BatteryStatsHistory.VarintParceler();
+ private static final byte PARCEL_FORMAT_VERSION = 1;
+
+ private static final int PARCEL_FORMAT_VERSION_MASK = 0x000000FF;
+ private static final int PARCEL_FORMAT_VERSION_SHIFT =
+ Integer.numberOfTrailingZeros(PARCEL_FORMAT_VERSION_MASK);
+ private static final int STATS_ARRAY_LENGTH_MASK = 0x0000FF00;
+ private static final int STATS_ARRAY_LENGTH_SHIFT =
+ Integer.numberOfTrailingZeros(STATS_ARRAY_LENGTH_MASK);
+ public static final int MAX_STATS_ARRAY_LENGTH =
+ 2 ^ Integer.bitCount(STATS_ARRAY_LENGTH_MASK) - 1;
+ private static final int UID_STATS_ARRAY_LENGTH_MASK = 0x00FF0000;
+ private static final int UID_STATS_ARRAY_LENGTH_SHIFT =
+ Integer.numberOfTrailingZeros(UID_STATS_ARRAY_LENGTH_MASK);
+ public static final int MAX_UID_STATS_ARRAY_LENGTH =
+ (2 ^ Integer.bitCount(UID_STATS_ARRAY_LENGTH_MASK)) - 1;
+
/**
- * Power component (e.g. CPU, WIFI etc) that this snapshot relates to.
+ * Descriptor of the stats collected for a given power component (e.g. CPU, WiFi etc).
+ * This descriptor is used for storing PowerStats and can also be used by power models
+ * to adjust the algorithm in accordance with the stats available on the device.
*/
- public @BatteryConsumer.PowerComponent int powerComponentId;
+ public static class Descriptor {
+ /**
+ * {@link BatteryConsumer.PowerComponent} (e.g. CPU, WIFI etc) that this snapshot relates
+ * to; or a custom power component ID (if the value
+ * is &gt;= {@link BatteryConsumer#FIRST_CUSTOM_POWER_COMPONENT_ID}).
+ */
+ public final int powerComponentId;
+ public final String name;
+
+ public final int statsArrayLength;
+ public final int uidStatsArrayLength;
+
+ /**
+ * Extra parameters specific to the power component, e.g. the availability of power
+ * monitors.
+ */
+ public final PersistableBundle extras;
+
+ public Descriptor(@BatteryConsumer.PowerComponent int powerComponentId,
+ int statsArrayLength, int uidStatsArrayLength, @NonNull PersistableBundle extras) {
+ this(powerComponentId, BatteryConsumer.powerComponentIdToString(powerComponentId),
+ statsArrayLength, uidStatsArrayLength, extras);
+ }
+
+ public Descriptor(int customPowerComponentId, String name, int statsArrayLength,
+ int uidStatsArrayLength, PersistableBundle extras) {
+ if (statsArrayLength > MAX_STATS_ARRAY_LENGTH) {
+ throw new IllegalArgumentException(
+ "statsArrayLength is too high. Max = " + MAX_STATS_ARRAY_LENGTH);
+ }
+ if (uidStatsArrayLength > MAX_UID_STATS_ARRAY_LENGTH) {
+ throw new IllegalArgumentException(
+ "uidStatsArrayLength is too high. Max = " + MAX_UID_STATS_ARRAY_LENGTH);
+ }
+ this.powerComponentId = customPowerComponentId;
+ this.name = name;
+ this.statsArrayLength = statsArrayLength;
+ this.uidStatsArrayLength = uidStatsArrayLength;
+ this.extras = extras;
+ }
+
+ /**
+ * Writes the Descriptor into the parcel.
+ */
+ public void writeSummaryToParcel(Parcel parcel) {
+ int firstWord = ((PARCEL_FORMAT_VERSION << PARCEL_FORMAT_VERSION_SHIFT)
+ & PARCEL_FORMAT_VERSION_MASK)
+ | ((statsArrayLength << STATS_ARRAY_LENGTH_SHIFT)
+ & STATS_ARRAY_LENGTH_MASK)
+ | ((uidStatsArrayLength << UID_STATS_ARRAY_LENGTH_SHIFT)
+ & UID_STATS_ARRAY_LENGTH_MASK);
+ parcel.writeInt(firstWord);
+ parcel.writeInt(powerComponentId);
+ parcel.writeString(name);
+ extras.writeToParcel(parcel, 0);
+ }
+
+ /**
+ * Reads a Descriptor from the parcel. If the parcel has an incompatible format,
+ * returns null.
+ */
+ @Nullable
+ public static Descriptor readSummaryFromParcel(Parcel parcel) {
+ int firstWord = parcel.readInt();
+ int version = (firstWord & PARCEL_FORMAT_VERSION_MASK) >>> PARCEL_FORMAT_VERSION_SHIFT;
+ if (version != PARCEL_FORMAT_VERSION) {
+ Log.w(TAG, "Cannot read PowerStats from Parcel - the parcel format version has "
+ + "changed from " + version + " to " + PARCEL_FORMAT_VERSION);
+ return null;
+ }
+ int statsArrayLength =
+ (firstWord & STATS_ARRAY_LENGTH_MASK) >>> STATS_ARRAY_LENGTH_SHIFT;
+ int uidStatsArrayLength =
+ (firstWord & UID_STATS_ARRAY_LENGTH_MASK) >>> UID_STATS_ARRAY_LENGTH_SHIFT;
+ int powerComponentId = parcel.readInt();
+ String name = parcel.readString();
+ PersistableBundle extras = parcel.readPersistableBundle();
+ return new Descriptor(powerComponentId, name, statsArrayLength, uidStatsArrayLength,
+ extras);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof Descriptor)) return false;
+ Descriptor that = (Descriptor) o;
+ return powerComponentId == that.powerComponentId
+ && statsArrayLength == that.statsArrayLength
+ && uidStatsArrayLength == that.uidStatsArrayLength
+ && Objects.equals(name, that.name)
+ && extras.size() == that.extras.size() // Unparcel the Parcel if not yet
+ && Bundle.kindofEquals(extras,
+ that.extras); // Since the Parcel is now unparceled, do a deep comparison
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(powerComponentId);
+ }
+
+ @Override
+ public String toString() {
+ if (extras != null) {
+ extras.size(); // Unparcel
+ }
+ return "PowerStats.Descriptor{"
+ + "powerComponentId=" + powerComponentId
+ + ", name='" + name + '\''
+ + ", statsArrayLength=" + statsArrayLength
+ + ", uidStatsArrayLength=" + uidStatsArrayLength
+ + ", extras=" + extras
+ + '}';
+ }
+ }
+
+ /**
+ * A registry for all supported power component types (e.g. CPU, WiFi).
+ */
+ public static class DescriptorRegistry {
+ private final SparseArray<Descriptor> mDescriptors = new SparseArray<>();
+
+ /**
+ * Adds the specified descriptor to the registry. If the registry already
+ * contained a descriptor for the same power component, then the new one replaces
+ * the old one.
+ */
+ public void register(Descriptor descriptor) {
+ mDescriptors.put(descriptor.powerComponentId, descriptor);
+ }
+
+ /**
+ * @param powerComponentId either a BatteryConsumer.PowerComponent or a custom power
+ * component ID
+ */
+ public Descriptor get(int powerComponentId) {
+ return mDescriptors.get(powerComponentId);
+ }
+ }
+
+ public final Descriptor descriptor;
/**
* Duration, in milliseconds, covered by this snapshot.
@@ -47,12 +216,98 @@ public final class PowerStats {
*/
public final SparseArray<long[]> uidStats = new SparseArray<>();
+ public PowerStats(Descriptor descriptor) {
+ this.descriptor = descriptor;
+ stats = new long[descriptor.statsArrayLength];
+ }
+
+ /**
+ * Writes the object into the parcel.
+ */
+ public void writeToParcel(Parcel parcel) {
+ int lengthPos = parcel.dataPosition();
+ parcel.writeInt(0); // Placeholder for length
+
+ int startPos = parcel.dataPosition();
+ parcel.writeInt(descriptor.powerComponentId);
+ parcel.writeLong(durationMs);
+ VARINT_PARCELER.writeLongArray(parcel, stats);
+ parcel.writeInt(uidStats.size());
+ for (int i = 0; i < uidStats.size(); i++) {
+ parcel.writeInt(uidStats.keyAt(i));
+ VARINT_PARCELER.writeLongArray(parcel, uidStats.valueAt(i));
+ }
+
+ int endPos = parcel.dataPosition();
+ parcel.setDataPosition(lengthPos);
+ parcel.writeInt(endPos - startPos);
+ parcel.setDataPosition(endPos);
+ }
+
+ /**
+ * Reads a PowerStats object from the supplied Parcel. If the parcel has an incompatible
+ * format, returns null.
+ */
+ @Nullable
+ public static PowerStats readFromParcel(Parcel parcel, DescriptorRegistry registry) {
+ int length = parcel.readInt();
+ int startPos = parcel.dataPosition();
+ int endPos = startPos + length;
+
+ try {
+ int powerComponentId = parcel.readInt();
+
+ Descriptor descriptor = registry.get(powerComponentId);
+ if (descriptor == null) {
+ Log.e(TAG, "Unsupported PowerStats for power component ID: " + powerComponentId);
+ return null;
+ }
+ PowerStats stats = new PowerStats(descriptor);
+ stats.durationMs = parcel.readLong();
+ stats.stats = new long[descriptor.statsArrayLength];
+ VARINT_PARCELER.readLongArray(parcel, stats.stats);
+ int uidCount = parcel.readInt();
+ for (int i = 0; i < uidCount; i++) {
+ int uid = parcel.readInt();
+ long[] uidStats = new long[descriptor.uidStatsArrayLength];
+ VARINT_PARCELER.readLongArray(parcel, uidStats);
+ stats.uidStats.put(uid, uidStats);
+ }
+ if (parcel.dataPosition() != endPos) {
+ Log.e(TAG, "Corrupted PowerStats parcel. Expected length: " + length
+ + ", actual length: " + (parcel.dataPosition() - startPos));
+ return null;
+ }
+ return stats;
+ } finally {
+ // Unconditionally skip to the end of the written data, even if the actual parcel
+ // format is incompatible
+ parcel.setDataPosition(endPos);
+ }
+ }
+
+ /**
+ * Formats the stats as a string suitable to be included in the Battery History dump.
+ */
+ public String formatForBatteryHistory(String uidPrefix) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("duration=").append(durationMs).append(" ").append(descriptor.name);
+ if (stats.length > 0) {
+ sb.append("=").append(Arrays.toString(stats));
+ }
+ for (int i = 0; i < uidStats.size(); i++) {
+ sb.append(uidPrefix)
+ .append(UserHandle.formatUid(uidStats.keyAt(i)))
+ .append(": ").append(Arrays.toString(uidStats.valueAt(i)));
+ }
+ return sb.toString();
+ }
+
/**
* Prints the contents of the stats snapshot.
*/
public void dump(IndentingPrintWriter pw) {
- pw.print("PowerStats: ");
- pw.println(BatteryConsumer.powerComponentIdToString(powerComponentId));
+ pw.println("PowerStats: " + descriptor.name + " (" + descriptor.powerComponentId + ')');
pw.increaseIndent();
pw.print("duration", durationMs).println();
for (int i = 0; i < uidStats.size(); i++) {
@@ -64,4 +319,9 @@ public final class PowerStats {
}
pw.decreaseIndent();
}
+
+ @Override
+ public String toString() {
+ return "PowerStats: " + formatForBatteryHistory(" UID ");
+ }
}
diff --git a/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp b/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp
index 76f5c107c970..69202111f74c 100644
--- a/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp
+++ b/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp
@@ -65,6 +65,16 @@ static void native_updateValues(jlong nativePtr, jlong longArrayContainerNativeP
counter->updateValue(*vector, timestamp);
}
+static void native_incrementValues(jlong nativePtr, jlong longArrayContainerNativePtr,
+ jlong timestamp) {
+ battery::LongArrayMultiStateCounter *counter =
+ reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr);
+ std::vector<uint64_t> *vector =
+ reinterpret_cast<std::vector<uint64_t> *>(longArrayContainerNativePtr);
+
+ counter->incrementValue(*vector, timestamp);
+}
+
static void native_addCounts(jlong nativePtr, jlong longArrayContainerNativePtr) {
battery::LongArrayMultiStateCounter *counter =
reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr);
@@ -202,6 +212,8 @@ static const JNINativeMethod g_LongArrayMultiStateCounter_methods[] = {
// @CriticalNative
{"native_updateValues", "(JJJ)V", (void *)native_updateValues},
// @CriticalNative
+ {"native_incrementValues", "(JJJ)V", (void *)native_incrementValues},
+ // @CriticalNative
{"native_addCounts", "(JJ)V", (void *)native_addCounts},
// @CriticalNative
{"native_reset", "(J)V", (void *)native_reset},
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
index 38c3aa04ea35..2cbeaa17332c 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
@@ -35,8 +35,9 @@ import org.junit.runners.Suite;
LongArrayMultiStateCounterTest.class,
LongMultiStateCounterTest.class,
PowerProfileTest.class,
+ PowerStatsTest.class,
EnergyConsumerStatsTest.class
})
public class BatteryStatsTests {
-} \ No newline at end of file
+}
diff --git a/core/tests/coretests/src/com/android/internal/os/PowerStatsTest.java b/core/tests/coretests/src/com/android/internal/os/PowerStatsTest.java
new file mode 100644
index 000000000000..29da2319adc2
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/os/PowerStatsTest.java
@@ -0,0 +1,132 @@
+/*
+ * 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.internal.os;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.BatteryConsumer;
+import android.os.Parcel;
+import android.os.PersistableBundle;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class PowerStatsTest {
+
+ private PowerStats.DescriptorRegistry mRegistry;
+ private PowerStats.Descriptor mDescriptor;
+
+ @Before
+ public void setup() {
+ mRegistry = new PowerStats.DescriptorRegistry();
+ PersistableBundle extras = new PersistableBundle();
+ extras.putBoolean("hasPowerMonitor", true);
+ mDescriptor = new PowerStats.Descriptor(BatteryConsumer.POWER_COMPONENT_CPU, 3, 2, extras);
+ mRegistry.register(mDescriptor);
+ }
+
+ @Test
+ public void parceling_compatibleParcel() {
+ PowerStats stats = new PowerStats(mDescriptor);
+ stats.durationMs = 1234;
+ stats.stats[0] = 10;
+ stats.stats[1] = 20;
+ stats.stats[2] = 30;
+ stats.uidStats.put(42, new long[]{40, 50});
+ stats.uidStats.put(99, new long[]{60, 70});
+
+ Parcel parcel = Parcel.obtain();
+ mDescriptor.writeSummaryToParcel(parcel);
+ stats.writeToParcel(parcel);
+ parcel.writeString("END");
+
+ Parcel newParcel = marshallAndUnmarshall(parcel);
+
+ PowerStats.Descriptor newDescriptor =
+ PowerStats.Descriptor.readSummaryFromParcel(newParcel);
+ assertThat(newDescriptor.powerComponentId).isEqualTo(BatteryConsumer.POWER_COMPONENT_CPU);
+ assertThat(newDescriptor.name).isEqualTo("cpu");
+ assertThat(newDescriptor.statsArrayLength).isEqualTo(3);
+ assertThat(newDescriptor.uidStatsArrayLength).isEqualTo(2);
+ assertThat(newDescriptor.extras.getBoolean("hasPowerMonitor")).isTrue();
+
+ mRegistry.register(newDescriptor);
+
+ PowerStats newStats = PowerStats.readFromParcel(newParcel, mRegistry);
+ assertThat(newStats.durationMs).isEqualTo(1234);
+ assertThat(newStats.stats).isEqualTo(new long[]{10, 20, 30});
+ assertThat(newStats.uidStats.size()).isEqualTo(2);
+ assertThat(newStats.uidStats.get(42)).isEqualTo(new long[]{40, 50});
+ assertThat(newStats.uidStats.get(99)).isEqualTo(new long[]{60, 70});
+
+ String end = newParcel.readString();
+ assertThat(end).isEqualTo("END");
+ }
+
+ @Test
+ public void parceling_unrecognizedPowerComponent() {
+ PowerStats stats = new PowerStats(
+ new PowerStats.Descriptor(777, "luck", 3, 2, new PersistableBundle()));
+ stats.durationMs = 1234;
+
+ Parcel parcel = Parcel.obtain();
+ stats.writeToParcel(parcel);
+ parcel.writeString("END");
+
+ Parcel newParcel = marshallAndUnmarshall(parcel);
+
+ PowerStats newStats = PowerStats.readFromParcel(newParcel, mRegistry);
+ assertThat(newStats).isNull();
+
+ String end = newParcel.readString();
+ assertThat(end).isEqualTo("END");
+ }
+
+ @Test
+ public void parceling_wrongArrayLength() {
+ PowerStats stats = new PowerStats(mDescriptor);
+ stats.stats = new long[5]; // Is expected to be 3
+
+ Parcel parcel = Parcel.obtain();
+ stats.writeToParcel(parcel);
+ parcel.writeString("END");
+
+ Parcel newParcel = marshallAndUnmarshall(parcel);
+
+ PowerStats newStats = PowerStats.readFromParcel(newParcel, mRegistry);
+ assertThat(newStats).isNull();
+
+ String end = newParcel.readString();
+ assertThat(end).isEqualTo("END");
+ }
+
+ private static Parcel marshallAndUnmarshall(Parcel parcel) {
+ byte[] bytes = parcel.marshall();
+ parcel.recycle();
+
+ Parcel newParcel = Parcel.obtain();
+ newParcel.unmarshall(bytes, 0, bytes.length);
+ newParcel.setDataPosition(0);
+ return newParcel;
+ }
+}
diff --git a/libs/WindowManager/Shell/res/layout/bubble_bar_manage_education.xml b/libs/WindowManager/Shell/res/layout/bubble_bar_manage_education.xml
new file mode 100644
index 000000000000..a0a06f1b3721
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/bubble_bar_manage_education.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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
+ -->
+<com.android.wm.shell.common.bubbles.BubblePopupView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal"
+ android:layout_marginHorizontal="@dimen/bubble_popup_margin_horizontal"
+ android:layout_marginTop="@dimen/bubble_popup_margin_top"
+ android:elevation="@dimen/bubble_manage_menu_elevation"
+ android:gravity="center_horizontal"
+ android:orientation="vertical">
+
+ <ImageView
+ android:layout_width="32dp"
+ android:layout_height="32dp"
+ android:tint="?android:attr/colorAccent"
+ android:contentDescription="@null"
+ android:src="@drawable/pip_ic_settings"/>
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="16dp"
+ android:maxWidth="@dimen/bubble_popup_content_max_width"
+ android:maxLines="1"
+ android:ellipsize="end"
+ android:textAppearance="@android:style/TextAppearance.DeviceDefault.Headline"
+ android:textColor="?android:attr/textColorPrimary"
+ android:text="@string/bubble_bar_education_manage_title"/>
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="16dp"
+ android:maxWidth="@dimen/bubble_popup_content_max_width"
+ android:textAppearance="@android:style/TextAppearance.DeviceDefault"
+ android:textColor="?android:attr/textColorSecondary"
+ android:textAlignment="center"
+ android:text="@string/bubble_bar_education_manage_text"/>
+
+</com.android.wm.shell.common.bubbles.BubblePopupView> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 597e899d098d..20bf81da5561 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -226,6 +226,20 @@
<dimen name="bubble_user_education_padding_end">58dp</dimen>
<!-- Padding between the bubble and the user education text. -->
<dimen name="bubble_user_education_stack_padding">16dp</dimen>
+ <!-- Max width for the bubble popup view. -->
+ <dimen name="bubble_popup_content_max_width">300dp</dimen>
+ <!-- Horizontal margin for the bubble popup view. -->
+ <dimen name="bubble_popup_margin_horizontal">32dp</dimen>
+ <!-- Top margin for the bubble popup view. -->
+ <dimen name="bubble_popup_margin_top">16dp</dimen>
+ <!-- Width for the bubble popup view arrow. -->
+ <dimen name="bubble_popup_arrow_width">12dp</dimen>
+ <!-- Height for the bubble popup view arrow. -->
+ <dimen name="bubble_popup_arrow_height">10dp</dimen>
+ <!-- Corner radius for the bubble popup view arrow. -->
+ <dimen name="bubble_popup_arrow_corner_radius">2dp</dimen>
+ <!-- Padding for the bubble popup view contents. -->
+ <dimen name="bubble_popup_padding">24dp</dimen>
<!-- The size of the caption bar inset at the top of bubble bar expanded view. -->
<dimen name="bubble_bar_expanded_view_caption_height">32dp</dimen>
<!-- The height of the dots shown for the caption menu in the bubble bar expanded view.. -->
diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index 8cbc3d016b01..00c63d70d3a0 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -163,6 +163,11 @@
<!-- [CHAR LIMIT=NONE] Empty overflow subtitle -->
<string name="bubble_overflow_empty_subtitle">Recent bubbles and dismissed bubbles will appear here</string>
+ <!-- Title text for the bubble bar "manage" button tool tip highlighting where users can go to control bubble settings. [CHAR LIMIT=60]-->
+ <string name="bubble_bar_education_manage_title">Control bubbles anytime</string>
+ <!-- Descriptive text for the bubble bar "manage" button tool tip highlighting where users can go to control bubble settings. [CHAR LIMIT=80]-->
+ <string name="bubble_bar_education_manage_text">Tap here to manage which apps and conversations can bubble</string>
+
<!-- [CHAR LIMIT=100] Notification Importance title -->
<string name="notification_bubble_title">Bubble</string>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
index 7e09c989e1b3..ff67110634ba 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
@@ -60,7 +60,6 @@ import java.util.concurrent.Executor;
/**
* Encapsulates the data and UI elements of a bubble.
*/
-@VisibleForTesting
public class Bubble implements BubbleViewProvider {
private static final String TAG = "Bubble";
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleEducationController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleEducationController.kt
new file mode 100644
index 000000000000..e57f02c71e44
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleEducationController.kt
@@ -0,0 +1,74 @@
+/*
+ * 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.wm.shell.bubbles
+
+import android.content.Context
+import android.util.Log
+import androidx.core.content.edit
+import com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_USER_EDUCATION
+import com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES
+import com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME
+
+/** Manages bubble education flags. Provides convenience methods to check the education state */
+class BubbleEducationController(private val context: Context) {
+ private val prefs = context.getSharedPreferences(context.packageName, Context.MODE_PRIVATE)
+
+ /** Whether the user has seen the stack education */
+ @get:JvmName(name = "hasSeenStackEducation")
+ var hasSeenStackEducation: Boolean
+ get() = prefs.getBoolean(PREF_STACK_EDUCATION, false)
+ set(value) = prefs.edit { putBoolean(PREF_STACK_EDUCATION, value) }
+
+ /** Whether the user has seen the expanded view "manage" menu education */
+ @get:JvmName(name = "hasSeenManageEducation")
+ var hasSeenManageEducation: Boolean
+ get() = prefs.getBoolean(PREF_MANAGED_EDUCATION, false)
+ set(value) = prefs.edit { putBoolean(PREF_MANAGED_EDUCATION, value) }
+
+ /** Whether education view should show for the collapsed stack. */
+ fun shouldShowStackEducation(bubble: BubbleViewProvider?): Boolean {
+ val shouldShow = bubble != null &&
+ bubble.isConversationBubble && // show education for conversation bubbles only
+ (!hasSeenStackEducation || BubbleDebugConfig.forceShowUserEducation(context))
+ logDebug("Show stack edu: $shouldShow")
+ return shouldShow
+ }
+
+ /** Whether the educational view should show for the expanded view "manage" menu. */
+ fun shouldShowManageEducation(bubble: BubbleViewProvider?): Boolean {
+ val shouldShow = bubble != null &&
+ bubble.isConversationBubble && // show education for conversation bubbles only
+ (!hasSeenManageEducation || BubbleDebugConfig.forceShowUserEducation(context))
+ logDebug("Show manage edu: $shouldShow")
+ return shouldShow
+ }
+
+ private fun logDebug(message: String) {
+ if (DEBUG_USER_EDUCATION) {
+ Log.d(TAG, message)
+ }
+ }
+
+ companion object {
+ private val TAG = if (TAG_WITH_CLASS_NAME) "BubbleEducationController" else TAG_BUBBLES
+ const val PREF_STACK_EDUCATION: String = "HasSeenBubblesOnboarding"
+ const val PREF_MANAGED_EDUCATION: String = "HasSeenBubblesManageOnboarding"
+ }
+}
+
+/** Convenience extension method to check if the bubble is a conversation bubble */
+private val BubbleViewProvider.isConversationBubble: Boolean
+ get() = if (this is Bubble) isConversation else false
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePopupViewExt.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePopupViewExt.kt
new file mode 100644
index 000000000000..bdb09e11d5ad
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePopupViewExt.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.wm.shell.bubbles
+
+import android.graphics.Color
+import com.android.wm.shell.R
+import com.android.wm.shell.common.bubbles.BubblePopupDrawable
+import com.android.wm.shell.common.bubbles.BubblePopupView
+
+/**
+ * A convenience method to setup the [BubblePopupView] with the correct config using local resources
+ */
+fun BubblePopupView.setup() {
+ val attrs =
+ context.obtainStyledAttributes(
+ intArrayOf(
+ com.android.internal.R.attr.materialColorSurface,
+ android.R.attr.dialogCornerRadius
+ )
+ )
+
+ val res = context.resources
+ val config =
+ BubblePopupDrawable.Config(
+ color = attrs.getColor(0, Color.WHITE),
+ cornerRadius = attrs.getDimension(1, 0f),
+ contentPadding = res.getDimensionPixelSize(R.dimen.bubble_popup_padding),
+ arrowWidth = res.getDimension(R.dimen.bubble_popup_arrow_width),
+ arrowHeight = res.getDimension(R.dimen.bubble_popup_arrow_height),
+ arrowRadius = res.getDimension(R.dimen.bubble_popup_arrow_corner_radius)
+ )
+ attrs.recycle()
+ setupBackground(config)
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
index 6b6d6baa3d39..79f188ab2611 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
@@ -39,7 +39,6 @@ import com.android.wm.shell.bubbles.BubbleTaskViewHelper;
import com.android.wm.shell.bubbles.Bubbles;
import com.android.wm.shell.taskview.TaskView;
-import java.util.function.Consumer;
import java.util.function.Supplier;
/**
@@ -48,6 +47,18 @@ import java.util.function.Supplier;
* {@link BubbleController#isShowingAsBubbleBar()}
*/
public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskViewHelper.Listener {
+ /**
+ * The expanded view listener notifying the {@link BubbleBarLayerView} about the internal
+ * actions and events
+ */
+ public interface Listener {
+ /** Called when the task view task is first created. */
+ void onTaskCreated();
+ /** Called when expanded view needs to un-bubble the given conversation */
+ void onUnBubbleConversation(String bubbleKey);
+ /** Called when expanded view task view back button pressed */
+ void onBackPressed();
+ }
private static final String TAG = BubbleBarExpandedView.class.getSimpleName();
private static final int INVALID_TASK_ID = -1;
@@ -57,7 +68,7 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
private BubbleTaskViewHelper mBubbleTaskViewHelper;
private BubbleBarMenuViewController mMenuViewController;
private @Nullable Supplier<Rect> mLayerBoundsSupplier;
- private @Nullable Consumer<String> mUnBubbleConversationCallback;
+ private @Nullable Listener mListener;
private BubbleBarHandleView mHandleView = new BubbleBarHandleView(getContext());
private @Nullable TaskView mTaskView;
@@ -145,15 +156,13 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
mMenuViewController.setListener(new BubbleBarMenuViewController.Listener() {
@Override
public void onMenuVisibilityChanged(boolean visible) {
- if (mTaskView == null || mLayerBoundsSupplier == null) return;
- // Updates the obscured touchable region for the task surface.
- mTaskView.setObscuredTouchRect(visible ? mLayerBoundsSupplier.get() : null);
+ setObscured(visible);
}
@Override
public void onUnBubbleConversation(Bubble bubble) {
- if (mUnBubbleConversationCallback != null) {
- mUnBubbleConversationCallback.accept(bubble.getKey());
+ if (mListener != null) {
+ mListener.onUnBubbleConversation(bubble.getKey());
}
}
@@ -231,6 +240,9 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
public void onTaskCreated() {
setContentVisibility(true);
updateHandleColor(false /* animated */);
+ if (mListener != null) {
+ mListener.onTaskCreated();
+ }
}
@Override
@@ -240,7 +252,8 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
@Override
public void onBackPressed() {
- mController.collapseStack();
+ if (mListener == null) return;
+ mListener.onBackPressed();
}
/** Cleans up task view, should be called when the bubble is no longer active. */
@@ -254,6 +267,18 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
mMenuViewController.hideMenu(false /* animated */);
}
+ /**
+ * Hides the current modal menu view or collapses the bubble stack.
+ * Called from {@link BubbleBarLayerView}
+ */
+ public void hideMenuOrCollapse() {
+ if (mMenuViewController.isMenuVisible()) {
+ mMenuViewController.hideMenu(/* animated = */ true);
+ } else {
+ mController.collapseStack();
+ }
+ }
+
/** Updates the bubble shown in the expanded view. */
public void update(Bubble bubble) {
mBubbleTaskViewHelper.update(bubble);
@@ -270,10 +295,16 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
mLayerBoundsSupplier = supplier;
}
- /** Sets the function to call to un-bubble the given conversation. */
- public void setUnBubbleConversationCallback(
- @Nullable Consumer<String> unBubbleConversationCallback) {
- mUnBubbleConversationCallback = unBubbleConversationCallback;
+ /** Sets expanded view listener */
+ void setListener(@Nullable Listener listener) {
+ mListener = listener;
+ }
+
+ /** Sets whether the view is obscured by some modal view */
+ void setObscured(boolean obscured) {
+ if (mTaskView == null || mLayerBoundsSupplier == null) return;
+ // Updates the obscured touchable region for the task surface.
+ mTaskView.setObscuredTouchRect(obscured ? mLayerBoundsSupplier.get() : null);
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
index bc04bfc8c18b..8f11253290ea 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
@@ -52,6 +52,7 @@ public class BubbleBarLayerView extends FrameLayout
private final BubbleController mBubbleController;
private final BubblePositioner mPositioner;
private final BubbleBarAnimationHelper mAnimationHelper;
+ private final BubbleEducationViewController mEducationViewController;
private final View mScrimView;
@Nullable
@@ -80,6 +81,10 @@ public class BubbleBarLayerView extends FrameLayout
mAnimationHelper = new BubbleBarAnimationHelper(context,
this, mPositioner);
+ mEducationViewController = new BubbleEducationViewController(context, (boolean visible) -> {
+ if (mExpandedView == null) return;
+ mExpandedView.setObscured(visible);
+ });
mScrimView = new View(getContext());
mScrimView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
@@ -90,9 +95,7 @@ public class BubbleBarLayerView extends FrameLayout
mScrimView.setBackgroundDrawable(new ColorDrawable(
getResources().getColor(android.R.color.system_neutral1_1000)));
- setOnClickListener(view -> {
- mBubbleController.collapseStack();
- });
+ setOnClickListener(view -> hideMenuOrCollapse());
}
@Override
@@ -108,6 +111,7 @@ public class BubbleBarLayerView extends FrameLayout
getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
if (mExpandedView != null) {
+ mEducationViewController.hideManageEducation(/* animated = */ false);
removeView(mExpandedView);
mExpandedView = null;
}
@@ -162,14 +166,27 @@ public class BubbleBarLayerView extends FrameLayout
final int width = mPositioner.getExpandedViewWidthForBubbleBar(isOverflowExpanded);
final int height = mPositioner.getExpandedViewHeightForBubbleBar(isOverflowExpanded);
mExpandedView.setVisibility(GONE);
- mExpandedView.setUnBubbleConversationCallback(mUnBubbleConversationCallback);
+ mExpandedView.setY(mPositioner.getExpandedViewBottomForBubbleBar() - height);
mExpandedView.setLayerBoundsSupplier(() -> new Rect(0, 0, getWidth(), getHeight()));
- mExpandedView.setUnBubbleConversationCallback(bubbleKey -> {
- if (mUnBubbleConversationCallback != null) {
- mUnBubbleConversationCallback.accept(bubbleKey);
+ mExpandedView.setListener(new BubbleBarExpandedView.Listener() {
+ @Override
+ public void onTaskCreated() {
+ mEducationViewController.maybeShowManageEducation(b, mExpandedView);
+ }
+
+ @Override
+ public void onUnBubbleConversation(String bubbleKey) {
+ if (mUnBubbleConversationCallback != null) {
+ mUnBubbleConversationCallback.accept(bubbleKey);
+ }
+ }
+
+ @Override
+ public void onBackPressed() {
+ hideMenuOrCollapse();
}
});
- mExpandedView.setY(mPositioner.getExpandedViewBottomForBubbleBar() - height);
+
addView(mExpandedView, new FrameLayout.LayoutParams(width, height));
}
@@ -193,6 +210,7 @@ public class BubbleBarLayerView extends FrameLayout
public void collapse() {
mIsExpanded = false;
final BubbleBarExpandedView viewToRemove = mExpandedView;
+ mEducationViewController.hideManageEducation(/* animated = */ true);
mAnimationHelper.animateCollapse(() -> removeView(viewToRemove));
mBubbleController.getSysuiProxy().onStackExpandChanged(false);
mExpandedView = null;
@@ -206,6 +224,17 @@ public class BubbleBarLayerView extends FrameLayout
mUnBubbleConversationCallback = unBubbleConversationCallback;
}
+ /** Hides the current modal education/menu view, expanded view or collapses the bubble stack */
+ private void hideMenuOrCollapse() {
+ if (mEducationViewController.isManageEducationVisible()) {
+ mEducationViewController.hideManageEducation(/* animated = */ true);
+ } else if (isExpanded() && mExpandedView != null) {
+ mExpandedView.hideMenuOrCollapse();
+ } else {
+ mBubbleController.collapseStack();
+ }
+ }
+
/** Updates the expanded view size and position. */
private void updateExpandedView() {
if (mExpandedView == null) return;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuViewController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuViewController.java
index 8be140c16435..81e7582e0dba 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuViewController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuViewController.java
@@ -56,6 +56,11 @@ class BubbleBarMenuViewController {
SpringForce.STIFFNESS_MEDIUM, SpringForce.DAMPING_RATIO_LOW_BOUNCY);
}
+ /** Tells if the menu is visible or being animated */
+ boolean isMenuVisible() {
+ return mMenuView != null && mMenuView.getVisibility() == View.VISIBLE;
+ }
+
/** Sets menu actions listener */
void setListener(@Nullable Listener listener) {
mListener = listener;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleEducationViewController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleEducationViewController.kt
new file mode 100644
index 000000000000..7b39c6fd4059
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleEducationViewController.kt
@@ -0,0 +1,148 @@
+/*
+ * 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.wm.shell.bubbles.bar
+
+import android.content.Context
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import androidx.core.view.doOnLayout
+import androidx.dynamicanimation.animation.DynamicAnimation
+import androidx.dynamicanimation.animation.SpringForce
+import com.android.wm.shell.R
+import com.android.wm.shell.animation.PhysicsAnimator
+import com.android.wm.shell.bubbles.BubbleEducationController
+import com.android.wm.shell.bubbles.BubbleViewProvider
+import com.android.wm.shell.bubbles.setup
+import com.android.wm.shell.common.bubbles.BubblePopupView
+
+/** Manages bubble education presentation and animation */
+class BubbleEducationViewController(private val context: Context, private val listener: Listener) {
+ interface Listener {
+ fun onManageEducationVisibilityChanged(isVisible: Boolean)
+ }
+
+ private var rootView: ViewGroup? = null
+ private var educationView: BubblePopupView? = null
+ private var animator: PhysicsAnimator<BubblePopupView>? = null
+
+ private val springConfig by lazy {
+ PhysicsAnimator.SpringConfig(
+ SpringForce.STIFFNESS_MEDIUM,
+ SpringForce.DAMPING_RATIO_LOW_BOUNCY
+ )
+ }
+
+ private val controller by lazy { BubbleEducationController(context) }
+
+ /** Whether the education view is visible or being animated */
+ val isManageEducationVisible: Boolean
+ get() = educationView != null && rootView != null
+
+ /**
+ * Show manage bubble education if hasn't been shown before
+ *
+ * @param bubble the bubble used for the manage education check
+ * @param root the view to show manage education in
+ */
+ fun maybeShowManageEducation(bubble: BubbleViewProvider, root: ViewGroup) {
+ if (!controller.shouldShowManageEducation(bubble)) return
+ showManageEducation(root)
+ }
+
+ /**
+ * Hide the manage education view if visible
+ *
+ * @param animated whether should hide with animation
+ */
+ fun hideManageEducation(animated: Boolean) {
+ rootView?.let {
+ fun cleanUp() {
+ it.removeView(educationView)
+ rootView = null
+ listener.onManageEducationVisibilityChanged(isVisible = false)
+ }
+
+ if (animated) {
+ animateTransition(show = false, ::cleanUp)
+ } else {
+ cleanUp()
+ }
+ }
+ }
+
+ /**
+ * Show manage education with animation
+ *
+ * @param root the view to show manage education in
+ */
+ private fun showManageEducation(root: ViewGroup) {
+ hideManageEducation(animated = false)
+ if (educationView == null) {
+ val eduView = createEducationView(root)
+ educationView = eduView
+ animator = createAnimation(eduView)
+ }
+ root.addView(educationView)
+ rootView = root
+ animateTransition(show = true) {
+ controller.hasSeenManageEducation = true
+ listener.onManageEducationVisibilityChanged(isVisible = true)
+ }
+ }
+
+ /**
+ * Animate show/hide transition for the education view
+ *
+ * @param show whether to show or hide the view
+ * @param endActions a closure to be called when the animation completes
+ */
+ private fun animateTransition(show: Boolean, endActions: () -> Unit) {
+ animator?.let { animator ->
+ animator
+ .spring(DynamicAnimation.ALPHA, if (show) 1f else 0f)
+ .spring(DynamicAnimation.SCALE_X, if (show) 1f else EDU_SCALE_HIDDEN)
+ .spring(DynamicAnimation.SCALE_Y, if (show) 1f else EDU_SCALE_HIDDEN)
+ .withEndActions(endActions)
+ .start()
+ } ?: endActions()
+ }
+
+ private fun createEducationView(root: ViewGroup): BubblePopupView {
+ val view =
+ LayoutInflater.from(context).inflate(R.layout.bubble_bar_manage_education, root, false)
+ as BubblePopupView
+
+ return view.apply {
+ setup()
+ alpha = 0f
+ pivotY = 0f
+ scaleX = EDU_SCALE_HIDDEN
+ scaleY = EDU_SCALE_HIDDEN
+ doOnLayout { it.pivotX = it.width / 2f }
+ setOnClickListener { hideManageEducation(animated = true) }
+ }
+ }
+
+ private fun createAnimation(view: BubblePopupView): PhysicsAnimator<BubblePopupView> {
+ val animator = PhysicsAnimator.getInstance(view)
+ animator.setDefaultSpringConfig(springConfig)
+ return animator
+ }
+
+ companion object {
+ private const val EDU_SCALE_HIDDEN = 0.5f
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubblePopupDrawable.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubblePopupDrawable.kt
new file mode 100644
index 000000000000..8b5283d83683
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubblePopupDrawable.kt
@@ -0,0 +1,232 @@
+/*
+ * 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.wm.shell.common.bubbles
+
+import android.annotation.ColorInt
+import android.graphics.Canvas
+import android.graphics.ColorFilter
+import android.graphics.Matrix
+import android.graphics.Outline
+import android.graphics.Paint
+import android.graphics.Path
+import android.graphics.Rect
+import android.graphics.RectF
+import android.graphics.drawable.Drawable
+import kotlin.math.atan
+import kotlin.math.cos
+import kotlin.math.sin
+import kotlin.properties.Delegates
+
+/** A drawable for the [BubblePopupView] that draws a popup background with a directional arrow */
+class BubblePopupDrawable(private val config: Config) : Drawable() {
+ /** The direction of the arrow in the popup drawable */
+ enum class ArrowDirection {
+ UP,
+ DOWN
+ }
+
+ /** The arrow position on the side of the popup bubble */
+ sealed class ArrowPosition {
+ object Start : ArrowPosition()
+ object Center : ArrowPosition()
+ object End : ArrowPosition()
+ class Custom(val value: Float) : ArrowPosition()
+ }
+
+ /** The configuration for drawable features */
+ data class Config(
+ @ColorInt val color: Int,
+ val cornerRadius: Float,
+ val contentPadding: Int,
+ val arrowWidth: Float,
+ val arrowHeight: Float,
+ val arrowRadius: Float
+ )
+
+ /**
+ * The direction of the arrow in the popup drawable. It affects the content padding and requires
+ * it to be updated in the view.
+ */
+ var arrowDirection: ArrowDirection by
+ Delegates.observable(ArrowDirection.UP) { _, _, _ -> requestPathUpdate() }
+
+ /**
+ * Arrow position along the X axis and its direction. The position is adjusted to the content
+ * corner radius when applied so it doesn't go into rounded corner area
+ */
+ var arrowPosition: ArrowPosition by
+ Delegates.observable(ArrowPosition.Center) { _, _, _ -> requestPathUpdate() }
+
+ private val path = Path()
+ private val paint = Paint()
+ private var shouldUpdatePath = true
+
+ init {
+ paint.color = config.color
+ paint.style = Paint.Style.FILL
+ paint.isAntiAlias = true
+ }
+
+ override fun draw(canvas: Canvas) {
+ updatePathIfNeeded()
+ canvas.drawPath(path, paint)
+ }
+
+ override fun onBoundsChange(bounds: Rect?) {
+ requestPathUpdate()
+ }
+
+ /** Should be applied to the view padding if arrow direction changes */
+ override fun getPadding(padding: Rect): Boolean {
+ padding.set(
+ config.contentPadding,
+ config.contentPadding,
+ config.contentPadding,
+ config.contentPadding
+ )
+ when (arrowDirection) {
+ ArrowDirection.UP -> padding.top += config.arrowHeight.toInt()
+ ArrowDirection.DOWN -> padding.bottom += config.arrowHeight.toInt()
+ }
+ return true
+ }
+
+ override fun getOutline(outline: Outline) {
+ updatePathIfNeeded()
+ outline.setPath(path)
+ }
+
+ override fun getOpacity(): Int {
+ return paint.alpha
+ }
+
+ override fun setAlpha(alpha: Int) {
+ paint.alpha = alpha
+ }
+
+ override fun setColorFilter(colorFilter: ColorFilter?) {
+ paint.colorFilter = colorFilter
+ }
+
+ /** Schedules path update for the next redraw */
+ private fun requestPathUpdate() {
+ shouldUpdatePath = true
+ }
+
+ /** Updates the path if required, when bounds or arrow direction/position changes */
+ private fun updatePathIfNeeded() {
+ if (shouldUpdatePath) {
+ updatePath()
+ shouldUpdatePath = false
+ }
+ }
+
+ /** Updates the path value using the current bounds, config, arrow direction and position */
+ private fun updatePath() {
+ if (bounds.isEmpty) return
+ // Reset the path state
+ path.reset()
+ // The content rect where the filled rounded rect will be drawn
+ val contentRect = RectF(bounds)
+ when (arrowDirection) {
+ ArrowDirection.UP -> {
+ // Add rounded arrow pointing up to the path
+ addRoundedArrowPositioned(path, arrowPosition)
+ // Inset content rect by the arrow size from the top
+ contentRect.top += config.arrowHeight
+ }
+ ArrowDirection.DOWN -> {
+ val matrix = Matrix()
+ // Flip the path with the matrix to draw arrow pointing down
+ matrix.setScale(1f, -1f, bounds.width() / 2f, bounds.height() / 2f)
+ path.transform(matrix)
+ // Add rounded arrow with the flipped matrix applied, will point down
+ addRoundedArrowPositioned(path, arrowPosition)
+ // Restore the path matrix to the original state with inverted matrix
+ matrix.invert(matrix)
+ path.transform(matrix)
+ // Inset content rect by the arrow size from the bottom
+ contentRect.bottom -= config.arrowHeight
+ }
+ }
+ // Add the content area rounded rect
+ path.addRoundRect(contentRect, config.cornerRadius, config.cornerRadius, Path.Direction.CW)
+ }
+
+ /** Add a rounded arrow pointing up in the horizontal position on the canvas */
+ private fun addRoundedArrowPositioned(path: Path, position: ArrowPosition) {
+ val matrix = Matrix()
+ var translationX = positionValue(position) - config.arrowWidth / 2
+ // Offset to position between rounded corners of the content view
+ translationX = translationX.coerceIn(config.cornerRadius,
+ bounds.width() - config.cornerRadius - config.arrowWidth)
+ // Translate to add the arrow in the center horizontally
+ matrix.setTranslate(-translationX, 0f)
+ path.transform(matrix)
+ // Add rounded arrow
+ addRoundedArrow(path)
+ // Restore the path matrix to the original state with inverted matrix
+ matrix.invert(matrix)
+ path.transform(matrix)
+ }
+
+ /** Adds a rounded arrow pointing up to the path, can be flipped if needed */
+ private fun addRoundedArrow(path: Path) {
+ // Theta is half of the angle inside the triangle tip
+ val thetaTan = config.arrowWidth / (config.arrowHeight * 2f)
+ val theta = atan(thetaTan)
+ val thetaDeg = Math.toDegrees(theta.toDouble()).toFloat()
+ // The center Y value of the circle for the triangle tip
+ val tipCircleCenterY = config.arrowRadius / sin(theta)
+ // The length from triangle tip to intersection point with the circle
+ val tipIntersectionSideLength = config.arrowRadius / thetaTan
+ // The offset from the top to the point of intersection
+ val intersectionTopOffset = tipIntersectionSideLength * cos(theta)
+ // The offset from the center to the point of intersection
+ val intersectionCenterOffset = tipIntersectionSideLength * sin(theta)
+ // The center X of the triangle
+ val arrowCenterX = config.arrowWidth / 2f
+
+ // Set initial position in bottom left of the arrow
+ path.moveTo(0f, config.arrowHeight)
+ // Add the left side of the triangle
+ path.lineTo(arrowCenterX - intersectionCenterOffset, intersectionTopOffset)
+ // Add the arc from the left to the right side of the triangle
+ path.arcTo(
+ /* left = */ arrowCenterX - config.arrowRadius,
+ /* top = */ tipCircleCenterY - config.arrowRadius,
+ /* right = */ arrowCenterX + config.arrowRadius,
+ /* bottom = */ tipCircleCenterY + config.arrowRadius,
+ /* startAngle = */ 180 + thetaDeg,
+ /* sweepAngle = */ 180 - (2 * thetaDeg),
+ /* forceMoveTo = */ false
+ )
+ // Add the right side of the triangle
+ path.lineTo(config.arrowWidth, config.arrowHeight)
+ // Close the path
+ path.close()
+ }
+
+ /** The value of the arrow position provided the position and current bounds */
+ private fun positionValue(position: ArrowPosition): Float {
+ return when (position) {
+ is ArrowPosition.Start -> 0f
+ is ArrowPosition.Center -> bounds.width().toFloat() / 2f
+ is ArrowPosition.End -> bounds.width().toFloat()
+ is ArrowPosition.Custom -> position.value
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubblePopupView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubblePopupView.kt
new file mode 100644
index 000000000000..f8a4946bb5c5
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubblePopupView.kt
@@ -0,0 +1,65 @@
+/*
+ * 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.wm.shell.common.bubbles
+
+import android.content.Context
+import android.graphics.Rect
+import android.util.AttributeSet
+import android.widget.LinearLayout
+
+/** A popup container view that uses [BubblePopupDrawable] as a background */
+open class BubblePopupView
+@JvmOverloads
+constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyleAttr: Int = 0,
+ defStyleRes: Int = 0
+) : LinearLayout(context, attrs, defStyleAttr, defStyleRes) {
+ private var popupDrawable: BubblePopupDrawable? = null
+
+ /**
+ * Sets up the popup drawable with the config provided. Required to remove dependency on local
+ * resources
+ */
+ fun setupBackground(config: BubblePopupDrawable.Config) {
+ popupDrawable = BubblePopupDrawable(config)
+ background = popupDrawable
+ forceLayout()
+ }
+
+ /**
+ * Sets the arrow direction for the background drawable and updates the padding to fit the
+ * content inside of the popup drawable
+ */
+ fun setArrowDirection(direction: BubblePopupDrawable.ArrowDirection) {
+ popupDrawable?.let {
+ it.arrowDirection = direction
+ val padding = Rect()
+ if (it.getPadding(padding)) {
+ setPadding(padding.left, padding.top, padding.right, padding.bottom)
+ }
+ }
+ }
+
+ /** Sets the arrow position for the background drawable and triggers redraw */
+ fun setArrowPosition(position: BubblePopupDrawable.ArrowPosition) {
+ popupDrawable?.let {
+ it.arrowPosition = position
+ invalidate()
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
index 54f89846ac85..7bf0893c60c7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
@@ -230,8 +230,10 @@ public class CompatUIController implements OnDisplaysChangedListener,
// The user aspect ratio button should not be handled when a new TaskInfo is
// sent because of a double tap or when in multi-window mode.
if (taskInfo.getWindowingMode() != WINDOWING_MODE_FULLSCREEN) {
- mUserAspectRatioSettingsLayout.release();
- mUserAspectRatioSettingsLayout = null;
+ if (mUserAspectRatioSettingsLayout != null) {
+ mUserAspectRatioSettingsLayout.release();
+ mUserAspectRatioSettingsLayout = null;
+ }
return;
}
if (!taskInfo.isFromLetterboxDoubleTap) {
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index c3087bc1c0d2..e2f407294458 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -4739,6 +4739,97 @@ public class AudioManager {
/**
* @hide
+ * Test method to return the list of UIDs currently marked as ducked because of their
+ * audio focus status
+ * @return the list of UIDs, can be empty when no app is being ducked.
+ */
+ @TestApi
+ @RequiresPermission("android.permission.QUERY_AUDIO_STATE")
+ public @NonNull List<Integer> getFocusDuckedUidsForTest() {
+ try {
+ return getService().getFocusDuckedUidsForTest();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @hide
+ * Test method to return the duration of the fade out applied on the players of a focus loser
+ * @return the fade out duration in ms
+ */
+ @TestApi
+ @RequiresPermission("android.permission.QUERY_AUDIO_STATE")
+ public long getFocusFadeOutDurationForTest() {
+ try {
+ return getService().getFocusFadeOutDurationForTest();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @hide
+ * Test method to return the length of time after a fade-out before the focus loser is unmuted
+ * (and is faded back in).
+ * @return the time gap after a fade-out completion on focus loss, and fade-in start in ms.
+ */
+ @TestApi
+ @RequiresPermission("android.permission.QUERY_AUDIO_STATE")
+ public long getFocusUnmuteDelayAfterFadeOutForTest() {
+ try {
+ return getService().getFocusUnmuteDelayAfterFadeOutForTest();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @hide
+ * Test method to start preventing applications from requesting audio focus during a test,
+ * which could interfere with the functionality/behavior under test.
+ * Calling this method needs to be paired with a call to {@link #exitAudioFocusFreezeForTest}
+ * when the testing is done. If this is not the case (e.g. in case of a test crash),
+ * a death observer mechanism will ensure the system is not left in a bad state, but this should
+ * not be relied on when implementing tests.
+ * @param exemptedUids a list of UIDs that are exempt from the freeze. This would for instance
+ * be those of the test runner and other players used in the test, or the "fake" UIDs used
+ * for testing with {@link #requestAudioFocusForTest(AudioFocusRequest, String, int, int)}.
+ * @return true if the focus freeze mode is successfully entered, false if there was an issue,
+ * such as another freeze in place at the time of invocation.
+ * A false result should result in a test failure as this would indicate the system is not
+ * in a proper state with a predictable behavior for audio focus management.
+ */
+ @TestApi
+ @RequiresPermission("Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED")
+ public boolean enterAudioFocusFreezeForTest(@NonNull List<Integer> exemptedUids) {
+ Objects.requireNonNull(exemptedUids);
+ try {
+ final int[] uids = exemptedUids.stream().mapToInt(Integer::intValue).toArray();
+ return getService().enterAudioFocusFreezeForTest(mICallBack, uids);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @hide
+ * Test method to end preventing applications from requesting audio focus during a test.
+ * @return true if the focus freeze mode is successfully exited, false if there was an issue,
+ * such as the freeze already having ended, or not started.
+ */
+ @TestApi
+ @RequiresPermission("Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED")
+ public boolean exitAudioFocusFreezeForTest() {
+ try {
+ return getService().exitAudioFocusFreezeForTest(mICallBack);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @hide
* Request or lock audio focus.
* This method is to be used by system components that have registered an
* {@link android.media.audiopolicy.AudioPolicy} to request audio focus, but also to "lock" it
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 5cbb4e539d0a..e45ef404995c 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -529,6 +529,23 @@ interface IAudioService {
long getFadeOutDurationOnFocusLossMillis(in AudioAttributes aa);
+ @EnforcePermission("QUERY_AUDIO_STATE")
+ /* Returns a List<Integer> */
+ @SuppressWarnings(value = {"untyped-collection"})
+ List getFocusDuckedUidsForTest();
+
+ @EnforcePermission("QUERY_AUDIO_STATE")
+ long getFocusFadeOutDurationForTest();
+
+ @EnforcePermission("QUERY_AUDIO_STATE")
+ long getFocusUnmuteDelayAfterFadeOutForTest();
+
+ @EnforcePermission("MODIFY_AUDIO_SETTINGS_PRIVILEGED")
+ boolean enterAudioFocusFreezeForTest(IBinder cb, in int[] uids);
+
+ @EnforcePermission("MODIFY_AUDIO_SETTINGS_PRIVILEGED")
+ boolean exitAudioFocusFreezeForTest(IBinder cb);
+
void registerModeDispatcher(IAudioModeDispatcher dispatcher);
oneway void unregisterModeDispatcher(IAudioModeDispatcher dispatcher);
diff --git a/media/java/android/media/projection/IMediaProjectionManager.aidl b/media/java/android/media/projection/IMediaProjectionManager.aidl
index 304eecb9701a..d294601b44cc 100644
--- a/media/java/android/media/projection/IMediaProjectionManager.aidl
+++ b/media/java/android/media/projection/IMediaProjectionManager.aidl
@@ -108,6 +108,7 @@ interface IMediaProjectionManager {
+ ".permission.MANAGE_MEDIA_PROJECTION)")
void notifyActiveProjectionCapturedContentVisibilityChanged(boolean isVisible);
+ @EnforcePermission("MANAGE_MEDIA_PROJECTION")
@JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
+ ".permission.MANAGE_MEDIA_PROJECTION)")
void addCallback(IMediaProjectionWatcherCallback callback);
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/Keyboards.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/Keyboards.kt
index 3f7cc199aed8..b6500340be25 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/Keyboards.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/Keyboards.kt
@@ -21,7 +21,6 @@ import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.snapshotFlow
-import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
@@ -29,7 +28,6 @@ import kotlinx.coroutines.flow.filter
/**
* An action when run, hides the keyboard if it's open.
*/
-@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun hideKeyboardAction(): () -> Unit {
val keyboardController = LocalSoftwareKeyboardController.current
@@ -41,7 +39,6 @@ fun hideKeyboardAction(): () -> Unit {
*
* And when user scrolling the lazy list, hides the keyboard if it's open.
*/
-@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun rememberLazyListStateAndHideKeyboardWhenStartScroll(): LazyListState {
val listState = rememberLazyListState()
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/compose/KeyboardsTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/compose/KeyboardsTest.kt
index 944ef7fa33fb..e9b3109eefdd 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/compose/KeyboardsTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/compose/KeyboardsTest.kt
@@ -21,7 +21,6 @@ import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material3.Text
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.platform.SoftwareKeyboardController
@@ -32,12 +31,11 @@ import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
-import org.mockito.Mockito.never
-import org.mockito.Mockito.verify
import org.mockito.junit.MockitoJUnit
import org.mockito.junit.MockitoRule
+import org.mockito.kotlin.never
+import org.mockito.kotlin.verify
-@OptIn(ExperimentalComposeUiApi::class)
@RunWith(AndroidJUnit4::class)
class KeyboardsTest {
@get:Rule
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/theme/SettingsThemeTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/theme/SettingsThemeTest.kt
index 2ff3039eb197..bd8a54bfa4a3 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/theme/SettingsThemeTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/theme/SettingsThemeTest.kt
@@ -31,10 +31,10 @@ import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
-import org.mockito.Mockito.anyInt
import org.mockito.junit.MockitoJUnit
import org.mockito.junit.MockitoRule
-import org.mockito.Mockito.`when` as whenever
+import org.mockito.kotlin.any
+import org.mockito.kotlin.whenever
@RunWith(AndroidJUnit4::class)
class SettingsThemeTest {
@@ -55,7 +55,7 @@ class SettingsThemeTest {
@Before
fun setUp() {
whenever(context.resources).thenReturn(resources)
- whenever(resources.getString(anyInt())).thenReturn("")
+ whenever(resources.getString(any())).thenReturn("")
}
private fun mockAndroidConfig(configName: String, configValue: String) {
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/AnnotatedTextTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/AnnotatedTextTest.kt
index 2c218e3050e0..5e596201128b 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/AnnotatedTextTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/AnnotatedTextTest.kt
@@ -32,9 +32,9 @@ import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
-import org.mockito.Mockito.verify
import org.mockito.junit.MockitoJUnit
import org.mockito.junit.MockitoRule
+import org.mockito.kotlin.verify
@RunWith(AndroidJUnit4::class)
class AnnotatedTextTest {
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffoldTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffoldTest.kt
index 872d957a6a24..3e8fdecca810 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffoldTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffoldTest.kt
@@ -33,9 +33,9 @@ import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
-import org.mockito.Mockito.verify
import org.mockito.junit.MockitoJUnit
import org.mockito.junit.MockitoRule
+import org.mockito.kotlin.verify
@RunWith(AndroidJUnit4::class)
class SettingsScaffoldTest {
diff --git a/packages/SettingsLib/Spa/testutils/Android.bp b/packages/SettingsLib/Spa/testutils/Android.bp
index 65f5d34bd8d1..4031cd7f7a6f 100644
--- a/packages/SettingsLib/Spa/testutils/Android.bp
+++ b/packages/SettingsLib/Spa/testutils/Android.bp
@@ -30,7 +30,7 @@ android_library {
"androidx.compose.ui_ui-test-junit4",
"androidx.compose.ui_ui-test-manifest",
"androidx.lifecycle_lifecycle-runtime-testing",
- "mockito",
+ "mockito-kotlin2",
"truth-prebuilt",
],
kotlincflags: [
diff --git a/packages/SettingsLib/Spa/testutils/build.gradle.kts b/packages/SettingsLib/Spa/testutils/build.gradle.kts
index f5a22c9fbb5d..50243dcd8c9b 100644
--- a/packages/SettingsLib/Spa/testutils/build.gradle.kts
+++ b/packages/SettingsLib/Spa/testutils/build.gradle.kts
@@ -41,7 +41,12 @@ dependencies {
api("androidx.arch.core:core-testing:2.2.0-alpha01")
api("androidx.compose.ui:ui-test-junit4:$jetpackComposeVersion")
api("androidx.lifecycle:lifecycle-runtime-testing")
+ api("org.mockito.kotlin:mockito-kotlin:5.1.0")
+ api("org.mockito:mockito-core") {
+ version {
+ strictly("2.28.2")
+ }
+ }
api(libs.truth)
- api("org.mockito:mockito-core:2.21.0")
debugApi("androidx.compose.ui:ui-test-manifest:$jetpackComposeVersion")
}
diff --git a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt
index f005bab55de6..fae9fec0c26d 100644
--- a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt
+++ b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt
@@ -33,8 +33,7 @@ import javax.inject.Named
SettingsUtilModule::class,
])
abstract class FlagsModule {
- @Binds
- abstract fun bindsFeatureFlagDebug(impl: FeatureFlagsDebug): FeatureFlags
+ @Binds abstract fun bindsFeatureFlagDebug(impl: FeatureFlagsClassicDebug): FeatureFlagsClassic
@Binds
@IntoSet
diff --git a/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt b/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt
index 927d4604b823..7aacb4efba8e 100644
--- a/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt
+++ b/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt
@@ -29,7 +29,7 @@ import javax.inject.Named
])
abstract class FlagsModule {
@Binds
- abstract fun bindsFeatureFlagRelease(impl: FeatureFlagsRelease): FeatureFlags
+ abstract fun bindsFeatureFlagRelease(impl: FeatureFlagsClassicRelease): FeatureFlagsClassic
@Binds
@IntoSet
diff --git a/packages/SystemUI/src/com/android/systemui/flags/ConditionalRestarter.kt b/packages/SystemUI/src/com/android/systemui/flags/ConditionalRestarter.kt
index b20e33a63776..83c239f169ef 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/ConditionalRestarter.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/ConditionalRestarter.kt
@@ -44,12 +44,12 @@ constructor(
private var androidRestartRequested = false
override fun restartSystemUI(reason: String) {
- Log.d(FeatureFlagsDebug.TAG, "SystemUI Restart requested. Restarting when idle.")
+ Log.d(FeatureFlagsClassicDebug.TAG, "SystemUI Restart requested. Restarting when idle.")
scheduleRestart(reason)
}
override fun restartAndroid(reason: String) {
- Log.d(FeatureFlagsDebug.TAG, "Android Restart requested. Restarting when idle.")
+ Log.d(FeatureFlagsClassicDebug.TAG, "Android Restart requested. Restarting when idle.")
androidRestartRequested = true
scheduleRestart(reason)
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.kt b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.kt
index 95e7ad969e35..d48eb2927a14 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.kt
@@ -23,6 +23,21 @@ import android.util.Dumpable
*
* See [Flags] for instructions on defining new flags.
*/
+interface FeatureFlagsClassic : FeatureFlags
+
+/**
+ * Class to manage simple DeviceConfig-based feature flags.
+ *
+ * See [Flags] for instructions on defining new flags.
+ */
+@Deprecated(
+ message = "Use FeatureFlagsClassic instead.",
+ replaceWith =
+ ReplaceWith(
+ "FeatureFlagsClassic",
+ "com.android.systemui.flags.FeatureFlagsClassic",
+ ),
+)
interface FeatureFlags : FlagListenable, Dumpable {
/** Returns a boolean value for the given flag. */
fun isEnabled(flag: UnreleasedFlag): Boolean
@@ -47,4 +62,4 @@ interface FeatureFlags : FlagListenable, Dumpable {
/** Returns an int value for a given flag/ */
fun getInt(flag: ResourceIntFlag): Int
-}
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsClassicDebug.java
index 4c78e4ce4fe4..126a1b5e7115 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsClassicDebug.java
@@ -67,7 +67,7 @@ import javax.inject.Named;
* To restore a flag back to its default, leave the `--ez value <0|1>` off of the command.
*/
@SysUISingleton
-public class FeatureFlagsDebug implements FeatureFlags {
+public class FeatureFlagsClassicDebug implements FeatureFlagsClassic {
static final String TAG = "SysUIFlags";
private final FlagManager mFlagManager;
@@ -116,7 +116,7 @@ public class FeatureFlagsDebug implements FeatureFlags {
};
@Inject
- public FeatureFlagsDebug(
+ public FeatureFlagsClassicDebug(
FlagManager flagManager,
Context context,
GlobalSettings globalSettings,
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsClassicRelease.java
index e03b43873e05..79ebf9c7d8df 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsClassicRelease.java
@@ -43,7 +43,7 @@ import javax.inject.Named;
* how to set flags.
*/
@SysUISingleton
-public class FeatureFlagsRelease implements FeatureFlags {
+public class FeatureFlagsClassicRelease implements FeatureFlagsClassic {
static final String TAG = "SysUIFlags";
private final Resources mResources;
@@ -89,7 +89,7 @@ public class FeatureFlagsRelease implements FeatureFlags {
};
@Inject
- public FeatureFlagsRelease(
+ public FeatureFlagsClassicRelease(
@Main Resources resources,
SystemPropertiesHelper systemProperties,
ServerFlagReader serverFlagReader,
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt
index 28c45b874b24..dd560b797c7e 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt
@@ -34,13 +34,13 @@ constructor(
dumpManager: DumpManager,
private val commandRegistry: CommandRegistry,
private val flagCommand: FlagCommand,
- private val featureFlags: FeatureFlagsDebug,
+ private val featureFlags: FeatureFlagsClassicDebug,
private val broadcastSender: BroadcastSender,
private val initializationChecker: InitializationChecker,
) : CoreStartable {
init {
- dumpManager.registerCriticalDumpable(FeatureFlagsDebug.TAG) { pw, args ->
+ dumpManager.registerCriticalDumpable(FeatureFlagsClassicDebug.TAG) { pw, args ->
featureFlags.dump(pw, args)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseStartable.kt b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseStartable.kt
index f97112d384be..dfcc7ead11d0 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseStartable.kt
@@ -32,7 +32,7 @@ constructor(
) : CoreStartable {
init {
- dumpManager.registerCriticalDumpable(FeatureFlagsRelease.TAG) { pw, args ->
+ dumpManager.registerCriticalDumpable(FeatureFlagsClassicRelease.TAG) { pw, args ->
featureFlags.dump(pw, args)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagCommand.java b/packages/SystemUI/src/com/android/systemui/flags/FlagCommand.java
index bd0ed482c961..e3cc2b02177c 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FlagCommand.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagCommand.java
@@ -38,12 +38,12 @@ public class FlagCommand implements Command {
private final List<String> mOnCommands = List.of("true", "on", "1", "enabled");
private final List<String> mOffCommands = List.of("false", "off", "0", "disable");
private final List<String> mSetCommands = List.of("set", "put");
- private final FeatureFlagsDebug mFeatureFlags;
+ private final FeatureFlagsClassicDebug mFeatureFlags;
private final Map<String, Flag<?>> mAllFlags;
@Inject
FlagCommand(
- FeatureFlagsDebug featureFlags,
+ FeatureFlagsClassicDebug featureFlags,
@Named(ALL_FLAGS) Map<String, Flag<?>> allFlags
) {
mFeatureFlags = featureFlags;
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index f0a048b69ca9..90e78c608089 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -33,7 +33,7 @@ import com.android.systemui.flags.FlagsFactory.unreleasedFlag
* On public release builds, flags will always return their default value. There is no way to change
* their value on release builds.
*
- * See [FeatureFlagsDebug] for instructions on flipping the flags via adb.
+ * See [FeatureFlagsClassicDebug] for instructions on flipping the flags via adb.
*/
object Flags {
@JvmField val TEAMFOOD = unreleasedFlag("teamfood")
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagsCommonModule.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagsCommonModule.kt
index 3c5012559a89..d13fa1eb7628 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FlagsCommonModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagsCommonModule.kt
@@ -25,6 +25,8 @@ import javax.inject.Named
interface FlagsCommonModule {
@Binds fun bindsRestarter(impl: ConditionalRestarter): Restarter
+ @Binds fun bindsClassic(impl: FeatureFlagsClassic): FeatureFlags
+
companion object {
const val ALL_FLAGS = "all_flags"
diff --git a/packages/SystemUI/src/com/android/systemui/flags/SystemExitRestarter.kt b/packages/SystemUI/src/com/android/systemui/flags/SystemExitRestarter.kt
index 46e28a7d0ef2..9f41b61c6f4d 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/SystemExitRestarter.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/SystemExitRestarter.kt
@@ -26,12 +26,12 @@ constructor(
private val barService: IStatusBarService,
) : Restarter {
override fun restartAndroid(reason: String) {
- Log.d(FeatureFlagsDebug.TAG, "Restarting Android: " + reason)
+ Log.d(FeatureFlagsClassicDebug.TAG, "Restarting Android: " + reason)
barService.restart()
}
override fun restartSystemUI(reason: String) {
- Log.d(FeatureFlagsDebug.TAG, "Restarting SystemUI: " + reason)
+ Log.d(FeatureFlagsClassicDebug.TAG, "Restarting SystemUI: " + reason)
System.exit(0)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepository.kt
index 059f72b9f708..7234757081e2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepository.kt
@@ -17,7 +17,10 @@
package com.android.systemui.keyguard.data.repository
+import android.view.View
+import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
+import androidx.core.view.children
import com.android.systemui.common.ui.data.repository.ConfigurationRepository
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
@@ -96,8 +99,16 @@ constructor(
/** Determines the constraints for the ConstraintSet in the lockscreen root view. */
interface KeyguardBlueprint {
val id: String
+ val shouldRemoveUnconstrainedViews: Boolean
+ get() = true
- fun apply(constraintSet: ConstraintSet)
+ fun apply(constraintLayout: ConstraintSet)
+ fun removeUnConstrainedViews(constraintLayout: ConstraintLayout, constraintSet: ConstraintSet) {
+ constraintLayout.children
+ .map { it.id }
+ .filterNot { constraintSet.knownIds.contains(it) }
+ .forEach { constraintSet.setVisibility(it, View.GONE) }
+ }
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
index ed4dd6a15c18..e40c2793176e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
@@ -43,6 +43,7 @@ class KeyguardBlueprintViewBinder {
val emptyLayout = ConstraintSet.Layout()
knownIds.forEach { getConstraint(it).layout.copyFrom(emptyLayout) }
blueprint?.apply(this)
+ blueprint?.removeUnConstrainedViews(constraintLayout, this)
applyTo(constraintLayout)
}
Trace.endSection()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt
index ff15cb39b640..14c5ec0361f6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt
@@ -29,6 +29,10 @@ import com.android.systemui.util.mockito.nullable
import com.android.systemui.util.mockito.withArgCaptor
import com.android.systemui.util.settings.GlobalSettings
import com.google.common.truth.Truth.assertThat
+import java.io.PrintWriter
+import java.io.Serializable
+import java.io.StringWriter
+import java.util.function.Consumer
import org.junit.Assert
import org.junit.Before
import org.junit.Test
@@ -41,44 +45,30 @@ import org.mockito.Mockito.never
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
-import org.mockito.MockitoAnnotations
-import java.io.PrintWriter
-import java.io.Serializable
-import java.io.StringWriter
-import java.util.function.Consumer
import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
/**
* NOTE: This test is for the version of FeatureFlagManager in src-debug, which allows overriding
* the default.
*/
@SmallTest
-class FeatureFlagsDebugTest : SysuiTestCase() {
- private lateinit var featureFlagsDebug: FeatureFlagsDebug
-
- @Mock
- private lateinit var flagManager: FlagManager
- @Mock
- private lateinit var mockContext: Context
- @Mock
- private lateinit var globalSettings: GlobalSettings
- @Mock
- private lateinit var systemProperties: SystemPropertiesHelper
- @Mock
- private lateinit var resources: Resources
- @Mock
- private lateinit var restarter: Restarter
+class FeatureFlagsClassicDebugTest : SysuiTestCase() {
+ private lateinit var mFeatureFlagsClassicDebug: FeatureFlagsClassicDebug
+
+ @Mock private lateinit var flagManager: FlagManager
+ @Mock private lateinit var mockContext: Context
+ @Mock private lateinit var globalSettings: GlobalSettings
+ @Mock private lateinit var systemProperties: SystemPropertiesHelper
+ @Mock private lateinit var resources: Resources
+ @Mock private lateinit var restarter: Restarter
private val flagMap = mutableMapOf<String, Flag<*>>()
private lateinit var broadcastReceiver: BroadcastReceiver
private lateinit var clearCacheAction: Consumer<String>
private val serverFlagReader = ServerFlagReaderFake()
- private val teamfoodableFlagA = UnreleasedFlag(
- name = "a", namespace = "test", teamfood = true
- )
- private val teamfoodableFlagB = ReleasedFlag(
- name = "b", namespace = "test", teamfood = true
- )
+ private val teamfoodableFlagA = UnreleasedFlag(name = "a", namespace = "test", teamfood = true)
+ private val teamfoodableFlagB = ReleasedFlag(name = "b", namespace = "test", teamfood = true)
@Before
fun setup() {
@@ -86,27 +76,23 @@ class FeatureFlagsDebugTest : SysuiTestCase() {
flagMap.put(Flags.TEAMFOOD.name, Flags.TEAMFOOD)
flagMap.put(teamfoodableFlagA.name, teamfoodableFlagA)
flagMap.put(teamfoodableFlagB.name, teamfoodableFlagB)
- featureFlagsDebug = FeatureFlagsDebug(
- flagManager,
- mockContext,
- globalSettings,
- systemProperties,
- resources,
- serverFlagReader,
- flagMap,
- restarter
- )
- featureFlagsDebug.init()
+ mFeatureFlagsClassicDebug =
+ FeatureFlagsClassicDebug(
+ flagManager,
+ mockContext,
+ globalSettings,
+ systemProperties,
+ resources,
+ serverFlagReader,
+ flagMap,
+ restarter
+ )
+ mFeatureFlagsClassicDebug.init()
verify(flagManager).onSettingsChangedAction = any()
broadcastReceiver = withArgCaptor {
- verify(mockContext).registerReceiver(
- capture(), any(), nullable(), nullable(),
- any()
- )
- }
- clearCacheAction = withArgCaptor {
- verify(flagManager).clearCacheAction = capture()
+ verify(mockContext).registerReceiver(capture(), any(), nullable(), nullable(), any())
}
+ clearCacheAction = withArgCaptor { verify(flagManager).clearCacheAction = capture() }
whenever(flagManager.nameToSettingsKey(any())).thenAnswer { "key-${it.arguments[0]}" }
}
@@ -117,45 +103,29 @@ class FeatureFlagsDebugTest : SysuiTestCase() {
whenever(flagManager.readFlagValue<Boolean>(eq("4"), any())).thenReturn(false)
assertThat(
- featureFlagsDebug.isEnabled(
- ReleasedFlag(
- name = "2",
- namespace = "test"
- )
+ mFeatureFlagsClassicDebug.isEnabled(ReleasedFlag(name = "2", namespace = "test"))
)
- ).isTrue()
+ .isTrue()
assertThat(
- featureFlagsDebug.isEnabled(
- UnreleasedFlag(
- name = "3",
- namespace = "test"
- )
+ mFeatureFlagsClassicDebug.isEnabled(UnreleasedFlag(name = "3", namespace = "test"))
)
- ).isTrue()
+ .isTrue()
assertThat(
- featureFlagsDebug.isEnabled(
- ReleasedFlag(
- name = "4",
- namespace = "test"
- )
+ mFeatureFlagsClassicDebug.isEnabled(ReleasedFlag(name = "4", namespace = "test"))
)
- ).isFalse()
+ .isFalse()
assertThat(
- featureFlagsDebug.isEnabled(
- UnreleasedFlag(
- name = "5",
- namespace = "test"
- )
+ mFeatureFlagsClassicDebug.isEnabled(UnreleasedFlag(name = "5", namespace = "test"))
)
- ).isFalse()
+ .isFalse()
}
@Test
fun teamFoodFlag_False() {
- whenever(flagManager.readFlagValue<Boolean>(
- eq(Flags.TEAMFOOD.name), any())).thenReturn(false)
- assertThat(featureFlagsDebug.isEnabled(teamfoodableFlagA)).isFalse()
- assertThat(featureFlagsDebug.isEnabled(teamfoodableFlagB)).isTrue()
+ whenever(flagManager.readFlagValue<Boolean>(eq(Flags.TEAMFOOD.name), any()))
+ .thenReturn(false)
+ assertThat(mFeatureFlagsClassicDebug.isEnabled(teamfoodableFlagA)).isFalse()
+ assertThat(mFeatureFlagsClassicDebug.isEnabled(teamfoodableFlagB)).isTrue()
// Regular boolean flags should still test the same.
// Only our teamfoodableFlag should change.
@@ -164,10 +134,10 @@ class FeatureFlagsDebugTest : SysuiTestCase() {
@Test
fun teamFoodFlag_True() {
- whenever(flagManager.readFlagValue<Boolean>(
- eq(Flags.TEAMFOOD.name), any())).thenReturn(true)
- assertThat(featureFlagsDebug.isEnabled(teamfoodableFlagA)).isTrue()
- assertThat(featureFlagsDebug.isEnabled(teamfoodableFlagB)).isTrue()
+ whenever(flagManager.readFlagValue<Boolean>(eq(Flags.TEAMFOOD.name), any()))
+ .thenReturn(true)
+ assertThat(mFeatureFlagsClassicDebug.isEnabled(teamfoodableFlagA)).isTrue()
+ assertThat(mFeatureFlagsClassicDebug.isEnabled(teamfoodableFlagB)).isTrue()
// Regular boolean flags should still test the same.
// Only our teamfoodableFlag should change.
@@ -180,10 +150,10 @@ class FeatureFlagsDebugTest : SysuiTestCase() {
.thenReturn(true)
whenever(flagManager.readFlagValue<Boolean>(eq(teamfoodableFlagB.name), any()))
.thenReturn(false)
- whenever(flagManager.readFlagValue<Boolean>(
- eq(Flags.TEAMFOOD.name), any())).thenReturn(true)
- assertThat(featureFlagsDebug.isEnabled(teamfoodableFlagA)).isTrue()
- assertThat(featureFlagsDebug.isEnabled(teamfoodableFlagB)).isFalse()
+ whenever(flagManager.readFlagValue<Boolean>(eq(Flags.TEAMFOOD.name), any()))
+ .thenReturn(true)
+ assertThat(mFeatureFlagsClassicDebug.isEnabled(teamfoodableFlagA)).isTrue()
+ assertThat(mFeatureFlagsClassicDebug.isEnabled(teamfoodableFlagB)).isFalse()
// Regular boolean flags should still test the same.
// Only our teamfoodableFlag should change.
@@ -201,25 +171,20 @@ class FeatureFlagsDebugTest : SysuiTestCase() {
whenever(flagManager.readFlagValue<Boolean>(eq("3"), any())).thenReturn(true)
whenever(flagManager.readFlagValue<Boolean>(eq("5"), any())).thenReturn(false)
- assertThat(
- featureFlagsDebug.isEnabled(
- ResourceBooleanFlag(
- "1",
- "test",
- 1001
- )
- )
- ).isFalse()
- assertThat(featureFlagsDebug.isEnabled(ResourceBooleanFlag("2", "test", 1002))).isTrue()
- assertThat(featureFlagsDebug.isEnabled(ResourceBooleanFlag("3", "test", 1003))).isTrue()
+ assertThat(mFeatureFlagsClassicDebug.isEnabled(ResourceBooleanFlag("1", "test", 1001)))
+ .isFalse()
+ assertThat(mFeatureFlagsClassicDebug.isEnabled(ResourceBooleanFlag("2", "test", 1002)))
+ .isTrue()
+ assertThat(mFeatureFlagsClassicDebug.isEnabled(ResourceBooleanFlag("3", "test", 1003)))
+ .isTrue()
Assert.assertThrows(NameNotFoundException::class.java) {
- featureFlagsDebug.isEnabled(ResourceBooleanFlag("4", "test", 1004))
+ mFeatureFlagsClassicDebug.isEnabled(ResourceBooleanFlag("4", "test", 1004))
}
// Test that resource is loaded (and validated) even when the setting is set.
// This prevents developers from not noticing when they reference an invalid resource.
Assert.assertThrows(NameNotFoundException::class.java) {
- featureFlagsDebug.isEnabled(ResourceBooleanFlag("5", "test", 1005))
+ mFeatureFlagsClassicDebug.isEnabled(ResourceBooleanFlag("5", "test", 1005))
}
}
@@ -232,29 +197,27 @@ class FeatureFlagsDebugTest : SysuiTestCase() {
return@thenAnswer it.getArgument(1)
}
- assertThat(featureFlagsDebug.isEnabled(SysPropBooleanFlag("a", "test"))).isFalse()
- assertThat(featureFlagsDebug.isEnabled(SysPropBooleanFlag("b", "test"))).isTrue()
- assertThat(featureFlagsDebug.isEnabled(SysPropBooleanFlag("c", "test", true))).isTrue()
- assertThat(
- featureFlagsDebug.isEnabled(
- SysPropBooleanFlag(
- "d",
- "test",
- false
- )
- )
- ).isFalse()
- assertThat(featureFlagsDebug.isEnabled(SysPropBooleanFlag("e", "test"))).isFalse()
+ assertThat(mFeatureFlagsClassicDebug.isEnabled(SysPropBooleanFlag("a", "test"))).isFalse()
+ assertThat(mFeatureFlagsClassicDebug.isEnabled(SysPropBooleanFlag("b", "test"))).isTrue()
+ assertThat(mFeatureFlagsClassicDebug.isEnabled(SysPropBooleanFlag("c", "test", true)))
+ .isTrue()
+ assertThat(mFeatureFlagsClassicDebug.isEnabled(SysPropBooleanFlag("d", "test", false)))
+ .isFalse()
+ assertThat(mFeatureFlagsClassicDebug.isEnabled(SysPropBooleanFlag("e", "test"))).isFalse()
}
@Test
fun readStringFlag() {
whenever(flagManager.readFlagValue<String>(eq("3"), any())).thenReturn("foo")
whenever(flagManager.readFlagValue<String>(eq("4"), any())).thenReturn("bar")
- assertThat(featureFlagsDebug.getString(StringFlag("1", "test", "biz"))).isEqualTo("biz")
- assertThat(featureFlagsDebug.getString(StringFlag("2", "test", "baz"))).isEqualTo("baz")
- assertThat(featureFlagsDebug.getString(StringFlag("3", "test", "buz"))).isEqualTo("foo")
- assertThat(featureFlagsDebug.getString(StringFlag("4", "test", "buz"))).isEqualTo("bar")
+ assertThat(mFeatureFlagsClassicDebug.getString(StringFlag("1", "test", "biz")))
+ .isEqualTo("biz")
+ assertThat(mFeatureFlagsClassicDebug.getString(StringFlag("2", "test", "baz")))
+ .isEqualTo("baz")
+ assertThat(mFeatureFlagsClassicDebug.getString(StringFlag("3", "test", "buz")))
+ .isEqualTo("foo")
+ assertThat(mFeatureFlagsClassicDebug.getString(StringFlag("4", "test", "buz")))
+ .isEqualTo("bar")
}
@Test
@@ -270,44 +233,23 @@ class FeatureFlagsDebugTest : SysuiTestCase() {
whenever(flagManager.readFlagValue<String>(eq("4"), any())).thenReturn("override4")
whenever(flagManager.readFlagValue<String>(eq("6"), any())).thenReturn("override6")
- assertThat(
- featureFlagsDebug.getString(
- ResourceStringFlag(
- "1",
- "test",
- 1001
- )
- )
- ).isEqualTo("")
- assertThat(
- featureFlagsDebug.getString(
- ResourceStringFlag(
- "2",
- "test",
- 1002
- )
- )
- ).isEqualTo("resource2")
- assertThat(
- featureFlagsDebug.getString(
- ResourceStringFlag(
- "3",
- "test",
- 1003
- )
- )
- ).isEqualTo("override3")
+ assertThat(mFeatureFlagsClassicDebug.getString(ResourceStringFlag("1", "test", 1001)))
+ .isEqualTo("")
+ assertThat(mFeatureFlagsClassicDebug.getString(ResourceStringFlag("2", "test", 1002)))
+ .isEqualTo("resource2")
+ assertThat(mFeatureFlagsClassicDebug.getString(ResourceStringFlag("3", "test", 1003)))
+ .isEqualTo("override3")
Assert.assertThrows(NullPointerException::class.java) {
- featureFlagsDebug.getString(ResourceStringFlag("4", "test", 1004))
+ mFeatureFlagsClassicDebug.getString(ResourceStringFlag("4", "test", 1004))
}
Assert.assertThrows(NameNotFoundException::class.java) {
- featureFlagsDebug.getString(ResourceStringFlag("5", "test", 1005))
+ mFeatureFlagsClassicDebug.getString(ResourceStringFlag("5", "test", 1005))
}
// Test that resource is loaded (and validated) even when the setting is set.
// This prevents developers from not noticing when they reference an invalid resource.
Assert.assertThrows(NameNotFoundException::class.java) {
- featureFlagsDebug.getString(ResourceStringFlag("6", "test", 1005))
+ mFeatureFlagsClassicDebug.getString(ResourceStringFlag("6", "test", 1005))
}
}
@@ -315,10 +257,10 @@ class FeatureFlagsDebugTest : SysuiTestCase() {
fun readIntFlag() {
whenever(flagManager.readFlagValue<Int>(eq("3"), any())).thenReturn(22)
whenever(flagManager.readFlagValue<Int>(eq("4"), any())).thenReturn(48)
- assertThat(featureFlagsDebug.getInt(IntFlag("1", "test", 12))).isEqualTo(12)
- assertThat(featureFlagsDebug.getInt(IntFlag("2", "test", 93))).isEqualTo(93)
- assertThat(featureFlagsDebug.getInt(IntFlag("3", "test", 8))).isEqualTo(22)
- assertThat(featureFlagsDebug.getInt(IntFlag("4", "test", 234))).isEqualTo(48)
+ assertThat(mFeatureFlagsClassicDebug.getInt(IntFlag("1", "test", 12))).isEqualTo(12)
+ assertThat(mFeatureFlagsClassicDebug.getInt(IntFlag("2", "test", 93))).isEqualTo(93)
+ assertThat(mFeatureFlagsClassicDebug.getInt(IntFlag("3", "test", 8))).isEqualTo(22)
+ assertThat(mFeatureFlagsClassicDebug.getInt(IntFlag("4", "test", 234))).isEqualTo(48)
}
@Test
@@ -334,17 +276,20 @@ class FeatureFlagsDebugTest : SysuiTestCase() {
whenever(flagManager.readFlagValue<Int>(eq("4"), any())).thenReturn(500)
whenever(flagManager.readFlagValue<Int>(eq("5"), any())).thenReturn(9519)
- assertThat(featureFlagsDebug.getInt(ResourceIntFlag("1", "test", 1001))).isEqualTo(88)
- assertThat(featureFlagsDebug.getInt(ResourceIntFlag("2", "test", 1002))).isEqualTo(61)
- assertThat(featureFlagsDebug.getInt(ResourceIntFlag("3", "test", 1003))).isEqualTo(20)
+ assertThat(mFeatureFlagsClassicDebug.getInt(ResourceIntFlag("1", "test", 1001)))
+ .isEqualTo(88)
+ assertThat(mFeatureFlagsClassicDebug.getInt(ResourceIntFlag("2", "test", 1002)))
+ .isEqualTo(61)
+ assertThat(mFeatureFlagsClassicDebug.getInt(ResourceIntFlag("3", "test", 1003)))
+ .isEqualTo(20)
Assert.assertThrows(NotFoundException::class.java) {
- featureFlagsDebug.getInt(ResourceIntFlag("4", "test", 1004))
+ mFeatureFlagsClassicDebug.getInt(ResourceIntFlag("4", "test", 1004))
}
// Test that resource is loaded (and validated) even when the setting is set.
// This prevents developers from not noticing when they reference an invalid resource.
Assert.assertThrows(NotFoundException::class.java) {
- featureFlagsDebug.getInt(ResourceIntFlag("5", "test", 1005))
+ mFeatureFlagsClassicDebug.getInt(ResourceIntFlag("5", "test", 1005))
}
}
@@ -424,11 +369,11 @@ class FeatureFlagsDebugTest : SysuiTestCase() {
whenever(flagManager.readFlagValue<String>(eq("1"), any())).thenReturn("original")
// gets the flag & cache it
- assertThat(featureFlagsDebug.getString(flag1)).isEqualTo("original")
+ assertThat(mFeatureFlagsClassicDebug.getString(flag1)).isEqualTo("original")
verify(flagManager, times(1)).readFlagValue(eq("1"), eq(StringFlagSerializer))
// hit the cache
- assertThat(featureFlagsDebug.getString(flag1)).isEqualTo("original")
+ assertThat(mFeatureFlagsClassicDebug.getString(flag1)).isEqualTo("original")
verifyNoMoreInteractions(flagManager)
// set the flag
@@ -436,7 +381,7 @@ class FeatureFlagsDebugTest : SysuiTestCase() {
verifyPutData("1", "{\"type\":\"string\",\"value\":\"new\"}", numReads = 2)
whenever(flagManager.readFlagValue<String>(eq("1"), any())).thenReturn("new")
- assertThat(featureFlagsDebug.getString(flag1)).isEqualTo("new")
+ assertThat(mFeatureFlagsClassicDebug.getString(flag1)).isEqualTo("new")
verify(flagManager, times(3)).readFlagValue(eq("1"), eq(StringFlagSerializer))
}
@@ -446,7 +391,7 @@ class FeatureFlagsDebugTest : SysuiTestCase() {
serverFlagReader.setFlagValue(flag.namespace, flag.name, false)
- assertThat(featureFlagsDebug.isEnabled(flag)).isFalse()
+ assertThat(mFeatureFlagsClassicDebug.isEnabled(flag)).isFalse()
}
@Test
@@ -454,32 +399,41 @@ class FeatureFlagsDebugTest : SysuiTestCase() {
val flag = UnreleasedFlag(name = "100", namespace = "test")
serverFlagReader.setFlagValue(flag.namespace, flag.name, true)
- assertThat(featureFlagsDebug.isEnabled(flag)).isTrue()
+ assertThat(mFeatureFlagsClassicDebug.isEnabled(flag)).isTrue()
}
@Test
fun serverSide_OverrideUncached_NoRestart() {
// No one has read the flag, so it's not in the cache.
serverFlagReader.setFlagValue(
- teamfoodableFlagA.namespace, teamfoodableFlagA.name, !teamfoodableFlagA.default)
+ teamfoodableFlagA.namespace,
+ teamfoodableFlagA.name,
+ !teamfoodableFlagA.default
+ )
verify(restarter, never()).restartSystemUI(anyString())
}
@Test
fun serverSide_Override_Restarts() {
// Read it to put it in the cache.
- featureFlagsDebug.isEnabled(teamfoodableFlagA)
+ mFeatureFlagsClassicDebug.isEnabled(teamfoodableFlagA)
serverFlagReader.setFlagValue(
- teamfoodableFlagA.namespace, teamfoodableFlagA.name, !teamfoodableFlagA.default)
+ teamfoodableFlagA.namespace,
+ teamfoodableFlagA.name,
+ !teamfoodableFlagA.default
+ )
verify(restarter).restartSystemUI(anyString())
}
@Test
fun serverSide_RedundantOverride_NoRestart() {
// Read it to put it in the cache.
- featureFlagsDebug.isEnabled(teamfoodableFlagA)
+ mFeatureFlagsClassicDebug.isEnabled(teamfoodableFlagA)
serverFlagReader.setFlagValue(
- teamfoodableFlagA.namespace, teamfoodableFlagA.name, teamfoodableFlagA.default)
+ teamfoodableFlagA.namespace,
+ teamfoodableFlagA.name,
+ teamfoodableFlagA.default
+ )
verify(restarter, never()).restartSystemUI(anyString())
}
@@ -500,13 +454,13 @@ class FeatureFlagsDebugTest : SysuiTestCase() {
.thenReturn("override7")
// WHEN the flags have been accessed
- assertThat(featureFlagsDebug.isEnabled(flag1)).isTrue()
- assertThat(featureFlagsDebug.isEnabled(flag2)).isTrue()
- assertThat(featureFlagsDebug.isEnabled(flag3)).isFalse()
- assertThat(featureFlagsDebug.getString(flag4)).isEmpty()
- assertThat(featureFlagsDebug.getString(flag5)).isEqualTo("flag5default")
- assertThat(featureFlagsDebug.getString(flag6)).isEqualTo("resource1006")
- assertThat(featureFlagsDebug.getString(flag7)).isEqualTo("override7")
+ assertThat(mFeatureFlagsClassicDebug.isEnabled(flag1)).isTrue()
+ assertThat(mFeatureFlagsClassicDebug.isEnabled(flag2)).isTrue()
+ assertThat(mFeatureFlagsClassicDebug.isEnabled(flag3)).isFalse()
+ assertThat(mFeatureFlagsClassicDebug.getString(flag4)).isEmpty()
+ assertThat(mFeatureFlagsClassicDebug.getString(flag5)).isEqualTo("flag5default")
+ assertThat(mFeatureFlagsClassicDebug.getString(flag6)).isEqualTo("resource1006")
+ assertThat(mFeatureFlagsClassicDebug.getString(flag7)).isEqualTo("override7")
// THEN the dump contains the flags and the default values
val dump = dumpToString()
@@ -520,12 +474,15 @@ class FeatureFlagsDebugTest : SysuiTestCase() {
}
private fun verifyPutData(name: String, data: String, numReads: Int = 1) {
- inOrder(flagManager, globalSettings).apply {
- verify(flagManager, times(numReads)).readFlagValue(eq(name), any<FlagSerializer<*>>())
- verify(flagManager).nameToSettingsKey(eq(name))
- verify(globalSettings).putStringForUser(eq("key-$name"), eq(data), anyInt())
- verify(flagManager).dispatchListenersAndMaybeRestart(eq(name), any())
- }.verifyNoMoreInteractions()
+ inOrder(flagManager, globalSettings)
+ .apply {
+ verify(flagManager, times(numReads))
+ .readFlagValue(eq(name), any<FlagSerializer<*>>())
+ verify(flagManager).nameToSettingsKey(eq(name))
+ verify(globalSettings).putStringForUser(eq("key-$name"), eq(data), anyInt())
+ verify(flagManager).dispatchListenersAndMaybeRestart(eq(name), any())
+ }
+ .verifyNoMoreInteractions()
verifyNoMoreInteractions(flagManager, globalSettings)
}
@@ -545,7 +502,7 @@ class FeatureFlagsDebugTest : SysuiTestCase() {
private fun dumpToString(): String {
val sw = StringWriter()
val pw = PrintWriter(sw)
- featureFlagsDebug.dump(pw, emptyArray<String>())
+ mFeatureFlagsClassicDebug.dump(pw, emptyArray<String>())
pw.flush()
return sw.toString()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicReleaseTest.kt
index 16b459556cb9..70d6dd93459e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicReleaseTest.kt
@@ -26,16 +26,16 @@ import org.junit.Test
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.never
-import org.mockito.MockitoAnnotations
import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
/**
* NOTE: This test is for the version of FeatureFlagManager in src-release, which should not allow
* overriding, and should never return any value other than the one provided as the default.
*/
@SmallTest
-class FeatureFlagsReleaseTest : SysuiTestCase() {
- private lateinit var featureFlagsRelease: FeatureFlagsRelease
+class FeatureFlagsClassicReleaseTest : SysuiTestCase() {
+ private lateinit var mFeatureFlagsClassicRelease: FeatureFlagsClassicRelease
@Mock private lateinit var mResources: Resources
@Mock private lateinit var mSystemProperties: SystemPropertiesHelper
@@ -43,21 +43,22 @@ class FeatureFlagsReleaseTest : SysuiTestCase() {
private val flagMap = mutableMapOf<String, Flag<*>>()
private val serverFlagReader = ServerFlagReaderFake()
-
private val flagA = ReleasedFlag(name = "a", namespace = "test")
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
flagMap.put(flagA.name, flagA)
- featureFlagsRelease = FeatureFlagsRelease(
- mResources,
- mSystemProperties,
- serverFlagReader,
- flagMap,
- restarter)
-
- featureFlagsRelease.init()
+ mFeatureFlagsClassicRelease =
+ FeatureFlagsClassicRelease(
+ mResources,
+ mSystemProperties,
+ serverFlagReader,
+ flagMap,
+ restarter
+ )
+
+ mFeatureFlagsClassicRelease.init()
}
@Test
@@ -67,7 +68,7 @@ class FeatureFlagsReleaseTest : SysuiTestCase() {
val flagNamespace = "test"
val flag = ResourceBooleanFlag(flagName, flagNamespace, flagResourceId)
whenever(mResources.getBoolean(flagResourceId)).thenReturn(true)
- assertThat(featureFlagsRelease.isEnabled(flag)).isTrue()
+ assertThat(mFeatureFlagsClassicRelease.isEnabled(flag)).isTrue()
}
@Test
@@ -77,16 +78,16 @@ class FeatureFlagsReleaseTest : SysuiTestCase() {
whenever(mResources.getString(1003)).thenReturn(null)
whenever(mResources.getString(1004)).thenAnswer { throw NameNotFoundException() }
- assertThat(featureFlagsRelease.getString(
- ResourceStringFlag("1", "test", 1001))).isEqualTo("")
- assertThat(featureFlagsRelease.getString(
- ResourceStringFlag("2", "test", 1002))).isEqualTo("res2")
+ assertThat(mFeatureFlagsClassicRelease.getString(ResourceStringFlag("1", "test", 1001)))
+ .isEqualTo("")
+ assertThat(mFeatureFlagsClassicRelease.getString(ResourceStringFlag("2", "test", 1002)))
+ .isEqualTo("res2")
assertThrows(NullPointerException::class.java) {
- featureFlagsRelease.getString(ResourceStringFlag("3", "test", 1003))
+ mFeatureFlagsClassicRelease.getString(ResourceStringFlag("3", "test", 1003))
}
assertThrows(NameNotFoundException::class.java) {
- featureFlagsRelease.getString(ResourceStringFlag("4", "test", 1004))
+ mFeatureFlagsClassicRelease.getString(ResourceStringFlag("4", "test", 1004))
}
}
@@ -98,7 +99,7 @@ class FeatureFlagsReleaseTest : SysuiTestCase() {
val flag = SysPropBooleanFlag(flagName, flagNamespace, flagDefault)
whenever(mSystemProperties.getBoolean(flagName, flagDefault)).thenReturn(flagDefault)
- assertThat(featureFlagsRelease.isEnabled(flag)).isEqualTo(flagDefault)
+ assertThat(mFeatureFlagsClassicRelease.isEnabled(flag)).isEqualTo(flagDefault)
}
@Test
@@ -107,7 +108,7 @@ class FeatureFlagsReleaseTest : SysuiTestCase() {
serverFlagReader.setFlagValue(flag.namespace, flag.name, false)
- assertThat(featureFlagsRelease.isEnabled(flag)).isFalse()
+ assertThat(mFeatureFlagsClassicRelease.isEnabled(flag)).isFalse()
}
@Test
@@ -116,32 +117,29 @@ class FeatureFlagsReleaseTest : SysuiTestCase() {
serverFlagReader.setFlagValue(flag.namespace, flag.name, true)
- assertThat(featureFlagsRelease.isEnabled(flag)).isFalse()
+ assertThat(mFeatureFlagsClassicRelease.isEnabled(flag)).isFalse()
}
@Test
fun serverSide_OverrideUncached_NoRestart() {
// No one has read the flag, so it's not in the cache.
- serverFlagReader.setFlagValue(
- flagA.namespace, flagA.name, !flagA.default)
+ serverFlagReader.setFlagValue(flagA.namespace, flagA.name, !flagA.default)
Mockito.verify(restarter, never()).restartSystemUI(Mockito.anyString())
}
@Test
fun serverSide_Override_Restarts() {
// Read it to put it in the cache.
- featureFlagsRelease.isEnabled(flagA)
- serverFlagReader.setFlagValue(
- flagA.namespace, flagA.name, !flagA.default)
+ mFeatureFlagsClassicRelease.isEnabled(flagA)
+ serverFlagReader.setFlagValue(flagA.namespace, flagA.name, !flagA.default)
Mockito.verify(restarter).restartSystemUI(Mockito.anyString())
}
@Test
fun serverSide_RedundantOverride_NoRestart() {
// Read it to put it in the cache.
- featureFlagsRelease.isEnabled(flagA)
- serverFlagReader.setFlagValue(
- flagA.namespace, flagA.name, flagA.default)
+ mFeatureFlagsClassicRelease.isEnabled(flagA)
+ serverFlagReader.setFlagValue(flagA.namespace, flagA.name, flagA.default)
Mockito.verify(restarter, never()).restartSystemUI(Mockito.anyString())
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagCommandTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagCommandTest.kt
index b02baa7fd1b5..7c1325e2b355 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagCommandTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagCommandTest.kt
@@ -30,7 +30,7 @@ import org.mockito.MockitoAnnotations
@SmallTest
class FlagCommandTest : SysuiTestCase() {
- @Mock private lateinit var featureFlags: FeatureFlagsDebug
+ @Mock private lateinit var featureFlags: FeatureFlagsClassicDebug
@Mock private lateinit var pw: PrintWriter
private val flagMap = mutableMapOf<String, Flag<*>>()
private val flagA = UnreleasedFlag("500", "test")
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
index fdc4f372b329..c12df9837a63 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -635,6 +635,8 @@ public class VolumeDialogImplTest extends SysuiTestCase {
*/
private void assertRingerContainerDescribesItsState(int ringerMode,
RingerDrawerState drawerState) {
+ assumeHasDrawer();
+
State state = createShellState();
state.ringerModeInternal = ringerMode;
mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubbleEducationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubbleEducationControllerTest.kt
new file mode 100644
index 000000000000..94ed608f4844
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubbleEducationControllerTest.kt
@@ -0,0 +1,118 @@
+/*
+ * 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.wmshell
+
+import android.content.ContentResolver
+import android.content.Context
+import android.content.SharedPreferences
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.model.SysUiStateTest
+import com.android.wm.shell.bubbles.Bubble
+import com.android.wm.shell.bubbles.BubbleEducationController
+import com.android.wm.shell.bubbles.PREF_MANAGED_EDUCATION
+import com.android.wm.shell.bubbles.PREF_STACK_EDUCATION
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyString
+import org.mockito.Mockito
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+class BubbleEducationControllerTest : SysUiStateTest() {
+ private val sharedPrefsEditor = Mockito.mock(SharedPreferences.Editor::class.java)
+ private val sharedPrefs = Mockito.mock(SharedPreferences::class.java)
+ private val context = Mockito.mock(Context::class.java)
+ private lateinit var sut: BubbleEducationController
+
+ @Before
+ fun setUp() {
+ Mockito.`when`(context.packageName).thenReturn("packageName")
+ Mockito.`when`(context.getSharedPreferences(anyString(), anyInt())).thenReturn(sharedPrefs)
+ Mockito.`when`(context.contentResolver)
+ .thenReturn(Mockito.mock(ContentResolver::class.java))
+ Mockito.`when`(sharedPrefs.edit()).thenReturn(sharedPrefsEditor)
+ sut = BubbleEducationController(context)
+ }
+
+ @Test
+ fun testSeenStackEducation_read() {
+ Mockito.`when`(sharedPrefs.getBoolean(anyString(), anyBoolean())).thenReturn(true)
+ assertEquals(sut.hasSeenStackEducation, true)
+ Mockito.verify(sharedPrefs).getBoolean(PREF_STACK_EDUCATION, false)
+ }
+
+ @Test
+ fun testSeenStackEducation_write() {
+ sut.hasSeenStackEducation = true
+ Mockito.verify(sharedPrefsEditor).putBoolean(PREF_STACK_EDUCATION, true)
+ }
+
+ @Test
+ fun testSeenManageEducation_read() {
+ Mockito.`when`(sharedPrefs.getBoolean(anyString(), anyBoolean())).thenReturn(true)
+ assertEquals(sut.hasSeenManageEducation, true)
+ Mockito.verify(sharedPrefs).getBoolean(PREF_MANAGED_EDUCATION, false)
+ }
+
+ @Test
+ fun testSeenManageEducation_write() {
+ sut.hasSeenManageEducation = true
+ Mockito.verify(sharedPrefsEditor).putBoolean(PREF_MANAGED_EDUCATION, true)
+ }
+
+ @Test
+ fun testShouldShowStackEducation() {
+ val bubble = Mockito.mock(Bubble::class.java)
+ // When bubble is null
+ assertEquals(sut.shouldShowStackEducation(null), false)
+ // When bubble is not conversation
+ Mockito.`when`(bubble.isConversation).thenReturn(false)
+ assertEquals(sut.shouldShowStackEducation(bubble), false)
+ // When bubble is conversation and has seen stack edu
+ Mockito.`when`(bubble.isConversation).thenReturn(true)
+ Mockito.`when`(sharedPrefs.getBoolean(anyString(), anyBoolean())).thenReturn(true)
+ assertEquals(sut.shouldShowStackEducation(bubble), false)
+ // When bubble is conversation and has not seen stack edu
+ Mockito.`when`(bubble.isConversation).thenReturn(true)
+ Mockito.`when`(sharedPrefs.getBoolean(anyString(), anyBoolean())).thenReturn(false)
+ assertEquals(sut.shouldShowStackEducation(bubble), true)
+ }
+
+ @Test
+ fun testShouldShowManageEducation() {
+ val bubble = Mockito.mock(Bubble::class.java)
+ // When bubble is null
+ assertEquals(sut.shouldShowManageEducation(null), false)
+ // When bubble is not conversation
+ Mockito.`when`(bubble.isConversation).thenReturn(false)
+ assertEquals(sut.shouldShowManageEducation(bubble), false)
+ // When bubble is conversation and has seen stack edu
+ Mockito.`when`(bubble.isConversation).thenReturn(true)
+ Mockito.`when`(sharedPrefs.getBoolean(anyString(), anyBoolean())).thenReturn(true)
+ assertEquals(sut.shouldShowManageEducation(bubble), false)
+ // When bubble is conversation and has not seen stack edu
+ Mockito.`when`(bubble.isConversation).thenReturn(true)
+ Mockito.`when`(sharedPrefs.getBoolean(anyString(), anyBoolean())).thenReturn(false)
+ assertEquals(sut.shouldShowManageEducation(bubble), true)
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt
index 013dbb458c22..43c9c99744c5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt
@@ -18,7 +18,17 @@ package com.android.systemui.flags
import java.io.PrintWriter
-class FakeFeatureFlags : FeatureFlags {
+class FakeFeatureFlagsClassic : FakeFeatureFlags()
+
+@Deprecated(
+ message = "Use FakeFeatureFlagsClassic instead.",
+ replaceWith =
+ ReplaceWith(
+ "FakeFeatureFlagsClassic",
+ "com.android.systemui.flags.FakeFeatureFlagsClassic",
+ ),
+)
+open class FakeFeatureFlags : FeatureFlagsClassic {
private val booleanFlags = mutableMapOf<String, Boolean>()
private val stringFlags = mutableMapOf<String, String>()
private val intFlags = mutableMapOf<String, Int>()
@@ -66,12 +76,11 @@ class FakeFeatureFlags : FeatureFlags {
* Set the given flag's default value if no other value has been set.
*
* REMINDER: You should always test your code with your flag in both configurations, so
- * generally you should be setting a particular value. This method should be reserved for
- * situations where the flag needs to be read (e.g. in the class constructor), but its
- * value shouldn't affect the actual test cases. In those cases, it's mildly safer to use
- * this method than to hard-code `false` or `true` because then at least if you're wrong,
- * and the flag value *does* matter, you'll notice when the flag is flipped and tests
- * start failing.
+ * generally you should be setting a particular value. This method should be reserved for
+ * situations where the flag needs to be read (e.g. in the class constructor), but its value
+ * shouldn't affect the actual test cases. In those cases, it's mildly safer to use this method
+ * than to hard-code `false` or `true` because then at least if you're wrong, and the flag value
+ * *does* matter, you'll notice when the flag is flipped and tests start failing.
*/
fun setDefault(flag: BooleanFlag) = booleanFlags.putIfAbsent(flag.name, flag.default)
@@ -79,12 +88,11 @@ class FakeFeatureFlags : FeatureFlags {
* Set the given flag's default value if no other value has been set.
*
* REMINDER: You should always test your code with your flag in both configurations, so
- * generally you should be setting a particular value. This method should be reserved for
- * situations where the flag needs to be read (e.g. in the class constructor), but its
- * value shouldn't affect the actual test cases. In those cases, it's mildly safer to use
- * this method than to hard-code `false` or `true` because then at least if you're wrong,
- * and the flag value *does* matter, you'll notice when the flag is flipped and tests
- * start failing.
+ * generally you should be setting a particular value. This method should be reserved for
+ * situations where the flag needs to be read (e.g. in the class constructor), but its value
+ * shouldn't affect the actual test cases. In those cases, it's mildly safer to use this method
+ * than to hard-code `false` or `true` because then at least if you're wrong, and the flag value
+ * *does* matter, you'll notice when the flag is flipped and tests start failing.
*/
fun setDefault(flag: SysPropBooleanFlag) = booleanFlags.putIfAbsent(flag.name, flag.default)
@@ -123,10 +131,8 @@ class FakeFeatureFlags : FeatureFlags {
}
override fun removeListener(listener: FlagListenable.Listener) {
- listenerflagNames.remove(listener)?.let {
- flagNames -> flagNames.forEach {
- id -> flagListeners[id]?.remove(listener)
- }
+ listenerflagNames.remove(listener)?.let { flagNames ->
+ flagNames.forEach { id -> flagListeners[id]?.remove(listener) }
}
}
diff --git a/services/companion/java/com/android/server/companion/virtual/Android.bp b/services/companion/java/com/android/server/companion/virtual/Android.bp
new file mode 100644
index 000000000000..50a09b9a8ea9
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/virtual/Android.bp
@@ -0,0 +1,12 @@
+java_aconfig_library {
+ name: "virtualdevice_flags_lib",
+ aconfig_declarations: "virtualdevice_flags",
+}
+
+aconfig_declarations {
+ name: "virtualdevice_flags",
+ package: "com.android.server.companion.virtual",
+ srcs: [
+ "flags.aconfig",
+ ],
+} \ No newline at end of file
diff --git a/services/companion/java/com/android/server/companion/virtual/flags.aconfig b/services/companion/java/com/android/server/companion/virtual/flags.aconfig
new file mode 100644
index 000000000000..4fe4c870a283
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/virtual/flags.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.server.companion.virtual"
+
+flag {
+ name: "dump_history"
+ namespace: "virtual_devices"
+ description: "This flag controls if a history of virtual devices is shown in dumpsys virtualdevices"
+ bug: "293114719"
+} \ No newline at end of file
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index b0f30d6875ae..610b8af48cd1 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -2631,6 +2631,10 @@ public final class BatteryStatsService extends IBatteryStats.Stub
mStats.dumpStatsSample(pw);
}
+ private void dumpAggregatedStats(PrintWriter pw) {
+ mStats.dumpAggregatedStats(pw, /* startTime */ 0, /* endTime */0);
+ }
+
private void dumpMeasuredEnergyStats(PrintWriter pw) {
// Wait for the completion of pending works if there is any
awaitCompletion();
@@ -2875,6 +2879,9 @@ public final class BatteryStatsService extends IBatteryStats.Stub
} else if ("--sample".equals(arg)) {
dumpStatsSample(pw);
return;
+ } else if ("--aggregated".equals(arg)) {
+ dumpAggregatedStats(pw);
+ return;
} else if ("-a".equals(arg)) {
flags |= BatteryStats.DUMP_VERBOSE;
} else if (arg.length() > 0 && arg.charAt(0) == '-'){
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 4e01997e678f..4a523af142a4 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -2409,7 +2409,11 @@ public class AudioDeviceBroker {
}
@GuardedBy("mDeviceStateLock")
- private boolean communnicationDeviceCompatOn() {
+ // LE Audio: For system server (Telecom) and APKs targeting S and above, we let the audio
+ // policy routing rules select the default communication device.
+ // For older APKs, we force LE Audio headset when connected as those APKs cannot select a LE
+ // Audiodevice explicitly.
+ private boolean communnicationDeviceLeAudioCompatOn() {
return mAudioModeOwner.mMode == AudioSystem.MODE_IN_COMMUNICATION
&& !(CompatChanges.isChangeEnabled(
USE_SET_COMMUNICATION_DEVICE, mAudioModeOwner.mUid)
@@ -2417,19 +2421,25 @@ public class AudioDeviceBroker {
}
@GuardedBy("mDeviceStateLock")
+ // Hearing Aid: For system server (Telecom) and IN_CALL mode we let the audio
+ // policy routing rules select the default communication device.
+ // For 3p apps and IN_COMMUNICATION mode we force Hearing aid when connected to maintain
+ // backwards compatibility
+ private boolean communnicationDeviceHaCompatOn() {
+ return mAudioModeOwner.mMode == AudioSystem.MODE_IN_COMMUNICATION
+ && !(mAudioModeOwner.mUid == android.os.Process.SYSTEM_UID);
+ }
+
+ @GuardedBy("mDeviceStateLock")
AudioDeviceAttributes getDefaultCommunicationDevice() {
- // For system server (Telecom) and APKs targeting S and above, we let the audio
- // policy routing rules select the default communication device.
- // For older APKs, we force Hearing Aid or LE Audio headset when connected as
- // those APKs cannot select a LE Audio or Hearing Aid device explicitly.
AudioDeviceAttributes device = null;
- if (communnicationDeviceCompatOn()) {
- // If both LE and Hearing Aid are active (thie should not happen),
- // priority to Hearing Aid.
+ // If both LE and Hearing Aid are active (thie should not happen),
+ // priority to Hearing Aid.
+ if (communnicationDeviceHaCompatOn()) {
device = mDeviceInventory.getDeviceOfType(AudioSystem.DEVICE_OUT_HEARING_AID);
- if (device == null) {
- device = mDeviceInventory.getDeviceOfType(AudioSystem.DEVICE_OUT_BLE_HEADSET);
- }
+ }
+ if (device == null && communnicationDeviceLeAudioCompatOn()) {
+ device = mDeviceInventory.getDeviceOfType(AudioSystem.DEVICE_OUT_BLE_HEADSET);
}
return device;
}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 0bc047271bab..5f15995687b7 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -43,6 +43,7 @@ import static com.android.server.utils.EventLogger.Event.ALOGI;
import static com.android.server.utils.EventLogger.Event.ALOGW;
import android.Manifest;
+import android.annotation.EnforcePermission;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
@@ -10005,6 +10006,14 @@ public class AudioService extends IAudioService.Stub
return mMediaFocusControl.abandonAudioFocus(fd, clientId, aa, callingPackageName);
}
+ /** see {@link AudioManager#getFocusDuckedUidsForTest()} */
+ @Override
+ @EnforcePermission("android.permission.QUERY_AUDIO_STATE")
+ public @NonNull List<Integer> getFocusDuckedUidsForTest() {
+ super.getFocusDuckedUidsForTest_enforcePermission();
+ return mPlaybackMonitor.getFocusDuckedUids();
+ }
+
public void unregisterAudioFocusClient(String clientId) {
new MediaMetrics.Item(mMetricsId + "focus")
.set(MediaMetrics.Property.CLIENT_NAME, clientId)
@@ -10021,6 +10030,68 @@ public class AudioService extends IAudioService.Stub
return mMediaFocusControl.getFocusRampTimeMs(focusGain, attr);
}
+ /**
+ * Test method to return the duration of the fade out applied on the players of a focus loser
+ * @see AudioManager#getFocusFadeOutDurationForTest()
+ * @return the fade out duration, in ms
+ */
+ @EnforcePermission("android.permission.QUERY_AUDIO_STATE")
+ public long getFocusFadeOutDurationForTest() {
+ super.getFocusFadeOutDurationForTest_enforcePermission();
+ return mMediaFocusControl.getFocusFadeOutDurationForTest();
+ }
+
+ /**
+ * Test method to return the length of time after a fade out before the focus loser is unmuted
+ * (and is faded back in).
+ * @see AudioManager#getFocusUnmuteDelayAfterFadeOutForTest()
+ * @return the time gap after a fade out completion on focus loss, and fade in start, in ms
+ */
+ @Override
+ @EnforcePermission("android.permission.QUERY_AUDIO_STATE")
+ public long getFocusUnmuteDelayAfterFadeOutForTest() {
+ super.getFocusUnmuteDelayAfterFadeOutForTest_enforcePermission();
+ return mMediaFocusControl.getFocusUnmuteDelayAfterFadeOutForTest();
+ }
+
+ /**
+ * Test method to start preventing applications from requesting audio focus during a test,
+ * which could interfere with the testing of the functionality/behavior under test.
+ * Calling this method needs to be paired with a call to {@link #exitAudioFocusFreezeForTest}
+ * when the testing is done. If this is not the case (e.g. in case of a test crash),
+ * a death observer mechanism will ensure the system is not left in a bad state, but this should
+ * not be relied on when implementing tests.
+ * @see AudioManager#enterAudioFocusFreezeForTest(List)
+ * @param cb IBinder to track the death of the client of this method
+ * @param exemptedUids a list of UIDs that are exempt from the freeze. This would for instance
+ * be those of the test runner and other players used in the test
+ * @return true if the focus freeze mode is successfully entered, false if there was an issue,
+ * such as another freeze currently used.
+ */
+ @Override
+ @EnforcePermission("android.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED")
+ public boolean enterAudioFocusFreezeForTest(IBinder cb, int[] exemptedUids) {
+ super.enterAudioFocusFreezeForTest_enforcePermission();
+ Objects.requireNonNull(exemptedUids);
+ Objects.requireNonNull(cb);
+ return mMediaFocusControl.enterAudioFocusFreezeForTest(cb, exemptedUids);
+ }
+
+ /**
+ * Test method to end preventing applications from requesting audio focus during a test.
+ * @see AudioManager#exitAudioFocusFreezeForTest()
+ * @param cb IBinder identifying the client of this method
+ * @return true if the focus freeze mode is successfully exited, false if there was an issue,
+ * such as the freeze already having ended, or not started.
+ */
+ @Override
+ @EnforcePermission("android.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED")
+ public boolean exitAudioFocusFreezeForTest(IBinder cb) {
+ super.exitAudioFocusFreezeForTest_enforcePermission();
+ Objects.requireNonNull(cb);
+ return mMediaFocusControl.exitAudioFocusFreezeForTest(cb);
+ }
+
/** only public for mocking/spying, do not call outside of AudioService */
@VisibleForTesting
public boolean hasAudioFocusUsers() {
@@ -10028,6 +10099,7 @@ public class AudioService extends IAudioService.Stub
}
/** see {@link AudioManager#getFadeOutDurationOnFocusLossMillis(AudioAttributes)} */
+ @Override
public long getFadeOutDurationOnFocusLossMillis(AudioAttributes aa) {
if (!enforceQueryAudioStateForTest("fade out duration")) {
return 0;
diff --git a/services/core/java/com/android/server/audio/FocusRequester.java b/services/core/java/com/android/server/audio/FocusRequester.java
index 88a4b0531cb1..010d5f41bc7d 100644
--- a/services/core/java/com/android/server/audio/FocusRequester.java
+++ b/services/core/java/com/android/server/audio/FocusRequester.java
@@ -43,7 +43,7 @@ import java.io.PrintWriter;
public class FocusRequester {
// on purpose not using this classe's name, as it will only be used from MediaFocusControl
- private static final String TAG = "MediaFocusControl";
+ private static final String TAG = "FocusRequester";
private static final boolean DEBUG = false;
private AudioFocusDeathHandler mDeathHandler; // may be null
@@ -340,6 +340,9 @@ public class FocusRequester {
@GuardedBy("MediaFocusControl.mAudioFocusLock")
boolean handleFocusLossFromGain(int focusGain, final FocusRequester frWinner, boolean forceDuck)
{
+ if (DEBUG) {
+ Log.i(TAG, "handleFocusLossFromGain for " + mClientId + " gain:" + focusGain);
+ }
final int focusLoss = focusLossForGainRequest(focusGain);
handleFocusLoss(focusLoss, frWinner, forceDuck);
return (focusLoss == AudioManager.AUDIOFOCUS_LOSS);
@@ -378,6 +381,9 @@ public class FocusRequester {
@GuardedBy("MediaFocusControl.mAudioFocusLock")
void handleFocusLoss(int focusLoss, @Nullable final FocusRequester frWinner, boolean forceDuck)
{
+ if (DEBUG) {
+ Log.i(TAG, "handleFocusLoss for " + mClientId + " loss:" + focusLoss);
+ }
try {
if (focusLoss != mFocusLossReceived) {
mFocusLossReceived = focusLoss;
@@ -427,6 +433,9 @@ public class FocusRequester {
toAudioFocusInfo(), true /* wasDispatched */);
mFocusLossWasNotified = true;
fd.dispatchAudioFocusChange(mFocusLossReceived, mClientId);
+ } else if (DEBUG) {
+ Log.i(TAG, "NOT dispatching " + focusChangeToString(mFocusLossReceived)
+ + " to " + mClientId + " no IAudioFocusDispatcher");
}
}
} catch (android.os.RemoteException e) {
diff --git a/services/core/java/com/android/server/audio/MediaFocusControl.java b/services/core/java/com/android/server/audio/MediaFocusControl.java
index b2180962a96e..65f6c9b8d459 100644
--- a/services/core/java/com/android/server/audio/MediaFocusControl.java
+++ b/services/core/java/com/android/server/audio/MediaFocusControl.java
@@ -45,6 +45,7 @@ import com.android.server.utils.EventLogger;
import java.io.PrintWriter;
import java.text.DateFormat;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
@@ -122,6 +123,23 @@ public class MediaFocusControl implements PlayerFocusEnforcer {
dumpMultiAudioFocus(pw);
}
+ /**
+ * Test method to return the duration of the fade out applied on the players of a focus loser
+ * @return the fade out duration in ms
+ */
+ public long getFocusFadeOutDurationForTest() {
+ return FadeOutManager.FADE_OUT_DURATION_MS;
+ }
+
+ /**
+ * Test method to return the length of time after a fade out before the focus loser is unmuted
+ * (and is faded back in).
+ * @return the time gap after a fade out completion on focus loss, and fade in start in ms
+ */
+ public long getFocusUnmuteDelayAfterFadeOutForTest() {
+ return FadeOutManager.DELAY_FADE_IN_OFFENDERS_MS;
+ }
+
//=================================================================
// PlayerFocusEnforcer implementation
@Override
@@ -304,17 +322,26 @@ public class MediaFocusControl implements PlayerFocusEnforcer {
@GuardedBy("mAudioFocusLock")
private void propagateFocusLossFromGain_syncAf(int focusGain, final FocusRequester fr,
boolean forceDuck) {
+ if (DEBUG) {
+ Log.i(TAG, "propagateFocusLossFromGain_syncAf gain:" + focusGain);
+ }
final List<String> clientsToRemove = new LinkedList<String>();
// going through the audio focus stack to signal new focus, traversing order doesn't
// matter as all entries respond to the same external focus gain
if (!mFocusStack.empty()) {
for (FocusRequester focusLoser : mFocusStack) {
+ if (DEBUG) {
+ Log.i(TAG, "propagateFocusLossFromGain_syncAf checking client:"
+ + focusLoser.getClientId());
+ }
final boolean isDefinitiveLoss =
focusLoser.handleFocusLossFromGain(focusGain, fr, forceDuck);
if (isDefinitiveLoss) {
clientsToRemove.add(focusLoser.getClientId());
}
}
+ } else if (DEBUG) {
+ Log.i(TAG, "propagateFocusLossFromGain_syncAf empty stack");
}
if (mMultiAudioFocusEnabled && !mMultiAudioFocusList.isEmpty()) {
@@ -370,6 +397,9 @@ public class MediaFocusControl implements PlayerFocusEnforcer {
@GuardedBy("mAudioFocusLock")
private void removeFocusStackEntry(String clientToRemove, boolean signal,
boolean notifyFocusFollowers) {
+ if (DEBUG) {
+ Log.i(TAG, "removeFocusStackEntry client:" + clientToRemove);
+ }
AudioFocusInfo abandonSource = null;
// is the current top of the focus stack abandoning focus? (because of request, not death)
if (!mFocusStack.empty() && mFocusStack.peek().hasSameClient(clientToRemove))
@@ -1000,6 +1030,24 @@ public class MediaFocusControl implements PlayerFocusEnforcer {
}
synchronized(mAudioFocusLock) {
+ // check whether a focus freeze is in place and filter
+ if (isFocusFrozenForTest()) {
+ int focusRequesterUid;
+ if ((flags & AudioManager.AUDIOFOCUS_FLAG_TEST)
+ == AudioManager.AUDIOFOCUS_FLAG_TEST) {
+ focusRequesterUid = testUid;
+ } else {
+ focusRequesterUid = Binder.getCallingUid();
+ }
+ if (isFocusFrozenForTestForUid(focusRequesterUid)) {
+ Log.i(TAG, "requestAudioFocus: focus frozen for test for uid:"
+ + focusRequesterUid);
+ return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
+ }
+ Log.i(TAG, "requestAudioFocus: focus frozen for test but uid:" + focusRequesterUid
+ + " is exempt");
+ }
+
if (mFocusStack.size() > MAX_STACK_SIZE) {
Log.e(TAG, "Max AudioFocus stack size reached, failing requestAudioFocus()");
return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
@@ -1191,6 +1239,110 @@ public class MediaFocusControl implements PlayerFocusEnforcer {
return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
}
+ /**
+ * Reference to the caller of {@link #enterAudioFocusFreezeForTest(IBinder, int[])}
+ * Will be null when there is no focus freeze for test
+ */
+ @GuardedBy("mAudioFocusLock")
+ @Nullable
+ private IBinder mFocusFreezerForTest = null;
+
+ /**
+ * The death handler for {@link #mFocusFreezerForTest}
+ * Will be null when there is no focus freeze for test
+ */
+ @GuardedBy("mAudioFocusLock")
+ @Nullable
+ private IBinder.DeathRecipient mFocusFreezerDeathHandler = null;
+
+ /**
+ * Array of UIDs exempt from focus freeze when focus is frozen for test, null during normal
+ * operations.
+ * Will be null when there is no focus freeze for test
+ */
+ @GuardedBy("mAudioFocusLock")
+ @Nullable
+ private int[] mFocusFreezeExemptUids = null;
+
+ @GuardedBy("mAudioFocusLock")
+ private boolean isFocusFrozenForTest() {
+ return (mFocusFreezerForTest != null);
+ }
+
+ /**
+ * Checks if the given UID can request focus when a focus freeze is in place for a test.
+ * Focus can be requested if focus is not frozen or if it's frozen but the UID is exempt.
+ * @param uidToCheck
+ * @return true if that UID is barred from requesting focus, false if its focus request
+ * can proceed being processed
+ */
+ @GuardedBy("mAudioFocusLock")
+ private boolean isFocusFrozenForTestForUid(int uidToCheck) {
+ if (isFocusFrozenForTest()) {
+ return false;
+ }
+ // check the list of exempts (array is not null because we're in a freeze for test
+ for (int uid : mFocusFreezeExemptUids) {
+ if (uid == uidToCheck) {
+ return false;
+ }
+ }
+ // uid was not found in the exempt list, its focus request is denied
+ return true;
+ }
+
+ protected boolean enterAudioFocusFreezeForTest(
+ @NonNull IBinder cb, @NonNull int[] exemptedUids) {
+ Log.i(TAG, "enterAudioFocusFreezeForTest UIDs exempt:" + Arrays.toString(exemptedUids));
+ synchronized (mAudioFocusLock) {
+ if (mFocusFreezerForTest != null) {
+ Log.e(TAG, "Error enterAudioFocusFreezeForTest: focus already frozen");
+ return false;
+ }
+ // new focus freeze, register death handler
+ try {
+ mFocusFreezerDeathHandler = new IBinder.DeathRecipient() {
+ @Override
+ public void binderDied() {
+ Log.i(TAG, "Audio focus freezer died, exiting focus freeze for test");
+ releaseFocusFreeze();
+ }
+ };
+ cb.linkToDeath(mFocusFreezerDeathHandler, 0);
+ mFocusFreezerForTest = cb;
+ mFocusFreezeExemptUids = exemptedUids.clone();
+ } catch (RemoteException e) {
+ // client has already died!
+ mFocusFreezerForTest = null;
+ mFocusFreezeExemptUids = null;
+ return false;
+ }
+ }
+ return true;
+ }
+
+ protected boolean exitAudioFocusFreezeForTest(@NonNull IBinder cb) {
+ synchronized (mAudioFocusLock) {
+ if (mFocusFreezerForTest != cb) {
+ Log.e(TAG, "Error exitAudioFocusFreezeForTest: "
+ + ((mFocusFreezerForTest == null)
+ ? "call to exit while not frozen"
+ : "call to exit not coming from freeze owner"));
+ return false;
+ }
+ mFocusFreezerForTest.unlinkToDeath(mFocusFreezerDeathHandler, 0);
+ releaseFocusFreeze();
+ }
+ return true;
+ }
+
+ private void releaseFocusFreeze() {
+ synchronized (mAudioFocusLock) {
+ mFocusFreezerDeathHandler = null;
+ mFocusFreezeExemptUids = null;
+ mFocusFreezerForTest = null;
+ }
+ }
protected void unregisterAudioFocusClient(String clientId) {
synchronized(mAudioFocusLock) {
diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
index 23a0782dc8a3..54fa6fbc3bfd 100644
--- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
@@ -1208,6 +1208,17 @@ public final class PlaybackActivityMonitor
}
}
+ protected @NonNull List<Integer> getFocusDuckedUids() {
+ final ArrayList<Integer> duckedUids;
+ synchronized (mPlayerLock) {
+ duckedUids = new ArrayList(mDuckingManager.mDuckers.keySet());
+ }
+ if (DEBUG) {
+ Log.i(TAG, "current ducked UIDs: " + duckedUids);
+ }
+ return duckedUids;
+ }
+
//=================================================================
// For logging
private static final class PlayerEvent extends EventLogger.Event {
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
index e7bd68efd597..515c7fb09ab0 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -735,12 +735,9 @@ public final class MediaProjectionManagerService extends SystemService
}
@Override //Binder call
+ @EnforcePermission(MANAGE_MEDIA_PROJECTION)
public void addCallback(final IMediaProjectionWatcherCallback callback) {
- if (mContext.checkCallingPermission(MANAGE_MEDIA_PROJECTION)
- != PackageManager.PERMISSION_GRANTED) {
- throw new SecurityException("Requires MANAGE_MEDIA_PROJECTION in order to add "
- + "projection callbacks");
- }
+ addCallback_enforcePermission();
final long token = Binder.clearCallingIdentity();
try {
MediaProjectionManagerService.this.addCallback(callback);
diff --git a/services/core/java/com/android/server/power/stats/AggregatedPowerStats.java b/services/core/java/com/android/server/power/stats/AggregatedPowerStats.java
new file mode 100644
index 000000000000..6cc9d0a54607
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/AggregatedPowerStats.java
@@ -0,0 +1,147 @@
+/*
+ * 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.server.power.stats;
+
+import android.annotation.CurrentTimeMillisLong;
+import android.annotation.DurationMillisLong;
+import android.os.UserHandle;
+import android.text.format.DateFormat;
+import android.util.IndentingPrintWriter;
+
+import com.android.internal.os.PowerStats;
+
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * This class represents aggregated power stats for a variety of power components (CPU, WiFi,
+ * etc) covering a specific period of power usage history.
+ */
+class AggregatedPowerStats {
+ private final PowerComponentAggregatedPowerStats[] mPowerComponentStats;
+
+ @CurrentTimeMillisLong
+ private long mStartTime;
+
+ @DurationMillisLong
+ private long mDurationMs;
+
+ AggregatedPowerStats(PowerComponentAggregatedPowerStats... powerComponentAggregatedPowerStats) {
+ this.mPowerComponentStats = powerComponentAggregatedPowerStats;
+ }
+
+ void setStartTime(@CurrentTimeMillisLong long startTime) {
+ mStartTime = startTime;
+ }
+
+ @CurrentTimeMillisLong
+ public long getStartTime() {
+ return mStartTime;
+ }
+
+ void setDuration(long durationMs) {
+ mDurationMs = durationMs;
+ }
+
+ @DurationMillisLong
+ public long getDuration() {
+ return mDurationMs;
+ }
+
+ PowerComponentAggregatedPowerStats getPowerComponentStats(int powerComponentId) {
+ for (PowerComponentAggregatedPowerStats stats : mPowerComponentStats) {
+ if (stats.powerComponentId == powerComponentId) {
+ return stats;
+ }
+ }
+ return null;
+ }
+
+ void setDeviceState(@PowerStatsAggregator.TrackedState int stateId, int state, long time) {
+ for (PowerComponentAggregatedPowerStats stats : mPowerComponentStats) {
+ stats.setState(stateId, state, time);
+ }
+ }
+
+ void setUidState(int uid, @PowerStatsAggregator.TrackedState int stateId, int state,
+ long time) {
+ for (PowerComponentAggregatedPowerStats stats : mPowerComponentStats) {
+ stats.setUidState(uid, stateId, state, time);
+ }
+ }
+
+ boolean isCompatible(PowerStats powerStats) {
+ int powerComponentId = powerStats.descriptor.powerComponentId;
+ for (PowerComponentAggregatedPowerStats stats : mPowerComponentStats) {
+ if (stats.powerComponentId == powerComponentId && !stats.isCompatible(powerStats)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ void addPowerStats(PowerStats powerStats, long time) {
+ int powerComponentId = powerStats.descriptor.powerComponentId;
+ for (PowerComponentAggregatedPowerStats stats : mPowerComponentStats) {
+ if (stats.powerComponentId == powerComponentId) {
+ stats.addPowerStats(powerStats, time);
+ }
+ }
+ }
+
+ void reset() {
+ mStartTime = 0;
+ mDurationMs = 0;
+ for (PowerComponentAggregatedPowerStats stats : mPowerComponentStats) {
+ stats.reset();
+ }
+ }
+
+ void dump(PrintWriter pw) {
+ IndentingPrintWriter ipw = new IndentingPrintWriter(pw);
+ ipw.print("Start time: ");
+ ipw.print(DateFormat.format("yyyy-MM-dd-HH-mm-ss", mStartTime));
+ ipw.print(" duration: ");
+ ipw.print(mDurationMs);
+ ipw.println();
+
+ ipw.println("Device");
+ ipw.increaseIndent();
+ for (PowerComponentAggregatedPowerStats stats : mPowerComponentStats) {
+ stats.dumpDevice(ipw);
+ }
+ ipw.decreaseIndent();
+
+ Set<Integer> uids = new HashSet<>();
+ for (PowerComponentAggregatedPowerStats stats : mPowerComponentStats) {
+ stats.collectUids(uids);
+ }
+
+ Integer[] allUids = uids.toArray(new Integer[uids.size()]);
+ Arrays.sort(allUids);
+ for (int uid : allUids) {
+ ipw.println(UserHandle.formatUid(uid));
+ ipw.increaseIndent();
+ for (PowerComponentAggregatedPowerStats stats : mPowerComponentStats) {
+ stats.dumpUid(ipw, uid);
+ }
+ ipw.decreaseIndent();
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java b/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java
index f4b2f52eef9c..daf02ca81a10 100644
--- a/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java
+++ b/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java
@@ -687,12 +687,6 @@ public class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStat
BatteryStats.HistoryItem.EVENT_COLLECT_EXTERNAL_STATS,
reason, 0);
- if (energyConsumerDeltas != null && !energyConsumerDeltas.isEmpty()
- && mStats.isUsageHistoryEnabled()) {
- mStats.recordEnergyConsumerDetailsLocked(elapsedRealtime, uptime,
- mEnergyConsumerSnapshot.getEnergyConsumerDetails(energyConsumerDeltas));
- }
-
if ((updateFlags & UPDATE_CPU) != 0) {
if (useLatestStates) {
onBattery = mStats.isOnBatteryLocked();
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index 5b10afadc0fc..613f18982b86 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -125,6 +125,7 @@ import com.android.internal.os.KernelSingleUidTimeReader;
import com.android.internal.os.LongArrayMultiStateCounter;
import com.android.internal.os.LongMultiStateCounter;
import com.android.internal.os.PowerProfile;
+import com.android.internal.os.PowerStats;
import com.android.internal.os.RailStats;
import com.android.internal.os.RpmStats;
import com.android.internal.power.EnergyConsumerStats;
@@ -135,6 +136,7 @@ import com.android.internal.util.XmlUtils;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
import com.android.net.module.util.NetworkCapabilitiesUtils;
+import com.android.server.power.optimization.Flags;
import com.android.server.power.stats.SystemServerCpuThreadReader.SystemServiceCpuThreadTimes;
import libcore.util.EmptyArray;
@@ -280,8 +282,8 @@ public class BatteryStatsImpl extends BatteryStats {
= new KernelMemoryBandwidthStats();
private final LongSparseArray<SamplingTimer> mKernelMemoryStats = new LongSparseArray<>();
private int[] mCpuPowerBracketMap;
- private final CpuUsageDetails mCpuUsageDetails = new CpuUsageDetails();
private final CpuPowerStatsCollector mCpuPowerStatsCollector;
+ private final PowerStatsAggregator mPowerStatsAggregator;
public LongSparseArray<SamplingTimer> getKernelMemoryStats() {
return mKernelMemoryStats;
@@ -613,15 +615,8 @@ public class BatteryStatsImpl extends BatteryStats {
LongArrayMultiStateCounter onBatteryScreenOffCounter =
u.getProcStateScreenOffTimeCounter(elapsedRealtimeMs).getCounter();
- if (isUsageHistoryEnabled()) {
- LongArrayMultiStateCounter.LongArrayContainer deltaContainer =
- getCpuTimeInFreqContainer();
- mKernelSingleUidTimeReader.addDelta(uid, onBatteryCounter, elapsedRealtimeMs,
- deltaContainer);
- recordCpuUsage(uid, deltaContainer, elapsedRealtimeMs, uptimeMs);
- } else {
- mKernelSingleUidTimeReader.addDelta(uid, onBatteryCounter, elapsedRealtimeMs);
- }
+
+ mKernelSingleUidTimeReader.addDelta(uid, onBatteryCounter, elapsedRealtimeMs);
mKernelSingleUidTimeReader.addDelta(uid, onBatteryScreenOffCounter, elapsedRealtimeMs);
if (u.mChildUids != null) {
@@ -635,25 +630,12 @@ public class BatteryStatsImpl extends BatteryStats {
mKernelSingleUidTimeReader.addDelta(u.mChildUids.keyAt(j),
cpuTimeInFreqCounter, elapsedRealtimeMs, deltaContainer);
onBatteryCounter.addCounts(deltaContainer);
- if (isUsageHistoryEnabled()) {
- recordCpuUsage(uid, deltaContainer, elapsedRealtimeMs, uptimeMs);
- }
onBatteryScreenOffCounter.addCounts(deltaContainer);
}
}
}
}
- private void recordCpuUsage(int uid, LongArrayMultiStateCounter.LongArrayContainer cpuUsage,
- long elapsedRealtimeMs, long uptimeMs) {
- if (!cpuUsage.combineValues(mCpuUsageDetails.cpuUsageMs, mCpuPowerBracketMap)) {
- return;
- }
-
- mCpuUsageDetails.uid = uid;
- mHistory.recordCpuUsage(elapsedRealtimeMs, uptimeMs, mCpuUsageDetails);
- }
-
/**
* Removes kernel CPU stats for removed UIDs, in the order they were added to the
* mPendingRemovedUids queue.
@@ -674,6 +656,10 @@ public class BatteryStatsImpl extends BatteryStats {
*/
@SuppressWarnings("GuardedBy") // errorprone false positive on getProcStateTimeCounter
public void updateCpuTimesForAllUids() {
+ if (mCpuPowerStatsCollector != null) {
+ mCpuPowerStatsCollector.schedule();
+ }
+
synchronized (BatteryStatsImpl.this) {
if (!trackPerProcStateCpuTimes()) {
return;
@@ -705,16 +691,8 @@ public class BatteryStatsImpl extends BatteryStats {
u.getProcStateScreenOffTimeCounter(elapsedRealtimeMs).getCounter();
if (uid == parentUid || Process.isSdkSandboxUid(uid)) {
- if (isUsageHistoryEnabled()) {
- LongArrayMultiStateCounter.LongArrayContainer deltaContainer =
- getCpuTimeInFreqContainer();
- mKernelSingleUidTimeReader.addDelta(parentUid, onBatteryCounter,
- elapsedRealtimeMs, deltaContainer);
- recordCpuUsage(parentUid, deltaContainer, elapsedRealtimeMs, uptimeMs);
- } else {
- mKernelSingleUidTimeReader.addDelta(parentUid, onBatteryCounter,
- elapsedRealtimeMs);
- }
+ mKernelSingleUidTimeReader.addDelta(parentUid, onBatteryCounter,
+ elapsedRealtimeMs);
mKernelSingleUidTimeReader.addDelta(parentUid, onBatteryScreenOffCounter,
elapsedRealtimeMs);
} else {
@@ -727,9 +705,6 @@ public class BatteryStatsImpl extends BatteryStats {
mKernelSingleUidTimeReader.addDelta(uid, counter, elapsedRealtimeMs,
deltaContainer);
onBatteryCounter.addCounts(deltaContainer);
- if (isUsageHistoryEnabled()) {
- recordCpuUsage(uid, deltaContainer, elapsedRealtimeMs, uptimeMs);
- }
onBatteryScreenOffCounter.addCounts(deltaContainer);
}
}
@@ -860,6 +835,8 @@ public class BatteryStatsImpl extends BatteryStats {
private final HistoryEventTracker mActiveEvents = new HistoryEventTracker();
private final HistoryStepDetailsCalculatorImpl mStepDetailsCalculator =
new HistoryStepDetailsCalculatorImpl();
+ private final PowerStats.DescriptorRegistry mPowerStatsDescriptorRegistry =
+ new PowerStats.DescriptorRegistry();
private boolean mHaveBatteryLevel = false;
private boolean mBatteryPluggedIn;
@@ -1771,6 +1748,7 @@ public class BatteryStatsImpl extends BatteryStats {
mEnergyConsumerRetriever = null;
mUserInfoProvider = null;
mCpuPowerStatsCollector = null;
+ mPowerStatsAggregator = null;
}
private void init(Clock clock) {
@@ -4403,6 +4381,12 @@ public class BatteryStatsImpl extends BatteryStats {
public void noteCurrentTimeChangedLocked(long currentTimeMs,
long elapsedRealtimeMs, long uptimeMs) {
mHistory.recordCurrentTimeChange(elapsedRealtimeMs, uptimeMs, currentTimeMs);
+ adjustStartClockTime(currentTimeMs);
+ }
+
+ private void adjustStartClockTime(long currentTimeMs) {
+ mStartClockTimeMs =
+ currentTimeMs - (mClock.elapsedRealtime() - (mRealtimeStartUs / 1000));
}
@GuardedBy("this")
@@ -7674,18 +7658,6 @@ public class BatteryStatsImpl extends BatteryStats {
return names;
}
- /**
- * Adds energy consumer delta to battery history.
- */
- @GuardedBy("this")
- public void recordEnergyConsumerDetailsLocked(long elapsedRealtimeMs,
- long uptimeMs, EnergyConsumerDetails energyConsumerDetails) {
- if (isUsageHistoryEnabled()) {
- mHistory.recordEnergyConsumerDetails(elapsedRealtimeMs, uptimeMs,
- energyConsumerDetails);
- }
- }
-
@GuardedBy("this")
@Override public long getStartClockTime() {
final long currentTimeMs = mClock.currentTimeMillis();
@@ -7696,9 +7668,8 @@ public class BatteryStatsImpl extends BatteryStats {
// the previous time was completely bogus. So we are going to figure out a
// new time based on how much time has elapsed since we started counting.
mHistory.recordCurrentTimeChange(mClock.elapsedRealtime(), mClock.uptimeMillis(),
- currentTimeMs
- );
- return currentTimeMs - (mClock.elapsedRealtime() - (mRealtimeStartUs / 1000));
+ currentTimeMs);
+ adjustStartClockTime(currentTimeMs);
}
return mStartClockTimeMs;
}
@@ -10589,6 +10560,10 @@ public class BatteryStatsImpl extends BatteryStats {
final int batteryConsumerProcessState =
mapUidProcessStateToBatteryConsumerProcessState(uidRunningState);
+ if (mBsi.mSystemReady && Flags.streamlinedBatteryStats()) {
+ mBsi.mHistory.recordProcessStateChange(elapsedRealtimeMs, uptimeMs, mUid,
+ batteryConsumerProcessState);
+ }
getCpuActiveTimeCounter().setState(batteryConsumerProcessState, elapsedRealtimeMs);
getMobileRadioActiveTimeCounter()
@@ -10972,6 +10947,19 @@ public class BatteryStatsImpl extends BatteryStats {
mCpuPowerStatsCollector = new CpuPowerStatsCollector(mCpuScalingPolicies, mPowerProfile,
mHandler, mBatteryStatsConfig.getPowerStatsThrottlePeriodCpu());
+ mCpuPowerStatsCollector.addConsumer(this::recordPowerStats);
+
+ PowerStatsAggregator.Builder builder = new PowerStatsAggregator.Builder(mHistory);
+ builder.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_CPU)
+ .trackDeviceStates(
+ PowerStatsAggregator.STATE_POWER,
+ PowerStatsAggregator.STATE_SCREEN)
+ .trackUidStates(
+ PowerStatsAggregator.STATE_POWER,
+ PowerStatsAggregator.STATE_SCREEN,
+ PowerStatsAggregator.STATE_PROCESS_STATE);
+
+ mPowerStatsAggregator = builder.build();
mStartCount++;
initTimersAndCounters();
@@ -10991,6 +10979,14 @@ public class BatteryStatsImpl extends BatteryStats {
FrameworkStatsLog.write(FrameworkStatsLog.DEVICE_IDLE_MODE_STATE_CHANGED, mDeviceIdleMode);
}
+ private void recordPowerStats(PowerStats stats) {
+ if (stats.durationMs > 0) {
+ synchronized (this) {
+ mHistory.recordPowerStats(mClock.elapsedRealtime(), mClock.uptimeMillis(), stats);
+ }
+ }
+ }
+
@VisibleForTesting
protected void initTimersAndCounters() {
mScreenOnTimer = new StopwatchTimer(mClock, null, -1, null, mOnBatteryTimeBase);
@@ -11100,14 +11096,6 @@ public class BatteryStatsImpl extends BatteryStats {
}
}
- int cpuPowerBracketCount = mPowerProfile.getCpuPowerBracketCount();
- mCpuUsageDetails.cpuBracketDescriptions = new String[cpuPowerBracketCount];
- mCpuUsageDetails.cpuUsageMs = new long[cpuPowerBracketCount];
- for (int i = 0; i < cpuPowerBracketCount; i++) {
- mCpuUsageDetails.cpuBracketDescriptions[i] =
- mPowerProfile.getCpuPowerBracketDescription(mCpuScalingPolicies, i);
- }
-
if (mEstimatedBatteryCapacityMah == -1) {
// Initialize the estimated battery capacity to a known preset one.
mEstimatedBatteryCapacityMah = (int) mPowerProfile.getBatteryCapacity();
@@ -11483,8 +11471,9 @@ public class BatteryStatsImpl extends BatteryStats {
* Creates an iterator for battery stats history.
*/
@Override
- public BatteryStatsHistoryIterator iterateBatteryStatsHistory() {
- return mHistory.copy().iterate();
+ public BatteryStatsHistoryIterator iterateBatteryStatsHistory(long startTimeMs,
+ long endTimeMs) {
+ return mHistory.copy().iterate(startTimeMs, endTimeMs);
}
@Override
@@ -15233,11 +15222,6 @@ public class BatteryStatsImpl extends BatteryStats {
}
@GuardedBy("this")
- boolean isUsageHistoryEnabled() {
- return mConstants.RECORD_USAGE_HISTORY;
- }
-
- @GuardedBy("this")
public void systemServicesReady(Context context) {
mConstants.startObserving(context.getContentResolver());
registerUsbStateReceiver(context);
@@ -15348,8 +15332,6 @@ public class BatteryStatsImpl extends BatteryStats {
public static final String KEY_MAX_HISTORY_BUFFER_KB = "max_history_buffer_kb";
public static final String KEY_BATTERY_CHARGED_DELAY_MS =
"battery_charged_delay_ms";
- public static final String KEY_RECORD_USAGE_HISTORY =
- "record_usage_history";
public static final String KEY_PER_UID_MODEM_POWER_MODEL =
"per_uid_modem_power_model";
public static final String KEY_PHONE_ON_EXTERNAL_STATS_COLLECTION =
@@ -15400,7 +15382,6 @@ public class BatteryStatsImpl extends BatteryStats {
private static final int DEFAULT_MAX_HISTORY_FILES_LOW_RAM_DEVICE = 64;
private static final int DEFAULT_MAX_HISTORY_BUFFER_LOW_RAM_DEVICE_KB = 64; /*Kilo Bytes*/
private static final int DEFAULT_BATTERY_CHARGED_DELAY_MS = 900000; /* 15 min */
- private static final boolean DEFAULT_RECORD_USAGE_HISTORY = false;
@PerUidModemPowerModel
private static final int DEFAULT_PER_UID_MODEM_MODEL =
PER_UID_MODEM_POWER_MODEL_MODEM_ACTIVITY_INFO_RX_TX;
@@ -15422,7 +15403,6 @@ public class BatteryStatsImpl extends BatteryStats {
public int MAX_HISTORY_FILES;
public int MAX_HISTORY_BUFFER; /*Bytes*/
public int BATTERY_CHARGED_DELAY_MS = DEFAULT_BATTERY_CHARGED_DELAY_MS;
- public boolean RECORD_USAGE_HISTORY = DEFAULT_RECORD_USAGE_HISTORY;
public int PER_UID_MODEM_MODEL = DEFAULT_PER_UID_MODEM_MODEL;
public boolean PHONE_ON_EXTERNAL_STATS_COLLECTION =
DEFAULT_PHONE_ON_EXTERNAL_STATS_COLLECTION;
@@ -15503,8 +15483,6 @@ public class BatteryStatsImpl extends BatteryStats {
DEFAULT_MAX_HISTORY_BUFFER_LOW_RAM_DEVICE_KB
: DEFAULT_MAX_HISTORY_BUFFER_KB)
* 1024;
- RECORD_USAGE_HISTORY = mParser.getBoolean(
- KEY_RECORD_USAGE_HISTORY, DEFAULT_RECORD_USAGE_HISTORY);
final String perUidModemModel = mParser.getString(KEY_PER_UID_MODEM_POWER_MODEL,
"");
PER_UID_MODEM_MODEL = getPerUidModemModel(perUidModemModel);
@@ -15582,8 +15560,6 @@ public class BatteryStatsImpl extends BatteryStats {
pw.println(MAX_HISTORY_BUFFER/1024);
pw.print(KEY_BATTERY_CHARGED_DELAY_MS); pw.print("=");
pw.println(BATTERY_CHARGED_DELAY_MS);
- pw.print(KEY_RECORD_USAGE_HISTORY); pw.print("=");
- pw.println(RECORD_USAGE_HISTORY);
pw.print(KEY_PER_UID_MODEM_POWER_MODEL); pw.print("=");
pw.println(getPerUidModemModelName(PER_UID_MODEM_MODEL));
pw.print(KEY_PHONE_ON_EXTERNAL_STATS_COLLECTION); pw.print("=");
@@ -15732,6 +15708,14 @@ public class BatteryStatsImpl extends BatteryStats {
mCpuPowerStatsCollector.collectAndDump(pw);
}
+ /**
+ * Aggregates power stats between the specified times and prints them.
+ */
+ public void dumpAggregatedStats(PrintWriter pw, long startTimeMs, long endTimeMs) {
+ mPowerStatsAggregator.aggregateBatteryStats(startTimeMs, endTimeMs,
+ stats-> stats.dump(pw));
+ }
+
private final Runnable mWriteAsyncRunnable = () -> {
synchronized (BatteryStatsImpl.this) {
writeSyncLocked();
diff --git a/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/MockitoHelper.kt b/services/core/java/com/android/server/power/stats/CpuAggregatedPowerStats.java
index 5ba54c12b0d0..5b3fe064d79a 100644
--- a/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/MockitoHelper.kt
+++ b/services/core/java/com/android/server/power/stats/CpuAggregatedPowerStats.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * 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.
@@ -14,16 +14,16 @@
* limitations under the License.
*/
-package com.android.settingslib.spa.testutils
+package com.android.server.power.stats;
-import org.mockito.Mockito
+import android.os.BatteryConsumer;
-/**
- * Returns Mockito.any() as nullable type to avoid java.lang.IllegalStateException when null is
- * returned.
- *
- * Generic T is nullable because implicitly bounded by Any?.
- */
-fun <T> any(type: Class<T>): T = Mockito.any(type)
+import com.android.internal.os.MultiStateStats;
+
+class CpuAggregatedPowerStats extends PowerComponentAggregatedPowerStats {
-inline fun <reified T> any(): T = any(T::class.java)
+ CpuAggregatedPowerStats(MultiStateStats.States[] deviceStates,
+ MultiStateStats.States[] uidStates) {
+ super(BatteryConsumer.POWER_COMPONENT_CPU, deviceStates, uidStates);
+ }
+}
diff --git a/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java
index 14017467e60f..376ca897fbd1 100644
--- a/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java
+++ b/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java
@@ -16,7 +16,9 @@
package com.android.server.power.stats;
+import android.os.BatteryConsumer;
import android.os.Handler;
+import android.os.PersistableBundle;
import android.util.SparseArray;
import com.android.internal.annotations.Keep;
@@ -42,7 +44,7 @@ public class CpuPowerStatsCollector extends PowerStatsCollector {
private final SparseArray<UidStats> mUidStats = new SparseArray<>();
private final int mUidStatsSize;
// Reusable instance
- private final PowerStats mCpuPowerStats = new PowerStats();
+ private final PowerStats mCpuPowerStats;
private long mLastUpdateTimestampNanos;
public CpuPowerStatsCollector(CpuScalingPolicies cpuScalingPolicies, PowerProfile powerProfile,
@@ -69,6 +71,10 @@ public class CpuPowerStatsCollector extends PowerStatsCollector {
}
mUidStatsSize = powerProfile.getCpuPowerBracketCount();
mTempUidStats = new long[mUidStatsSize];
+
+ mCpuPowerStats = new PowerStats(
+ new PowerStats.Descriptor(BatteryConsumer.POWER_COMPONENT_CPU, 0, mUidStatsSize,
+ new PersistableBundle()));
}
/**
diff --git a/services/core/java/com/android/server/power/stats/EnergyConsumerSnapshot.java b/services/core/java/com/android/server/power/stats/EnergyConsumerSnapshot.java
index 939a08ba0b6a..7f50ae02aa39 100644
--- a/services/core/java/com/android/server/power/stats/EnergyConsumerSnapshot.java
+++ b/services/core/java/com/android/server/power/stats/EnergyConsumerSnapshot.java
@@ -23,7 +23,6 @@ import android.hardware.power.stats.EnergyConsumer;
import android.hardware.power.stats.EnergyConsumerAttribution;
import android.hardware.power.stats.EnergyConsumerResult;
import android.hardware.power.stats.EnergyConsumerType;
-import android.os.BatteryStats.EnergyConsumerDetails;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseIntArray;
@@ -85,8 +84,6 @@ public class EnergyConsumerSnapshot {
*/
private final SparseArray<SparseLongArray> mAttributionSnapshots;
- private EnergyConsumerDetails mEnergyConsumerDetails;
-
/**
* Constructor that initializes to the given id->EnergyConsumer map, indicating which consumers
* exist and what their details are.
@@ -423,122 +420,4 @@ public class EnergyConsumerSnapshot {
// since the last snapshot. Round off to the nearest whole long.
return (deltaEnergyUJ * MILLIVOLTS_PER_VOLT + (avgVoltageMV / 2)) / avgVoltageMV;
}
-
- /**
- * Converts the EnergyConsumerDeltaData object to EnergyConsumerDetails, which can
- * be saved in battery history.
- */
- EnergyConsumerDetails getEnergyConsumerDetails(
- EnergyConsumerDeltaData delta) {
- if (mEnergyConsumerDetails == null) {
- mEnergyConsumerDetails = createEnergyConsumerDetails();
- }
-
- final long[] chargeUC = mEnergyConsumerDetails.chargeUC;
- for (int i = 0; i < mEnergyConsumerDetails.consumers.length; i++) {
- EnergyConsumerDetails.EnergyConsumer energyConsumer =
- mEnergyConsumerDetails.consumers[i];
- switch (energyConsumer.type) {
- case EnergyConsumerType.BLUETOOTH:
- chargeUC[i] = delta.bluetoothChargeUC;
- break;
- case EnergyConsumerType.CPU_CLUSTER:
- if (delta.cpuClusterChargeUC != null) {
- chargeUC[i] = delta.cpuClusterChargeUC[energyConsumer.ordinal];
- } else {
- chargeUC[i] = UNAVAILABLE;
- }
- break;
- case EnergyConsumerType.DISPLAY:
- if (delta.displayChargeUC != null) {
- chargeUC[i] = delta.displayChargeUC[energyConsumer.ordinal];
- } else {
- chargeUC[i] = UNAVAILABLE;
- }
- break;
- case EnergyConsumerType.GNSS:
- chargeUC[i] = delta.gnssChargeUC;
- break;
- case EnergyConsumerType.MOBILE_RADIO:
- chargeUC[i] = delta.mobileRadioChargeUC;
- break;
- case EnergyConsumerType.WIFI:
- chargeUC[i] = delta.wifiChargeUC;
- break;
- case EnergyConsumerType.CAMERA:
- chargeUC[i] = delta.cameraChargeUC;
- break;
- case EnergyConsumerType.OTHER:
- if (delta.otherTotalChargeUC != null) {
- chargeUC[i] = delta.otherTotalChargeUC[energyConsumer.ordinal];
- } else {
- chargeUC[i] = UNAVAILABLE;
- }
- break;
- default:
- chargeUC[i] = UNAVAILABLE;
- break;
- }
- }
- return mEnergyConsumerDetails;
- }
-
- private EnergyConsumerDetails createEnergyConsumerDetails() {
- EnergyConsumerDetails details = new EnergyConsumerDetails();
- details.consumers =
- new EnergyConsumerDetails.EnergyConsumer[mEnergyConsumers.size()];
- for (int i = 0; i < mEnergyConsumers.size(); i++) {
- EnergyConsumer energyConsumer = mEnergyConsumers.valueAt(i);
- EnergyConsumerDetails.EnergyConsumer consumer =
- new EnergyConsumerDetails.EnergyConsumer();
- consumer.type = energyConsumer.type;
- consumer.ordinal = energyConsumer.ordinal;
- switch (consumer.type) {
- case EnergyConsumerType.BLUETOOTH:
- consumer.name = "BLUETOOTH";
- break;
- case EnergyConsumerType.CPU_CLUSTER:
- consumer.name = "CPU";
- break;
- case EnergyConsumerType.DISPLAY:
- consumer.name = "DISPLAY";
- break;
- case EnergyConsumerType.GNSS:
- consumer.name = "GNSS";
- break;
- case EnergyConsumerType.MOBILE_RADIO:
- consumer.name = "MOBILE_RADIO";
- break;
- case EnergyConsumerType.WIFI:
- consumer.name = "WIFI";
- break;
- case EnergyConsumerType.OTHER:
- consumer.name = sanitizeCustomBucketName(energyConsumer.name);
- break;
- default:
- consumer.name = "UNKNOWN";
- break;
- }
- if (consumer.type != EnergyConsumerType.OTHER) {
- boolean hasOrdinal = consumer.ordinal != 0;
- if (!hasOrdinal) {
- // See if any other EnergyConsumer of the same type has an ordinal
- for (int j = 0; j < mEnergyConsumers.size(); j++) {
- EnergyConsumer aConsumer = mEnergyConsumers.valueAt(j);
- if (aConsumer.type == consumer.type && aConsumer.ordinal != 0) {
- hasOrdinal = true;
- break;
- }
- }
- }
- if (hasOrdinal) {
- consumer.name = consumer.name + "/" + energyConsumer.ordinal;
- }
- }
- details.consumers[i] = consumer;
- }
-
- details.chargeUC = new long[details.consumers.length];
- return details;
- }
}
diff --git a/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java b/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java
new file mode 100644
index 000000000000..686268fea22b
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java
@@ -0,0 +1,232 @@
+/*
+ * 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.server.power.stats;
+
+import android.util.IndentingPrintWriter;
+import android.util.SparseArray;
+
+import com.android.internal.os.MultiStateStats;
+import com.android.internal.os.PowerStats;
+
+import java.util.Collection;
+
+/**
+ * Aggregated power stats for a specific power component (e.g. CPU, WiFi, etc). This class
+ * treats stats as arrays of nonspecific longs. Subclasses contain specific logic to interpret those
+ * longs and use them for calculations such as power attribution. They may use meta-data supplied
+ * as part of the {@link PowerStats.Descriptor}.
+ */
+class PowerComponentAggregatedPowerStats {
+ public final int powerComponentId;
+ private final MultiStateStats.States[] mDeviceStateConfig;
+ private final MultiStateStats.States[] mUidStateConfig;
+ private final int[] mDeviceStates;
+ private final long[] mDeviceStateTimestamps;
+
+ private MultiStateStats.Factory mStatsFactory;
+ private MultiStateStats.Factory mUidStatsFactory;
+ private PowerStats.Descriptor mPowerStatsDescriptor;
+ private MultiStateStats mDeviceStats;
+ private final SparseArray<UidStats> mUidStats = new SparseArray<>();
+
+ private static class UidStats {
+ public int[] states;
+ public long[] stateTimestampMs;
+ public MultiStateStats stats;
+ }
+
+ PowerComponentAggregatedPowerStats(int powerComponentId,
+ MultiStateStats.States[] deviceStates,
+ MultiStateStats.States[] uidStates) {
+ this.powerComponentId = powerComponentId;
+ mDeviceStateConfig = deviceStates;
+ mUidStateConfig = uidStates;
+ mDeviceStates = new int[mDeviceStateConfig.length];
+ mDeviceStateTimestamps = new long[mDeviceStateConfig.length];
+ }
+
+ void setState(@PowerStatsAggregator.TrackedState int stateId, int state, long time) {
+ mDeviceStates[stateId] = state;
+ mDeviceStateTimestamps[stateId] = time;
+
+ if (mDeviceStateConfig[stateId].isTracked()) {
+ if (mDeviceStats != null || createDeviceStats()) {
+ mDeviceStats.setState(stateId, state, time);
+ }
+ }
+
+ if (mUidStateConfig[stateId].isTracked()) {
+ for (int i = mUidStats.size() - 1; i >= 0; i--) {
+ PowerComponentAggregatedPowerStats.UidStats uidStats = mUidStats.valueAt(i);
+ if (uidStats.stats != null || createUidStats(uidStats)) {
+ uidStats.stats.setState(stateId, state, time);
+ }
+ }
+ }
+ }
+
+ void setUidState(int uid, @PowerStatsAggregator.TrackedState int stateId, int state,
+ long time) {
+ if (!mUidStateConfig[stateId].isTracked()) {
+ return;
+ }
+
+ UidStats uidStats = getUidStats(uid);
+ uidStats.states[stateId] = state;
+ uidStats.stateTimestampMs[stateId] = time;
+
+ if (uidStats.stats != null || createUidStats(uidStats)) {
+ uidStats.stats.setState(stateId, state, time);
+ }
+ }
+
+ boolean isCompatible(PowerStats powerStats) {
+ return mPowerStatsDescriptor == null || mPowerStatsDescriptor.equals(powerStats.descriptor);
+ }
+
+ void addPowerStats(PowerStats powerStats, long timestampMs) {
+ mPowerStatsDescriptor = powerStats.descriptor;
+
+ if (mDeviceStats == null) {
+ if (mStatsFactory == null) {
+ mStatsFactory = new MultiStateStats.Factory(
+ mPowerStatsDescriptor.statsArrayLength, mDeviceStateConfig);
+ mUidStatsFactory = new MultiStateStats.Factory(
+ mPowerStatsDescriptor.uidStatsArrayLength, mUidStateConfig);
+ }
+
+ createDeviceStats();
+ }
+
+ mDeviceStats.increment(powerStats.stats, timestampMs);
+
+ for (int i = powerStats.uidStats.size() - 1; i >= 0; i--) {
+ int uid = powerStats.uidStats.keyAt(i);
+ PowerComponentAggregatedPowerStats.UidStats uidStats = getUidStats(uid);
+ if (uidStats.stats == null) {
+ createUidStats(uidStats);
+ }
+ uidStats.stats.increment(powerStats.uidStats.valueAt(i), timestampMs);
+ }
+ }
+
+ void reset() {
+ mPowerStatsDescriptor = null;
+ mStatsFactory = null;
+ mUidStatsFactory = null;
+ mDeviceStats = null;
+ for (int i = mUidStats.size() - 1; i >= 0; i--) {
+ mUidStats.valueAt(i).stats = null;
+ }
+ }
+
+ private UidStats getUidStats(int uid) {
+ // TODO(b/292247660): map isolated and sandbox UIDs
+ UidStats uidStats = mUidStats.get(uid);
+ if (uidStats == null) {
+ uidStats = new UidStats();
+ uidStats.states = new int[mUidStateConfig.length];
+ uidStats.stateTimestampMs = new long[mUidStateConfig.length];
+ mUidStats.put(uid, uidStats);
+ }
+ return uidStats;
+ }
+
+ void collectUids(Collection<Integer> uids) {
+ for (int i = mUidStats.size() - 1; i >= 0; i--) {
+ if (mUidStats.valueAt(i).stats != null) {
+ uids.add(mUidStats.keyAt(i));
+ }
+ }
+ }
+
+ boolean getDeviceStats(long[] outValues, int[] deviceStates) {
+ if (deviceStates.length != mDeviceStateConfig.length) {
+ throw new IllegalArgumentException(
+ "Invalid number of tracked states: " + deviceStates.length
+ + " expected: " + mDeviceStateConfig.length);
+ }
+ if (mDeviceStats != null) {
+ mDeviceStats.getStats(outValues, deviceStates);
+ return true;
+ }
+ return false;
+ }
+
+ boolean getUidStats(long[] outValues, int uid, int[] uidStates) {
+ if (uidStates.length != mUidStateConfig.length) {
+ throw new IllegalArgumentException(
+ "Invalid number of tracked states: " + uidStates.length
+ + " expected: " + mUidStateConfig.length);
+ }
+ UidStats uidStats = mUidStats.get(uid);
+ if (uidStats != null && uidStats.stats != null) {
+ uidStats.stats.getStats(outValues, uidStates);
+ return true;
+ }
+ return false;
+ }
+
+ private boolean createDeviceStats() {
+ if (mStatsFactory == null) {
+ return false;
+ }
+
+ mDeviceStats = mStatsFactory.create();
+ for (int stateId = 0; stateId < mDeviceStateConfig.length; stateId++) {
+ mDeviceStats.setState(stateId, mDeviceStates[stateId],
+ mDeviceStateTimestamps[stateId]);
+ }
+ return true;
+ }
+
+ private boolean createUidStats(UidStats uidStats) {
+ if (mUidStatsFactory == null) {
+ return false;
+ }
+
+ uidStats.stats = mUidStatsFactory.create();
+ for (int stateId = 0; stateId < mDeviceStateConfig.length; stateId++) {
+ uidStats.stats.setState(stateId, mDeviceStates[stateId],
+ mDeviceStateTimestamps[stateId]);
+ }
+ for (int stateId = mDeviceStateConfig.length; stateId < mUidStateConfig.length; stateId++) {
+ uidStats.stats.setState(stateId, uidStats.states[stateId],
+ uidStats.stateTimestampMs[stateId]);
+ }
+ return true;
+ }
+
+ void dumpDevice(IndentingPrintWriter ipw) {
+ if (mDeviceStats != null) {
+ ipw.println(mPowerStatsDescriptor.name);
+ ipw.increaseIndent();
+ mDeviceStats.dump(ipw);
+ ipw.decreaseIndent();
+ }
+ }
+
+ void dumpUid(IndentingPrintWriter ipw, int uid) {
+ UidStats uidStats = mUidStats.get(uid);
+ if (uidStats != null && uidStats.stats != null) {
+ ipw.println(mPowerStatsDescriptor.name);
+ ipw.increaseIndent();
+ uidStats.stats.dump(ipw);
+ ipw.decreaseIndent();
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsAggregator.java b/services/core/java/com/android/server/power/stats/PowerStatsAggregator.java
new file mode 100644
index 000000000000..6a1c1da93163
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/PowerStatsAggregator.java
@@ -0,0 +1,226 @@
+/*
+ * 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.server.power.stats;
+
+import android.annotation.IntDef;
+import android.os.BatteryConsumer;
+import android.os.BatteryStats;
+
+import com.android.internal.os.BatteryStatsHistory;
+import com.android.internal.os.BatteryStatsHistoryIterator;
+import com.android.internal.os.MultiStateStats;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+class PowerStatsAggregator {
+ public static final int STATE_POWER = 0;
+ public static final int STATE_SCREEN = 1;
+ public static final int STATE_PROCESS_STATE = 2;
+
+ @IntDef({
+ STATE_POWER,
+ STATE_SCREEN,
+ STATE_PROCESS_STATE,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface TrackedState {
+ }
+
+ static final int POWER_STATE_BATTERY = 0;
+ static final int POWER_STATE_OTHER = 1; // Plugged in, or on wireless charger, etc.
+ static final String[] STATE_LABELS_POWER = {"pwr-battery", "pwr-other"};
+
+ static final int SCREEN_STATE_ON = 0;
+ static final int SCREEN_STATE_OTHER = 1; // Off, doze etc
+ static final String[] STATE_LABELS_SCREEN = {"scr-on", "scr-other"};
+
+ static final String[] STATE_LABELS_PROCESS_STATE;
+
+ static {
+ String[] procStateLabels = new String[BatteryConsumer.PROCESS_STATE_COUNT];
+ for (int i = 0; i < BatteryConsumer.PROCESS_STATE_COUNT; i++) {
+ procStateLabels[i] = BatteryConsumer.processStateToString(i);
+ }
+ STATE_LABELS_PROCESS_STATE = procStateLabels;
+ }
+
+ private final BatteryStatsHistory mHistory;
+ private final AggregatedPowerStats mStats;
+
+ private PowerStatsAggregator(BatteryStatsHistory history,
+ AggregatedPowerStats aggregatedPowerStats) {
+ mHistory = history;
+ mStats = aggregatedPowerStats;
+ }
+
+ /**
+ * Iterates of the battery history and aggregates power stats between the specified times.
+ * The start and end are specified in the battery-stats monotonic time, which is the
+ * adjusted elapsed time found in HistoryItem.time.
+ * <p>
+ * The aggregated stats are sent to the consumer. One aggregation pass may produce
+ * multiple sets of aggregated stats if there was an incompatible change that occurred in the
+ * middle of the recorded battery history.
+ * <p>
+ * Note: the AggregatedPowerStats object is reused, so the consumer should fully consume
+ * the stats in the <code>accept</code> method and never cache it.
+ */
+ void aggregateBatteryStats(long startTimeMs, long endTimeMs,
+ Consumer<AggregatedPowerStats> consumer) {
+ mStats.reset();
+
+ int currentBatteryState = POWER_STATE_BATTERY;
+ int currentScreenState = SCREEN_STATE_OTHER;
+ long baseTime = -1;
+ long lastTime = 0;
+ try (BatteryStatsHistoryIterator iterator =
+ mHistory.copy().iterate(startTimeMs, endTimeMs)) {
+ while (iterator.hasNext()) {
+ BatteryStats.HistoryItem item = iterator.next();
+
+ if (baseTime < 0) {
+ mStats.setStartTime(item.currentTime);
+ baseTime = item.time;
+ }
+
+ lastTime = item.time;
+
+ int batteryState =
+ (item.states & BatteryStats.HistoryItem.STATE_BATTERY_PLUGGED_FLAG) != 0
+ ? POWER_STATE_OTHER : POWER_STATE_BATTERY;
+ if (batteryState != currentBatteryState) {
+ mStats.setDeviceState(STATE_POWER, batteryState, item.time);
+ currentBatteryState = batteryState;
+ }
+
+ int screenState =
+ (item.states & BatteryStats.HistoryItem.STATE_SCREEN_ON_FLAG) != 0
+ ? SCREEN_STATE_ON : SCREEN_STATE_OTHER;
+ if (screenState != currentScreenState) {
+ mStats.setDeviceState(STATE_SCREEN, screenState, item.time);
+ currentScreenState = screenState;
+ }
+
+ if (item.processStateChange != null) {
+ mStats.setUidState(item.processStateChange.uid, STATE_PROCESS_STATE,
+ item.processStateChange.processState, item.time);
+ }
+
+ if (item.powerStats != null) {
+ if (!mStats.isCompatible(item.powerStats)) {
+ mStats.setDuration(lastTime - baseTime);
+ consumer.accept(mStats);
+ mStats.reset();
+ mStats.setStartTime(item.currentTime);
+ baseTime = lastTime = item.time;
+ }
+ mStats.addPowerStats(item.powerStats, item.time);
+ }
+ }
+ }
+ mStats.setDuration(lastTime - baseTime);
+ consumer.accept(mStats);
+ }
+
+ static class Builder {
+ static class PowerComponentAggregateStatsBuilder {
+ private final int mPowerComponentId;
+ private @TrackedState int[] mTrackedDeviceStates;
+ private @TrackedState int[] mTrackedUidStates;
+
+ PowerComponentAggregateStatsBuilder(int powerComponentId) {
+ this.mPowerComponentId = powerComponentId;
+ }
+
+ public PowerComponentAggregateStatsBuilder trackDeviceStates(
+ @TrackedState int... states) {
+ mTrackedDeviceStates = states;
+ return this;
+ }
+
+ public PowerComponentAggregateStatsBuilder trackUidStates(@TrackedState int... states) {
+ mTrackedUidStates = states;
+ return this;
+ }
+
+ private PowerComponentAggregatedPowerStats build() {
+ MultiStateStats.States[] deviceStates = new MultiStateStats.States[]{
+ new MultiStateStats.States(isTracked(mTrackedDeviceStates, STATE_POWER),
+ PowerStatsAggregator.STATE_LABELS_POWER),
+ new MultiStateStats.States(isTracked(mTrackedDeviceStates, STATE_SCREEN),
+ PowerStatsAggregator.STATE_LABELS_SCREEN),
+ };
+
+ MultiStateStats.States[] uidStates = new MultiStateStats.States[]{
+ new MultiStateStats.States(isTracked(mTrackedUidStates, STATE_POWER),
+ PowerStatsAggregator.STATE_LABELS_POWER),
+ new MultiStateStats.States(isTracked(mTrackedUidStates, STATE_SCREEN),
+ PowerStatsAggregator.STATE_LABELS_SCREEN),
+ new MultiStateStats.States(
+ isTracked(mTrackedUidStates, STATE_PROCESS_STATE),
+ PowerStatsAggregator.STATE_LABELS_PROCESS_STATE),
+ };
+
+ switch (mPowerComponentId) {
+ case BatteryConsumer.POWER_COMPONENT_CPU:
+ return new CpuAggregatedPowerStats(deviceStates, uidStates);
+ default:
+ return new PowerComponentAggregatedPowerStats(mPowerComponentId,
+ deviceStates, uidStates);
+ }
+ }
+
+ private boolean isTracked(int[] trackedStates, int state) {
+ if (trackedStates == null) {
+ return false;
+ }
+
+ for (int trackedState : trackedStates) {
+ if (trackedState == state) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ private final BatteryStatsHistory mHistory;
+ private final List<PowerComponentAggregateStatsBuilder> mPowerComponents =
+ new ArrayList<>();
+
+ Builder(BatteryStatsHistory history) {
+ mHistory = history;
+ }
+
+ PowerComponentAggregateStatsBuilder trackPowerComponent(int powerComponentId) {
+ PowerComponentAggregateStatsBuilder builder = new PowerComponentAggregateStatsBuilder(
+ powerComponentId);
+ mPowerComponents.add(builder);
+ return builder;
+ }
+
+ PowerStatsAggregator build() {
+ return new PowerStatsAggregator(mHistory, new AggregatedPowerStats(
+ mPowerComponents.stream()
+ .map(PowerComponentAggregateStatsBuilder::build)
+ .toArray(PowerComponentAggregatedPowerStats[]::new)));
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index ddc05194c300..aaf48fbc01f1 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -2000,12 +2000,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
WallpaperData wallpaper, IRemoteCallback reply, ServiceInfo serviceInfo) {
if (serviceInfo == null) {
- if (wallpaper.mWhich == (FLAG_LOCK | FLAG_SYSTEM)) {
- clearWallpaperLocked(FLAG_SYSTEM, wallpaper.userId, null);
- clearWallpaperLocked(FLAG_LOCK, wallpaper.userId, reply);
- } else {
- clearWallpaperLocked(wallpaper.mWhich, wallpaper.userId, reply);
- }
+ clearWallpaperLocked(wallpaper.mWhich, wallpaper.userId, reply);
return;
}
Slog.w(TAG, "Wallpaper isn't direct boot aware; using fallback until unlocked");
@@ -2037,7 +2032,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
WallpaperData data = null;
synchronized (mLock) {
if (mIsLockscreenLiveWallpaperEnabled) {
- clearWallpaperLocked(callingPackage, which, userId);
+ clearWallpaperLocked(callingPackage, which, userId, null);
} else {
clearWallpaperLocked(which, userId, null);
}
@@ -2057,7 +2052,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
}
}
- private void clearWallpaperLocked(String callingPackage, int which, int userId) {
+ private void clearWallpaperLocked(String callingPackage, int which, int userId,
+ IRemoteCallback reply) {
// Might need to bring it in the first time to establish our rewrite
if (!mWallpaperMap.contains(userId)) {
@@ -2111,8 +2107,14 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
withCleanCallingIdentity(() -> clearWallpaperComponentLocked(wallpaper));
}
- // TODO(b/266818039) remove this version of the method
private void clearWallpaperLocked(int which, int userId, IRemoteCallback reply) {
+
+ if (mIsLockscreenLiveWallpaperEnabled) {
+ String callingPackage = mPackageManagerInternal.getNameForUid(getCallingUid());
+ clearWallpaperLocked(callingPackage, which, userId, reply);
+ return;
+ }
+
if (which != FLAG_SYSTEM && which != FLAG_LOCK) {
throw new IllegalArgumentException("Must specify exactly one kind of wallpaper to clear");
}
@@ -3284,15 +3286,21 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
boolean setWallpaperComponent(ComponentName name, String callingPackage,
@SetWallpaperFlags int which, int userId) {
if (mIsLockscreenLiveWallpaperEnabled) {
- return setWallpaperComponentInternal(name, callingPackage, which, userId);
+ return setWallpaperComponentInternal(name, callingPackage, which, userId, null);
} else {
setWallpaperComponentInternalLegacy(name, callingPackage, which, userId);
return true;
}
}
+ private boolean setWallpaperComponent(ComponentName name, @SetWallpaperFlags int which,
+ int userId) {
+ String callingPackage = mPackageManagerInternal.getNameForUid(getCallingUid());
+ return setWallpaperComponentInternal(name, callingPackage, which, userId, null);
+ }
+
private boolean setWallpaperComponentInternal(ComponentName name, String callingPackage,
- @SetWallpaperFlags int which, int userIdIn) {
+ @SetWallpaperFlags int which, int userIdIn, IRemoteCallback reply) {
if (DEBUG) {
Slog.v(TAG, "Setting new live wallpaper: which=" + which + ", component: " + name);
}
@@ -3341,6 +3349,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
Slog.d(TAG, "publish system wallpaper changed!");
}
liveSync.complete();
+ if (reply != null) reply.sendResult(null);
}
};
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index cba215ad23fd..64c7c6f9875b 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -4381,7 +4381,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// Reset the last saved PiP snap fraction on removal.
mDisplayContent.mPinnedTaskController.onActivityHidden(mActivityComponent);
mDisplayContent.onRunningActivityChanged();
- mWmService.mEmbeddedWindowController.onActivityRemoved(this);
mRemovingFromDisplay = false;
}
diff --git a/services/core/java/com/android/server/wm/AsyncRotationController.java b/services/core/java/com/android/server/wm/AsyncRotationController.java
index 2eceeccd9d8f..025047588ea5 100644
--- a/services/core/java/com/android/server/wm/AsyncRotationController.java
+++ b/services/core/java/com/android/server/wm/AsyncRotationController.java
@@ -172,10 +172,9 @@ class AsyncRotationController extends FadeAnimationController implements Consume
if (recents != null && recents.isNavigationBarAttachedToApp()) {
return;
}
- } else if (navigationBarCanMove || mTransitionOp == OP_CHANGE_MAY_SEAMLESS) {
+ } else if (navigationBarCanMove || mTransitionOp == OP_CHANGE_MAY_SEAMLESS
+ || mDisplayContent.mTransitionController.mNavigationBarAttachedToApp) {
action = Operation.ACTION_SEAMLESS;
- } else if (mDisplayContent.mTransitionController.mNavigationBarAttachedToApp) {
- return;
}
mTargetWindowTokens.put(w.mToken, new Operation(action));
return;
@@ -294,6 +293,11 @@ class AsyncRotationController extends FadeAnimationController implements Consume
finishOp(mTargetWindowTokens.keyAt(i));
}
mTargetWindowTokens.clear();
+ onAllCompleted();
+ }
+
+ private void onAllCompleted() {
+ if (DEBUG) Slog.d(TAG, "onAllCompleted");
if (mTimeoutRunnable != null) {
mService.mH.removeCallbacks(mTimeoutRunnable);
}
@@ -333,7 +337,7 @@ class AsyncRotationController extends FadeAnimationController implements Consume
if (DEBUG) Slog.d(TAG, "Complete directly " + token.getTopChild());
finishOp(token);
if (mTargetWindowTokens.isEmpty()) {
- if (mTimeoutRunnable != null) mService.mH.removeCallbacks(mTimeoutRunnable);
+ onAllCompleted();
return true;
}
}
@@ -411,14 +415,18 @@ class AsyncRotationController extends FadeAnimationController implements Consume
if (mDisplayContent.mInputMethodWindow == null) return;
final WindowToken imeWindowToken = mDisplayContent.mInputMethodWindow.mToken;
if (isTargetToken(imeWindowToken)) return;
+ hideImmediately(imeWindowToken, Operation.ACTION_TOGGLE_IME);
+ if (DEBUG) Slog.d(TAG, "hideImeImmediately " + imeWindowToken.getTopChild());
+ }
+
+ private void hideImmediately(WindowToken token, @Operation.Action int action) {
final boolean original = mHideImmediately;
mHideImmediately = true;
- final Operation op = new Operation(Operation.ACTION_TOGGLE_IME);
- mTargetWindowTokens.put(imeWindowToken, op);
- fadeWindowToken(false /* show */, imeWindowToken, ANIMATION_TYPE_TOKEN_TRANSFORM);
- op.mLeash = imeWindowToken.getAnimationLeash();
+ final Operation op = new Operation(action);
+ mTargetWindowTokens.put(token, op);
+ fadeWindowToken(false /* show */, token, ANIMATION_TYPE_TOKEN_TRANSFORM);
+ op.mLeash = token.getAnimationLeash();
mHideImmediately = original;
- if (DEBUG) Slog.d(TAG, "hideImeImmediately " + imeWindowToken.getTopChild());
}
/** Returns {@code true} if the window will rotate independently. */
@@ -428,11 +436,20 @@ class AsyncRotationController extends FadeAnimationController implements Consume
|| isTargetToken(w.mToken);
}
- /** Returns {@code true} if the controller will run fade animations on the window. */
+ /**
+ * Returns {@code true} if the rotation transition appearance of the window is currently
+ * managed by this controller.
+ */
boolean isTargetToken(WindowToken token) {
return mTargetWindowTokens.containsKey(token);
}
+ /** Returns {@code true} if the controller will run fade animations on the window. */
+ boolean hasFadeOperation(WindowToken token) {
+ final Operation op = mTargetWindowTokens.get(token);
+ return op != null && op.mAction == Operation.ACTION_FADE;
+ }
+
/**
* Whether the insets animation leash should use previous position when running fade animation
* or seamless transformation in a rotated display.
@@ -564,7 +581,18 @@ class AsyncRotationController extends FadeAnimationController implements Consume
return false;
}
final Operation op = mTargetWindowTokens.get(w.mToken);
- if (op == null) return false;
+ if (op == null) {
+ // If a window becomes visible after the rotation transition is requested but before
+ // the transition is ready, hide it by an animation leash so it won't be flickering
+ // by drawing the rotated content before applying projection transaction of display.
+ // And it will fade in after the display transition is finished.
+ if (mTransitionOp == OP_APP_SWITCH && !mIsStartTransactionCommitted
+ && canBeAsync(w.mToken)) {
+ hideImmediately(w.mToken, Operation.ACTION_FADE);
+ if (DEBUG) Slog.d(TAG, "Hide on finishDrawing " + w.mToken.getTopChild());
+ }
+ return false;
+ }
if (DEBUG) Slog.d(TAG, "handleFinishDrawing " + w);
if (postDrawTransaction == null || !mIsSyncDrawRequested
|| canDrawBeforeStartTransaction(op)) {
diff --git a/services/core/java/com/android/server/wm/EmbeddedWindowController.java b/services/core/java/com/android/server/wm/EmbeddedWindowController.java
index 98027bbed37f..c9bae127b800 100644
--- a/services/core/java/com/android/server/wm/EmbeddedWindowController.java
+++ b/services/core/java/com/android/server/wm/EmbeddedWindowController.java
@@ -135,19 +135,6 @@ class EmbeddedWindowController {
return mWindowsByWindowToken.get(windowToken);
}
- void onActivityRemoved(ActivityRecord activityRecord) {
- for (int i = mWindows.size() - 1; i >= 0; i--) {
- final EmbeddedWindow window = mWindows.valueAt(i);
- if (window.mHostActivityRecord == activityRecord) {
- final WindowProcessController processController =
- mAtmService.getProcessController(window.mOwnerPid, window.mOwnerUid);
- if (processController != null) {
- processController.removeHostActivity(activityRecord);
- }
- }
- }
- }
-
static class EmbeddedWindow implements InputTarget {
final IWindow mClient;
@Nullable final WindowState mHostWindowState;
@@ -230,6 +217,13 @@ class EmbeddedWindowController {
mInputChannel.dispose();
mInputChannel = null;
}
+ if (mHostActivityRecord != null) {
+ final WindowProcessController wpc =
+ mWmService.mAtmService.getProcessController(mOwnerPid, mOwnerUid);
+ if (wpc != null) {
+ wpc.removeHostActivity(mHostActivityRecord);
+ }
+ }
}
@Override
diff --git a/services/core/java/com/android/server/wm/NavBarFadeAnimationController.java b/services/core/java/com/android/server/wm/NavBarFadeAnimationController.java
index 2e5474e5e415..79b26d2ee994 100644
--- a/services/core/java/com/android/server/wm/NavBarFadeAnimationController.java
+++ b/services/core/java/com/android/server/wm/NavBarFadeAnimationController.java
@@ -86,7 +86,7 @@ public class NavBarFadeAnimationController extends FadeAnimationController{
ANIMATION_TYPE_TOKEN_TRANSFORM);
if (controller == null) {
fadeAnim.run();
- } else if (!controller.isTargetToken(mNavigationBar.mToken)) {
+ } else if (!controller.hasFadeOperation(mNavigationBar.mToken)) {
// If fade rotation animation is running and the nav bar is not controlled by it:
// - For fade-in animation, defer the animation until fade rotation animation finishes.
// - For fade-out animation, just play the animation.
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 1566bb2c8610..e9af42bb7b90 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -1930,11 +1930,6 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
break;
}
- final AsyncRotationController asyncRotationController = dc.getAsyncRotationController();
- if (asyncRotationController != null) {
- asyncRotationController.accept(navWindow);
- }
-
if (animate) {
final NavBarFadeAnimationController controller =
new NavBarFadeAnimationController(dc);
diff --git a/services/tests/powerstatstests/Android.bp b/services/tests/powerstatstests/Android.bp
index 5ea1929e185f..8ab45070a017 100644
--- a/services/tests/powerstatstests/Android.bp
+++ b/services/tests/powerstatstests/Android.bp
@@ -23,6 +23,7 @@ android_test {
"androidx.test.uiautomator_uiautomator",
"mockito-target-minus-junit4",
"servicestests-utils",
+ "platform-test-annotations",
"flag-junit",
],
diff --git a/services/tests/powerstatstests/AndroidManifest.xml b/services/tests/powerstatstests/AndroidManifest.xml
index d3a88d2bc38c..d6898a1f6589 100644
--- a/services/tests/powerstatstests/AndroidManifest.xml
+++ b/services/tests/powerstatstests/AndroidManifest.xml
@@ -21,6 +21,7 @@
<uses-permission android:name="android.permission.CHANGE_COMPONENT_ENABLED_STATE"/>
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL"/>
<uses-permission android:name="android.permission.MANAGE_USERS"/>
+ <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
<queries>
<package android:name="com.android.coretests.apps.bstatstestapp" />
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java
index 4fde73bd8408..77124d0120f7 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java
@@ -32,6 +32,8 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Random;
import java.util.concurrent.Future;
@@ -49,6 +51,7 @@ public class BatteryStatsHistoryIteratorTest {
@Before
public void setup() {
final File historyDir = createTemporaryDirectory(getClass().getSimpleName());
+ mMockClock.currentTime = 3000;
mBatteryStats = new MockBatteryStatsImpl(mMockClock, historyDir);
mBatteryStats.setDummyExternalStatsSync(mExternalStatsSync);
mBatteryStats.setRecordAllHistoryLocked(true);
@@ -70,20 +73,10 @@ public class BatteryStatsHistoryIteratorTest {
}
@Test
- public void testIterator() {
- mMockClock.realtime = 1000;
- mMockClock.uptime = 1000;
+ public void unconstrainedIteration() {
+ prepareHistory();
- mBatteryStats.setBatteryStateLocked(BatteryManager.BATTERY_STATUS_DISCHARGING,
- 100, /* plugType */ 0, 90, 72, 3700, 3_600_000, 4_000_000, 0, 1_000_000,
- 1_000_000, 1_000_000);
- mBatteryStats.setBatteryStateLocked(BatteryManager.BATTERY_STATUS_DISCHARGING,
- 100, /* plugType */ 0, 80, 72, 3700, 2_400_000, 4_000_000, 0, 2_000_000,
- 2_000_000, 2_000_000);
- mBatteryStats.noteAlarmStartLocked("foo", null, APP_UID, 3_000_000, 2_000_000);
- mBatteryStats.noteAlarmFinishLocked("foo", null, APP_UID, 3_001_000, 2_001_000);
-
- final BatteryStatsHistoryIterator iterator = mBatteryStats.iterateBatteryStatsHistory();
+ final BatteryStatsHistoryIterator iterator = mBatteryStats.iterateBatteryStatsHistory(0, 0);
BatteryStats.HistoryItem item;
@@ -116,23 +109,75 @@ public class BatteryStatsHistoryIteratorTest {
assertThat(iterator.next()).isNull();
}
- // Test history that spans multiple buffers and uses more than 32k different strings.
@Test
- public void tagsLongHistory() {
+ public void constrainedIteration() {
+ prepareHistory();
+
+ // Initial time is 3000
+ assertIncludedEvents(mBatteryStats.iterateBatteryStatsHistory(0, 0),
+ 3_000L, 3_000L, 1003_000L, 2003_000L, 2004_000L);
+ assertIncludedEvents(mBatteryStats.iterateBatteryStatsHistory(1000_000, 0),
+ 1003_000L, 2003_000L, 2004_000L);
+ assertIncludedEvents(mBatteryStats.iterateBatteryStatsHistory(0, 2000_000L),
+ 3_000L, 3_000L, 1003_000L);
+ assertIncludedEvents(mBatteryStats.iterateBatteryStatsHistory(1003_000L, 2004_000L),
+ 1003_000L, 2003_000L);
+ }
+
+ private void prepareHistory() {
+ mMockClock.realtime = 1000;
+ mMockClock.uptime = 1000;
+ mMockClock.currentTime = 3000;
+
mBatteryStats.setBatteryStateLocked(BatteryManager.BATTERY_STATUS_DISCHARGING,
100, /* plugType */ 0, 90, 72, 3700, 3_600_000, 4_000_000, 0, 1_000_000,
1_000_000, 1_000_000);
+ mBatteryStats.setBatteryStateLocked(BatteryManager.BATTERY_STATUS_DISCHARGING,
+ 100, /* plugType */ 0, 80, 72, 3700, 2_400_000, 4_000_000, 0, 2_000_000,
+ 2_000_000, 2_000_000);
+ mBatteryStats.noteAlarmStartLocked("foo", null, APP_UID, 3_000_000, 2_000_000);
+ mBatteryStats.noteAlarmFinishLocked("foo", null, APP_UID, 3_001_000, 2_001_000);
+ }
+
+ private void assertIncludedEvents(BatteryStatsHistoryIterator iterator,
+ Long... expectedTimestamps) {
+ ArrayList<Long> actualTimestamps = new ArrayList<>();
+ while (iterator.hasNext()) {
+ BatteryStats.HistoryItem item = iterator.next();
+ actualTimestamps.add(item.currentTime);
+ }
+ assertThat(actualTimestamps).isEqualTo(Arrays.asList(expectedTimestamps));
+ }
+
+ // Test history that spans multiple buffers and uses more than 32k different strings.
+ @Test
+ public void tagsLongHistory() {
+ mMockClock.currentTime = 1_000_000;
+ mMockClock.realtime = 1_000_000;
+ mMockClock.uptime = 1_000_000;
+
+ mBatteryStats.setBatteryStateLocked(BatteryManager.BATTERY_STATUS_DISCHARGING,
+ 100, /* plugType */ 0, 90, 72, 3700, 3_600_000, 4_000_000, 0, mMockClock.realtime,
+ mMockClock.uptime, mMockClock.currentTime);
// More than 32k strings
final int eventCount = 0x7FFF + 100;
for (int i = 0; i < eventCount; i++) {
// Names repeat in order to verify de-duping of identical history tags.
String name = "a" + (i % 10);
- mBatteryStats.noteAlarmStartLocked(name, null, APP_UID, 3_000_000, 2_000_000);
- mBatteryStats.noteAlarmFinishLocked(name, null, APP_UID, 3_500_000, 2_500_000);
+ mMockClock.currentTime += 1_000_000;
+ mMockClock.realtime += 1_000_000;
+ mMockClock.uptime += 1_000_000;
+ mBatteryStats.noteAlarmStartLocked(name, null, APP_UID,
+ mMockClock.realtime, mMockClock.uptime);
+ mMockClock.currentTime += 500_000;
+ mMockClock.realtime += 500_000;
+ mMockClock.uptime += 500_000;
+ mBatteryStats.noteAlarmFinishLocked(name, null, APP_UID,
+ mMockClock.realtime, mMockClock.uptime);
}
- final BatteryStatsHistoryIterator iterator = mBatteryStats.iterateBatteryStatsHistory();
+ final BatteryStatsHistoryIterator iterator = mBatteryStats.iterateBatteryStatsHistory(0, 0);
BatteryStats.HistoryItem item;
assertThat(item = iterator.next()).isNotNull();
@@ -146,12 +191,6 @@ public class BatteryStatsHistoryIteratorTest {
assertThat(item.eventTag).isNull();
assertThat(item.time).isEqualTo(1_000_000);
- assertThat(item = iterator.next()).isNotNull();
- assertThat(item.cmd).isEqualTo((int) BatteryStats.HistoryItem.CMD_UPDATE);
- assertThat(item.eventCode).isEqualTo(BatteryStats.HistoryItem.EVENT_NONE);
- assertThat(item.eventTag).isNull();
- assertThat(item.time).isEqualTo(2_000_000);
-
for (int i = 0; i < eventCount; i++) {
String name = "a" + (i % 10);
do {
@@ -205,7 +244,7 @@ public class BatteryStatsHistoryIteratorTest {
mExternalStatsSync.updateCpuStats(300, 7_100_000, 4_100_000);
- final BatteryStatsHistoryIterator iterator = mBatteryStats.iterateBatteryStatsHistory();
+ final BatteryStatsHistoryIterator iterator = mBatteryStats.iterateBatteryStatsHistory(0, 0);
BatteryStats.HistoryItem item;
assertThat(item = iterator.next()).isNotNull();
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java
index f2cbef69c8e5..f22296a6261c 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java
@@ -24,13 +24,16 @@ import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.when;
import android.content.Context;
+import android.os.BatteryConsumer;
import android.os.BatteryManager;
import android.os.BatteryStats;
-import android.os.BatteryStats.CpuUsageDetails;
-import android.os.BatteryStats.EnergyConsumerDetails;
import android.os.BatteryStats.HistoryItem;
import android.os.Parcel;
+import android.os.PersistableBundle;
+import android.os.Process;
+import android.os.UserHandle;
import android.telephony.NetworkRegistrationInfo;
+import android.util.AtomicFile;
import android.util.Log;
import androidx.test.InstrumentationRegistry;
@@ -38,6 +41,7 @@ import androidx.test.runner.AndroidJUnit4;
import com.android.internal.os.BatteryStatsHistory;
import com.android.internal.os.BatteryStatsHistoryIterator;
+import com.android.internal.os.PowerStats;
import org.junit.Before;
import org.junit.Test;
@@ -71,6 +75,7 @@ public class BatteryStatsHistoryTest {
private BatteryStatsHistory.TraceDelegate mTracer;
@Mock
private BatteryStatsHistory.HistoryStepDetailsCalculator mStepDetailsCalculator;
+ private List<String> mReadFiles = new ArrayList<>();
@Before
public void setUp() {
@@ -85,8 +90,17 @@ public class BatteryStatsHistoryTest {
}
}
mHistoryDir.delete();
+
+ mClock.realtime = 123;
+
mHistory = new BatteryStatsHistory(mHistoryBuffer, mSystemDir, 32, 1024,
- mStepDetailsCalculator, mClock, mTracer);
+ mStepDetailsCalculator, mClock, mTracer) {
+ @Override
+ public boolean readFileToParcel(Parcel out, AtomicFile file) {
+ mReadFiles.add(file.getBaseFile().getName());
+ return super.readFileToParcel(out, file);
+ }
+ };
when(mStepDetailsCalculator.getHistoryStepDetails())
.thenReturn(new BatteryStats.HistoryStepDetails());
@@ -179,70 +193,165 @@ public class BatteryStatsHistoryTest {
@Test
public void testConstruct() {
createActiveFile(mHistory);
- verifyFileNumbers(mHistory, Arrays.asList(0));
- verifyActiveFile(mHistory, "0.bin");
+ verifyFileNames(mHistory, Arrays.asList("123.bh"));
+ verifyActiveFile(mHistory, "123.bh");
}
@Test
public void testStartNextFile() {
- List<Integer> fileList = new ArrayList<>();
- fileList.add(0);
+ mClock.realtime = 123;
+
+ List<String> fileList = new ArrayList<>();
+ fileList.add("123.bh");
createActiveFile(mHistory);
// create file 1 to 31.
for (int i = 1; i < 32; i++) {
- fileList.add(i);
+ mClock.realtime = 1000 * i;
+ fileList.add(mClock.realtime + ".bh");
+
mHistory.startNextFile();
createActiveFile(mHistory);
- verifyFileNumbers(mHistory, fileList);
- verifyActiveFile(mHistory, i + ".bin");
+ verifyFileNames(mHistory, fileList);
+ verifyActiveFile(mHistory, mClock.realtime + ".bh");
}
// create file 32
+ mClock.realtime = 1000 * 32;
mHistory.startNextFile();
createActiveFile(mHistory);
- fileList.add(32);
+ fileList.add("32000.bh");
fileList.remove(0);
// verify file 0 is deleted.
- verifyFileDeleted("0.bin");
- verifyFileNumbers(mHistory, fileList);
- verifyActiveFile(mHistory, "32.bin");
+ verifyFileDeleted("123.bh");
+ verifyFileNames(mHistory, fileList);
+ verifyActiveFile(mHistory, "32000.bh");
// create file 33
+ mClock.realtime = 1000 * 33;
mHistory.startNextFile();
createActiveFile(mHistory);
// verify file 1 is deleted
- fileList.add(33);
+ fileList.add("33000.bh");
fileList.remove(0);
- verifyFileDeleted("1.bin");
- verifyFileNumbers(mHistory, fileList);
- verifyActiveFile(mHistory, "33.bin");
-
- assertEquals(0, mHistory.getHistoryUsedSize());
+ verifyFileDeleted("1000.bh");
+ verifyFileNames(mHistory, fileList);
+ verifyActiveFile(mHistory, "33000.bh");
// create a new BatteryStatsHistory object, it will pick up existing history files.
BatteryStatsHistory history2 = new BatteryStatsHistory(mHistoryBuffer, mSystemDir, 32, 1024,
null, mClock, mTracer);
// verify constructor can pick up all files from file system.
- verifyFileNumbers(history2, fileList);
- verifyActiveFile(history2, "33.bin");
+ verifyFileNames(history2, fileList);
+ verifyActiveFile(history2, "33000.bh");
+
+ mClock.realtime = 1234567;
history2.reset();
createActiveFile(history2);
+
// verify all existing files are deleted.
- for (int i = 2; i < 33; ++i) {
- verifyFileDeleted(i + ".bin");
+ for (String file : fileList) {
+ verifyFileDeleted(file);
}
// verify file 0 is created
- verifyFileNumbers(history2, Arrays.asList(0));
- verifyActiveFile(history2, "0.bin");
+ verifyFileNames(history2, Arrays.asList("1234567.bh"));
+ verifyActiveFile(history2, "1234567.bh");
// create file 1.
+ mClock.realtime = 2345678;
+
history2.startNextFile();
createActiveFile(history2);
- verifyFileNumbers(history2, Arrays.asList(0, 1));
- verifyActiveFile(history2, "1.bin");
+ verifyFileNames(history2, Arrays.asList("1234567.bh", "2345678.bh"));
+ verifyActiveFile(history2, "2345678.bh");
+ }
+
+ @Test
+ public void unconstrainedIteration() {
+ prepareMultiFileHistory();
+
+ mReadFiles.clear();
+
+ // Prepare history for iteration
+ mHistory.iterate(0, 0);
+
+ Parcel parcel = mHistory.getNextParcel(0, Long.MAX_VALUE);
+ assertThat(parcel).isNotNull();
+ assertThat(mReadFiles).containsExactly("123.bh");
+
+ // Skip to the end to force reading the next parcel
+ parcel.setDataPosition(parcel.dataSize());
+ mReadFiles.clear();
+ parcel = mHistory.getNextParcel(0, Long.MAX_VALUE);
+ assertThat(parcel).isNotNull();
+ assertThat(mReadFiles).containsExactly("1000.bh");
+
+ parcel.setDataPosition(parcel.dataSize());
+ mReadFiles.clear();
+ parcel = mHistory.getNextParcel(0, Long.MAX_VALUE);
+ assertThat(parcel).isNotNull();
+ assertThat(mReadFiles).containsExactly("2000.bh");
+
+ parcel.setDataPosition(parcel.dataSize());
+ mReadFiles.clear();
+ parcel = mHistory.getNextParcel(0, Long.MAX_VALUE);
+ assertThat(parcel).isNull();
+ assertThat(mReadFiles).isEmpty();
+ }
+
+ @Test
+ public void constrainedIteration() {
+ prepareMultiFileHistory();
+
+ mReadFiles.clear();
+
+ // Prepare history for iteration
+ mHistory.iterate(1000, 3000);
+
+ Parcel parcel = mHistory.getNextParcel(1000, 3000);
+ assertThat(parcel).isNotNull();
+ assertThat(mReadFiles).containsExactly("1000.bh");
+
+ // Skip to the end to force reading the next parcel
+ parcel.setDataPosition(parcel.dataSize());
+ mReadFiles.clear();
+ parcel = mHistory.getNextParcel(1000, 3000);
+ assertThat(parcel).isNotNull();
+ assertThat(mReadFiles).containsExactly("2000.bh");
+
+ parcel.setDataPosition(parcel.dataSize());
+ mReadFiles.clear();
+ parcel = mHistory.getNextParcel(1000, 3000);
+ assertThat(parcel).isNull();
+ assertThat(mReadFiles).isEmpty();
+ }
+
+ private void prepareMultiFileHistory() {
+ mHistory.forceRecordAllHistory();
+
+ mClock.realtime = 1000;
+ mClock.uptime = 1000;
+ mHistory.recordEvent(mClock.realtime, mClock.uptime,
+ BatteryStats.HistoryItem.EVENT_JOB_START, "job", 42);
+
+ mHistory.startNextFile(); // 1000.bh
+
+ mClock.realtime = 2000;
+ mClock.uptime = 2000;
+ mHistory.recordEvent(mClock.realtime, mClock.uptime,
+ BatteryStats.HistoryItem.EVENT_JOB_FINISH, "job", 42);
+
+ mHistory.startNextFile(); // 2000.bh
+
+ mClock.realtime = 3000;
+ mClock.uptime = 3000;
+ mHistory.recordEvent(mClock.realtime, mClock.uptime,
+ HistoryItem.EVENT_ALARM, "alarm", 42);
+
+ // Flush accumulated history to disk
+ mHistory.startNextFile();
}
private void verifyActiveFile(BatteryStatsHistory history, String file) {
@@ -251,12 +360,11 @@ public class BatteryStatsHistoryTest {
assertTrue(expectedFile.exists());
}
- private void verifyFileNumbers(BatteryStatsHistory history, List<Integer> fileList) {
- assertEquals(fileList.size(), history.getFilesNumbers().size());
+ private void verifyFileNames(BatteryStatsHistory history, List<String> fileList) {
+ assertEquals(fileList.size(), history.getFilesNames().size());
for (int i = 0; i < fileList.size(); i++) {
- assertEquals(fileList.get(i), history.getFilesNumbers().get(i));
- final File expectedFile =
- new File(mHistoryDir, fileList.get(i) + ".bin");
+ assertEquals(fileList.get(i), history.getFilesNames().get(i));
+ final File expectedFile = new File(mHistoryDir, fileList.get(i));
assertTrue(expectedFile.exists());
}
}
@@ -267,6 +375,9 @@ public class BatteryStatsHistoryTest {
private void createActiveFile(BatteryStatsHistory history) {
final File file = history.getActiveFile().getBaseFile();
+ if (file.exists()) {
+ return;
+ }
try {
file.createNewFile();
} catch (IOException e) {
@@ -275,49 +386,18 @@ public class BatteryStatsHistoryTest {
}
@Test
- public void testRecordMeasuredEnergyDetails() {
- mHistory.forceRecordAllHistory();
- mHistory.startRecordingHistory(0, 0, /* reset */ true);
- mHistory.setBatteryState(true /* charging */, BatteryManager.BATTERY_STATUS_CHARGING, 80,
- 1234);
-
- EnergyConsumerDetails details = new EnergyConsumerDetails();
- EnergyConsumerDetails.EnergyConsumer consumer1 =
- new EnergyConsumerDetails.EnergyConsumer();
- consumer1.type = 42;
- consumer1.ordinal = 0;
- consumer1.name = "A";
-
- EnergyConsumerDetails.EnergyConsumer consumer2 =
- new EnergyConsumerDetails.EnergyConsumer();
- consumer2.type = 777;
- consumer2.ordinal = 0;
- consumer2.name = "B/0";
-
- EnergyConsumerDetails.EnergyConsumer consumer3 =
- new EnergyConsumerDetails.EnergyConsumer();
- consumer3.type = 777;
- consumer3.ordinal = 1;
- consumer3.name = "B/1";
-
- EnergyConsumerDetails.EnergyConsumer consumer4 =
- new EnergyConsumerDetails.EnergyConsumer();
- consumer4.type = 314;
- consumer4.ordinal = 1;
- consumer4.name = "C";
-
- details.consumers =
- new EnergyConsumerDetails.EnergyConsumer[]{consumer1, consumer2, consumer3,
- consumer4};
- details.chargeUC = new long[details.consumers.length];
- for (int i = 0; i < details.chargeUC.length; i++) {
- details.chargeUC[i] = 100L * i;
- }
- details.chargeUC[3] = BatteryStats.POWER_DATA_UNAVAILABLE;
-
- mHistory.recordEnergyConsumerDetails(200, 200, details);
-
- BatteryStatsHistoryIterator iterator = mHistory.iterate();
+ public void recordPowerStats() {
+ PowerStats.Descriptor descriptor = new PowerStats.Descriptor(42, "foo", 1, 2,
+ new PersistableBundle());
+ PowerStats powerStats = new PowerStats(descriptor);
+ powerStats.durationMs = 100;
+ powerStats.stats[0] = 200;
+ powerStats.uidStats.put(300, new long[]{400, 500});
+ powerStats.uidStats.put(600, new long[]{700, 800});
+
+ mHistory.recordPowerStats(200, 200, powerStats);
+
+ BatteryStatsHistoryIterator iterator = mHistory.iterate(0, 0);
BatteryStats.HistoryItem item;
assertThat(item = iterator.next()).isNotNull(); // First item contains current time only
@@ -325,62 +405,10 @@ public class BatteryStatsHistoryTest {
String dump = toString(item, /* checkin */ false);
assertThat(dump).contains("+200ms");
- assertThat(dump).contains("ext=energy:A=0 B/0=100 B/1=200");
- assertThat(dump).doesNotContain("C=");
-
- String checkin = toString(item, /* checkin */ true);
- assertThat(checkin).contains("XE");
- assertThat(checkin).contains("A=0,B/0=100,B/1=200");
- assertThat(checkin).doesNotContain("C=");
- }
-
- @Test
- public void cpuUsageDetails() {
- mHistory.forceRecordAllHistory();
- mHistory.startRecordingHistory(0, 0, /* reset */ true);
- mHistory.setBatteryState(true /* charging */, BatteryManager.BATTERY_STATUS_CHARGING, 80,
- 1234);
-
- CpuUsageDetails details = new CpuUsageDetails();
- details.cpuBracketDescriptions = new String[] {"low", "Med", "HIGH"};
- details.uid = 10123;
- details.cpuUsageMs = new long[] { 100, 200, 300};
- mHistory.recordCpuUsage(200, 200, details);
-
- details.uid = 10321;
- details.cpuUsageMs = new long[] { 400, 500, 600};
- mHistory.recordCpuUsage(300, 300, details);
-
- BatteryStatsHistoryIterator iterator = mHistory.iterate();
- BatteryStats.HistoryItem item = new BatteryStats.HistoryItem();
- assertThat(item = iterator.next()).isNotNull(); // First item contains current time only
-
- assertThat(item = iterator.next()).isNotNull();
-
- String dump = toString(item, /* checkin */ false);
- assertThat(dump).contains("+200ms");
- assertThat(dump).contains("ext=cpu:u0a123: 100, 200, 300");
- assertThat(dump).contains("ext=cpu-bracket:0:low");
- assertThat(dump).contains("ext=cpu-bracket:1:Med");
- assertThat(dump).contains("ext=cpu-bracket:2:HIGH");
-
- String checkin = toString(item, /* checkin */ true);
- assertThat(checkin).contains("XB,3,0,low");
- assertThat(checkin).contains("XB,3,1,Med");
- assertThat(checkin).contains("XB,3,2,HIGH");
- assertThat(checkin).contains("XC,10123,100,200,300");
-
- assertThat(item = iterator.next()).isNotNull();
-
- dump = toString(item, /* checkin */ false);
- assertThat(dump).contains("+300ms");
- assertThat(dump).contains("ext=cpu:u0a321: 400, 500, 600");
- // Power bracket descriptions are written only once
- assertThat(dump).doesNotContain("ext=cpu-bracket");
-
- checkin = toString(item, /* checkin */ true);
- assertThat(checkin).doesNotContain("XB");
- assertThat(checkin).contains("XC,10321,400,500,600");
+ assertThat(dump).contains("duration=100");
+ assertThat(dump).contains("foo=[200]");
+ assertThat(dump).contains("300: [400, 500]");
+ assertThat(dump).contains("600: [700, 800]");
}
@Test
@@ -399,7 +427,7 @@ public class BatteryStatsHistoryTest {
mHistory.recordNrStateChangeEvent(500, 500,
NetworkRegistrationInfo.NR_STATE_NONE);
- BatteryStatsHistoryIterator iterator = mHistory.iterate();
+ BatteryStatsHistoryIterator iterator = mHistory.iterate(0, 0);
BatteryStats.HistoryItem item = new BatteryStats.HistoryItem();
assertThat(item = iterator.next()).isNotNull(); // First item contains current time only
@@ -440,7 +468,7 @@ public class BatteryStatsHistoryTest {
mHistory.recordNrStateChangeEvent(500, 500,
NetworkRegistrationInfo.NR_STATE_NONE);
- BatteryStatsHistoryIterator iterator = mHistory.iterate();
+ BatteryStatsHistoryIterator iterator = mHistory.iterate(0, 0);
BatteryStats.HistoryItem item = new BatteryStats.HistoryItem();
assertThat(item = iterator.next()).isNotNull(); // First item contains current time only
@@ -498,7 +526,7 @@ public class BatteryStatsHistoryTest {
mClock.uptime = 1_000_000;
// More than 32k strings
final int tagCount = 0x7FFF + 20;
- for (int tag = 0; tag < tagCount;) {
+ for (int tag = 0; tag < tagCount; ) {
mClock.realtime += 10;
mClock.uptime += 10;
mHistory.recordEvent(mClock.realtime, mClock.uptime, HistoryItem.EVENT_ALARM_START,
@@ -522,8 +550,8 @@ public class BatteryStatsHistoryTest {
int wakelockTagsUnpooled = 0;
int wakeReasonTagsPooled = 0;
int wakeReasonTagsUnpooled = 0;
- for (BatteryStatsHistoryIterator iterator = mHistory.iterate(); iterator.hasNext(); ) {
- HistoryItem item = iterator.next();
+ for (BatteryStatsHistoryIterator iterator = mHistory.iterate(0, 0); iterator.hasNext(); ) {
+ HistoryItem item = iterator.next();
if (item.cmd != HistoryItem.CMD_UPDATE) {
continue;
}
@@ -569,10 +597,40 @@ public class BatteryStatsHistoryTest {
assertThat(wakeReasonTagsUnpooled).isGreaterThan(0);
}
+ @Test
+ public void recordProcStateChange() {
+ mHistory.recordProcessStateChange(200, 200, 42, BatteryConsumer.PROCESS_STATE_BACKGROUND);
+ mHistory.recordProcessStateChange(300, 300, 42, BatteryConsumer.PROCESS_STATE_FOREGROUND);
+ // Large UID, > 0xFFFFFF
+ mHistory.recordProcessStateChange(400, 400,
+ UserHandle.getUid(777, Process.LAST_ISOLATED_UID),
+ BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE);
+
+ BatteryStatsHistoryIterator iterator = mHistory.iterate(0, 0);
+ BatteryStats.HistoryItem item;
+ assertThat(item = iterator.next()).isNotNull(); // First item contains current time only
+
+ assertThat(item = iterator.next()).isNotNull();
+
+ String dump = toString(item, /* checkin */ false);
+ assertThat(dump).contains("+200ms");
+ assertThat(dump).contains("procstate: 42: bg");
+
+ assertThat(item = iterator.next()).isNotNull();
+ dump = toString(item, /* checkin */ false);
+ assertThat(dump).contains("+300ms");
+ assertThat(dump).contains("procstate: 42: fg");
+
+ assertThat(item = iterator.next()).isNotNull();
+ dump = toString(item, /* checkin */ false);
+ assertThat(dump).contains("+400ms");
+ assertThat(dump).contains("procstate: u777i999: fgs");
+ }
+
private String toString(BatteryStats.HistoryItem item, boolean checkin) {
StringWriter writer = new StringWriter();
PrintWriter pw = new PrintWriter(writer);
- mHistoryPrinter.printNextItem(pw, item, 0, checkin, /* verbose */ false);
+ mHistoryPrinter.printNextItem(pw, item, 0, checkin, /* verbose */ true);
pw.flush();
return writer.toString();
}
@@ -596,6 +654,7 @@ public class BatteryStatsHistoryTest {
0xffffffffffffffffL};
// Parcel subarrays of different lengths and assert the size of the resulting parcel
+ testVarintParceler(Arrays.copyOfRange(values, 0, 0), 0);
testVarintParceler(Arrays.copyOfRange(values, 0, 1), 4); // v. 8
testVarintParceler(Arrays.copyOfRange(values, 0, 2), 4); // v. 16
testVarintParceler(Arrays.copyOfRange(values, 0, 3), 4); // v. 24
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsNoteTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsNoteTest.java
index 88b9522d4cb1..7ef1a3fd0d83 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsNoteTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsNoteTest.java
@@ -263,7 +263,7 @@ public class BatteryStatsNoteTest extends TestCase {
clocks.realtime = clocks.uptime = 220;
bi.noteLongPartialWakelockFinish(name, historyName, ISOLATED_UID);
- final BatteryStatsHistoryIterator iterator = bi.iterateBatteryStatsHistory();
+ final BatteryStatsHistoryIterator iterator = bi.iterateBatteryStatsHistory(0, 0);
BatteryStats.HistoryItem item;
@@ -319,7 +319,7 @@ public class BatteryStatsNoteTest extends TestCase {
clocks.realtime = clocks.uptime = 220;
bi.noteLongPartialWakelockFinish(name, historyName, ISOLATED_UID);
- final BatteryStatsHistoryIterator iterator = bi.iterateBatteryStatsHistory();
+ final BatteryStatsHistoryIterator iterator = bi.iterateBatteryStatsHistory(0, 0);
BatteryStats.HistoryItem item;
@@ -933,7 +933,7 @@ public class BatteryStatsNoteTest extends TestCase {
clocks.realtime = clocks.uptime = 5000;
bi.noteAlarmFinishLocked("foo", null, UID);
- BatteryStatsHistoryIterator iterator = bi.iterateBatteryStatsHistory();
+ BatteryStatsHistoryIterator iterator = bi.iterateBatteryStatsHistory(0, 0);
HistoryItem item;
assertThat(item = iterator.next()).isNotNull();
@@ -972,7 +972,7 @@ public class BatteryStatsNoteTest extends TestCase {
clocks.realtime = clocks.uptime = 5000;
bi.noteAlarmFinishLocked("foo", ws, UID);
- BatteryStatsHistoryIterator iterator = bi.iterateBatteryStatsHistory();
+ BatteryStatsHistoryIterator iterator = bi.iterateBatteryStatsHistory(0, 0);
HistoryItem item;
assertThat(item = iterator.next()).isNotNull();
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/EnergyConsumerSnapshotTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/EnergyConsumerSnapshotTest.java
index 28f4799656b7..6cd08653bc33 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/EnergyConsumerSnapshotTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/EnergyConsumerSnapshotTest.java
@@ -26,7 +26,6 @@ import android.hardware.power.stats.EnergyConsumer;
import android.hardware.power.stats.EnergyConsumerAttribution;
import android.hardware.power.stats.EnergyConsumerResult;
import android.hardware.power.stats.EnergyConsumerType;
-import android.os.BatteryStats;
import android.util.SparseArray;
import android.util.SparseLongArray;
@@ -238,17 +237,6 @@ public final class EnergyConsumerSnapshotTest {
}
@Test
- public void getMeasuredEnergyDetails() {
- final EnergyConsumerSnapshot snapshot = new EnergyConsumerSnapshot(ALL_ID_CONSUMER_MAP);
- snapshot.updateAndGetDelta(RESULTS_0, VOLTAGE_0);
- EnergyConsumerDeltaData delta = snapshot.updateAndGetDelta(RESULTS_1, VOLTAGE_1);
- BatteryStats.EnergyConsumerDetails details = snapshot.getEnergyConsumerDetails(delta);
- assertThat(details.consumers).hasLength(4);
- assertThat(details.chargeUC).isEqualTo(new long[]{2667, 3200000, 0, 0});
- assertThat(details.toString()).isEqualTo("DISPLAY=2667 HPU=3200000 GPU=0 IPU &_=0");
- }
-
- @Test
public void testUpdateAndGetDelta_updatesCameraCharge() {
EnergyConsumer cameraConsumer =
createEnergyConsumer(7, 0, EnergyConsumerType.CAMERA, "CAMERA");
@@ -266,12 +254,8 @@ public final class EnergyConsumerSnapshotTest {
createEnergyConsumerResult(cameraConsumer.id, 90_000, null, null),
};
EnergyConsumerDeltaData delta = snapshot.updateAndGetDelta(result1, VOLTAGE_1);
-
- // Verify that the delta between the two results is reported.
- BatteryStats.EnergyConsumerDetails details = snapshot.getEnergyConsumerDetails(delta);
- assertThat(details.consumers).hasLength(1);
long expectedDeltaUC = calculateChargeConsumedUC(60_000, VOLTAGE_1, 90_000, VOLTAGE_1);
- assertThat(details.chargeUC[0]).isEqualTo(expectedDeltaUC);
+ assertThat(delta.cameraChargeUC).isEqualTo(expectedDeltaUC);
}
private static EnergyConsumer createEnergyConsumer(int id, int ord, byte type, String name) {
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java
new file mode 100644
index 000000000000..4ecee9fe7d23
--- /dev/null
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java
@@ -0,0 +1,267 @@
+/*
+ * 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.server.power.stats;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.assertThrows;
+
+import android.os.BatteryConsumer;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.os.MultiStateStats;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.Arrays;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class MultiStateStatsTest {
+
+ public static final int DIMENSION_COUNT = 2;
+
+ @Test
+ public void compositeStateIndex_allEnabled() {
+ MultiStateStats.Factory factory = makeFactory(true, true, true);
+ assertThatCpuPerformanceStatsFactory(factory)
+ .hasSerialStateCount(BatteryConsumer.PROCESS_STATE_COUNT * 4)
+ .haveDifferentSerialStates(
+ state(false, false, BatteryConsumer.PROCESS_STATE_FOREGROUND),
+ state(false, false, BatteryConsumer.PROCESS_STATE_BACKGROUND),
+ state(false, true, BatteryConsumer.PROCESS_STATE_FOREGROUND),
+ state(false, true, BatteryConsumer.PROCESS_STATE_BACKGROUND),
+ state(true, false, BatteryConsumer.PROCESS_STATE_FOREGROUND),
+ state(true, false, BatteryConsumer.PROCESS_STATE_BACKGROUND),
+ state(true, true, BatteryConsumer.PROCESS_STATE_FOREGROUND),
+ state(true, true, BatteryConsumer.PROCESS_STATE_BACKGROUND));
+ }
+
+ @Test
+ public void compositeStateIndex_procStateTrackingDisabled() {
+ MultiStateStats.Factory factory = makeFactory(true, false, true);
+ assertThatCpuPerformanceStatsFactory(factory)
+ .hasSerialStateCount(4)
+ .haveDifferentSerialStates(
+ state(false, false, BatteryConsumer.PROCESS_STATE_FOREGROUND),
+ state(false, true, BatteryConsumer.PROCESS_STATE_FOREGROUND),
+ state(true, false, BatteryConsumer.PROCESS_STATE_FOREGROUND),
+ state(true, true, BatteryConsumer.PROCESS_STATE_FOREGROUND))
+ .haveSameSerialStates(
+ state(false, false, BatteryConsumer.PROCESS_STATE_FOREGROUND),
+ state(false, false, BatteryConsumer.PROCESS_STATE_BACKGROUND))
+ .haveSameSerialStates(
+ state(false, true, BatteryConsumer.PROCESS_STATE_FOREGROUND),
+ state(false, true, BatteryConsumer.PROCESS_STATE_BACKGROUND))
+ .haveSameSerialStates(
+ state(true, false, BatteryConsumer.PROCESS_STATE_FOREGROUND),
+ state(true, false, BatteryConsumer.PROCESS_STATE_BACKGROUND))
+ .haveSameSerialStates(
+ state(true, true, BatteryConsumer.PROCESS_STATE_FOREGROUND),
+ state(true, true, BatteryConsumer.PROCESS_STATE_BACKGROUND));
+ }
+
+ @Test
+ public void compositeStateIndex_screenTrackingDisabled() {
+ MultiStateStats.Factory factory = makeFactory(true, true, false);
+ assertThatCpuPerformanceStatsFactory(factory)
+ .hasSerialStateCount(BatteryConsumer.PROCESS_STATE_COUNT * 2)
+ .haveDifferentSerialStates(
+ state(false, false, BatteryConsumer.PROCESS_STATE_FOREGROUND),
+ state(false, true, BatteryConsumer.PROCESS_STATE_BACKGROUND),
+ state(true, false, BatteryConsumer.PROCESS_STATE_FOREGROUND),
+ state(true, true, BatteryConsumer.PROCESS_STATE_BACKGROUND))
+ .haveSameSerialStates(
+ state(false, false, BatteryConsumer.PROCESS_STATE_FOREGROUND),
+ state(false, true, BatteryConsumer.PROCESS_STATE_FOREGROUND))
+ .haveSameSerialStates(
+ state(true, false, BatteryConsumer.PROCESS_STATE_BACKGROUND),
+ state(true, true, BatteryConsumer.PROCESS_STATE_BACKGROUND));
+ }
+
+ @Test
+ public void compositeStateIndex_allDisabled() {
+ MultiStateStats.Factory factory = makeFactory(false, false, false);
+ assertThatCpuPerformanceStatsFactory(factory)
+ .hasSerialStateCount(1)
+ .haveSameSerialStates(
+ state(false, false, BatteryConsumer.PROCESS_STATE_FOREGROUND),
+ state(false, false, BatteryConsumer.PROCESS_STATE_BACKGROUND),
+ state(false, true, BatteryConsumer.PROCESS_STATE_FOREGROUND),
+ state(false, true, BatteryConsumer.PROCESS_STATE_BACKGROUND),
+ state(true, false, BatteryConsumer.PROCESS_STATE_FOREGROUND),
+ state(true, false, BatteryConsumer.PROCESS_STATE_BACKGROUND),
+ state(true, true, BatteryConsumer.PROCESS_STATE_FOREGROUND),
+ state(true, true, BatteryConsumer.PROCESS_STATE_BACKGROUND));
+ }
+
+ @Test
+ public void tooManyStates() {
+ // 4 bits needed to represent
+ String[] labels = {"a", "b", "c", "d", "e", "f", "g", "h", "i", "j"};
+ // 4 * 10 = 40 bits needed to represent the composite state
+ MultiStateStats.States[] states = new MultiStateStats.States[10];
+ for (int i = 0; i < states.length; i++) {
+ states[i] = new MultiStateStats.States(true, labels);
+ }
+ IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
+ () -> new MultiStateStats.Factory(DIMENSION_COUNT, states));
+ assertThat(e.getMessage()).contains("40");
+ }
+
+ @Test
+ public void multiStateStats_aggregation() {
+ MultiStateStats.Factory factory = makeFactory(true, true, false);
+ MultiStateStats multiStateStats = factory.create();
+ multiStateStats.setState(0 /* batteryState */, 1 /* on */, 1000);
+ multiStateStats.setState(1 /* procState */, BatteryConsumer.PROCESS_STATE_FOREGROUND, 1000);
+ multiStateStats.setState(2 /* screenState */, 0 /* off */, 1000);
+
+ multiStateStats.increment(new long[]{100, 200}, 1000);
+
+ multiStateStats.setState(0 /* batteryState */, 0 /* off */, 2000);
+ multiStateStats.setState(2 /* screenState */, 1 /* on */, 2000); // untracked
+
+ multiStateStats.increment(new long[]{300, 500}, 3000);
+
+ multiStateStats.setState(1 /* procState */, BatteryConsumer.PROCESS_STATE_BACKGROUND, 4000);
+
+ multiStateStats.increment(new long[]{200, 200}, 5000);
+
+ long[] stats = new long[DIMENSION_COUNT];
+ multiStateStats.getStats(stats, new int[]{0, BatteryConsumer.PROCESS_STATE_FOREGROUND, 0});
+ // (400 - 100) * 0.5 + (600 - 400) * 0.5
+ assertThat(stats).isEqualTo(new long[]{250, 350});
+
+ multiStateStats.getStats(stats, new int[]{1, BatteryConsumer.PROCESS_STATE_FOREGROUND, 0});
+ // (400 - 100) * 0.5 + (600 - 400) * 0
+ assertThat(stats).isEqualTo(new long[]{150, 250});
+
+ // Note that screen state does not affect the result, as it is untracked
+ multiStateStats.getStats(stats, new int[]{0, BatteryConsumer.PROCESS_STATE_BACKGROUND, 1});
+ // (400 - 100) * 0 + (600 - 400) * 0.5
+ assertThat(stats).isEqualTo(new long[]{100, 100});
+
+ multiStateStats.getStats(stats, new int[]{1, BatteryConsumer.PROCESS_STATE_BACKGROUND, 0});
+ // Never been in this composite state
+ assertThat(stats).isEqualTo(new long[]{0, 0});
+ }
+
+ @Test
+ public void dump() {
+ MultiStateStats.Factory factory = makeFactory(true, true, false);
+ MultiStateStats multiStateStats = factory.create();
+ multiStateStats.setState(0 /* batteryState */, 0 /* off */, 1000);
+ multiStateStats.setState(1 /* procState */, BatteryConsumer.PROCESS_STATE_FOREGROUND, 1000);
+ multiStateStats.setState(2 /* screenState */, 0 /* off */, 1000);
+ multiStateStats.setState(0 /* batteryState */, 1 /* on */, 2000);
+ multiStateStats.setState(1 /* procState */, BatteryConsumer.PROCESS_STATE_BACKGROUND, 3000);
+ multiStateStats.increment(new long[]{100, 200}, 5000);
+
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw, true);
+ multiStateStats.dump(pw);
+ assertThat(sw.toString()).isEqualTo(
+ "plugged-in fg [25, 50]\n"
+ + "on-battery fg [25, 50]\n"
+ + "on-battery bg [50, 100]\n"
+ );
+ }
+
+ private static MultiStateStats.Factory makeFactory(boolean trackBatteryState,
+ boolean trackProcState, boolean trackScreenState) {
+ return new MultiStateStats.Factory(DIMENSION_COUNT,
+ new MultiStateStats.States(trackBatteryState, "plugged-in", "on-battery"),
+ new MultiStateStats.States(trackProcState,
+ BatteryConsumer.processStateToString(
+ BatteryConsumer.PROCESS_STATE_UNSPECIFIED),
+ BatteryConsumer.processStateToString(
+ BatteryConsumer.PROCESS_STATE_FOREGROUND),
+ BatteryConsumer.processStateToString(
+ BatteryConsumer.PROCESS_STATE_BACKGROUND),
+ BatteryConsumer.processStateToString(
+ BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE),
+ BatteryConsumer.processStateToString(
+ BatteryConsumer.PROCESS_STATE_CACHED)),
+ new MultiStateStats.States(trackScreenState, "screen-off", "plugged-in"));
+ }
+
+ private FactorySubject assertThatCpuPerformanceStatsFactory(
+ MultiStateStats.Factory factory) {
+ FactorySubject subject = new FactorySubject();
+ subject.mFactory = factory;
+ return subject;
+ }
+
+ private static class FactorySubject {
+ private MultiStateStats.Factory mFactory;
+
+ FactorySubject hasSerialStateCount(int stateCount) {
+ assertThat(mFactory.getSerialStateCount()).isEqualTo(stateCount);
+ return this;
+ }
+
+ public FactorySubject haveDifferentSerialStates(State... states) {
+ int[] serialStates = getSerialStates(states);
+ assertWithMessage("Expected all to be different: " + Arrays.toString(serialStates))
+ .that(Arrays.stream(serialStates).distinct().toArray())
+ .hasLength(states.length);
+ return this;
+ }
+
+ public FactorySubject haveSameSerialStates(State... states) {
+ int[] serialStates = getSerialStates(states);
+ assertWithMessage("Expected all to be the same: " + Arrays.toString(serialStates))
+ .that(Arrays.stream(serialStates).distinct().toArray())
+ .hasLength(1);
+ return this;
+ }
+
+ private int[] getSerialStates(State[] states) {
+ int[] serialStates = new int[states.length];
+ for (int i = 0; i < states.length; i++) {
+ serialStates[i] = mFactory.getSerialState(
+ new int[]{
+ states[i].batteryState ? 0 : 1,
+ states[i].procstate,
+ states[i].screenState ? 0 : 1
+ });
+ }
+ return serialStates;
+ }
+ }
+
+ private State state(boolean batteryState, boolean screenState, int procstate) {
+ State state = new State();
+ state.batteryState = batteryState;
+ state.screenState = screenState;
+ state.procstate = procstate;
+ return state;
+ }
+
+ private static class State {
+ public boolean batteryState;
+ public boolean screenState;
+ public int procstate;
+ }
+}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsAggregatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsAggregatorTest.java
new file mode 100644
index 000000000000..47de44324ae1
--- /dev/null
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsAggregatorTest.java
@@ -0,0 +1,232 @@
+/*
+ * 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.server.power.stats;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+
+import android.os.BatteryConsumer;
+import android.os.BatteryStats;
+import android.os.PersistableBundle;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.os.BatteryStatsHistory;
+import com.android.internal.os.PowerStats;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class PowerStatsAggregatorTest {
+ private static final int TEST_POWER_COMPONENT = 77;
+ private static final int TEST_UID = 42;
+
+ private final MockClock mClock = new MockClock();
+ private long mStartTime;
+ private BatteryStatsHistory mHistory;
+ private PowerStatsAggregator mAggregator;
+ private int mAggregatedStatsCount;
+
+ @Before
+ public void setup() throws ParseException {
+ mHistory = new BatteryStatsHistory(32, 1024,
+ mock(BatteryStatsHistory.HistoryStepDetailsCalculator.class), mClock);
+ mStartTime = new SimpleDateFormat("yyyy-MM-dd HH:mm")
+ .parse("2008-09-23 08:00").getTime();
+ mClock.currentTime = mStartTime;
+
+ PowerStatsAggregator.Builder builder = new PowerStatsAggregator.Builder(mHistory);
+ builder.trackPowerComponent(TEST_POWER_COMPONENT)
+ .trackDeviceStates(
+ PowerStatsAggregator.STATE_POWER,
+ PowerStatsAggregator.STATE_SCREEN)
+ .trackUidStates(
+ PowerStatsAggregator.STATE_POWER,
+ PowerStatsAggregator.STATE_SCREEN,
+ PowerStatsAggregator.STATE_PROCESS_STATE);
+ mAggregator = builder.build();
+ }
+
+ @Test
+ public void stateUpdates() {
+ mHistory.forceRecordAllHistory();
+ mHistory.recordBatteryState(mClock.realtime, mClock.uptime, 10, /* plugged */ true);
+ mHistory.recordStateStartEvent(mClock.realtime, mClock.uptime,
+ BatteryStats.HistoryItem.STATE_SCREEN_ON_FLAG);
+ mHistory.recordProcessStateChange(mClock.realtime, mClock.uptime, TEST_UID,
+ BatteryConsumer.PROCESS_STATE_FOREGROUND);
+
+ advance(1000);
+
+ PowerStats.Descriptor descriptor =
+ new PowerStats.Descriptor(TEST_POWER_COMPONENT, "majorDrain", 1, 1,
+ new PersistableBundle());
+ PowerStats powerStats = new PowerStats(descriptor);
+ powerStats.stats = new long[]{10000};
+ powerStats.uidStats.put(TEST_UID, new long[]{1234});
+ mHistory.recordPowerStats(mClock.realtime, mClock.uptime, powerStats);
+
+ mHistory.recordBatteryState(mClock.realtime, mClock.uptime, 90, /* plugged */ false);
+ mHistory.recordStateStopEvent(mClock.realtime, mClock.uptime,
+ BatteryStats.HistoryItem.STATE_SCREEN_ON_FLAG);
+
+ advance(1000);
+
+ mHistory.recordProcessStateChange(mClock.realtime, mClock.uptime, TEST_UID,
+ BatteryConsumer.PROCESS_STATE_BACKGROUND);
+
+ advance(3000);
+
+ powerStats.stats = new long[]{20000};
+ powerStats.uidStats.put(TEST_UID, new long[]{4444});
+ mHistory.recordPowerStats(mClock.realtime, mClock.uptime, powerStats);
+
+ mAggregator.aggregateBatteryStats(0, 0, stats -> {
+ assertThat(mAggregatedStatsCount++).isEqualTo(0);
+ assertThat(stats.getStartTime()).isEqualTo(mStartTime);
+ assertThat(stats.getDuration()).isEqualTo(5000);
+
+ long[] values = new long[1];
+
+ PowerComponentAggregatedPowerStats powerComponentStats = stats.getPowerComponentStats(
+ TEST_POWER_COMPONENT);
+
+ assertThat(powerComponentStats.getDeviceStats(values, new int[]{
+ PowerStatsAggregator.POWER_STATE_OTHER,
+ PowerStatsAggregator.SCREEN_STATE_ON}))
+ .isTrue();
+ assertThat(values).isEqualTo(new long[]{10000});
+
+ assertThat(powerComponentStats.getDeviceStats(values, new int[]{
+ PowerStatsAggregator.POWER_STATE_BATTERY,
+ PowerStatsAggregator.SCREEN_STATE_OTHER}))
+ .isTrue();
+ assertThat(values).isEqualTo(new long[]{20000});
+
+ assertThat(powerComponentStats.getUidStats(values, TEST_UID, new int[]{
+ PowerStatsAggregator.POWER_STATE_OTHER,
+ PowerStatsAggregator.SCREEN_STATE_ON,
+ BatteryConsumer.PROCESS_STATE_FOREGROUND}))
+ .isTrue();
+ assertThat(values).isEqualTo(new long[]{1234});
+
+ assertThat(powerComponentStats.getUidStats(values, TEST_UID, new int[]{
+ PowerStatsAggregator.POWER_STATE_BATTERY,
+ PowerStatsAggregator.SCREEN_STATE_OTHER,
+ BatteryConsumer.PROCESS_STATE_FOREGROUND}))
+ .isTrue();
+ assertThat(values).isEqualTo(new long[]{1111});
+
+ assertThat(powerComponentStats.getUidStats(values, TEST_UID, new int[]{
+ PowerStatsAggregator.POWER_STATE_BATTERY,
+ PowerStatsAggregator.SCREEN_STATE_OTHER,
+ BatteryConsumer.PROCESS_STATE_BACKGROUND}))
+ .isTrue();
+ assertThat(values).isEqualTo(new long[]{3333});
+ });
+ }
+
+ @Test
+ public void incompatiblePowerStats() {
+ mHistory.forceRecordAllHistory();
+ mHistory.recordBatteryState(mClock.realtime, mClock.uptime, 10, /* plugged */ true);
+ mHistory.recordProcessStateChange(mClock.realtime, mClock.uptime, TEST_UID,
+ BatteryConsumer.PROCESS_STATE_FOREGROUND);
+
+ advance(1000);
+
+ PowerStats.Descriptor descriptor =
+ new PowerStats.Descriptor(TEST_POWER_COMPONENT, "majorDrain", 1, 1,
+ new PersistableBundle());
+ PowerStats powerStats = new PowerStats(descriptor);
+ powerStats.stats = new long[]{10000};
+ powerStats.uidStats.put(TEST_UID, new long[]{1234});
+ mHistory.recordPowerStats(mClock.realtime, mClock.uptime, powerStats);
+
+ mHistory.recordBatteryState(mClock.realtime, mClock.uptime, 90, /* plugged */ false);
+
+ advance(1000);
+
+ descriptor = new PowerStats.Descriptor(TEST_POWER_COMPONENT, "majorDrain", 1, 1,
+ PersistableBundle.forPair("something", "changed"));
+ powerStats = new PowerStats(descriptor);
+ powerStats.stats = new long[]{20000};
+ powerStats.uidStats.put(TEST_UID, new long[]{4444});
+ mHistory.recordPowerStats(mClock.realtime, mClock.uptime, powerStats);
+
+ advance(1000);
+
+ mHistory.recordBatteryState(mClock.realtime, mClock.uptime, 50, /* plugged */ true);
+
+ mAggregator.aggregateBatteryStats(0, 0, stats -> {
+ long[] values = new long[1];
+
+ PowerComponentAggregatedPowerStats powerComponentStats =
+ stats.getPowerComponentStats(TEST_POWER_COMPONENT);
+
+ if (mAggregatedStatsCount == 0) {
+ assertThat(stats.getStartTime()).isEqualTo(mStartTime);
+ assertThat(stats.getDuration()).isEqualTo(2000);
+
+ assertThat(powerComponentStats.getDeviceStats(values, new int[]{
+ PowerStatsAggregator.POWER_STATE_OTHER,
+ PowerStatsAggregator.SCREEN_STATE_ON}))
+ .isTrue();
+ assertThat(values).isEqualTo(new long[]{10000});
+ assertThat(powerComponentStats.getUidStats(values, TEST_UID, new int[]{
+ PowerStatsAggregator.POWER_STATE_OTHER,
+ PowerStatsAggregator.SCREEN_STATE_ON,
+ BatteryConsumer.PROCESS_STATE_FOREGROUND}))
+ .isTrue();
+ assertThat(values).isEqualTo(new long[]{1234});
+ } else if (mAggregatedStatsCount == 1) {
+ assertThat(stats.getStartTime()).isEqualTo(mStartTime + 2000);
+ assertThat(stats.getDuration()).isEqualTo(1000);
+
+ assertThat(powerComponentStats.getDeviceStats(values, new int[]{
+ PowerStatsAggregator.POWER_STATE_BATTERY,
+ PowerStatsAggregator.SCREEN_STATE_ON}))
+ .isTrue();
+ assertThat(values).isEqualTo(new long[]{20000});
+ assertThat(powerComponentStats.getUidStats(values, TEST_UID, new int[]{
+ PowerStatsAggregator.POWER_STATE_BATTERY,
+ PowerStatsAggregator.SCREEN_STATE_ON,
+ BatteryConsumer.PROCESS_STATE_FOREGROUND}))
+ .isTrue();
+ assertThat(values).isEqualTo(new long[]{4444});
+ } else {
+ fail();
+ }
+ mAggregatedStatsCount++;
+ });
+ }
+
+ private void advance(long durationMs) {
+ mClock.realtime += durationMs;
+ mClock.uptime += durationMs;
+ mClock.currentTime += durationMs;
+ }
+}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsCollectorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsCollectorTest.java
index 08c821344670..330f698277f8 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsCollectorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsCollectorTest.java
@@ -21,6 +21,7 @@ import static com.google.common.truth.Truth.assertThat;
import android.os.ConditionVariable;
import android.os.Handler;
import android.os.HandlerThread;
+import android.os.PersistableBundle;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -49,7 +50,7 @@ public class PowerStatsCollectorTest {
mMockClock) {
@Override
protected PowerStats collectStats() {
- return new PowerStats();
+ return new PowerStats(new PowerStats.Descriptor(0, 0, 0, new PersistableBundle()));
}
};
mCollector.addConsumer(stats -> mCollectedStats = stats);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 57aa0b96a56a..579bbc8a417a 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -6044,6 +6044,49 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
+ public void testVisitUris_styleExtrasWithoutStyle() {
+ Notification notification = new Notification.Builder(mContext, "a")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .build();
+
+ Notification.MessagingStyle messagingStyle = new Notification.MessagingStyle(
+ personWithIcon("content://user"))
+ .addHistoricMessage(new Notification.MessagingStyle.Message("Heyhey!",
+ System.currentTimeMillis(),
+ personWithIcon("content://historicalMessenger")))
+ .addMessage(new Notification.MessagingStyle.Message("Are you there",
+ System.currentTimeMillis(),
+ personWithIcon("content://messenger")))
+ .setShortcutIcon(
+ Icon.createWithContentUri("content://conversationShortcut"));
+ messagingStyle.addExtras(notification.extras); // Instead of Builder.setStyle(style).
+
+ Notification.CallStyle callStyle = Notification.CallStyle.forOngoingCall(
+ personWithIcon("content://caller"),
+ PendingIntent.getActivity(mContext, 0, new Intent(),
+ PendingIntent.FLAG_IMMUTABLE))
+ .setVerificationIcon(Icon.createWithContentUri("content://callVerification"));
+ callStyle.addExtras(notification.extras); // Same.
+
+ Consumer<Uri> visitor = (Consumer<Uri>) spy(Consumer.class);
+ notification.visitUris(visitor);
+
+ verify(visitor).accept(eq(Uri.parse("content://user")));
+ verify(visitor).accept(eq(Uri.parse("content://historicalMessenger")));
+ verify(visitor).accept(eq(Uri.parse("content://messenger")));
+ verify(visitor).accept(eq(Uri.parse("content://conversationShortcut")));
+ verify(visitor).accept(eq(Uri.parse("content://caller")));
+ verify(visitor).accept(eq(Uri.parse("content://callVerification")));
+ }
+
+ private static Person personWithIcon(String iconUri) {
+ return new Person.Builder()
+ .setName("Mr " + iconUri)
+ .setIcon(Icon.createWithContentUri(iconUri))
+ .build();
+ }
+
+ @Test
public void testVisitUris_wearableExtender() {
Icon actionIcon = Icon.createWithContentUri("content://media/action");
Icon wearActionIcon = Icon.createWithContentUri("content://media/wearAction");
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index 9b6d4e216744..873d09b42c83 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -1191,6 +1191,7 @@ public class TransitionTests extends WindowTestsBase {
final WindowState statusBar = createWindow(null, TYPE_STATUS_BAR, "statusBar");
makeWindowVisible(statusBar);
mDisplayContent.getDisplayPolicy().addWindowLw(statusBar, statusBar.mAttrs);
+ final WindowState navBar = createWindow(null, TYPE_NAVIGATION_BAR, "navBar");
final ActivityRecord app = createActivityRecord(mDisplayContent);
final Transition transition = app.mTransitionController.createTransition(TRANSIT_OPEN);
app.mTransitionController.requestStartTransition(transition, app.getTask(),
@@ -1220,9 +1221,17 @@ public class TransitionTests extends WindowTestsBase {
mDisplayContent.mTransitionController.dispatchLegacyAppTransitionFinished(app);
assertTrue(mDisplayContent.hasTopFixedRotationLaunchingApp());
+ // The bar was invisible so it is not handled by the controller. But if it becomes visible
+ // and drawn before the transition starts,
+ assertFalse(asyncRotationController.isTargetToken(navBar.mToken));
+ navBar.finishDrawing(null /* postDrawTransaction */, Integer.MAX_VALUE);
+ assertTrue(asyncRotationController.isTargetToken(navBar.mToken));
+
player.startTransition();
// Non-app windows should not be collected.
assertFalse(mDisplayContent.mTransitionController.isCollecting(statusBar.mToken));
+ // Avoid DeviceStateController disturbing the test by triggering another rotation change.
+ doReturn(false).when(mDisplayContent).updateRotationUnchecked();
onRotationTransactionReady(player, mWm.mTransactionFactory.get()).onTransactionCommitted();
assertEquals(ROTATION_ANIMATION_SEAMLESS, player.mLastReady.getChange(
diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestUtil.java b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestUtil.java
index c9b5c96c9920..12556bcf7ded 100644
--- a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestUtil.java
+++ b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestUtil.java
@@ -154,7 +154,7 @@ public final class ImeStressTestUtil {
* <p>The given {@code pred} will be called on the main thread.
*/
public static void waitOnMainUntil(String message, Callable<Boolean> pred) {
- eventually(() -> assertWithMessage(message).that(pred.call()).isTrue(), TIMEOUT);
+ eventually(() -> assertWithMessage(message).that(callOnMainSync(pred)).isTrue(), TIMEOUT);
}
/** Waits until IME is shown, or throws on timeout. */
diff --git a/tools/aapt2/trace/TraceBuffer.cpp b/tools/aapt2/trace/TraceBuffer.cpp
index fab2df383e3f..0988c313b65b 100644
--- a/tools/aapt2/trace/TraceBuffer.cpp
+++ b/tools/aapt2/trace/TraceBuffer.cpp
@@ -44,14 +44,16 @@ struct TracePoint {
std::vector<TracePoint> traces;
bool enabled = true;
+constinit std::chrono::steady_clock::time_point startTime = {};
int64_t GetTime() noexcept {
auto now = std::chrono::steady_clock::now();
- return std::chrono::duration_cast<std::chrono::microseconds>(now.time_since_epoch()).count();
+ if (startTime == decltype(tracebuffer::startTime){}) {
+ startTime = now;
+ }
+ return std::chrono::duration_cast<std::chrono::microseconds>(now - startTime).count();
}
-} // namespace anonymous
-
void AddWithTime(std::string tag, char type, int64_t time) noexcept {
TracePoint t = {type, getpid(), time, std::move(tag)};
traces.emplace_back(std::move(t));
@@ -76,7 +78,7 @@ void Flush(const std::string& basePath) {
// Wrap the trace in a JSON array [] to make Chrome/Perfetto UI handle it.
char delimiter = '[';
- for(const TracePoint& trace : traces) {
+ for (const TracePoint& trace : traces) {
fprintf(f,
"%c{\"ts\" : \"%" PRIu64
"\", \"ph\" : \"%c\", \"tid\" : \"%d\" , \"pid\" : \"%d\", \"name\" : \"%s\" }\n",
@@ -90,6 +92,8 @@ void Flush(const std::string& basePath) {
traces.clear();
}
+} // namespace
+
} // namespace tracebuffer
void BeginTrace(std::string tag) {
@@ -166,7 +170,7 @@ FlushTrace::FlushTrace(std::string_view basepath, std::string_view tag,
FlushTrace::~FlushTrace() {
if (!tracebuffer::enabled) return;
- tracebuffer::Add(tag_, tracebuffer::kEnd);
+ tracebuffer::Add(std::move(tag_), tracebuffer::kEnd);
tracebuffer::Flush(basepath_);
}