diff options
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 >= {@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_); } |