diff options
189 files changed, 4971 insertions, 2084 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp index 75fb215b8d45..e3cbd92703aa 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -21,6 +21,7 @@ aconfig_srcjars = [ ":android.hardware.flags-aconfig-java{.generated_srcjars}", ":android.hardware.radio.flags-aconfig-java{.generated_srcjars}", ":android.location.flags-aconfig-java{.generated_srcjars}", + ":android.net.vcn.flags-aconfig-java{.generated_srcjars}", ":android.nfc.flags-aconfig-java{.generated_srcjars}", ":android.os.flags-aconfig-java{.generated_srcjars}", ":android.os.vibrator.flags-aconfig-java{.generated_srcjars}", @@ -568,6 +569,19 @@ java_aconfig_library { defaults: ["framework-minus-apex-aconfig-java-defaults"], } +// VCN +aconfig_declarations { + name: "android.net.vcn.flags-aconfig", + package: "android.net.vcn", + srcs: ["core/java/android/net/vcn/*.aconfig"], +} + +java_aconfig_library { + name: "android.net.vcn.flags-aconfig-java", + aconfig_declarations: "android.net.vcn.flags-aconfig", + defaults: ["framework-minus-apex-aconfig-java-defaults"], +} + // DevicePolicy aconfig_declarations { name: "device_policy_aconfig_flags", diff --git a/Android.bp b/Android.bp index 986a07108b91..b5f7e99c8823 100644 --- a/Android.bp +++ b/Android.bp @@ -695,12 +695,10 @@ stubs_defaults { "--hide CallbackInterface", "--hide DeprecationMismatch", "--hide HiddenSuperclass", - "--hide HiddenTypeParameter", "--hide MissingPermission", "--hide RequiresPermission", "--hide SdkConstant", "--hide Todo", - "--hide UnavailableSymbol", "--hide-package android.audio.policy.configuration.V7_0", "--hide-package com.android.server", "--manifest $(location :frameworks-base-core-AndroidManifest.xml)", diff --git a/api/gen_combined_removed_dex.sh b/api/gen_combined_removed_dex.sh index 71f366a6aae2..e0153f7c1091 100755 --- a/api/gen_combined_removed_dex.sh +++ b/api/gen_combined_removed_dex.sh @@ -6,6 +6,6 @@ shift 2 # Convert each removed.txt to the "dex format" equivalent, and print all output. for f in "$@"; do - "$metalava_path" "$f" --dex-api "${tmp_dir}/tmp" + "$metalava_path" signature-to-dex "$f" "${tmp_dir}/tmp" cat "${tmp_dir}/tmp" done diff --git a/core/api/current.txt b/core/api/current.txt index 3f1f720f9a4e..cce83292b2c9 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -13322,9 +13322,12 @@ package android.content.pm { public final class SigningInfo implements android.os.Parcelable { ctor public SigningInfo(); + ctor @FlaggedApi("android.content.pm.archiving") public SigningInfo(@IntRange(from=0) int, @Nullable java.util.Collection<android.content.pm.Signature>, @Nullable java.util.Collection<java.security.PublicKey>, @Nullable java.util.Collection<android.content.pm.Signature>); ctor public SigningInfo(android.content.pm.SigningInfo); method public int describeContents(); method public android.content.pm.Signature[] getApkContentsSigners(); + method @FlaggedApi("android.content.pm.archiving") @NonNull public java.util.Collection<java.security.PublicKey> getPublicKeys(); + method @FlaggedApi("android.content.pm.archiving") @IntRange(from=0) public int getSchemeVersion(); method public android.content.pm.Signature[] getSigningCertificateHistory(); method public boolean hasMultipleSigners(); method public boolean hasPastSigningCertificates(); diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 8d9ad5a99209..83234a5dbce2 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -872,7 +872,7 @@ package android.content { ctor public AttributionSource(int, @Nullable String, @Nullable String); ctor public AttributionSource(int, @Nullable String, @Nullable String, @NonNull android.os.IBinder); ctor public AttributionSource(int, @Nullable String, @Nullable String, @Nullable java.util.Set<java.lang.String>, @Nullable android.content.AttributionSource); - ctor public AttributionSource(int, int, @Nullable String, @Nullable String, @NonNull android.os.IBinder, @Nullable String[], @Nullable android.content.AttributionSource); + ctor @FlaggedApi("android.permission.flags.attribution_source_constructor") public AttributionSource(int, int, @Nullable String, @Nullable String, @NonNull android.os.IBinder, @Nullable String[], @Nullable android.content.AttributionSource); ctor @FlaggedApi("android.permission.flags.device_aware_permission_apis") public AttributionSource(int, int, @Nullable String, @Nullable String, @NonNull android.os.IBinder, @Nullable String[], int, @Nullable android.content.AttributionSource); method public void enforceCallingPid(); } @@ -2289,7 +2289,7 @@ package android.os { public final class PowerManager { method public boolean areAutoPowerSaveModesEnabled(); method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_LOW_POWER_STANDBY, android.Manifest.permission.DEVICE_POWER}) public void forceLowPowerStandbyActive(boolean); - method public boolean isBatterySaverSupported(); + method @FlaggedApi("android.os.battery_saver_supported_check_api") public boolean isBatterySaverSupported(); field public static final String ACTION_ENHANCED_DISCHARGE_PREDICTION_CHANGED = "android.os.action.ENHANCED_DISCHARGE_PREDICTION_CHANGED"; field @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public static final int SYSTEM_WAKELOCK = -2147483648; // 0x80000000 } diff --git a/core/java/android/app/smartspace/flags.aconfig b/core/java/android/app/smartspace/flags.aconfig index 6aefa38ac18c..12af888bfaa5 100644 --- a/core/java/android/app/smartspace/flags.aconfig +++ b/core/java/android/app/smartspace/flags.aconfig @@ -6,3 +6,10 @@ flag { description: "Flag to enable the FlaggedApi to include RemoteViews in SmartspaceTarget" bug: "300157758" } + +flag { + name: "access_smartspace" + namespace: "sysui_integrations" + description: "Flag to enable the ACCESS_SMARTSPACE check in SmartspaceManagerService" + bug: "297207196" +} diff --git a/core/java/android/content/AttributionSource.java b/core/java/android/content/AttributionSource.java index 62fbcaff79e3..4b2cee698df2 100644 --- a/core/java/android/content/AttributionSource.java +++ b/core/java/android/content/AttributionSource.java @@ -155,6 +155,7 @@ public final class AttributionSource implements Parcelable { /** @hide */ @TestApi + @FlaggedApi(Flags.FLAG_ATTRIBUTION_SOURCE_CONSTRUCTOR) public AttributionSource(int uid, int pid, @Nullable String packageName, @Nullable String attributionTag, @NonNull IBinder token, @Nullable String[] renouncedPermissions, diff --git a/core/java/android/content/pm/SigningInfo.java b/core/java/android/content/pm/SigningInfo.java index 554de0c2ea7b..543703e734c2 100644 --- a/core/java/android/content/pm/SigningInfo.java +++ b/core/java/android/content/pm/SigningInfo.java @@ -16,9 +16,16 @@ package android.content.pm; +import android.annotation.FlaggedApi; +import android.annotation.IntRange; import android.annotation.NonNull; +import android.annotation.Nullable; import android.os.Parcel; import android.os.Parcelable; +import android.util.ArraySet; + +import java.security.PublicKey; +import java.util.Collection; /** * Information pertaining to the signing certificates used to sign a package. @@ -33,6 +40,43 @@ public final class SigningInfo implements Parcelable { } /** + * Creates a new instance of information used to sign the APK. + * + * @param schemeVersion version of signing schema. + * @param apkContentsSigners signing certificates. + * @param publicKeys for the signing certificates. + * @param signingCertificateHistory All signing certificates the package has proven it is + * authorized to use. + * + * @see + * <a href="https://source.android.com/docs/security/features/apksigning#schemes">APK signing + * schemas</a> + */ + @FlaggedApi(Flags.FLAG_ARCHIVING) + public SigningInfo(@IntRange(from = 0) int schemeVersion, + @Nullable Collection<Signature> apkContentsSigners, + @Nullable Collection<PublicKey> publicKeys, + @Nullable Collection<Signature> signingCertificateHistory) { + if (schemeVersion <= 0 || apkContentsSigners == null) { + mSigningDetails = SigningDetails.UNKNOWN; + return; + } + Signature[] signatures = apkContentsSigners != null && !apkContentsSigners.isEmpty() + ? apkContentsSigners.toArray(new Signature[apkContentsSigners.size()]) + : null; + Signature[] pastSignatures = + signingCertificateHistory != null && !signingCertificateHistory.isEmpty() + ? signingCertificateHistory.toArray(new Signature[signingCertificateHistory.size()]) + : null; + if (Signature.areExactArraysMatch(signatures, pastSignatures)) { + pastSignatures = null; + } + ArraySet<PublicKey> keys = + publicKeys != null && !publicKeys.isEmpty() ? new ArraySet<>(publicKeys) : null; + mSigningDetails = new SigningDetails(signatures, schemeVersion, keys, pastSignatures); + } + + /** * @hide only packagemanager should be populating this */ public SigningInfo(SigningDetails signingDetails) { @@ -116,6 +160,26 @@ public final class SigningInfo implements Parcelable { return mSigningDetails.getSignatures(); } + /** + * Returns the version of signing schema used to sign the APK. + * + * @see + * <a href="https://source.android.com/docs/security/features/apksigning#schemes">APK signing + * schemas</a> + */ + @FlaggedApi(Flags.FLAG_ARCHIVING) + public @IntRange(from = 0) int getSchemeVersion() { + return mSigningDetails.getSignatureSchemeVersion(); + } + + /** + * Returns the public keys for the signing certificates. + */ + @FlaggedApi(Flags.FLAG_ARCHIVING) + public @NonNull Collection<PublicKey> getPublicKeys() { + return mSigningDetails.getPublicKeys(); + } + @Override public int describeContents() { return 0; diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java index d2a15d1a2ea1..75f0ceb7e651 100644 --- a/core/java/android/hardware/display/DisplayManagerGlobal.java +++ b/core/java/android/hardware/display/DisplayManagerGlobal.java @@ -1435,12 +1435,10 @@ public final class DisplayManagerGlobal { sExtraDisplayListenerLogging = !TextUtils.isEmpty(EXTRA_LOGGING_PACKAGE_NAME) && EXTRA_LOGGING_PACKAGE_NAME.equals(sCurrentPackageName); } - // TODO: b/306170135 - return sExtraDisplayListenerLogging instead - return true; + return sExtraDisplayListenerLogging; } private static boolean extraLogging() { - // TODO: b/306170135 - return sExtraDisplayListenerLogging & package name check instead - return true; + return sExtraDisplayListenerLogging; } } diff --git a/core/java/android/net/vcn/flags.aconfig b/core/java/android/net/vcn/flags.aconfig new file mode 100644 index 000000000000..6956916af0f1 --- /dev/null +++ b/core/java/android/net/vcn/flags.aconfig @@ -0,0 +1,8 @@ +package: "android.net.vcn" + +flag { + name: "safe_mode_config" + namespace: "vcn" + description: "Feature flag for safe mode configurability" + bug: "276358140" +}
\ No newline at end of file diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java index fce715ade5af..d2c17556bb2f 100644 --- a/core/java/android/os/PowerManager.java +++ b/core/java/android/os/PowerManager.java @@ -16,6 +16,7 @@ package android.os; +import android.annotation.FlaggedApi; import android.Manifest.permission; import android.annotation.CallbackExecutor; import android.annotation.CurrentTimeMillisLong; @@ -1940,6 +1941,7 @@ public final class PowerManager { * * @hide */ + @FlaggedApi(android.os.Flags.FLAG_BATTERY_SAVER_SUPPORTED_CHECK_API) @TestApi public boolean isBatterySaverSupported() { try { diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig index 10b9e3a15834..86f03cd1dbfc 100644 --- a/core/java/android/os/flags.aconfig +++ b/core/java/android/os/flags.aconfig @@ -41,3 +41,10 @@ flag { description: "Guards the ADPF power efficiency API" bug: "288117936" } + +flag { + name: "battery_saver_supported_check_api" + namespace: "backstage_power" + description: "Guards a new API in PowerManager to check if battery saver is supported or not." + bug: "305067031" +}
\ No newline at end of file diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig index 3f06a91f6e5b..0798f6523a23 100644 --- a/core/java/android/permission/flags.aconfig +++ b/core/java/android/permission/flags.aconfig @@ -35,3 +35,10 @@ flag { description: "enable the shouldRegisterAttributionSource API" bug: "305057691" } + +flag { + name: "attribution_source_constructor" + namespace: "permissions" + description: "enable AttributionSource(int, int, String, String, IBinder, String[], AttributionSource)" + bug: "304478648" +}
\ No newline at end of file diff --git a/core/java/android/service/voice/HotwordTrainingDataLimitEnforcer.java b/core/java/android/service/voice/HotwordTrainingDataLimitEnforcer.java index 76e506cc6728..7eb52807e93f 100644 --- a/core/java/android/service/voice/HotwordTrainingDataLimitEnforcer.java +++ b/core/java/android/service/voice/HotwordTrainingDataLimitEnforcer.java @@ -27,10 +27,9 @@ import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import java.io.File; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.Locale; -import java.util.TimeZone; +import java.time.LocalDate; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; /** * Enforces daily limits on the egress of {@link HotwordTrainingData} from the hotword detection @@ -111,9 +110,9 @@ public class HotwordTrainingDataLimitEnforcer { } private boolean incrementTrainingDataEgressCountLocked() { - SimpleDateFormat dt = new SimpleDateFormat("yyyy-MM-dd", Locale.US); - dt.setTimeZone(TimeZone.getTimeZone("UTC")); - String currentDate = dt.format(new Date()); + LocalDate utcDate = LocalDate.now(ZoneOffset.UTC); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + String currentDate = utcDate.format(formatter); String storedDate = mSharedPreferences.getString(TRAINING_DATA_EGRESS_DATE, ""); int storedCount = mSharedPreferences.getInt(TRAINING_DATA_EGRESS_COUNT, 0); diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index dfada58771a6..be4693bc0377 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -1020,8 +1020,7 @@ public final class ViewRootImpl implements ViewParent, mDisplay = display; mBasePackageName = context.getBasePackageName(); final String name = DisplayProperties.debug_vri_package().orElse(null); - // TODO: b/306170135 - return to using textutils check on package name. - mExtraDisplayListenerLogging = true; + mExtraDisplayListenerLogging = !TextUtils.isEmpty(name) && name.equals(mBasePackageName); mThread = Thread.currentThread(); mLocation = new WindowLeaked(null); mLocation.fillInStackTrace(); diff --git a/core/java/com/android/internal/os/BatteryStatsHistory.java b/core/java/com/android/internal/os/BatteryStatsHistory.java index 039943098d7a..7d78f299c625 100644 --- a/core/java/com/android/internal/os/BatteryStatsHistory.java +++ b/core/java/com/android/internal/os/BatteryStatsHistory.java @@ -603,6 +603,17 @@ public class BatteryStatsHistory { } /** + * Returns the monotonic clock time when the available battery history collection started. + */ + public long getStartTime() { + if (!mHistoryFiles.isEmpty()) { + return mHistoryFiles.get(0).monotonicTimeMs; + } else { + return mHistoryBufferStartTime; + } + } + + /** * Start iterating history files and history buffer. * * @param startTimeMs monotonic time (the HistoryItem.time field) to start iterating from, diff --git a/core/java/com/android/internal/os/MultiStateStats.java b/core/java/com/android/internal/os/MultiStateStats.java index dc5055a0881b..ecfed537b798 100644 --- a/core/java/com/android/internal/os/MultiStateStats.java +++ b/core/java/com/android/internal/os/MultiStateStats.java @@ -29,6 +29,8 @@ import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; import java.io.PrintWriter; import java.util.Arrays; +import java.util.function.Consumer; +import java.util.function.Function; /** * Maintains multidimensional multi-state stats. States could be something like on-battery (0,1), @@ -52,13 +54,55 @@ public class MultiStateStats { public States(String name, boolean tracked, String... labels) { mName = name; - this.mTracked = tracked; - this.mLabels = labels; + mTracked = tracked; + mLabels = labels; } public boolean isTracked() { return mTracked; } + + public String getName() { + return mName; + } + + public String[] getLabels() { + return mLabels; + } + + /** + * Iterates over all combinations of tracked states and invokes <code>consumer</code> + * for each of them. + */ + public static void forEachTrackedStateCombination(States[] states, + Consumer<int[]> consumer) { + forEachTrackedStateCombination(consumer, states, new int[states.length], 0); + } + + /** + * Recursive function that does a depth-first traversal of the multi-dimensional + * state space. Each time the traversal reaches the end of the <code>states</code> array, + * <code>statesValues</code> contains a unique combination of values for all tracked states. + * For untracked states, the corresponding values are left as 0. The end result is + * that the <code>consumer</code> is invoked for every unique combination of tracked state + * values. For example, it may be a sequence of calls like screen-on/power-on, + * screen-on/power-off, screen-off/power-on, screen-off/power-off. + */ + private static void forEachTrackedStateCombination(Consumer<int[]> consumer, + States[] states, int[] statesValues, int stateIndex) { + if (stateIndex < statesValues.length) { + if (!states[stateIndex].mTracked) { + forEachTrackedStateCombination(consumer, states, statesValues, stateIndex + 1); + return; + } + for (int i = 0; i < states[stateIndex].mLabels.length; i++) { + statesValues[stateIndex] = i; + forEachTrackedStateCombination(consumer, states, statesValues, stateIndex + 1); + } + return; + } + consumer.accept(statesValues); + } } /** @@ -276,6 +320,13 @@ public class MultiStateStats { } /** + * Updates the stats values for the provided combination of states. + */ + public void setStats(int[] states, long[] values) { + mCounter.setValues(mFactory.getSerialState(states), values); + } + + /** * Resets the counters. */ public void reset() { @@ -293,24 +344,27 @@ public class MultiStateStats { */ public void writeXml(TypedXmlSerializer serializer) throws IOException { long[] tmpArray = new long[mCounter.getArrayLength()]; - writeXmlAllStates(serializer, new int[mFactory.mStates.length], 0, tmpArray); - } - - private void writeXmlAllStates(TypedXmlSerializer serializer, int[] states, int stateIndex, - long[] values) throws IOException { - if (stateIndex < states.length) { - if (!mFactory.mStates[stateIndex].mTracked) { - writeXmlAllStates(serializer, states, stateIndex + 1, values); - return; - } - for (int i = 0; i < mFactory.mStates[stateIndex].mLabels.length; i++) { - states[stateIndex] = i; - writeXmlAllStates(serializer, states, stateIndex + 1, values); + try { + States.forEachTrackedStateCombination(mFactory.mStates, + states -> { + try { + writeXmlForStates(serializer, states, tmpArray); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + } catch (RuntimeException e) { + if (e.getCause() instanceof IOException) { + throw (IOException) e.getCause(); + } else { + throw e; } - return; } + } + private void writeXmlForStates(TypedXmlSerializer serializer, int[] states, long[] values) + throws IOException { mCounter.getCounts(values, mFactory.getSerialState(states)); boolean nonZero = false; for (long value : values) { @@ -391,48 +445,33 @@ public class MultiStateStats { /** * 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); + public void dump(PrintWriter pw, Function<long[], String> statsFormatter) { + long[] values = new long[mCounter.getArrayLength()]; + States.forEachTrackedStateCombination(mFactory.mStates, states -> { + mCounter.getCounts(values, mFactory.getSerialState(states)); + boolean nonZero = false; + for (long value : values) { + if (value != 0) { + nonZero = true; + break; + } } - return; - } - - mCounter.getCounts(values, mFactory.getSerialState(states)); - boolean nonZero = false; - for (long value : values) { - if (value != 0) { - nonZero = true; - break; + if (!nonZero) { + return; } - } - 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(" "); + 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(mFactory.mStates[i].mLabels[states[i]]); } - } - sb.append(" "); - sb.append(Arrays.toString(values)); - pw.println(sb); + sb.append(" "); + sb.append(statsFormatter.apply(values)); + pw.println(sb); + }); } } diff --git a/core/java/com/android/internal/os/PowerProfile.java b/core/java/com/android/internal/os/PowerProfile.java index 503e689c0d5d..2298cbde10d1 100644 --- a/core/java/com/android/internal/os/PowerProfile.java +++ b/core/java/com/android/internal/os/PowerProfile.java @@ -44,7 +44,6 @@ import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; -import java.util.Locale; /** * Reports power consumption values for various device activities. Reads values from an XML file. @@ -295,7 +294,7 @@ public class PowerProfile { private static final long SUBSYSTEM_FIELDS_MASK = 0xFFFF_FFFF; - private static final int DEFAULT_CPU_POWER_BRACKET_NUMBER = 3; + public static final int POWER_BRACKETS_UNSPECIFIED = -1; /** * A map from Power Use Item to its power consumption. @@ -361,7 +360,7 @@ public class PowerProfile { } initCpuClusters(); initCpuScalingPolicies(); - initCpuPowerBrackets(DEFAULT_CPU_POWER_BRACKET_NUMBER); + initCpuPowerBrackets(); initDisplays(); initModem(); } @@ -560,8 +559,7 @@ public class PowerProfile { /** * Parses or computes CPU power brackets: groups of states with similar power requirements. */ - @VisibleForTesting - public void initCpuPowerBrackets(int defaultCpuPowerBracketNumber) { + private void initCpuPowerBrackets() { boolean anyBracketsSpecified = false; boolean allBracketsSpecified = true; for (int i = mCpuScalingPolicies.size() - 1; i >= 0; i--) { @@ -580,79 +578,32 @@ public class PowerProfile { "Power brackets should be specified for all scaling policies or none"); } - mCpuPowerBracketCount = 0; - if (allBracketsSpecified) { - for (int i = mCpuScalingPolicies.size() - 1; i >= 0; i--) { - int policy = mCpuScalingPolicies.keyAt(i); - CpuScalingPolicyPower cpuScalingPolicyPower = mCpuScalingPolicies.valueAt(i); - final Double[] data = sPowerArrayMap.get(CPU_POWER_BRACKETS_PREFIX + policy); - if (data.length != cpuScalingPolicyPower.powerBrackets.length) { - throw new RuntimeException( - "Wrong number of items in " + CPU_POWER_BRACKETS_PREFIX + policy - + ", expected: " - + cpuScalingPolicyPower.powerBrackets.length); - } + if (!allBracketsSpecified) { + mCpuPowerBracketCount = POWER_BRACKETS_UNSPECIFIED; + return; + } - for (int j = 0; j < data.length; j++) { - final int bracket = (int) Math.round(data[j]); - cpuScalingPolicyPower.powerBrackets[j] = bracket; - if (bracket > mCpuPowerBracketCount) { - mCpuPowerBracketCount = bracket; - } - } - } - mCpuPowerBracketCount++; - } else { - double minPower = Double.MAX_VALUE; - double maxPower = Double.MIN_VALUE; - int stateCount = 0; - for (int i = mCpuScalingPolicies.size() - 1; i >= 0; i--) { - int policy = mCpuScalingPolicies.keyAt(i); - CpuScalingPolicyPower cpuScalingPolicyPower = mCpuScalingPolicies.valueAt(i); - final int steps = cpuScalingPolicyPower.stepPower.length; - for (int step = 0; step < steps; step++) { - final double power = getAveragePowerForCpuScalingStep(policy, step); - if (power < minPower) { - minPower = power; - } - if (power > maxPower) { - maxPower = power; - } - } - stateCount += steps; + mCpuPowerBracketCount = 0; + for (int i = mCpuScalingPolicies.size() - 1; i >= 0; i--) { + int policy = mCpuScalingPolicies.keyAt(i); + CpuScalingPolicyPower cpuScalingPolicyPower = mCpuScalingPolicies.valueAt(i); + final Double[] data = sPowerArrayMap.get(CPU_POWER_BRACKETS_PREFIX + policy); + if (data.length != cpuScalingPolicyPower.powerBrackets.length) { + throw new RuntimeException( + "Wrong number of items in " + CPU_POWER_BRACKETS_PREFIX + policy + + ", expected: " + + cpuScalingPolicyPower.powerBrackets.length); } - if (stateCount <= defaultCpuPowerBracketNumber) { - mCpuPowerBracketCount = stateCount; - int bracket = 0; - for (int i = 0; i < mCpuScalingPolicies.size(); i++) { - CpuScalingPolicyPower cpuScalingPolicyPower = mCpuScalingPolicies.valueAt(i); - final int steps = cpuScalingPolicyPower.stepPower.length; - for (int step = 0; step < steps; step++) { - cpuScalingPolicyPower.powerBrackets[step] = bracket++; - } - } - } else { - mCpuPowerBracketCount = defaultCpuPowerBracketNumber; - final double minLogPower = Math.log(minPower); - final double logBracket = (Math.log(maxPower) - minLogPower) - / defaultCpuPowerBracketNumber; - - for (int i = mCpuScalingPolicies.size() - 1; i >= 0; i--) { - int policy = mCpuScalingPolicies.keyAt(i); - CpuScalingPolicyPower cpuScalingPolicyPower = mCpuScalingPolicies.valueAt(i); - final int steps = cpuScalingPolicyPower.stepPower.length; - for (int step = 0; step < steps; step++) { - final double power = getAveragePowerForCpuScalingStep(policy, step); - int bracket = (int) ((Math.log(power) - minLogPower) / logBracket); - if (bracket >= defaultCpuPowerBracketNumber) { - bracket = defaultCpuPowerBracketNumber - 1; - } - cpuScalingPolicyPower.powerBrackets[step] = bracket; - } + for (int j = 0; j < data.length; j++) { + final int bracket = (int) Math.round(data[j]); + cpuScalingPolicyPower.powerBrackets[j] = bracket; + if (bracket > mCpuPowerBracketCount) { + mCpuPowerBracketCount = bracket; } } } + mCpuPowerBracketCount++; } private static class CpuScalingPolicyPower { @@ -771,44 +722,13 @@ public class PowerProfile { /** * Returns the number of CPU power brackets: groups of states with similar power requirements. + * If power brackets are not specified, returns {@link #POWER_BRACKETS_UNSPECIFIED} */ public int getCpuPowerBracketCount() { return mCpuPowerBracketCount; } /** - * Description of a CPU power bracket: which cluster/frequency combinations are included. - */ - public String getCpuPowerBracketDescription(CpuScalingPolicies cpuScalingPolicies, - int powerBracket) { - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < mCpuScalingPolicies.size(); i++) { - int policy = mCpuScalingPolicies.keyAt(i); - CpuScalingPolicyPower cpuScalingPolicyPower = mCpuScalingPolicies.valueAt(i); - int[] brackets = cpuScalingPolicyPower.powerBrackets; - int[] freqs = cpuScalingPolicies.getFrequencies(policy); - for (int step = 0; step < brackets.length; step++) { - if (brackets[step] == powerBracket) { - if (sb.length() != 0) { - sb.append(", "); - } - if (mCpuScalingPolicies.size() > 1) { - sb.append(policy).append('/'); - } - if (step < freqs.length) { - sb.append(freqs[step] / 1000); - } - sb.append('('); - sb.append(String.format(Locale.US, "%.1f", - getAveragePowerForCpuScalingStep(policy, step))); - sb.append(')'); - } - } - } - return sb.toString(); - } - - /** * Returns the CPU power bracket corresponding to the specified scaling policy and frequency * step */ diff --git a/core/java/com/android/internal/os/PowerStats.java b/core/java/com/android/internal/os/PowerStats.java index 1130a454c6b0..1a7efac82278 100644 --- a/core/java/com/android/internal/os/PowerStats.java +++ b/core/java/com/android/internal/os/PowerStats.java @@ -55,12 +55,12 @@ public final class PowerStats { 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; + (1 << 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; + (1 << Integer.bitCount(UID_STATS_ARRAY_LENGTH_MASK)) - 1; /** * Descriptor of the stats collected for a given power component (e.g. CPU, WiFi etc). diff --git a/core/tests/coretests/src/android/content/BrickDeniedTest.java b/core/tests/coretests/src/android/content/BrickDeniedTest.java deleted file mode 100644 index d8c9baa8df5b..000000000000 --- a/core/tests/coretests/src/android/content/BrickDeniedTest.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2008 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 android.content; - -import android.test.AndroidTestCase; - -import androidx.test.filters.SmallTest; - -/** Test to make sure brick intents <b>don't</b> work without permission. */ -public class BrickDeniedTest extends AndroidTestCase { - @SmallTest - public void testBrick() { - // Try both the old and new brick intent names. Neither should work, - // since this test application doesn't have the required permission. - // If it does work, well, the test certainly won't pass. - getContext().sendBroadcast(new Intent("SHES_A_BRICK_HOUSE")); - getContext().sendBroadcast(new Intent("android.intent.action.BRICK")); - } -} diff --git a/core/tests/coretests/src/com/android/internal/os/PowerProfileTest.java b/core/tests/coretests/src/com/android/internal/os/PowerProfileTest.java index 8fa63760d231..77202d1faa31 100644 --- a/core/tests/coretests/src/com/android/internal/os/PowerProfileTest.java +++ b/core/tests/coretests/src/com/android/internal/os/PowerProfileTest.java @@ -21,16 +21,12 @@ import static com.android.internal.os.PowerProfile.POWER_GROUP_DISPLAY_AMBIENT; import static com.android.internal.os.PowerProfile.POWER_GROUP_DISPLAY_SCREEN_FULL; import static com.android.internal.os.PowerProfile.POWER_GROUP_DISPLAY_SCREEN_ON; -import static com.google.common.truth.Truth.assertThat; - -import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; import android.annotation.XmlRes; import android.content.Context; import android.content.res.Resources; import android.content.res.XmlResourceParser; -import android.util.SparseArray; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; @@ -540,66 +536,4 @@ public class PowerProfileTest { private void assertEquals(double expected, double actual) { Assert.assertEquals(expected, actual, 0.1); } - - @Test - public void powerBrackets_specifiedInPowerProfile() { - mProfile.forceInitForTesting(mContext, R.xml.power_profile_test_power_brackets); - mProfile.initCpuPowerBrackets(8); - - int cpuPowerBracketCount = mProfile.getCpuPowerBracketCount(); - assertThat(cpuPowerBracketCount).isEqualTo(2); - assertThat(new int[]{ - mProfile.getCpuPowerBracketForScalingStep(0, 0), - mProfile.getCpuPowerBracketForScalingStep(4, 0), - mProfile.getCpuPowerBracketForScalingStep(4, 1), - }).isEqualTo(new int[]{1, 1, 0}); - } - - @Test - public void powerBrackets_automatic() { - mProfile.forceInitForTesting(mContext, R.xml.power_profile_test); - CpuScalingPolicies scalingPolicies = new CpuScalingPolicies( - new SparseArray<>() {{ - put(0, new int[]{0, 1, 2}); - put(3, new int[]{3, 4}); - }}, - new SparseArray<>() {{ - put(0, new int[]{300000, 1000000, 2000000}); - put(3, new int[]{300000, 1000000, 2500000, 3000000}); - }}); - - assertThat(mProfile.getCpuPowerBracketCount()).isEqualTo(3); - assertThat(mProfile.getCpuPowerBracketDescription(scalingPolicies, 0)) - .isEqualTo("0/300(10.0)"); - assertThat(mProfile.getCpuPowerBracketDescription(scalingPolicies, 1)) - .isEqualTo("0/1000(20.0), 0/2000(30.0), 3/300(25.0)"); - assertThat(mProfile.getCpuPowerBracketDescription(scalingPolicies, 2)) - .isEqualTo("3/1000(35.0), 3/2500(50.0), 3/3000(60.0)"); - assertThat(new int[]{ - mProfile.getCpuPowerBracketForScalingStep(0, 0), - mProfile.getCpuPowerBracketForScalingStep(0, 1), - mProfile.getCpuPowerBracketForScalingStep(0, 2), - mProfile.getCpuPowerBracketForScalingStep(3, 0), - mProfile.getCpuPowerBracketForScalingStep(3, 1), - mProfile.getCpuPowerBracketForScalingStep(3, 2), - mProfile.getCpuPowerBracketForScalingStep(3, 3), - }).isEqualTo(new int[]{0, 1, 1, 1, 2, 2, 2}); - } - - @Test - public void powerBrackets_moreBracketsThanStates() { - mProfile.forceInitForTesting(mContext, R.xml.power_profile_test); - mProfile.initCpuPowerBrackets(8); - - assertThat(mProfile.getCpuPowerBracketCount()).isEqualTo(7); - assertThat(new int[]{ - mProfile.getCpuPowerBracketForScalingStep(0, 0), - mProfile.getCpuPowerBracketForScalingStep(0, 1), - mProfile.getCpuPowerBracketForScalingStep(0, 2), - mProfile.getCpuPowerBracketForScalingStep(3, 0), - mProfile.getCpuPowerBracketForScalingStep(3, 1), - mProfile.getCpuPowerBracketForScalingStep(3, 2), - mProfile.getCpuPowerBracketForScalingStep(3, 3), - }).isEqualTo(new int[]{0, 1, 2, 3, 4, 5, 6}); - } } diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml index fa56516e0d22..c525a297b2e0 100644 --- a/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml +++ b/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml @@ -35,8 +35,8 @@ <ImageView android:id="@+id/application_icon" - android:layout_width="24dp" - android:layout_height="24dp" + android:layout_width="@dimen/desktop_mode_caption_icon_radius" + android:layout_height="@dimen/desktop_mode_caption_icon_radius" android:layout_gravity="center_vertical" android:contentDescription="@string/app_icon_text" /> diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml index 87e0b2867090..c6f85a0b4ed4 100644 --- a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml +++ b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml @@ -34,10 +34,10 @@ <ImageView android:id="@+id/application_icon" - android:layout_width="24dp" - android:layout_height="24dp" - android:layout_marginStart="14dp" - android:layout_marginEnd="14dp" + android:layout_width="@dimen/desktop_mode_caption_icon_radius" + android:layout_height="@dimen/desktop_mode_caption_icon_radius" + android:layout_marginStart="12dp" + android:layout_marginEnd="12dp" android:contentDescription="@string/app_icon_text"/> <TextView diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml index 1f6f7aeadd45..c4be384f48b3 100644 --- a/libs/WindowManager/Shell/res/values/dimen.xml +++ b/libs/WindowManager/Shell/res/values/dimen.xml @@ -454,6 +454,9 @@ <!-- The radius of the caption menu corners. --> <dimen name="desktop_mode_handle_menu_corner_radius">26dp</dimen> + <!-- The radius of the caption menu icon. --> + <dimen name="desktop_mode_caption_icon_radius">28dp</dimen> + <!-- The radius of the caption menu shadow. --> <dimen name="desktop_mode_handle_menu_shadow_radius">2dp</dimen> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java index ac5ba51ec139..3660fa29e9e4 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java @@ -57,6 +57,7 @@ import android.view.SurfaceView; import android.view.View; import android.view.ViewGroup; import android.view.ViewOutlineProvider; +import android.view.ViewPropertyAnimator; import android.view.ViewTreeObserver; import android.view.WindowManagerPolicyConstants; import android.view.accessibility.AccessibilityNodeInfo; @@ -212,7 +213,8 @@ public class BubbleStackView extends FrameLayout private ExpandedViewAnimationController mExpandedViewAnimationController; private View mScrim; - private boolean mScrimAnimating; + @Nullable + private ViewPropertyAnimator mScrimAnimation; private View mManageMenuScrim; private FrameLayout mExpandedViewContainer; @@ -748,8 +750,8 @@ public class BubbleStackView extends FrameLayout float collapsed = -Math.min(dy, 0); mExpandedViewAnimationController.updateDrag((int) collapsed); - // Update scrim - if (!mScrimAnimating) { + // Update scrim if it's not animating already + if (mScrimAnimation == null) { mScrim.setAlpha(getScrimAlphaForDrag(collapsed)); } } @@ -768,8 +770,8 @@ public class BubbleStackView extends FrameLayout } else { mExpandedViewAnimationController.animateBackToExpanded(); - // Update scrim - if (!mScrimAnimating) { + // Update scrim if it's not animating already + if (mScrimAnimation == null) { showScrim(true, null /* runnable */); } } @@ -2237,26 +2239,27 @@ public class BubbleStackView extends FrameLayout private void showScrim(boolean show, Runnable after) { AnimatorListenerAdapter listener = new AnimatorListenerAdapter() { @Override - public void onAnimationStart(Animator animation) { - mScrimAnimating = true; - } - - @Override public void onAnimationEnd(Animator animation) { - mScrimAnimating = false; + mScrimAnimation = null; if (after != null) { after.run(); } } }; + if (mScrimAnimation != null) { + // Cancel scrim animation if it animates + mScrimAnimation.cancel(); + } if (show) { - mScrim.animate() + mScrimAnimation = mScrim.animate(); + mScrimAnimation .setInterpolator(ALPHA_IN) .alpha(BUBBLE_EXPANDED_SCRIM_ALPHA) .setListener(listener) .start(); } else { - mScrim.animate() + mScrimAnimation = mScrim.animate(); + mScrimAnimation .alpha(0f) .setInterpolator(ALPHA_OUT) .setListener(listener) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java index de03f5826925..9cd318f27355 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java @@ -25,6 +25,7 @@ import static android.app.ActivityOptions.ANIM_SCENE_TRANSITION; import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_DOWN; import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_UP; import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM; +import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_RESOURCE_UPDATED; import static android.app.admin.DevicePolicyManager.EXTRA_RESOURCE_TYPE; @@ -515,7 +516,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { } if (backgroundColorForTransition != 0) { - addBackgroundColorOnTDA(info, backgroundColorForTransition, startTransaction, + addBackgroundColor(info, backgroundColorForTransition, startTransaction, finishTransaction); } @@ -546,7 +547,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { return true; } - private void addBackgroundColorOnTDA(@NonNull TransitionInfo info, + private void addBackgroundColor(@NonNull TransitionInfo info, @ColorInt int color, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction) { final Color bgColor = Color.valueOf(color); @@ -558,9 +559,19 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { .setName("animation-background") .setCallsite("DefaultTransitionHandler") .setColorLayer(); - - mRootTDAOrganizer.attachToDisplayArea(displayId, colorLayerBuilder); final SurfaceControl backgroundSurface = colorLayerBuilder.build(); + + // Attaching the background surface to the transition root could unexpectedly make it + // cover one of the split root tasks. To avoid this, put the background surface just + // above the display area when split is on. + final boolean isSplitTaskInvolved = + info.getChanges().stream().anyMatch(c-> c.getTaskInfo() != null + && c.getTaskInfo().getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW); + if (isSplitTaskInvolved) { + mRootTDAOrganizer.attachToDisplayArea(displayId, colorLayerBuilder); + } else { + startTransaction.reparent(backgroundSurface, info.getRootLeash()); + } startTransaction.setColor(backgroundSurface, colorArray) .setLayer(backgroundSurface, -1) .show(backgroundSurface); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java index 3aed9ebc6c5e..a976584e9811 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java @@ -20,6 +20,8 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.windowingModeToString; +import static com.android.launcher3.icons.BaseIconFactory.MODE_DEFAULT; + import android.app.ActivityManager; import android.app.WindowConfiguration.WindowingMode; import android.content.Context; @@ -27,6 +29,7 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.res.Configuration; import android.content.res.Resources; +import android.graphics.Bitmap; import android.graphics.Point; import android.graphics.PointF; import android.graphics.Rect; @@ -44,6 +47,7 @@ import android.widget.ImageButton; import android.window.WindowContainerTransaction; import com.android.internal.policy.ScreenDecorationsUtils; +import com.android.launcher3.icons.BaseIconFactory; import com.android.launcher3.icons.IconProvider; import com.android.wm.shell.R; import com.android.wm.shell.RootTaskDisplayAreaOrganizer; @@ -93,7 +97,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin private ResizeVeil mResizeVeil; - private Drawable mAppIcon; + private Drawable mAppIconDrawable; + private Bitmap mAppIconBitmap; private CharSequence mAppName; private ExclusionRegionListener mExclusionRegionListener; @@ -255,7 +260,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin mOnCaptionButtonClickListener, mOnCaptionLongClickListener, mAppName, - mAppIcon + mAppIconBitmap ); } else { throw new IllegalArgumentException("Unexpected layout resource id"); @@ -362,10 +367,15 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin String packageName = mTaskInfo.realActivity.getPackageName(); PackageManager pm = mContext.getApplicationContext().getPackageManager(); try { - IconProvider provider = new IconProvider(mContext); - mAppIcon = provider.getIcon(pm.getActivityInfo(mTaskInfo.baseActivity, + final IconProvider provider = new IconProvider(mContext); + mAppIconDrawable = provider.getIcon(pm.getActivityInfo(mTaskInfo.baseActivity, PackageManager.ComponentInfoFlags.of(0))); - ApplicationInfo applicationInfo = pm.getApplicationInfo(packageName, + final Resources resources = mContext.getResources(); + final BaseIconFactory factory = new BaseIconFactory(mContext, + resources.getDisplayMetrics().densityDpi, + resources.getDimensionPixelSize(R.dimen.desktop_mode_caption_icon_radius)); + mAppIconBitmap = factory.createScaledBitmap(mAppIconDrawable, MODE_DEFAULT); + final ApplicationInfo applicationInfo = pm.getApplicationInfo(packageName, PackageManager.ApplicationInfoFlags.of(0)); mAppName = pm.getApplicationLabel(applicationInfo); } catch (PackageManager.NameNotFoundException e) { @@ -386,7 +396,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin * until a resize event calls showResizeVeil below. */ void createResizeVeil() { - mResizeVeil = new ResizeVeil(mContext, mAppIcon, mTaskInfo, + mResizeVeil = new ResizeVeil(mContext, mAppIconDrawable, mTaskInfo, mSurfaceControlBuilderSupplier, mDisplay, mSurfaceControlTransactionSupplier); } @@ -459,7 +469,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin */ void createHandleMenu() { mHandleMenu = new HandleMenu.Builder(this) - .setAppIcon(mAppIcon) + .setAppIcon(mAppIconBitmap) .setAppName(mAppName) .setOnClickListener(mOnCaptionButtonClickListener) .setOnTouchListener(mOnCaptionTouchListener) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java index 6391518b5911..1941d66cc172 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java @@ -29,9 +29,9 @@ import android.content.res.ColorStateList; import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.TypedArray; +import android.graphics.Bitmap; import android.graphics.Color; import android.graphics.PointF; -import android.graphics.drawable.Drawable; import android.view.MotionEvent; import android.view.SurfaceControl; import android.view.View; @@ -58,7 +58,7 @@ class HandleMenu { private WindowDecoration.AdditionalWindow mHandleMenuWindow; private final PointF mHandleMenuPosition = new PointF(); private final boolean mShouldShowWindowingPill; - private final Drawable mAppIcon; + private final Bitmap mAppIconBitmap; private final CharSequence mAppName; private final View.OnClickListener mOnClickListener; private final View.OnTouchListener mOnTouchListener; @@ -76,7 +76,7 @@ class HandleMenu { HandleMenu(WindowDecoration parentDecor, int layoutResId, int captionX, int captionY, View.OnClickListener onClickListener, View.OnTouchListener onTouchListener, - Drawable appIcon, CharSequence appName, boolean shouldShowWindowingPill, + Bitmap appIcon, CharSequence appName, boolean shouldShowWindowingPill, int captionHeight) { mParentDecor = parentDecor; mContext = mParentDecor.mDecorWindowContext; @@ -86,7 +86,7 @@ class HandleMenu { mCaptionY = captionY; mOnClickListener = onClickListener; mOnTouchListener = onTouchListener; - mAppIcon = appIcon; + mAppIconBitmap = appIcon; mAppName = appName; mShouldShowWindowingPill = shouldShowWindowingPill; mCaptionHeight = captionHeight; @@ -150,7 +150,7 @@ class HandleMenu { final ImageView appIcon = handleMenu.findViewById(R.id.application_icon); final TextView appName = handleMenu.findViewById(R.id.application_name); collapseBtn.setOnClickListener(mOnClickListener); - appIcon.setImageDrawable(mAppIcon); + appIcon.setImageBitmap(mAppIconBitmap); appName.setText(mAppName); } @@ -335,7 +335,7 @@ class HandleMenu { static final class Builder { private final WindowDecoration mParent; private CharSequence mName; - private Drawable mAppIcon; + private Bitmap mAppIcon; private View.OnClickListener mOnClickListener; private View.OnTouchListener mOnTouchListener; private int mLayoutId; @@ -354,7 +354,7 @@ class HandleMenu { return this; } - Builder setAppIcon(@Nullable Drawable appIcon) { + Builder setAppIcon(@Nullable Bitmap appIcon) { mAppIcon = appIcon; return this; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt index 400dec4df506..d64312a1c0c8 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt @@ -2,7 +2,7 @@ package com.android.wm.shell.windowdecor.viewholder import android.app.ActivityManager.RunningTaskInfo import android.content.res.ColorStateList -import android.graphics.drawable.Drawable +import android.graphics.Bitmap import android.graphics.drawable.GradientDrawable import android.view.View import android.view.View.OnLongClickListener @@ -22,7 +22,7 @@ internal class DesktopModeAppControlsWindowDecorationViewHolder( onCaptionButtonClickListener: View.OnClickListener, onLongClickListener: OnLongClickListener, appName: CharSequence, - appIcon: Drawable + appIconBitmap: Bitmap ) : DesktopModeWindowDecorationViewHolder(rootView) { private val captionView: View = rootView.requireViewById(R.id.desktop_mode_caption) @@ -44,7 +44,7 @@ internal class DesktopModeAppControlsWindowDecorationViewHolder( maximizeWindowButton.onLongClickListener = onLongClickListener closeWindowButton.setOnTouchListener(onCaptionTouchListener) appNameTextView.text = appName - appIconImageView.setImageDrawable(appIcon) + appIconImageView.setImageBitmap(appIconBitmap) } override fun bindData(taskInfo: RunningTaskInfo) { diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 5b880797b7fd..9ad5c3e79543 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -20,6 +20,8 @@ import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAUL import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_AUDIO; import static android.content.Context.DEVICE_ID_DEFAULT; +import static com.android.media.audio.flags.Flags.autoPublicVolumeApiHardening; + import android.Manifest; import android.annotation.CallbackExecutor; import android.annotation.IntDef; @@ -1060,8 +1062,17 @@ public class AudioManager { * @see #isVolumeFixed() */ public void adjustVolume(int direction, @PublicVolumeFlags int flags) { - MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(getContext()); - helper.sendAdjustVolumeBy(USE_DEFAULT_STREAM_TYPE, direction, flags); + if (autoPublicVolumeApiHardening()) { + final IAudioService service = getService(); + try { + service.adjustVolume(direction, flags); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } else { + MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(getContext()); + helper.sendAdjustVolumeBy(USE_DEFAULT_STREAM_TYPE, direction, flags); + } } /** @@ -1090,8 +1101,17 @@ public class AudioManager { */ public void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, @PublicVolumeFlags int flags) { - MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(getContext()); - helper.sendAdjustVolumeBy(suggestedStreamType, direction, flags); + if (autoPublicVolumeApiHardening()) { + final IAudioService service = getService(); + try { + service.adjustSuggestedStreamVolume(direction, suggestedStreamType, flags); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } else { + MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(getContext()); + helper.sendAdjustVolumeBy(suggestedStreamType, direction, flags); + } } /** @hide */ diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl index 0e7718b060bc..8584dbc62ef9 100644 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -498,6 +498,10 @@ interface IAudioService { in String packageName, int uid, int pid, in UserHandle userHandle, int targetSdkVersion); + oneway void adjustVolume(int direction, int flags); + + oneway void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags); + boolean isMusicActive(in boolean remotely); int getDeviceMaskForStream(in int streamType); diff --git a/packages/CredentialManager/shared/Android.bp b/packages/CredentialManager/shared/Android.bp index 0d4af2a60672..47ca944c479c 100644 --- a/packages/CredentialManager/shared/Android.bp +++ b/packages/CredentialManager/shared/Android.bp @@ -16,5 +16,6 @@ android_library { "androidx.core_core-ktx", "androidx.credentials_credentials", "guava", + "hilt_android", ], } diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/repository/PasswordRepository.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/repository/PasswordRepository.kt index 1cce3baa303e..5738feea0772 100644 --- a/packages/CredentialManager/shared/src/com/android/credentialmanager/repository/PasswordRepository.kt +++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/repository/PasswordRepository.kt @@ -25,8 +25,11 @@ import android.util.Log import com.android.credentialmanager.TAG import com.android.credentialmanager.model.Password import com.android.credentialmanager.model.Request +import javax.inject.Inject +import javax.inject.Singleton -class PasswordRepository { +@Singleton +class PasswordRepository @Inject constructor() { suspend fun selectPassword( password: Password, diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/repository/RequestRepository.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/repository/RequestRepository.kt index 5ab5ab974720..1973fc175c1f 100644 --- a/packages/CredentialManager/shared/src/com/android/credentialmanager/repository/RequestRepository.kt +++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/repository/RequestRepository.kt @@ -16,17 +16,20 @@ package com.android.credentialmanager.repository -import android.app.Application import android.content.Intent +import android.content.pm.PackageManager import android.util.Log import com.android.credentialmanager.TAG import com.android.credentialmanager.model.Request import com.android.credentialmanager.parse import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import javax.inject.Inject +import javax.inject.Singleton -class RequestRepository( - private val application: Application, +@Singleton +class RequestRepository @Inject constructor( + private val packageManager: PackageManager, ) { private val _requests = MutableStateFlow<Request?>(null) @@ -34,7 +37,7 @@ class RequestRepository( suspend fun processRequest(intent: Intent, previousIntent: Intent? = null) { val request = intent.parse( - packageManager = application.packageManager, + packageManager = packageManager, previousIntent = previousIntent ) diff --git a/packages/CredentialManager/wear/Android.bp b/packages/CredentialManager/wear/Android.bp index e5f5cc255733..c883b1f26160 100644 --- a/packages/CredentialManager/wear/Android.bp +++ b/packages/CredentialManager/wear/Android.bp @@ -22,6 +22,7 @@ android_app { static_libs: [ "CredentialManagerShared", + "hilt_android", "Horologist", "PlatformComposeCore", "androidx.activity_activity-compose", diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorActivity.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorActivity.kt index 273d0b120972..0a63cb74a25a 100644 --- a/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorActivity.kt +++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorActivity.kt @@ -29,13 +29,13 @@ import com.android.credentialmanager.ui.WearApp import com.android.credentialmanager.ui.screens.single.password.SinglePasswordScreen import com.google.android.horologist.annotations.ExperimentalHorologistApi import com.google.android.horologist.compose.layout.belowTimeTextPreview +import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.launch -class CredentialSelectorActivity : ComponentActivity() { +@AndroidEntryPoint(ComponentActivity::class) +class CredentialSelectorActivity : Hilt_CredentialSelectorActivity() { - private val viewModel: CredentialSelectorViewModel by viewModels { - CredentialSelectorViewModel.Factory - } + private val viewModel: CredentialSelectorViewModel by viewModels() @OptIn(ExperimentalHorologistApi::class) override fun onCreate(savedInstanceState: Bundle?) { diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorApp.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorApp.kt index e8e403301129..6bd166e855ff 100644 --- a/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorApp.kt +++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorApp.kt @@ -17,18 +17,7 @@ package com.android.credentialmanager import android.app.Application -import com.android.credentialmanager.di.inject -import com.android.credentialmanager.repository.PasswordRepository -import com.android.credentialmanager.repository.RequestRepository +import dagger.hilt.android.HiltAndroidApp -class CredentialSelectorApp : Application() { - - lateinit var requestRepository: RequestRepository - lateinit var passwordRepository: PasswordRepository - - override fun onCreate() { - super.onCreate() - - inject() - } -}
\ No newline at end of file +@HiltAndroidApp(Application::class) +class CredentialSelectorApp : Hilt_CredentialSelectorApp()
\ No newline at end of file diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorViewModel.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorViewModel.kt index d557dc071db7..435cd377114d 100644 --- a/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorViewModel.kt +++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorViewModel.kt @@ -18,20 +18,20 @@ package com.android.credentialmanager import android.content.Intent import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider -import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.APPLICATION_KEY import androidx.lifecycle.viewModelScope -import androidx.lifecycle.viewmodel.CreationExtras import com.android.credentialmanager.model.Request import com.android.credentialmanager.repository.RequestRepository import com.android.credentialmanager.ui.mappers.toGet +import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch +import javax.inject.Inject -class CredentialSelectorViewModel( +@HiltViewModel +class CredentialSelectorViewModel @Inject constructor( private val requestRepository: RequestRepository, ) : ViewModel() { @@ -56,22 +56,6 @@ class CredentialSelectorViewModel( requestRepository.processRequest(intent = intent, previousIntent = previousIntent) } } - - companion object { - val Factory: ViewModelProvider.Factory = object : ViewModelProvider.Factory { - @Suppress("UNCHECKED_CAST") - override fun <T : ViewModel> create( - modelClass: Class<T>, - extras: CreationExtras - ): T { - val application = checkNotNull(extras[APPLICATION_KEY]) - - return CredentialSelectorViewModel( - requestRepository = (application as CredentialSelectorApp).requestRepository, - ) as T - } - } - } } sealed class CredentialSelectorUiState { diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/di/AppModule.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/di/AppModule.kt new file mode 100644 index 000000000000..cb1a4a13690e --- /dev/null +++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/di/AppModule.kt @@ -0,0 +1,18 @@ +package com.android.credentialmanager.di + +import android.content.Context +import android.content.pm.PackageManager +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent +@Module +@InstallIn(SingletonComponent::class) +internal object AppModule { + @Provides + @JvmStatic + fun providePackageManager(@ApplicationContext context: Context): PackageManager = + context.packageManager +} + diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/di/DI.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/di/DI.kt deleted file mode 100644 index 1e8f83d69e42..000000000000 --- a/packages/CredentialManager/wear/src/com/android/credentialmanager/di/DI.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.android.credentialmanager.di - -import android.app.Application -import com.android.credentialmanager.CredentialSelectorApp -import com.android.credentialmanager.repository.PasswordRepository -import com.android.credentialmanager.repository.RequestRepository - -// TODO b/301601582 add Hilt for dependency injection - -fun CredentialSelectorApp.inject() { - requestRepository = requestRepository(application = this) - passwordRepository = passwordRepository() -} - -private fun requestRepository( - application: Application, -): RequestRepository = RequestRepository( - application = application, -) - -private fun passwordRepository(): PasswordRepository = PasswordRepository() diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreen.kt index c87cfd374cd3..81a067289d89 100644 --- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreen.kt +++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreen.kt @@ -47,8 +47,7 @@ fun SinglePasswordScreen( columnState: ScalingLazyColumnState, onCloseApp: () -> Unit, modifier: Modifier = Modifier, - viewModel: SinglePasswordScreenViewModel = - viewModel(factory = SinglePasswordScreenViewModel.Factory), + viewModel: SinglePasswordScreenViewModel = viewModel(), ) { viewModel.initialize() diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreenViewModel.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreenViewModel.kt index 3167e6784e66..43514a039661 100644 --- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreenViewModel.kt +++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreenViewModel.kt @@ -21,11 +21,7 @@ import android.util.Log import androidx.activity.result.IntentSenderRequest import androidx.annotation.MainThread import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider -import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.APPLICATION_KEY import androidx.lifecycle.viewModelScope -import androidx.lifecycle.viewmodel.CreationExtras -import com.android.credentialmanager.CredentialSelectorApp import com.android.credentialmanager.TAG import com.android.credentialmanager.ktx.getIntentSenderRequest import com.android.credentialmanager.model.Password @@ -33,12 +29,15 @@ import com.android.credentialmanager.model.Request import com.android.credentialmanager.repository.PasswordRepository import com.android.credentialmanager.repository.RequestRepository import com.android.credentialmanager.ui.model.PasswordUiModel +import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch +import javax.inject.Inject -class SinglePasswordScreenViewModel( +@HiltViewModel +class SinglePasswordScreenViewModel @Inject constructor( private val requestRepository: RequestRepository, private val passwordRepository: PasswordRepository, ) : ViewModel() { @@ -105,23 +104,6 @@ class SinglePasswordScreenViewModel( _uiState.value = SinglePasswordScreenUiState.Completed } } - - companion object { - val Factory: ViewModelProvider.Factory = object : ViewModelProvider.Factory { - @Suppress("UNCHECKED_CAST") - override fun <T : ViewModel> create( - modelClass: Class<T>, - extras: CreationExtras - ): T { - val application = checkNotNull(extras[APPLICATION_KEY]) - - return SinglePasswordScreenViewModel( - requestRepository = (application as CredentialSelectorApp).requestRepository, - passwordRepository = application.passwordRepository, - ) as T - } - } - } } sealed class SinglePasswordScreenUiState { diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarDelegate.java b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarDelegate.java index 155cfbbc6677..b63333719334 100644 --- a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarDelegate.java +++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarDelegate.java @@ -173,13 +173,12 @@ public class CollapsingToolbarDelegate { return mCoordinatorLayout; } - /** Sets the title on the collapsing layout, delegating to host if needed. */ + /** Sets the title on the collapsing layout and delegates to host. */ public void setTitle(CharSequence title) { if (mCollapsingToolbarLayout != null) { mCollapsingToolbarLayout.setTitle(title); - } else { - mHostCallback.setOuterTitle(title); } + mHostCallback.setOuterTitle(title); } /** Returns an instance of collapsing toolbar. */ diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index 96876747c082..6eaabbb389c2 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -1098,19 +1098,6 @@ <!-- Used to let users know that they have more than some amount of battery life remaining. ex: more than 1 day remaining [CHAR LIMIT = 40] --> <string name="power_remaining_only_more_than_subtext">More than <xliff:g id="time_remaining">%1$s</xliff:g> left</string> - <!-- [CHAR_LIMIT=50] Short label for imminent shutdown warning of device --> - <string name="power_remaining_duration_only_shutdown_imminent" product="default">Phone may shut down soon</string> - <!-- [CHAR_LIMIT=50] Short label for imminent shutdown warning of device --> - <string name="power_remaining_duration_only_shutdown_imminent" product="tablet">Tablet may shut down soon</string> - <!-- [CHAR_LIMIT=50] Short label for imminent shutdown warning of device --> - <string name="power_remaining_duration_only_shutdown_imminent" product="device">Device may shut down soon</string> - <!-- [CHAR_LIMIT=60] Label for battery level chart when shutdown is imminent--> - <string name="power_remaining_duration_shutdown_imminent" product="default">Phone may shut down soon (<xliff:g id="level">%1$s</xliff:g>)</string> - <!-- [CHAR_LIMIT=60] Label for battery level chart when shutdown is imminent--> - <string name="power_remaining_duration_shutdown_imminent" product="tablet">Tablet may shut down soon (<xliff:g id="level">%1$s</xliff:g>)</string> - <!-- [CHAR_LIMIT=60] Label for battery level chart when shutdown is imminent--> - <string name="power_remaining_duration_shutdown_imminent" product="device">Device may shut down soon (<xliff:g id="level">%1$s</xliff:g>)</string> - <!-- [CHAR_LIMIT=40] Label for battery level chart when charging --> <string name="power_charging"><xliff:g id="level">%1$s</xliff:g> - <xliff:g id="state">%2$s</xliff:g></string> <!-- [CHAR_LIMIT=40] Label for estimated remaining duration of battery charging --> diff --git a/packages/SettingsLib/src/com/android/settingslib/PrimarySwitchPreference.java b/packages/SettingsLib/src/com/android/settingslib/PrimarySwitchPreference.java index 363e20aace0d..7fbd35b8afea 100644 --- a/packages/SettingsLib/src/com/android/settingslib/PrimarySwitchPreference.java +++ b/packages/SettingsLib/src/com/android/settingslib/PrimarySwitchPreference.java @@ -19,7 +19,7 @@ package com.android.settingslib; import android.content.Context; import android.util.AttributeSet; import android.view.MotionEvent; -import android.widget.Switch; +import android.widget.CompoundButton; import androidx.annotation.Keep; import androidx.annotation.Nullable; @@ -35,7 +35,7 @@ import com.android.settingslib.core.instrumentation.SettingsJankMonitor; */ public class PrimarySwitchPreference extends RestrictedPreference { - private Switch mSwitch; + private CompoundButton mSwitch; private boolean mChecked; private boolean mCheckedSet; private boolean mEnableSwitch = true; @@ -65,7 +65,7 @@ public class PrimarySwitchPreference extends RestrictedPreference { @Override public void onBindViewHolder(PreferenceViewHolder holder) { super.onBindViewHolder(holder); - mSwitch = (Switch) holder.findViewById(R.id.switchWidget); + mSwitch = (CompoundButton) holder.findViewById(R.id.switchWidget); if (mSwitch != null) { mSwitch.setOnClickListener(v -> { if (mSwitch != null && !mSwitch.isEnabled()) { @@ -153,7 +153,7 @@ public class PrimarySwitchPreference extends RestrictedPreference { setSwitchEnabled(admin == null); } - public Switch getSwitch() { + public CompoundButton getSwitch() { return mSwitch; } diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java index 758f090118ad..60321eb1a9dc 100644 --- a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java +++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java @@ -36,18 +36,17 @@ import android.widget.LinearLayout; import android.widget.TextView; import androidx.annotation.VisibleForTesting; -import androidx.core.content.res.TypedArrayUtils; import androidx.preference.PreferenceManager; import androidx.preference.PreferenceViewHolder; -import androidx.preference.SwitchPreference; +import androidx.preference.SwitchPreferenceCompat; import com.android.settingslib.utils.BuildCompatUtils; /** - * Version of SwitchPreference that can be disabled by a device admin + * Version of SwitchPreferenceCompat that can be disabled by a device admin * using a user restriction. */ -public class RestrictedSwitchPreference extends SwitchPreference { +public class RestrictedSwitchPreference extends SwitchPreferenceCompat { RestrictedPreferenceHelper mHelper; AppOpsManager mAppOpsManager; boolean mUseAdditionalSummary = false; @@ -93,8 +92,7 @@ public class RestrictedSwitchPreference extends SwitchPreference { } public RestrictedSwitchPreference(Context context, AttributeSet attrs) { - this(context, attrs, TypedArrayUtils.getAttr(context, R.attr.switchPreferenceStyle, - android.R.attr.switchPreferenceStyle)); + this(context, attrs, androidx.preference.R.attr.switchPreferenceCompatStyle); } public RestrictedSwitchPreference(Context context) { @@ -113,7 +111,7 @@ public class RestrictedSwitchPreference extends SwitchPreference { @Override public void onBindViewHolder(PreferenceViewHolder holder) { super.onBindViewHolder(holder); - final View switchView = holder.findViewById(android.R.id.switch_widget); + final View switchView = holder.findViewById(androidx.preference.R.id.switchWidget); if (switchView != null) { final View rootView = switchView.getRootView(); rootView.setFilterTouchesWhenObscured(true); diff --git a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/SettingsJankMonitor.kt b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/SettingsJankMonitor.kt index 02d76304f077..f98883737645 100644 --- a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/SettingsJankMonitor.kt +++ b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/SettingsJankMonitor.kt @@ -37,7 +37,7 @@ object SettingsJankMonitor { const val MONITORED_ANIMATION_DURATION_MS = 300L /** - * Detects the jank when click on a SwitchPreference. + * Detects the jank when click on a TwoStatePreference. * * @param recyclerView the recyclerView contains the preference * @param preference the clicked preference diff --git a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodPreference.java b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodPreference.java index 2999c838a866..1501e27eb86f 100644 --- a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodPreference.java +++ b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodPreference.java @@ -31,8 +31,8 @@ import android.view.ViewGroup; import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.InputMethodSubtype; +import android.widget.CompoundButton; import android.widget.ImageView; -import android.widget.Switch; import android.widget.Toast; import androidx.preference.Preference; @@ -138,7 +138,7 @@ public class InputMethodPreference extends PrimarySwitchPreference @Override public void onBindViewHolder(PreferenceViewHolder holder) { super.onBindViewHolder(holder); - final Switch switchWidget = getSwitch(); + final CompoundButton switchWidget = getSwitch(); if (switchWidget != null) { // Avoid default behavior in {@link PrimarySwitchPreference#onBindViewHolder}. switchWidget.setOnClickListener(v -> { diff --git a/packages/SettingsLib/src/com/android/settingslib/utils/PowerUtil.java b/packages/SettingsLib/src/com/android/settingslib/utils/PowerUtil.java index 673f2438de8a..22726549ce05 100644 --- a/packages/SettingsLib/src/com/android/settingslib/utils/PowerUtil.java +++ b/packages/SettingsLib/src/com/android/settingslib/utils/PowerUtil.java @@ -44,45 +44,6 @@ public class PowerUtil { private static final long ONE_MIN_MILLIS = TimeUnit.MINUTES.toMillis(1); /** - * This method produces the text used in various places throughout the system to describe the - * remaining battery life of the phone in a consistent manner. - * - * @param context - * @param drainTimeMs The estimated time remaining before the phone dies in milliseconds. - * @param percentageString An optional percentage of battery remaining string. - * @param basedOnUsage Whether this estimate is based on usage or simple extrapolation. - * @return a properly formatted and localized string describing how much time remains - * before the battery runs out. - */ - public static String getBatteryRemainingStringFormatted(Context context, long drainTimeMs, - @Nullable String percentageString, boolean basedOnUsage) { - if (drainTimeMs > 0) { - if (drainTimeMs <= SEVEN_MINUTES_MILLIS) { - // show a imminent shutdown warning if less than 7 minutes remain - return getShutdownImminentString(context, percentageString); - } else if (drainTimeMs <= FIFTEEN_MINUTES_MILLIS) { - // show a less than 15 min remaining warning if appropriate - CharSequence timeString = StringUtil.formatElapsedTime(context, - FIFTEEN_MINUTES_MILLIS, - false /* withSeconds */, false /* collapseTimeUnit */); - return getUnderFifteenString(context, timeString, percentageString); - } else if (drainTimeMs >= TWO_DAYS_MILLIS) { - // just say more than two day if over 48 hours - return getMoreThanTwoDaysString(context, percentageString); - } else if (drainTimeMs >= ONE_DAY_MILLIS) { - // show remaining days & hours if more than a day - return getMoreThanOneDayString(context, drainTimeMs, - percentageString, basedOnUsage); - } else { - // show the time of day we think you'll run out - return getRegularTimeRemainingString(context, drainTimeMs, - percentageString, basedOnUsage); - } - } - return null; - } - - /** * Method to produce a shortened string describing the remaining battery. Suitable for Quick * Settings and other areas where space is constrained. * @@ -128,14 +89,6 @@ public class PowerUtil { } } - private static String getShutdownImminentString(Context context, String percentageString) { - return TextUtils.isEmpty(percentageString) - ? context.getString(R.string.power_remaining_duration_only_shutdown_imminent) - : context.getString( - R.string.power_remaining_duration_shutdown_imminent, - percentageString); - } - private static String getUnderFifteenString(Context context, CharSequence timeString, String percentageString) { return TextUtils.isEmpty(percentageString) @@ -268,4 +221,4 @@ public class PowerUtil { return time - remainder + multiple; } } -}
\ No newline at end of file +} diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/PrimarySwitchPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/PrimarySwitchPreferenceTest.java index d9cf9f285164..debfa49af794 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/PrimarySwitchPreferenceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/PrimarySwitchPreferenceTest.java @@ -23,8 +23,8 @@ import static org.mockito.Mockito.verify; import android.content.Context; import android.view.LayoutInflater; +import android.widget.CompoundButton; import android.widget.LinearLayout; -import android.widget.Switch; import androidx.preference.Preference.OnPreferenceChangeListener; import androidx.preference.PreferenceViewHolder; @@ -62,7 +62,7 @@ public class PrimarySwitchPreferenceTest { @Test public void setChecked_shouldUpdateButtonCheckedState() { - final Switch toggle = (Switch) mHolder.findViewById(R.id.switchWidget); + final CompoundButton toggle = (CompoundButton) mHolder.findViewById(R.id.switchWidget); mPreference.onBindViewHolder(mHolder); mPreference.setChecked(true); @@ -74,7 +74,7 @@ public class PrimarySwitchPreferenceTest { @Test public void setSwitchEnabled_shouldUpdateButtonEnabledState() { - final Switch toggle = (Switch) mHolder.findViewById(R.id.switchWidget); + final CompoundButton toggle = (CompoundButton) mHolder.findViewById(R.id.switchWidget); mPreference.onBindViewHolder(mHolder); mPreference.setSwitchEnabled(true); @@ -86,7 +86,7 @@ public class PrimarySwitchPreferenceTest { @Test public void setSwitchEnabled_shouldUpdateButtonEnabledState_beforeViewBound() { - final Switch toggle = (Switch) mHolder.findViewById(R.id.switchWidget); + final CompoundButton toggle = (CompoundButton) mHolder.findViewById(R.id.switchWidget); mPreference.setSwitchEnabled(false); mPreference.onBindViewHolder(mHolder); @@ -97,7 +97,7 @@ public class PrimarySwitchPreferenceTest { public void clickWidgetView_shouldToggleButton() { assertThat(mWidgetView).isNotNull(); - final Switch toggle = (Switch) mHolder.findViewById(R.id.switchWidget); + final CompoundButton toggle = (CompoundButton) mHolder.findViewById(R.id.switchWidget); mPreference.onBindViewHolder(mHolder); toggle.performClick(); @@ -111,7 +111,7 @@ public class PrimarySwitchPreferenceTest { public void clickWidgetView_shouldNotToggleButtonIfDisabled() { assertThat(mWidgetView).isNotNull(); - final Switch toggle = (Switch) mHolder.findViewById(R.id.switchWidget); + final CompoundButton toggle = (CompoundButton) mHolder.findViewById(R.id.switchWidget); mPreference.onBindViewHolder(mHolder); toggle.setEnabled(false); @@ -122,7 +122,7 @@ public class PrimarySwitchPreferenceTest { @Test public void clickWidgetView_shouldNotifyPreferenceChanged() { - final Switch toggle = (Switch) mHolder.findViewById(R.id.switchWidget); + final CompoundButton toggle = (CompoundButton) mHolder.findViewById(R.id.switchWidget); final OnPreferenceChangeListener listener = mock(OnPreferenceChangeListener.class); mPreference.setOnPreferenceChangeListener(listener); @@ -139,7 +139,7 @@ public class PrimarySwitchPreferenceTest { @Test public void setDisabledByAdmin_hasEnforcedAdmin_shouldDisableButton() { - final Switch toggle = (Switch) mHolder.findViewById(R.id.switchWidget); + final CompoundButton toggle = (CompoundButton) mHolder.findViewById(R.id.switchWidget); toggle.setEnabled(true); mPreference.onBindViewHolder(mHolder); @@ -149,7 +149,7 @@ public class PrimarySwitchPreferenceTest { @Test public void setDisabledByAdmin_noEnforcedAdmin_shouldEnableButton() { - final Switch toggle = (Switch) mHolder.findViewById(R.id.switchWidget); + final CompoundButton toggle = (CompoundButton) mHolder.findViewById(R.id.switchWidget); toggle.setEnabled(false); mPreference.onBindViewHolder(mHolder); @@ -159,7 +159,7 @@ public class PrimarySwitchPreferenceTest { @Test public void onBindViewHolder_toggleButtonShouldHaveContentDescription() { - final Switch toggle = (Switch) mHolder.findViewById(R.id.switchWidget); + final CompoundButton toggle = (CompoundButton) mHolder.findViewById(R.id.switchWidget); final String label = "TestButton"; mPreference.setTitle(label); diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/PowerUtilTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/PowerUtilTest.java index ae542062ce50..2e7905f2e1e4 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/PowerUtilTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/PowerUtilTest.java @@ -59,155 +59,6 @@ public class PowerUtilTest { } @Test - public void testGetBatteryRemainingStringFormatted_moreThanFifteenMinutes_withPercentage() { - String info = PowerUtil.getBatteryRemainingStringFormatted(mContext, - SEVENTEEN_MIN_MILLIS, - TEST_BATTERY_LEVEL_10, - true /* basedOnUsage */); - String info2 = PowerUtil.getBatteryRemainingStringFormatted(mContext, - SEVENTEEN_MIN_MILLIS, - TEST_BATTERY_LEVEL_10, - false /* basedOnUsage */); - - // We only add special mention for the long string - // ex: Will last about 1:15 PM based on your usage (10%) - assertThat(info).containsMatch(Pattern.compile( - NORMAL_CASE_EXPECTED_PREFIX - + TIME_OF_DAY_REGEX - + ENHANCED_SUFFIX - + PERCENTAGE_REGEX)); - // shortened string should not have extra text - // ex: Will last about 1:15 PM (10%) - assertThat(info2).containsMatch(Pattern.compile( - NORMAL_CASE_EXPECTED_PREFIX - + TIME_OF_DAY_REGEX - + PERCENTAGE_REGEX)); - } - - @Test - public void testGetBatteryRemainingStringFormatted_moreThanFifteenMinutes_noPercentage() { - String info = PowerUtil.getBatteryRemainingStringFormatted(mContext, - SEVENTEEN_MIN_MILLIS, - null /* percentageString */, - true /* basedOnUsage */); - String info2 = PowerUtil.getBatteryRemainingStringFormatted(mContext, - SEVENTEEN_MIN_MILLIS, - null /* percentageString */, - false /* basedOnUsage */); - - // We only have % when it is provided - // ex: Will last about 1:15 PM based on your usage - assertThat(info).containsMatch(Pattern.compile( - NORMAL_CASE_EXPECTED_PREFIX - + TIME_OF_DAY_REGEX - + ENHANCED_SUFFIX - + "(" + PERCENTAGE_REGEX + "){0}")); // no percentage - // shortened string should not have extra text - // ex: Will last about 1:15 PM - assertThat(info2).containsMatch(Pattern.compile( - NORMAL_CASE_EXPECTED_PREFIX - + TIME_OF_DAY_REGEX - + "(" + PERCENTAGE_REGEX + "){0}")); // no percentage - } - - @Test - public void testGetBatteryRemainingStringFormatted_lessThanSevenMinutes_usesCorrectString() { - String info = PowerUtil.getBatteryRemainingStringFormatted(mContext, - FIVE_MINUTES_MILLIS, - TEST_BATTERY_LEVEL_10 /* percentageString */, - true /* basedOnUsage */); - String info2 = PowerUtil.getBatteryRemainingStringFormatted(mContext, - FIVE_MINUTES_MILLIS, - null /* percentageString */, - true /* basedOnUsage */); - - // additional battery percentage in this string - assertThat(info.contains("may shut down soon (10%)")).isTrue(); - // shortened string should not have percentage - assertThat(info2.contains("may shut down soon")).isTrue(); - } - - @Test - public void testGetBatteryRemainingStringFormatted_betweenSevenAndFifteenMinutes_usesCorrectString() { - String info = PowerUtil.getBatteryRemainingStringFormatted(mContext, - TEN_MINUTES_MILLIS, - null /* percentageString */, - true /* basedOnUsage */); - String info2 = PowerUtil.getBatteryRemainingStringFormatted(mContext, - TEN_MINUTES_MILLIS, - TEST_BATTERY_LEVEL_10 /* percentageString */, - true /* basedOnUsage */); - - // shortened string should not have percentage - assertThat(info).isEqualTo("Less than 15 min left"); - // Add percentage to string when provided - assertThat(info2).isEqualTo("Less than 15 min left (10%)"); - } - - @Test - public void testGetBatteryRemainingStringFormatted_betweenOneAndTwoDays_usesCorrectString() { - String info = PowerUtil.getBatteryRemainingStringFormatted(mContext, - THIRTY_HOURS_MILLIS, - null /* percentageString */, - true /* basedOnUsage */); - String info2 = PowerUtil.getBatteryRemainingStringFormatted(mContext, - THIRTY_HOURS_MILLIS, - TEST_BATTERY_LEVEL_10 /* percentageString */, - false /* basedOnUsage */); - String info3 = PowerUtil.getBatteryRemainingStringFormatted(mContext, - THIRTY_HOURS_MILLIS + TEN_MINUTES_MILLIS, - null /* percentageString */, - false /* basedOnUsage */); - - // We only add special mention for the long string - assertThat(info).isEqualTo("About 1 day, 6 hr left based on your usage"); - // shortened string should not have extra text - assertThat(info2).isEqualTo("About 1 day, 6 hr left (10%)"); - // present 2 time unit at most - assertThat(info3).isEqualTo("About 1 day, 6 hr left"); - } - - @Test - public void testGetBatteryRemainingStringFormatted_lessThanOneDay_usesCorrectString() { - String info = PowerUtil.getBatteryRemainingStringFormatted(mContext, - TEN_HOURS_MILLIS, - null /* percentageString */, - true /* basedOnUsage */); - String info2 = PowerUtil.getBatteryRemainingStringFormatted(mContext, - TEN_HOURS_MILLIS, - TEST_BATTERY_LEVEL_10 /* percentageString */, - false /* basedOnUsage */); - String info3 = PowerUtil.getBatteryRemainingStringFormatted(mContext, - TEN_HOURS_MILLIS + TEN_MINUTES_MILLIS + TEN_SEC_MILLIS, - null /* percentageString */, - false /* basedOnUsage */); - - // We only add special mention for the long string - assertThat(info).isEqualTo("About 10 hr left based on your usage"); - // shortened string should not have extra text - assertThat(info2).isEqualTo("About 10 hr left (10%)"); - // present 2 time unit at most - assertThat(info3).isEqualTo("About 10 hr, 10 min left"); - } - - @Test - public void testGetBatteryRemainingStringFormatted_moreThanTwoDays_usesCorrectString() { - String info = PowerUtil.getBatteryRemainingStringFormatted(mContext, - THREE_DAYS_MILLIS, - null /* percentageString */, - true /* basedOnUsage */); - String info2 = PowerUtil.getBatteryRemainingStringFormatted(mContext, - THREE_DAYS_MILLIS, - TEST_BATTERY_LEVEL_10 /* percentageString */, - true /* basedOnUsage */); - - // shortened string should not have percentage - assertThat(info).isEqualTo("More than 2 days left"); - // Add percentage to string when provided - assertThat(info2).isEqualTo("More than 2 days left (10%)"); - } - - @Test public void getBatteryTipStringFormatted_moreThanOneDay_usesCorrectString() { String info = PowerUtil.getBatteryTipStringFormatted(mContext, THREE_DAYS_MILLIS); diff --git a/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java b/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java index 02ec486a0205..cd35f67a1369 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java @@ -45,7 +45,8 @@ final class GenerationRegistry { private static final boolean DEBUG = false; - private final Object mLock; + // This lock is not the same lock used in SettingsProvider and SettingsState + private final Object mLock = new Object(); // Key -> backingStore mapping @GuardedBy("mLock") @@ -74,8 +75,7 @@ final class GenerationRegistry { private final int mMaxNumBackingStore; - GenerationRegistry(Object lock, int maxNumUsers) { - mLock = lock; + GenerationRegistry(int maxNumUsers) { // Add some buffer to maxNumUsers to accommodate corner cases when the actual number of // users in the system exceeds the limit maxNumUsers = maxNumUsers + 2; diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index 95d7039859b5..5acc1cad160a 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -2884,7 +2884,7 @@ public class SettingsProvider extends ContentProvider { public SettingsRegistry() { mHandler = new MyHandler(getContext().getMainLooper()); - mGenerationRegistry = new GenerationRegistry(mLock, UserManager.getMaxSupportedUsers()); + mGenerationRegistry = new GenerationRegistry(UserManager.getMaxSupportedUsers()); mBackupManager = new BackupManager(getContext()); } diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/GenerationRegistryTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/GenerationRegistryTest.java index 12865f452e22..8029785ba78f 100644 --- a/packages/SettingsProvider/test/src/com/android/providers/settings/GenerationRegistryTest.java +++ b/packages/SettingsProvider/test/src/com/android/providers/settings/GenerationRegistryTest.java @@ -36,7 +36,7 @@ import java.io.IOException; public class GenerationRegistryTest { @Test public void testGenerationsWithRegularSetting() throws IOException { - final GenerationRegistry generationRegistry = new GenerationRegistry(new Object(), 2); + final GenerationRegistry generationRegistry = new GenerationRegistry(2); final int secureKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_SECURE, 0); final String testSecureSetting = "test_secure_setting"; Bundle b = new Bundle(); @@ -93,7 +93,7 @@ public class GenerationRegistryTest { @Test public void testGenerationsWithConfigSetting() throws IOException { - final GenerationRegistry generationRegistry = new GenerationRegistry(new Object(), 1); + final GenerationRegistry generationRegistry = new GenerationRegistry(1); final String prefix = "test_namespace/"; final int configKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_CONFIG, 0); @@ -110,7 +110,7 @@ public class GenerationRegistryTest { @Test public void testMaxNumBackingStores() throws IOException { - final GenerationRegistry generationRegistry = new GenerationRegistry(new Object(), 2); + final GenerationRegistry generationRegistry = new GenerationRegistry(2); final String testSecureSetting = "test_secure_setting"; Bundle b = new Bundle(); for (int i = 0; i < generationRegistry.getMaxNumBackingStores(); i++) { @@ -133,7 +133,7 @@ public class GenerationRegistryTest { @Test public void testMaxSizeBackingStore() throws IOException { - final GenerationRegistry generationRegistry = new GenerationRegistry(new Object(), 1); + final GenerationRegistry generationRegistry = new GenerationRegistry(1); final int secureKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_SECURE, 0); final String testSecureSetting = "test_secure_setting"; Bundle b = new Bundle(); @@ -153,7 +153,7 @@ public class GenerationRegistryTest { @Test public void testUnsetSettings() throws IOException { - final GenerationRegistry generationRegistry = new GenerationRegistry(new Object(), 1); + final GenerationRegistry generationRegistry = new GenerationRegistry(1); final int secureKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_SECURE, 0); final String testSecureSetting = "test_secure_setting"; Bundle b = new Bundle(); @@ -172,7 +172,7 @@ public class GenerationRegistryTest { @Test public void testGlobalSettings() throws IOException { - final GenerationRegistry generationRegistry = new GenerationRegistry(new Object(), 2); + final GenerationRegistry generationRegistry = new GenerationRegistry(2); final int globalKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_GLOBAL, 0); final String testGlobalSetting = "test_global_setting"; final Bundle b = new Bundle(); @@ -190,11 +190,11 @@ public class GenerationRegistryTest { @Test public void testNumberOfBackingStores() { - GenerationRegistry generationRegistry = new GenerationRegistry(new Object(), 0); + GenerationRegistry generationRegistry = new GenerationRegistry(0); // Test that the capacity of the backing stores is always valid assertThat(generationRegistry.getMaxNumBackingStores()).isEqualTo( GenerationRegistry.MIN_NUM_BACKING_STORE); - generationRegistry = new GenerationRegistry(new Object(), 100); + generationRegistry = new GenerationRegistry(100); // Test that the capacity of the backing stores is always valid assertThat(generationRegistry.getMaxNumBackingStores()).isEqualTo( GenerationRegistry.MAX_NUM_BACKING_STORE); diff --git a/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml b/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml index 82410703c9e6..ad9a775ed243 100644 --- a/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml +++ b/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml @@ -115,8 +115,7 @@ android:layout_marginStart="@dimen/dream_overlay_status_icon_margin" android:src="@drawable/dream_overlay_assistant_attention_indicator" android:visibility="gone" - android:contentDescription= - "@string/dream_overlay_status_bar_assistant_attention_indicator" /> + android:contentDescription="@string/assistant_attention_content_description" /> </LinearLayout> </com.android.systemui.dreams.DreamOverlayStatusBarView> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 2e5bc47903e3..4c41ca4153da 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -3022,8 +3022,6 @@ <string name="dream_overlay_status_bar_mic_off">Mic is off</string> <!-- Content description for the camera and mic off icon in the dream overlay status bar [CHAR LIMIT=NONE] --> <string name="dream_overlay_status_bar_camera_mic_off">Camera and mic are off</string> - <!-- Content description for the assistant attention indicator [CHAR LIMIT=NONE] --> - <string name="dream_overlay_status_bar_assistant_attention_indicator">Assistant is listening</string> <!-- Content description for the notifications indicator icon in the dream overlay status bar [CHAR LIMIT=NONE] --> <string name="dream_overlay_status_bar_notification_indicator">{count, plural, =1 {# notification} @@ -3209,7 +3207,7 @@ <string name="priority_mode_dream_overlay_content_description">Priority mode on</string> <!-- Content description for when assistant attention is active [CHAR LIMIT=NONE] --> - <string name="assistant_attention_content_description">Assistant attention on</string> + <string name="assistant_attention_content_description">User presence is detected</string> <!--- Content of toast triggered when the notes app entry point is triggered without setting a default notes app. [CHAR LIMIT=NONE] --> <string name="set_default_notes_app_toast_content">Set default notes app in Settings</string> diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java index 8efe165e0117..7bf3e8f140a0 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java @@ -59,6 +59,7 @@ import com.android.systemui.statusbar.notification.PropertyAnimator; import com.android.systemui.statusbar.notification.icon.ui.viewbinder.AlwaysOnDisplayNotificationIconViewStore; import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder; import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerAlwaysOnDisplayViewModel; +import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor; import com.android.systemui.statusbar.notification.stack.AnimationProperties; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.NotificationIconAreaController; @@ -346,7 +347,7 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS int getNotificationIconAreaHeight() { if (mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { return 0; - } else if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR)) { + } else if (NotificationIconContainerRefactor.isEnabled()) { return mAodIconContainer != null ? mAodIconContainer.getHeight() : 0; } else { return mNotificationIconAreaController.getHeight(); @@ -565,7 +566,7 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS NotificationIconContainer nic = (NotificationIconContainer) mView.findViewById( com.android.systemui.res.R.id.left_aligned_notification_icon_container); - if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR)) { + if (NotificationIconContainerRefactor.isEnabled()) { if (mAodIconsBindJob != null) { mAodIconsBindJob.dispose(); } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java index 584357bb739c..0bd4859eefd5 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java @@ -1461,8 +1461,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold mDragView.setColorFilter(filter); } - @VisibleForTesting - void setBounceEffectDuration(int duration) { + private void setBounceEffectDuration(int duration) { mBounceEffectDuration = duration; } diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingView.kt b/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingView.kt index 323070a84863..07814512b4b8 100644 --- a/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingView.kt +++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingView.kt @@ -22,6 +22,7 @@ import android.content.Context import android.util.AttributeSet import android.view.MotionEvent import android.view.View +import android.view.ViewConfiguration import com.android.systemui.shade.TouchLogger import kotlin.math.pow import kotlin.math.sqrt @@ -36,11 +37,18 @@ import kotlinx.coroutines.DisposableHandle class LongPressHandlingView( context: Context, attrs: AttributeSet?, + private val longPressDuration: () -> Long, ) : View( context, attrs, ) { + + constructor( + context: Context, + attrs: AttributeSet?, + ) : this(context, attrs, { ViewConfiguration.getLongPressTimeout().toLong() }) + interface Listener { /** Notifies that a long-press has been detected by the given view. */ fun onLongPressDetected( @@ -77,6 +85,7 @@ class LongPressHandlingView( ) }, onSingleTapDetected = { listener?.onSingleTapDetected(this@LongPressHandlingView) }, + longPressDuration = longPressDuration, ) } diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingViewInteractionHandler.kt b/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingViewInteractionHandler.kt index c2d4d12d03c3..a742e8d614b1 100644 --- a/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingViewInteractionHandler.kt +++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingViewInteractionHandler.kt @@ -33,6 +33,8 @@ class LongPressHandlingViewInteractionHandler( private val onLongPressDetected: (x: Int, y: Int) -> Unit, /** Callback reporting the a single tap gesture was detected at the given coordinates. */ private val onSingleTapDetected: () -> Unit, + /** Time for the touch to be considered a long-press in ms */ + private val longPressDuration: () -> Long, ) { sealed class MotionEventModel { object Other : MotionEventModel() @@ -77,7 +79,7 @@ class LongPressHandlingViewInteractionHandler( cancelScheduledLongPress() if ( event.distanceMoved <= ViewConfiguration.getTouchSlop() && - event.gestureDuration < ViewConfiguration.getLongPressTimeout() + event.gestureDuration < longPressDuration() ) { dispatchSingleTap() } @@ -103,7 +105,7 @@ class LongPressHandlingViewInteractionHandler( y = y, ) }, - ViewConfiguration.getLongPressTimeout().toLong(), + longPressDuration(), ) } diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index 7933a2e68e1d..06ec17ff80d1 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -87,11 +87,6 @@ object Flags { @JvmField val NOTIFICATION_SHELF_REFACTOR = releasedFlag("notification_shelf_refactor") - // TODO(b/290787599): Tracking Bug - @JvmField - val NOTIFICATION_ICON_CONTAINER_REFACTOR = - unreleasedFlag("notification_icon_container_refactor") - // TODO(b/288326013): Tracking Bug @JvmField val NOTIFICATION_ASYNC_HYBRID_VIEW_INFLATION = diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt index 8a9ea25ce99d..b7fe9605a221 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt @@ -35,6 +35,7 @@ import com.android.systemui.shade.NotificationPanelView import com.android.systemui.statusbar.notification.icon.ui.viewbinder.AlwaysOnDisplayNotificationIconViewStore import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerAlwaysOnDisplayViewModel +import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor import com.android.systemui.statusbar.phone.DozeParameters import com.android.systemui.statusbar.phone.NotificationIconAreaController import com.android.systemui.statusbar.phone.NotificationIconContainer @@ -86,7 +87,7 @@ constructor( return } - if (featureFlags.isEnabled(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR)) { + if (NotificationIconContainerRefactor.isEnabled) { nic.setOnLockScreen(true) nicBindingDisposable?.dispose() nicBindingDisposable = diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java index 38204ab1b6d9..a6c623391bb0 100644 --- a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java +++ b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java @@ -64,14 +64,13 @@ import com.android.internal.logging.UiEventLogger; import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; import com.android.settingslib.Utils; import com.android.settingslib.fuelgauge.BatterySaverUtils; -import com.android.settingslib.utils.PowerUtil; -import com.android.systemui.res.R; import com.android.systemui.SystemUIApplication; import com.android.systemui.animation.DialogCuj; import com.android.systemui.animation.DialogLaunchAnimator; import com.android.systemui.broadcast.BroadcastSender; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.res.R; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.phone.SystemUIDialog; import com.android.systemui.statusbar.policy.BatteryController; @@ -376,14 +375,6 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI { TAG_AUTO_SAVER, SystemMessage.NOTE_AUTO_SAVER_SUGGESTION, n, UserHandle.ALL); } - private String getHybridContentString(String percentage) { - return PowerUtil.getBatteryRemainingStringFormatted( - mContext, - mCurrentBatterySnapshot.getTimeRemainingMillis(), - percentage, - mCurrentBatterySnapshot.isBasedOnUsage()); - } - private PendingIntent pendingBroadcast(String action) { return PendingIntent.getBroadcastAsUser( mContext, diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java index 8f26e694a067..bd4c6e1930ba 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java +++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java @@ -32,7 +32,6 @@ import com.android.systemui.qs.external.QSExternalModule; import com.android.systemui.qs.pipeline.dagger.QSPipelineModule; import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.qs.tiles.di.QSTilesModule; -import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel; import com.android.systemui.statusbar.phone.AutoTileManager; import com.android.systemui.statusbar.phone.ManagedProfileController; import com.android.systemui.statusbar.policy.CastController; @@ -73,13 +72,6 @@ public interface QSModule { @Multibinds Map<String, QSTileImpl<?>> tileMap(); - /** - * A map of internal QS tile ViewModels. Ensures that this can be injected even if - * it is empty - */ - @Multibinds - Map<String, QSTileViewModel> tileViewModelMap(); - @Provides @SysUISingleton static AutoTileManager provideAutoTileManager( diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelFactory.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelFactory.kt index 936bf9c8f4da..736f7cfbfce9 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelFactory.kt @@ -18,6 +18,7 @@ package com.android.systemui.qs.tiles.base.viewmodel import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.plugins.FalsingManager +import com.android.systemui.qs.pipeline.shared.TileSpec import com.android.systemui.qs.tiles.base.analytics.QSTileAnalytics import com.android.systemui.qs.tiles.base.interactor.DisabledByPolicyInteractor import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor @@ -25,7 +26,7 @@ import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor import com.android.systemui.qs.tiles.base.logging.QSTileLogger import com.android.systemui.qs.tiles.impl.di.QSTileComponent -import com.android.systemui.qs.tiles.viewmodel.QSTileConfig +import com.android.systemui.qs.tiles.viewmodel.QSTileConfigProvider import com.android.systemui.qs.tiles.viewmodel.QSTileState import com.android.systemui.user.data.repository.UserRepository import com.android.systemui.util.time.SystemClock @@ -53,6 +54,7 @@ sealed interface QSTileViewModelFactory<T> { private val falsingManager: FalsingManager, private val qsTileAnalytics: QSTileAnalytics, private val qsTileLogger: QSTileLogger, + private val qsTileConfigProvider: QSTileConfigProvider, private val systemClock: SystemClock, @Background private val backgroundDispatcher: CoroutineDispatcher, ) : QSTileViewModelFactory<T> { @@ -61,9 +63,9 @@ sealed interface QSTileViewModelFactory<T> { * Creates [QSTileViewModelImpl] based on the interactors obtained from [component]. * Reference of that [component] is then stored along the view model. */ - fun create(component: QSTileComponent<T>): QSTileViewModelImpl<T> = + fun create(tileSpec: TileSpec, component: QSTileComponent<T>): QSTileViewModelImpl<T> = QSTileViewModelImpl( - component::config, + qsTileConfigProvider.getConfig(tileSpec.spec), component::userActionInteractor, component::dataInteractor, component::dataToStateMapper, @@ -89,12 +91,13 @@ sealed interface QSTileViewModelFactory<T> { private val falsingManager: FalsingManager, private val qsTileAnalytics: QSTileAnalytics, private val qsTileLogger: QSTileLogger, + private val qsTileConfigProvider: QSTileConfigProvider, private val systemClock: SystemClock, @Background private val backgroundDispatcher: CoroutineDispatcher, ) : QSTileViewModelFactory<T> { /** - * @param config contains all the static information (like TileSpec) about the tile. + * @param tileSpec of the created tile. * @param userActionInteractor encapsulates user input processing logic. Use it to start * activities, show dialogs or otherwise update the tile state. * @param tileDataInteractor provides [DATA_TYPE] and its availability. @@ -103,13 +106,13 @@ sealed interface QSTileViewModelFactory<T> { * operations there. */ fun create( - config: QSTileConfig, + tileSpec: TileSpec, userActionInteractor: QSTileUserActionInteractor<T>, tileDataInteractor: QSTileDataInteractor<T>, mapper: QSTileDataToStateMapper<T>, ): QSTileViewModelImpl<T> = QSTileViewModelImpl( - { config }, + qsTileConfigProvider.getConfig(tileSpec.spec), { userActionInteractor }, { tileDataInteractor }, { mapper }, diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImpl.kt index bbb74453abbd..0bee48fd01ab 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImpl.kt @@ -67,7 +67,7 @@ import kotlinx.coroutines.flow.stateIn */ @OptIn(ExperimentalCoroutinesApi::class) class QSTileViewModelImpl<DATA_TYPE>( - val tileConfig: () -> QSTileConfig, + override val config: QSTileConfig, private val userActionInteractor: () -> QSTileUserActionInteractor<DATA_TYPE>, private val tileDataInteractor: () -> QSTileDataInteractor<DATA_TYPE>, private val mapper: () -> QSTileDataToStateMapper<DATA_TYPE>, @@ -92,8 +92,6 @@ class QSTileViewModelImpl<DATA_TYPE>( private val tileData: SharedFlow<DATA_TYPE> = createTileDataFlow() - override val config - get() = tileConfig() override val state: SharedFlow<QSTileState> = tileData .map { data -> diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt index 0a6becd6e4ca..7d7af64a3038 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt @@ -19,6 +19,7 @@ package com.android.systemui.qs.tiles.di import com.android.systemui.dagger.SysUISingleton import com.android.systemui.plugins.qs.QSFactory import com.android.systemui.plugins.qs.QSTile +import com.android.systemui.qs.tiles.viewmodel.QSTileConfigProvider import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel import com.android.systemui.qs.tiles.viewmodel.QSTileViewModelAdapter import javax.inject.Inject @@ -29,11 +30,19 @@ import javax.inject.Provider class NewQSTileFactory @Inject constructor( + qsTileConfigProvider: QSTileConfigProvider, private val adapterFactory: QSTileViewModelAdapter.Factory, private val tileMap: Map<String, @JvmSuppressWildcards Provider<@JvmSuppressWildcards QSTileViewModel>>, ) : QSFactory { + init { + for (viewModelTileSpec in tileMap.keys) { + // throws an exception when there is no config for a tileSpec of an injected viewModel + qsTileConfigProvider.getConfig(viewModelTileSpec) + } + } + override fun createTile(tileSpec: String): QSTile? = tileMap[tileSpec]?.let { val tile = it.get() diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/di/QSTilesModule.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/di/QSTilesModule.kt index 94b39b6db9d2..32522ad66626 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/di/QSTilesModule.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/di/QSTilesModule.kt @@ -17,7 +17,13 @@ package com.android.systemui.qs.tiles.di import com.android.systemui.qs.tiles.impl.custom.di.CustomTileComponent +import com.android.systemui.qs.tiles.viewmodel.QSTileConfig +import com.android.systemui.qs.tiles.viewmodel.QSTileConfigProvider +import com.android.systemui.qs.tiles.viewmodel.QSTileConfigProviderImpl +import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel +import dagger.Binds import dagger.Module +import dagger.multibindings.Multibinds /** Module listing subcomponents */ @Module( @@ -26,4 +32,17 @@ import dagger.Module CustomTileComponent::class, ] ) -interface QSTilesModule +interface QSTilesModule { + + /** + * A map of internal QS tile ViewModels. Ensures that this can be injected even if it is empty + */ + @Multibinds fun tileViewModelConfigs(): Map<String, QSTileConfig> + + /** + * A map of internal QS tile ViewModels. Ensures that this can be injected even if it is empty + */ + @Multibinds fun tileViewModelMap(): Map<String, QSTileViewModel> + + @Binds fun bindQSTileConfigProvider(impl: QSTileConfigProviderImpl): QSTileConfigProvider +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/di/QSTileComponent.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/di/QSTileComponent.kt index 6f351cdb9b33..b3d916a86144 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/di/QSTileComponent.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/di/QSTileComponent.kt @@ -19,7 +19,6 @@ package com.android.systemui.qs.tiles.impl.di import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor -import com.android.systemui.qs.tiles.viewmodel.QSTileConfig /** * Base QS tile component. It should be used with [QSTileScope] to create a custom tile scoped @@ -32,7 +31,5 @@ interface QSTileComponent<T> { fun userActionInteractor(): QSTileUserActionInteractor<T> - fun config(): QSTileConfig - fun dataToStateMapper(): QSTileDataToStateMapper<T> } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfig.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfig.kt index 4a3bcae17fd0..c4d7dfba23bf 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfig.kt @@ -16,20 +16,49 @@ package com.android.systemui.qs.tiles.viewmodel +import android.content.res.Resources +import androidx.annotation.DrawableRes import androidx.annotation.StringRes import com.android.internal.logging.InstanceId -import com.android.systemui.common.shared.model.Icon import com.android.systemui.qs.pipeline.shared.TileSpec data class QSTileConfig( val tileSpec: TileSpec, - val tileIcon: Icon, - @StringRes val tileLabelRes: Int, + val uiConfig: QSTileUIConfig, val instanceId: InstanceId, val metricsSpec: String = tileSpec.spec, val policy: QSTilePolicy = QSTilePolicy.NoRestrictions, ) +/** + * Static tile icon and label to be used when the fully operational tile isn't needed (ex. in edit + * mode). Icon and label are resources to better support config/locale changes. + */ +sealed interface QSTileUIConfig { + + val tileIconRes: Int + @DrawableRes get + val tileLabelRes: Int + @StringRes get + + /** + * Represents the absence of static UI state. This should be avoided by platform tiles in favour + * of [Resource]. Returns [Resources.ID_NULL] for each field. + */ + data object Empty : QSTileUIConfig { + override val tileIconRes: Int + get() = Resources.ID_NULL + override val tileLabelRes: Int + get() = Resources.ID_NULL + } + + /** Config containing actual icon and label resources. */ + data class Resource( + @StringRes override val tileIconRes: Int, + @StringRes override val tileLabelRes: Int, + ) : QSTileUIConfig +} + /** Represents policy restrictions that may be imposed on the tile. */ sealed interface QSTilePolicy { /** Tile has no policy restrictions */ diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigProvider.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigProvider.kt new file mode 100644 index 000000000000..3f3b94e65294 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigProvider.kt @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.viewmodel + +import com.android.internal.util.Preconditions +import com.android.systemui.dagger.SysUISingleton +import javax.inject.Inject + +interface QSTileConfigProvider { + + /** + * Returns a [QSTileConfig] for a [tileSpec] or throws [IllegalArgumentException] if there is no + * config for such [tileSpec]. + */ + fun getConfig(tileSpec: String): QSTileConfig +} + +@SysUISingleton +class QSTileConfigProviderImpl @Inject constructor(private val configs: Map<String, QSTileConfig>) : + QSTileConfigProvider { + + init { + for (entry in configs.entries) { + val configTileSpec = entry.value.tileSpec.spec + val keyTileSpec = entry.key + Preconditions.checkArgument( + configTileSpec == keyTileSpec, + "A wrong config is injected keySpec=$keyTileSpec configSpec=$configTileSpec" + ) + } + } + + override fun getConfig(tileSpec: String): QSTileConfig = + configs[tileSpec] ?: throw IllegalArgumentException("There is no config for spec=$tileSpec") +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt index 28536f5b19a7..efa6da764e6e 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt @@ -189,7 +189,13 @@ constructor( override fun getInstanceId(): InstanceId = qsTileViewModel.config.instanceId override fun getTileLabel(): CharSequence = - context.getString(qsTileViewModel.config.tileLabelRes) + with(qsTileViewModel.config.uiConfig) { + when (this) { + is QSTileUIConfig.Empty -> qsTileViewModel.currentState?.label ?: "" + is QSTileUIConfig.Resource -> context.getString(tileLabelRes) + } + } + override fun getTileSpec(): String = qsTileViewModel.config.tileSpec.spec private companion object { diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 8dc97c0f797c..cc59f8750b56 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -2629,7 +2629,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump if (isPanelExpanded() != isExpanded) { setExpandedOrAwaitingInputTransfer(isExpanded); updateSystemUiStateFlags(); - mShadeExpansionStateManager.onShadeExpansionFullyChanged(isExpanded); if (!isExpanded) { mQsController.closeQsCustomizer(); } diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java index 3c68438ff61b..0ec7a36b5a6d 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java @@ -65,7 +65,6 @@ import com.android.keyguard.FaceAuthApiRequestReason; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.DejankUtils; import com.android.systemui.Dumpable; -import com.android.systemui.res.R; import com.android.systemui.classifier.Classifier; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dump.DumpManager; @@ -77,6 +76,7 @@ import com.android.systemui.media.controls.pipeline.MediaDataManager; import com.android.systemui.media.controls.ui.MediaHierarchyManager; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QS; +import com.android.systemui.res.R; import com.android.systemui.screenrecord.RecordingController; import com.android.systemui.shade.data.repository.ShadeRepository; import com.android.systemui.shade.domain.interactor.ShadeInteractor; @@ -1026,7 +1026,6 @@ public class QuickSettingsController implements Dumpable { && mPanelViewControllerLazy.get().mAnimateBack) { mPanelViewControllerLazy.get().adjustBackAnimationScale(adjustedExpansionFraction); } - mShadeExpansionStateManager.onQsExpansionFractionChanged(qsExpansionFraction); mMediaHierarchyManager.setQsExpansion(qsExpansionFraction); int qsPanelBottomY = calculateBottomPosition(qsExpansionFraction); mScrimController.setQsPosition(qsExpansionFraction, qsPanelBottomY); diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt index 0554c5855d61..949398969d67 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt @@ -36,10 +36,7 @@ import javax.inject.Inject class ShadeExpansionStateManager @Inject constructor() : ShadeStateEvents { private val expansionListeners = CopyOnWriteArrayList<ShadeExpansionListener>() - private val fullExpansionListeners = CopyOnWriteArrayList<ShadeFullExpansionListener>() private val qsExpansionListeners = CopyOnWriteArrayList<ShadeQsExpansionListener>() - private val qsExpansionFractionListeners = - CopyOnWriteArrayList<ShadeQsExpansionFractionListener>() private val stateListeners = CopyOnWriteArrayList<ShadeStateListener>() private val shadeStateEventsListeners = CopyOnWriteArrayList<ShadeStateEventsListener>() @@ -67,15 +64,6 @@ class ShadeExpansionStateManager @Inject constructor() : ShadeStateEvents { expansionListeners.remove(listener) } - fun addFullExpansionListener(listener: ShadeFullExpansionListener) { - fullExpansionListeners.add(listener) - listener.onShadeExpansionFullyChanged(qsExpanded) - } - - fun removeFullExpansionListener(listener: ShadeFullExpansionListener) { - fullExpansionListeners.remove(listener) - } - fun addQsExpansionListener(listener: ShadeQsExpansionListener) { qsExpansionListeners.add(listener) listener.onQsExpansionChanged(qsExpanded) @@ -85,25 +73,11 @@ class ShadeExpansionStateManager @Inject constructor() : ShadeStateEvents { qsExpansionListeners.remove(listener) } - fun addQsExpansionFractionListener(listener: ShadeQsExpansionFractionListener) { - qsExpansionFractionListeners.add(listener) - listener.onQsExpansionFractionChanged(qsExpansionFraction) - } - - fun removeQsExpansionFractionListener(listener: ShadeQsExpansionFractionListener) { - qsExpansionFractionListeners.remove(listener) - } - /** Adds a listener that will be notified when the panel state has changed. */ fun addStateListener(listener: ShadeStateListener) { stateListeners.add(listener) } - /** Removes a state listener. */ - fun removeStateListener(listener: ShadeStateListener) { - stateListeners.remove(listener) - } - override fun addShadeStateEventsListener(listener: ShadeStateEventsListener) { shadeStateEventsListeners.addIfAbsent(listener) } @@ -187,22 +161,6 @@ class ShadeExpansionStateManager @Inject constructor() : ShadeStateEvents { qsExpansionListeners.forEach { it.onQsExpansionChanged(qsExpanded) } } - fun onQsExpansionFractionChanged(qsExpansionFraction: Float) { - this.qsExpansionFraction = qsExpansionFraction - - debugLog("qsExpansionFraction=$qsExpansionFraction") - qsExpansionFractionListeners.forEach { - it.onQsExpansionFractionChanged(qsExpansionFraction) - } - } - - fun onShadeExpansionFullyChanged(isExpanded: Boolean) { - this.expanded = isExpanded - - debugLog("expanded=$isExpanded") - fullExpansionListeners.forEach { it.onShadeExpansionFullyChanged(isExpanded) } - } - /** Updates the panel state if necessary. */ fun updateState(@PanelState state: Int) { debugLog( diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeFullExpansionListener.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeFullExpansionListener.kt deleted file mode 100644 index 6d13e1972255..000000000000 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeFullExpansionListener.kt +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (c) 2022 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.shade - -/** A listener interface to be notified of expansion events for the notification shade. */ -fun interface ShadeFullExpansionListener { - /** Invoked whenever the shade expansion changes, when it is fully collapsed or expanded */ - fun onShadeExpansionFullyChanged(isExpanded: Boolean) -} diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt index 2f684762a13a..f043c717f923 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt @@ -148,12 +148,13 @@ constructor( * * TODO(b/300258424) remove all but the first sentence of this comment */ - val isAnyExpanded: Flow<Boolean> = + val isAnyExpanded: StateFlow<Boolean> = if (sceneContainerFlags.isEnabled()) { - anyExpansion.map { it > 0f }.distinctUntilChanged() - } else { - repository.legacyExpandedOrAwaitingInputTransfer - } + anyExpansion.map { it > 0f }.distinctUntilChanged() + } else { + repository.legacyExpandedOrAwaitingInputTransfer + } + .stateIn(scope, SharingStarted.Eagerly, false) /** * Whether the user is expanding or collapsing the shade with user input. This will be true even @@ -184,7 +185,7 @@ constructor( * but a transition they initiated is still animating. */ val isUserInteracting: Flow<Boolean> = - combine(isUserInteractingWithShade, isUserInteractingWithShade) { shade, qs -> shade || qs } + combine(isUserInteractingWithShade, isUserInteractingWithQs) { shade, qs -> shade || qs } .distinctUntilChanged() /** Are touches allowed on the notification panel? */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java index 7d81e55d336a..c8669072cd7d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java @@ -48,6 +48,13 @@ public class CrossFadeHelper { fadeOut(view, duration, delay, (Runnable) null); } + /** + * Perform a fade-out animation, invoking {@code endRunnable} when the animation ends. It will + * not be invoked if the animation is cancelled. + * + * @deprecated Use {@link #fadeOut(View, long, int, Animator.AnimatorListener)} instead. + */ + @Deprecated public static void fadeOut(final View view, long duration, int delay, @Nullable final Runnable endRunnable) { view.animate().cancel(); @@ -155,6 +162,13 @@ public class CrossFadeHelper { fadeIn(view, duration, delay, /* endRunnable= */ (Runnable) null); } + /** + * Perform a fade-in animation, invoking {@code endRunnable} when the animation ends. It will + * not be invoked if the animation is cancelled. + * + * @deprecated Use {@link #fadeIn(View, long, int, Animator.AnimatorListener)} instead. + */ + @Deprecated public static void fadeIn(final View view, long duration, int delay, @Nullable Runnable endRunnable) { view.animate().cancel(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java index 2e1e395518a0..49743bf85b8c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java @@ -29,14 +29,13 @@ import android.util.Log; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.flags.FeatureFlagsClassic; -import com.android.systemui.flags.Flags; import com.android.systemui.plugins.PluginManager; import com.android.systemui.statusbar.dagger.CentralSurfacesModule; import com.android.systemui.statusbar.domain.interactor.SilentNotificationStatusIconsVisibilityInteractor; import com.android.systemui.statusbar.notification.collection.NotifCollection; import com.android.systemui.statusbar.notification.collection.PipelineDumpable; import com.android.systemui.statusbar.notification.collection.PipelineDumper; +import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor; import com.android.systemui.statusbar.phone.CentralSurfaces; import com.android.systemui.statusbar.phone.NotificationListenerWithPlugins; import com.android.systemui.util.time.SystemClock; @@ -62,7 +61,6 @@ public class NotificationListener extends NotificationListenerWithPlugins implem private static final long MAX_RANKING_DELAY_MILLIS = 500L; private final Context mContext; - private final FeatureFlagsClassic mFeatureFlags; private final NotificationManager mNotificationManager; private final SilentNotificationStatusIconsVisibilityInteractor mStatusIconInteractor; private final SystemClock mSystemClock; @@ -80,7 +78,6 @@ public class NotificationListener extends NotificationListenerWithPlugins implem @Inject public NotificationListener( Context context, - FeatureFlagsClassic featureFlags, NotificationManager notificationManager, SilentNotificationStatusIconsVisibilityInteractor statusIconInteractor, SystemClock systemClock, @@ -88,7 +85,6 @@ public class NotificationListener extends NotificationListenerWithPlugins implem PluginManager pluginManager) { super(pluginManager); mContext = context; - mFeatureFlags = featureFlags; mNotificationManager = notificationManager; mStatusIconInteractor = statusIconInteractor; mSystemClock = systemClock; @@ -106,6 +102,7 @@ public class NotificationListener extends NotificationListenerWithPlugins implem /** Registers a listener that's notified when any notification-related settings change. */ @Deprecated public void addNotificationSettingsListener(NotificationSettingsListener listener) { + NotificationIconContainerRefactor.assertInLegacyMode(); mSettingsListeners.add(listener); } @@ -240,7 +237,7 @@ public class NotificationListener extends NotificationListenerWithPlugins implem @Override public void onSilentStatusBarIconsVisibilityChanged(boolean hideSilentStatusIcons) { - if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR)) { + if (NotificationIconContainerRefactor.isEnabled()) { mStatusIconInteractor.setHideSilentStatusIcons(hideSilentStatusIcons); } else { for (NotificationSettingsListener listener : mSettingsListeners) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java index 61ebcc0c99d1..5f0b2988cd27 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java @@ -48,11 +48,13 @@ import androidx.annotation.Nullable; import com.android.internal.statusbar.IStatusBarService; import com.android.internal.statusbar.NotificationVisibility; +import com.android.systemui.CoreStartable; import com.android.systemui.Dumpable; -import com.android.systemui.dump.DumpManager; +import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.power.domain.interactor.PowerInteractor; import com.android.systemui.res.R; +import com.android.systemui.shade.domain.interactor.ShadeInteractor; import com.android.systemui.statusbar.dagger.CentralSurfacesDependenciesModule; import com.android.systemui.statusbar.notification.NotifPipelineFlags; import com.android.systemui.statusbar.notification.RemoteInputControllerLogger; @@ -65,6 +67,7 @@ import com.android.systemui.statusbar.policy.RemoteInputUriController; import com.android.systemui.statusbar.policy.RemoteInputView; import com.android.systemui.util.DumpUtilsKt; import com.android.systemui.util.ListenerSet; +import com.android.systemui.util.kotlin.JavaAdapter; import java.io.PrintWriter; import java.util.ArrayList; @@ -72,13 +75,16 @@ import java.util.List; import java.util.Objects; import java.util.function.Consumer; +import javax.inject.Inject; + /** * Class for handling remote input state over a set of notifications. This class handles things * like keeping notifications temporarily that were cancelled as a response to a remote input * interaction, keeping track of notifications to remove when NotificationPresenter is collapsed, * and handling clicks on remote views. */ -public class NotificationRemoteInputManager implements Dumpable { +@SysUISingleton +public class NotificationRemoteInputManager implements CoreStartable { public static final boolean ENABLE_REMOTE_INPUT = SystemProperties.getBoolean("debug.enable_remote_input", true); public static boolean FORCE_REMOTE_INPUT_HISTORY = @@ -94,6 +100,8 @@ public class NotificationRemoteInputManager implements Dumpable { private final NotificationVisibilityProvider mVisibilityProvider; private final PowerInteractor mPowerInteractor; private final ActionClickLogger mLogger; + private final JavaAdapter mJavaAdapter; + private final ShadeInteractor mShadeInteractor; protected final Context mContext; protected final NotifPipelineFlags mNotifPipelineFlags; private final UserManager mUserManager; @@ -249,6 +257,7 @@ public class NotificationRemoteInputManager implements Dumpable { /** * Injected constructor. See {@link CentralSurfacesDependenciesModule}. */ + @Inject public NotificationRemoteInputManager( Context context, NotifPipelineFlags notifPipelineFlags, @@ -261,7 +270,8 @@ public class NotificationRemoteInputManager implements Dumpable { RemoteInputControllerLogger remoteInputControllerLogger, NotificationClickNotifier clickNotifier, ActionClickLogger logger, - DumpManager dumpManager) { + JavaAdapter javaAdapter, + ShadeInteractor shadeInteractor) { mContext = context; mNotifPipelineFlags = notifPipelineFlags; mLockscreenUserManager = lockscreenUserManager; @@ -269,6 +279,8 @@ public class NotificationRemoteInputManager implements Dumpable { mVisibilityProvider = visibilityProvider; mPowerInteractor = powerInteractor; mLogger = logger; + mJavaAdapter = javaAdapter; + mShadeInteractor = shadeInteractor; mBarService = IStatusBarService.Stub.asInterface( ServiceManager.getService(Context.STATUS_BAR_SERVICE)); mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE); @@ -277,8 +289,25 @@ public class NotificationRemoteInputManager implements Dumpable { mRemoteInputUriController = remoteInputUriController; mRemoteInputControllerLogger = remoteInputControllerLogger; mClickNotifier = clickNotifier; + } + + @Override + public void start() { + mJavaAdapter.alwaysCollectFlow(mShadeInteractor.isAnyExpanded(), + this::onShadeOrQsExpanded); + } - dumpManager.registerDumpable(this); + private void onShadeOrQsExpanded(boolean expanded) { + if (expanded && mStatusBarStateController.getState() != StatusBarState.KEYGUARD) { + try { + mBarService.clearNotificationEffects(); + } catch (RemoteException e) { + // Won't fail unless the world has ended. + } + } + if (!expanded) { + onPanelCollapsed(); + } } /** Add a listener for various remote input events. Works with NEW pipeline only. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java index 37a4ef168423..537f8a866fed 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java @@ -42,15 +42,16 @@ import com.android.internal.jank.InteractionJankMonitor.Configuration; import com.android.internal.logging.UiEventLogger; import com.android.keyguard.KeyguardClockSwitch; import com.android.systemui.DejankUtils; -import com.android.systemui.Dumpable; -import com.android.systemui.res.R; import com.android.systemui.dagger.SysUISingleton; -import com.android.systemui.dump.DumpManager; import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener; -import com.android.systemui.shade.ShadeExpansionStateManager; +import com.android.systemui.res.R; +import com.android.systemui.shade.domain.interactor.ShadeInteractor; import com.android.systemui.statusbar.notification.stack.StackStateAnimator; import com.android.systemui.statusbar.policy.CallbackController; import com.android.systemui.util.Compile; +import com.android.systemui.util.kotlin.JavaAdapter; + +import dagger.Lazy; import java.io.PrintWriter; import java.util.ArrayList; @@ -64,8 +65,7 @@ import javax.inject.Inject; @SysUISingleton public class StatusBarStateControllerImpl implements SysuiStatusBarStateController, - CallbackController<StateListener>, - Dumpable { + CallbackController<StateListener> { private static final String TAG = "SbStateController"; private static final boolean DEBUG_IMMERSIVE_APPS = SystemProperties.getBoolean("persist.debug.immersive_apps", false); @@ -95,6 +95,8 @@ public class StatusBarStateControllerImpl implements private final ArrayList<RankedListener> mListeners = new ArrayList<>(); private final UiEventLogger mUiEventLogger; private final InteractionJankMonitor mInteractionJankMonitor; + private final JavaAdapter mJavaAdapter; + private final Lazy<ShadeInteractor> mShadeInteractorLazy; private int mState; private int mLastState; private int mUpcomingState; @@ -156,18 +158,22 @@ public class StatusBarStateControllerImpl implements @Inject public StatusBarStateControllerImpl( UiEventLogger uiEventLogger, - DumpManager dumpManager, InteractionJankMonitor interactionJankMonitor, - ShadeExpansionStateManager shadeExpansionStateManager - ) { + JavaAdapter javaAdapter, + Lazy<ShadeInteractor> shadeInteractorLazy) { mUiEventLogger = uiEventLogger; mInteractionJankMonitor = interactionJankMonitor; + mJavaAdapter = javaAdapter; + mShadeInteractorLazy = shadeInteractorLazy; for (int i = 0; i < HISTORY_SIZE; i++) { mHistoricalRecords[i] = new HistoricalState(); } - shadeExpansionStateManager.addFullExpansionListener(this::onShadeExpansionFullyChanged); + } - dumpManager.registerDumpable(this); + @Override + public void start() { + mJavaAdapter.alwaysCollectFlow(mShadeInteractorLazy.get().isAnyExpanded(), + this::onShadeOrQsExpanded); } @Override @@ -345,7 +351,7 @@ public class StatusBarStateControllerImpl implements } } - private void onShadeExpansionFullyChanged(Boolean isExpanded) { + private void onShadeOrQsExpanded(Boolean isExpanded) { if (mIsExpanded != isExpanded) { mIsExpanded = isExpanded; String tag = getClass().getSimpleName() + "#setIsExpanded"; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java b/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java index aa32d5c4dee9..8104755b5e7b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java @@ -21,6 +21,7 @@ import static java.lang.annotation.RetentionPolicy.SOURCE; import android.annotation.IntDef; import android.view.View; +import com.android.systemui.CoreStartable; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.phone.CentralSurfaces; @@ -29,7 +30,7 @@ import java.lang.annotation.Retention; /** * Sends updates to {@link StateListener}s about changes to the status bar state and dozing state */ -public interface SysuiStatusBarStateController extends StatusBarStateController { +public interface SysuiStatusBarStateController extends StatusBarStateController, CoreStartable { // TODO: b/115739177 (remove this explicit ordering if we can) @Retention(SOURCE) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java index 1fe6b83b47b1..a957095536eb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java @@ -23,6 +23,7 @@ import android.util.Log; import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.statusbar.IStatusBarService; +import com.android.systemui.CoreStartable; import com.android.systemui.animation.ActivityLaunchAnimator; import com.android.systemui.animation.AnimationFeatureFlags; import com.android.systemui.animation.DialogLaunchAnimator; @@ -33,24 +34,19 @@ import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; import com.android.systemui.media.controls.pipeline.MediaDataManager; -import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.power.domain.interactor.PowerInteractor; import com.android.systemui.settings.DisplayTracker; import com.android.systemui.shade.NotificationPanelViewController; import com.android.systemui.shade.ShadeSurface; import com.android.systemui.shade.carrier.ShadeCarrierGroupController; -import com.android.systemui.statusbar.ActionClickLogger; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.NotificationClickNotifier; -import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.SmartReplyController; import com.android.systemui.statusbar.StatusBarStateControllerImpl; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.commandline.CommandRegistry; -import com.android.systemui.statusbar.notification.NotifPipelineFlags; -import com.android.systemui.statusbar.notification.RemoteInputControllerLogger; import com.android.systemui.statusbar.notification.collection.NotifCollection; import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider; @@ -63,12 +59,13 @@ import com.android.systemui.statusbar.phone.StatusBarIconList; import com.android.systemui.statusbar.phone.StatusBarNotificationPresenterModule; import com.android.systemui.statusbar.phone.StatusBarRemoteInputCallback; import com.android.systemui.statusbar.policy.KeyguardStateController; -import com.android.systemui.statusbar.policy.RemoteInputUriController; import dagger.Binds; import dagger.Lazy; import dagger.Module; import dagger.Provides; +import dagger.multibindings.ClassKey; +import dagger.multibindings.IntoMap; /** * This module provides instances needed to construct {@link CentralSurfacesImpl}. These are moved to @@ -78,36 +75,12 @@ import dagger.Provides; */ @Module(includes = {StatusBarNotificationPresenterModule.class}) public interface CentralSurfacesDependenciesModule { + /** */ - @SysUISingleton - @Provides - static NotificationRemoteInputManager provideNotificationRemoteInputManager( - Context context, - NotifPipelineFlags notifPipelineFlags, - NotificationLockscreenUserManager lockscreenUserManager, - SmartReplyController smartReplyController, - NotificationVisibilityProvider visibilityProvider, - PowerInteractor powerInteractor, - StatusBarStateController statusBarStateController, - RemoteInputUriController remoteInputUriController, - RemoteInputControllerLogger remoteInputControllerLogger, - NotificationClickNotifier clickNotifier, - ActionClickLogger actionClickLogger, - DumpManager dumpManager) { - return new NotificationRemoteInputManager( - context, - notifPipelineFlags, - lockscreenUserManager, - smartReplyController, - visibilityProvider, - powerInteractor, - statusBarStateController, - remoteInputUriController, - remoteInputControllerLogger, - clickNotifier, - actionClickLogger, - dumpManager); - } + @Binds + @IntoMap + @ClassKey(NotificationRemoteInputManager.class) + CoreStartable bindsStartNotificationRemoteInputManager(NotificationRemoteInputManager nrim); /** */ @SysUISingleton @@ -164,20 +137,23 @@ public interface CentralSurfacesDependenciesModule { return new CommandQueue(context, displayTracker, registry, dumpHandler, powerInteractor); } - /** - */ + /** */ @Binds ManagedProfileController provideManagedProfileController( ManagedProfileControllerImpl controllerImpl); - /** - */ + /** */ @Binds SysuiStatusBarStateController providesSysuiStatusBarStateController( StatusBarStateControllerImpl statusBarStateControllerImpl); - /** - */ + /** */ + @Binds + @IntoMap + @ClassKey(SysuiStatusBarStateController.class) + CoreStartable bindsStartStatusBarStateController(StatusBarStateControllerImpl sbsc); + + /** */ @Binds StatusBarIconController provideStatusBarIconController( StatusBarIconControllerImpl controllerImpl); @@ -212,16 +188,14 @@ public interface CentralSurfacesDependenciesModule { ShadeCarrierGroupController.SlotIndexResolver provideSlotIndexResolver( ShadeCarrierGroupController.SubscriptionManagerSlotIndexResolver impl); - /** - */ + /** */ @Provides @SysUISingleton static ActivityLaunchAnimator provideActivityLaunchAnimator() { return new ActivityLaunchAnimator(); } - /** - */ + /** */ @Provides @SysUISingleton static DialogLaunchAnimator provideDialogLaunchAnimator(IDreamManager dreamManager, @@ -253,8 +227,7 @@ public interface CentralSurfacesDependenciesModule { return new DialogLaunchAnimator(callback, interactionJankMonitor, animationFeatureFlags); } - /** - */ + /** */ @Provides @SysUISingleton static AnimationFeatureFlags provideAnimationFeatureFlags(FeatureFlags featureFlags) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt index 07e84bb37fa0..e0c4bfab153e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt @@ -16,8 +16,6 @@ package com.android.systemui.statusbar.notification.collection.coordinator -import com.android.systemui.flags.FeatureFlagsClassic -import com.android.systemui.flags.Flags import com.android.systemui.statusbar.notification.collection.ListEntry import com.android.systemui.statusbar.notification.collection.NotifPipeline import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope @@ -25,6 +23,7 @@ import com.android.systemui.statusbar.notification.collection.render.GroupExpans import com.android.systemui.statusbar.notification.collection.render.NotifStackController import com.android.systemui.statusbar.notification.collection.render.NotifStats import com.android.systemui.statusbar.notification.domain.interactor.RenderNotificationListInteractor +import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor import com.android.systemui.statusbar.notification.stack.BUCKET_SILENT import com.android.systemui.statusbar.phone.NotificationIconAreaController import com.android.systemui.util.traceSection @@ -38,7 +37,6 @@ import javax.inject.Inject class StackCoordinator @Inject internal constructor( - private val featureFlags: FeatureFlagsClassic, private val groupExpansionManagerImpl: GroupExpansionManagerImpl, private val notificationIconAreaController: NotificationIconAreaController, private val renderListInteractor: RenderNotificationListInteractor, @@ -52,7 +50,7 @@ internal constructor( fun onAfterRenderList(entries: List<ListEntry>, controller: NotifStackController) = traceSection("StackCoordinator.onAfterRenderList") { controller.setNotifStats(calculateNotifStats(entries)) - if (featureFlags.isEnabled(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR)) { + if (NotificationIconContainerRefactor.isEnabled) { renderListInteractor.setRenderedList(entries) } else { notificationIconAreaController.updateNotificationIcons(entries) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImpl.kt index 41b42e32c48d..1992ea8709c4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImpl.kt @@ -22,6 +22,7 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.statusbar.NotificationShelfController import com.android.systemui.statusbar.StatusBarIconView import com.android.systemui.statusbar.notification.collection.ListEntry +import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor import com.android.systemui.statusbar.notification.shelf.ui.viewbinder.NotificationShelfViewBinderWrapperControllerImpl import com.android.systemui.statusbar.phone.NotificationIconAreaController import com.android.systemui.statusbar.phone.NotificationIconContainer @@ -71,7 +72,8 @@ class NotificationIconAreaControllerViewBinderWrapperImpl @Inject constructor() val unsupported: Nothing get() = error( - "Code path not supported when NOTIFICATION_ICON_CONTAINER_REFACTOR is disabled" + "Code path not supported when ${NotificationIconContainerRefactor.FLAG_NAME}" + + " is disabled" ) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt index 75926194af58..c1f728a0b06e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt @@ -17,13 +17,15 @@ package com.android.systemui.statusbar.notification.icon.ui.viewbinder import android.animation.Animator import android.animation.AnimatorListenerAdapter +import android.graphics.Color import android.graphics.Rect import android.view.View +import android.view.ViewGroup import android.view.ViewPropertyAnimator import android.widget.FrameLayout +import androidx.annotation.ColorInt import androidx.collection.ArrayMap -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.repeatOnLifecycle +import androidx.lifecycle.lifecycleScope import com.android.app.animation.Interpolators import com.android.internal.policy.SystemBarUtils import com.android.internal.util.ContrastColorUtil @@ -37,9 +39,12 @@ import com.android.systemui.statusbar.StatusBarIconView import com.android.systemui.statusbar.notification.NotificationUtils import com.android.systemui.statusbar.notification.collection.NotifCollection import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder.IconViewStore -import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerViewModel -import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerViewModel.IconColors -import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerViewModel.IconsViewData +import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconColorLookup +import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconColors +import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerAlwaysOnDisplayViewModel +import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerShelfViewModel +import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerStatusBarViewModel +import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconsViewData import com.android.systemui.statusbar.phone.DozeParameters import com.android.systemui.statusbar.phone.NotificationIconContainer import com.android.systemui.statusbar.phone.ScreenOffAnimationController @@ -49,6 +54,7 @@ import com.android.systemui.util.children import com.android.systemui.util.kotlin.mapValuesNotNullTo import com.android.systemui.util.kotlin.sample import com.android.systemui.util.kotlin.stateFlow +import com.android.systemui.util.ui.AnimatedValue import com.android.systemui.util.ui.isAnimating import com.android.systemui.util.ui.stopAnimating import com.android.systemui.util.ui.value @@ -62,12 +68,53 @@ import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch -/** Binds a [NotificationIconContainer] to its [view model][NotificationIconContainerViewModel]. */ +/** Binds a view-model to a [NotificationIconContainer]. */ object NotificationIconContainerViewBinder { @JvmStatic fun bind( view: NotificationIconContainer, - viewModel: NotificationIconContainerViewModel, + viewModel: NotificationIconContainerShelfViewModel, + configuration: ConfigurationState, + configurationController: ConfigurationController, + viewStore: ShelfNotificationIconViewStore, + ): DisposableHandle { + return view.repeatWhenAttached { + lifecycleScope.launch { + viewModel.icons.bindIcons(view, configuration, configurationController, viewStore) + } + } + } + + @JvmStatic + fun bind( + view: NotificationIconContainer, + viewModel: NotificationIconContainerStatusBarViewModel, + configuration: ConfigurationState, + configurationController: ConfigurationController, + viewStore: StatusBarNotificationIconViewStore, + ): DisposableHandle { + val contrastColorUtil = ContrastColorUtil.getInstance(view.context) + return view.repeatWhenAttached { + lifecycleScope.run { + launch { + viewModel.icons.bindIcons( + view, + configuration, + configurationController, + viewStore + ) + } + launch { viewModel.iconColors.bindIconColors(view, contrastColorUtil) } + launch { viewModel.bindIsolatedIcon(view, viewStore) } + launch { viewModel.animationsEnabled.bindAnimationsEnabled(view) } + } + } + } + + @JvmStatic + fun bind( + view: NotificationIconContainer, + viewModel: NotificationIconContainerAlwaysOnDisplayViewModel, configuration: ConfigurationState, configurationController: ConfigurationController, dozeParameters: DozeParameters, @@ -75,60 +122,72 @@ object NotificationIconContainerViewBinder { screenOffAnimationController: ScreenOffAnimationController, viewStore: IconViewStore, ): DisposableHandle { - val contrastColorUtil = ContrastColorUtil.getInstance(view.context) return view.repeatWhenAttached { - repeatOnLifecycle(Lifecycle.State.CREATED) { - launch { bindAnimationsEnabled(viewModel, view) } - launch { bindIsDozing(viewModel, view, dozeParameters) } + lifecycleScope.launch { + launch { + viewModel.icons.bindIcons( + view, + configuration, + configurationController, + viewStore, + ) + } + launch { viewModel.animationsEnabled.bindAnimationsEnabled(view) } + launch { viewModel.isDozing.bindIsDozing(view, dozeParameters) } // TODO(b/278765923): this should live where AOD is bound, not inside of the NIC // view-binder launch { - bindVisibility( - viewModel, + viewModel.isVisible.bindIsVisible( view, configuration, featureFlags, screenOffAnimationController, ) } - launch { bindIconColors(viewModel, view, contrastColorUtil) } launch { - bindIconViewData( - viewModel, - view, - configuration, - configurationController, - viewStore, - ) + configuration + .getColorAttr(R.attr.wallpaperTextColor, DEFAULT_AOD_ICON_COLOR) + .bindIconColors(view) } - launch { bindIsolatedIcon(viewModel, view, viewStore) } } } } - private suspend fun bindAnimationsEnabled( - viewModel: NotificationIconContainerViewModel, - view: NotificationIconContainer - ) { - viewModel.animationsEnabled.collect(view::setAnimationsEnabled) + /** Binds to [NotificationIconContainer.setAnimationsEnabled] */ + private suspend fun Flow<Boolean>.bindAnimationsEnabled(view: NotificationIconContainer) { + collect(view::setAnimationsEnabled) } - private suspend fun bindIconColors( - viewModel: NotificationIconContainerViewModel, + /** + * Binds to the [StatusBarIconView.setStaticDrawableColor] and [StatusBarIconView.setDecorColor] + * of the [children] of an [NotificationIconContainer]. + */ + private suspend fun Flow<NotificationIconColorLookup>.bindIconColors( view: NotificationIconContainer, contrastColorUtil: ContrastColorUtil, ) { - viewModel.iconColors - .mapNotNull { lookup -> lookup.iconColors(view.viewBounds) } - .collect { iconLookup -> applyTint(view, iconLookup, contrastColorUtil) } + mapNotNull { lookup -> lookup.iconColors(view.viewBounds) } + .collect { iconLookup -> view.applyTint(iconLookup, contrastColorUtil) } } - private suspend fun bindIsDozing( - viewModel: NotificationIconContainerViewModel, + /** + * Binds to the [StatusBarIconView.setStaticDrawableColor] and [StatusBarIconView.setDecorColor] + * of the [children] of an [NotificationIconContainer]. + */ + private suspend fun Flow<Int>.bindIconColors(view: NotificationIconContainer) { + collect { tint -> + view.children.filterIsInstance<StatusBarIconView>().forEach { icon -> + icon.staticDrawableColor = tint + icon.setDecorColor(tint) + } + } + } + + private suspend fun Flow<AnimatedValue<Boolean>>.bindIsDozing( view: NotificationIconContainer, dozeParameters: DozeParameters, ) { - viewModel.isDozing.collect { isDozing -> + collect { isDozing -> if (isDozing.isAnimating) { val animate = !dozeParameters.displayNeedsBlanking view.setDozing( @@ -147,19 +206,18 @@ object NotificationIconContainerViewBinder { } } - private suspend fun bindIsolatedIcon( - viewModel: NotificationIconContainerViewModel, + private suspend fun NotificationIconContainerStatusBarViewModel.bindIsolatedIcon( view: NotificationIconContainer, viewStore: IconViewStore, ) { coroutineScope { launch { - viewModel.isolatedIconLocation.collect { location -> + isolatedIconLocation.collect { location -> view.setIsolatedIconLocation(location, true) } } launch { - viewModel.isolatedIcon.collect { iconInfo -> + isolatedIcon.collect { iconInfo -> val iconView = iconInfo.value?.let { viewStore.iconView(it.notifKey) } if (iconInfo.isAnimating) { view.showIconIsolatedAnimated(iconView, iconInfo::stopAnimating) @@ -171,8 +229,8 @@ object NotificationIconContainerViewBinder { } } - private suspend fun bindIconViewData( - viewModel: NotificationIconContainerViewModel, + /** Binds [NotificationIconsViewData] to a [NotificationIconContainer]'s [children]. */ + private suspend fun Flow<NotificationIconsViewData>.bindIcons( view: NotificationIconContainer, configuration: ConfigurationState, configurationController: ConfigurationController, @@ -205,11 +263,11 @@ object NotificationIconContainerViewBinder { } } - var prevIcons = IconsViewData() - viewModel.iconsViewData.sample(layoutParams, ::Pair).collect { - (iconsData: IconsViewData, layoutParams: FrameLayout.LayoutParams), + var prevIcons = NotificationIconsViewData() + sample(layoutParams, ::Pair).collect { + (iconsData: NotificationIconsViewData, layoutParams: FrameLayout.LayoutParams), -> - val iconsDiff = IconsViewData.computeDifference(iconsData, prevIcons) + val iconsDiff = NotificationIconsViewData.computeDifference(iconsData, prevIcons) prevIcons = iconsData val replacingIcons = @@ -255,30 +313,27 @@ object NotificationIconContainerViewBinder { // TODO(b/305739416): Once StatusBarIconView has its own Recommended Architecture stack, this // can be moved there and cleaned up. - private fun applyTint( - view: NotificationIconContainer, - iconColors: IconColors, + private fun ViewGroup.applyTint( + iconColors: NotificationIconColors, contrastColorUtil: ContrastColorUtil, ) { - view.children + children .filterIsInstance<StatusBarIconView>() .filter { it.width != 0 } - .forEach { iv -> updateTintForIcon(iv, iconColors, contrastColorUtil) } + .forEach { iv -> iv.updateTintForIcon(iconColors, contrastColorUtil) } } - private fun updateTintForIcon( - v: StatusBarIconView, - iconColors: IconColors, + private fun StatusBarIconView.updateTintForIcon( + iconColors: NotificationIconColors, contrastColorUtil: ContrastColorUtil, ) { - val isPreL = java.lang.Boolean.TRUE == v.getTag(R.id.icon_is_pre_L) - val isColorized = !isPreL || NotificationUtils.isGrayscale(v, contrastColorUtil) - v.staticDrawableColor = iconColors.staticDrawableColor(v.viewBounds, isColorized) - v.setDecorColor(iconColors.tint) + val isPreL = java.lang.Boolean.TRUE == getTag(R.id.icon_is_pre_L) + val isColorized = !isPreL || NotificationUtils.isGrayscale(this, contrastColorUtil) + staticDrawableColor = iconColors.staticDrawableColor(viewBounds, isColorized) + setDecorColor(iconColors.tint) } - private suspend fun bindVisibility( - viewModel: NotificationIconContainerViewModel, + private suspend fun Flow<AnimatedValue<Boolean>>.bindIsVisible( view: NotificationIconContainer, configuration: ConfigurationState, featureFlags: FeatureFlagsClassic, @@ -287,7 +342,7 @@ object NotificationIconContainerViewBinder { val iconAppearTranslation = configuration.getDimensionPixelSize(R.dimen.shelf_appear_translation).stateIn(this) val statusViewMigrated = featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW) - viewModel.isVisible.collect { isVisible -> + collect { isVisible -> view.animate().cancel() val animatorListener = object : AnimatorListenerAdapter() { @@ -304,7 +359,7 @@ object NotificationIconContainerViewBinder { view.visibility = if (isVisible.value) View.VISIBLE else View.INVISIBLE } featureFlags.isEnabled(Flags.NEW_AOD_TRANSITION) -> { - animateInIconTranslation(view, statusViewMigrated) + view.animateInIconTranslation(statusViewMigrated) if (isVisible.value) { CrossFadeHelper.fadeIn(view, animatorListener) } else { @@ -313,15 +368,14 @@ object NotificationIconContainerViewBinder { } !isVisible.value -> { // Let's make sure the icon are translated to 0, since we cancelled it above - animateInIconTranslation(view, statusViewMigrated) + view.animateInIconTranslation(statusViewMigrated) CrossFadeHelper.fadeOut(view, animatorListener) } view.visibility != View.VISIBLE -> { // No fading here, let's just appear the icons instead! view.visibility = View.VISIBLE view.alpha = 1f - appearIcons( - view, + view.appearIcons( animate = screenOffAnimationController.shouldAnimateAodIcons(), iconAppearTranslation.value, statusViewMigrated, @@ -330,7 +384,7 @@ object NotificationIconContainerViewBinder { } else -> { // Let's make sure the icons are translated to 0, since we cancelled it above - animateInIconTranslation(view, statusViewMigrated) + view.animateInIconTranslation(statusViewMigrated) // We were fading out, let's fade in instead CrossFadeHelper.fadeIn(view, animatorListener) } @@ -338,8 +392,7 @@ object NotificationIconContainerViewBinder { } } - private fun appearIcons( - view: View, + private fun View.appearIcons( animate: Boolean, iconAppearTranslation: Int, statusViewMigrated: Boolean, @@ -347,11 +400,10 @@ object NotificationIconContainerViewBinder { ) { if (animate) { if (!statusViewMigrated) { - view.translationY = -iconAppearTranslation.toFloat() + translationY = -iconAppearTranslation.toFloat() } - view.alpha = 0f - view - .animate() + alpha = 0f + animate() .alpha(1f) .setInterpolator(Interpolators.LINEAR) .setDuration(AOD_ICONS_APPEAR_DURATION) @@ -359,40 +411,29 @@ object NotificationIconContainerViewBinder { .setListener(animatorListener) .start() } else { - view.alpha = 1.0f + alpha = 1.0f if (!statusViewMigrated) { - view.translationY = 0f + translationY = 0f } } } - private fun animateInIconTranslation(view: View, statusViewMigrated: Boolean) { + private fun View.animateInIconTranslation(statusViewMigrated: Boolean) { if (!statusViewMigrated) { - view.animate().animateInIconTranslation().setDuration(AOD_ICONS_APPEAR_DURATION).start() + animate().animateInIconTranslation().setDuration(AOD_ICONS_APPEAR_DURATION).start() } } private fun ViewPropertyAnimator.animateInIconTranslation(): ViewPropertyAnimator = setInterpolator(Interpolators.DECELERATE_QUINT).translationY(0f) - private const val AOD_ICONS_APPEAR_DURATION: Long = 200 - - private val View.viewBounds: Rect - get() { - val tmpArray = intArrayOf(0, 0) - getLocationOnScreen(tmpArray) - return Rect( - /* left = */ tmpArray[0], - /* top = */ tmpArray[1], - /* right = */ left + width, - /* bottom = */ top + height, - ) - } - /** External storage for [StatusBarIconView] instances. */ fun interface IconViewStore { fun iconView(key: String): StatusBarIconView? } + + private const val AOD_ICONS_APPEAR_DURATION: Long = 200 + @ColorInt private val DEFAULT_AOD_ICON_COLOR = Color.WHITE } /** [IconViewStore] for the [com.android.systemui.statusbar.NotificationShelf] */ @@ -424,3 +465,15 @@ constructor( override fun iconView(key: String): StatusBarIconView? = notifCollection.getEntry(key)?.icons?.statusBarIcon } + +private val View.viewBounds: Rect + get() { + val tmpArray = intArrayOf(0, 0) + getLocationOnScreen(tmpArray) + return Rect( + /* left = */ tmpArray[0], + /* top = */ tmpArray[1], + /* right = */ left + width, + /* bottom = */ top + height, + ) + } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconColors.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconColors.kt new file mode 100644 index 000000000000..97d1e1b5f393 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconColors.kt @@ -0,0 +1,39 @@ +/* + * 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.statusbar.notification.icon.ui.viewmodel + +import android.graphics.Rect + +/** + * Lookup the colors to use for the notification icons based on the bounds of the icon container. A + * result of `null` indicates that no color changes should be applied. + */ +fun interface NotificationIconColorLookup { + fun iconColors(viewBounds: Rect): NotificationIconColors? +} + +/** Colors to apply to notification icons. */ +interface NotificationIconColors { + + /** A tint to apply to the icons. */ + val tint: Int + + /** + * Returns the color to be applied to an icon, based on that icon's view bounds and whether or + * not the notification icon is colorized. + */ + fun staticDrawableColor(viewBounds: Rect, isColorized: Boolean): Int +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModel.kt index 120d342b18d8..611ed89c89af 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModel.kt @@ -15,10 +15,7 @@ */ package com.android.systemui.statusbar.notification.icon.ui.viewmodel -import android.graphics.Color import android.graphics.Rect -import androidx.annotation.ColorInt -import com.android.systemui.common.ui.ConfigurationState import com.android.systemui.dagger.SysUISingleton import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor import com.android.systemui.flags.FeatureFlagsClassic @@ -27,14 +24,9 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionStep -import com.android.systemui.res.R import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.notification.domain.interactor.NotificationsKeyguardInteractor import com.android.systemui.statusbar.notification.icon.domain.interactor.AlwaysOnDisplayNotificationIconsInteractor -import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerViewModel.ColorLookup -import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerViewModel.IconColors -import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerViewModel.IconInfo -import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerViewModel.IconsViewData import com.android.systemui.statusbar.phone.DozeParameters import com.android.systemui.statusbar.phone.ScreenOffAnimationController import com.android.systemui.util.kotlin.pairwise @@ -47,8 +39,6 @@ import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.emptyFlow -import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map /** View-model for the row of notification icons displayed on the always-on display. */ @@ -56,7 +46,6 @@ import kotlinx.coroutines.flow.map class NotificationIconContainerAlwaysOnDisplayViewModel @Inject constructor( - configuration: ConfigurationState, private val deviceEntryInteractor: DeviceEntryInteractor, private val dozeParameters: DozeParameters, private val featureFlags: FeatureFlagsClassic, @@ -66,14 +55,10 @@ constructor( private val notificationsKeyguardInteractor: NotificationsKeyguardInteractor, screenOffAnimationController: ScreenOffAnimationController, shadeInteractor: ShadeInteractor, -) : NotificationIconContainerViewModel { +) { - override val iconColors: Flow<ColorLookup> = - configuration.getColorAttr(R.attr.wallpaperTextColor, DEFAULT_AOD_ICON_COLOR).map { tint -> - ColorLookup { IconColorsImpl(tint) } - } - - override val animationsEnabled: Flow<Boolean> = + /** Are changes to the icon container animated? */ + val animationsEnabled: Flow<Boolean> = combine( shadeInteractor.isShadeTouchable, keyguardInteractor.isKeyguardVisible, @@ -81,7 +66,8 @@ constructor( panelTouchesEnabled && isKeyguardVisible } - override val isDozing: Flow<AnimatedValue<Boolean>> = + /** Should icons be rendered in "dozing" mode? */ + val isDozing: Flow<AnimatedValue<Boolean>> = keyguardTransitionInteractor.startedKeyguardTransitionStep // Determine if we're dozing based on the most recent transition .map { step: TransitionStep -> @@ -98,7 +84,8 @@ constructor( .distinctUntilChanged() .toAnimatedValueFlow() - override val isVisible: Flow<AnimatedValue<Boolean>> = + /** Is the icon container visible? */ + val isVisible: Flow<AnimatedValue<Boolean>> = combine( keyguardTransitionInteractor.finishedKeyguardState.map { it != KeyguardState.GONE }, deviceEntryInteractor.isBypassEnabled, @@ -136,17 +123,14 @@ constructor( } .distinctUntilChanged() - override val iconsViewData: Flow<IconsViewData> = + /** [NotificationIconsViewData] indicating which icons to display in the view. */ + val icons: Flow<NotificationIconsViewData> = iconsInteractor.aodNotifs.map { entries -> - IconsViewData( + NotificationIconsViewData( visibleKeys = entries.mapNotNull { it.toIconInfo(it.aodIcon) }, ) } - override val isolatedIcon: Flow<AnimatedValue<IconInfo?>> = - flowOf(AnimatedValue.NotAnimating(null)) - override val isolatedIconLocation: Flow<Rect> = emptyFlow() - /** Is there an expanded pulse, are we animating in response? */ private fun isPulseExpandingAnimated(): Flow<AnimatedValue<Boolean>> { return notificationsKeyguardInteractor.isPulseExpanding @@ -182,11 +166,7 @@ constructor( .toAnimatedValueFlow() } - private class IconColorsImpl(override val tint: Int) : IconColors { + private class IconColorsImpl(override val tint: Int) : NotificationIconColors { override fun staticDrawableColor(viewBounds: Rect, isColorized: Boolean): Int = tint } - - companion object { - @ColorInt private val DEFAULT_AOD_ICON_COLOR = Color.WHITE - } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerShelfViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerShelfViewModel.kt index c6aabb7527da..1560106bfadb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerShelfViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerShelfViewModel.kt @@ -15,16 +15,9 @@ */ package com.android.systemui.statusbar.notification.icon.ui.viewmodel -import android.graphics.Rect import com.android.systemui.statusbar.notification.icon.domain.interactor.NotificationIconsInteractor -import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerViewModel.ColorLookup -import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerViewModel.IconInfo -import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerViewModel.IconsViewData -import com.android.systemui.util.ui.AnimatedValue import javax.inject.Inject import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.emptyFlow -import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map /** View-model for the overflow row of notification icons displayed in the notification shade. */ @@ -32,19 +25,11 @@ class NotificationIconContainerShelfViewModel @Inject constructor( interactor: NotificationIconsInteractor, -) : NotificationIconContainerViewModel { - - override val animationsEnabled: Flow<Boolean> = flowOf(true) - override val isDozing: Flow<AnimatedValue<Boolean>> = emptyFlow() - override val isVisible: Flow<AnimatedValue<Boolean>> = emptyFlow() - override val iconColors: Flow<ColorLookup> = emptyFlow() - override val isolatedIcon: Flow<AnimatedValue<IconInfo?>> = - flowOf(AnimatedValue.NotAnimating(null)) - override val isolatedIconLocation: Flow<Rect> = emptyFlow() - - override val iconsViewData: Flow<IconsViewData> = +) { + /** [NotificationIconsViewData] indicating which icons to display in the view. */ + val icons: Flow<NotificationIconsViewData> = interactor.filteredNotifSet().map { entries -> - IconsViewData( + NotificationIconsViewData( visibleKeys = entries.mapNotNull { it.toIconInfo(it.shelfIcon) }, ) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt index 4d14024fcd99..53631e330d8c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt @@ -22,10 +22,6 @@ import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationIconInteractor import com.android.systemui.statusbar.notification.icon.domain.interactor.StatusBarNotificationIconsInteractor -import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerViewModel.ColorLookup -import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerViewModel.IconColors -import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerViewModel.IconInfo -import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerViewModel.IconsViewData import com.android.systemui.statusbar.phone.domain.interactor.DarkIconInteractor import com.android.systemui.util.kotlin.pairwise import com.android.systemui.util.kotlin.sample @@ -35,7 +31,6 @@ import com.android.systemui.util.ui.toAnimatedValueFlow import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.map @@ -49,8 +44,10 @@ constructor( keyguardInteractor: KeyguardInteractor, notificationsInteractor: ActiveNotificationsInteractor, shadeInteractor: ShadeInteractor, -) : NotificationIconContainerViewModel { - override val animationsEnabled: Flow<Boolean> = +) { + + /** Are changes to the icon container animated? */ + val animationsEnabled: Flow<Boolean> = combine( shadeInteractor.isShadeTouchable, keyguardInteractor.isKeyguardShowing, @@ -58,14 +55,15 @@ constructor( panelTouchesEnabled && !isKeyguardShowing } - override val iconColors: Flow<ColorLookup> = + /** The colors with which to display the notification icons. */ + val iconColors: Flow<NotificationIconColorLookup> = combine( darkIconInteractor.tintAreas, darkIconInteractor.tintColor, // Included so that tints are re-applied after entries are changed. notificationsInteractor.notifications, ) { areas, tint, _ -> - ColorLookup { viewBounds: Rect -> + NotificationIconColorLookup { viewBounds: Rect -> if (DarkIconDispatcher.isInAreas(areas, viewBounds)) { IconColorsImpl(tint, areas) } else { @@ -74,20 +72,19 @@ constructor( } } - override val isDozing: Flow<AnimatedValue<Boolean>> = emptyFlow() - override val isVisible: Flow<AnimatedValue<Boolean>> = emptyFlow() - - override val iconsViewData: Flow<IconsViewData> = + /** [NotificationIconsViewData] indicating which icons to display in the view. */ + val icons: Flow<NotificationIconsViewData> = iconsInteractor.statusBarNotifs.map { entries -> - IconsViewData( + NotificationIconsViewData( visibleKeys = entries.mapNotNull { it.toIconInfo(it.statusBarIcon) }, ) } - override val isolatedIcon: Flow<AnimatedValue<IconInfo?>> = + /** An Icon to show "isolated" in the IconContainer. */ + val isolatedIcon: Flow<AnimatedValue<NotificationIconInfo?>> = headsUpIconInteractor.isolatedNotification .pairwise(initialValue = null) - .sample(combine(iconsViewData, shadeInteractor.shadeExpansion, ::Pair)) { + .sample(combine(icons, shadeInteractor.shadeExpansion, ::Pair)) { (prev, isolatedNotif), (iconsViewData, shadeExpansion), -> @@ -105,13 +102,14 @@ constructor( } .toAnimatedValueFlow() - override val isolatedIconLocation: Flow<Rect> = + /** Location to show an isolated icon, if there is one. */ + val isolatedIconLocation: Flow<Rect> = headsUpIconInteractor.isolatedIconLocation.filterNotNull() private class IconColorsImpl( override val tint: Int, private val areas: Collection<Rect>, - ) : IconColors { + ) : NotificationIconColors { override fun staticDrawableColor(viewBounds: Rect, isColorized: Boolean): Int { return if (isColorized && DarkIconDispatcher.isInAreas(areas, viewBounds)) { tint diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerViewModel.kt deleted file mode 100644 index a611323201e3..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerViewModel.kt +++ /dev/null @@ -1,196 +0,0 @@ -/* - * 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.statusbar.notification.icon.ui.viewmodel - -import android.graphics.Rect -import android.graphics.drawable.Icon -import androidx.collection.ArrayMap -import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerViewModel.IconInfo -import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel -import com.android.systemui.util.kotlin.mapValuesNotNullTo -import com.android.systemui.util.ui.AnimatedValue -import kotlinx.coroutines.flow.Flow - -/** - * View-model for the row of notification icons displayed in the NotificationShelf, StatusBar, and - * AOD. - */ -interface NotificationIconContainerViewModel { - - /** Are changes to the icon container animated? */ - val animationsEnabled: Flow<Boolean> - - /** Should icons be rendered in "dozing" mode? */ - val isDozing: Flow<AnimatedValue<Boolean>> - - /** Is the icon container visible? */ - val isVisible: Flow<AnimatedValue<Boolean>> - - /** The colors with which to display the notification icons. */ - val iconColors: Flow<ColorLookup> - - /** [IconsViewData] indicating which icons to display in the view. */ - val iconsViewData: Flow<IconsViewData> - - /** An Icon to show "isolated" in the IconContainer. */ - val isolatedIcon: Flow<AnimatedValue<IconInfo?>> - - /** Location to show an isolated icon, if there is one. */ - val isolatedIconLocation: Flow<Rect> - - /** - * Lookup the colors to use for the notification icons based on the bounds of the icon - * container. A result of `null` indicates that no color changes should be applied. - */ - fun interface ColorLookup { - fun iconColors(viewBounds: Rect): IconColors? - } - - /** Colors to apply to notification icons. */ - interface IconColors { - - /** A tint to apply to the icons. */ - val tint: Int - - /** - * Returns the color to be applied to an icon, based on that icon's view bounds and whether - * or not the notification icon is colorized. - */ - fun staticDrawableColor(viewBounds: Rect, isColorized: Boolean): Int - } - - /** Encapsulates the collection of notification icons present on the device. */ - data class IconsViewData( - /** Icons that are visible in the container. */ - val visibleKeys: List<IconInfo> = emptyList(), - /** Keys of icons that are "behind" the overflow dot. */ - val collapsedKeys: Set<String> = emptySet(), - /** Whether the overflow dot should be shown regardless if [collapsedKeys] is empty. */ - val forceShowDot: Boolean = false, - ) { - /** The difference between two [IconsViewData]s. */ - data class Diff( - /** Icons added in the newer dataset. */ - val added: List<IconInfo> = emptyList(), - /** Icons removed from the older dataset. */ - val removed: List<String> = emptyList(), - /** - * Groups whose icon was replaced with a single new notification icon. The key of the - * [Map] is the notification group key, and the value is the new icon. - * - * Specifically, this models a difference where the older dataset had notification - * groups with a single icon in the set, and the newer dataset has a single, different - * icon for the same group. A view binder can use this information for special - * animations for this specific change. - */ - val groupReplacements: Map<String, IconInfo> = emptyMap(), - ) - - companion object { - /** - * Returns an [IconsViewData.Diff] calculated from a [new] and [previous][prev] - * [IconsViewData] state. - */ - fun computeDifference(new: IconsViewData, prev: IconsViewData): Diff { - val added: List<IconInfo> = - new.visibleKeys.filter { - it.notifKey !in prev.visibleKeys.asSequence().map { it.notifKey } - } - val removed: List<IconInfo> = - prev.visibleKeys.filter { - it.notifKey !in new.visibleKeys.asSequence().map { it.notifKey } - } - val groupsToShow: Set<IconGroupInfo> = - new.visibleKeys.asSequence().map { it.groupInfo }.toSet() - val replacements: ArrayMap<String, IconInfo> = - removed - .asSequence() - .filter { keyToRemove -> keyToRemove.groupInfo in groupsToShow } - .groupBy { it.groupInfo.groupKey } - .mapValuesNotNullTo(ArrayMap()) { (_, vs) -> - vs.takeIf { it.size == 1 }?.get(0) - } - return Diff(added, removed.map { it.notifKey }, replacements) - } - } - } - - /** An Icon, and keys for unique identification. */ - data class IconInfo( - val sourceIcon: Icon, - val notifKey: String, - val groupKey: String, - ) -} - -/** - * Construct an [IconInfo] out of an [ActiveNotificationModel], or return `null` if one cannot be - * created due to missing information. - */ -fun ActiveNotificationModel.toIconInfo(sourceIcon: Icon?): IconInfo? { - return sourceIcon?.let { - groupKey?.let { groupKey -> - IconInfo( - sourceIcon = sourceIcon, - notifKey = key, - groupKey = groupKey, - ) - } - } -} - -private val IconInfo.groupInfo: IconGroupInfo - get() = IconGroupInfo(sourceIcon, groupKey) - -private data class IconGroupInfo( - val sourceIcon: Icon, - val groupKey: String, -) { - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as IconGroupInfo - - if (groupKey != other.groupKey) return false - return sourceIcon.sameAs(other.sourceIcon) - } - - override fun hashCode(): Int { - var result = groupKey.hashCode() - result = 31 * result + sourceIcon.type.hashCode() - when (sourceIcon.type) { - Icon.TYPE_BITMAP, - Icon.TYPE_ADAPTIVE_BITMAP -> { - result = 31 * result + sourceIcon.bitmap.hashCode() - } - Icon.TYPE_DATA -> { - result = 31 * result + sourceIcon.dataLength.hashCode() - result = 31 * result + sourceIcon.dataOffset.hashCode() - } - Icon.TYPE_RESOURCE -> { - result = 31 * result + sourceIcon.resId.hashCode() - result = 31 * result + sourceIcon.resPackage.hashCode() - } - Icon.TYPE_URI, - Icon.TYPE_URI_ADAPTIVE_BITMAP -> { - result = 31 * result + sourceIcon.uriString.hashCode() - } - } - return result - } -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconsViewData.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconsViewData.kt new file mode 100644 index 000000000000..867be84f3a12 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconsViewData.kt @@ -0,0 +1,146 @@ +/* + * 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.statusbar.notification.icon.ui.viewmodel + +import android.graphics.drawable.Icon +import androidx.collection.ArrayMap +import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel +import com.android.systemui.util.kotlin.mapValuesNotNullTo + +/** Encapsulates the collection of notification icons present on the device. */ +data class NotificationIconsViewData( + /** Icons that are visible in the container. */ + val visibleKeys: List<NotificationIconInfo> = emptyList(), + /** Keys of icons that are "behind" the overflow dot. */ + val collapsedKeys: Set<String> = emptySet(), + /** Whether the overflow dot should be shown regardless if [collapsedKeys] is empty. */ + val forceShowDot: Boolean = false, +) { + /** The difference between two [NotificationIconsViewData]s. */ + data class Diff( + /** Icons added in the newer dataset. */ + val added: List<NotificationIconInfo> = emptyList(), + /** Icons removed from the older dataset. */ + val removed: List<String> = emptyList(), + /** + * Groups whose icon was replaced with a single new notification icon. The key of the [Map] + * is the notification group key, and the value is the new icon. + * + * Specifically, this models a difference where the older dataset had notification groups + * with a single icon in the set, and the newer dataset has a single, different icon for the + * same group. A view binder can use this information for special animations for this + * specific change. + */ + val groupReplacements: Map<String, NotificationIconInfo> = emptyMap(), + ) + + companion object { + /** + * Returns an [NotificationIconsViewData.Diff] calculated from a [new] and [previous][prev] + * [NotificationIconsViewData] state. + */ + fun computeDifference( + new: NotificationIconsViewData, + prev: NotificationIconsViewData + ): Diff { + val added: List<NotificationIconInfo> = + new.visibleKeys.filter { + it.notifKey !in prev.visibleKeys.asSequence().map { it.notifKey } + } + val removed: List<NotificationIconInfo> = + prev.visibleKeys.filter { + it.notifKey !in new.visibleKeys.asSequence().map { it.notifKey } + } + val groupsToShow: Set<IconGroupInfo> = + new.visibleKeys.asSequence().map { it.groupInfo }.toSet() + val replacements: ArrayMap<String, NotificationIconInfo> = + removed + .asSequence() + .filter { keyToRemove -> keyToRemove.groupInfo in groupsToShow } + .groupBy { it.groupInfo.groupKey } + .mapValuesNotNullTo(ArrayMap()) { (_, vs) -> + vs.takeIf { it.size == 1 }?.get(0) + } + return Diff(added, removed.map { it.notifKey }, replacements) + } + } +} + +/** An Icon, and keys for unique identification. */ +data class NotificationIconInfo( + val sourceIcon: Icon, + val notifKey: String, + val groupKey: String, +) + +/** + * Construct an [NotificationIconInfo] out of an [ActiveNotificationModel], or return `null` if one + * cannot be created due to missing information. + */ +fun ActiveNotificationModel.toIconInfo(sourceIcon: Icon?): NotificationIconInfo? { + return sourceIcon?.let { + groupKey?.let { groupKey -> + NotificationIconInfo( + sourceIcon = sourceIcon, + notifKey = key, + groupKey = groupKey, + ) + } + } +} + +private val NotificationIconInfo.groupInfo: IconGroupInfo + get() = IconGroupInfo(sourceIcon, groupKey) + +private data class IconGroupInfo( + val sourceIcon: Icon, + val groupKey: String, +) { + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as IconGroupInfo + + if (groupKey != other.groupKey) return false + return sourceIcon.sameAs(other.sourceIcon) + } + + override fun hashCode(): Int { + var result = groupKey.hashCode() + result = 31 * result + sourceIcon.type.hashCode() + when (sourceIcon.type) { + Icon.TYPE_BITMAP, + Icon.TYPE_ADAPTIVE_BITMAP -> { + result = 31 * result + sourceIcon.bitmap.hashCode() + } + Icon.TYPE_DATA -> { + result = 31 * result + sourceIcon.dataLength.hashCode() + result = 31 * result + sourceIcon.dataOffset.hashCode() + } + Icon.TYPE_RESOURCE -> { + result = 31 * result + sourceIcon.resId.hashCode() + result = 31 * result + sourceIcon.resPackage.hashCode() + } + Icon.TYPE_URI, + Icon.TYPE_URI_ADAPTIVE_BITMAP -> { + result = 31 * result + sourceIcon.uriString.hashCode() + } + } + return result + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt index 4a823a40a272..2fffd379e6f9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt @@ -239,11 +239,11 @@ class NotificationInterruptLogger @Inject constructor( }) } - fun logNoPulsingNotificationHidden(entry: NotificationEntry) { + fun logNoPulsingNotificationHiddenOverride(entry: NotificationEntry) { buffer.log(TAG, DEBUG, { str1 = entry.logKey }, { - "No pulsing: notification hidden on lock screen: $str1" + "No pulsing: notification hidden on lock screen by override: $str1" }) } @@ -290,11 +290,11 @@ class NotificationInterruptLogger @Inject constructor( }) } - fun keyguardHideNotification(entry: NotificationEntry) { + fun logNoAlertingNotificationHidden(entry: NotificationEntry) { buffer.log(TAG, DEBUG, { str1 = entry.logKey }, { - "Keyguard Hide Notification: $str1" + "No alerting: notification hidden on lock screen: $str1" }) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java index 6ec9dbe003a2..b0155f13dbec 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java @@ -180,4 +180,9 @@ public interface NotificationInterruptStateProvider { * Add a component that can suppress visual interruptions. */ void addSuppressor(NotificationInterruptSuppressor suppressor); + + /** + * Remove a component that can suppress visual interruptions. + */ + void removeSuppressor(NotificationInterruptSuppressor suppressor); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java index 3819843aa7b2..301ddbf42ff2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java @@ -175,6 +175,11 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter } @Override + public void removeSuppressor(NotificationInterruptSuppressor suppressor) { + mSuppressors.remove(suppressor); + } + + @Override public boolean shouldBubbleUp(NotificationEntry entry) { final StatusBarNotification sbn = entry.getSbn(); @@ -505,7 +510,7 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter if (entry.getRanking().getLockscreenVisibilityOverride() == Notification.VISIBILITY_PRIVATE) { - if (log) mLogger.logNoPulsingNotificationHidden(entry); + if (log) mLogger.logNoPulsingNotificationHiddenOverride(entry); return false; } @@ -536,7 +541,7 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter } if (mKeyguardNotificationVisibilityProvider.shouldHideNotification(entry)) { - if (log) mLogger.keyguardHideNotification(entry); + if (log) mLogger.logNoAlertingNotificationHidden(entry); return false; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapper.kt index ebdeded6e329..d7f0baf4f002 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapper.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapper.kt @@ -58,6 +58,10 @@ class NotificationInterruptStateProviderWrapper( wrapped.addSuppressor(suppressor) } + override fun removeLegacySuppressor(suppressor: NotificationInterruptSuppressor) { + wrapped.removeSuppressor(suppressor) + } + override fun makeUnloggedHeadsUpDecision(entry: NotificationEntry): Decision = wrapped.checkHeadsUp(entry, /* log= */ false).let { DecisionImpl.of(it) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProvider.kt index 454ba02b2d73..920bbe96b33b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProvider.kt @@ -60,6 +60,13 @@ interface VisualInterruptionDecisionProvider { fun addLegacySuppressor(suppressor: NotificationInterruptSuppressor) /** + * Removes a [component][suppressor] that can suppress visual interruptions. + * + * @param[suppressor] the suppressor to remove + */ + fun removeLegacySuppressor(suppressor: NotificationInterruptSuppressor) + + /** * Decides whether a [notification][entry] should display as heads-up or not, but does not log * that decision. * diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionSuppressor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionSuppressor.kt new file mode 100644 index 000000000000..4ef80e38bc63 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionSuppressor.kt @@ -0,0 +1,76 @@ +/* + * 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.statusbar.notification.interruption + +import com.android.internal.logging.UiEventLogger.UiEventEnum +import com.android.systemui.statusbar.notification.collection.NotificationEntry + +/** + * A reason why visual interruptions might be suppressed. + * + * @see VisualInterruptionCondition + * @see VisualInterruptionFilter + */ +enum class VisualInterruptionType { + /* HUN when awake */ + PEEK, + + /* HUN when dozing */ + PULSE, + + /* Bubble */ + BUBBLE +} + +/** + * A reason why visual interruptions might be suppressed. + * + * @see VisualInterruptionCondition + * @see VisualInterruptionFilter + */ +sealed interface VisualInterruptionSuppressor { + /** The type(s) of interruption that this suppresses. */ + val types: Set<VisualInterruptionType> + + /** A human-readable string to be logged to explain why this suppressed an interruption. */ + val reason: String + + /** An optional UiEvent ID to be recorded when this suppresses an interruption. */ + val uiEventId: UiEventEnum? +} + +/** A reason why visual interruptions might be suppressed regardless of the notification. */ +abstract class VisualInterruptionCondition( + override val types: Set<VisualInterruptionType>, + override val reason: String, + override val uiEventId: UiEventEnum? = null +) : VisualInterruptionSuppressor { + /** @return true if these interruptions should be suppressed right now. */ + abstract fun shouldSuppress(): Boolean +} + +/** A reason why visual interruptions might be suppressed based on the notification. */ +abstract class VisualInterruptionFilter( + override val types: Set<VisualInterruptionType>, + override val reason: String, + override val uiEventId: UiEventEnum? = null +) : VisualInterruptionSuppressor { + /** + * @param entry the notification to consider suppressing + * @return true if these interruptions should be suppressed for this notification right now + */ + abstract fun shouldSuppress(entry: NotificationEntry): Boolean +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt index 2a7d0876912f..d35e4b56775f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt @@ -21,8 +21,6 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle import com.android.systemui.common.ui.ConfigurationState import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.flags.FeatureFlagsClassic -import com.android.systemui.flags.Flags import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.plugins.FalsingManager import com.android.systemui.statusbar.LegacyNotificationShelfControllerImpl @@ -31,13 +29,12 @@ import com.android.systemui.statusbar.NotificationShelfController import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder import com.android.systemui.statusbar.notification.icon.ui.viewbinder.ShelfNotificationIconViewStore import com.android.systemui.statusbar.notification.row.ui.viewbinder.ActivatableNotificationViewBinder +import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor import com.android.systemui.statusbar.notification.shelf.ui.viewmodel.NotificationShelfViewModel import com.android.systemui.statusbar.notification.stack.AmbientState import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController -import com.android.systemui.statusbar.phone.DozeParameters import com.android.systemui.statusbar.phone.NotificationIconAreaController import com.android.systemui.statusbar.phone.NotificationIconContainer -import com.android.systemui.statusbar.phone.ScreenOffAnimationController import com.android.systemui.statusbar.policy.ConfigurationController import javax.inject.Inject import kotlinx.coroutines.awaitCancellation @@ -84,24 +81,18 @@ object NotificationShelfViewBinder { viewModel: NotificationShelfViewModel, configuration: ConfigurationState, configurationController: ConfigurationController, - dozeParameters: DozeParameters, falsingManager: FalsingManager, - featureFlags: FeatureFlagsClassic, notificationIconAreaController: NotificationIconAreaController, - screenOffAnimationController: ScreenOffAnimationController, shelfIconViewStore: ShelfNotificationIconViewStore, ) { ActivatableNotificationViewBinder.bind(viewModel, shelf, falsingManager) shelf.apply { - if (featureFlags.isEnabled(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR)) { + if (NotificationIconContainerRefactor.isEnabled) { NotificationIconContainerViewBinder.bind( shelfIcons, viewModel.icons, configuration, configurationController, - dozeParameters, - featureFlags, - screenOffAnimationController, shelfIconViewStore, ) } else { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java index b770b83cae9f..21efd639825e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java @@ -217,8 +217,6 @@ public class NotificationStackScrollLayoutController { private final NotificationDismissibilityProvider mDismissibilityProvider; private final ActivityStarter mActivityStarter; private final ConfigurationState mConfigurationState; - private final DozeParameters mDozeParameters; - private final ScreenOffAnimationController mScreenOffAnimationController; private final ShelfNotificationIconViewStore mShelfIconViewStore; private View mLongPressedView; @@ -683,8 +681,7 @@ public class NotificationStackScrollLayoutController { NotificationDismissibilityProvider dismissibilityProvider, ActivityStarter activityStarter, SplitShadeStateController splitShadeStateController, - ConfigurationState configurationState, DozeParameters dozeParameters, - ScreenOffAnimationController screenOffAnimationController, + ConfigurationState configurationState, ShelfNotificationIconViewStore shelfIconViewStore) { mView = view; mKeyguardTransitionRepo = keyguardTransitionRepo; @@ -736,8 +733,6 @@ public class NotificationStackScrollLayoutController { mDismissibilityProvider = dismissibilityProvider; mActivityStarter = activityStarter; mConfigurationState = configurationState; - mDozeParameters = dozeParameters; - mScreenOffAnimationController = screenOffAnimationController; mShelfIconViewStore = shelfIconViewStore; mView.passSplitShadeStateController(splitShadeStateController); updateResources(); @@ -848,8 +843,8 @@ public class NotificationStackScrollLayoutController { mViewModel.ifPresent( vm -> NotificationListViewBinder .bind(mView, vm, mConfigurationState, mConfigurationController, - mDozeParameters, mFalsingManager, mFeatureFlags, - mNotifIconAreaController, mScreenOffAnimationController, + mFalsingManager, + mNotifIconAreaController, mShelfIconViewStore)); collectFlow(mView, mKeyguardTransitionRepo.getTransitions(), diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt index 69b96fa95dbe..95b467fdad40 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt @@ -19,7 +19,6 @@ package com.android.systemui.statusbar.notification.stack.ui.viewbinder import android.view.LayoutInflater import com.android.systemui.common.ui.ConfigurationState import com.android.systemui.common.ui.reinflateAndBindLatest -import com.android.systemui.flags.FeatureFlagsClassic import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.plugins.FalsingManager import com.android.systemui.res.R @@ -30,9 +29,7 @@ import com.android.systemui.statusbar.notification.icon.ui.viewbinder.ShelfNotif import com.android.systemui.statusbar.notification.shelf.ui.viewbinder.NotificationShelfViewBinder import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationListViewModel -import com.android.systemui.statusbar.phone.DozeParameters import com.android.systemui.statusbar.phone.NotificationIconAreaController -import com.android.systemui.statusbar.phone.ScreenOffAnimationController import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.util.traceSection @@ -44,11 +41,8 @@ object NotificationListViewBinder { viewModel: NotificationListViewModel, configuration: ConfigurationState, configurationController: ConfigurationController, - dozeParameters: DozeParameters, falsingManager: FalsingManager, - featureFlags: FeatureFlagsClassic, iconAreaController: NotificationIconAreaController, - screenOffAnimationController: ScreenOffAnimationController, shelfIconViewStore: ShelfNotificationIconViewStore, ) { val shelf = @@ -59,11 +53,8 @@ object NotificationListViewBinder { viewModel.shelf, configuration, configurationController, - dozeParameters, falsingManager, - featureFlags, iconAreaController, - screenOffAnimationController, shelfIconViewStore, ) view.setShelf(shelf) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java index 8295f65f2ece..897bb42e7f94 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -206,6 +206,7 @@ import com.android.systemui.statusbar.notification.init.NotificationsController; import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; +import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; @@ -236,8 +237,6 @@ import com.android.wm.shell.startingsurface.StartingSurface; import dalvik.annotation.optimization.NeverCompile; -import dagger.Lazy; - import java.io.PrintWriter; import java.io.StringWriter; import java.util.List; @@ -249,6 +248,8 @@ import javax.inject.Inject; import javax.inject.Named; import javax.inject.Provider; +import dagger.Lazy; + /** * A class handling initialization and coordination between some of the key central surfaces in * System UI: The notification shade, the keyguard (lockscreen), and the status bar. @@ -809,8 +810,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mShadeExpansionStateManager.addExpansionListener(shadeExpansionListener); shadeExpansionListener.onPanelExpansionChanged(currentState); - mShadeExpansionStateManager.addFullExpansionListener(this::onShadeExpansionFullyChanged); - mActivityIntentHelper = new ActivityIntentHelper(mContext); mActivityLaunchAnimator = activityLaunchAnimator; @@ -1397,20 +1396,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { } } - @VisibleForTesting - void onShadeExpansionFullyChanged(Boolean isExpanded) { - if (isExpanded && mStatusBarStateController.getState() != StatusBarState.KEYGUARD) { - if (DEBUG) { - Log.v(TAG, "clearing notification effects from Height"); - } - clearNotificationEffects(); - } - - if (!isExpanded) { - mRemoteInputManager.onPanelCollapsed(); - } - } - @NonNull @Override public Lifecycle getLifecycle() { @@ -2635,7 +2620,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { !mDozeServiceHost.isPulsing(), mDeviceProvisionedController.isFrpActive()); mShadeSurface.setTouchAndAnimationDisabled(disabled); - if (!mFeatureFlags.isEnabled(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR)) { + if (!NotificationIconContainerRefactor.isEnabled()) { mNotificationIconAreaController.setAnimationsEnabled(!disabled); } } @@ -3102,7 +3087,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { } // TODO: Bring these out of CentralSurfaces. mUserInfoControllerImpl.onDensityOrFontScaleChanged(); - if (!mFeatureFlags.isEnabled(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR)) { + if (!NotificationIconContainerRefactor.isEnabled()) { mNotificationIconAreaController.onDensityOrFontScaleChanged(mContext); } } @@ -3122,7 +3107,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { if (mAmbientIndicationContainer instanceof AutoReinflateContainer) { ((AutoReinflateContainer) mAmbientIndicationContainer).inflateLayout(); } - if (!mFeatureFlags.isEnabled(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR)) { + if (!NotificationIconContainerRefactor.isEnabled()) { mNotificationIconAreaController.onThemeChanged(); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java index 66341ba42a64..600d4afde935 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java @@ -38,7 +38,6 @@ import com.android.systemui.doze.DozeHost; import com.android.systemui.doze.DozeLog; import com.android.systemui.doze.DozeReceiver; import com.android.systemui.flags.FeatureFlagsClassic; -import com.android.systemui.flags.Flags; import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.keyguard.domain.interactor.DozeInteractor; import com.android.systemui.shade.NotificationShadeWindowViewController; @@ -49,18 +48,18 @@ import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor; import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; import com.android.systemui.util.Assert; -import dagger.Lazy; - import java.util.ArrayList; import javax.inject.Inject; +import dagger.Lazy; import kotlinx.coroutines.ExperimentalCoroutinesApi; /** @@ -178,7 +177,7 @@ public final class DozeServiceHost implements DozeHost { void fireNotificationPulse(NotificationEntry entry) { Runnable pulseSuppressedListener = () -> { - if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR)) { + if (NotificationIconContainerRefactor.isEnabled()) { mHeadsUpManager.removeNotification( entry.getKey(), /* releaseImmediately= */ true, /* animate= */ false); } else { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java index 8fee5c0487f3..beeee1be401d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java @@ -27,7 +27,6 @@ import androidx.annotation.NonNull; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.widget.ViewClippingUtil; import com.android.systemui.flags.FeatureFlagsClassic; -import com.android.systemui.flags.Flags; import com.android.systemui.plugins.DarkIconDispatcher; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.res.R; @@ -42,6 +41,7 @@ import com.android.systemui.statusbar.notification.SourceType; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationIconInteractor; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; +import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor; import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentScope; @@ -179,7 +179,7 @@ public class HeadsUpAppearanceController extends ViewController<HeadsUpStatusBar mHeadsUpManager.addListener(this); mView.setOnDrawingRectChangedListener( () -> updateIsolatedIconLocation(true /* requireUpdate */)); - if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR)) { + if (NotificationIconContainerRefactor.isEnabled()) { updateIsolatedIconLocation(true); } mWakeUpCoordinator.addListener(this); @@ -197,7 +197,7 @@ public class HeadsUpAppearanceController extends ViewController<HeadsUpStatusBar protected void onViewDetached() { mHeadsUpManager.removeListener(this); mView.setOnDrawingRectChangedListener(null); - if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR)) { + if (NotificationIconContainerRefactor.isEnabled()) { mHeadsUpNotificationIconInteractor.setIsolatedIconLocation(null); } mWakeUpCoordinator.removeListener(this); @@ -208,7 +208,7 @@ public class HeadsUpAppearanceController extends ViewController<HeadsUpStatusBar } private void updateIsolatedIconLocation(boolean requireStateUpdate) { - if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR)) { + if (NotificationIconContainerRefactor.isEnabled()) { mHeadsUpNotificationIconInteractor .setIsolatedIconLocation(mView.getIconDrawingRect()); } else { @@ -250,7 +250,7 @@ public class HeadsUpAppearanceController extends ViewController<HeadsUpStatusBar setShown(true); animateIsolation = !isExpanded(); } - if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR)) { + if (NotificationIconContainerRefactor.isEnabled()) { mHeadsUpNotificationIconInteractor.setIsolatedIconNotificationKey( newEntry == null ? null : newEntry.getKey()); } else { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java index f4862c73606f..3a95e6d053e8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java @@ -34,7 +34,7 @@ import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener; import com.android.systemui.res.R; -import com.android.systemui.shade.ShadeExpansionStateManager; +import com.android.systemui.shade.domain.interactor.ShadeInteractor; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.provider.OnReorderingAllowedListener; @@ -48,6 +48,7 @@ import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.HeadsUpManagerLogger; import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; import com.android.systemui.statusbar.policy.OnHeadsUpPhoneListenerChange; +import com.android.systemui.util.kotlin.JavaAdapter; import java.io.PrintWriter; import java.util.ArrayList; @@ -105,7 +106,8 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements OnHeadsUp /////////////////////////////////////////////////////////////////////////////////////////////// // Constructor: @Inject - public HeadsUpManagerPhone(@NonNull final Context context, + public HeadsUpManagerPhone( + @NonNull final Context context, HeadsUpManagerLogger logger, StatusBarStateController statusBarStateController, KeyguardBypassController bypassController, @@ -115,7 +117,8 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements OnHeadsUp @Main Handler handler, AccessibilityManagerWrapper accessibilityManagerWrapper, UiEventLogger uiEventLogger, - ShadeExpansionStateManager shadeExpansionStateManager) { + JavaAdapter javaAdapter, + ShadeInteractor shadeInteractor) { super(context, logger, handler, accessibilityManagerWrapper, uiEventLogger); Resources resources = mContext.getResources(); mExtensionTime = resources.getInteger(R.integer.ambient_notification_extension_time); @@ -136,8 +139,7 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements OnHeadsUp updateResources(); } }); - - shadeExpansionStateManager.addFullExpansionListener(this::onShadeExpansionFullyChanged); + javaAdapter.alwaysCollectFlow(shadeInteractor.isAnyExpanded(), this::onShadeOrQsExpanded); } public void setAnimationStateHandler(AnimationStateHandler handler) { @@ -230,7 +232,7 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements OnHeadsUp mTrackingHeadsUp = trackingHeadsUp; } - private void onShadeExpansionFullyChanged(Boolean isExpanded) { + private void onShadeOrQsExpanded(Boolean isExpanded) { if (isExpanded != mIsExpanded) { mIsExpanded = isExpanded; if (isExpanded) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaControllerModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaControllerModule.kt index d1ddd51a04c7..ba693708f401 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaControllerModule.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaControllerModule.kt @@ -15,9 +15,8 @@ */ package com.android.systemui.statusbar.phone -import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconAreaControllerViewBinderWrapperImpl +import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor import dagger.Module import dagger.Provides import javax.inject.Provider @@ -26,11 +25,10 @@ import javax.inject.Provider object NotificationIconAreaControllerModule { @Provides fun provideNotificationIconAreaControllerImpl( - featureFlags: FeatureFlags, legacyProvider: Provider<LegacyNotificationIconAreaControllerImpl>, newProvider: Provider<NotificationIconAreaControllerViewBinderWrapperImpl>, ): NotificationIconAreaController = - if (featureFlags.isEnabled(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR)) { + if (NotificationIconContainerRefactor.isEnabled) { newProvider.get() } else { legacyProvider.get() diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java index 535f6acab5be..efb8e2cfce19 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java @@ -40,10 +40,9 @@ import androidx.collection.ArrayMap; import com.android.app.animation.Interpolators; import com.android.internal.statusbar.StatusBarIcon; import com.android.settingslib.Utils; -import com.android.systemui.flags.Flags; -import com.android.systemui.flags.RefactorFlag; import com.android.systemui.res.R; import com.android.systemui.statusbar.StatusBarIconView; +import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor; import com.android.systemui.statusbar.notification.stack.AnimationFilter; import com.android.systemui.statusbar.notification.stack.AnimationProperties; import com.android.systemui.statusbar.notification.stack.ViewState; @@ -133,9 +132,6 @@ public class NotificationIconContainer extends ViewGroup { } }.setDuration(CONTENT_FADE_DURATION); - private final RefactorFlag mIconContainerRefactorFlag = - RefactorFlag.forView(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR); - /* Maximum number of icons on AOD when also showing overflow dot. */ private int mMaxIconsOnAod; @@ -352,7 +348,7 @@ public class NotificationIconContainer extends ViewGroup { StatusBarIconView iconView = (StatusBarIconView) child; Icon sourceIcon = iconView.getSourceIcon(); String groupKey = iconView.getNotification().getGroupKey(); - if (mIconContainerRefactorFlag.isEnabled()) { + if (NotificationIconContainerRefactor.isEnabled()) { if (mReplacingIcons == null) { return false; } @@ -695,18 +691,18 @@ public class NotificationIconContainer extends ViewGroup { } public void setReplacingIconsLegacy(ArrayMap<String, ArrayList<StatusBarIcon>> replacingIcons) { - mIconContainerRefactorFlag.assertInLegacyMode(); + NotificationIconContainerRefactor.assertInLegacyMode(); mReplacingIconsLegacy = replacingIcons; } public void setReplacingIcons(ArrayMap<String, StatusBarIcon> replacingIcons) { - if (mIconContainerRefactorFlag.isUnexpectedlyInLegacyMode()) return; + if (NotificationIconContainerRefactor.isUnexpectedlyInLegacyMode()) return; mReplacingIcons = replacingIcons; } @Deprecated public void showIconIsolated(StatusBarIconView icon, boolean animated) { - mIconContainerRefactorFlag.assertInLegacyMode(); + NotificationIconContainerRefactor.assertInLegacyMode(); if (animated) { showIconIsolatedAnimated(icon, null); } else { @@ -716,14 +712,14 @@ public class NotificationIconContainer extends ViewGroup { public void showIconIsolatedAnimated(StatusBarIconView icon, @Nullable Runnable onAnimationEnd) { - if (mIconContainerRefactorFlag.isUnexpectedlyInLegacyMode()) return; + if (NotificationIconContainerRefactor.isUnexpectedlyInLegacyMode()) return; mIsolatedIconForAnimation = icon != null ? icon : mIsolatedIcon; mIsolatedIconAnimationEndRunnable = onAnimationEnd; showIconIsolated(icon); } public void showIconIsolated(StatusBarIconView icon) { - if (mIconContainerRefactorFlag.isUnexpectedlyInLegacyMode()) return; + if (NotificationIconContainerRefactor.isUnexpectedlyInLegacyMode()) return; mIsolatedIcon = icon; updateState(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHideIconsForBouncerManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHideIconsForBouncerManager.kt index 4d9de09fded4..fa6d2797a37e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHideIconsForBouncerManager.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHideIconsForBouncerManager.kt @@ -3,12 +3,15 @@ package com.android.systemui.statusbar.phone import android.app.StatusBarManager import com.android.systemui.Dumpable import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dump.DumpManager -import com.android.systemui.shade.ShadeExpansionStateManager +import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.window.StatusBarWindowStateController import com.android.systemui.util.concurrency.DelayableExecutor +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch import java.io.PrintWriter import javax.inject.Inject @@ -25,14 +28,14 @@ import javax.inject.Inject */ @SysUISingleton class StatusBarHideIconsForBouncerManager @Inject constructor( - private val commandQueue: CommandQueue, - @Main private val mainExecutor: DelayableExecutor, - statusBarWindowStateController: StatusBarWindowStateController, - shadeExpansionStateManager: ShadeExpansionStateManager, - dumpManager: DumpManager + @Application private val scope: CoroutineScope, + private val commandQueue: CommandQueue, + @Main private val mainExecutor: DelayableExecutor, + statusBarWindowStateController: StatusBarWindowStateController, + val shadeInteractor: ShadeInteractor, + dumpManager: DumpManager ) : Dumpable { // State variables set by external classes. - private var panelExpanded: Boolean = false private var isOccluded: Boolean = false private var bouncerShowing: Boolean = false private var topAppHidesStatusBar: Boolean = false @@ -49,10 +52,9 @@ class StatusBarHideIconsForBouncerManager @Inject constructor( statusBarWindowStateController.addListener { state -> setStatusBarStateAndTriggerUpdate(state) } - shadeExpansionStateManager.addFullExpansionListener { isExpanded -> - if (panelExpanded != isExpanded) { - panelExpanded = isExpanded - updateHideIconsForBouncer(animate = false) + scope.launch { + shadeInteractor.isAnyExpanded.collect { + updateHideIconsForBouncer(false) } } } @@ -101,7 +103,7 @@ class StatusBarHideIconsForBouncerManager @Inject constructor( topAppHidesStatusBar && isOccluded && (statusBarWindowHidden || bouncerShowing) - val hideBecauseKeyguard = !panelExpanded && !isOccluded && bouncerShowing + val hideBecauseKeyguard = !isShadeOrQsExpanded() && !isOccluded && bouncerShowing val shouldHideIconsForBouncer = hideBecauseApp || hideBecauseKeyguard if (hideIconsForBouncer != shouldHideIconsForBouncer) { hideIconsForBouncer = shouldHideIconsForBouncer @@ -125,9 +127,13 @@ class StatusBarHideIconsForBouncerManager @Inject constructor( } } + private fun isShadeOrQsExpanded(): Boolean { + return shadeInteractor.isAnyExpanded.value + } + override fun dump(pw: PrintWriter, args: Array<out String>) { pw.println("---- State variables set externally ----") - pw.println("panelExpanded=$panelExpanded") + pw.println("isShadeOrQsExpanded=${isShadeOrQsExpanded()}") pw.println("isOccluded=$isOccluded") pw.println("bouncerShowing=$bouncerShowing") pw.println("topAppHideStatusBar=$topAppHidesStatusBar") diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java index ba73c1098637..6d8ec44ad55e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java @@ -39,6 +39,7 @@ import com.android.systemui.res.R; import com.android.systemui.scene.domain.interactor.SceneInteractor; import com.android.systemui.scene.shared.flag.SceneContainerFlags; import com.android.systemui.shade.ShadeExpansionStateManager; +import com.android.systemui.shade.domain.interactor.ShadeInteractor; import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener; @@ -86,8 +87,9 @@ public final class StatusBarTouchableRegionManager implements Dumpable { ConfigurationController configurationController, HeadsUpManager headsUpManager, ShadeExpansionStateManager shadeExpansionStateManager, + ShadeInteractor shadeInteractor, Provider<SceneInteractor> sceneInteractor, - Provider<JavaAdapter> javaAdapter, + JavaAdapter javaAdapter, SceneContainerFlags sceneContainerFlags, UnlockedScreenOffAnimationController unlockedScreenOffAnimationController, PrimaryBouncerInteractor primaryBouncerInteractor, @@ -126,12 +128,12 @@ public final class StatusBarTouchableRegionManager implements Dumpable { }); mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController; - shadeExpansionStateManager.addFullExpansionListener(this::onShadeExpansionFullyChanged); + javaAdapter.alwaysCollectFlow(shadeInteractor.isAnyExpanded(), this::onShadeOrQsExpanded); if (sceneContainerFlags.isEnabled()) { - javaAdapter.get().alwaysCollectFlow( + javaAdapter.alwaysCollectFlow( sceneInteractor.get().isVisible(), - this::onShadeExpansionFullyChanged); + this::onShadeOrQsExpanded); } mPrimaryBouncerInteractor = primaryBouncerInteractor; @@ -151,7 +153,7 @@ public final class StatusBarTouchableRegionManager implements Dumpable { pw.println(mTouchableRegion); } - private void onShadeExpansionFullyChanged(Boolean isExpanded) { + private void onShadeOrQsExpanded(Boolean isExpanded) { if (isExpanded != mIsStatusBarExpanded) { mIsStatusBarExpanded = isExpanded; if (isExpanded) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java index 90e52d026b30..3921e69501d2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java @@ -44,7 +44,6 @@ import com.android.systemui.demomode.DemoMode; import com.android.systemui.demomode.DemoModeController; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlagsClassic; -import com.android.systemui.flags.Flags; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.res.R; import com.android.systemui.shade.ShadeExpansionStateManager; @@ -59,12 +58,10 @@ import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler; import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder; import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarNotificationIconViewStore; import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerStatusBarViewModel; -import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerViewModel; -import com.android.systemui.statusbar.phone.DozeParameters; +import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor; import com.android.systemui.statusbar.phone.NotificationIconAreaController; import com.android.systemui.statusbar.phone.NotificationIconContainer; import com.android.systemui.statusbar.phone.PhoneStatusBarView; -import com.android.systemui.statusbar.phone.ScreenOffAnimationController; import com.android.systemui.statusbar.phone.StatusBarHideIconsForBouncerManager; import com.android.systemui.statusbar.phone.StatusBarIconController; import com.android.systemui.statusbar.phone.StatusBarIconController.DarkIconManager; @@ -155,12 +152,10 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue private final DumpManager mDumpManager; private final StatusBarWindowStateController mStatusBarWindowStateController; private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; - private final NotificationIconContainerViewModel mStatusBarIconsViewModel; + private final NotificationIconContainerStatusBarViewModel mStatusBarIconsViewModel; private final ConfigurationState mConfigurationState; private final ConfigurationController mConfigurationController; - private final DozeParameters mDozeParameters; - private final ScreenOffAnimationController mScreenOffAnimationController; - private final NotificationIconContainerViewBinder.IconViewStore mStatusBarIconViewStore; + private final StatusBarNotificationIconViewStore mStatusBarIconViewStore; private final DemoModeController mDemoModeController; private List<String> mBlockedIcons = new ArrayList<>(); @@ -252,8 +247,6 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue NotificationIconContainerStatusBarViewModel statusBarIconsViewModel, ConfigurationState configurationState, ConfigurationController configurationController, - DozeParameters dozeParameters, - ScreenOffAnimationController screenOffAnimationController, StatusBarNotificationIconViewStore statusBarIconViewStore, DemoModeController demoModeController) { mStatusBarFragmentComponentFactory = statusBarFragmentComponentFactory; @@ -283,8 +276,6 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue mStatusBarIconsViewModel = statusBarIconsViewModel; mConfigurationState = configurationState; mConfigurationController = configurationController; - mDozeParameters = dozeParameters; - mScreenOffAnimationController = screenOffAnimationController; mStatusBarIconViewStore = statusBarIconViewStore; mDemoModeController = demoModeController; } @@ -317,7 +308,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mStatusBarWindowStateController.addListener(mStatusBarWindowStateListener); - if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR)) { + if (NotificationIconContainerRefactor.isEnabled()) { mDemoModeController.addCallback(mDemoModeCallback); } } @@ -326,7 +317,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue public void onDestroy() { super.onDestroy(); mStatusBarWindowStateController.removeListener(mStatusBarWindowStateListener); - if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR)) { + if (NotificationIconContainerRefactor.isEnabled()) { mDemoModeController.removeCallback(mDemoModeCallback); } } @@ -469,7 +460,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue /** Initializes views related to the notification icon area. */ public void initNotificationIconArea() { ViewGroup notificationIconArea = mStatusBar.requireViewById(R.id.notification_icon_area); - if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR)) { + if (NotificationIconContainerRefactor.isEnabled()) { mNotificationIconAreaInner = LayoutInflater.from(getContext()) .inflate(R.layout.notification_icon_area, notificationIconArea, true); @@ -480,9 +471,6 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue mStatusBarIconsViewModel, mConfigurationState, mConfigurationController, - mDozeParameters, - mFeatureFlags, - mScreenOffAnimationController, mStatusBarIconViewStore); } else { mNotificationIconAreaInner = diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/VariableDateViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/VariableDateViewController.kt index eaae0f0c4f75..fa6ea4cdc2e1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/VariableDateViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/VariableDateViewController.kt @@ -30,10 +30,13 @@ import android.text.TextUtils import android.util.Log import android.view.View.MeasureSpec import androidx.annotation.VisibleForTesting +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.repeatOnLifecycle import com.android.systemui.Dependency import com.android.systemui.broadcast.BroadcastDispatcher -import com.android.systemui.shade.ShadeExpansionStateManager +import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.shade.ShadeLogger +import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.util.ViewController import com.android.systemui.util.time.SystemClock import java.text.FieldPosition @@ -83,7 +86,7 @@ private const val TAG = "VariableDateViewController" class VariableDateViewController( private val systemClock: SystemClock, private val broadcastDispatcher: BroadcastDispatcher, - private val shadeExpansionStateManager: ShadeExpansionStateManager, + private val shadeInteractor: ShadeInteractor, private val shadeLogger: ShadeLogger, private val timeTickHandler: Handler, view: VariableDateView @@ -174,8 +177,11 @@ class VariableDateViewController( broadcastDispatcher.registerReceiver(intentReceiver, filter, HandlerExecutor(timeTickHandler), UserHandle.SYSTEM) - - shadeExpansionStateManager.addQsExpansionFractionListener(::onQsExpansionFractionChanged) + mView.repeatWhenAttached { + repeatOnLifecycle(Lifecycle.State.STARTED) { + shadeInteractor.qsExpansion.collect(::onQsExpansionFractionChanged) + } + } post(::updateClock) mView.onAttach(onMeasureListener) } @@ -183,7 +189,6 @@ class VariableDateViewController( override fun onViewDetached() { dateFormat = null mView.onAttach(null) - shadeExpansionStateManager.removeQsExpansionFractionListener(::onQsExpansionFractionChanged) broadcastDispatcher.unregisterReceiver(intentReceiver) } @@ -237,18 +242,18 @@ class VariableDateViewController( class Factory @Inject constructor( private val systemClock: SystemClock, private val broadcastDispatcher: BroadcastDispatcher, - private val shadeExpansionStateManager: ShadeExpansionStateManager, + private val shadeInteractor: ShadeInteractor, private val shadeLogger: ShadeLogger, @Named(Dependency.TIME_TICK_HANDLER_NAME) private val handler: Handler ) { fun create(view: VariableDateView): VariableDateViewController { return VariableDateViewController( - systemClock, - broadcastDispatcher, - shadeExpansionStateManager, - shadeLogger, - handler, - view + systemClock, + broadcastDispatcher, + shadeInteractor, + shadeLogger, + handler, + view ) } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java index 153f3f7a1049..ac40ba617936 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java @@ -21,6 +21,7 @@ import static android.view.View.INVISIBLE; import static com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR; import static com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED; import static com.android.systemui.flags.Flags.MIGRATE_KEYGUARD_STATUS_VIEW; +import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.atLeast; @@ -39,7 +40,6 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.common.ui.ConfigurationState; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FakeFeatureFlags; -import com.android.systemui.flags.Flags; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory; import com.android.systemui.log.LogBuffer; @@ -58,6 +58,7 @@ import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController; import com.android.systemui.statusbar.notification.icon.ui.viewbinder.AlwaysOnDisplayNotificationIconViewStore; import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerAlwaysOnDisplayViewModel; +import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.NotificationIconAreaController; import com.android.systemui.statusbar.phone.NotificationIconContainer; @@ -143,6 +144,7 @@ public class KeyguardClockSwitchControllerBaseTest extends SysuiTestCase { public void setup() { MockitoAnnotations.initMocks(this); + setFlagDefault(mSetFlagsRule, NotificationIconContainerRefactor.FLAG_NAME); mFakeDateView.setTag(R.id.tag_smartspace_view, new Object()); mFakeWeatherView.setTag(R.id.tag_smartspace_view, new Object()); mFakeSmartspaceView.setTag(R.id.tag_smartspace_view, new Object()); @@ -173,7 +175,6 @@ public class KeyguardClockSwitchControllerBaseTest extends SysuiTestCase { when(mSmartspaceController.buildAndConnectView(any())).thenReturn(mFakeSmartspaceView); mExecutor = new FakeExecutor(new FakeSystemClock()); mFakeFeatureFlags = new FakeFeatureFlags(); - mFakeFeatureFlags.setDefault(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR); mFakeFeatureFlags.set(FACE_AUTH_REFACTOR, false); mFakeFeatureFlags.set(LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false); mFakeFeatureFlags.set(MIGRATE_KEYGUARD_STATUS_VIEW, false); diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MockMagnificationAnimationCallback.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MockMagnificationAnimationCallback.java deleted file mode 100644 index 89389b0dd424..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MockMagnificationAnimationCallback.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) 2022 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.accessibility; - -import android.os.RemoteException; -import android.view.accessibility.IRemoteMagnificationAnimationCallback; - -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.atomic.AtomicInteger; - -public class MockMagnificationAnimationCallback extends IRemoteMagnificationAnimationCallback.Stub { - - private final CountDownLatch mCountDownLatch; - private final AtomicInteger mSuccessCount; - private final AtomicInteger mFailedCount; - - MockMagnificationAnimationCallback(CountDownLatch countDownLatch) { - mCountDownLatch = countDownLatch; - mSuccessCount = new AtomicInteger(); - mFailedCount = new AtomicInteger(); - } - - public int getSuccessCount() { - return mSuccessCount.get(); - } - - public int getFailedCount() { - return mFailedCount.get(); - } - - @Override - public void onResult(boolean success) throws RemoteException { - if (success) { - mSuccessCount.getAndIncrement(); - } else { - mFailedCount.getAndIncrement(); - } - // It should be put at the last line to avoid making CountDownLatch#await passed without - // updating values. - mCountDownLatch.countDown(); - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java index f15164e3fdcc..284c273fa831 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java @@ -35,7 +35,6 @@ import android.graphics.Rect; import android.os.Handler; import android.os.RemoteException; import android.testing.AndroidTestingRunner; -import android.testing.TestableLooper.RunWithLooper; import android.view.SurfaceControl; import android.view.View; import android.view.WindowManager; @@ -68,7 +67,6 @@ import java.util.concurrent.atomic.AtomicReference; @LargeTest @RunWith(AndroidTestingRunner.class) -@RunWithLooper(setAsMainLooper = true) public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { @Rule @@ -135,7 +133,9 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { @After public void tearDown() throws Exception { - mController.deleteWindowMagnification(); + mInstrumentation.runOnMainSync(() -> { + mController.deleteWindowMagnification(); + }); } @Test @@ -170,8 +170,7 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { final float targetCenterX = DEFAULT_CENTER_X + 100; final float targetCenterY = DEFAULT_CENTER_Y + 100; - mWindowMagnificationAnimationController.enableWindowMagnification(targetScale, - targetCenterX, targetCenterY, null); + enableWindowMagnificationWithoutAnimation(targetScale, targetCenterX, targetCenterY); verifyFinalSpec(targetScale, targetCenterX, targetCenterY); } @@ -179,10 +178,11 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { @Test public void enableWindowMagnificationWithScaleOne_disabled_NoAnimationAndInvokeCallback() throws RemoteException { - mWindowMagnificationAnimationController.enableWindowMagnification(1, + enableWindowMagnificationAndWaitAnimating( + mWaitAnimationDuration, /* targetScale= */ 1.0f, DEFAULT_CENTER_X, DEFAULT_CENTER_Y, mAnimationCallback); - verify(mSpyController).enableWindowMagnificationInternal(1, DEFAULT_CENTER_X, + verify(mSpyController).enableWindowMagnificationInternal(1.0f, DEFAULT_CENTER_X, DEFAULT_CENTER_Y, 0f, 0f); verify(mAnimationCallback).onResult(true); } @@ -196,13 +196,15 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { final float targetCenterX = DEFAULT_CENTER_X + 100; final float targetCenterY = DEFAULT_CENTER_Y + 100; - Mockito.reset(mSpyController); - mWindowMagnificationAnimationController.enableWindowMagnification(targetScale, - targetCenterX, targetCenterY, mAnimationCallback2); - mCurrentScale.set(mController.getScale()); - mCurrentCenterX.set(mController.getCenterX()); - mCurrentCenterY.set(mController.getCenterY()); - advanceTimeBy(mWaitAnimationDuration); + resetMockObjects(); + mInstrumentation.runOnMainSync(() -> { + mWindowMagnificationAnimationController.enableWindowMagnification(targetScale, + targetCenterX, targetCenterY, mAnimationCallback2); + mCurrentScale.set(mController.getScale()); + mCurrentCenterX.set(mController.getCenterX()); + mCurrentCenterY.set(mController.getCenterY()); + advanceTimeBy(mWaitAnimationDuration); + }); verify(mSpyController, atLeast(2)).enableWindowMagnificationInternal( mScaleCaptor.capture(), @@ -224,9 +226,8 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { enableWindowMagnificationAndWaitAnimating(mWaitPartialAnimationDuration, mAnimationCallback); - mWindowMagnificationAnimationController.enableWindowMagnification(Float.NaN, + enableWindowMagnificationAndWaitAnimating(mWaitAnimationDuration, Float.NaN, Float.NaN, Float.NaN, mAnimationCallback2); - advanceTimeBy(mWaitAnimationDuration); // The callback in 2nd enableWindowMagnification will return true verify(mAnimationCallback2).onResult(true); @@ -245,12 +246,14 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { final float targetCenterY = DEFAULT_CENTER_Y + 100; Mockito.reset(mSpyController); - mWindowMagnificationAnimationController.enableWindowMagnification(targetScale, - targetCenterX, targetCenterY, mAnimationCallback); - mCurrentScale.set(mController.getScale()); - mCurrentCenterX.set(mController.getCenterX()); - mCurrentCenterY.set(mController.getCenterY()); - advanceTimeBy(mWaitAnimationDuration); + mInstrumentation.runOnMainSync(() -> { + mWindowMagnificationAnimationController.enableWindowMagnification(targetScale, + targetCenterX, targetCenterY, mAnimationCallback); + mCurrentScale.set(mController.getScale()); + mCurrentCenterX.set(mController.getCenterX()); + mCurrentCenterY.set(mController.getCenterY()); + advanceTimeBy(mWaitAnimationDuration); + }); verify(mSpyController, atLeast(2)).enableWindowMagnificationInternal( mScaleCaptor.capture(), @@ -279,12 +282,14 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { final float targetCenterY = DEFAULT_CENTER_Y + 100; Mockito.reset(mSpyController); - mWindowMagnificationAnimationController.enableWindowMagnification(targetScale, - targetCenterX, targetCenterY, mAnimationCallback); - mCurrentScale.set(mController.getScale()); - mCurrentCenterX.set(mController.getCenterX()); - mCurrentCenterY.set(mController.getCenterY()); - advanceTimeBy(mWaitAnimationDuration); + mInstrumentation.runOnMainSync(() -> { + mWindowMagnificationAnimationController.enableWindowMagnification(targetScale, + targetCenterX, targetCenterY, mAnimationCallback); + mCurrentScale.set(mController.getScale()); + mCurrentCenterX.set(mController.getCenterX()); + mCurrentCenterY.set(mController.getCenterY()); + advanceTimeBy(mWaitAnimationDuration); + }); verify(mSpyController, atLeast(2)).enableWindowMagnificationInternal( mScaleCaptor.capture(), @@ -314,8 +319,7 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { final float targetCenterY = DEFAULT_CENTER_Y + 100; Mockito.reset(mSpyController); - mWindowMagnificationAnimationController.enableWindowMagnification(targetScale, - targetCenterX, targetCenterY, null); + enableWindowMagnificationWithoutAnimation(targetScale, targetCenterX, targetCenterY); verifyFinalSpec(Float.NaN, Float.NaN, Float.NaN); assertEquals(WindowMagnificationAnimationController.STATE_DISABLED, @@ -333,8 +337,7 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { final float targetCenterY = DEFAULT_CENTER_Y + 100; Mockito.reset(mSpyController); - mWindowMagnificationAnimationController.enableWindowMagnification(targetScale, - targetCenterX, targetCenterY, null); + enableWindowMagnificationWithoutAnimation(targetScale, targetCenterX, targetCenterY); verifyFinalSpec(targetScale, targetCenterX, targetCenterY); verify(mAnimationCallback).onResult(false); @@ -347,9 +350,8 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { mAnimationCallback); Mockito.reset(mSpyController); - mWindowMagnificationAnimationController.enableWindowMagnification(Float.NaN, - Float.NaN, Float.NaN, mAnimationCallback2); - advanceTimeBy(mWaitAnimationDuration); + enableWindowMagnificationAndWaitAnimating( + mWaitAnimationDuration, Float.NaN, Float.NaN, Float.NaN, mAnimationCallback2); verify(mSpyController, never()).enableWindowMagnificationInternal(anyFloat(), anyFloat(), anyFloat()); @@ -368,20 +370,25 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { final float targetCenterY = DEFAULT_CENTER_Y + 100; Mockito.reset(mSpyController); - mWindowMagnificationAnimationController.enableWindowMagnification(targetScale, - targetCenterX, targetCenterY, mAnimationCallback2); - mCurrentScale.set(mController.getScale()); - mCurrentCenterX.set(mController.getCenterX()); - mCurrentCenterY.set(mController.getCenterY()); + mInstrumentation.runOnMainSync(() -> { + mWindowMagnificationAnimationController.enableWindowMagnification(targetScale, + targetCenterX, targetCenterY, mAnimationCallback2); + mCurrentScale.set(mController.getScale()); + mCurrentCenterX.set(mController.getCenterX()); + mCurrentCenterY.set(mController.getCenterY()); + }); // Current spec shouldn't match given spec. verify(mAnimationCallback2, never()).onResult(anyBoolean()); verify(mAnimationCallback).onResult(false); - // ValueAnimator.reverse() could not work correctly with the AnimatorTestRule since it is - // using SystemClock in reverse() (b/305731398). Therefore, we call end() on the animator - // directly to verify the result of animation is correct instead of querying the animation - // frame at a specific timing. - mValueAnimator.end(); + + mInstrumentation.runOnMainSync(() -> { + // ValueAnimator.reverse() could not work correctly with the AnimatorTestRule since it + // is using SystemClock in reverse() (b/305731398). Therefore, we call end() on the + // animator directly to verify the result of animation is correct instead of querying + // the animation frame at a specific timing. + mValueAnimator.end(); + }); verify(mSpyController).enableWindowMagnificationInternal( mScaleCaptor.capture(), @@ -410,8 +417,7 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { final float targetCenterY = DEFAULT_CENTER_Y + 100; Mockito.reset(mSpyController); - mWindowMagnificationAnimationController.enableWindowMagnification(targetScale, - targetCenterX, targetCenterY, null); + enableWindowMagnificationWithoutAnimation(targetScale, targetCenterX, targetCenterY); verify(mAnimationCallback).onResult(false); verifyFinalSpec(targetScale, targetCenterX, targetCenterY); @@ -425,9 +431,8 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { mAnimationCallback); Mockito.reset(mSpyController); - mWindowMagnificationAnimationController.enableWindowMagnification(Float.NaN, + enableWindowMagnificationAndWaitAnimating(mWaitAnimationDuration, Float.NaN, Float.NaN, Float.NaN, mAnimationCallback2); - advanceTimeBy(mWaitAnimationDuration); verify(mSpyController, never()).enableWindowMagnificationInternal(anyFloat(), anyFloat(), anyFloat()); @@ -445,12 +450,14 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { final float targetCenterY = DEFAULT_CENTER_Y + 100; Mockito.reset(mSpyController); - mWindowMagnificationAnimationController.enableWindowMagnification(targetScale, - targetCenterX, targetCenterY, mAnimationCallback2); - mCurrentScale.set(mController.getScale()); - mCurrentCenterX.set(mController.getCenterX()); - mCurrentCenterY.set(mController.getCenterY()); - advanceTimeBy(mWaitAnimationDuration); + mInstrumentation.runOnMainSync(() -> { + mWindowMagnificationAnimationController.enableWindowMagnification(targetScale, + targetCenterX, targetCenterY, mAnimationCallback2); + mCurrentScale.set(mController.getScale()); + mCurrentCenterX.set(mController.getCenterX()); + mCurrentCenterY.set(mController.getCenterY()); + advanceTimeBy(mWaitAnimationDuration); + }); verify(mSpyController, atLeast(2)).enableWindowMagnificationInternal( mScaleCaptor.capture(), @@ -471,25 +478,26 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { final Rect windowBounds = new Rect(mWindowManager.getCurrentWindowMetrics().getBounds()); Mockito.reset(mSpyController); - mWindowMagnificationAnimationController.enableWindowMagnification(DEFAULT_SCALE, - windowBounds.exactCenterX(), windowBounds.exactCenterY(), - offsetRatio, offsetRatio, mAnimationCallback); - advanceTimeBy(mWaitAnimationDuration); + mInstrumentation.runOnMainSync(() -> { + mWindowMagnificationAnimationController.enableWindowMagnification(DEFAULT_SCALE, + windowBounds.exactCenterX(), windowBounds.exactCenterY(), + offsetRatio, offsetRatio, mAnimationCallback); + advanceTimeBy(mWaitAnimationDuration); + }); - // We delay the time of verifying to wait for the measurement and layout of the view - mHandler.postDelayed(() -> { - final View attachedView = mWindowManager.getAttachedView(); - assertNotNull(attachedView); - final Rect mirrorViewBound = new Rect(); - final View mirrorView = attachedView.findViewById(R.id.surface_view); - assertNotNull(mirrorView); - mirrorView.getBoundsOnScreen(mirrorViewBound); + // Wait for Rects update + waitForIdleSync(); + final View attachedView = mWindowManager.getAttachedView(); + assertNotNull(attachedView); + final Rect mirrorViewBound = new Rect(); + final View mirrorView = attachedView.findViewById(R.id.surface_view); + assertNotNull(mirrorView); + mirrorView.getBoundsOnScreen(mirrorViewBound); - assertEquals((int) (offsetRatio * mirrorViewBound.width() / 2), - (int) (mirrorViewBound.exactCenterX() - windowBounds.exactCenterX())); - assertEquals((int) (offsetRatio * mirrorViewBound.height() / 2), - (int) (mirrorViewBound.exactCenterY() - windowBounds.exactCenterY())); - }, 100); + assertEquals((int) (offsetRatio * mirrorViewBound.width() / 2), + (int) (mirrorViewBound.exactCenterX() - windowBounds.exactCenterX())); + assertEquals((int) (offsetRatio * mirrorViewBound.height() / 2), + (int) (mirrorViewBound.exactCenterY() - windowBounds.exactCenterY())); } @Test @@ -498,9 +506,11 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { final float targetCenterY = DEFAULT_CENTER_Y + 100; enableWindowMagnificationWithoutAnimation(); - mWindowMagnificationAnimationController.moveWindowMagnifierToPosition( - targetCenterX, targetCenterY, mAnimationCallback); - advanceTimeBy(mWaitAnimationDuration); + mInstrumentation.runOnMainSync(() -> { + mWindowMagnificationAnimationController.moveWindowMagnifierToPosition( + targetCenterX, targetCenterY, mAnimationCallback); + advanceTimeBy(mWaitAnimationDuration); + }); verify(mAnimationCallback).onResult(true); verify(mAnimationCallback, never()).onResult(false); @@ -512,15 +522,17 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { throws RemoteException { enableWindowMagnificationWithoutAnimation(); - mWindowMagnificationAnimationController.moveWindowMagnifierToPosition( - DEFAULT_CENTER_X + 10, DEFAULT_CENTER_Y + 10, mAnimationCallback); - mWindowMagnificationAnimationController.moveWindowMagnifierToPosition( - DEFAULT_CENTER_X + 20, DEFAULT_CENTER_Y + 20, mAnimationCallback); - mWindowMagnificationAnimationController.moveWindowMagnifierToPosition( - DEFAULT_CENTER_X + 30, DEFAULT_CENTER_Y + 30, mAnimationCallback); - mWindowMagnificationAnimationController.moveWindowMagnifierToPosition( - DEFAULT_CENTER_X + 40, DEFAULT_CENTER_Y + 40, mAnimationCallback2); - advanceTimeBy(mWaitAnimationDuration); + mInstrumentation.runOnMainSync(() -> { + mWindowMagnificationAnimationController.moveWindowMagnifierToPosition( + DEFAULT_CENTER_X + 10, DEFAULT_CENTER_Y + 10, mAnimationCallback); + mWindowMagnificationAnimationController.moveWindowMagnifierToPosition( + DEFAULT_CENTER_X + 20, DEFAULT_CENTER_Y + 20, mAnimationCallback); + mWindowMagnificationAnimationController.moveWindowMagnifierToPosition( + DEFAULT_CENTER_X + 30, DEFAULT_CENTER_Y + 30, mAnimationCallback); + mWindowMagnificationAnimationController.moveWindowMagnifierToPosition( + DEFAULT_CENTER_X + 40, DEFAULT_CENTER_Y + 40, mAnimationCallback2); + advanceTimeBy(mWaitAnimationDuration); + }); // only the last one callback will return true verify(mAnimationCallback2).onResult(true); @@ -538,9 +550,11 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { enableWindowMagnificationAndWaitAnimating(mWaitPartialAnimationDuration, mAnimationCallback); - mWindowMagnificationAnimationController.moveWindowMagnifierToPosition( - targetCenterX, targetCenterY, mAnimationCallback2); - advanceTimeBy(mWaitAnimationDuration); + mInstrumentation.runOnMainSync(() -> { + mWindowMagnificationAnimationController.moveWindowMagnifierToPosition( + targetCenterX, targetCenterY, mAnimationCallback2); + advanceTimeBy(mWaitAnimationDuration); + }); // The callback in moveWindowMagnifierToPosition will return true verify(mAnimationCallback2).onResult(true); @@ -556,9 +570,11 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { enableWindowMagnificationAndWaitAnimating(mWaitPartialAnimationDuration, mAnimationCallback); - mWindowMagnificationAnimationController.moveWindowMagnifierToPosition( - Float.NaN, Float.NaN, mAnimationCallback2); - advanceTimeBy(mWaitAnimationDuration); + mInstrumentation.runOnMainSync(() -> { + mWindowMagnificationAnimationController.moveWindowMagnifierToPosition( + Float.NaN, Float.NaN, mAnimationCallback2); + advanceTimeBy(mWaitAnimationDuration); + }); // The callback in moveWindowMagnifierToPosition will return true verify(mAnimationCallback2).onResult(true); @@ -584,6 +600,7 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { throws RemoteException { enableWindowMagnificationWithoutAnimation(); + resetMockObjects(); deleteWindowMagnificationAndWaitAnimating(mWaitAnimationDuration, mAnimationCallback); verify(mSpyController, atLeast(2)).enableWindowMagnificationInternal( @@ -625,16 +642,18 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { mAnimationCallback); Mockito.reset(mSpyController); - mWindowMagnificationAnimationController.deleteWindowMagnification( - mAnimationCallback2); - mCurrentScale.set(mController.getScale()); - mCurrentCenterX.set(mController.getCenterX()); - mCurrentCenterY.set(mController.getCenterY()); - // ValueAnimator.reverse() could not work correctly with the AnimatorTestRule since it is - // using SystemClock in reverse() (b/305731398). Therefore, we call end() on the animator - // directly to verify the result of animation is correct instead of querying the animation - // frame at a specific timing. - mValueAnimator.end(); + mInstrumentation.runOnMainSync(() -> { + mWindowMagnificationAnimationController.deleteWindowMagnification( + mAnimationCallback2); + mCurrentScale.set(mController.getScale()); + mCurrentCenterX.set(mController.getCenterX()); + mCurrentCenterY.set(mController.getCenterY()); + // ValueAnimator.reverse() could not work correctly with the AnimatorTestRule since it + // is using SystemClock in reverse() (b/305731398). Therefore, we call end() on the + // animator directly to verify the result of animation is correct instead of querying + // the animation frame at a specific timing. + mValueAnimator.end(); + }); verify(mSpyController).enableWindowMagnificationInternal( mScaleCaptor.capture(), @@ -661,7 +680,7 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { mAnimationCallback); Mockito.reset(mSpyController); - mWindowMagnificationAnimationController.deleteWindowMagnification(null); + deleteWindowMagnificationWithoutAnimation(); verifyFinalSpec(Float.NaN, Float.NaN, Float.NaN); verify(mAnimationCallback).onResult(false); @@ -673,6 +692,7 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { deleteWindowMagnificationAndWaitAnimating(mWaitPartialAnimationDuration, mAnimationCallback); + resetMockObjects(); deleteWindowMagnificationAndWaitAnimating(mWaitAnimationDuration, mAnimationCallback2); verify(mSpyController).enableWindowMagnificationInternal( @@ -710,8 +730,7 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { final float offsetY = (float) Math.ceil(offsetX * WindowMagnificationController.HORIZONTAL_LOCK_BASE) + 1.0f; - - mController.moveWindowMagnifier(offsetX, offsetY); + mInstrumentation.runOnMainSync(()-> mController.moveWindowMagnifier(offsetX, offsetY)); verify(mSpyController).moveWindowMagnifier(offsetX, offsetY); verifyFinalSpec(DEFAULT_SCALE, DEFAULT_CENTER_X, DEFAULT_CENTER_Y + offsetY); @@ -726,8 +745,8 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { final float offsetY = (float) Math.floor(offsetX * WindowMagnificationController.HORIZONTAL_LOCK_BASE) - 1.0f; - - mController.moveWindowMagnifier(offsetX, offsetY); + mInstrumentation.runOnMainSync(() -> + mController.moveWindowMagnifier(offsetX, offsetY)); verify(mSpyController).moveWindowMagnifier(offsetX, offsetY); verifyFinalSpec(DEFAULT_SCALE, DEFAULT_CENTER_X + offsetX, DEFAULT_CENTER_Y); @@ -742,8 +761,10 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { (float) Math.ceil(offsetX * WindowMagnificationController.HORIZONTAL_LOCK_BASE); // while diagonal scrolling enabled, // should move with both offsetX and offsetY without regrading offsetY/offsetX - mController.setDiagonalScrolling(true); - mController.moveWindowMagnifier(offsetX, offsetY); + mInstrumentation.runOnMainSync(() -> { + mController.setDiagonalScrolling(true); + mController.moveWindowMagnifier(offsetX, offsetY); + }); verify(mSpyController).moveWindowMagnifier(offsetX, offsetY); verifyFinalSpec(DEFAULT_SCALE, DEFAULT_CENTER_X + offsetX, DEFAULT_CENTER_Y + offsetY); @@ -755,9 +776,11 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { final float targetCenterY = DEFAULT_CENTER_Y + 100; enableWindowMagnificationWithoutAnimation(); - mController.moveWindowMagnifierToPosition(targetCenterX, targetCenterY, - mAnimationCallback); - advanceTimeBy(mWaitAnimationDuration); + mInstrumentation.runOnMainSync(() -> { + mController.moveWindowMagnifierToPosition(targetCenterX, targetCenterY, + mAnimationCallback); + advanceTimeBy(mWaitAnimationDuration); + }); verifyFinalSpec(DEFAULT_SCALE, targetCenterX, targetCenterY); } @@ -774,24 +797,49 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { } private void enableWindowMagnificationWithoutAnimation() { - Mockito.reset(mSpyController); - mWindowMagnificationAnimationController.enableWindowMagnification(DEFAULT_SCALE, - DEFAULT_CENTER_X, DEFAULT_CENTER_Y, null); + enableWindowMagnificationWithoutAnimation( + DEFAULT_SCALE, DEFAULT_CENTER_X, DEFAULT_CENTER_Y); + } + + private void enableWindowMagnificationWithoutAnimation( + float targetScale, float targetCenterX, float targetCenterY) { + mInstrumentation.runOnMainSync(() -> { + mWindowMagnificationAnimationController.enableWindowMagnification( + targetScale, targetCenterX, targetCenterY, null); + }); } private void enableWindowMagnificationAndWaitAnimating(long duration, @Nullable IRemoteMagnificationAnimationCallback callback) { - Mockito.reset(mSpyController); - mWindowMagnificationAnimationController.enableWindowMagnification(DEFAULT_SCALE, - DEFAULT_CENTER_X, DEFAULT_CENTER_Y, callback); - advanceTimeBy(duration); + enableWindowMagnificationAndWaitAnimating( + duration, DEFAULT_SCALE, DEFAULT_CENTER_X, DEFAULT_CENTER_Y, callback); + } + + private void enableWindowMagnificationAndWaitAnimating( + long duration, + float targetScale, + float targetCenterX, + float targetCenterY, + @Nullable IRemoteMagnificationAnimationCallback callback) { + mInstrumentation.runOnMainSync(() -> { + mWindowMagnificationAnimationController.enableWindowMagnification( + targetScale, targetCenterX, targetCenterY, callback); + advanceTimeBy(duration); + }); + } + + private void deleteWindowMagnificationWithoutAnimation() { + mInstrumentation.runOnMainSync(() -> { + mWindowMagnificationAnimationController.deleteWindowMagnification(null); + }); } private void deleteWindowMagnificationAndWaitAnimating(long duration, @Nullable IRemoteMagnificationAnimationCallback callback) { - resetMockObjects(); - mWindowMagnificationAnimationController.deleteWindowMagnification(callback); - advanceTimeBy(duration); + mInstrumentation.runOnMainSync(() -> { + mWindowMagnificationAnimationController.deleteWindowMagnification(callback); + advanceTimeBy(duration); + }); } private void verifyStartValue(ArgumentCaptor<Float> captor, float startValue) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java index 86ae51768219..06421dbbf2bb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java @@ -45,6 +45,7 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.times; @@ -53,6 +54,7 @@ import static org.mockito.Mockito.when; import android.animation.ValueAnimator; import android.annotation.IdRes; +import android.annotation.Nullable; import android.app.Instrumentation; import android.content.Context; import android.content.pm.ActivityInfo; @@ -89,6 +91,7 @@ import androidx.test.filters.LargeTest; import com.android.internal.graphics.SfVsyncFrameCallbackProvider; import com.android.systemui.SysuiTestCase; +import com.android.systemui.animation.AnimatorTestRule; import com.android.systemui.model.SysUiState; import com.android.systemui.res.R; import com.android.systemui.settings.FakeDisplayTracker; @@ -102,6 +105,7 @@ import org.junit.After; import org.junit.Assume; import org.junit.Before; import org.junit.Ignore; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Answers; @@ -111,8 +115,6 @@ import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import java.util.List; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; @LargeTest @@ -120,12 +122,10 @@ import java.util.concurrent.atomic.AtomicInteger; @RunWith(AndroidTestingRunner.class) public class WindowMagnificationControllerTest extends SysuiTestCase { + @Rule + public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule(); + private static final int LAYOUT_CHANGE_TIMEOUT_MS = 5000; - // The duration couldn't too short, otherwise the animation check on bounce effect - // won't work in expectation. (b/299537784) - private static final int BOUNCE_EFFECT_DURATION_MS = 2000; - private static final long ANIMATION_DURATION_MS = 300; - private final long mWaitingAnimationPeriod = 2 * ANIMATION_DURATION_MS; @Mock private SfVsyncFrameCallbackProvider mSfVsyncFrameProvider; @Mock @@ -134,11 +134,16 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { private WindowMagnifierCallback mWindowMagnifierCallback; @Mock IRemoteMagnificationAnimationCallback mAnimationCallback; + @Mock + IRemoteMagnificationAnimationCallback mAnimationCallback2; @Mock(answer = Answers.RETURNS_DEEP_STUBS) private SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction(); @Mock private SecureSettings mSecureSettings; + private long mWaitAnimationDuration; + private long mWaitBounceEffectDuration; + private Handler mHandler; private TestableWindowManager mWindowManager; private SysUiState mSysUiState; @@ -194,6 +199,13 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { mResources.getConfiguration().orientation = ORIENTATION_PORTRAIT; } + // Using the animation duration in WindowMagnificationAnimationController for testing. + mWaitAnimationDuration = mResources.getInteger( + com.android.internal.R.integer.config_longAnimTime); + // Using the bounce effect duration in WindowMagnificationController for testing. + mWaitBounceEffectDuration = mResources.getInteger( + com.android.internal.R.integer.config_shortAnimTime); + mWindowMagnificationAnimationController = new WindowMagnificationAnimationController( mContext, mValueAnimator); mWindowMagnificationController = @@ -208,7 +220,6 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { mSysUiState, () -> mWindowSessionSpy, mSecureSettings); - mWindowMagnificationController.setBounceEffectDuration(BOUNCE_EFFECT_DURATION_MS); verify(mMirrorWindowControl).setWindowDelegate( any(MirrorWindowControl.MirrorWindowDelegate.class)); @@ -281,9 +292,9 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { /* magnificationFrameOffsetRatioY= */ 0, Mockito.mock(IRemoteMagnificationAnimationCallback.class)); }); + advanceTimeBy(LAYOUT_CHANGE_TIMEOUT_MS); - verify(mSfVsyncFrameProvider, - timeout(LAYOUT_CHANGE_TIMEOUT_MS).atLeast(2)).postFrameCallback(any()); + verify(mSfVsyncFrameProvider, atLeast(2)).postFrameCallback(any()); } @Test @@ -401,14 +412,12 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { @Test public void moveWindowMagnifierToPositionWithAnimation_expectedValuesAndInvokeCallback() - throws InterruptedException { + throws RemoteException { mInstrumentation.runOnMainSync(() -> { mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN, Float.NaN, 0, 0, null); }); - final CountDownLatch countDownLatch = new CountDownLatch(1); - final MockMagnificationAnimationCallback animationCallback = - new MockMagnificationAnimationCallback(countDownLatch); + final ArgumentCaptor<Rect> sourceBoundsCaptor = ArgumentCaptor.forClass(Rect.class); verify(mWindowMagnifierCallback, timeout(LAYOUT_CHANGE_TIMEOUT_MS)) .onSourceBoundsChanged((eq(mContext.getDisplayId())), sourceBoundsCaptor.capture()); @@ -417,12 +426,12 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { mInstrumentation.runOnMainSync(() -> { mWindowMagnificationController.moveWindowMagnifierToPosition( - targetCenterX, targetCenterY, animationCallback); + targetCenterX, targetCenterY, mAnimationCallback); }); + advanceTimeBy(mWaitAnimationDuration); - assertTrue(countDownLatch.await(mWaitingAnimationPeriod, TimeUnit.MILLISECONDS)); - assertEquals(1, animationCallback.getSuccessCount()); - assertEquals(0, animationCallback.getFailedCount()); + verify(mAnimationCallback, times(1)).onResult(eq(true)); + verify(mAnimationCallback, never()).onResult(eq(false)); verify(mWindowMagnifierCallback, timeout(LAYOUT_CHANGE_TIMEOUT_MS)) .onSourceBoundsChanged((eq(mContext.getDisplayId())), sourceBoundsCaptor.capture()); assertEquals(mWindowMagnificationController.getCenterX(), @@ -435,14 +444,12 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { @Test public void moveWindowMagnifierToPositionMultipleTimes_expectedValuesAndInvokeCallback() - throws InterruptedException { + throws RemoteException { mInstrumentation.runOnMainSync(() -> { mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN, Float.NaN, 0, 0, null); }); - final CountDownLatch countDownLatch = new CountDownLatch(4); - final MockMagnificationAnimationCallback animationCallback = - new MockMagnificationAnimationCallback(countDownLatch); + final ArgumentCaptor<Rect> sourceBoundsCaptor = ArgumentCaptor.forClass(Rect.class); verify(mWindowMagnifierCallback, timeout(LAYOUT_CHANGE_TIMEOUT_MS)) .onSourceBoundsChanged((eq(mContext.getDisplayId())), sourceBoundsCaptor.capture()); @@ -451,20 +458,20 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { mInstrumentation.runOnMainSync(() -> { mWindowMagnificationController.moveWindowMagnifierToPosition( - centerX + 10, centerY + 10, animationCallback); + centerX + 10, centerY + 10, mAnimationCallback); mWindowMagnificationController.moveWindowMagnifierToPosition( - centerX + 20, centerY + 20, animationCallback); + centerX + 20, centerY + 20, mAnimationCallback); mWindowMagnificationController.moveWindowMagnifierToPosition( - centerX + 30, centerY + 30, animationCallback); + centerX + 30, centerY + 30, mAnimationCallback); mWindowMagnificationController.moveWindowMagnifierToPosition( - centerX + 40, centerY + 40, animationCallback); + centerX + 40, centerY + 40, mAnimationCallback2); }); + advanceTimeBy(mWaitAnimationDuration); - assertTrue(countDownLatch.await(mWaitingAnimationPeriod, TimeUnit.MILLISECONDS)); // only the last one callback will return true - assertEquals(1, animationCallback.getSuccessCount()); + verify(mAnimationCallback2).onResult(eq(true)); // the others will return false - assertEquals(3, animationCallback.getFailedCount()); + verify(mAnimationCallback, times(3)).onResult(eq(false)); verify(mWindowMagnifierCallback, timeout(LAYOUT_CHANGE_TIMEOUT_MS)) .onSourceBoundsChanged((eq(mContext.getDisplayId())), sourceBoundsCaptor.capture()); assertEquals(mWindowMagnificationController.getCenterX(), @@ -1078,27 +1085,16 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { final View mirrorView = mWindowManager.getAttachedView(); - final long timeout = SystemClock.uptimeMillis() + 5000; final AtomicDouble maxScaleX = new AtomicDouble(); - final Runnable onAnimationFrame = new Runnable() { - @Override - public void run() { - // For some reason the fancy way doesn't compile... -// maxScaleX.getAndAccumulate(mirrorView.getScaleX(), Math::max); - final double oldMax = maxScaleX.get(); - final double newMax = Math.max(mirrorView.getScaleX(), oldMax); - assertTrue(maxScaleX.compareAndSet(oldMax, newMax)); - - if (SystemClock.uptimeMillis() < timeout) { - mirrorView.postOnAnimation(this); - } - } - }; - mirrorView.postOnAnimation(onAnimationFrame); - - waitForIdleSync(); + advanceTimeBy(mWaitBounceEffectDuration, /* runnableOnEachRefresh= */ () -> { + // For some reason the fancy way doesn't compile... + // maxScaleX.getAndAccumulate(mirrorView.getScaleX(), Math::max); + final double oldMax = maxScaleX.get(); + final double newMax = Math.max(mirrorView.getScaleX(), oldMax); + assertTrue(maxScaleX.compareAndSet(oldMax, newMax)); + }); - ReferenceTestUtils.waitForCondition(() -> maxScaleX.get() > 1.0); + assertTrue(maxScaleX.get() > 1.0); } @Test @@ -1455,4 +1451,23 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { return newRotation; } + // advance time based on the device frame refresh rate + private void advanceTimeBy(long timeDelta) { + advanceTimeBy(timeDelta, /* runnableOnEachRefresh= */ null); + } + + // advance time based on the device frame refresh rate, and trigger runnable on each refresh + private void advanceTimeBy(long timeDelta, @Nullable Runnable runnableOnEachRefresh) { + final float frameRate = mContext.getDisplay().getRefreshRate(); + final int timeSlot = (int) (1000 / frameRate); + int round = (int) Math.ceil((double) timeDelta / timeSlot); + for (; round >= 0; round--) { + mInstrumentation.runOnMainSync(() -> { + mAnimatorTestRule.advanceTimeBy(timeSlot); + if (runnableOnEachRefresh != null) { + runnableOnEachRefresh.run(); + } + }); + } + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/common/ui/view/LongPressHandlingViewInteractionHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/common/ui/view/LongPressHandlingViewInteractionHandlerTest.kt index 1b2fc93ddb3f..2f4fc96ebf6c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/common/ui/view/LongPressHandlingViewInteractionHandlerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/common/ui/view/LongPressHandlingViewInteractionHandlerTest.kt @@ -67,6 +67,7 @@ class LongPressHandlingViewInteractionHandlerTest : SysuiTestCase() { isAttachedToWindow = { isAttachedToWindow }, onLongPressDetected = onLongPressDetected, onSingleTapDetected = onSingleTapDetected, + longPressDuration = { ViewConfiguration.getLongPressTimeout().toLong() } ) underTest.isLongPressHandlingEnabled = true } diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/SetFlagsRuleExtensions.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/SetFlagsRuleExtensions.kt index e16b8d400149..f4d2cfd837f0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/flags/SetFlagsRuleExtensions.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/flags/SetFlagsRuleExtensions.kt @@ -19,15 +19,15 @@ package com.android.systemui.flags import android.platform.test.flag.junit.SetFlagsRule /** - * Set the given flag's value to the real value for the current build configuration. - * This prevents test code from crashing because it is reading an unspecified flag value. + * Set the given flag's value to the real value for the current build configuration. This prevents + * test code from crashing because it is reading an unspecified flag value. * - * REMINDER: You should always test your code with your flag in both configurations, so - * generally you should be explicitly enabling or disabling your flag. This method is 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. + * REMINDER: You should always test your code with your flag in both configurations, so generally + * you should be explicitly enabling or disabling your flag. This method is 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 SetFlagsRule.setFlagDefault(flagName: String) { if (getFlagDefault(flagName)) { @@ -37,6 +37,19 @@ fun SetFlagsRule.setFlagDefault(flagName: String) { } } +/** + * Set the given flag to an explicit value, or, if null, to the real value for the current build + * configuration. This allows for convenient provisioning in tests where certain tests don't care + * what the value is (`setFlagValue(FLAG_FOO, null)`), and others want an explicit value. + */ +fun SetFlagsRule.setFlagValue(name: String, value: Boolean?) { + when (value) { + null -> setFlagDefault(name) + true -> enableFlags(name) + false -> disableFlags(name) + } +} + // NOTE: This code uses reflection to gain access to private members of aconfig generated // classes (in the same way SetFlagsRule does internally) because this is the only way to get // at the underlying information and read the current value of the flag. diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigProviderTest.kt new file mode 100644 index 000000000000..682b2d0d3983 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigProviderTest.kt @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.viewmodel + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.qs.pipeline.shared.TileSpec +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class QSTileConfigProviderTest : SysuiTestCase() { + + private val underTest = + QSTileConfigProviderImpl( + mapOf(VALID_SPEC.spec to QSTileConfigTestBuilder.build { tileSpec = VALID_SPEC }) + ) + + @Test + fun providerReturnsConfig() { + assertThat(underTest.getConfig(VALID_SPEC.spec)).isNotNull() + } + + @Test(expected = IllegalArgumentException::class) + fun throwsForInvalidSpec() { + underTest.getConfig(INVALID_SPEC.spec) + } + + @Test(expected = IllegalArgumentException::class) + fun validatesSpecUponCreation() { + QSTileConfigProviderImpl( + mapOf(VALID_SPEC.spec to QSTileConfigTestBuilder.build { tileSpec = INVALID_SPEC }) + ) + } + + private companion object { + + val VALID_SPEC = TileSpec.create("valid_tile_spec") + val INVALID_SPEC = TileSpec.create("invalid_tile_spec") + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelInterfaceComplianceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelInterfaceComplianceTest.kt index 1a4553558e28..9bf4a759a1f2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelInterfaceComplianceTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelInterfaceComplianceTest.kt @@ -20,12 +20,10 @@ import android.os.UserHandle import android.testing.TestableLooper import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.MediumTest -import com.android.internal.logging.InstanceId import com.android.systemui.SysuiTestCase import com.android.systemui.classifier.FalsingManagerFake import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon -import com.android.systemui.qs.pipeline.shared.TileSpec import com.android.systemui.qs.tiles.base.analytics.QSTileAnalytics import com.android.systemui.qs.tiles.base.interactor.FakeDisabledByPolicyInteractor import com.android.systemui.qs.tiles.base.interactor.FakeQSTileDataInteractor @@ -93,7 +91,7 @@ class QSTileViewModelInterfaceComplianceTest : SysuiTestCase() { config: QSTileConfig = TEST_QS_TILE_CONFIG, ): QSTileViewModel = QSTileViewModelImpl( - { config }, + config, { fakeQSTileUserActionInteractor }, { fakeQSTileDataInteractor }, { @@ -114,12 +112,6 @@ class QSTileViewModelInterfaceComplianceTest : SysuiTestCase() { private companion object { - val TEST_QS_TILE_CONFIG = - QSTileConfig( - TileSpec.create("default"), - Icon.Resource(0, null), - 0, - InstanceId.fakeInstanceId(0), - ) + val TEST_QS_TILE_CONFIG = QSTileConfigTestBuilder.build {} } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java index 6223e250d603..2ce4b04b037a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java @@ -84,6 +84,7 @@ import com.android.systemui.biometrics.AuthController; import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor; import com.android.systemui.classifier.FalsingCollectorFake; import com.android.systemui.classifier.FalsingManagerFake; +import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository; import com.android.systemui.common.ui.view.LongPressHandlingView; import com.android.systemui.doze.DozeLog; import com.android.systemui.dump.DumpManager; @@ -120,6 +121,8 @@ import com.android.systemui.plugins.qs.QS; import com.android.systemui.power.domain.interactor.PowerInteractor; import com.android.systemui.qs.QSFragmentLegacy; import com.android.systemui.res.R; +import com.android.systemui.scene.SceneTestUtils; +import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags; import com.android.systemui.screenrecord.RecordingController; import com.android.systemui.shade.data.repository.FakeShadeRepository; import com.android.systemui.shade.data.repository.ShadeRepository; @@ -137,6 +140,7 @@ import com.android.systemui.statusbar.QsFrameTranslateController; import com.android.systemui.statusbar.StatusBarStateControllerImpl; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.VibratorHelper; +import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository; import com.android.systemui.statusbar.notification.ConversationNotificationManager; import com.android.systemui.statusbar.notification.DynamicPrivacyController; import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; @@ -168,6 +172,7 @@ import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager; import com.android.systemui.statusbar.phone.TapAgainViewController; import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController; +import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository; import com.android.systemui.statusbar.policy.CastController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.HeadsUpManager; @@ -176,8 +181,10 @@ import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.KeyguardUserSwitcherController; import com.android.systemui.statusbar.policy.KeyguardUserSwitcherView; import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController; +import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository; import com.android.systemui.statusbar.window.StatusBarWindowStateController; import com.android.systemui.unfold.SysUIUnfoldComponent; +import com.android.systemui.user.domain.interactor.UserSwitcherInteractor; import com.android.systemui.util.kotlin.JavaAdapter; import com.android.systemui.util.time.FakeSystemClock; import com.android.systemui.util.time.SystemClock; @@ -197,6 +204,8 @@ import java.util.List; import java.util.Optional; import kotlinx.coroutines.CoroutineDispatcher; +import kotlinx.coroutines.flow.StateFlowKt; +import kotlinx.coroutines.test.TestScope; public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { @@ -324,7 +333,6 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { mEmptySpaceClickListenerCaptor; @Mock protected ActivityStarter mActivityStarter; @Mock protected KeyguardFaceAuthInteractor mKeyguardFaceAuthInteractor; - @Mock private ShadeInteractor mShadeInteractor; @Mock private JavaAdapter mJavaAdapter; @Mock private CastController mCastController; @Mock private KeyguardRootView mKeyguardRootView; @@ -335,6 +343,9 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { protected KeyguardBottomAreaInteractor mKeyguardBottomAreaInteractor; protected FakeKeyguardRepository mFakeKeyguardRepository; protected KeyguardInteractor mKeyguardInteractor; + protected SceneTestUtils mUtils = new SceneTestUtils(this); + protected TestScope mTestScope = mUtils.getTestScope(); + protected ShadeInteractor mShadeInteractor; protected PowerInteractor mPowerInteractor; protected NotificationPanelViewController.TouchHandler mTouchHandler; protected ConfigurationController mConfigurationController; @@ -370,10 +381,31 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { mKeyguardInteractor = keyguardInteractorDeps.getKeyguardInteractor(); mShadeRepository = new FakeShadeRepository(); mPowerInteractor = keyguardInteractorDeps.getPowerInteractor(); + when(mKeyguardTransitionInteractor.isInTransitionToStateWhere(any())).thenReturn( + StateFlowKt.MutableStateFlow(false)); + mShadeInteractor = new ShadeInteractor( + mTestScope.getBackgroundScope(), + new FakeDeviceProvisioningRepository(), + new FakeDisableFlagsRepository(), + mDozeParameters, + new FakeSceneContainerFlags(), + mUtils::sceneInteractor, + mFakeKeyguardRepository, + mKeyguardTransitionInteractor, + mPowerInteractor, + new FakeUserSetupRepository(), + mock(UserSwitcherInteractor.class), + new SharedNotificationContainerInteractor( + new FakeConfigurationRepository(), + mContext, + new ResourcesSplitShadeStateController() + ), + mShadeRepository + ); SystemClock systemClock = new FakeSystemClock(); - mStatusBarStateController = new StatusBarStateControllerImpl(mUiEventLogger, mDumpManager, - mInteractionJankMonitor, mShadeExpansionStateManager); + mStatusBarStateController = new StatusBarStateControllerImpl(mUiEventLogger, + mInteractionJankMonitor, mJavaAdapter, () -> mShadeInteractor); KeyguardStatusView keyguardStatusView = new KeyguardStatusView(mContext); keyguardStatusView.setId(R.id.keyguard_status_view); @@ -532,8 +564,9 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { new NotificationWakeUpCoordinator( mDumpManager, mock(HeadsUpManager.class), - new StatusBarStateControllerImpl(new UiEventLoggerFake(), mDumpManager, - mInteractionJankMonitor, mShadeExpansionStateManager), + new StatusBarStateControllerImpl(new UiEventLoggerFake(), + mInteractionJankMonitor, + mJavaAdapter, () -> mShadeInteractor), mKeyguardBypassController, mDozeParameters, mScreenOffAnimationController, diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java index 7931e9ea5d7b..2f45b1260dc2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java @@ -187,8 +187,8 @@ public class QuickSettingsControllerBaseTest extends SysuiTestCase { public void setup() { MockitoAnnotations.initMocks(this); when(mPanelViewControllerLazy.get()).thenReturn(mNotificationPanelViewController); - mStatusBarStateController = new StatusBarStateControllerImpl(mUiEventLogger, mDumpManager, - mInteractionJankMonitor, mShadeExpansionStateManager); + mStatusBarStateController = new StatusBarStateControllerImpl(mUiEventLogger, + mInteractionJankMonitor, mock(JavaAdapter.class), () -> mShadeInteractor); FakeDeviceProvisioningRepository deviceProvisioningRepository = new FakeDeviceProvisioningRepository(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java index 8f06e636b479..6eabf4412474 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java @@ -145,22 +145,15 @@ public class PluginInstanceTest extends SysuiTestCase { } @Test - public void testOnRepeatedlyLoadUnload_PluginFreed() { + public void testOnUnloadAfterLoad() { mPluginInstance.onCreate(); mPluginInstance.loadPlugin(); + assertNotNull(mPluginInstance.getPlugin()); assertInstances(1, 1); mPluginInstance.unloadPlugin(); assertNull(mPluginInstance.getPlugin()); assertInstances(0, 0); - - mPluginInstance.loadPlugin(); - assertInstances(1, 1); - - mPluginInstance.unloadPlugin(); - mPluginInstance.onDestroy(); - assertNull(mPluginInstance.getPlugin()); - assertInstances(0, 0); } @Test @@ -169,7 +162,7 @@ public class PluginInstanceTest extends SysuiTestCase { mPluginInstance.onCreate(); assertEquals(1, mPluginListener.mAttachedCount); assertEquals(0, mPluginListener.mLoadCount); - assertEquals(null, mPluginInstance.getPlugin()); + assertNull(mPluginInstance.getPlugin()); assertInstances(0, 0); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java index 2b3fd34cedbf..a4c12f6d6b71 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java @@ -16,6 +16,8 @@ package com.android.systemui.statusbar; +import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.anyInt; @@ -37,12 +39,11 @@ import android.testing.AndroidTestingRunner; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; -import com.android.systemui.flags.FakeFeatureFlagsClassic; -import com.android.systemui.flags.Flags; import com.android.systemui.plugins.PluginManager; import com.android.systemui.statusbar.NotificationListener.NotificationHandler; import com.android.systemui.statusbar.data.repository.NotificationListenerSettingsRepository; import com.android.systemui.statusbar.domain.interactor.SilentNotificationStatusIconsVisibilityInteractor; +import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor; import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.time.FakeSystemClock; @@ -71,12 +72,9 @@ public class NotificationListenerTest extends SysuiTestCase { @Before public void setUp() { MockitoAnnotations.initMocks(this); - - FakeFeatureFlagsClassic featureFlags = new FakeFeatureFlagsClassic(); - featureFlags.setDefault(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR); + setFlagDefault(mSetFlagsRule, NotificationIconContainerRefactor.FLAG_NAME); mListener = new NotificationListener( mContext, - featureFlags, mNotificationManager, new SilentNotificationStatusIconsVisibilityInteractor( new NotificationListenerSettingsRepository()), diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java index 764f7b6b8887..560ebc6c3d98 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java @@ -33,9 +33,9 @@ import android.testing.TestableLooper; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; -import com.android.systemui.dump.DumpManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.power.domain.interactor.PowerInteractor; +import com.android.systemui.shade.domain.interactor.ShadeInteractor; import com.android.systemui.statusbar.notification.NotifPipelineFlags; import com.android.systemui.statusbar.notification.RemoteInputControllerLogger; import com.android.systemui.statusbar.notification.collection.NotificationEntry; @@ -43,6 +43,7 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntryB import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.policy.RemoteInputUriController; +import com.android.systemui.util.kotlin.JavaAdapter; import org.junit.Before; import org.junit.Test; @@ -87,7 +88,8 @@ public class NotificationRemoteInputManagerTest extends SysuiTestCase { new RemoteInputControllerLogger(logcatLogBuffer()), mClickNotifier, new ActionClickLogger(logcatLogBuffer()), - mock(DumpManager.class)); + mock(JavaAdapter.class), + mock(ShadeInteractor.class)); mEntry = new NotificationEntryBuilder() .setPkg(TEST_PACKAGE_NAME) .setOpPkg(TEST_PACKAGE_NAME) @@ -145,7 +147,8 @@ public class NotificationRemoteInputManagerTest extends SysuiTestCase { RemoteInputControllerLogger remoteInputControllerLogger, NotificationClickNotifier clickNotifier, ActionClickLogger actionClickLogger, - DumpManager dumpManager) { + JavaAdapter javaAdapter, + ShadeInteractor shadeInteractor) { super( context, notifPipelineFlags, @@ -158,7 +161,8 @@ public class NotificationRemoteInputManagerTest extends SysuiTestCase { remoteInputControllerLogger, clickNotifier, actionClickLogger, - dumpManager); + javaAdapter, + shadeInteractor); } public void setUpWithPresenterForTest(Callback callback, @@ -170,3 +174,4 @@ public class NotificationRemoteInputManagerTest extends SysuiTestCase { } } + diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt index 3327e42b930f..d6dfc5e928a7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt @@ -23,9 +23,30 @@ import androidx.test.filters.SmallTest import com.android.internal.jank.InteractionJankMonitor import com.android.internal.logging.testing.UiEventLoggerFake import com.android.systemui.SysuiTestCase -import com.android.systemui.dump.DumpManager +import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository +import com.android.systemui.classifier.FalsingCollectorFake +import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository +import com.android.systemui.flags.FakeFeatureFlagsClassic +import com.android.systemui.keyguard.data.repository.FakeCommandQueue +import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository +import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository +import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.plugins.statusbar.StatusBarStateController -import com.android.systemui.shade.ShadeExpansionStateManager +import com.android.systemui.power.data.repository.FakePowerRepository +import com.android.systemui.power.domain.interactor.PowerInteractor +import com.android.systemui.scene.SceneTestUtils +import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags +import com.android.systemui.shade.data.repository.FakeShadeRepository +import com.android.systemui.shade.domain.interactor.ShadeInteractor +import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository +import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor +import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository +import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController +import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository +import com.android.systemui.util.mockito.mock import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue @@ -40,17 +61,22 @@ import org.mockito.Mock import org.mockito.Mockito import org.mockito.Mockito.mock import org.mockito.Mockito.verify -import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations +import org.mockito.Mockito.`when` as whenever @SmallTest @RunWith(AndroidTestingRunner::class) @TestableLooper.RunWithLooper class StatusBarStateControllerImplTest : SysuiTestCase() { + private val utils = SceneTestUtils(this) + private val testScope = utils.testScope + private lateinit var shadeInteractor: ShadeInteractor + private lateinit var fromLockscreenTransitionInteractor: FromLockscreenTransitionInteractor + private lateinit var fromPrimaryBouncerTransitionInteractor: + FromPrimaryBouncerTransitionInteractor @Mock lateinit var interactionJankMonitor: InteractionJankMonitor - @Mock private lateinit var mockDarkAnimator: ObjectAnimator - @Mock private lateinit var shadeExpansionStateManager: ShadeExpansionStateManager + @Mock lateinit var mockDarkAnimator: ObjectAnimator private lateinit var controller: StatusBarStateControllerImpl private lateinit var uiEventLogger: UiEventLoggerFake @@ -64,11 +90,75 @@ class StatusBarStateControllerImplTest : SysuiTestCase() { uiEventLogger = UiEventLoggerFake() controller = object : StatusBarStateControllerImpl( uiEventLogger, - mock(DumpManager::class.java), - interactionJankMonitor, shadeExpansionStateManager + interactionJankMonitor, + mock(), + { shadeInteractor } ) { override fun createDarkAnimator(): ObjectAnimator { return mockDarkAnimator } } + + val powerInteractor = PowerInteractor( + FakePowerRepository(), + FalsingCollectorFake(), + mock(), + controller) + val keyguardRepository = FakeKeyguardRepository() + val keyguardTransitionRepository = FakeKeyguardTransitionRepository() + val featureFlags = FakeFeatureFlagsClassic() + val shadeRepository = FakeShadeRepository() + val sceneContainerFlags = FakeSceneContainerFlags() + val configurationRepository = FakeConfigurationRepository() + val keyguardInteractor = KeyguardInteractor( + keyguardRepository, + FakeCommandQueue(), + powerInteractor, + featureFlags, + sceneContainerFlags, + FakeKeyguardBouncerRepository(), + configurationRepository, + shadeRepository, + utils::sceneInteractor) + val keyguardTransitionInteractor = KeyguardTransitionInteractor( + testScope.backgroundScope, + keyguardTransitionRepository, + { keyguardInteractor }, + { fromLockscreenTransitionInteractor }, + { fromPrimaryBouncerTransitionInteractor }) + fromLockscreenTransitionInteractor = FromLockscreenTransitionInteractor( + keyguardTransitionRepository, + keyguardTransitionInteractor, + testScope.backgroundScope, + keyguardInteractor, + featureFlags, + shadeRepository, + powerInteractor) + fromPrimaryBouncerTransitionInteractor = FromPrimaryBouncerTransitionInteractor( + keyguardTransitionRepository, + keyguardTransitionInteractor, + testScope.backgroundScope, + keyguardInteractor, + featureFlags, + mock(), + mock(), + powerInteractor) + shadeInteractor = ShadeInteractor( + testScope.backgroundScope, + FakeDeviceProvisioningRepository(), + FakeDisableFlagsRepository(), + mock(), + sceneContainerFlags, + utils::sceneInteractor, + keyguardRepository, + keyguardTransitionInteractor, + powerInteractor, + FakeUserSetupRepository(), + mock(), + SharedNotificationContainerInteractor( + configurationRepository, + mContext, + ResourcesSplitShadeStateController()), + shadeRepository, + ) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt index a736182b6329..e81207e096e3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt @@ -19,8 +19,7 @@ import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.flags.FakeFeatureFlagsClassic -import com.android.systemui.flags.Flags +import com.android.systemui.flags.setFlagValue import com.android.systemui.statusbar.notification.collection.NotifPipeline import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder @@ -30,6 +29,7 @@ import com.android.systemui.statusbar.notification.collection.render.GroupExpans import com.android.systemui.statusbar.notification.collection.render.NotifStackController import com.android.systemui.statusbar.notification.collection.render.NotifStats import com.android.systemui.statusbar.notification.domain.interactor.RenderNotificationListInteractor +import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor import com.android.systemui.statusbar.notification.stack.BUCKET_ALERTING import com.android.systemui.statusbar.notification.stack.BUCKET_SILENT import com.android.systemui.statusbar.phone.NotificationIconAreaController @@ -39,6 +39,7 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock +import org.mockito.Mockito.reset import org.mockito.Mockito.verify import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations.initMocks @@ -59,15 +60,18 @@ class StackCoordinatorTest : SysuiTestCase() { @Mock private lateinit var stackController: NotifStackController @Mock private lateinit var section: NotifSection - val featureFlags = - FakeFeatureFlagsClassic().apply { setDefault(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR) } - @Before fun setUp() { initMocks(this) + setUp(NotificationIconContainerRefactor.FLAG_NAME to null) + entry = NotificationEntryBuilder().setSection(section).build() + } + + private fun setUp(vararg flags: Pair<String, Boolean?>) { + flags.forEach { (name, value) -> mSetFlagsRule.setFlagValue(name, value) } + reset(pipeline) coordinator = StackCoordinator( - featureFlags, groupExpansionManagerImpl, notificationIconAreaController, renderListInteractor, @@ -76,19 +80,18 @@ class StackCoordinatorTest : SysuiTestCase() { afterRenderListListener = withArgCaptor { verify(pipeline).addOnAfterRenderListListener(capture()) } - entry = NotificationEntryBuilder().setSection(section).build() } @Test fun testUpdateNotificationIcons() { - featureFlags.set(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR, false) + setUp(NotificationIconContainerRefactor.FLAG_NAME to false) afterRenderListListener.onAfterRenderList(listOf(entry), stackController) verify(notificationIconAreaController).updateNotificationIcons(eq(listOf(entry))) } @Test fun testSetRenderedListOnInteractor() { - featureFlags.set(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR, true) + setUp(NotificationIconContainerRefactor.FLAG_NAME to true) afterRenderListListener.onAfterRenderList(listOf(entry), stackController) verify(renderListInteractor).setRenderedList(eq(listOf(entry))) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java index 2b944c345643..39e3d5da3318 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java @@ -92,10 +92,8 @@ import com.android.systemui.statusbar.notification.row.NotificationGutsManager; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController.NotificationPanelEvent; import com.android.systemui.statusbar.notification.stack.NotificationSwipeHelper.NotificationCallback; import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationListViewModel; -import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.NotificationIconAreaController; -import com.android.systemui.statusbar.phone.ScreenOffAnimationController; import com.android.systemui.statusbar.phone.ScrimController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; @@ -722,8 +720,6 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { mActivityStarter, new ResourcesSplitShadeStateController(), mock(ConfigurationState.class), - mock(DozeParameters.class), - mock(ScreenOffAnimationController.class), mock(ShelfNotificationIconViewStore.class)); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java index 41eaf858f27a..6478a3e9894b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java @@ -21,6 +21,7 @@ import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK; import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN; import static android.app.StatusBarManager.WINDOW_STATE_SHOWING; +import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault; import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; import static com.android.systemui.statusbar.StatusBarState.SHADE; @@ -163,6 +164,7 @@ import com.android.systemui.statusbar.notification.interruption.KeyguardNotifica import com.android.systemui.statusbar.notification.interruption.NotificationInterruptLogger; import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; +import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment; @@ -185,8 +187,6 @@ import com.android.systemui.volume.VolumeComponent; import com.android.wm.shell.bubbles.Bubbles; import com.android.wm.shell.startingsurface.StartingSurface; -import dagger.Lazy; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -200,6 +200,8 @@ import java.util.Optional; import javax.inject.Provider; +import dagger.Lazy; + @SmallTest @RunWith(AndroidTestingRunner.class) @RunWithLooper(setAsMainLooper = true) @@ -335,7 +337,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { mFeatureFlags.set(Flags.WM_ENABLE_PREDICTIVE_BACK_SYSUI, false); // Set default value to avoid IllegalStateException. mFeatureFlags.set(Flags.SHORTCUT_LIST_SEARCH_LAYOUT, false); - mFeatureFlags.setDefault(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR); + setFlagDefault(mSetFlagsRule, NotificationIconContainerRefactor.FLAG_NAME); // For the Shade to respond to Back gesture, we must enable the event routing mFeatureFlags.set(Flags.WM_SHADE_ALLOW_BACK_GESTURE, true); // For the Shade to animate during the Back gesture, we must enable the animation flag. diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java index 472709cd622e..19215e3ca336 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.phone; +import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault; + import static org.junit.Assert.assertFalse; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; @@ -43,7 +45,6 @@ import com.android.systemui.biometrics.AuthController; import com.android.systemui.doze.DozeHost; import com.android.systemui.doze.DozeLog; import com.android.systemui.flags.FakeFeatureFlagsClassic; -import com.android.systemui.flags.Flags; import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.keyguard.domain.interactor.DozeInteractor; import com.android.systemui.shade.NotificationShadeWindowViewController; @@ -53,6 +54,7 @@ import com.android.systemui.statusbar.PulseExpansionHandler; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.StatusBarStateControllerImpl; import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; +import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor; import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.HeadsUpManager; @@ -105,7 +107,7 @@ public class DozeServiceHostTest extends SysuiTestCase { @Before public void setup() { MockitoAnnotations.initMocks(this); - mFeatureFlags.setDefault(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR); + setFlagDefault(mSetFlagsRule, NotificationIconContainerRefactor.FLAG_NAME); mDozeServiceHost = new DozeServiceHost(mDozeLog, mPowerManager, mWakefullnessLifecycle, mStatusBarStateController, mDeviceProvisionedController, mFeatureFlags, mHeadsUpManager, mBatteryController, mScrimController, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java index 529e2c9a8e7e..1fad2a2861a5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.phone; +import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; @@ -35,7 +37,6 @@ import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; import com.android.systemui.flags.FakeFeatureFlagsClassic; -import com.android.systemui.flags.Flags; import com.android.systemui.plugins.DarkIconDispatcher; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.shade.ShadeHeadsUpTracker; @@ -47,6 +48,7 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationIconInteractor; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.NotificationTestHelper; +import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor; import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; import com.android.systemui.statusbar.policy.Clock; @@ -90,7 +92,7 @@ public class HeadsUpAppearanceControllerTest extends SysuiTestCase { @Before public void setUp() throws Exception { allowTestableLooperAsMainThread(); - mFeatureFlags.setDefault(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR); + setFlagDefault(mSetFlagsRule, NotificationIconContainerRefactor.FLAG_NAME); mTestHelper = new NotificationTestHelper( mContext, mDependency, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java index cda2a74609bd..48b95d407246 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java @@ -34,7 +34,7 @@ import androidx.test.filters.SmallTest; import com.android.internal.logging.UiEventLogger; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.res.R; -import com.android.systemui.shade.ShadeExpansionStateManager; +import com.android.systemui.shade.domain.interactor.ShadeInteractor; import com.android.systemui.statusbar.AlertingNotificationManager; import com.android.systemui.statusbar.AlertingNotificationManagerTest; import com.android.systemui.statusbar.NotificationShadeWindowController; @@ -45,6 +45,7 @@ import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.HeadsUpManagerLogger; +import com.android.systemui.util.kotlin.JavaAdapter; import org.junit.After; import org.junit.Before; @@ -56,6 +57,8 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; +import kotlinx.coroutines.flow.StateFlowKt; + @SmallTest @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper @@ -70,8 +73,9 @@ public class HeadsUpManagerPhoneTest extends AlertingNotificationManagerTest { @Mock private KeyguardBypassController mBypassController; @Mock private ConfigurationControllerImpl mConfigurationController; @Mock private AccessibilityManagerWrapper mAccessibilityManagerWrapper; - @Mock private ShadeExpansionStateManager mShadeExpansionStateManager; @Mock private UiEventLogger mUiEventLogger; + @Mock private JavaAdapter mJavaAdapter; + @Mock private ShadeInteractor mShadeInteractor; private static final class TestableHeadsUpManagerPhone extends HeadsUpManagerPhone { TestableHeadsUpManagerPhone( @@ -85,7 +89,8 @@ public class HeadsUpManagerPhoneTest extends AlertingNotificationManagerTest { Handler handler, AccessibilityManagerWrapper accessibilityManagerWrapper, UiEventLogger uiEventLogger, - ShadeExpansionStateManager shadeExpansionStateManager + JavaAdapter javaAdapter, + ShadeInteractor shadeInteractor ) { super( context, @@ -98,7 +103,8 @@ public class HeadsUpManagerPhoneTest extends AlertingNotificationManagerTest { handler, accessibilityManagerWrapper, uiEventLogger, - shadeExpansionStateManager + javaAdapter, + shadeInteractor ); mMinimumDisplayTime = TEST_MINIMUM_DISPLAY_TIME; mAutoDismissNotificationDecay = TEST_AUTO_DISMISS_TIME; @@ -117,7 +123,8 @@ public class HeadsUpManagerPhoneTest extends AlertingNotificationManagerTest { mTestHandler, mAccessibilityManagerWrapper, mUiEventLogger, - mShadeExpansionStateManager + mJavaAdapter, + mShadeInteractor ); } @@ -129,6 +136,7 @@ public class HeadsUpManagerPhoneTest extends AlertingNotificationManagerTest { @Before @Override public void setUp() { + when(mShadeInteractor.isAnyExpanded()).thenReturn(StateFlowKt.MutableStateFlow(false)); final AccessibilityManagerWrapper accessibilityMgr = mDependency.injectMockDependency(AccessibilityManagerWrapper.class); when(accessibilityMgr.getRecommendedTimeoutMillis(anyInt(), anyInt())) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImplTest.java index 1b8cfd43e6b6..92e40dfe2c46 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImplTest.java @@ -15,6 +15,8 @@ */ package com.android.systemui.statusbar.phone; +import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault; + import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.verify; @@ -28,12 +30,14 @@ import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; import com.android.systemui.demomode.DemoModeController; import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.flags.SetFlagsRuleExtensionsKt; import com.android.systemui.plugins.DarkIconDispatcher; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.NotificationListener; import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider; +import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor; import com.android.systemui.statusbar.window.StatusBarWindowController; import com.android.wm.shell.bubbles.Bubbles; @@ -82,6 +86,7 @@ public class LegacyNotificationIconAreaControllerImplTest extends SysuiTestCase @Before public void setup() { MockitoAnnotations.initMocks(this); + mSetFlagsRule.disableFlags(NotificationIconContainerRefactor.FLAG_NAME); mController = new LegacyNotificationIconAreaControllerImpl( mContext, mStatusBarStateController, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt index c282c1ef0cf6..2b28562c4dd5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt @@ -20,12 +20,15 @@ import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.flags.setFlagDefault import com.android.systemui.statusbar.StatusBarIconView import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN +import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor import junit.framework.Assert.assertEquals import junit.framework.Assert.assertFalse import junit.framework.Assert.assertTrue +import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.mock @@ -41,6 +44,11 @@ class NotificationIconContainerTest : SysuiTestCase() { private val iconContainer = NotificationIconContainer(context, /* attrs= */ null) + @Before + fun setup() { + mSetFlagsRule.setFlagDefault(NotificationIconContainerRefactor.FLAG_NAME) + } + @Test fun calculateWidthFor_zeroIcons_widthIsZero() { assertEquals(/* expected= */ iconContainer.calculateWidthFor(/* numIcons= */ 0f), diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java index ed9996138a14..d1b9b8aae70e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java @@ -60,10 +60,8 @@ import com.android.systemui.statusbar.disableflags.DisableFlagsLogger; import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler; import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarNotificationIconViewStore; import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerStatusBarViewModel; -import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.HeadsUpAppearanceController; import com.android.systemui.statusbar.phone.NotificationIconAreaController; -import com.android.systemui.statusbar.phone.ScreenOffAnimationController; import com.android.systemui.statusbar.phone.StatusBarHideIconsForBouncerManager; import com.android.systemui.statusbar.phone.StatusBarIconController; import com.android.systemui.statusbar.phone.StatusBarLocationPublisher; @@ -720,8 +718,6 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { mock(NotificationIconContainerStatusBarViewModel.class), mock(ConfigurationState.class), mock(ConfigurationController.class), - mock(DozeParameters.class), - mock(ScreenOffAnimationController.class), mock(StatusBarNotificationIconViewStore.class), mock(DemoModeController.class)); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/VariableDateViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/VariableDateViewControllerTest.kt index b78e83990411..63de0685ea96 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/VariableDateViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/VariableDateViewControllerTest.kt @@ -23,26 +23,27 @@ import android.view.View import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.broadcast.BroadcastDispatcher -import com.android.systemui.shade.ShadeExpansionStateManager +import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.capture import com.android.systemui.util.mockito.mock import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.flow.MutableStateFlow import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor import org.mockito.Captor import org.mockito.Mock -import org.mockito.Mockito.`when` import org.mockito.Mockito.anyString import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations import java.util.Date @RunWith(AndroidTestingRunner::class) -@TestableLooper.RunWithLooper +@TestableLooper.RunWithLooper(setAsMainLooper = true) @SmallTest class VariableDateViewControllerTest : SysuiTestCase() { @@ -57,12 +58,15 @@ class VariableDateViewControllerTest : SysuiTestCase() { private lateinit var broadcastDispatcher: BroadcastDispatcher @Mock private lateinit var view: VariableDateView + @Mock + private lateinit var shadeInteractor: ShadeInteractor @Captor private lateinit var onMeasureListenerCaptor: ArgumentCaptor<VariableDateView.OnMeasureListener> + private val qsExpansion = MutableStateFlow(0F) + private var lastText: String? = null - private lateinit var shadeExpansionStateManager: ShadeExpansionStateManager private lateinit var systemClock: FakeSystemClock private lateinit var testableLooper: TestableLooper private lateinit var testableHandler: Handler @@ -80,7 +84,7 @@ class VariableDateViewControllerTest : SysuiTestCase() { systemClock = FakeSystemClock() systemClock.setCurrentTimeMillis(TIME_STAMP) - shadeExpansionStateManager = ShadeExpansionStateManager() + `when`(shadeInteractor.qsExpansion).thenReturn(qsExpansion) `when`(view.longerPattern).thenReturn(LONG_PATTERN) `when`(view.shorterPattern).thenReturn(SHORT_PATTERN) @@ -91,6 +95,7 @@ class VariableDateViewControllerTest : SysuiTestCase() { Unit } `when`(view.isAttachedToWindow).thenReturn(true) + `when`(view.viewTreeObserver).thenReturn(mock()) val date = Date(TIME_STAMP) longText = getTextForFormat(date, getFormatFromPattern(LONG_PATTERN)) @@ -103,12 +108,12 @@ class VariableDateViewControllerTest : SysuiTestCase() { } controller = VariableDateViewController( - systemClock, - broadcastDispatcher, - shadeExpansionStateManager, - mock(), - testableHandler, - view + systemClock, + broadcastDispatcher, + shadeInteractor, + mock(), + testableHandler, + view ) controller.init() @@ -180,7 +185,7 @@ class VariableDateViewControllerTest : SysuiTestCase() { @Test fun testQsExpansionTrue_ignoreAtMostMeasureRequests() { - shadeExpansionStateManager.onQsExpansionFractionChanged(0f) + qsExpansion.value = 0f onMeasureListenerCaptor.value.onMeasureAction( getTextLength(shortText).toInt(), @@ -195,7 +200,7 @@ class VariableDateViewControllerTest : SysuiTestCase() { @Test fun testQsExpansionFalse_acceptAtMostMeasureRequests() { - shadeExpansionStateManager.onQsExpansionFractionChanged(1f) + qsExpansion.value = 1f onMeasureListenerCaptor.value.onMeasureAction( getTextLength(shortText).toInt(), diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java index 6ef812be0350..e6e6b7bb6e3e 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java @@ -17,7 +17,6 @@ package com.android.systemui; import static com.android.systemui.Flags.FLAG_EXAMPLE_FLAG; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; @@ -38,11 +37,7 @@ import androidx.core.animation.AndroidXAnimatorIsolationRule; import androidx.test.InstrumentationRegistry; import androidx.test.uiautomator.UiDevice; -import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.broadcast.FakeBroadcastDispatcher; -import com.android.systemui.broadcast.logging.BroadcastDispatcherLogger; -import com.android.systemui.dump.DumpManager; -import com.android.systemui.settings.UserTracker; import org.junit.After; import org.junit.AfterClass; @@ -53,7 +48,6 @@ import org.mockito.Mockito; import java.io.FileInputStream; import java.io.IOException; import java.util.concurrent.ExecutionException; -import java.util.concurrent.Executor; import java.util.concurrent.Future; /** @@ -86,7 +80,7 @@ public abstract class SysuiTestCase { public TestableDependency mDependency; private Instrumentation mRealInstrumentation; - private FakeBroadcastDispatcher mFakeBroadcastDispatcher; + private SysuiTestDependency mSysuiDependency; @Before public void SysuiSetup() throws Exception { @@ -97,18 +91,8 @@ public abstract class SysuiTestCase { // Set the value of a single gantry flag inside the com.android.systemui package to // ensure all flags in that package are faked (and thus require a value to be set). mSetFlagsRule.disableFlags(FLAG_EXAMPLE_FLAG); - - mDependency = SysuiTestDependencyKt.installSysuiTestDependency(mContext); - mFakeBroadcastDispatcher = new FakeBroadcastDispatcher( - mContext, - mContext.getMainExecutor(), - mock(Looper.class), - mock(Executor.class), - mock(DumpManager.class), - mock(BroadcastDispatcherLogger.class), - mock(UserTracker.class), - shouldFailOnLeakedReceiver()); - + mSysuiDependency = new SysuiTestDependency(mContext, shouldFailOnLeakedReceiver()); + mDependency = mSysuiDependency.install(); mRealInstrumentation = InstrumentationRegistry.getInstrumentation(); Instrumentation inst = spy(mRealInstrumentation); when(inst.getContext()).thenAnswer(invocation -> { @@ -120,11 +104,6 @@ public abstract class SysuiTestCase { "SysUI Tests should use SysuiTestCase#getContext or SysuiTestCase#mContext"); }); InstrumentationRegistry.registerInstance(inst, InstrumentationRegistry.getArguments()); - // Many tests end up creating a BroadcastDispatcher. Instead, give them a fake that will - // record receivers registered. They are not actually leaked as they are kept just as a weak - // reference and are never sent to the Context. This will also prevent a real - // BroadcastDispatcher from actually registering receivers. - mDependency.injectTestDependency(BroadcastDispatcher.class, mFakeBroadcastDispatcher); } protected boolean shouldFailOnLeakedReceiver() { @@ -144,8 +123,9 @@ public abstract class SysuiTestCase { } disallowTestableLooperAsMainThread(); mContext.cleanUpReceivers(this.getClass().getSimpleName()); - if (mFakeBroadcastDispatcher != null) { - mFakeBroadcastDispatcher.cleanUpReceivers(this.getClass().getSimpleName()); + FakeBroadcastDispatcher dispatcher = getFakeBroadcastDispatcher(); + if (dispatcher != null) { + dispatcher.cleanUpReceivers(this.getClass().getSimpleName()); } } @@ -172,7 +152,7 @@ public abstract class SysuiTestCase { } public FakeBroadcastDispatcher getFakeBroadcastDispatcher() { - return mFakeBroadcastDispatcher; + return mSysuiDependency.getFakeBroadcastDispatcher(); } public SysuiTestableContext getContext() { diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestDependency.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestDependency.kt index c791f4f70920..d89d7b00592e 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestDependency.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestDependency.kt @@ -1,26 +1,60 @@ package com.android.systemui import android.annotation.SuppressLint -import android.content.Context +import android.os.Looper import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.animation.DialogLaunchAnimator import com.android.systemui.animation.fakeDialogLaunchAnimator +import com.android.systemui.broadcast.BroadcastDispatcher +import com.android.systemui.broadcast.FakeBroadcastDispatcher +import com.android.systemui.broadcast.logging.BroadcastDispatcherLogger +import com.android.systemui.dump.DumpManager +import com.android.systemui.settings.UserTracker import com.android.systemui.statusbar.phone.SystemUIDialogManager +import java.util.concurrent.Executor +import org.mockito.Mockito.mock -@SuppressLint("VisibleForTests") -fun installSysuiTestDependency(context: Context): TestableDependency { - val initializer: SystemUIInitializer = SystemUIInitializerImpl(context) - initializer.init(true) +class SysuiTestDependency( + val context: SysuiTestableContext, + private val shouldFailOnLeakedReceiver: Boolean +) { + var fakeBroadcastDispatcher: FakeBroadcastDispatcher? = null - val dependency = TestableDependency(initializer.sysUIComponent.createDependency()) - Dependency.setInstance(dependency) + @SuppressLint("VisibleForTests") + fun install(): TestableDependency { + val initializer: SystemUIInitializer = SystemUIInitializerImpl(context) + initializer.init(true) - dependency.injectMockDependency(KeyguardUpdateMonitor::class.java) + val dependency = TestableDependency(initializer.sysUIComponent.createDependency()) + Dependency.setInstance(dependency) - // Make sure that all tests on any SystemUIDialog does not crash because this dependency - // is missing (constructing the actual one would throw). - // TODO(b/219008720): Remove this. - dependency.injectMockDependency(SystemUIDialogManager::class.java) - dependency.injectTestDependency(DialogLaunchAnimator::class.java, fakeDialogLaunchAnimator()) - return dependency + dependency.injectMockDependency(KeyguardUpdateMonitor::class.java) + + // Make sure that all tests on any SystemUIDialog does not crash because this dependency + // is missing (constructing the actual one would throw). + // TODO(b/219008720): Remove this. + dependency.injectMockDependency(SystemUIDialogManager::class.java) + dependency.injectTestDependency( + DialogLaunchAnimator::class.java, + fakeDialogLaunchAnimator() + ) + + // Many tests end up creating a BroadcastDispatcher. Instead, give them a fake that will + // record receivers registered. They are not actually leaked as they are kept just as a weak + // reference and are never sent to the Context. This will also prevent a real + // BroadcastDispatcher from actually registering receivers. + fakeBroadcastDispatcher = + FakeBroadcastDispatcher( + context, + context.mainExecutor, + mock(Looper::class.java), + mock(Executor::class.java), + mock(DumpManager::class.java), + mock(BroadcastDispatcherLogger::class.java), + mock(UserTracker::class.java), + shouldFailOnLeakedReceiver + ) + dependency.injectTestDependency(BroadcastDispatcher::class.java, fakeBroadcastDispatcher) + return dependency + } } diff --git a/services/core/java/com/android/server/power/stats/CpuAggregatedPowerStats.java b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/FakeQSTileConfigProvider.kt index fbf692800e52..de72a7dc30d7 100644 --- a/services/core/java/com/android/server/power/stats/CpuAggregatedPowerStats.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/FakeQSTileConfigProvider.kt @@ -14,10 +14,19 @@ * limitations under the License. */ -package com.android.server.power.stats; +package com.android.systemui.qs.tiles.viewmodel -class CpuAggregatedPowerStats extends PowerComponentAggregatedPowerStats { - CpuAggregatedPowerStats(AggregatedPowerStatsConfig.PowerComponent config) { - super(config); +import com.android.systemui.qs.pipeline.shared.TileSpec + +class FakeQSTileConfigProvider : QSTileConfigProvider { + + private val configs: MutableMap<String, QSTileConfig> = mutableMapOf() + + override fun getConfig(tileSpec: String): QSTileConfig = configs.getValue(tileSpec) + + fun putConfig(tileSpec: TileSpec, config: QSTileConfig) { + configs[tileSpec.spec] = config } + + fun removeConfig(tileSpec: TileSpec): QSTileConfig? = configs.remove(tileSpec.spec) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigTestBuilder.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigTestBuilder.kt index 201926df5a5c..2a0ee888db35 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigTestBuilder.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigTestBuilder.kt @@ -16,10 +16,7 @@ package com.android.systemui.qs.tiles.viewmodel -import androidx.annotation.StringRes import com.android.internal.logging.InstanceId -import com.android.systemui.common.shared.model.ContentDescription -import com.android.systemui.common.shared.model.Icon import com.android.systemui.qs.pipeline.shared.TileSpec object QSTileConfigTestBuilder { @@ -29,8 +26,7 @@ object QSTileConfigTestBuilder { class BuildingScope { var tileSpec: TileSpec = TileSpec.create("test_spec") - var tileIcon: Icon = Icon.Resource(0, ContentDescription.Resource(0)) - @StringRes var tileLabel: Int = 0 + var uiConfig: QSTileUIConfig = QSTileUIConfig.Empty var instanceId: InstanceId = InstanceId.fakeInstanceId(0) var metricsSpec: String = tileSpec.spec var policy: QSTilePolicy = QSTilePolicy.NoRestrictions @@ -38,8 +34,7 @@ object QSTileConfigTestBuilder { fun build() = QSTileConfig( tileSpec, - tileIcon, - tileLabel, + uiConfig, instanceId, metricsSpec, policy, diff --git a/rs/java/android/renderscript/ScriptC.java b/rs/java/android/renderscript/ScriptC.java index 1866a9983495..67c2caa338a6 100644 --- a/rs/java/android/renderscript/ScriptC.java +++ b/rs/java/android/renderscript/ScriptC.java @@ -16,9 +16,12 @@ package android.renderscript; +import android.app.compat.CompatChanges; +import android.compat.annotation.ChangeId; +import android.compat.annotation.EnabledAfter; import android.content.res.Resources; +import android.util.Slog; -import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -35,6 +38,15 @@ public class ScriptC extends Script { private static final String TAG = "ScriptC"; /** + * In targetSdkVersion 35 and above, Renderscript's ScriptC stops being supported + * and an exception is thrown when the class is instantiated. + * In targetSdkVersion 34 and below, Renderscript's ScriptC still works. + */ + @ChangeId + @EnabledAfter(targetSdkVersion = 35) + private static final long RENDERSCRIPT_SCRIPTC_DEPRECATION_CHANGE_ID = 297019750L; + + /** * Only intended for use by the generated derived classes. * * @param id @@ -89,7 +101,19 @@ public class ScriptC extends Script { setID(id); } + private static void throwExceptionIfSDKTooHigh() { + String message = + "ScriptC scripts are not supported when targeting an API Level >= 35. Please refer " + + "to https://developer.android.com/guide/topics/renderscript/migration-guide " + + "for proposed alternatives."; + Slog.w(TAG, message); + if (CompatChanges.isChangeEnabled(RENDERSCRIPT_SCRIPTC_DEPRECATION_CHANGE_ID)) { + throw new UnsupportedOperationException(message); + } + } + private static synchronized long internalCreate(RenderScript rs, Resources resources, int resourceID) { + throwExceptionIfSDKTooHigh(); byte[] pgm; int pgmLength; InputStream is = resources.openRawResource(resourceID); @@ -126,6 +150,7 @@ public class ScriptC extends Script { private static synchronized long internalStringCreate(RenderScript rs, String resName, byte[] bitcode) { // Log.v(TAG, "Create script for resource = " + resName); + throwExceptionIfSDKTooHigh(); return rs.nScriptCCreate(resName, RenderScript.getCachePath(), bitcode, bitcode.length); } } diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java index 5af80da894cc..29137167f526 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java @@ -145,6 +145,13 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo /** Flag for intercepting generic motion events. */ static final int FLAG_FEATURE_INTERCEPT_GENERIC_MOTION_EVENTS = 0x00000800; + /** + * Flag for enabling the two-finger triple-tap magnification feature. + * + * @see #setUserAndEnabledFeatures(int, int) + */ + static final int FLAG_FEATURE_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP = 0x00001000; + static final int FEATURES_AFFECTING_MOTION_EVENTS = FLAG_FEATURE_INJECT_MOTION_EVENTS | FLAG_FEATURE_AUTOCLICK diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index d575102bd5e1..a7b532527381 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -2786,6 +2786,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub flags |= AccessibilityInputFilter .FLAG_FEATURE_MAGNIFICATION_SINGLE_FINGER_TRIPLE_TAP; } + if (Flags.enableMagnificationMultipleFingerMultipleTapGesture()) { + if (userState.isMagnificationSingleFingerTripleTapEnabledLocked()) { + flags |= AccessibilityInputFilter + .FLAG_FEATURE_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP; + } + } if (userState.isShortcutMagnificationEnabledLocked()) { flags |= AccessibilityInputFilter.FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER; } @@ -3150,6 +3156,21 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub return false; } + private boolean readMagnificationTwoFingerTripleTapSettingsLocked( + AccessibilityUserState userState) { + final boolean magnificationTwoFingerTripleTapEnabled = Settings.Secure.getIntForUser( + mContext.getContentResolver(), + Settings.Secure.ACCESSIBILITY_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP_ENABLED, + 0, userState.mUserId) == 1; + if ((magnificationTwoFingerTripleTapEnabled + != userState.isMagnificationTwoFingerTripleTapEnabledLocked())) { + userState.setMagnificationTwoFingerTripleTapEnabledLocked( + magnificationTwoFingerTripleTapEnabled); + return true; + } + return false; + } + private boolean readAutoclickEnabledSettingLocked(AccessibilityUserState userState) { final boolean autoclickEnabled = Settings.Secure.getIntForUser( mContext.getContentResolver(), @@ -3385,6 +3406,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub // displays in one display. It's not a real display and there's no input events for it. final ArrayList<Display> displays = getValidDisplayList(); if (userState.isMagnificationSingleFingerTripleTapEnabledLocked() + || userState.isMagnificationTwoFingerTripleTapEnabledLocked() || userState.isShortcutMagnificationEnabledLocked()) { for (int i = 0; i < displays.size(); i++) { final Display display = displays.get(i); @@ -4920,6 +4942,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub private final Uri mMagnificationmSingleFingerTripleTapEnabledUri = Settings.Secure .getUriFor(Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED); + private final Uri mMagnificationTwoFingerTripleTapEnabledUri = Settings.Secure.getUriFor( + Settings.Secure.ACCESSIBILITY_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP_ENABLED); + private final Uri mAutoclickEnabledUri = Settings.Secure.getUriFor( Settings.Secure.ACCESSIBILITY_AUTOCLICK_ENABLED); @@ -4977,6 +5002,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub false, this, UserHandle.USER_ALL); contentResolver.registerContentObserver(mMagnificationmSingleFingerTripleTapEnabledUri, false, this, UserHandle.USER_ALL); + if (Flags.enableMagnificationMultipleFingerMultipleTapGesture()) { + contentResolver.registerContentObserver(mMagnificationTwoFingerTripleTapEnabledUri, + false, this, UserHandle.USER_ALL); + } contentResolver.registerContentObserver(mAutoclickEnabledUri, false, this, UserHandle.USER_ALL); contentResolver.registerContentObserver(mEnabledAccessibilityServicesUri, @@ -5027,6 +5056,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub if (readMagnificationEnabledSettingsLocked(userState)) { onUserStateChangedLocked(userState); } + } else if (Flags.enableMagnificationMultipleFingerMultipleTapGesture() + && mMagnificationTwoFingerTripleTapEnabledUri.equals(uri)) { + if (readMagnificationTwoFingerTripleTapSettingsLocked(userState)) { + onUserStateChangedLocked(userState); + } } else if (mAutoclickEnabledUri.equals(uri)) { if (readAutoclickEnabledSettingLocked(userState)) { onUserStateChangedLocked(userState); diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java index b4efec1a4b38..68ee78076f3d 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java @@ -110,6 +110,7 @@ class AccessibilityUserState { private boolean mIsAudioDescriptionByDefaultRequested; private boolean mIsAutoclickEnabled; private boolean mIsMagnificationSingleFingerTripleTapEnabled; + private boolean mMagnificationTwoFingerTripleTapEnabled; private boolean mIsFilterKeyEventsEnabled; private boolean mIsPerformGesturesEnabled; private boolean mAccessibilityFocusOnlyInActiveWindow; @@ -212,6 +213,7 @@ class AccessibilityUserState { mRequestTwoFingerPassthrough = false; mSendMotionEventsEnabled = false; mIsMagnificationSingleFingerTripleTapEnabled = false; + mMagnificationTwoFingerTripleTapEnabled = false; mIsAutoclickEnabled = false; mUserNonInteractiveUiTimeout = 0; mUserInteractiveUiTimeout = 0; @@ -633,6 +635,14 @@ class AccessibilityUserState { mIsMagnificationSingleFingerTripleTapEnabled = enabled; } + public boolean isMagnificationTwoFingerTripleTapEnabledLocked() { + return mMagnificationTwoFingerTripleTapEnabled; + } + + public void setMagnificationTwoFingerTripleTapEnabledLocked(boolean enabled) { + mMagnificationTwoFingerTripleTapEnabled = enabled; + } + public boolean isFilterKeyEventsEnabledLocked() { return mIsFilterKeyEventsEnabled; } diff --git a/services/core/java/com/android/server/BrickReceiver.java b/services/core/java/com/android/server/BrickReceiver.java deleted file mode 100644 index cff3805b68d2..000000000000 --- a/services/core/java/com/android/server/BrickReceiver.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (C) 2008 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; - -import android.content.Context; -import android.content.Intent; -import android.content.BroadcastReceiver; -import android.os.SystemService; -import android.util.Slog; - -public class BrickReceiver extends BroadcastReceiver { - @Override - public void onReceive(Context context, Intent intent) { - Slog.w("BrickReceiver", "!!! BRICKING DEVICE !!!"); - SystemService.start("brick"); - } -} diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index e88d0c6baf26..210c18d78c23 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -15934,7 +15934,7 @@ public class ActivityManagerService extends IActivityManager.Stub try { sdkSandboxInfo = sandboxManagerLocal.getSdkSandboxApplicationInfoForInstrumentation( - sdkSandboxClientAppInfo, userId, isSdkInSandbox); + sdkSandboxClientAppInfo, isSdkInSandbox); } catch (NameNotFoundException e) { reportStartInstrumentationFailureLocked( watcher, className, "Can't find SdkSandbox package"); diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index 0ab81a5e4eac..9e48b0a2385b 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -121,6 +121,7 @@ import com.android.server.power.stats.AggregatedPowerStatsConfig; import com.android.server.power.stats.BatteryExternalStatsWorker; import com.android.server.power.stats.BatteryStatsImpl; import com.android.server.power.stats.BatteryUsageStatsProvider; +import com.android.server.power.stats.CpuAggregatedPowerStatsProcessor; import com.android.server.power.stats.PowerStatsAggregator; import com.android.server.power.stats.PowerStatsScheduler; import com.android.server.power.stats.PowerStatsStore; @@ -440,7 +441,9 @@ public final class BatteryStatsService extends IBatteryStats.Stub .trackUidStates( AggregatedPowerStatsConfig.STATE_POWER, AggregatedPowerStatsConfig.STATE_SCREEN, - AggregatedPowerStatsConfig.STATE_PROCESS_STATE); + AggregatedPowerStatsConfig.STATE_PROCESS_STATE) + .setProcessor( + new CpuAggregatedPowerStatsProcessor(mPowerProfile, mCpuScalingPolicies)); return config; } diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java index a57a785153e0..439fe0bc13ec 100644 --- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java +++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java @@ -123,6 +123,7 @@ public class SettingsToPropertiesMapper { "angle", "arc_next", "bluetooth", + "build", "biometrics_framework", "biometrics_integration", "camera_platform", @@ -136,10 +137,12 @@ public class SettingsToPropertiesMapper { "context_hub", "core_experiments_team_internal", "core_graphics", + "game", "haptics", "hardware_backed_security_mainline", "input", "machine_learning", + "mainline_modularization", "mainline_sdk", "media_audio", "media_drm", @@ -152,9 +155,12 @@ public class SettingsToPropertiesMapper { "platform_security", "power", "preload_safety", + "privacy_infra_policy", + "resource_manager", "responsible_apis", "rust", "safety_center", + "sensors", "system_performance", "test_suites", "text", diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java index a1d2e1412777..d7076899fcb6 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java +++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java @@ -924,9 +924,6 @@ public class AudioDeviceInventory { @NonNull List<AudioDeviceAttributes> devices) { int status = AudioSystem.ERROR; try (SafeCloseable ignored = ClearCallingIdentityContext.create()) { - AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent( - "setPreferredDevicesForStrategy, strategy: " + strategy - + " devices: " + devices)).printLog(TAG)); status = setDevicesRoleForStrategy( strategy, AudioSystem.DEVICE_ROLE_PREFERRED, devices, false /* internal */); } @@ -952,10 +949,6 @@ public class AudioDeviceInventory { int status = AudioSystem.ERROR; try (SafeCloseable ignored = ClearCallingIdentityContext.create()) { - AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent( - "removePreferredDevicesForStrategy, strategy: " - + strategy)).printLog(TAG)); - status = clearDevicesRoleForStrategy( strategy, AudioSystem.DEVICE_ROLE_PREFERRED, false /*internal */); } @@ -974,10 +967,6 @@ public class AudioDeviceInventory { try (SafeCloseable ignored = ClearCallingIdentityContext.create()) { List<AudioDeviceAttributes> devices = new ArrayList<>(); devices.add(device); - - AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent( - "setDeviceAsNonDefaultForStrategyAndSave, strategy: " + strategy - + " device: " + device)).printLog(TAG)); status = addDevicesRoleForStrategy( strategy, AudioSystem.DEVICE_ROLE_DISABLED, devices, false /* internal */); } @@ -995,11 +984,6 @@ public class AudioDeviceInventory { try (SafeCloseable ignored = ClearCallingIdentityContext.create()) { List<AudioDeviceAttributes> devices = new ArrayList<>(); devices.add(device); - - AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent( - "removeDeviceAsNonDefaultForStrategyAndSave, strategy: " - + strategy + " devices: " + device)).printLog(TAG)); - status = removeDevicesRoleForStrategy( strategy, AudioSystem.DEVICE_ROLE_DISABLED, devices, false /* internal */); } diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 9b03afbcc569..a8fa313abc7a 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -16,8 +16,8 @@ package com.android.server.audio; -import static android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED; import static android.app.BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT; +import static android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED; import static android.media.AudioDeviceInfo.TYPE_BLE_HEADSET; import static android.media.AudioDeviceInfo.TYPE_BLE_SPEAKER; import static android.media.AudioDeviceInfo.TYPE_BLUETOOTH_A2DP; @@ -150,6 +150,7 @@ import android.media.permission.SafeCloseable; import android.media.projection.IMediaProjection; import android.media.projection.IMediaProjectionCallback; import android.media.projection.IMediaProjectionManager; +import android.media.session.MediaSessionManager; import android.net.Uri; import android.os.Binder; import android.os.Build; @@ -309,6 +310,9 @@ public class AudioService extends IAudioService.Stub private final ContentResolver mContentResolver; private final AppOpsManager mAppOps; + /** do not use directly, use getMediaSessionManager() which handles lazy initialization */ + @Nullable private volatile MediaSessionManager mMediaSessionManager; + // the platform type affects volume and silent mode behavior private final int mPlatformType; @@ -940,6 +944,8 @@ public class AudioService extends IAudioService.Stub private final SoundDoseHelper mSoundDoseHelper; + private final HardeningEnforcer mHardeningEnforcer; + private final Object mSupportedSystemUsagesLock = new Object(); @GuardedBy("mSupportedSystemUsagesLock") private @AttributeSystemUsage int[] mSupportedSystemUsages = @@ -1314,6 +1320,8 @@ public class AudioService extends IAudioService.Stub mDisplayManager = context.getSystemService(DisplayManager.class); mMusicFxHelper = new MusicFxHelper(mContext, mAudioHandler); + + mHardeningEnforcer = new HardeningEnforcer(mContext, isPlatformAutomotive()); } private void initVolumeStreamStates() { @@ -1383,7 +1391,6 @@ public class AudioService extends IAudioService.Stub // check on volume initialization checkVolumeRangeInitialization("AudioService()"); - } private SubscriptionManager.OnSubscriptionsChangedListener mSubscriptionChangedListener = @@ -1396,6 +1403,14 @@ public class AudioService extends IAudioService.Stub } }; + private MediaSessionManager getMediaSessionManager() { + if (mMediaSessionManager == null) { + mMediaSessionManager = (MediaSessionManager) mContext + .getSystemService(Context.MEDIA_SESSION_SERVICE); + } + return mMediaSessionManager; + } + /** * Initialize intent receives and settings observers for this service. * Must be called after createStreamStates() as the handling of some events @@ -2921,11 +2936,11 @@ public class AudioService extends IAudioService.Stub super.removePreferredDevicesForStrategy_enforcePermission(); final String logString = - String.format("removePreferredDeviceForStrategy strat:%d", strategy); + String.format("removePreferredDevicesForStrategy strat:%d", strategy); sDeviceLogger.enqueue(new EventLogger.StringEvent(logString).printLog(TAG)); final int status = mDeviceBroker.removePreferredDevicesForStrategySync(strategy); - if (status != AudioSystem.SUCCESS) { + if (status != AudioSystem.SUCCESS && status != AudioSystem.BAD_VALUE) { Log.e(TAG, String.format("Error %d in %s)", status, logString)); } return status; @@ -3009,7 +3024,7 @@ public class AudioService extends IAudioService.Stub } final int status = mDeviceBroker.removeDeviceAsNonDefaultForStrategySync(strategy, device); - if (status != AudioSystem.SUCCESS) { + if (status != AudioSystem.SUCCESS && status != AudioSystem.BAD_VALUE) { Log.e(TAG, String.format("Error %d in %s)", status, logString)); } return status; @@ -3129,7 +3144,7 @@ public class AudioService extends IAudioService.Stub sDeviceLogger.enqueue(new EventLogger.StringEvent(logString).printLog(TAG)); final int status = mDeviceBroker.clearPreferredDevicesForCapturePresetSync(capturePreset); - if (status != AudioSystem.SUCCESS) { + if (status != AudioSystem.SUCCESS && status != AudioSystem.BAD_VALUE) { Log.e(TAG, String.format("Error %d in %s", status, logString)); } return status; @@ -3424,6 +3439,10 @@ public class AudioService extends IAudioService.Stub * Part of service interface, check permissions here */ public void adjustStreamVolumeWithAttribution(int streamType, int direction, int flags, String callingPackage, String attributionTag) { + if (mHardeningEnforcer.blockVolumeMethod( + HardeningEnforcer.METHOD_AUDIO_MANAGER_ADJUST_STREAM_VOLUME)) { + return; + } if ((streamType == AudioManager.STREAM_ACCESSIBILITY) && !canChangeAccessibilityVolume()) { Log.w(TAG, "Trying to call adjustStreamVolume() for a11y without" + "CHANGE_ACCESSIBILITY_VOLUME / callingPackage=" + callingPackage); @@ -4200,6 +4219,10 @@ public class AudioService extends IAudioService.Stub * Part of service interface, check permissions here */ public void setStreamVolumeWithAttribution(int streamType, int index, int flags, String callingPackage, String attributionTag) { + if (mHardeningEnforcer.blockVolumeMethod( + HardeningEnforcer.METHOD_AUDIO_MANAGER_SET_STREAM_VOLUME)) { + return; + } setStreamVolumeWithAttributionInt(streamType, index, flags, /*device*/ null, callingPackage, attributionTag); } @@ -5052,6 +5075,7 @@ public class AudioService extends IAudioService.Stub /** @see AudioManager#setMasterMute(boolean, int) */ public void setMasterMute(boolean mute, int flags, String callingPackage, int userId, String attributionTag) { + super.setMasterMute_enforcePermission(); setMasterMuteInternal(mute, flags, callingPackage, @@ -5417,6 +5441,10 @@ public class AudioService extends IAudioService.Stub } public void setRingerModeExternal(int ringerMode, String caller) { + if (mHardeningEnforcer.blockVolumeMethod( + HardeningEnforcer.METHOD_AUDIO_MANAGER_SET_RINGER_MODE)) { + return; + } if (isAndroidNPlus(caller) && wouldToggleZenMode(ringerMode) && !mNm.isNotificationPolicyAccessGrantedForPackage(caller)) { throw new SecurityException("Not allowed to change Do Not Disturb state"); @@ -6169,6 +6197,35 @@ public class AudioService extends IAudioService.Stub AudioDeviceVolumeManager.ADJUST_MODE_NORMAL); } + /** + * @see AudioManager#adjustVolume(int, int) + * This method is redirected from AudioManager to AudioService for API hardening rules + * enforcement then to MediaSession for implementation. + */ + @Override + public void adjustVolume(int direction, int flags) { + if (mHardeningEnforcer.blockVolumeMethod( + HardeningEnforcer.METHOD_AUDIO_MANAGER_ADJUST_VOLUME)) { + return; + } + getMediaSessionManager().dispatchAdjustVolume(AudioManager.USE_DEFAULT_STREAM_TYPE, + direction, flags); + } + + /** + * @see AudioManager#adjustSuggestedStreamVolume(int, int, int) + * This method is redirected from AudioManager to AudioService for API hardening rules + * enforcement then to MediaSession for implementation. + */ + @Override + public void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags) { + if (mHardeningEnforcer.blockVolumeMethod( + HardeningEnforcer.METHOD_AUDIO_MANAGER_ADJUST_SUGGESTED_STREAM_VOLUME)) { + return; + } + getMediaSessionManager().dispatchAdjustVolume(suggestedStreamType, direction, flags); + } + /** @see AudioManager#setStreamVolumeForUid(int, int, int, String, int, int, int) */ @Override public void setStreamVolumeForUid(int streamType, int index, int flags, diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java index 6af409e7af3a..7d7e6d000f62 100644 --- a/services/core/java/com/android/server/audio/BtHelper.java +++ b/services/core/java/com/android/server/audio/BtHelper.java @@ -44,7 +44,9 @@ import com.android.server.utils.EventLogger; import java.io.PrintWriter; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Objects; /** @@ -66,6 +68,8 @@ public class BtHelper { // Bluetooth headset device private @Nullable BluetoothDevice mBluetoothHeadsetDevice; + private final Map<BluetoothDevice, AudioDeviceAttributes> mResolvedScoAudioDevices = + new HashMap<>(); private @Nullable BluetoothHearingAid mHearingAid; @@ -590,7 +594,16 @@ public class BtHelper { if (mBluetoothHeadsetDevice == null) { return null; } - return btHeadsetDeviceToAudioDevice(mBluetoothHeadsetDevice); + return getHeadsetAudioDevice(mBluetoothHeadsetDevice); + } + + private @NonNull AudioDeviceAttributes getHeadsetAudioDevice(BluetoothDevice btDevice) { + AudioDeviceAttributes deviceAttr = mResolvedScoAudioDevices.get(btDevice); + if (deviceAttr != null) { + // Returns the cached device attributes so that it is consistent as the previous one. + return deviceAttr; + } + return btHeadsetDeviceToAudioDevice(btDevice); } private static AudioDeviceAttributes btHeadsetDeviceToAudioDevice(BluetoothDevice btDevice) { @@ -628,7 +641,7 @@ public class BtHelper { return true; } int inDevice = AudioSystem.DEVICE_IN_BLUETOOTH_SCO_HEADSET; - AudioDeviceAttributes audioDevice = btHeadsetDeviceToAudioDevice(btDevice); + AudioDeviceAttributes audioDevice = btHeadsetDeviceToAudioDevice(btDevice); boolean result = false; if (isActive) { result |= mDeviceBroker.handleDeviceConnection(audioDevice, isActive, btDevice); @@ -648,6 +661,13 @@ public class BtHelper { result = mDeviceBroker.handleDeviceConnection(new AudioDeviceAttributes( inDevice, audioDevice.getAddress(), audioDevice.getName()), isActive, btDevice) && result; + if (result) { + if (isActive) { + mResolvedScoAudioDevices.put(btDevice, audioDevice); + } else { + mResolvedScoAudioDevices.remove(btDevice); + } + } return result; } diff --git a/services/core/java/com/android/server/audio/HardeningEnforcer.java b/services/core/java/com/android/server/audio/HardeningEnforcer.java new file mode 100644 index 000000000000..c7556dacb783 --- /dev/null +++ b/services/core/java/com/android/server/audio/HardeningEnforcer.java @@ -0,0 +1,109 @@ +/* + * Copyright 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.audio; + +import static com.android.media.audio.flags.Flags.autoPublicVolumeApiHardening; + +import android.Manifest; +import android.content.Context; +import android.content.pm.PackageManager; +import android.media.AudioManager; +import android.os.Binder; +import android.os.UserHandle; +import android.text.TextUtils; +import android.util.Log; + +/** + * Class to encapsulate all audio API hardening operations + */ +public class HardeningEnforcer { + + private static final String TAG = "AS.HardeningEnforcer"; + + final Context mContext; + final boolean mIsAutomotive; + + /** + * Matches calls from {@link AudioManager#setStreamVolume(int, int, int)} + */ + public static final int METHOD_AUDIO_MANAGER_SET_STREAM_VOLUME = 100; + /** + * Matches calls from {@link AudioManager#adjustVolume(int, int)} + */ + public static final int METHOD_AUDIO_MANAGER_ADJUST_VOLUME = 101; + /** + * Matches calls from {@link AudioManager#adjustSuggestedStreamVolume(int, int, int)} + */ + public static final int METHOD_AUDIO_MANAGER_ADJUST_SUGGESTED_STREAM_VOLUME = 102; + /** + * Matches calls from {@link AudioManager#adjustStreamVolume(int, int, int)} + */ + public static final int METHOD_AUDIO_MANAGER_ADJUST_STREAM_VOLUME = 103; + /** + * Matches calls from {@link AudioManager#setRingerMode(int)} + */ + public static final int METHOD_AUDIO_MANAGER_SET_RINGER_MODE = 200; + + public HardeningEnforcer(Context ctxt, boolean isAutomotive) { + mContext = ctxt; + mIsAutomotive = isAutomotive; + } + + /** + * Checks whether the call in the current thread should be allowed or blocked + * @param volumeMethod name of the method to check, for logging purposes + * @return false if the method call is allowed, true if it should be a no-op + */ + protected boolean blockVolumeMethod(int volumeMethod) { + // for Auto, volume methods require MODIFY_AUDIO_SETTINGS_PRIVILEGED + if (mIsAutomotive) { + if (!autoPublicVolumeApiHardening()) { + // automotive hardening flag disabled, no blocking on auto + return false; + } + if (mContext.checkCallingOrSelfPermission( + Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) + == PackageManager.PERMISSION_GRANTED) { + return false; + } + if (Binder.getCallingUid() < UserHandle.AID_APP_START) { + return false; + } + // TODO metrics? + // TODO log for audio dumpsys? + Log.e(TAG, "Preventing volume method " + volumeMethod + " for " + + getPackNameForUid(Binder.getCallingUid())); + return true; + } + // not blocking + return false; + } + + private String getPackNameForUid(int uid) { + final long token = Binder.clearCallingIdentity(); + try { + final String[] names = mContext.getPackageManager().getPackagesForUid(uid); + if (names == null + || names.length == 0 + || TextUtils.isEmpty(names[0])) { + return "[" + uid + "]"; + } + return names[0]; + } finally { + Binder.restoreCallingIdentity(token); + } + } +} diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index b1c92a6c2643..087cf20a6682 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -590,8 +590,7 @@ public final class DisplayManagerService extends SystemService { mSystemReady = false; mConfigParameterProvider = new DeviceConfigParameterProvider(DeviceConfigInterface.REAL); mExtraDisplayLoggingPackageName = DisplayProperties.debug_vri_package().orElse(null); - // TODO: b/306170135 - return TextUtils package name check instead - mExtraDisplayEventLogging = true; + mExtraDisplayEventLogging = !TextUtils.isEmpty(mExtraDisplayLoggingPackageName); } public void setupSchedulerPolicies() { @@ -3048,8 +3047,7 @@ public final class DisplayManagerService extends SystemService { } private boolean extraLogging(String packageName) { - // TODO: b/306170135 - return mExtraDisplayLoggingPackageName & package name check instead - return true; + return mExtraDisplayEventLogging && mExtraDisplayLoggingPackageName.equals(packageName); } // Runs on Handler thread. diff --git a/services/core/java/com/android/server/pm/AppDataHelper.java b/services/core/java/com/android/server/pm/AppDataHelper.java index bd9be30b681b..167c17cc0c80 100644 --- a/services/core/java/com/android/server/pm/AppDataHelper.java +++ b/services/core/java/com/android/server/pm/AppDataHelper.java @@ -121,7 +121,7 @@ public class AppDataHelper { StorageManagerInternal.class); for (UserInfo user : umInternal.getUsers(false /*excludeDying*/)) { final int flags; - if (StorageManager.isUserKeyUnlocked(user.id) + if (StorageManager.isCeStorageUnlocked(user.id) && smInternal.isCeStoragePrepared(user.id)) { flags = StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE; } else if (umInternal.isUserRunning(user.id)) { @@ -410,7 +410,7 @@ public class AppDataHelper { // First look for stale data that doesn't belong, and check if things // have changed since we did our last restorecon if ((flags & StorageManager.FLAG_STORAGE_CE) != 0) { - if (StorageManager.isFileEncrypted() && !StorageManager.isUserKeyUnlocked(userId)) { + if (StorageManager.isFileEncrypted() && !StorageManager.isCeStorageUnlocked(userId)) { throw new RuntimeException( "Yikes, someone asked us to reconcile CE storage while " + userId + " was still locked; this would have caused massive data loss!"); diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java index 510c06e9509e..3ed9f029c73b 100644 --- a/services/core/java/com/android/server/pm/ComputerEngine.java +++ b/services/core/java/com/android/server/pm/ComputerEngine.java @@ -3556,7 +3556,7 @@ public class ComputerEngine implements Computer { @Override public int getPackageStartability(boolean safeMode, @NonNull String packageName, int callingUid, @UserIdInt int userId) { - final boolean userKeyUnlocked = StorageManager.isUserKeyUnlocked(userId); + final boolean ceStorageUnlocked = StorageManager.isCeStorageUnlocked(userId); final PackageStateInternal ps = getPackageStateInternal(packageName); if (ps == null || shouldFilterApplication(ps, callingUid, userId) || !ps.getUserStateOrDefault(userId).isInstalled()) { @@ -3571,7 +3571,7 @@ public class ComputerEngine implements Computer { return PackageManagerService.PACKAGE_STARTABILITY_FROZEN; } - if (!userKeyUnlocked && !AndroidPackageUtils.isEncryptionAware(ps.getPkg())) { + if (!ceStorageUnlocked && !AndroidPackageUtils.isEncryptionAware(ps.getPkg())) { return PackageManagerService.PACKAGE_STARTABILITY_DIRECT_BOOT_UNSUPPORTED; } return PackageManagerService.PACKAGE_STARTABILITY_OK; diff --git a/services/core/java/com/android/server/pm/MovePackageHelper.java b/services/core/java/com/android/server/pm/MovePackageHelper.java index 9ad8318c2b5f..f5f5577329e6 100644 --- a/services/core/java/com/android/server/pm/MovePackageHelper.java +++ b/services/core/java/com/android/server/pm/MovePackageHelper.java @@ -196,7 +196,8 @@ public final class MovePackageHelper { // If we're moving app data around, we need all the users unlocked if (moveCompleteApp) { for (int userId : installedUserIds) { - if (StorageManager.isFileEncrypted() && !StorageManager.isUserKeyUnlocked(userId)) { + if (StorageManager.isFileEncrypted() + && !StorageManager.isCeStorageUnlocked(userId)) { freezer.close(); throw new PackageManagerException(MOVE_FAILED_LOCKED_USER, "User " + userId + " must be unlocked"); diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 6b4ac5b4fa4e..c9303f2b30a2 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -3357,7 +3357,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService UserManagerInternal umInternal = mInjector.getUserManagerInternal(); StorageManagerInternal smInternal = mInjector.getLocalService(StorageManagerInternal.class); final int flags; - if (StorageManager.isUserKeyUnlocked(userId) && smInternal.isCeStoragePrepared(userId)) { + if (StorageManager.isCeStorageUnlocked(userId) && smInternal.isCeStoragePrepared(userId)) { flags = StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE; } else if (umInternal.isUserRunning(userId)) { flags = StorageManager.FLAG_STORAGE_DE; diff --git a/services/core/java/com/android/server/pm/StorageEventHelper.java b/services/core/java/com/android/server/pm/StorageEventHelper.java index c725cdca9d76..70aa19ae9cef 100644 --- a/services/core/java/com/android/server/pm/StorageEventHelper.java +++ b/services/core/java/com/android/server/pm/StorageEventHelper.java @@ -183,7 +183,7 @@ public final class StorageEventHelper extends StorageEventListener { StorageManagerInternal.class); for (UserInfo user : mPm.mUserManager.getUsers(false /* includeDying */)) { final int flags; - if (StorageManager.isUserKeyUnlocked(user.id) + if (StorageManager.isCeStorageUnlocked(user.id) && smInternal.isCeStoragePrepared(user.id)) { flags = StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE; } else if (umInternal.isUserRunning(user.id)) { diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index bb55a39f8e4b..978d8e4a0e8a 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -1427,7 +1427,7 @@ public class UserManagerService extends IUserManager.Stub { } final boolean needToShowConfirmCredential = !dontAskCredential && mLockPatternUtils.isSecure(userId) - && (!hasUnifiedChallenge || !StorageManager.isUserKeyUnlocked(userId)); + && (!hasUnifiedChallenge || !StorageManager.isCeStorageUnlocked(userId)); if (needToShowConfirmCredential) { if (onlyIfCredentialNotRequired) { return false; @@ -7178,9 +7178,9 @@ public class UserManagerService extends IUserManager.Stub { synchronized (mUserStates) { state = mUserStates.get(userId, -1); } - // Special case, in the stopping/shutdown state user key can still be unlocked + // Special case: in the stopping/shutdown state, CE storage can still be unlocked. if (state == UserState.STATE_STOPPING || state == UserState.STATE_SHUTDOWN) { - return StorageManager.isUserKeyUnlocked(userId); + return StorageManager.isCeStorageUnlocked(userId); } return (state == UserState.STATE_RUNNING_UNLOCKING) || (state == UserState.STATE_RUNNING_UNLOCKED); @@ -7197,9 +7197,9 @@ public class UserManagerService extends IUserManager.Stub { synchronized (mUserStates) { state = mUserStates.get(userId, -1); } - // Special case, in the stopping/shutdown state user key can still be unlocked + // Special case: in the stopping/shutdown state, CE storage can still be unlocked. if (state == UserState.STATE_STOPPING || state == UserState.STATE_SHUTDOWN) { - return StorageManager.isUserKeyUnlocked(userId); + return StorageManager.isCeStorageUnlocked(userId); } return state == UserState.STATE_RUNNING_UNLOCKED; } diff --git a/services/core/java/com/android/server/power/stats/AggregatedPowerStats.java b/services/core/java/com/android/server/power/stats/AggregatedPowerStats.java index bc90f5c46130..aadd03b25428 100644 --- a/services/core/java/com/android/server/power/stats/AggregatedPowerStats.java +++ b/services/core/java/com/android/server/power/stats/AggregatedPowerStats.java @@ -19,7 +19,6 @@ package com.android.server.power.stats; import android.annotation.CurrentTimeMillisLong; import android.annotation.DurationMillisLong; import android.annotation.NonNull; -import android.os.BatteryConsumer; import android.os.UserHandle; import android.text.format.DateFormat; import android.util.IndentingPrintWriter; @@ -66,17 +65,7 @@ class AggregatedPowerStats { aggregatedPowerStatsConfig.getPowerComponentsAggregatedStatsConfigs(); mPowerComponentStats = new PowerComponentAggregatedPowerStats[configs.size()]; for (int i = 0; i < configs.size(); i++) { - mPowerComponentStats[i] = createPowerComponentAggregatedPowerStats(configs.get(i)); - } - } - - private PowerComponentAggregatedPowerStats createPowerComponentAggregatedPowerStats( - AggregatedPowerStatsConfig.PowerComponent config) { - switch (config.getPowerComponentId()) { - case BatteryConsumer.POWER_COMPONENT_CPU: - return new CpuAggregatedPowerStats(config); - default: - return new PowerComponentAggregatedPowerStats(config); + mPowerComponentStats[i] = new PowerComponentAggregatedPowerStats(configs.get(i)); } } diff --git a/services/core/java/com/android/server/power/stats/AggregatedPowerStatsConfig.java b/services/core/java/com/android/server/power/stats/AggregatedPowerStatsConfig.java index 477c2286abe0..43fd15d690e6 100644 --- a/services/core/java/com/android/server/power/stats/AggregatedPowerStatsConfig.java +++ b/services/core/java/com/android/server/power/stats/AggregatedPowerStatsConfig.java @@ -16,13 +16,16 @@ package com.android.server.power.stats; import android.annotation.IntDef; +import android.annotation.NonNull; import android.os.BatteryConsumer; import com.android.internal.os.MultiStateStats; +import com.android.internal.os.PowerStats; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; /** @@ -73,6 +76,7 @@ public class AggregatedPowerStatsConfig { private final int mPowerComponentId; private @TrackedState int[] mTrackedDeviceStates; private @TrackedState int[] mTrackedUidStates; + private AggregatedPowerStatsProcessor mProcessor = NO_OP_PROCESSOR; PowerComponent(int powerComponentId) { this.mPowerComponentId = powerComponentId; @@ -94,6 +98,16 @@ public class AggregatedPowerStatsConfig { return this; } + /** + * Takes an object that should be invoked for every aggregated stats span + * before giving the aggregates stats to consumers. The processor can complete the + * aggregation process, for example by computing estimated power usage. + */ + public PowerComponent setProcessor(@NonNull AggregatedPowerStatsProcessor processor) { + mProcessor = processor; + return this; + } + public int getPowerComponentId() { return mPowerComponentId; } @@ -123,6 +137,11 @@ public class AggregatedPowerStatsConfig { }; } + @NonNull + public AggregatedPowerStatsProcessor getProcessor() { + return mProcessor; + } + private boolean isTracked(int[] trackedStates, int state) { if (trackedStates == null) { return false; @@ -153,4 +172,21 @@ public class AggregatedPowerStatsConfig { public List<PowerComponent> getPowerComponentsAggregatedStatsConfigs() { return mPowerComponents; } + + private static final AggregatedPowerStatsProcessor NO_OP_PROCESSOR = + new AggregatedPowerStatsProcessor() { + @Override + public void finish(PowerComponentAggregatedPowerStats stats) { + } + + @Override + public String deviceStatsToString(PowerStats.Descriptor descriptor, long[] stats) { + return Arrays.toString(stats); + } + + @Override + public String uidStatsToString(PowerStats.Descriptor descriptor, long[] stats) { + return Arrays.toString(stats); + } + }; } diff --git a/services/core/java/com/android/server/power/stats/AggregatedPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/AggregatedPowerStatsProcessor.java new file mode 100644 index 000000000000..5fd8ddfbf240 --- /dev/null +++ b/services/core/java/com/android/server/power/stats/AggregatedPowerStatsProcessor.java @@ -0,0 +1,348 @@ +/* + * 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.NonNull; +import android.annotation.Nullable; +import android.util.Log; + +import com.android.internal.os.MultiStateStats; +import com.android.internal.os.PowerStats; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/* + * The power estimation algorithm used by AggregatedPowerStatsProcessor can roughly be + * described like this: + * + * 1. Estimate power usage for each state combination (e.g. power-battery/screen-on) using + * a metric such as CPU time-in-state. + * + * 2. Combine estimates obtain in step 1, aggregating across states that are *not* tracked + * per UID. + * + * 2. For each UID, compute the proportion of the combined estimates in each state + * and attribute the corresponding portion of the total power estimate in that state to the UID. + */ +abstract class AggregatedPowerStatsProcessor { + private static final String TAG = "AggregatedPowerStatsProcessor"; + + private static final int INDEX_DOES_NOT_EXIST = -1; + private static final double MILLIAMPHOUR_PER_MICROCOULOMB = 1.0 / 1000.0 / 60.0 / 60.0; + + abstract void finish(PowerComponentAggregatedPowerStats stats); + + abstract String deviceStatsToString(PowerStats.Descriptor descriptor, long[] stats); + + abstract String uidStatsToString(PowerStats.Descriptor descriptor, long[] stats); + + protected static class PowerEstimationPlan { + private final AggregatedPowerStatsConfig.PowerComponent mConfig; + public List<DeviceStateEstimation> deviceStateEstimations = new ArrayList<>(); + public List<CombinedDeviceStateEstimate> combinedDeviceStateEstimations = new ArrayList<>(); + public List<UidStateEstimate> uidStateEstimates = new ArrayList<>(); + + public PowerEstimationPlan(AggregatedPowerStatsConfig.PowerComponent config) { + mConfig = config; + addDeviceStateEstimations(); + combineDeviceStateEstimations(); + addUidStateEstimations(); + } + + private void addDeviceStateEstimations() { + MultiStateStats.States[] config = mConfig.getDeviceStateConfig(); + int[][] deviceStateCombinations = getAllTrackedStateCombinations(config); + for (int[] deviceStateCombination : deviceStateCombinations) { + deviceStateEstimations.add( + new DeviceStateEstimation(config, deviceStateCombination)); + } + } + + private void combineDeviceStateEstimations() { + MultiStateStats.States[] deviceStateConfig = mConfig.getDeviceStateConfig(); + MultiStateStats.States[] uidStateConfig = mConfig.getUidStateConfig(); + MultiStateStats.States[] deviceStatesTrackedPerUid = + new MultiStateStats.States[deviceStateConfig.length]; + + for (int i = 0; i < deviceStateConfig.length; i++) { + if (!deviceStateConfig[i].isTracked()) { + continue; + } + + int index = findTrackedStateByName(uidStateConfig, deviceStateConfig[i].getName()); + if (index != INDEX_DOES_NOT_EXIST && uidStateConfig[index].isTracked()) { + deviceStatesTrackedPerUid[i] = deviceStateConfig[i]; + } + } + + combineDeviceStateEstimationsRecursively(deviceStateConfig, deviceStatesTrackedPerUid, + new int[deviceStateConfig.length], 0); + } + + private void combineDeviceStateEstimationsRecursively( + MultiStateStats.States[] deviceStateConfig, + MultiStateStats.States[] deviceStatesTrackedPerUid, int[] stateValues, int state) { + if (state >= deviceStateConfig.length) { + DeviceStateEstimation dse = getDeviceStateEstimate(stateValues); + CombinedDeviceStateEstimate cdse = getCombinedDeviceStateEstimate( + deviceStatesTrackedPerUid, stateValues); + if (cdse == null) { + cdse = new CombinedDeviceStateEstimate(deviceStatesTrackedPerUid, stateValues); + combinedDeviceStateEstimations.add(cdse); + } + cdse.deviceStateEstimations.add(dse); + return; + } + + if (deviceStateConfig[state].isTracked()) { + for (int stateValue = 0; + stateValue < deviceStateConfig[state].getLabels().length; + stateValue++) { + stateValues[state] = stateValue; + combineDeviceStateEstimationsRecursively(deviceStateConfig, + deviceStatesTrackedPerUid, stateValues, state + 1); + } + } else { + combineDeviceStateEstimationsRecursively(deviceStateConfig, + deviceStatesTrackedPerUid, stateValues, state + 1); + } + } + + private void addUidStateEstimations() { + MultiStateStats.States[] deviceStateConfig = mConfig.getDeviceStateConfig(); + MultiStateStats.States[] uidStateConfig = mConfig.getUidStateConfig(); + MultiStateStats.States[] uidStatesTrackedForDevice = + new MultiStateStats.States[uidStateConfig.length]; + MultiStateStats.States[] uidStatesNotTrackedForDevice = + new MultiStateStats.States[uidStateConfig.length]; + + for (int i = 0; i < uidStateConfig.length; i++) { + if (!uidStateConfig[i].isTracked()) { + continue; + } + + int index = findTrackedStateByName(deviceStateConfig, uidStateConfig[i].getName()); + if (index != INDEX_DOES_NOT_EXIST && deviceStateConfig[index].isTracked()) { + uidStatesTrackedForDevice[i] = uidStateConfig[i]; + } else { + uidStatesNotTrackedForDevice[i] = uidStateConfig[i]; + } + } + + @AggregatedPowerStatsConfig.TrackedState + int[][] uidStateCombinations = getAllTrackedStateCombinations(uidStateConfig); + for (int[] stateValues : uidStateCombinations) { + CombinedDeviceStateEstimate combined = + getCombinedDeviceStateEstimate(uidStatesTrackedForDevice, stateValues); + if (combined == null) { + // This is not supposed to be possible + Log.wtf(TAG, "Mismatch in UID and combined device states: " + + concatLabels(uidStatesTrackedForDevice, stateValues)); + continue; + } + UidStateEstimate uidStateEstimate = getUidStateEstimate(combined); + if (uidStateEstimate == null) { + uidStateEstimate = new UidStateEstimate(combined, uidStatesNotTrackedForDevice); + uidStateEstimates.add(uidStateEstimate); + } + uidStateEstimate.proportionalEstimates.add( + new UidStateProportionalEstimate(stateValues)); + } + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("Step 1. Compute device-wide power estimates for state combinations:\n"); + for (DeviceStateEstimation deviceStateEstimation : deviceStateEstimations) { + sb.append(" ").append(deviceStateEstimation.id).append("\n"); + } + sb.append("Step 2. Combine device-wide estimates that are untracked per UID:\n"); + boolean any = false; + for (CombinedDeviceStateEstimate cdse : combinedDeviceStateEstimations) { + if (cdse.deviceStateEstimations.size() <= 1) { + continue; + } + any = true; + sb.append(" ").append(cdse.id).append(": "); + for (int i = 0; i < cdse.deviceStateEstimations.size(); i++) { + if (i != 0) { + sb.append(" + "); + } + sb.append(cdse.deviceStateEstimations.get(i).id); + } + sb.append("\n"); + } + if (!any) { + sb.append(" N/A\n"); + } + sb.append("Step 3. Proportionally distribute power estimates to UIDs:\n"); + for (UidStateEstimate uidStateEstimate : uidStateEstimates) { + sb.append(" ").append(uidStateEstimate.combinedDeviceStateEstimate.id) + .append("\n among: "); + for (int i = 0; i < uidStateEstimate.proportionalEstimates.size(); i++) { + UidStateProportionalEstimate uspe = + uidStateEstimate.proportionalEstimates.get(i); + if (i != 0) { + sb.append(", "); + } + sb.append(concatLabels(uidStateEstimate.states, uspe.stateValues)); + } + sb.append("\n"); + } + return sb.toString(); + } + + @Nullable + public DeviceStateEstimation getDeviceStateEstimate( + @AggregatedPowerStatsConfig.TrackedState int[] stateValues) { + String label = concatLabels(mConfig.getDeviceStateConfig(), stateValues); + for (int i = 0; i < deviceStateEstimations.size(); i++) { + DeviceStateEstimation deviceStateEstimation = this.deviceStateEstimations.get(i); + if (deviceStateEstimation.id.equals(label)) { + return deviceStateEstimation; + } + } + return null; + } + + public CombinedDeviceStateEstimate getCombinedDeviceStateEstimate( + MultiStateStats.States[] deviceStates, + @AggregatedPowerStatsConfig.TrackedState int[] stateValues) { + String label = concatLabels(deviceStates, stateValues); + for (int i = 0; i < combinedDeviceStateEstimations.size(); i++) { + CombinedDeviceStateEstimate cdse = combinedDeviceStateEstimations.get(i); + if (cdse.id.equals(label)) { + return cdse; + } + } + return null; + } + + public UidStateEstimate getUidStateEstimate(CombinedDeviceStateEstimate combined) { + for (int i = 0; i < uidStateEstimates.size(); i++) { + UidStateEstimate uidStateEstimate = uidStateEstimates.get(i); + if (uidStateEstimate.combinedDeviceStateEstimate == combined) { + return uidStateEstimate; + } + } + return null; + } + + public void resetIntermediates() { + for (int i = deviceStateEstimations.size() - 1; i >= 0; i--) { + deviceStateEstimations.get(i).intermediates = null; + } + for (int i = deviceStateEstimations.size() - 1; i >= 0; i--) { + deviceStateEstimations.get(i).intermediates = null; + } + for (int i = uidStateEstimates.size() - 1; i >= 0; i--) { + UidStateEstimate uidStateEstimate = uidStateEstimates.get(i); + List<UidStateProportionalEstimate> proportionalEstimates = + uidStateEstimate.proportionalEstimates; + for (int j = proportionalEstimates.size() - 1; j >= 0; j--) { + proportionalEstimates.get(j).intermediates = null; + } + } + } + } + + protected static class DeviceStateEstimation { + public final String id; + @AggregatedPowerStatsConfig.TrackedState + public final int[] stateValues; + public Object intermediates; + + public DeviceStateEstimation(MultiStateStats.States[] config, + @AggregatedPowerStatsConfig.TrackedState int[] stateValues) { + id = concatLabels(config, stateValues); + this.stateValues = stateValues; + } + } + + protected static class CombinedDeviceStateEstimate { + public final String id; + public List<DeviceStateEstimation> deviceStateEstimations = new ArrayList<>(); + public Object intermediates; + + public CombinedDeviceStateEstimate(MultiStateStats.States[] config, + @AggregatedPowerStatsConfig.TrackedState int[] stateValues) { + id = concatLabels(config, stateValues); + } + } + + protected static class UidStateEstimate { + public final MultiStateStats.States[] states; + public CombinedDeviceStateEstimate combinedDeviceStateEstimate; + public List<UidStateProportionalEstimate> proportionalEstimates = new ArrayList<>(); + + public UidStateEstimate(CombinedDeviceStateEstimate combined, + MultiStateStats.States[] states) { + combinedDeviceStateEstimate = combined; + this.states = states; + } + } + + protected static class UidStateProportionalEstimate { + @AggregatedPowerStatsConfig.TrackedState + public final int[] stateValues; + public Object intermediates; + + protected UidStateProportionalEstimate( + @AggregatedPowerStatsConfig.TrackedState int[] stateValues) { + this.stateValues = stateValues; + } + } + + private static int findTrackedStateByName(MultiStateStats.States[] states, String name) { + for (int i = 0; i < states.length; i++) { + if (states[i].getName().equals(name)) { + return i; + } + } + return INDEX_DOES_NOT_EXIST; + } + + @NonNull + private static String concatLabels(MultiStateStats.States[] config, + @AggregatedPowerStatsConfig.TrackedState int[] stateValues) { + List<String> labels = new ArrayList<>(); + for (int state = 0; state < config.length; state++) { + if (config[state] != null && config[state].isTracked()) { + labels.add(config[state].getName() + + "=" + config[state].getLabels()[stateValues[state]]); + } + } + Collections.sort(labels); + return labels.toString(); + } + + @AggregatedPowerStatsConfig.TrackedState + private static int[][] getAllTrackedStateCombinations(MultiStateStats.States[] states) { + List<int[]> combinations = new ArrayList<>(); + MultiStateStats.States.forEachTrackedStateCombination(states, stateValues -> { + combinations.add(Arrays.copyOf(stateValues, stateValues.length)); + }); + return combinations.toArray(new int[combinations.size()][0]); + } + + public static double uCtoMah(long chargeUC) { + return chargeUC * MILLIAMPHOUR_PER_MICROCOULOMB; + } +} 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 a9c2bc28b7a0..a6558e07b2aa 100644 --- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java +++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java @@ -10937,7 +10937,8 @@ public class BatteryStatsImpl extends BatteryStats { } mCpuPowerStatsCollector = new CpuPowerStatsCollector(mCpuScalingPolicies, mPowerProfile, - mHandler, mBatteryStatsConfig.getPowerStatsThrottlePeriodCpu()); + () -> mBatteryVoltageMv, mHandler, + mBatteryStatsConfig.getPowerStatsThrottlePeriodCpu()); mCpuPowerStatsCollector.addConsumer(this::recordPowerStats); mStartCount++; @@ -14437,6 +14438,7 @@ public class BatteryStatsImpl extends BatteryStats { final int level, /* not final */ int temp, final int voltageMv, final int chargeUah, final int chargeFullUah, final long chargeTimeToFullSeconds, final long elapsedRealtimeMs, final long uptimeMs, final long currentTimeMs) { + // Temperature is encoded without the signed bit, so clamp any negative temperatures to 0. temp = Math.max(0, temp); @@ -15621,18 +15623,6 @@ public class BatteryStatsImpl extends BatteryStats { } } - @GuardedBy("this") - private void dumpCpuPowerBracketsLocked(PrintWriter pw) { - pw.println("CPU power brackets; cluster/freq in MHz(avg current in mA):"); - final int bracketCount = mPowerProfile.getCpuPowerBracketCount(); - for (int bracket = 0; bracket < bracketCount; bracket++) { - pw.print(" "); - pw.print(bracket); - pw.print(": "); - pw.println(mPowerProfile.getCpuPowerBracketDescription(mCpuScalingPolicies, bracket)); - } - } - /** * Dump EnergyConsumer stats */ @@ -16989,8 +16979,10 @@ public class BatteryStatsImpl extends BatteryStats { pw.println(); dumpConstantsLocked(pw); - pw.println(); - dumpCpuPowerBracketsLocked(pw); + if (mCpuPowerStatsCollector != null) { + pw.println(); + mCpuPowerStatsCollector.dumpCpuPowerBracketsLocked(pw); + } pw.println(); dumpEnergyConsumerStatsLocked(pw); diff --git a/services/core/java/com/android/server/power/stats/CpuAggregatedPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/CpuAggregatedPowerStatsProcessor.java new file mode 100644 index 000000000000..f40eef2ed820 --- /dev/null +++ b/services/core/java/com/android/server/power/stats/CpuAggregatedPowerStatsProcessor.java @@ -0,0 +1,545 @@ +/* + * 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.os.BatteryStats; +import android.util.ArraySet; +import android.util.Log; + +import com.android.internal.os.CpuScalingPolicies; +import com.android.internal.os.PowerProfile; +import com.android.internal.os.PowerStats; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.TimeUnit; + +public class CpuAggregatedPowerStatsProcessor extends AggregatedPowerStatsProcessor { + private static final String TAG = "CpuAggregatedPowerStatsProcessor"; + + private static final double HOUR_IN_MILLIS = TimeUnit.HOURS.toMillis(1); + private static final int UNKNOWN = -1; + + private final CpuScalingPolicies mCpuScalingPolicies; + // Number of CPU core clusters + private final int mCpuClusterCount; + // Total number of CPU scaling steps across all clusters + private final int mCpuScalingStepCount; + // Map of scaling step to the corresponding core cluster mScalingStepToCluster[step]->cluster + private final int[] mScalingStepToCluster; + // Average power consumed by the CPU when it is powered up (per power_profile.xml) + private final double mPowerMultiplierForCpuActive; + // Average power consumed by each cluster when it is powered up (per power_profile.xml) + private final double[] mPowerMultipliersByCluster; + // Average power consumed by each scaling step when running code (per power_profile.xml) + private final double[] mPowerMultipliersByScalingStep; + // A map used to combine energy consumers into a smaller set, in case power brackets + // are defined in a way that does not allow an unambiguous mapping of energy consumers to + // brackets + private int[] mEnergyConsumerToCombinedEnergyConsumerMap; + // A map of combined energy consumers to the corresponding collections of power brackets. + // For example, if there are two CPU_CLUSTER rails and each maps to three brackets, + // this map will look like this: + // 0 : [0, 1, 2] + // 1 : [3, 4, 5] + private int[][] mCombinedEnergyConsumerToPowerBracketMap; + + // Cached reference to a PowerStats descriptor. Almost never changes in practice, + // helping to avoid reparsing the descriptor for every PowerStats span. + private PowerStats.Descriptor mLastUsedDescriptor; + // Cached results of parsing of current PowerStats.Descriptor. Only refreshed when + // mLastUsedDescriptor changes + private CpuPowerStatsCollector.StatsArrayLayout mStatsLayout; + // Sequence of steps for power estimation and intermediate results. + private PowerEstimationPlan mPlan; + + // Temp array for retrieval of device power stats, to avoid repeated allocations + private long[] mTmpDeviceStatsArray; + // Temp array for retrieval of UID power stats, to avoid repeated allocations + private long[] mTmpUidStatsArray; + + public CpuAggregatedPowerStatsProcessor(PowerProfile powerProfile, + CpuScalingPolicies scalingPolicies) { + mCpuScalingPolicies = scalingPolicies; + mCpuScalingStepCount = scalingPolicies.getScalingStepCount(); + mScalingStepToCluster = new int[mCpuScalingStepCount]; + mPowerMultipliersByScalingStep = new double[mCpuScalingStepCount]; + + int step = 0; + int[] policies = scalingPolicies.getPolicies(); + mCpuClusterCount = policies.length; + mPowerMultipliersByCluster = new double[mCpuClusterCount]; + for (int cluster = 0; cluster < mCpuClusterCount; cluster++) { + int policy = policies[cluster]; + mPowerMultipliersByCluster[cluster] = + powerProfile.getAveragePowerForCpuScalingPolicy(policy) / HOUR_IN_MILLIS; + int[] frequencies = scalingPolicies.getFrequencies(policy); + for (int i = 0; i < frequencies.length; i++) { + mScalingStepToCluster[step] = cluster; + mPowerMultipliersByScalingStep[step] = + powerProfile.getAveragePowerForCpuScalingStep(policy, i) / HOUR_IN_MILLIS; + step++; + } + } + mPowerMultiplierForCpuActive = + powerProfile.getAveragePower(PowerProfile.POWER_CPU_ACTIVE) / HOUR_IN_MILLIS; + } + + private void unpackPowerStatsDescriptor(PowerStats.Descriptor descriptor) { + if (descriptor.equals(mLastUsedDescriptor)) { + return; + } + + mLastUsedDescriptor = descriptor; + mStatsLayout = new CpuPowerStatsCollector.StatsArrayLayout(); + mStatsLayout.fromExtras(descriptor.extras); + + mTmpDeviceStatsArray = new long[descriptor.statsArrayLength]; + mTmpUidStatsArray = new long[descriptor.uidStatsArrayLength]; + } + + /** + * Temporary struct to capture intermediate results of power estimation. + */ + private static final class Intermediates { + public long uptime; + // Sum of all time-in-step values, combining all time-in-step durations across all cores. + public long cumulativeTime; + // CPU activity durations per cluster + public long[] timeByCluster; + // Sums of time-in-step values, aggregated by cluster, combining all cores in the cluster. + public long[] cumulativeTimeByCluster; + public long[] timeByScalingStep; + public double[] powerByCluster; + public double[] powerByScalingStep; + public long[] powerByEnergyConsumer; + } + + /** + * Temporary struct to capture intermediate results of power estimation. + */ + private static class DeviceStatsIntermediates { + public double power; + public long[] timeByBracket; + public double[] powerByBracket; + } + + @Override + public void finish(PowerComponentAggregatedPowerStats stats) { + if (stats.getPowerStatsDescriptor() == null) { + return; + } + + unpackPowerStatsDescriptor(stats.getPowerStatsDescriptor()); + + if (mPlan == null) { + mPlan = new PowerEstimationPlan(stats.getConfig()); + if (mStatsLayout.getCpuClusterEnergyConsumerCount() != 0) { + initEnergyConsumerToPowerBracketMaps(); + } + } + + Intermediates intermediates = new Intermediates(); + + int cpuScalingStepCount = mStatsLayout.getCpuScalingStepCount(); + if (cpuScalingStepCount != mCpuScalingStepCount) { + Log.e(TAG, "Mismatched CPU scaling step count in PowerStats: " + cpuScalingStepCount + + ", expected: " + mCpuScalingStepCount); + return; + } + + int clusterCount = mStatsLayout.getCpuClusterCount(); + if (clusterCount != mCpuClusterCount) { + Log.e(TAG, "Mismatched CPU cluster count in PowerStats: " + clusterCount + + ", expected: " + mCpuClusterCount); + return; + } + + computeTotals(stats, intermediates); + if (intermediates.cumulativeTime == 0) { + return; + } + + estimatePowerByScalingStep(intermediates); + estimatePowerByDeviceState(stats, intermediates); + combineDeviceStateEstimates(); + + ArrayList<Integer> uids = new ArrayList<>(); + stats.collectUids(uids); + if (!uids.isEmpty()) { + for (int uid : uids) { + for (int i = 0; i < mPlan.uidStateEstimates.size(); i++) { + estimateUidPowerConsumption(stats, uid, mPlan.uidStateEstimates.get(i)); + } + } + } + mPlan.resetIntermediates(); + } + + /* + * Populate data structures (two maps) needed to use power rail data, aka energy consumers, + * to attribute power usage to apps. + * + * At this point, the algorithm covers only the most basic cases: + * - Each cluster is mapped to unique power brackets (possibly multiple for each cluster): + * CL_0: [bracket0, bracket1] + * CL_1: [bracket3] + * In this case, the consumed energy is distributed to the corresponding brackets + * proportionally. + * - Brackets span multiple clusters: + * CL_0: [bracket0, bracket1] + * CL_1: [bracket1, bracket2] + * CL_2: [bracket3, bracket4] + * In this case, we combine energy consumers into groups unambiguously mapped to + * brackets. In the above example, consumed energy for CL_0 and CL_1 will be combined + * because they both map to the same power bracket (bracket1): + * (CL_0+CL_1): [bracket0, bracket1, bracket2] + * CL_2: [bracket3, bracket4] + */ + private void initEnergyConsumerToPowerBracketMaps() { + int energyConsumerCount = mStatsLayout.getCpuClusterEnergyConsumerCount(); + int powerBracketCount = mStatsLayout.getCpuPowerBracketCount(); + + mEnergyConsumerToCombinedEnergyConsumerMap = new int[energyConsumerCount]; + mCombinedEnergyConsumerToPowerBracketMap = new int[energyConsumerCount][]; + + int[] policies = mCpuScalingPolicies.getPolicies(); + if (energyConsumerCount == policies.length) { + int[] scalingStepToPowerBracketMap = mStatsLayout.getScalingStepToPowerBracketMap(); + ArraySet<Integer>[] clusterToBrackets = new ArraySet[policies.length]; + int step = 0; + for (int cluster = 0; cluster < policies.length; cluster++) { + int[] freqs = mCpuScalingPolicies.getFrequencies(policies[cluster]); + clusterToBrackets[cluster] = new ArraySet<>(freqs.length); + for (int j = 0; j < freqs.length; j++) { + clusterToBrackets[cluster].add(scalingStepToPowerBracketMap[step++]); + } + } + + ArraySet<Integer>[] combinedEnergyConsumers = new ArraySet[policies.length]; + int combinedEnergyConsumersCount = 0; + + for (int cluster = 0; cluster < clusterToBrackets.length; cluster++) { + int combineWith = UNKNOWN; + for (int i = 0; i < combinedEnergyConsumersCount; i++) { + if (containsAny(combinedEnergyConsumers[i], clusterToBrackets[cluster])) { + combineWith = i; + break; + } + } + if (combineWith != UNKNOWN) { + mEnergyConsumerToCombinedEnergyConsumerMap[cluster] = combineWith; + combinedEnergyConsumers[combineWith].addAll(clusterToBrackets[cluster]); + } else { + mEnergyConsumerToCombinedEnergyConsumerMap[cluster] = + combinedEnergyConsumersCount; + combinedEnergyConsumers[combinedEnergyConsumersCount++] = + clusterToBrackets[cluster]; + } + } + + for (int i = combinedEnergyConsumers.length - 1; i >= 0; i--) { + mCombinedEnergyConsumerToPowerBracketMap[i] = + new int[combinedEnergyConsumers[i].size()]; + for (int j = combinedEnergyConsumers[i].size() - 1; j >= 0; j--) { + mCombinedEnergyConsumerToPowerBracketMap[i][j] = + combinedEnergyConsumers[i].valueAt(j); + } + } + } else { + // All CPU cluster energy consumers are combined into one, which is + // distributed proportionally to all power brackets. + int[] map = new int[powerBracketCount]; + for (int i = 0; i < map.length; i++) { + map[i] = i; + } + mCombinedEnergyConsumerToPowerBracketMap[0] = map; + } + } + + private static boolean containsAny(ArraySet<Integer> set1, ArraySet<Integer> set2) { + for (int i = 0; i < set2.size(); i++) { + if (set1.contains(set2.valueAt(i))) { + return true; + } + } + return false; + } + + private void computeTotals(PowerComponentAggregatedPowerStats stats, + Intermediates intermediates) { + intermediates.timeByScalingStep = new long[mCpuScalingStepCount]; + intermediates.timeByCluster = new long[mCpuClusterCount]; + intermediates.cumulativeTimeByCluster = new long[mCpuClusterCount]; + + List<DeviceStateEstimation> deviceStateEstimations = mPlan.deviceStateEstimations; + for (int i = deviceStateEstimations.size() - 1; i >= 0; i--) { + DeviceStateEstimation deviceStateEstimation = deviceStateEstimations.get(i); + if (!stats.getDeviceStats(mTmpDeviceStatsArray, deviceStateEstimation.stateValues)) { + continue; + } + + intermediates.uptime += mStatsLayout.getUptime(mTmpDeviceStatsArray); + + for (int cluster = 0; cluster < mCpuClusterCount; cluster++) { + intermediates.timeByCluster[cluster] += + mStatsLayout.getTimeByCluster(mTmpDeviceStatsArray, cluster); + } + + for (int step = 0; step < mCpuScalingStepCount; step++) { + long timeInStep = mStatsLayout.getTimeByScalingStep(mTmpDeviceStatsArray, step); + intermediates.cumulativeTime += timeInStep; + intermediates.timeByScalingStep[step] += timeInStep; + intermediates.cumulativeTimeByCluster[mScalingStepToCluster[step]] += timeInStep; + } + } + } + + private void estimatePowerByScalingStep(Intermediates intermediates) { + // CPU consumes some power when it's on - no matter which cores are running. + double cpuActivePower = mPowerMultiplierForCpuActive * intermediates.uptime; + + // Additionally, every cluster consumes some power when any of its cores are running + intermediates.powerByCluster = new double[mCpuClusterCount]; + for (int cluster = 0; cluster < mCpuClusterCount; cluster++) { + intermediates.powerByCluster[cluster] = + mPowerMultipliersByCluster[cluster] * intermediates.timeByCluster[cluster]; + } + + // Finally, additional power is consumed depending on the frequency scaling + intermediates.powerByScalingStep = new double[mCpuScalingStepCount]; + for (int step = 0; step < mCpuScalingStepCount; step++) { + int cluster = mScalingStepToCluster[step]; + + double power; + + // Distribute base power proportionally + power = cpuActivePower * intermediates.timeByScalingStep[step] + / intermediates.cumulativeTime; + + // Distribute per-cluster power proportionally + long cumulativeTimeInCluster = intermediates.cumulativeTimeByCluster[cluster]; + if (cumulativeTimeInCluster != 0) { + power += intermediates.powerByCluster[cluster] + * intermediates.timeByScalingStep[step] + / cumulativeTimeInCluster; + } + + power += mPowerMultipliersByScalingStep[step] * intermediates.timeByScalingStep[step]; + + intermediates.powerByScalingStep[step] = power; + } + } + + private void estimatePowerByDeviceState(PowerComponentAggregatedPowerStats stats, + Intermediates intermediates) { + int cpuScalingStepCount = mStatsLayout.getCpuScalingStepCount(); + int powerBracketCount = mStatsLayout.getCpuPowerBracketCount(); + int[] scalingStepToBracketMap = mStatsLayout.getScalingStepToPowerBracketMap(); + int energyConsumerCount = mStatsLayout.getCpuClusterEnergyConsumerCount(); + List<DeviceStateEstimation> deviceStateEstimations = mPlan.deviceStateEstimations; + for (int dse = deviceStateEstimations.size() - 1; dse >= 0; dse--) { + DeviceStateEstimation deviceStateEstimation = deviceStateEstimations.get(dse); + deviceStateEstimation.intermediates = new DeviceStatsIntermediates(); + DeviceStatsIntermediates deviceStatsIntermediates = + (DeviceStatsIntermediates) deviceStateEstimation.intermediates; + deviceStatsIntermediates.timeByBracket = new long[powerBracketCount]; + deviceStatsIntermediates.powerByBracket = new double[powerBracketCount]; + + stats.getDeviceStats(mTmpDeviceStatsArray, deviceStateEstimation.stateValues); + for (int step = 0; step < cpuScalingStepCount; step++) { + if (intermediates.timeByScalingStep[step] == 0) { + continue; + } + + long timeInStep = mStatsLayout.getTimeByScalingStep(mTmpDeviceStatsArray, step); + double stepPower = intermediates.powerByScalingStep[step] * timeInStep + / intermediates.timeByScalingStep[step]; + + int bracket = scalingStepToBracketMap[step]; + deviceStatsIntermediates.timeByBracket[bracket] += timeInStep; + deviceStatsIntermediates.powerByBracket[bracket] += stepPower; + } + + if (energyConsumerCount != 0) { + adjustEstimatesUsingEnergyConsumers(intermediates, deviceStatsIntermediates); + } + + double power = 0; + for (int i = deviceStatsIntermediates.powerByBracket.length - 1; i >= 0; i--) { + power += deviceStatsIntermediates.powerByBracket[i]; + } + deviceStatsIntermediates.power = power; + mStatsLayout.setDevicePowerEstimate(mTmpDeviceStatsArray, power); + stats.setDeviceStats(deviceStateEstimation.stateValues, mTmpDeviceStatsArray); + } + } + + private void adjustEstimatesUsingEnergyConsumers( + Intermediates intermediates, DeviceStatsIntermediates deviceStatsIntermediates) { + int energyConsumerCount = mStatsLayout.getCpuClusterEnergyConsumerCount(); + if (energyConsumerCount == 0) { + return; + } + + if (intermediates.powerByEnergyConsumer == null) { + intermediates.powerByEnergyConsumer = new long[energyConsumerCount]; + } else { + Arrays.fill(intermediates.powerByEnergyConsumer, 0); + } + for (int i = 0; i < energyConsumerCount; i++) { + intermediates.powerByEnergyConsumer[mEnergyConsumerToCombinedEnergyConsumerMap[i]] += + mStatsLayout.getConsumedEnergy(mTmpDeviceStatsArray, i); + } + + for (int combinedConsumer = mCombinedEnergyConsumerToPowerBracketMap.length - 1; + combinedConsumer >= 0; combinedConsumer--) { + int[] combinedEnergyConsumerToPowerBracketMap = + mCombinedEnergyConsumerToPowerBracketMap[combinedConsumer]; + if (combinedEnergyConsumerToPowerBracketMap == null) { + continue; + } + + double consumedEnergy = uCtoMah(intermediates.powerByEnergyConsumer[combinedConsumer]); + + double totalModeledPower = 0; + for (int bracket : combinedEnergyConsumerToPowerBracketMap) { + totalModeledPower += deviceStatsIntermediates.powerByBracket[bracket]; + } + if (totalModeledPower == 0) { + continue; + } + + for (int bracket : combinedEnergyConsumerToPowerBracketMap) { + deviceStatsIntermediates.powerByBracket[bracket] = + consumedEnergy * deviceStatsIntermediates.powerByBracket[bracket] + / totalModeledPower; + } + } + } + + private void combineDeviceStateEstimates() { + for (int i = mPlan.combinedDeviceStateEstimations.size() - 1; i >= 0; i--) { + CombinedDeviceStateEstimate cdse = mPlan.combinedDeviceStateEstimations.get(i); + DeviceStatsIntermediates cdseIntermediates = new DeviceStatsIntermediates(); + cdse.intermediates = cdseIntermediates; + int bracketCount = mStatsLayout.getCpuPowerBracketCount(); + cdseIntermediates.timeByBracket = new long[bracketCount]; + cdseIntermediates.powerByBracket = new double[bracketCount]; + List<DeviceStateEstimation> deviceStateEstimations = cdse.deviceStateEstimations; + for (int j = deviceStateEstimations.size() - 1; j >= 0; j--) { + DeviceStateEstimation dse = deviceStateEstimations.get(j); + DeviceStatsIntermediates intermediates = + (DeviceStatsIntermediates) dse.intermediates; + cdseIntermediates.power += intermediates.power; + for (int k = 0; k < bracketCount; k++) { + cdseIntermediates.timeByBracket[k] += intermediates.timeByBracket[k]; + cdseIntermediates.powerByBracket[k] += intermediates.powerByBracket[k]; + } + } + } + } + + private void estimateUidPowerConsumption(PowerComponentAggregatedPowerStats stats, int uid, + UidStateEstimate uidStateEstimate) { + CombinedDeviceStateEstimate combinedDeviceStateEstimate = + uidStateEstimate.combinedDeviceStateEstimate; + DeviceStatsIntermediates cdsIntermediates = + (DeviceStatsIntermediates) combinedDeviceStateEstimate.intermediates; + for (int i = 0; i < uidStateEstimate.proportionalEstimates.size(); i++) { + UidStateProportionalEstimate proportionalEstimate = + uidStateEstimate.proportionalEstimates.get(i); + if (!stats.getUidStats(mTmpUidStatsArray, uid, proportionalEstimate.stateValues)) { + continue; + } + + double power = 0; + for (int bracket = 0; bracket < mStatsLayout.getCpuPowerBracketCount(); bracket++) { + if (cdsIntermediates.timeByBracket[bracket] == 0) { + continue; + } + + long timeInBracket = mStatsLayout.getUidTimeByPowerBracket(mTmpUidStatsArray, + bracket); + if (timeInBracket == 0) { + continue; + } + + power += cdsIntermediates.powerByBracket[bracket] * timeInBracket + / cdsIntermediates.timeByBracket[bracket]; + } + + mStatsLayout.setUidPowerEstimate(mTmpUidStatsArray, power); + stats.setUidStats(uid, proportionalEstimate.stateValues, mTmpUidStatsArray); + } + } + + @Override + public String deviceStatsToString(PowerStats.Descriptor descriptor, long[] stats) { + unpackPowerStatsDescriptor(descriptor); + StringBuilder sb = new StringBuilder(); + int cpuScalingStepCount = mStatsLayout.getCpuScalingStepCount(); + sb.append("steps: ["); + for (int step = 0; step < cpuScalingStepCount; step++) { + if (step != 0) { + sb.append(", "); + } + sb.append(mStatsLayout.getTimeByScalingStep(stats, step)); + } + int clusterCount = mStatsLayout.getCpuClusterCount(); + sb.append("] clusters: ["); + for (int cluster = 0; cluster < clusterCount; cluster++) { + if (cluster != 0) { + sb.append(", "); + } + sb.append(mStatsLayout.getTimeByCluster(stats, cluster)); + } + sb.append("] uptime: ").append(mStatsLayout.getUptime(stats)); + int energyConsumerCount = mStatsLayout.getCpuClusterEnergyConsumerCount(); + if (energyConsumerCount > 0) { + sb.append(" energy: ["); + for (int i = 0; i < energyConsumerCount; i++) { + if (i != 0) { + sb.append(", "); + } + sb.append(mStatsLayout.getConsumedEnergy(stats, i)); + } + sb.append("]"); + } + sb.append(" power: ").append( + BatteryStats.formatCharge(mStatsLayout.getDevicePowerEstimate(stats))); + return sb.toString(); + } + + @Override + public String uidStatsToString(PowerStats.Descriptor descriptor, long[] stats) { + unpackPowerStatsDescriptor(descriptor); + StringBuilder sb = new StringBuilder(); + sb.append("["); + int powerBracketCount = mStatsLayout.getCpuPowerBracketCount(); + for (int bracket = 0; bracket < powerBracketCount; bracket++) { + if (bracket != 0) { + sb.append(", "); + } + sb.append(mStatsLayout.getUidTimeByPowerBracket(stats, bracket)); + } + sb.append("] power: ").append( + BatteryStats.formatCharge(mStatsLayout.getUidPowerEstimate(stats))); + return sb.toString(); + } +} 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 376ca897fbd1..a388932cb708 100644 --- a/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java +++ b/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java @@ -16,19 +16,39 @@ package com.android.server.power.stats; +import android.hardware.power.stats.EnergyConsumer; +import android.hardware.power.stats.EnergyConsumerResult; +import android.hardware.power.stats.EnergyConsumerType; import android.os.BatteryConsumer; import android.os.Handler; import android.os.PersistableBundle; +import android.power.PowerStatsInternal; +import android.util.Slog; import android.util.SparseArray; import com.android.internal.annotations.Keep; import com.android.internal.annotations.VisibleForNative; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.Clock; import com.android.internal.os.CpuScalingPolicies; import com.android.internal.os.PowerProfile; import com.android.internal.os.PowerStats; +import com.android.server.LocalServices; import com.android.server.power.optimization.Flags; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import java.util.Locale; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.function.IntSupplier; +import java.util.function.Supplier; + /** * Collects snapshots of power-related system statistics. * <p> @@ -36,91 +56,708 @@ import com.android.server.power.optimization.Flags; * constructor. Thus the object is not thread-safe except where noted. */ public class CpuPowerStatsCollector extends PowerStatsCollector { + private static final String TAG = "CpuPowerStatsCollector"; private static final long NANOS_PER_MILLIS = 1000000; + private static final long ENERGY_UNSPECIFIED = -1; + private static final int DEFAULT_CPU_POWER_BRACKETS = 3; + private static final int DEFAULT_CPU_POWER_BRACKETS_PER_ENERGY_CONSUMER = 2; + private static final long POWER_STATS_ENERGY_CONSUMERS_TIMEOUT = 20000; + private boolean mIsInitialized; + private final CpuScalingPolicies mCpuScalingPolicies; + private final PowerProfile mPowerProfile; private final KernelCpuStatsReader mKernelCpuStatsReader; - private final int[] mScalingStepToPowerBracketMap; - private final long[] mTempUidStats; + private final Supplier<PowerStatsInternal> mPowerStatsSupplier; + private final IntSupplier mVoltageSupplier; + private final int mDefaultCpuPowerBrackets; + private final int mDefaultCpuPowerBracketsPerEnergyConsumer; + private long[] mCpuTimeByScalingStep; + private long[] mTempCpuTimeByScalingStep; + private long[] mTempUidStats; private final SparseArray<UidStats> mUidStats = new SparseArray<>(); - private final int mUidStatsSize; + private boolean mIsPerUidTimeInStateSupported; + private PowerStatsInternal mPowerStatsInternal; + private int[] mCpuEnergyConsumerIds; + private PowerStats.Descriptor mPowerStatsDescriptor; // Reusable instance - private final PowerStats mCpuPowerStats; + private PowerStats mCpuPowerStats; + private StatsArrayLayout mLayout; private long mLastUpdateTimestampNanos; + private long mLastUpdateUptimeMillis; + private int mLastVoltageMv; + private long[] mLastConsumedEnergyUws; + + /** + * Captures the positions and lengths of sections of the stats array, such as time-in-state, + * power usage estimates etc. + */ + public static class StatsArrayLayout { + private static final String EXTRA_DEVICE_TIME_BY_SCALING_STEP_POSITION = "dt"; + private static final String EXTRA_DEVICE_TIME_BY_SCALING_STEP_COUNT = "dtc"; + private static final String EXTRA_DEVICE_TIME_BY_CLUSTER_POSITION = "dc"; + private static final String EXTRA_DEVICE_TIME_BY_CLUSTER_COUNT = "dcc"; + private static final String EXTRA_DEVICE_POWER_POSITION = "dp"; + private static final String EXTRA_DEVICE_UPTIME_POSITION = "du"; + private static final String EXTRA_DEVICE_ENERGY_CONSUMERS_POSITION = "de"; + private static final String EXTRA_DEVICE_ENERGY_CONSUMERS_COUNT = "dec"; + private static final String EXTRA_UID_BRACKETS_POSITION = "ub"; + private static final String EXTRA_UID_STATS_SCALING_STEP_TO_POWER_BRACKET = "us"; + private static final String EXTRA_UID_POWER_POSITION = "up"; + + private static final double MILLI_TO_NANO_MULTIPLIER = 1000000.0; + + private int mDeviceStatsArrayLength; + private int mUidStatsArrayLength; + + private int mDeviceCpuTimeByScalingStepPosition; + private int mDeviceCpuTimeByScalingStepCount; + private int mDeviceCpuTimeByClusterPosition; + private int mDeviceCpuTimeByClusterCount; + private int mDeviceCpuUptimePosition; + private int mDeviceEnergyConsumerPosition; + private int mDeviceEnergyConsumerCount; + private int mDevicePowerEstimatePosition; + + private int mUidPowerBracketsPosition; + private int mUidPowerBracketCount; + private int[][] mEnergyConsumerToPowerBucketMaps; + private int mUidPowerEstimatePosition; + + private int[] mScalingStepToPowerBracketMap; + + public int getDeviceStatsArrayLength() { + return mDeviceStatsArrayLength; + } + + public int getUidStatsArrayLength() { + return mUidStatsArrayLength; + } + + /** + * Declare that the stats array has a section capturing CPU time per scaling step + */ + public void addDeviceSectionCpuTimeByScalingStep(int scalingStepCount) { + mDeviceCpuTimeByScalingStepPosition = mDeviceStatsArrayLength; + mDeviceCpuTimeByScalingStepCount = scalingStepCount; + mDeviceStatsArrayLength += scalingStepCount; + } + + public int getCpuScalingStepCount() { + return mDeviceCpuTimeByScalingStepCount; + } + + /** + * Saves the time duration in the <code>stats</code> element + * corresponding to the CPU scaling <code>state</code>. + */ + public void setTimeByScalingStep(long[] stats, int step, long value) { + stats[mDeviceCpuTimeByScalingStepPosition + step] = value; + } + + /** + * Extracts the time duration from the <code>stats</code> element + * corresponding to the CPU scaling <code>step</code>. + */ + public long getTimeByScalingStep(long[] stats, int step) { + return stats[mDeviceCpuTimeByScalingStepPosition + step]; + } + + /** + * Declare that the stats array has a section capturing CPU time in each cluster + */ + public void addDeviceSectionCpuTimeByCluster(int clusterCount) { + mDeviceCpuTimeByClusterCount = clusterCount; + mDeviceCpuTimeByClusterPosition = mDeviceStatsArrayLength; + mDeviceStatsArrayLength += clusterCount; + } + + public int getCpuClusterCount() { + return mDeviceCpuTimeByClusterCount; + } + + /** + * Saves the time duration in the <code>stats</code> element + * corresponding to the CPU <code>cluster</code>. + */ + public void setTimeByCluster(long[] stats, int cluster, long value) { + stats[mDeviceCpuTimeByClusterPosition + cluster] = value; + } + + /** + * Extracts the time duration from the <code>stats</code> element + * corresponding to the CPU <code>cluster</code>. + */ + public long getTimeByCluster(long[] stats, int cluster) { + return stats[mDeviceCpuTimeByClusterPosition + cluster]; + } + + /** + * Declare that the stats array has a section capturing CPU uptime + */ + public void addDeviceSectionUptime() { + mDeviceCpuUptimePosition = mDeviceStatsArrayLength++; + } + + /** + * Saves the CPU uptime duration in the corresponding <code>stats</code> element. + */ + public void setUptime(long[] stats, long value) { + stats[mDeviceCpuUptimePosition] = value; + } + + /** + * Extracts the CPU uptime duration from the corresponding <code>stats</code> element. + */ + public long getUptime(long[] stats) { + return stats[mDeviceCpuUptimePosition]; + } + + /** + * Declares that the stats array has a section capturing EnergyConsumer data from + * PowerStatsService. + */ + public void addDeviceSectionEnergyConsumers(int energyConsumerCount) { + mDeviceEnergyConsumerPosition = mDeviceStatsArrayLength; + mDeviceEnergyConsumerCount = energyConsumerCount; + mDeviceStatsArrayLength += energyConsumerCount; + } + + public int getCpuClusterEnergyConsumerCount() { + return mDeviceEnergyConsumerCount; + } + + /** + * Saves the accumulated energy for the specified rail the corresponding + * <code>stats</code> element. + */ + public void setConsumedEnergy(long[] stats, int index, long energy) { + stats[mDeviceEnergyConsumerPosition + index] = energy; + } + + /** + * Extracts the EnergyConsumer data from a device stats array for the specified + * EnergyConsumer. + */ + public long getConsumedEnergy(long[] stats, int index) { + return stats[mDeviceEnergyConsumerPosition + index]; + } + + /** + * Declare that the stats array has a section capturing a power estimate + */ + public void addDeviceSectionPowerEstimate() { + mDevicePowerEstimatePosition = mDeviceStatsArrayLength++; + } + + /** + * Converts the supplied mAh power estimate to a long and saves it in the corresponding + * element of <code>stats</code>. + */ + public void setDevicePowerEstimate(long[] stats, double power) { + stats[mDevicePowerEstimatePosition] = (long) (power * MILLI_TO_NANO_MULTIPLIER); + } + + /** + * Extracts the power estimate from a device stats array and converts it to mAh. + */ + public double getDevicePowerEstimate(long[] stats) { + return stats[mDevicePowerEstimatePosition] / MILLI_TO_NANO_MULTIPLIER; + } + + /** + * Declare that the UID stats array has a section capturing CPU time per power bracket. + */ + public void addUidSectionCpuTimeByPowerBracket(int[] scalingStepToPowerBracketMap) { + mScalingStepToPowerBracketMap = scalingStepToPowerBracketMap; + mUidPowerBracketsPosition = mUidStatsArrayLength; + updatePowerBracketCount(); + mUidStatsArrayLength += mUidPowerBracketCount; + } + + private void updatePowerBracketCount() { + mUidPowerBracketCount = 1; + for (int bracket : mScalingStepToPowerBracketMap) { + if (bracket >= mUidPowerBracketCount) { + mUidPowerBracketCount = bracket + 1; + } + } + } + + public int[] getScalingStepToPowerBracketMap() { + return mScalingStepToPowerBracketMap; + } + + public int getCpuPowerBracketCount() { + return mUidPowerBracketCount; + } + + /** + * Saves time in <code>bracket</code> in the corresponding section of <code>stats</code>. + */ + public void setUidTimeByPowerBracket(long[] stats, int bracket, long value) { + stats[mUidPowerBracketsPosition + bracket] = value; + } + + /** + * Extracts the time in <code>bracket</code> from a UID stats array. + */ + public long getUidTimeByPowerBracket(long[] stats, int bracket) { + return stats[mUidPowerBracketsPosition + bracket]; + } + + /** + * Declare that the UID stats array has a section capturing a power estimate + */ + public void addUidSectionPowerEstimate() { + mUidPowerEstimatePosition = mUidStatsArrayLength++; + } + + /** + * Converts the supplied mAh power estimate to a long and saves it in the corresponding + * element of <code>stats</code>. + */ + public void setUidPowerEstimate(long[] stats, double power) { + stats[mUidPowerEstimatePosition] = (long) (power * MILLI_TO_NANO_MULTIPLIER); + } + + /** + * Extracts the power estimate from a UID stats array and converts it to mAh. + */ + public double getUidPowerEstimate(long[] stats) { + return stats[mUidPowerEstimatePosition] / MILLI_TO_NANO_MULTIPLIER; + } + + /** + * Copies the elements of the stats array layout into <code>extras</code> + */ + public void toExtras(PersistableBundle extras) { + extras.putInt(EXTRA_DEVICE_TIME_BY_SCALING_STEP_POSITION, + mDeviceCpuTimeByScalingStepPosition); + extras.putInt(EXTRA_DEVICE_TIME_BY_SCALING_STEP_COUNT, + mDeviceCpuTimeByScalingStepCount); + extras.putInt(EXTRA_DEVICE_TIME_BY_CLUSTER_POSITION, + mDeviceCpuTimeByClusterPosition); + extras.putInt(EXTRA_DEVICE_TIME_BY_CLUSTER_COUNT, + mDeviceCpuTimeByClusterCount); + extras.putInt(EXTRA_DEVICE_UPTIME_POSITION, mDeviceCpuUptimePosition); + extras.putInt(EXTRA_DEVICE_ENERGY_CONSUMERS_POSITION, + mDeviceEnergyConsumerPosition); + extras.putInt(EXTRA_DEVICE_ENERGY_CONSUMERS_COUNT, + mDeviceEnergyConsumerCount); + extras.putInt(EXTRA_DEVICE_POWER_POSITION, mDevicePowerEstimatePosition); + extras.putInt(EXTRA_UID_BRACKETS_POSITION, mUidPowerBracketsPosition); + extras.putIntArray(EXTRA_UID_STATS_SCALING_STEP_TO_POWER_BRACKET, + mScalingStepToPowerBracketMap); + extras.putInt(EXTRA_UID_POWER_POSITION, mUidPowerEstimatePosition); + } + + /** + * Retrieves elements of the stats array layout from <code>extras</code> + */ + public void fromExtras(PersistableBundle extras) { + mDeviceCpuTimeByScalingStepPosition = + extras.getInt(EXTRA_DEVICE_TIME_BY_SCALING_STEP_POSITION); + mDeviceCpuTimeByScalingStepCount = + extras.getInt(EXTRA_DEVICE_TIME_BY_SCALING_STEP_COUNT); + mDeviceCpuTimeByClusterPosition = + extras.getInt(EXTRA_DEVICE_TIME_BY_CLUSTER_POSITION); + mDeviceCpuTimeByClusterCount = + extras.getInt(EXTRA_DEVICE_TIME_BY_CLUSTER_COUNT); + mDeviceCpuUptimePosition = extras.getInt(EXTRA_DEVICE_UPTIME_POSITION); + mDeviceEnergyConsumerPosition = extras.getInt(EXTRA_DEVICE_ENERGY_CONSUMERS_POSITION); + mDeviceEnergyConsumerCount = extras.getInt(EXTRA_DEVICE_ENERGY_CONSUMERS_COUNT); + mDevicePowerEstimatePosition = extras.getInt(EXTRA_DEVICE_POWER_POSITION); + mUidPowerBracketsPosition = extras.getInt(EXTRA_UID_BRACKETS_POSITION); + mScalingStepToPowerBracketMap = + extras.getIntArray(EXTRA_UID_STATS_SCALING_STEP_TO_POWER_BRACKET); + if (mScalingStepToPowerBracketMap == null) { + mScalingStepToPowerBracketMap = new int[mDeviceCpuTimeByScalingStepCount]; + } + updatePowerBracketCount(); + mUidPowerEstimatePosition = extras.getInt(EXTRA_UID_POWER_POSITION); + } + } public CpuPowerStatsCollector(CpuScalingPolicies cpuScalingPolicies, PowerProfile powerProfile, - Handler handler, long throttlePeriodMs) { + IntSupplier voltageSupplier, Handler handler, long throttlePeriodMs) { this(cpuScalingPolicies, powerProfile, handler, new KernelCpuStatsReader(), - throttlePeriodMs, Clock.SYSTEM_CLOCK); + () -> LocalServices.getService(PowerStatsInternal.class), voltageSupplier, + throttlePeriodMs, Clock.SYSTEM_CLOCK, DEFAULT_CPU_POWER_BRACKETS, + DEFAULT_CPU_POWER_BRACKETS_PER_ENERGY_CONSUMER); } public CpuPowerStatsCollector(CpuScalingPolicies cpuScalingPolicies, PowerProfile powerProfile, Handler handler, KernelCpuStatsReader kernelCpuStatsReader, - long throttlePeriodMs, Clock clock) { + Supplier<PowerStatsInternal> powerStatsSupplier, + IntSupplier voltageSupplier, long throttlePeriodMs, Clock clock, + int defaultCpuPowerBrackets, int defaultCpuPowerBracketsPerEnergyConsumer) { super(handler, throttlePeriodMs, clock); + mCpuScalingPolicies = cpuScalingPolicies; + mPowerProfile = powerProfile; mKernelCpuStatsReader = kernelCpuStatsReader; + mPowerStatsSupplier = powerStatsSupplier; + mVoltageSupplier = voltageSupplier; + mDefaultCpuPowerBrackets = defaultCpuPowerBrackets; + mDefaultCpuPowerBracketsPerEnergyConsumer = defaultCpuPowerBracketsPerEnergyConsumer; + + } + + /** + * Initializes the collector during the boot sequence. + */ + public void onSystemReady() { + setEnabled(Flags.streamlinedBatteryStats()); + } + + private void ensureInitialized() { + if (mIsInitialized) { + return; + } + + if (!isEnabled()) { + return; + } + + mIsPerUidTimeInStateSupported = mKernelCpuStatsReader.nativeIsSupportedFeature(); + mPowerStatsInternal = mPowerStatsSupplier.get(); - int scalingStepCount = cpuScalingPolicies.getScalingStepCount(); - mScalingStepToPowerBracketMap = new int[scalingStepCount]; + if (mPowerStatsInternal != null) { + readCpuEnergyConsumerIds(); + } else { + mCpuEnergyConsumerIds = new int[0]; + } + + int cpuScalingStepCount = mCpuScalingPolicies.getScalingStepCount(); + mCpuTimeByScalingStep = new long[cpuScalingStepCount]; + mTempCpuTimeByScalingStep = new long[cpuScalingStepCount]; + int[] scalingStepToPowerBracketMap = initPowerBrackets(); + + mLayout = new StatsArrayLayout(); + mLayout.addDeviceSectionCpuTimeByScalingStep(cpuScalingStepCount); + mLayout.addDeviceSectionCpuTimeByCluster(mCpuScalingPolicies.getPolicies().length); + mLayout.addDeviceSectionUptime(); + mLayout.addDeviceSectionEnergyConsumers(mCpuEnergyConsumerIds.length); + mLayout.addDeviceSectionPowerEstimate(); + mLayout.addUidSectionCpuTimeByPowerBracket(scalingStepToPowerBracketMap); + mLayout.addUidSectionPowerEstimate(); + + PersistableBundle extras = new PersistableBundle(); + mLayout.toExtras(extras); + + mPowerStatsDescriptor = new PowerStats.Descriptor(BatteryConsumer.POWER_COMPONENT_CPU, + mLayout.getDeviceStatsArrayLength(), mLayout.getUidStatsArrayLength(), extras); + mCpuPowerStats = new PowerStats(mPowerStatsDescriptor); + + mTempUidStats = new long[mLayout.getCpuPowerBracketCount()]; + + mIsInitialized = true; + } + + private void readCpuEnergyConsumerIds() { + EnergyConsumer[] energyConsumerInfo = mPowerStatsInternal.getEnergyConsumerInfo(); + if (energyConsumerInfo == null) { + mCpuEnergyConsumerIds = new int[0]; + return; + } + + List<EnergyConsumer> cpuEnergyConsumers = new ArrayList<>(); + for (EnergyConsumer energyConsumer : energyConsumerInfo) { + if (energyConsumer.type == EnergyConsumerType.CPU_CLUSTER) { + cpuEnergyConsumers.add(energyConsumer); + } + } + if (cpuEnergyConsumers.isEmpty()) { + return; + } + + cpuEnergyConsumers.sort(Comparator.comparing(c -> c.ordinal)); + + mCpuEnergyConsumerIds = new int[cpuEnergyConsumers.size()]; + for (int i = 0; i < mCpuEnergyConsumerIds.length; i++) { + mCpuEnergyConsumerIds[i] = cpuEnergyConsumers.get(i).id; + } + mLastConsumedEnergyUws = new long[cpuEnergyConsumers.size()]; + Arrays.fill(mLastConsumedEnergyUws, ENERGY_UNSPECIFIED); + } + + private int[] initPowerBrackets() { + if (mPowerProfile.getCpuPowerBracketCount() != PowerProfile.POWER_BRACKETS_UNSPECIFIED) { + return initPowerBracketsFromPowerProfile(); + } else if (mCpuEnergyConsumerIds.length == 0 || mCpuEnergyConsumerIds.length == 1) { + return initDefaultPowerBrackets(mDefaultCpuPowerBrackets); + } else if (mCpuScalingPolicies.getPolicies().length == mCpuEnergyConsumerIds.length) { + return initPowerBracketsByCluster(mDefaultCpuPowerBracketsPerEnergyConsumer); + } else { + Slog.i(TAG, "Assigning a single power brackets to each CPU_CLUSTER energy consumer." + + " Number of CPU clusters (" + + mCpuScalingPolicies.getPolicies().length + + ") does not match the number of energy consumers (" + + mCpuEnergyConsumerIds.length + "). " + + " Using default power bucket assignment."); + return initDefaultPowerBrackets(mDefaultCpuPowerBrackets); + } + } + + private int[] initPowerBracketsFromPowerProfile() { + int[] stepToBracketMap = new int[mCpuScalingPolicies.getScalingStepCount()]; int index = 0; - for (int policy : cpuScalingPolicies.getPolicies()) { - int[] frequencies = cpuScalingPolicies.getFrequencies(policy); + for (int policy : mCpuScalingPolicies.getPolicies()) { + int[] frequencies = mCpuScalingPolicies.getFrequencies(policy); for (int step = 0; step < frequencies.length; step++) { - int bracket = powerProfile.getCpuPowerBracketForScalingStep(policy, step); - mScalingStepToPowerBracketMap[index++] = bracket; + int bracket = mPowerProfile.getCpuPowerBracketForScalingStep(policy, step); + stepToBracketMap[index++] = bracket; + } + } + return stepToBracketMap; + } + + private int[] initPowerBracketsByCluster(int defaultBracketCountPerCluster) { + int[] stepToBracketMap = new int[mCpuScalingPolicies.getScalingStepCount()]; + int index = 0; + int bracketBase = 0; + int[] policies = mCpuScalingPolicies.getPolicies(); + for (int policy : policies) { + int[] frequencies = mCpuScalingPolicies.getFrequencies(policy); + double[] powerByStep = new double[frequencies.length]; + for (int step = 0; step < frequencies.length; step++) { + powerByStep[step] = mPowerProfile.getAveragePowerForCpuScalingStep(policy, step); + } + + int[] policyStepToBracketMap = new int[frequencies.length]; + mapScalingStepsToDefaultBrackets(policyStepToBracketMap, powerByStep, + defaultBracketCountPerCluster); + int maxBracket = 0; + for (int step = 0; step < frequencies.length; step++) { + int bracket = bracketBase + policyStepToBracketMap[step]; + stepToBracketMap[index++] = bracket; + if (bracket > maxBracket) { + maxBracket = bracket; + } + } + bracketBase = maxBracket + 1; + } + return stepToBracketMap; + } + + private int[] initDefaultPowerBrackets(int defaultCpuPowerBracketCount) { + int[] stepToBracketMap = new int[mCpuScalingPolicies.getScalingStepCount()]; + double[] powerByStep = new double[mCpuScalingPolicies.getScalingStepCount()]; + int index = 0; + int[] policies = mCpuScalingPolicies.getPolicies(); + for (int policy : policies) { + int[] frequencies = mCpuScalingPolicies.getFrequencies(policy); + for (int step = 0; step < frequencies.length; step++) { + powerByStep[index++] = mPowerProfile.getAveragePowerForCpuScalingStep(policy, step); + } + } + mapScalingStepsToDefaultBrackets(stepToBracketMap, powerByStep, + defaultCpuPowerBracketCount); + return stepToBracketMap; + } + + private static void mapScalingStepsToDefaultBrackets(int[] stepToBracketMap, + double[] powerByStep, int defaultCpuPowerBracketCount) { + double minPower = Double.MAX_VALUE; + double maxPower = Double.MIN_VALUE; + for (final double power : powerByStep) { + if (power < minPower) { + minPower = power; + } + if (power > maxPower) { + maxPower = power; + } + } + if (powerByStep.length <= defaultCpuPowerBracketCount) { + for (int index = 0; index < stepToBracketMap.length; index++) { + stepToBracketMap[index] = index; + } + } else { + final double minLogPower = Math.log(minPower); + final double logBracket = (Math.log(maxPower) - minLogPower) + / defaultCpuPowerBracketCount; + + for (int step = 0; step < powerByStep.length; step++) { + int bracket = (int) ((Math.log(powerByStep[step]) - minLogPower) / logBracket); + if (bracket >= defaultCpuPowerBracketCount) { + bracket = defaultCpuPowerBracketCount - 1; + } + stepToBracketMap[step] = bracket; } } - mUidStatsSize = powerProfile.getCpuPowerBracketCount(); - mTempUidStats = new long[mUidStatsSize]; + } + + /** + * Prints the definitions of power brackets. + */ + public void dumpCpuPowerBracketsLocked(PrintWriter pw) { + ensureInitialized(); - mCpuPowerStats = new PowerStats( - new PowerStats.Descriptor(BatteryConsumer.POWER_COMPONENT_CPU, 0, mUidStatsSize, - new PersistableBundle())); + pw.println("CPU power brackets; cluster/freq in MHz(avg current in mA):"); + for (int bracket = 0; bracket < mLayout.getCpuPowerBracketCount(); bracket++) { + pw.print(" "); + pw.print(bracket); + pw.print(": "); + pw.println(getCpuPowerBracketDescription(bracket)); + } } /** - * Initializes the collector during the boot sequence. + * Description of a CPU power bracket: which cluster/frequency combinations are included. */ - public void onSystemReady() { - setEnabled(Flags.streamlinedBatteryStats()); + @VisibleForTesting + public String getCpuPowerBracketDescription(int powerBracket) { + ensureInitialized(); + + int[] stepToPowerBracketMap = mLayout.getScalingStepToPowerBracketMap(); + StringBuilder sb = new StringBuilder(); + int index = 0; + int[] policies = mCpuScalingPolicies.getPolicies(); + for (int policy : policies) { + int[] freqs = mCpuScalingPolicies.getFrequencies(policy); + for (int step = 0; step < freqs.length; step++) { + if (stepToPowerBracketMap[index] != powerBracket) { + index++; + continue; + } + + if (sb.length() != 0) { + sb.append(", "); + } + if (policies.length > 1) { + sb.append(policy).append('/'); + } + sb.append(freqs[step] / 1000); + sb.append('('); + sb.append(String.format(Locale.US, "%.1f", + mPowerProfile.getAveragePowerForCpuScalingStep(policy, step))); + sb.append(')'); + + index++; + } + } + return sb.toString(); + } + + /** + * Returns the descriptor of PowerStats produced by this collector. + */ + @VisibleForTesting + public PowerStats.Descriptor getPowerStatsDescriptor() { + ensureInitialized(); + + return mPowerStatsDescriptor; } @Override protected PowerStats collectStats() { + ensureInitialized(); + + if (!mIsPerUidTimeInStateSupported) { + return null; + } + mCpuPowerStats.uidStats.clear(); - long newTimestampNanos = mKernelCpuStatsReader.nativeReadCpuStats( - this::processUidStats, mScalingStepToPowerBracketMap, mLastUpdateTimestampNanos, - mTempUidStats); + // TODO(b/305120724): additionally retrieve time-in-cluster for each CPU cluster + long newTimestampNanos = mKernelCpuStatsReader.nativeReadCpuStats(this::processUidStats, + mLayout.getScalingStepToPowerBracketMap(), mLastUpdateTimestampNanos, + mTempCpuTimeByScalingStep, mTempUidStats); + for (int step = mLayout.getCpuScalingStepCount() - 1; step >= 0; step--) { + mLayout.setTimeByScalingStep(mCpuPowerStats.stats, step, + mTempCpuTimeByScalingStep[step] - mCpuTimeByScalingStep[step]); + mCpuTimeByScalingStep[step] = mTempCpuTimeByScalingStep[step]; + } + mCpuPowerStats.durationMs = (newTimestampNanos - mLastUpdateTimestampNanos) / NANOS_PER_MILLIS; mLastUpdateTimestampNanos = newTimestampNanos; + + long uptimeMillis = mClock.uptimeMillis(); + long uptimeDelta = uptimeMillis - mLastUpdateUptimeMillis; + mLastUpdateUptimeMillis = uptimeMillis; + + if (uptimeDelta > mCpuPowerStats.durationMs) { + uptimeDelta = mCpuPowerStats.durationMs; + } + mLayout.setUptime(mCpuPowerStats.stats, uptimeDelta); + + if (mCpuEnergyConsumerIds.length != 0) { + collectEnergyConsumers(); + } + return mCpuPowerStats; } + private void collectEnergyConsumers() { + int voltageMv = mVoltageSupplier.getAsInt(); + if (voltageMv <= 0) { + Slog.wtf(TAG, "Unexpected battery voltage (" + voltageMv + + " mV) when querying energy consumers"); + return; + } + + int averageVoltage = mLastVoltageMv != 0 ? (mLastVoltageMv + voltageMv) / 2 : voltageMv; + mLastVoltageMv = voltageMv; + + CompletableFuture<EnergyConsumerResult[]> future = + mPowerStatsInternal.getEnergyConsumedAsync(mCpuEnergyConsumerIds); + EnergyConsumerResult[] results = null; + try { + results = future.get( + POWER_STATS_ENERGY_CONSUMERS_TIMEOUT, TimeUnit.MILLISECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + Slog.e(TAG, "Could not obtain energy consumers from PowerStatsService", e); + } + if (results == null) { + return; + } + + for (int i = 0; i < mCpuEnergyConsumerIds.length; i++) { + int id = mCpuEnergyConsumerIds[i]; + for (EnergyConsumerResult result : results) { + if (result.id == id) { + long energyDelta = mLastConsumedEnergyUws[i] != ENERGY_UNSPECIFIED + ? result.energyUWs - mLastConsumedEnergyUws[i] : 0; + if (energyDelta < 0) { + // Likely, restart of powerstats HAL + energyDelta = 0; + } + mLayout.setConsumedEnergy(mCpuPowerStats.stats, i, + uJtoUc(energyDelta, averageVoltage)); + mLastConsumedEnergyUws[i] = result.energyUWs; + break; + } + } + } + } + @VisibleForNative interface KernelCpuStatsCallback { @Keep // Called from native - void processUidStats(int uid, long[] stats); + void processUidStats(int uid, long[] timeByPowerBracket); } - private void processUidStats(int uid, long[] stats) { + private void processUidStats(int uid, long[] timeByPowerBracket) { + int powerBracketCount = mLayout.getCpuPowerBracketCount(); + UidStats uidStats = mUidStats.get(uid); if (uidStats == null) { uidStats = new UidStats(); - uidStats.stats = new long[mUidStatsSize]; - uidStats.delta = new long[mUidStatsSize]; + uidStats.timeByPowerBracket = new long[powerBracketCount]; + uidStats.stats = new long[mLayout.getUidStatsArrayLength()]; mUidStats.put(uid, uidStats); } boolean nonzero = false; - for (int i = mUidStatsSize - 1; i >= 0; i--) { - long delta = uidStats.delta[i] = stats[i] - uidStats.stats[i]; + for (int bracket = powerBracketCount - 1; bracket >= 0; bracket--) { + long delta = timeByPowerBracket[bracket] - uidStats.timeByPowerBracket[bracket]; if (delta != 0) { nonzero = true; } - uidStats.stats[i] = stats[i]; + mLayout.setUidTimeByPowerBracket(uidStats.stats, bracket, delta); + uidStats.timeByPowerBracket[bracket] = timeByPowerBracket[bracket]; } if (nonzero) { - mCpuPowerStats.uidStats.put(uid, uidStats.delta); + mCpuPowerStats.uidStats.put(uid, uidStats.stats); } } @@ -128,13 +765,15 @@ public class CpuPowerStatsCollector extends PowerStatsCollector { * Native class that retrieves CPU stats from the kernel. */ public static class KernelCpuStatsReader { + protected native boolean nativeIsSupportedFeature(); + protected native long nativeReadCpuStats(KernelCpuStatsCallback callback, int[] scalingStepToPowerBracketMap, long lastUpdateTimestampNanos, - long[] tempForUidStats); + long[] outCpuTimeByScalingStep, long[] tempForUidStats); } private static class UidStats { public long[] stats; - public long[] delta; + public long[] timeByPowerBracket; } } diff --git a/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java b/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java index 05c0a13642b4..2c7843e626c9 100644 --- a/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java +++ b/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java @@ -16,6 +16,8 @@ package com.android.server.power.stats; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.util.IndentingPrintWriter; import android.util.SparseArray; @@ -46,6 +48,8 @@ class PowerComponentAggregatedPowerStats { public final int powerComponentId; private final MultiStateStats.States[] mDeviceStateConfig; private final MultiStateStats.States[] mUidStateConfig; + @NonNull + private final AggregatedPowerStatsConfig.PowerComponent mConfig; private final int[] mDeviceStates; private final long[] mDeviceStateTimestamps; @@ -62,13 +66,20 @@ class PowerComponentAggregatedPowerStats { } PowerComponentAggregatedPowerStats(AggregatedPowerStatsConfig.PowerComponent config) { - this.powerComponentId = config.getPowerComponentId(); + mConfig = config; + powerComponentId = config.getPowerComponentId(); mDeviceStateConfig = config.getDeviceStateConfig(); mUidStateConfig = config.getUidStateConfig(); mDeviceStates = new int[mDeviceStateConfig.length]; mDeviceStateTimestamps = new long[mDeviceStateConfig.length]; } + @NonNull + public AggregatedPowerStatsConfig.PowerComponent getConfig() { + return mConfig; + } + + @Nullable public PowerStats.Descriptor getPowerStatsDescriptor() { return mPowerStatsDescriptor; } @@ -108,6 +119,16 @@ class PowerComponentAggregatedPowerStats { } } + void setDeviceStats(@AggregatedPowerStatsConfig.TrackedState int[] states, long[] values) { + mDeviceStats.setStats(states, values); + } + + void setUidStats(int uid, @AggregatedPowerStatsConfig.TrackedState int[] states, + long[] values) { + UidStats uidStats = getUidStats(uid); + uidStats.stats.setStats(states, values); + } + boolean isCompatible(PowerStats powerStats) { return mPowerStatsDescriptor == null || mPowerStatsDescriptor.equals(powerStats.descriptor); } @@ -298,7 +319,8 @@ class PowerComponentAggregatedPowerStats { if (mDeviceStats != null) { ipw.println(mPowerStatsDescriptor.name); ipw.increaseIndent(); - mDeviceStats.dump(ipw); + mDeviceStats.dump(ipw, stats -> + mConfig.getProcessor().deviceStatsToString(mPowerStatsDescriptor, stats)); ipw.decreaseIndent(); } } @@ -308,7 +330,8 @@ class PowerComponentAggregatedPowerStats { if (uidStats != null && uidStats.stats != null) { ipw.println(mPowerStatsDescriptor.name); ipw.increaseIndent(); - uidStats.stats.dump(ipw); + uidStats.stats.dump(ipw, stats -> + mConfig.getProcessor().uidStatsToString(mPowerStatsDescriptor, stats)); 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 index f374fb77cae8..2f9d5674d78a 100644 --- a/services/core/java/com/android/server/power/stats/PowerStatsAggregator.java +++ b/services/core/java/com/android/server/power/stats/PowerStatsAggregator.java @@ -16,6 +16,7 @@ package com.android.server.power.stats; import android.os.BatteryStats; +import android.util.SparseArray; import com.android.internal.os.BatteryStatsHistory; import com.android.internal.os.BatteryStatsHistoryIterator; @@ -30,11 +31,17 @@ import java.util.function.Consumer; public class PowerStatsAggregator { private final AggregatedPowerStats mStats; private final BatteryStatsHistory mHistory; + private final SparseArray<AggregatedPowerStatsProcessor> mProcessors = new SparseArray<>(); public PowerStatsAggregator(AggregatedPowerStatsConfig aggregatedPowerStatsConfig, BatteryStatsHistory history) { mStats = new AggregatedPowerStats(aggregatedPowerStatsConfig); mHistory = history; + for (AggregatedPowerStatsConfig.PowerComponent powerComponentsConfig : + aggregatedPowerStatsConfig.getPowerComponentsAggregatedStatsConfigs()) { + AggregatedPowerStatsProcessor processor = powerComponentsConfig.getProcessor(); + mProcessors.put(powerComponentsConfig.getPowerComponentId(), processor); + } } /** @@ -100,6 +107,7 @@ public class PowerStatsAggregator { if (!mStats.isCompatible(item.powerStats)) { if (lastTime > baseTime) { mStats.setDuration(lastTime - baseTime); + finish(mStats); consumer.accept(mStats); } mStats.reset(); @@ -112,9 +120,20 @@ public class PowerStatsAggregator { } if (lastTime > baseTime) { mStats.setDuration(lastTime - baseTime); + finish(mStats); consumer.accept(mStats); } mStats.reset(); // to free up memory } + + private void finish(AggregatedPowerStats stats) { + for (int i = 0; i < mProcessors.size(); i++) { + PowerComponentAggregatedPowerStats component = + stats.getPowerComponentStats(mProcessors.keyAt(i)); + if (component != null) { + mProcessors.valueAt(i).finish(component); + } + } + } } diff --git a/services/core/java/com/android/server/power/stats/PowerStatsCollector.java b/services/core/java/com/android/server/power/stats/PowerStatsCollector.java index b49c89fcf6ad..84cc21e81536 100644 --- a/services/core/java/com/android/server/power/stats/PowerStatsCollector.java +++ b/services/core/java/com/android/server/power/stats/PowerStatsCollector.java @@ -38,8 +38,9 @@ import java.util.stream.Stream; * except where noted. */ public abstract class PowerStatsCollector { + private static final int MILLIVOLTS_PER_VOLT = 1000; private final Handler mHandler; - private final Clock mClock; + protected final Clock mClock; private final long mThrottlePeriodMs; private final Runnable mCollectAndDeliverStats = this::collectAndDeliverStats; private boolean mEnabled; @@ -100,6 +101,9 @@ public abstract class PowerStatsCollector { @SuppressWarnings("GuardedBy") // Field is volatile private void collectAndDeliverStats() { PowerStats stats = collectStats(); + if (stats == null) { + return; + } for (Consumer<PowerStats> consumer : mConsumerList) { consumer.accept(stats); } @@ -180,4 +184,11 @@ public abstract class PowerStatsCollector { mHandler.post(done::open); done.block(); } + + /** Calculate charge consumption (in microcoulombs) from a given energy and voltage */ + protected long uJtoUc(long deltaEnergyUj, int avgVoltageMv) { + // To overflow, a 3.7V 10000mAh battery would need to completely drain 69244 times + // since the last snapshot. Round off to the nearest whole long. + return (deltaEnergyUj * MILLIVOLTS_PER_VOLT + (avgVoltageMv / 2)) / avgVoltageMv; + } } diff --git a/services/core/java/com/android/server/power/stats/PowerStatsScheduler.java b/services/core/java/com/android/server/power/stats/PowerStatsScheduler.java index 58619c70e0af..551302ee8f14 100644 --- a/services/core/java/com/android/server/power/stats/PowerStatsScheduler.java +++ b/services/core/java/com/android/server/power/stats/PowerStatsScheduler.java @@ -108,6 +108,9 @@ public class PowerStatsScheduler { long currentTimeMillis = mClock.currentTimeMillis(); long currentMonotonicTime = mMonotonicClock.monotonicTime(); long startTime = getLastSavedSpanEndMonotonicTime(); + if (startTime < 0) { + startTime = mBatteryStats.getHistory().getStartTime(); + } long endTimeMs = alignToWallClock(startTime + mAggregatedPowerStatsSpanDuration, mAggregatedPowerStatsSpanDuration, currentMonotonicTime, currentTimeMillis); while (endTimeMs <= currentMonotonicTime) { @@ -214,6 +217,7 @@ public class PowerStatsScheduler { return mLastSavedSpanEndMonotonicTime; } + mLastSavedSpanEndMonotonicTime = -1; for (PowerStatsSpan.Metadata metadata : mPowerStatsStore.getTableOfContents()) { if (metadata.getSections().contains(AggregatedPowerStatsSection.TYPE)) { for (PowerStatsSpan.TimeFrame timeFrame : metadata.getTimeFrames()) { diff --git a/services/core/java/com/android/server/rollback/AppDataRollbackHelper.java b/services/core/java/com/android/server/rollback/AppDataRollbackHelper.java index 7b0fe9a9abc7..a01bac688fbf 100644 --- a/services/core/java/com/android/server/rollback/AppDataRollbackHelper.java +++ b/services/core/java/com/android/server/rollback/AppDataRollbackHelper.java @@ -266,10 +266,10 @@ public class AppDataRollbackHelper { } /** - * @return {@code true} iff. {@code userId} is locked on an FBE device. + * @return {@code true} iff the credential-encrypted storage for {@code userId} is locked. */ @VisibleForTesting public boolean isUserCredentialLocked(int userId) { - return StorageManager.isFileEncrypted() && !StorageManager.isUserKeyUnlocked(userId); + return StorageManager.isFileEncrypted() && !StorageManager.isCeStorageUnlocked(userId); } } diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index 7cccf6b578ff..34bf8edc148f 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -1097,23 +1097,22 @@ class ActivityStarter { "shouldAbortBackgroundActivityStart"); BackgroundActivityStartController balController = mSupervisor.getBackgroundActivityLaunchController(); - balCode = + BackgroundActivityStartController.BalVerdict balVerdict = balController.checkBackgroundActivityStart( - callingUid, - callingPid, - callingPackage, - realCallingUid, - realCallingPid, - callerApp, - request.originatingPendingIntent, - request.backgroundStartPrivileges, - intent, - checkedOptions); - if (balCode != BAL_ALLOW_DEFAULT) { - request.logMessage.append(" (").append( - BackgroundActivityStartController.balCodeToString(balCode)) - .append(")"); - } + callingUid, + callingPid, + callingPackage, + realCallingUid, + realCallingPid, + callerApp, + request.originatingPendingIntent, + request.backgroundStartPrivileges, + intent, + checkedOptions); + balCode = balVerdict.getCode(); + request.logMessage.append(" (").append( + BackgroundActivityStartController.balCodeToString(balCode)) + .append(")"); } finally { Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER); } diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java index 1e4b258ff5c5..9b7b8de633c4 100644 --- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java +++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java @@ -204,7 +204,7 @@ public class BackgroundActivityStartController { return checkBackgroundActivityStart(callingUid, callingPid, callingPackage, realCallingUid, realCallingPid, callerApp, originatingPendingIntent, - backgroundStartPrivileges, intent, checkedOptions) == BAL_BLOCK; + backgroundStartPrivileges, intent, checkedOptions).blocks(); } private class BalState { @@ -291,8 +291,8 @@ public class BackgroundActivityStartController { return name + "[debugOnly]"; } - private String dump(@BalCode int mResultIfPiCreatorAllowsBal, - @BalCode int mResultIfPiSenderAllowsBal) { + private String dump(BalVerdict resultIfPiCreatorAllowsBal, + BalVerdict resultIfPiSenderAllowsBal) { return " [callingPackage: " + getDebugPackageName(mCallingPackage, mCallingUid) + "; callingUid: " + mCallingUid + "; appSwitchState: " + mAppSwitchState @@ -321,19 +321,64 @@ public class BackgroundActivityStartController { + (mCallerApp != null && mCallerApp.hasActivityInVisibleTask()) + "; realInVisibleTask: " + (mRealCallerApp != null && mRealCallerApp.hasActivityInVisibleTask()) - + "; resultIfPiSenderAllowsBal: " + balCodeToString(mResultIfPiSenderAllowsBal) - + "; resultIfPiCreatorAllowsBal: " - + balCodeToString(mResultIfPiCreatorAllowsBal) + + "; resultIfPiSenderAllowsBal: " + resultIfPiSenderAllowsBal + + "; resultIfPiCreatorAllowsBal: " + resultIfPiCreatorAllowsBal + "]"; } } + static class BalVerdict { + + static final BalVerdict BLOCK = new BalVerdict(BAL_BLOCK, false, "Blocked"); + private final @BalCode int mCode; + private final boolean mBackground; + private final String mMessage; + private String mProcessInfo; + + BalVerdict(@BalCode int balCode, boolean background, String message) { + this.mBackground = background; + this.mCode = balCode; + this.mMessage = message; + } + + public BalVerdict withProcessInfo(String msg, WindowProcessController process) { + mProcessInfo = msg + " (uid=" + process.mUid + ",pid=" + process.getPid() + ")"; + return this; + } + + boolean blocks() { + return mCode == BAL_BLOCK; + } + + boolean allows() { + return !blocks(); + } + + public String toString() { + StringBuilder builder = new StringBuilder(); + if (mBackground) { + builder.append("Background "); + } + builder.append("Activity start allowed: " + mMessage + "."); + builder.append("BAL Code: "); + builder.append(balCodeToString(mCode)); + if (mProcessInfo != null) { + builder.append(" "); + builder.append(mProcessInfo); + } + return builder.toString(); + } + + public @BalCode int getCode() { + return mCode; + } + } + /** * @return A code denoting which BAL rule allows an activity to be started, * or {@link #BAL_BLOCK} if the launch should be blocked */ - @BalCode - int checkBackgroundActivityStart( + BalVerdict checkBackgroundActivityStart( int callingUid, int callingPid, final String callingPackage, @@ -362,36 +407,49 @@ public class BackgroundActivityStartController { // realCallingSdkSandboxUidToAppUid should probably just be used instead (or in addition // to realCallingUid when calculating resultForRealCaller below. if (mService.hasActiveVisibleWindow(realCallingSdkSandboxUidToAppUid)) { - return logStartAllowedAndReturnCode(BAL_ALLOW_SDK_SANDBOX, - /*background*/ false, state, + BalVerdict balVerdict = new BalVerdict(BAL_ALLOW_SDK_SANDBOX, /*background*/ false, "uid in SDK sandbox has visible (non-toast) window"); + return statsLog(balVerdict, state); } } - @BalCode int resultForCaller = checkBackgroundActivityStartAllowedByCaller(state); - @BalCode int resultForRealCaller = callingUid == realCallingUid + BalVerdict resultForCaller = checkBackgroundActivityStartAllowedByCaller(state); + BalVerdict resultForRealCaller = callingUid == realCallingUid ? resultForCaller // no need to calculate again : checkBackgroundActivityStartAllowedBySender(state, checkedOptions); - if (resultForCaller != BAL_BLOCK + if (resultForCaller.allows() && checkedOptions.getPendingIntentCreatorBackgroundActivityStartMode() == ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED) { if (DEBUG_ACTIVITY_STARTS) { Slog.d(TAG, "Activity start explicitly allowed by PI creator. " + state.dump(resultForCaller, resultForRealCaller)); } - return resultForCaller; + return statsLog(resultForCaller, state); } - if (resultForRealCaller != BAL_BLOCK + if (resultForRealCaller.allows() && checkedOptions.getPendingIntentBackgroundActivityStartMode() == ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED) { if (DEBUG_ACTIVITY_STARTS) { - Slog.i(TAG, "Activity start explicitly allowed by PI sender. " + Slog.d(TAG, "Activity start explicitly allowed by PI sender. " + state.dump(resultForCaller, resultForRealCaller)); } - return resultForRealCaller; + return statsLog(resultForRealCaller, state); + } + if (resultForCaller.allows() && resultForRealCaller.allows() + && checkedOptions.getPendingIntentCreatorBackgroundActivityStartMode() + == ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED + && checkedOptions.getPendingIntentBackgroundActivityStartMode() + == ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED) { + // Both caller and real caller allow with system defined behavior + Slog.wtf(TAG, + "With Android 15 BAL hardening this activity start would be blocked" + + " (missing opt in by PI creator)! " + + state.dump(resultForCaller, resultForRealCaller)); + // return the realCaller result for backwards compatibility + return statsLog(resultForRealCaller, state); } - if (resultForCaller != BAL_BLOCK + if (resultForCaller.allows() && checkedOptions.getPendingIntentCreatorBackgroundActivityStartMode() == ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED) { // Allowed before V by creator @@ -399,9 +457,9 @@ public class BackgroundActivityStartController { "With Android 15 BAL hardening this activity start would be blocked" + " (missing opt in by PI creator)! " + state.dump(resultForCaller, resultForRealCaller)); - return resultForCaller; + return statsLog(resultForCaller, state); } - if (resultForRealCaller != BAL_BLOCK + if (resultForRealCaller.allows() && checkedOptions.getPendingIntentBackgroundActivityStartMode() == ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED) { // Allowed before U by sender @@ -410,7 +468,7 @@ public class BackgroundActivityStartController { "With Android 14 BAL hardening this activity start would be blocked" + " (missing opt in by PI sender)! " + state.dump(resultForCaller, resultForRealCaller)); - return resultForRealCaller; + return statsLog(resultForRealCaller, state); } Slog.wtf(TAG, "Without Android 14 BAL hardening this activity start would be allowed" + " (missing opt in by PI sender)! " @@ -436,15 +494,14 @@ public class BackgroundActivityStartController { state.mRealCallingUidHasAnyVisibleWindow, (originatingPendingIntent != null)); } - return BAL_BLOCK; + return statsLog(BalVerdict.BLOCK, state); } /** * @return A code denoting which BAL rule allows an activity to be started, * or {@link #BAL_BLOCK} if the launch should be blocked */ - @BalCode - int checkBackgroundActivityStartAllowedByCaller(BalState state) { + BalVerdict checkBackgroundActivityStartAllowedByCaller(BalState state) { int callingUid = state.mCallingUid; int callingPid = state.mCallingPid; final String callingPackage = state.mCallingPackage; @@ -455,15 +512,15 @@ public class BackgroundActivityStartController { if (callingUid == Process.ROOT_UID || callingAppId == Process.SYSTEM_UID || callingAppId == Process.NFC_UID) { - return logStartAllowedAndReturnCode( + return new BalVerdict( BAL_ALLOW_ALLOWLISTED_UID, /*background*/ false, - state, "Important callingUid"); + "Important callingUid"); } // Always allow home application to start activities. if (isHomeApp(callingUid, callingPackage)) { - return logStartAllowedAndReturnCode(BAL_ALLOW_ALLOWLISTED_COMPONENT, - /*background*/ false, state, + return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT, + /*background*/ false, "Home app"); } @@ -471,8 +528,8 @@ public class BackgroundActivityStartController { final WindowState imeWindow = mService.mRootWindowContainer.getCurrentInputMethodWindow(); if (imeWindow != null && callingAppId == imeWindow.mOwnerUid) { - return logStartAllowedAndReturnCode(BAL_ALLOW_ALLOWLISTED_COMPONENT, - /*background*/ false, state, + return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT, + /*background*/ false, "Active ime"); } @@ -495,8 +552,8 @@ public class BackgroundActivityStartController { && callingUidHasAnyVisibleWindow) || isCallingUidPersistentSystemProcess; if (allowCallingUidStartActivity) { - return logStartAllowedAndReturnCode(BAL_ALLOW_VISIBLE_WINDOW, - /*background*/ false, state, + return new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, + /*background*/ false, "callingUidHasAnyVisibleWindow = " + callingUid + ", isCallingUidPersistentSystemProcess = " @@ -506,30 +563,30 @@ public class BackgroundActivityStartController { // don't abort if the callingUid has START_ACTIVITIES_FROM_BACKGROUND permission if (ActivityTaskManagerService.checkPermission(START_ACTIVITIES_FROM_BACKGROUND, callingPid, callingUid) == PERMISSION_GRANTED) { - return logStartAllowedAndReturnCode(BAL_ALLOW_PERMISSION, - /*background*/ true, state, + return new BalVerdict(BAL_ALLOW_PERMISSION, + /*background*/ true, "START_ACTIVITIES_FROM_BACKGROUND permission granted"); } // don't abort if the caller has the same uid as the recents component if (mSupervisor.mRecentTasks.isCallerRecents(callingUid)) { - return logStartAllowedAndReturnCode(BAL_ALLOW_ALLOWLISTED_COMPONENT, - /*background*/ true, state, "Recents Component"); + return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT, + /*background*/ true, "Recents Component"); } // don't abort if the callingUid is the device owner if (mService.isDeviceOwner(callingUid)) { - return logStartAllowedAndReturnCode(BAL_ALLOW_ALLOWLISTED_COMPONENT, - /*background*/ true, state, "Device Owner"); + return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT, + /*background*/ true, "Device Owner"); } // don't abort if the callingUid is a affiliated profile owner if (mService.isAffiliatedProfileOwner(callingUid)) { - return logStartAllowedAndReturnCode(BAL_ALLOW_ALLOWLISTED_COMPONENT, - /*background*/ true, state, "Affiliated Profile Owner"); + return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT, + /*background*/ true, "Affiliated Profile Owner"); } // don't abort if the callingUid has companion device final int callingUserId = UserHandle.getUserId(callingUid); if (mService.isAssociatedCompanionApp(callingUserId, callingUid)) { - return logStartAllowedAndReturnCode(BAL_ALLOW_ALLOWLISTED_COMPONENT, - /*background*/ true, state, "Companion App"); + return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT, + /*background*/ true, "Companion App"); } // don't abort if the callingUid has SYSTEM_ALERT_WINDOW permission if (mService.hasSystemAlertWindowPermission(callingUid, callingPid, callingPackage)) { @@ -538,16 +595,15 @@ public class BackgroundActivityStartController { "Background activity start for " + callingPackage + " allowed because SYSTEM_ALERT_WINDOW permission is granted."); - return logStartAllowedAndReturnCode(BAL_ALLOW_SAW_PERMISSION, - /*background*/ true, state, "SYSTEM_ALERT_WINDOW permission is granted"); + return new BalVerdict(BAL_ALLOW_SAW_PERMISSION, + /*background*/ true, "SYSTEM_ALERT_WINDOW permission is granted"); } // don't abort if the callingUid and callingPackage have the // OP_SYSTEM_EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION appop if (isSystemExemptFlagEnabled() && mService.getAppOpsManager().checkOpNoThrow( AppOpsManager.OP_SYSTEM_EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION, callingUid, callingPackage) == AppOpsManager.MODE_ALLOWED) { - return logStartAllowedAndReturnCode(BAL_ALLOW_PERMISSION, - /*background*/ true, state, + return new BalVerdict(BAL_ALLOW_PERMISSION, /*background*/ true, "OP_SYSTEM_EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION appop is granted"); } @@ -555,48 +611,48 @@ public class BackgroundActivityStartController { // That's the case for PendingIntent-based starts, since the creator's process might not be // up and alive. // Don't abort if the callerApp or other processes of that uid are allowed in any way. - @BalCode int callerAppAllowsBal = checkProcessAllowsBal(callerApp, state); - if (callerAppAllowsBal != BAL_BLOCK) { + BalVerdict callerAppAllowsBal = checkProcessAllowsBal(callerApp, state); + if (callerAppAllowsBal.allows()) { return callerAppAllowsBal; } // If we are here, it means all exemptions based on the creator failed - return BAL_BLOCK; + return BalVerdict.BLOCK; } /** * @return A code denoting which BAL rule allows an activity to be started, * or {@link #BAL_BLOCK} if the launch should be blocked */ - @BalCode - int checkBackgroundActivityStartAllowedBySender( + BalVerdict checkBackgroundActivityStartAllowedBySender( BalState state, ActivityOptions checkedOptions) { int realCallingUid = state.mRealCallingUid; + BackgroundStartPrivileges backgroundStartPrivileges = state.mBackgroundStartPrivileges; if (PendingIntentRecord.isPendingIntentBalAllowedByPermission(checkedOptions) && ActivityManager.checkComponentPermission( android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND, realCallingUid, -1, true) == PackageManager.PERMISSION_GRANTED) { - return logStartAllowedAndReturnCode(BAL_ALLOW_PENDING_INTENT, - /*background*/ false, state, + return new BalVerdict(BAL_ALLOW_PENDING_INTENT, + /*background*/ false, "realCallingUid has BAL permission. realCallingUid: " + realCallingUid); } // don't abort if the realCallingUid has a visible window // TODO(b/171459802): We should check appSwitchAllowed also if (state.mRealCallingUidHasAnyVisibleWindow) { - return logStartAllowedAndReturnCode(BAL_ALLOW_PENDING_INTENT, - /*background*/ false, state, + return new BalVerdict(BAL_ALLOW_PENDING_INTENT, + /*background*/ false, "realCallingUid has visible (non-toast) window. realCallingUid: " + realCallingUid); } // if the realCallingUid is a persistent system process, abort if the IntentSender // wasn't allowed to start an activity if (state.mIsRealCallingUidPersistentSystemProcess - && state.mBackgroundStartPrivileges.allowsBackgroundActivityStarts()) { - return logStartAllowedAndReturnCode(BAL_ALLOW_PENDING_INTENT, - /*background*/ false, state, + && backgroundStartPrivileges.allowsBackgroundActivityStarts()) { + return new BalVerdict(BAL_ALLOW_PENDING_INTENT, + /*background*/ false, "realCallingUid is persistent system process AND intent " + "sender allowed (allowBackgroundActivityStart = true). " + "realCallingUid: " + realCallingUid); @@ -604,21 +660,21 @@ public class BackgroundActivityStartController { // don't abort if the realCallingUid is an associated companion app if (mService.isAssociatedCompanionApp( UserHandle.getUserId(realCallingUid), realCallingUid)) { - return logStartAllowedAndReturnCode(BAL_ALLOW_PENDING_INTENT, - /*background*/ false, state, + return new BalVerdict(BAL_ALLOW_PENDING_INTENT, + /*background*/ false, "realCallingUid is a companion app. " + "realCallingUid: " + realCallingUid); } // don't abort if the callerApp or other processes of that uid are allowed in any way - @BalCode int realCallerAppAllowsBal = + BalVerdict realCallerAppAllowsBal = checkProcessAllowsBal(state.mRealCallerApp, state); - if (realCallerAppAllowsBal != BAL_BLOCK) { + if (realCallerAppAllowsBal.allows()) { return realCallerAppAllowsBal; } // If we are here, it means all exemptions based on PI sender failed - return BAL_BLOCK; + return BalVerdict.BLOCK; } /** @@ -628,18 +684,16 @@ public class BackgroundActivityStartController { * String, int, boolean, boolean, boolean, long, long, long)} for details on the * exceptions. */ - private @BalCode int checkProcessAllowsBal(WindowProcessController app, BalState state) { + private BalVerdict checkProcessAllowsBal(WindowProcessController app, + BalState state) { if (app == null) { - return BAL_BLOCK; + return BalVerdict.BLOCK; } // first check the original calling process - final @BalCode int balAllowedForCaller = app + final BalVerdict balAllowedForCaller = app .areBackgroundActivityStartsAllowed(state.mAppSwitchState); - if (balAllowedForCaller != BAL_BLOCK) { - return logStartAllowedAndReturnCode(balAllowedForCaller, - /*background*/ true, state, - "callerApp process (pid = " + app.getPid() - + ", uid = " + app.mUid + ") is allowed"); + if (balAllowedForCaller.allows()) { + return balAllowedForCaller.withProcessInfo("callerApp process", app); } else { // only if that one wasn't allowed, check the other ones final ArraySet<WindowProcessController> uidProcesses = @@ -647,18 +701,17 @@ public class BackgroundActivityStartController { if (uidProcesses != null) { for (int i = uidProcesses.size() - 1; i >= 0; i--) { final WindowProcessController proc = uidProcesses.valueAt(i); - int balAllowedForUid = proc.areBackgroundActivityStartsAllowed( - state.mAppSwitchState); - if (proc != app && balAllowedForUid != BAL_BLOCK) { - return logStartAllowedAndReturnCode(balAllowedForUid, - /*background*/ true, state, - "process" + proc.getPid() + " from uid " + app.mUid - + " is allowed"); + if (proc != app) { + BalVerdict balAllowedForUid = proc.areBackgroundActivityStartsAllowed( + state.mAppSwitchState); + if (balAllowedForUid.allows()) { + return balAllowedForCaller.withProcessInfo("process", proc); + } } } } } - return BAL_BLOCK; + return BalVerdict.BLOCK; } /** @@ -1156,36 +1209,6 @@ public class BackgroundActivityStartController { return joiner.toString(); } - static @BalCode int logStartAllowedAndReturnCode(@BalCode int code, - boolean background, int callingUid, int realCallingUid, Intent intent, int pid, - String msg) { - return logStartAllowedAndReturnCode(code, background, callingUid, realCallingUid, - intent, DEBUG_ACTIVITY_STARTS ? ("[Process(" + pid + ")]" + msg) : ""); - } - - private static @BalCode int logStartAllowedAndReturnCode(@BalCode int code, - boolean background, BalState state, String msg) { - return logStartAllowedAndReturnCode(code, background, state.mCallingUid, - state.mRealCallingUid, state.mIntent, msg); - } - - private static @BalCode int logStartAllowedAndReturnCode(@BalCode int code, - boolean background, int callingUid, int realCallingUid, Intent intent, String msg) { - statsLogBalAllowed(code, callingUid, realCallingUid, intent); - if (DEBUG_ACTIVITY_STARTS) { - StringBuilder builder = new StringBuilder(); - if (background) { - builder.append("Background "); - } - builder.append("Activity start allowed: " + msg + ". callingUid: " - + callingUid + ". "); - builder.append("BAL Code: "); - builder.append(balCodeToString(code)); - Slog.i(TAG, builder.toString()); - } - return code; - } - private static boolean isSystemExemptFlagEnabled() { return DeviceConfig.getBoolean( NAMESPACE_WINDOW_MANAGER, @@ -1193,8 +1216,12 @@ public class BackgroundActivityStartController { /* defaultValue= */ true); } - private static void statsLogBalAllowed( - @BalCode int code, int callingUid, int realCallingUid, Intent intent) { + private static BalVerdict statsLog(BalVerdict finalVerdict, BalState state) { + @BalCode int code = finalVerdict.getCode(); + int callingUid = state.mCallingUid; + int realCallingUid = state.mRealCallingUid; + Intent intent = state.mIntent; + if (code == BAL_ALLOW_PENDING_INTENT && (callingUid == Process.SYSTEM_UID || realCallingUid == Process.SYSTEM_UID)) { String activityName = @@ -1214,6 +1241,7 @@ public class BackgroundActivityStartController { callingUid, realCallingUid); } + return finalVerdict; } /** diff --git a/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java b/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java index 527edc13931a..e849589f4661 100644 --- a/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java +++ b/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java @@ -27,7 +27,6 @@ import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_ import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_GRACE_PERIOD; import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_PERMISSION; import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_VISIBLE_WINDOW; -import static com.android.server.wm.BackgroundActivityStartController.BAL_BLOCK; import static java.util.Objects.requireNonNull; @@ -49,6 +48,7 @@ import android.util.IntArray; import android.util.Slog; import com.android.internal.annotations.GuardedBy; +import com.android.server.wm.BackgroundActivityStartController.BalVerdict; import java.io.PrintWriter; import java.util.ArrayList; @@ -96,37 +96,33 @@ class BackgroundLaunchProcessController { mBackgroundActivityStartCallback = callback; } - @BackgroundActivityStartController.BalCode - int areBackgroundActivityStartsAllowed(int pid, int uid, String packageName, + BalVerdict areBackgroundActivityStartsAllowed( + int pid, int uid, String packageName, int appSwitchState, boolean isCheckingForFgsStart, boolean hasActivityInVisibleTask, boolean hasBackgroundActivityStartPrivileges, long lastStopAppSwitchesTime, long lastActivityLaunchTime, long lastActivityFinishTime) { // Allow if the proc is instrumenting with background activity starts privs. if (hasBackgroundActivityStartPrivileges) { - return BackgroundActivityStartController.logStartAllowedAndReturnCode( - BAL_ALLOW_PERMISSION, /*background*/ true, uid, uid, /*intent*/ null, - pid, "Activity start allowed: process instrumenting with background " - + "activity starts privileges"); + return new BalVerdict(BAL_ALLOW_PERMISSION, /*background*/ true, + "Activity start allowed: process instrumenting with background " + + "activity starts privileges"); } // Allow if the flag was explicitly set. if (isBackgroundStartAllowedByToken(uid, packageName, isCheckingForFgsStart)) { - return BackgroundActivityStartController.logStartAllowedAndReturnCode( - BAL_ALLOW_PERMISSION, /*background*/ true, uid, uid, /*intent*/ null, - pid, "Activity start allowed: process allowed by token"); + return new BalVerdict(BAL_ALLOW_PERMISSION, /*background*/ true, + "Activity start allowed: process allowed by token"); } // Allow if the caller is bound by a UID that's currently foreground. if (isBoundByForegroundUid()) { - return BackgroundActivityStartController.logStartAllowedAndReturnCode( - BAL_ALLOW_VISIBLE_WINDOW, /*background*/ false, uid, uid, /*intent*/ null, - pid, "Activity start allowed: process bound by foreground uid"); + return new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, /*background*/ false, + "Activity start allowed: process bound by foreground uid"); } // Allow if the caller has an activity in any foreground task. if (hasActivityInVisibleTask && (appSwitchState == APP_SWITCH_ALLOW || appSwitchState == APP_SWITCH_FG_ONLY)) { - return BackgroundActivityStartController.logStartAllowedAndReturnCode( - BAL_ALLOW_FOREGROUND, /*background*/ false, uid, uid, /*intent*/ null, - pid, "Activity start allowed: process has activity in foreground task"); + return new BalVerdict(BAL_ALLOW_FOREGROUND, /*background*/ false, + "Activity start allowed: process has activity in foreground task"); } // If app switching is not allowed, we ignore all the start activity grace period @@ -141,9 +137,8 @@ class BackgroundLaunchProcessController { // let app to be able to start background activity even it's in grace period. if (lastActivityLaunchTime > lastStopAppSwitchesTime || lastActivityFinishTime > lastStopAppSwitchesTime) { - return BackgroundActivityStartController.logStartAllowedAndReturnCode( - BAL_ALLOW_GRACE_PERIOD, /*background*/ true, uid, uid, /*intent*/ null, - pid, "Activity start allowed: within " + return new BalVerdict(BAL_ALLOW_GRACE_PERIOD, /*background*/ true, + "Activity start allowed: within " + ACTIVITY_BG_START_GRACE_PERIOD_MS + "ms grace period"); } if (DEBUG_ACTIVITY_STARTS) { @@ -154,7 +149,7 @@ class BackgroundLaunchProcessController { } } - return BAL_BLOCK; + return BalVerdict.BLOCK; } /** diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index e7893da60847..f31490060fc8 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -4716,6 +4716,17 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp scheduleAnimation(); mWmService.mH.post(() -> InputMethodManagerInternal.get().onImeParentChanged()); + } else if (mImeControlTarget != null && mImeControlTarget == mImeLayeringTarget) { + // Even if the IME surface parent is not changed, the layer target belonging to the + // parent may have changes. Then attempt to reassign if the IME control target is + // possible to be the relative layer. + final SurfaceControl lastRelativeLayer = mImeWindowsContainer.getLastRelativeLayer(); + if (lastRelativeLayer != mImeLayeringTarget.mSurfaceControl) { + assignRelativeLayerForIme(getSyncTransaction(), false /* forceUpdate */); + if (lastRelativeLayer != mImeWindowsContainer.getLastRelativeLayer()) { + scheduleAnimation(); + } + } } } diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java index ff766beee337..34ae370a6099 100644 --- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java @@ -157,6 +157,15 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr */ private final ArrayMap<IBinder, Integer> mDeferredTransitions = new ArrayMap<>(); + /** + * Map from {@link TaskFragmentTransaction#getTransactionToken()} to a + * {@link Transition.ReadyCondition} that is waiting for the {@link TaskFragmentTransaction} + * to complete. + * @see #onTransactionHandled + */ + private final ArrayMap<IBinder, Transition.ReadyCondition> mInFlightTransactions = + new ArrayMap<>(); + TaskFragmentOrganizerState(@NonNull ITaskFragmentOrganizer organizer, int pid, int uid, boolean isSystemOrganizer) { mOrganizer = organizer; @@ -173,7 +182,7 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr @Override public void binderDied() { synchronized (mGlobalLock) { - removeOrganizer(mOrganizer); + removeOrganizer(mOrganizer, "client died"); } } @@ -195,7 +204,7 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr mOrganizedTaskFragments.remove(taskFragment); } - void dispose() { + void dispose(@NonNull String reason) { boolean wasVisible = false; for (int i = mOrganizedTaskFragments.size() - 1; i >= 0; i--) { final TaskFragment taskFragment = mOrganizedTaskFragments.get(i); @@ -236,6 +245,10 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr // Cleanup any running transaction to unblock the current transition. onTransactionFinished(mDeferredTransitions.keyAt(i)); } + for (int i = mInFlightTransactions.size() - 1; i >= 0; i--) { + // Cleanup any in-flight transactions to unblock the transition. + mInFlightTransactions.valueAt(i).meetAlternate("disposed(" + reason + ")"); + } mOrganizer.asBinder().unlinkToDeath(this, 0 /* flags */); } @@ -398,11 +411,6 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr Slog.d(TAG, "Exception sending TaskFragmentTransaction", e); return; } - onTransactionStarted(transaction.getTransactionToken()); - } - - /** Called when the transaction is sent to the organizer. */ - void onTransactionStarted(@NonNull IBinder transactionToken) { if (!mWindowOrganizerController.getTransitionController().isCollecting()) { return; } @@ -410,9 +418,13 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr .getCollectingTransitionId(); ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Defer transition id=%d for TaskFragmentTransaction=%s", transitionId, - transactionToken); - mDeferredTransitions.put(transactionToken, transitionId); + transaction.getTransactionToken()); + mDeferredTransitions.put(transaction.getTransactionToken(), transitionId); mWindowOrganizerController.getTransitionController().deferTransitionReady(); + final Transition.ReadyCondition transactionApplied = new Transition.ReadyCondition( + "task-fragment transaction", transaction); + mWindowOrganizerController.getTransitionController().waitFor(transactionApplied); + mInFlightTransactions.put(transaction.getTransactionToken(), transactionApplied); } /** Called when the transaction is finished. */ @@ -496,7 +508,7 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Unregister task fragment organizer=%s uid=%d pid=%d", organizer.asBinder(), uid, pid); - removeOrganizer(organizer); + removeOrganizer(organizer, "unregistered"); } } finally { Binder.restoreCallingIdentity(origId); @@ -564,6 +576,11 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr : null; if (state != null) { state.onTransactionFinished(transactionToken); + final Transition.ReadyCondition condition = + state.mInFlightTransactions.remove(transactionToken); + if (condition != null) { + condition.meet(); + } } } } @@ -777,7 +794,8 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr return mTaskFragmentOrganizerState.containsKey(organizer.asBinder()); } - private void removeOrganizer(@NonNull ITaskFragmentOrganizer organizer) { + private void removeOrganizer(@NonNull ITaskFragmentOrganizer organizer, + @NonNull String reason) { final TaskFragmentOrganizerState state = mTaskFragmentOrganizerState.get( organizer.asBinder()); if (state == null) { @@ -788,7 +806,7 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr // event dispatch as result of surface placement. mPendingTaskFragmentEvents.remove(organizer.asBinder()); // remove all of the children of the organized TaskFragment - state.dispose(); + state.dispose(reason); mTaskFragmentOrganizerState.remove(organizer.asBinder()); } diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 4a0f44b58ecf..8f884d2fe29e 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -2706,6 +2706,10 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< return mLastLayer; } + SurfaceControl getLastRelativeLayer() { + return mLastRelativeToLayer; + } + protected void setRelativeLayer(Transaction t, SurfaceControl relativeTo, int layer) { if (mSurfaceFreezer.hasLeash()) { // When the freezer has created animation leash parent for the window, set the layer diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java index e769a2763fc5..a74a707d5ef9 100644 --- a/services/core/java/com/android/server/wm/WindowProcessController.java +++ b/services/core/java/com/android/server/wm/WindowProcessController.java @@ -42,7 +42,6 @@ import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM; import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.ActivityTaskManagerService.INSTRUMENTATION_KEY_DISPATCHING_TIMEOUT_MILLIS; import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_NONE; -import static com.android.server.wm.BackgroundActivityStartController.BAL_BLOCK; import static com.android.server.wm.WindowManagerService.MY_PID; import static java.util.Objects.requireNonNull; @@ -631,22 +630,23 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio */ @HotPath(caller = HotPath.START_SERVICE) public boolean areBackgroundFgsStartsAllowed() { - return areBackgroundActivityStartsAllowed(mAtm.getBalAppSwitchesState(), - true /* isCheckingForFgsStart */) != BAL_BLOCK; + return areBackgroundActivityStartsAllowed( + mAtm.getBalAppSwitchesState(), + true /* isCheckingForFgsStart */).allows(); } - @BackgroundActivityStartController.BalCode - int areBackgroundActivityStartsAllowed(int appSwitchState) { - return areBackgroundActivityStartsAllowed(appSwitchState, + BackgroundActivityStartController.BalVerdict areBackgroundActivityStartsAllowed( + int appSwitchState) { + return areBackgroundActivityStartsAllowed( + appSwitchState, false /* isCheckingForFgsStart */); } - @BackgroundActivityStartController.BalCode - private int areBackgroundActivityStartsAllowed(int appSwitchState, - boolean isCheckingForFgsStart) { - return mBgLaunchController.areBackgroundActivityStartsAllowed(mPid, mUid, mInfo.packageName, - appSwitchState, isCheckingForFgsStart, hasActivityInVisibleTask(), - mInstrumentingWithBackgroundActivityStartPrivileges, + private BackgroundActivityStartController.BalVerdict areBackgroundActivityStartsAllowed( + int appSwitchState, boolean isCheckingForFgsStart) { + return mBgLaunchController.areBackgroundActivityStartsAllowed(mPid, mUid, + mInfo.packageName, appSwitchState, isCheckingForFgsStart, + hasActivityInVisibleTask(), mInstrumentingWithBackgroundActivityStartPrivileges, mAtm.getLastStopAppSwitchesTime(), mLastActivityLaunchTime, mLastActivityFinishTime); } diff --git a/services/core/jni/com_android_server_power_stats_CpuPowerStatsCollector.cpp b/services/core/jni/com_android_server_power_stats_CpuPowerStatsCollector.cpp index a6084ea17806..cac13ebef410 100644 --- a/services/core/jni/com_android_server_power_stats_CpuPowerStatsCollector.cpp +++ b/services/core/jni/com_android_server_power_stats_CpuPowerStatsCollector.cpp @@ -34,9 +34,12 @@ namespace android { static constexpr uint64_t NSEC_PER_MSEC = 1000000; -static int extractUidStats(JNIEnv *env, std::vector<std::vector<uint64_t>> ×, - ScopedIntArrayRO &scopedScalingStepToPowerBracketMap, - jlongArray tempForUidStats); +static int flatten(JNIEnv *env, const std::vector<std::vector<uint64_t>> ×, + jlongArray outArray); + +static int combineByBracket(JNIEnv *env, const std::vector<std::vector<uint64_t>> ×, + ScopedIntArrayRO &scopedScalingStepToPowerBracketMap, + jlongArray outBrackets); static bool initialized = false; static jclass class_KernelCpuStatsCallback; @@ -62,25 +65,43 @@ static int init(JNIEnv *env) { return OK; } +static jboolean nativeIsSupportedFeature(JNIEnv *env) { + if (!android::bpf::startTrackingUidTimes()) { + return false; + } + auto totalByScalingStep = android::bpf::getTotalCpuFreqTimes(); + return totalByScalingStep.has_value(); +} + static jlong nativeReadCpuStats(JNIEnv *env, [[maybe_unused]] jobject zis, jobject callback, jintArray scalingStepToPowerBracketMap, - jlong lastUpdateTimestampNanos, jlongArray tempForUidStats) { + jlong lastUpdateTimestampNanos, jlongArray cpuTimeByScalingStep, + jlongArray tempForUidStats) { + ScopedIntArrayRO scopedScalingStepToPowerBracketMap(env, scalingStepToPowerBracketMap); + if (!initialized) { if (init(env) == EXCEPTION) { return 0L; } } + auto totalByScalingStep = android::bpf::getTotalCpuFreqTimes(); + if (!totalByScalingStep) { + jniThrowExceptionFmt(env, "java/lang/RuntimeException", "Unsupported kernel feature"); + return EXCEPTION; + } + + if (flatten(env, *totalByScalingStep, cpuTimeByScalingStep) == EXCEPTION) { + return 0L; + } + uint64_t newLastUpdate = lastUpdateTimestampNanos; auto data = android::bpf::getUidsUpdatedCpuFreqTimes(&newLastUpdate); if (!data.has_value()) return lastUpdateTimestampNanos; - ScopedIntArrayRO scopedScalingStepToPowerBracketMap(env, scalingStepToPowerBracketMap); - for (auto &[uid, times] : *data) { - int status = - extractUidStats(env, times, scopedScalingStepToPowerBracketMap, tempForUidStats); - if (status == EXCEPTION) { + if (combineByBracket(env, times, scopedScalingStepToPowerBracketMap, tempForUidStats) == + EXCEPTION) { return 0L; } env->CallVoidMethod(callback, method_KernelCpuStatsCallback_processUidStats, (jint)uid, @@ -89,13 +110,34 @@ static jlong nativeReadCpuStats(JNIEnv *env, [[maybe_unused]] jobject zis, jobje return newLastUpdate; } -static int extractUidStats(JNIEnv *env, std::vector<std::vector<uint64_t>> ×, - ScopedIntArrayRO &scopedScalingStepToPowerBracketMap, - jlongArray tempForUidStats) { - ScopedLongArrayRW scopedTempForStats(env, tempForUidStats); - uint64_t *arrayForStats = reinterpret_cast<uint64_t *>(scopedTempForStats.get()); - const uint8_t statsSize = scopedTempForStats.size(); - memset(arrayForStats, 0, statsSize * sizeof(uint64_t)); +static int flatten(JNIEnv *env, const std::vector<std::vector<uint64_t>> ×, + jlongArray outArray) { + ScopedLongArrayRW scopedOutArray(env, outArray); + const uint8_t scalingStepCount = scopedOutArray.size(); + uint64_t *out = reinterpret_cast<uint64_t *>(scopedOutArray.get()); + uint32_t scalingStep = 0; + for (const auto &subVec : times) { + for (uint32_t i = 0; i < subVec.size(); ++i) { + if (scalingStep >= scalingStepCount) { + jniThrowExceptionFmt(env, "java/lang/IndexOutOfBoundsException", + "Array is too short, size=%u, scalingStep=%u", + scalingStepCount, scalingStep); + return EXCEPTION; + } + out[scalingStep] = subVec[i] / NSEC_PER_MSEC; + scalingStep++; + } + } + return OK; +} + +static int combineByBracket(JNIEnv *env, const std::vector<std::vector<uint64_t>> ×, + ScopedIntArrayRO &scopedScalingStepToPowerBracketMap, + jlongArray outBrackets) { + ScopedLongArrayRW scopedOutBrackets(env, outBrackets); + uint64_t *brackets = reinterpret_cast<uint64_t *>(scopedOutBrackets.get()); + const uint8_t statsSize = scopedOutBrackets.size(); + memset(brackets, 0, statsSize * sizeof(uint64_t)); const uint8_t scalingStepCount = scopedScalingStepToPowerBracketMap.size(); uint32_t scalingStep = 0; @@ -108,14 +150,14 @@ static int extractUidStats(JNIEnv *env, std::vector<std::vector<uint64_t>> &time scalingStepCount, scalingStep); return EXCEPTION; } - uint32_t bucket = scopedScalingStepToPowerBracketMap[scalingStep]; - if (bucket >= statsSize) { + uint32_t bracket = scopedScalingStepToPowerBracketMap[scalingStep]; + if (bracket >= statsSize) { jniThrowExceptionFmt(env, "java/lang/IndexOutOfBoundsException", - "UidStats array is too short, length=%u, bucket[%u]=%u", - statsSize, scalingStep, bucket); + "Bracket array is too short, length=%u, bracket[%u]=%u", + statsSize, scalingStep, bracket); return EXCEPTION; } - arrayForStats[bucket] += subVec[i] / NSEC_PER_MSEC; + brackets[bracket] += subVec[i] / NSEC_PER_MSEC; scalingStep++; } } @@ -123,7 +165,8 @@ static int extractUidStats(JNIEnv *env, std::vector<std::vector<uint64_t>> &time } static const JNINativeMethod method_table[] = { - {"nativeReadCpuStats", "(L" JAVA_CLASS_KERNEL_CPU_STATS_CALLBACK ";[IJ[J)J", + {"nativeIsSupportedFeature", "()Z", (void *)nativeIsSupportedFeature}, + {"nativeReadCpuStats", "(L" JAVA_CLASS_KERNEL_CPU_STATS_CALLBACK ";[IJ[J[J)J", (void *)nativeReadCpuStats}, }; diff --git a/services/smartspace/java/com/android/server/smartspace/SmartspaceManagerService.java b/services/smartspace/java/com/android/server/smartspace/SmartspaceManagerService.java index ca57f51088f3..8b5d7f09f6fc 100644 --- a/services/smartspace/java/com/android/server/smartspace/SmartspaceManagerService.java +++ b/services/smartspace/java/com/android/server/smartspace/SmartspaceManagerService.java @@ -31,6 +31,7 @@ import android.app.smartspace.ISmartspaceManager; import android.app.smartspace.SmartspaceConfig; import android.app.smartspace.SmartspaceSessionId; import android.app.smartspace.SmartspaceTargetEvent; +import android.app.smartspace.flags.Flags; import android.content.Context; import android.os.Binder; import android.os.IBinder; @@ -165,7 +166,8 @@ public class SmartspaceManagerService extends } Context ctx = getContext(); if (!(ctx.checkCallingPermission(MANAGE_SMARTSPACE) == PERMISSION_GRANTED - || ctx.checkCallingPermission(ACCESS_SMARTSPACE) == PERMISSION_GRANTED + || (Flags.accessSmartspace() + && ctx.checkCallingPermission(ACCESS_SMARTSPACE) == PERMISSION_GRANTED) || mServiceNameResolver.isTemporary(userId) || mActivityTaskManagerInternal.isCallerRecents(Binder.getCallingUid()))) { diff --git a/services/tests/powerstatstests/res/xml/power_profile_test.xml b/services/tests/powerstatstests/res/xml/power_profile_test.xml new file mode 100644 index 000000000000..ecd88614d920 --- /dev/null +++ b/services/tests/powerstatstests/res/xml/power_profile_test.xml @@ -0,0 +1,101 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2022 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. + --> + +<device name="Android"> + <!-- This is the battery capacity in mAh --> + <item name="battery.capacity">3000</item> + + <!-- Power consumption when CPU is suspended --> + <item name="cpu.suspend">5</item> + <!-- Additional power consumption when CPU is in a kernel idle loop --> + <item name="cpu.idle">1.11</item> + <!-- Additional power consumption by CPU excluding cluster and core when running --> + <item name="cpu.active">2.55</item> + + <!-- Additional power consumption of CPU policy0 itself when running on related cores --> + <item name="cpu.scaling_policy_power.policy0">2.11</item> + <!-- Additional power consumption of CPU policy4 itself when running on related cores --> + <item name="cpu.scaling_policy_power.policy4">2.22</item> + + <!-- Additional power used by a CPU related to policy3 when running at different + speeds. --> + <array name="cpu.scaling_step_power.policy0"> + <value>10</value> <!-- 300 MHz CPU speed --> + <value>20</value> <!-- 1000 MHz CPU speed --> + <value>30</value> <!-- 1900 MHz CPU speed --> + </array> + <!-- Additional power used by a CPU related to policy3 when running at different + speeds. --> + <array name="cpu.scaling_step_power.policy4"> + <value>25</value> <!-- 300 MHz CPU speed --> + <value>35</value> <!-- 1000 MHz CPU speed --> + <value>50</value> <!-- 2500 MHz CPU speed --> + <value>60</value> <!-- 3000 MHz CPU speed --> + </array> + + <!-- Power used by display unit in ambient display mode, including back lighting--> + <item name="ambient.on">0.5</item> + <!-- Additional power used when screen is turned on at minimum brightness --> + <item name="screen.on">100</item> + <!-- Additional power used when screen is at maximum brightness, compared to + screen at minimum brightness --> + <item name="screen.full">800</item> + + <!-- Average power used by the camera flash module when on --> + <item name="camera.flashlight">500</item> + <!-- Average power use by the camera subsystem for a typical camera + application. Intended as a rough estimate for an application running a + preview and capturing approximately 10 full-resolution pictures per + minute. --> + <item name="camera.avg">600</item> + + <!-- Additional power used by the audio hardware, probably due to DSP --> + <item name="audio">100.0</item> + + <!-- Additional power used by the video hardware, probably due to DSP --> + <item name="video">150.0</item> <!-- ~50mA --> + + <!-- Additional power used when GPS is acquiring a signal --> + <item name="gps.on">10</item> + + <!-- Additional power used when cellular radio is transmitting/receiving --> + <item name="radio.active">60</item> + <!-- Additional power used when cellular radio is paging the tower --> + <item name="radio.scanning">3</item> + <!-- Additional power used when the cellular radio is on. Multi-value entry, + one per signal strength (no signal, weak, moderate, strong) --> + <array name="radio.on"> <!-- Strength 0 to BINS-1 --> + <value>6</value> <!-- none --> + <value>5</value> <!-- poor --> + <value>4</value> <!-- moderate --> + <value>3</value> <!-- good --> + <value>3</value> <!-- great --> + </array> + + <!-- Cellular modem related values. These constants are deprecated, but still supported and + need to be tested --> + <item name="modem.controller.sleep">123</item> + <item name="modem.controller.idle">456</item> + <item name="modem.controller.rx">789</item> + <array name="modem.controller.tx"> <!-- Strength 0 to 4 --> + <value>10</value> + <value>20</value> + <value>30</value> + <value>40</value> + <value>50</value> + </array> +</device>
\ No newline at end of file diff --git a/core/tests/coretests/res/xml/power_profile_test_power_brackets.xml b/services/tests/powerstatstests/res/xml/power_profile_test_power_brackets.xml index a46c6083009c..a46c6083009c 100644 --- a/core/tests/coretests/res/xml/power_profile_test_power_brackets.xml +++ b/services/tests/powerstatstests/res/xml/power_profile_test_power_brackets.xml diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/AggregatePowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/AggregatePowerStatsProcessorTest.java new file mode 100644 index 000000000000..48e2dd74fcef --- /dev/null +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/AggregatePowerStatsProcessorTest.java @@ -0,0 +1,139 @@ +/* + * 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.android.server.power.stats.AggregatedPowerStatsConfig.STATE_POWER; +import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_PROCESS_STATE; +import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_SCREEN; + +import static com.google.common.truth.Truth.assertThat; + +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.util.Arrays; +import java.util.List; +import java.util.Objects; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class AggregatePowerStatsProcessorTest { + + @Test + public void createPowerEstimationPlan_allDeviceStatesPresentInUidStates() { + AggregatedPowerStatsConfig.PowerComponent config = + new AggregatedPowerStatsConfig.PowerComponent(BatteryConsumer.POWER_COMPONENT_ANY) + .trackDeviceStates(STATE_POWER, STATE_SCREEN) + .trackUidStates(STATE_POWER, STATE_SCREEN, STATE_PROCESS_STATE); + + AggregatedPowerStatsProcessor.PowerEstimationPlan plan = + new AggregatedPowerStatsProcessor.PowerEstimationPlan(config); + assertThat(deviceStateEstimatesToStrings(plan)) + .containsExactly("[0, 0]", "[0, 1]", "[1, 0]", "[1, 1]"); + assertThat(combinedDeviceStatsToStrings(plan)) + .containsExactly("[[0, 0]]", "[[0, 1]]", "[[1, 0]]", "[[1, 1]]"); + assertThat(uidStateEstimatesToStrings(plan, config)) + .containsExactly( + "[[0, 0]]: [ps]: [[0, 0, 0], [0, 0, 1], [0, 0, 2], [0, 0, 3], [0, 0, 4]]", + "[[0, 1]]: [ps]: [[0, 1, 0], [0, 1, 1], [0, 1, 2], [0, 1, 3], [0, 1, 4]]", + "[[1, 0]]: [ps]: [[1, 0, 0], [1, 0, 1], [1, 0, 2], [1, 0, 3], [1, 0, 4]]", + "[[1, 1]]: [ps]: [[1, 1, 0], [1, 1, 1], [1, 1, 2], [1, 1, 3], [1, 1, 4]]"); + } + + @Test + public void createPowerEstimationPlan_combineDeviceStats() { + AggregatedPowerStatsConfig.PowerComponent config = + new AggregatedPowerStatsConfig.PowerComponent(BatteryConsumer.POWER_COMPONENT_ANY) + .trackDeviceStates(STATE_POWER, STATE_SCREEN) + .trackUidStates(STATE_POWER, STATE_PROCESS_STATE); + + AggregatedPowerStatsProcessor.PowerEstimationPlan plan = + new AggregatedPowerStatsProcessor.PowerEstimationPlan(config); + + assertThat(deviceStateEstimatesToStrings(plan)) + .containsExactly("[0, 0]", "[0, 1]", "[1, 0]", "[1, 1]"); + assertThat(combinedDeviceStatsToStrings(plan)) + .containsExactly( + "[[0, 0], [0, 1]]", + "[[1, 0], [1, 1]]"); + assertThat(uidStateEstimatesToStrings(plan, config)) + .containsExactly( + "[[0, 0], [0, 1]]: [ps]: [[0, 0], [0, 1], [0, 2], [0, 3], [0, 4]]", + "[[1, 0], [1, 1]]: [ps]: [[1, 0], [1, 1], [1, 2], [1, 3], [1, 4]]"); + } + + private static List<String> deviceStateEstimatesToStrings( + AggregatedPowerStatsProcessor.PowerEstimationPlan plan) { + return plan.deviceStateEstimations.stream() + .map(dse -> dse.stateValues).map(Arrays::toString).toList(); + } + + private static List<String> combinedDeviceStatsToStrings( + AggregatedPowerStatsProcessor.PowerEstimationPlan plan) { + return plan.combinedDeviceStateEstimations.stream() + .map(cds -> cds.deviceStateEstimations) + .map(dses -> dses.stream() + .map(dse -> dse.stateValues).map(Arrays::toString).toList()) + .map(Object::toString) + .toList(); + } + + private static List<String> uidStateEstimatesToStrings( + AggregatedPowerStatsProcessor.PowerEstimationPlan plan, + AggregatedPowerStatsConfig.PowerComponent config) { + MultiStateStats.States[] uidStateConfig = config.getUidStateConfig(); + return plan.uidStateEstimates.stream() + .map(use -> + use.combinedDeviceStateEstimate.deviceStateEstimations.stream() + .map(dse -> dse.stateValues).map(Arrays::toString).toList() + + ": " + + Arrays.stream(use.states) + .filter(Objects::nonNull) + .map(MultiStateStats.States::getName).toList() + + ": " + + use.proportionalEstimates.stream() + .map(pe -> trackedStatesToString(uidStateConfig, pe.stateValues)) + .toList()) + .toList(); + } + + private static Object trackedStatesToString(MultiStateStats.States[] states, + int[] stateValues) { + StringBuilder sb = new StringBuilder(); + sb.append("["); + boolean first = true; + for (int i = 0; i < states.length; i++) { + if (!states[i].isTracked()) { + continue; + } + + if (!first) { + sb.append(", "); + } + first = false; + sb.append(stateValues[i]); + } + sb.append("]"); + return sb.toString(); + } +} diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java index 3579fce11c8d..0b1095483dd0 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java @@ -142,6 +142,22 @@ public class BatteryUsageStatsRule implements TestRule { return this; } + /** + * Mocks the CPU bracket count + */ + public BatteryUsageStatsRule setCpuPowerBracketCount(int count) { + when(mPowerProfile.getCpuPowerBracketCount()).thenReturn(count); + return this; + } + + /** + * Mocks the CPU bracket for the given CPU scaling policy and step + */ + public BatteryUsageStatsRule setCpuPowerBracket(int policy, int step, int bracket) { + when(mPowerProfile.getCpuPowerBracketForScalingStep(policy, step)).thenReturn(bracket); + return this; + } + public BatteryUsageStatsRule setAveragePowerForOrdinal(String group, int ordinal, double value) { when(mPowerProfile.getAveragePowerForOrdinal(group, ordinal)).thenReturn(value); diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuAggregatedPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuAggregatedPowerStatsProcessorTest.java new file mode 100644 index 000000000000..79084cc1b04d --- /dev/null +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuAggregatedPowerStatsProcessorTest.java @@ -0,0 +1,318 @@ +/* + * 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 android.os.BatteryConsumer.PROCESS_STATE_BACKGROUND; +import static android.os.BatteryConsumer.PROCESS_STATE_CACHED; +import static android.os.BatteryConsumer.PROCESS_STATE_FOREGROUND; + +import static com.android.server.power.stats.AggregatedPowerStatsConfig.POWER_STATE_BATTERY; +import static com.android.server.power.stats.AggregatedPowerStatsConfig.POWER_STATE_OTHER; +import static com.android.server.power.stats.AggregatedPowerStatsConfig.SCREEN_STATE_ON; +import static com.android.server.power.stats.AggregatedPowerStatsConfig.SCREEN_STATE_OTHER; +import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_POWER; +import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_PROCESS_STATE; +import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_SCREEN; + +import static com.google.common.truth.Truth.assertThat; + +import static org.junit.Assert.fail; + +import android.os.BatteryConsumer; +import android.os.PersistableBundle; +import android.util.LongArray; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.internal.os.MultiStateStats; +import com.android.internal.os.PowerProfile; +import com.android.internal.os.PowerStats; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class CpuAggregatedPowerStatsProcessorTest { + @Rule + public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule() + .setAveragePower(PowerProfile.POWER_CPU_ACTIVE, 720) + .setCpuScalingPolicy(0, new int[]{0, 1}, new int[]{100, 200}) + .setCpuScalingPolicy(2, new int[]{2, 3}, new int[]{300}) + .setAveragePowerForCpuScalingPolicy(0, 360) + .setAveragePowerForCpuScalingPolicy(2, 480) + .setAveragePowerForCpuScalingStep(0, 0, 300) + .setAveragePowerForCpuScalingStep(0, 1, 400) + .setAveragePowerForCpuScalingStep(2, 0, 500) + .setCpuPowerBracketCount(3) + .setCpuPowerBracket(0, 0, 0) + .setCpuPowerBracket(0, 1, 1) + .setCpuPowerBracket(2, 0, 2); + + private AggregatedPowerStatsConfig.PowerComponent mConfig; + private CpuAggregatedPowerStatsProcessor mProcessor; + private MockPowerComponentAggregatedPowerStats mStats; + + @Before + public void setup() { + mConfig = new AggregatedPowerStatsConfig.PowerComponent(BatteryConsumer.POWER_COMPONENT_CPU) + .trackDeviceStates(STATE_POWER, STATE_SCREEN) + .trackUidStates(STATE_POWER, STATE_SCREEN, STATE_PROCESS_STATE); + + mProcessor = new CpuAggregatedPowerStatsProcessor( + mStatsRule.getPowerProfile(), mStatsRule.getCpuScalingPolicies()); + } + + @Test + public void powerProfileModel() { + mStats = new MockPowerComponentAggregatedPowerStats(mConfig, false); + mStats.setDeviceStats( + states(POWER_STATE_BATTERY, SCREEN_STATE_ON), + concat( + values(3500, 4500, 3000), // scaling steps + values(2000, 1000), // clusters + values(5000)), // uptime + 3.113732); + mStats.setDeviceStats( + states(POWER_STATE_OTHER, SCREEN_STATE_ON), + concat( + values(6000, 6500, 4000), + values(5000, 3000), + values(7000)), + 4.607245); + mStats.setDeviceStats( + states(POWER_STATE_OTHER, SCREEN_STATE_OTHER), + concat( + values(9000, 10000, 7000), + values(8000, 6000), + values(20000)), + 7.331799); + mStats.setUidStats(24, + states(POWER_STATE_BATTERY, SCREEN_STATE_ON, PROCESS_STATE_FOREGROUND), + values(400, 1500, 2000), 1.206947); + mStats.setUidStats(42, + states(POWER_STATE_BATTERY, SCREEN_STATE_ON, PROCESS_STATE_FOREGROUND), + values(900, 1000, 1500), 1.016182); + mStats.setUidStats(42, + states(POWER_STATE_BATTERY, SCREEN_STATE_ON, PROCESS_STATE_BACKGROUND), + values(600, 500, 300), 0.385042); + mStats.setUidStats(42, + states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_CACHED), + values(1500, 2000, 1000), 1.252578); + + mProcessor.finish(mStats); + + mStats.verifyPowerEstimates(); + } + + @Test + public void energyConsumerModel() { + mStats = new MockPowerComponentAggregatedPowerStats(mConfig, true); + mStats.setDeviceStats( + states(POWER_STATE_BATTERY, SCREEN_STATE_ON), + concat( + values(3500, 4500, 3000), // scaling steps + values(2000, 1000), // clusters + values(5000), // uptime + values(5_000_000L, 6_000_000L)), // energy, uC + 3.055555); + mStats.setDeviceStats( + states(POWER_STATE_OTHER, SCREEN_STATE_ON), + concat( + values(6000, 6500, 4000), + values(5000, 3000), + values(7000), + values(5_000_000L, 6_000_000L)), // same as above + 3.055555); // same as above - WAI + mStats.setDeviceStats( + states(POWER_STATE_OTHER, SCREEN_STATE_OTHER), + concat( + values(9000, 10000, 7000), + values(8000, 6000), + values(20000), + values(8_000_000L, 18_000_000L)), + 7.222222); + mStats.setUidStats(24, + states(POWER_STATE_BATTERY, SCREEN_STATE_ON, PROCESS_STATE_FOREGROUND), + values(400, 1500, 2000), 1.449078); + mStats.setUidStats(42, + states(POWER_STATE_BATTERY, SCREEN_STATE_ON, PROCESS_STATE_FOREGROUND), + values(900, 1000, 1500), 1.161902); + mStats.setUidStats(42, + states(POWER_STATE_BATTERY, SCREEN_STATE_ON, PROCESS_STATE_BACKGROUND), + values(600, 500, 300), 0.355406); + mStats.setUidStats(42, + states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_CACHED), + values(1500, 2000, 1000), 0.80773); + + mProcessor.finish(mStats); + + mStats.verifyPowerEstimates(); + } + + private int[] states(int... states) { + return states; + } + + private long[] values(long... values) { + return values; + } + + private long[] concat(long[]... arrays) { + LongArray all = new LongArray(); + for (long[] array : arrays) { + for (long value : array) { + all.add(value); + } + } + return all.toArray(); + } + + private static class MockPowerComponentAggregatedPowerStats extends + PowerComponentAggregatedPowerStats { + private final CpuPowerStatsCollector.StatsArrayLayout mStatsLayout; + private final PowerStats.Descriptor mDescriptor; + private HashMap<String, long[]> mDeviceStats = new HashMap<>(); + private HashMap<String, long[]> mUidStats = new HashMap<>(); + private HashSet<Integer> mUids = new HashSet<>(); + private HashMap<String, Double> mExpectedDevicePower = new HashMap<>(); + private HashMap<String, Double> mExpectedUidPower = new HashMap<>(); + + MockPowerComponentAggregatedPowerStats(AggregatedPowerStatsConfig.PowerComponent config, + boolean useEnergyConsumers) { + super(config); + mStatsLayout = new CpuPowerStatsCollector.StatsArrayLayout(); + mStatsLayout.addDeviceSectionCpuTimeByScalingStep(3); + mStatsLayout.addDeviceSectionCpuTimeByCluster(2); + mStatsLayout.addDeviceSectionUptime(); + if (useEnergyConsumers) { + mStatsLayout.addDeviceSectionEnergyConsumers(2); + } + mStatsLayout.addDeviceSectionPowerEstimate(); + mStatsLayout.addUidSectionCpuTimeByPowerBracket(new int[]{0, 1, 2}); + mStatsLayout.addUidSectionPowerEstimate(); + + PersistableBundle extras = new PersistableBundle(); + mStatsLayout.toExtras(extras); + mDescriptor = new PowerStats.Descriptor(BatteryConsumer.POWER_COMPONENT_CPU, + mStatsLayout.getDeviceStatsArrayLength(), mStatsLayout.getUidStatsArrayLength(), + extras); + } + + @Override + public PowerStats.Descriptor getPowerStatsDescriptor() { + return mDescriptor; + } + + @Override + boolean getDeviceStats(long[] outValues, int[] deviceStates) { + long[] values = getDeviceStats(deviceStates); + System.arraycopy(values, 0, outValues, 0, values.length); + return true; + } + + private long[] getDeviceStats(int[] deviceStates) { + String key = statesToString(getConfig().getDeviceStateConfig(), deviceStates); + long[] values = mDeviceStats.get(key); + return values == null ? new long[mDescriptor.statsArrayLength] : values; + } + + void setDeviceStats(int[] states, long[] values, double expectedPowerEstimate) { + setDeviceStats(states, values); + mExpectedDevicePower.put(statesToString(getConfig().getDeviceStateConfig(), states), + expectedPowerEstimate); + } + + @Override + void setDeviceStats(int[] states, long[] values) { + String key = statesToString(getConfig().getDeviceStateConfig(), states); + mDeviceStats.put(key, Arrays.copyOf(values, mDescriptor.statsArrayLength)); + } + + @Override + boolean getUidStats(long[] outValues, int uid, int[] uidStates) { + long[] values = getUidStats(uid, uidStates); + assertThat(values).isNotNull(); + System.arraycopy(values, 0, outValues, 0, values.length); + return true; + } + + private long[] getUidStats(int uid, int[] uidStates) { + String key = uid + " " + statesToString(getConfig().getUidStateConfig(), uidStates); + long[] values = mUidStats.get(key); + return values == null ? new long[mDescriptor.uidStatsArrayLength] : values; + } + + void setUidStats(int uid, int[] states, long[] values, double expectedPowerEstimate) { + setUidStats(uid, states, values); + mExpectedUidPower.put( + uid + " " + statesToString(getConfig().getUidStateConfig(), states), + expectedPowerEstimate); + } + + @Override + void setUidStats(int uid, int[] states, long[] values) { + mUids.add(uid); + String key = uid + " " + statesToString(getConfig().getUidStateConfig(), states); + mUidStats.put(key, Arrays.copyOf(values, mDescriptor.uidStatsArrayLength)); + } + + @Override + void collectUids(Collection<Integer> uids) { + uids.addAll(mUids); + } + + void verifyPowerEstimates() { + StringBuilder mismatches = new StringBuilder(); + for (Map.Entry<String, Double> entry : mExpectedDevicePower.entrySet()) { + String key = entry.getKey(); + double expected = mExpectedDevicePower.get(key); + double actual = mStatsLayout.getDevicePowerEstimate(mDeviceStats.get(key)); + if (Math.abs(expected - actual) > 0.005) { + mismatches.append(key + " expected: " + expected + " actual: " + actual + "\n"); + } + } + for (Map.Entry<String, Double> entry : mExpectedUidPower.entrySet()) { + String key = entry.getKey(); + double expected = mExpectedUidPower.get(key); + double actual = mStatsLayout.getUidPowerEstimate(mUidStats.get(key)); + if (Math.abs(expected - actual) > 0.005) { + mismatches.append(key + " expected: " + expected + " actual: " + actual + "\n"); + } + } + if (!mismatches.isEmpty()) { + fail("Unexpected power estimations:\n" + mismatches); + } + } + + private String statesToString(MultiStateStats.States[] config, int[] states) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < states.length; i++) { + sb.append(config[i].getName()).append("=").append(states[i]).append(" "); + } + return sb.toString(); + } + } +} diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorTest.java index f2ee6dbf4ed6..bc211df5f143 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorTest.java @@ -20,16 +20,26 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import android.content.Context; +import android.hardware.power.stats.EnergyConsumer; +import android.hardware.power.stats.EnergyConsumerResult; +import android.hardware.power.stats.EnergyConsumerType; +import android.os.BatteryConsumer; import android.os.ConditionVariable; import android.os.Handler; import android.os.HandlerThread; +import android.power.PowerStatsInternal; import android.util.SparseArray; +import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.frameworks.powerstatstests.R; import com.android.internal.os.CpuScalingPolicies; import com.android.internal.os.PowerProfile; import com.android.internal.os.PowerStats; @@ -40,85 +50,266 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; + @RunWith(AndroidJUnit4.class) @SmallTest public class CpuPowerStatsCollectorTest { + private Context mContext; private final MockClock mMockClock = new MockClock(); private final HandlerThread mHandlerThread = new HandlerThread("test"); private Handler mHandler; - private CpuPowerStatsCollector mCollector; private PowerStats mCollectedStats; - @Mock private PowerProfile mPowerProfile; @Mock private CpuPowerStatsCollector.KernelCpuStatsReader mMockKernelCpuStatsReader; + @Mock + private PowerStatsInternal mPowerStatsInternal; + private CpuScalingPolicies mCpuScalingPolicies; @Before public void setup() { MockitoAnnotations.initMocks(this); + mContext = InstrumentationRegistry.getContext(); mHandlerThread.start(); mHandler = mHandlerThread.getThreadHandler(); - when(mPowerProfile.getCpuPowerBracketCount()).thenReturn(2); - when(mPowerProfile.getCpuPowerBracketForScalingStep(0, 0)).thenReturn(0); - when(mPowerProfile.getCpuPowerBracketForScalingStep(0, 1)).thenReturn(1); - mCollector = new CpuPowerStatsCollector(new CpuScalingPolicies( + when(mMockKernelCpuStatsReader.nativeIsSupportedFeature()).thenReturn(true); + } + + @Test + public void powerBrackets_specifiedInPowerProfile() { + mPowerProfile = new PowerProfile(mContext); + mPowerProfile.forceInitForTesting(mContext, R.xml.power_profile_test_power_brackets); + mCpuScalingPolicies = new CpuScalingPolicies( new SparseArray<>() {{ put(0, new int[]{0}); + put(4, new int[]{4}); }}, new SparseArray<>() {{ - put(0, new int[]{1, 12}); - }}), - mPowerProfile, mHandler, mMockKernelCpuStatsReader, 60_000, mMockClock); - mCollector.addConsumer(stats -> mCollectedStats = stats); - mCollector.setEnabled(true); + put(0, new int[]{100}); + put(4, new int[]{400, 500}); + }}); + + CpuPowerStatsCollector collector = createCollector(8, 0); + + assertThat(getScalingStepToPowerBracketMap(collector)) + .isEqualTo(new int[]{1, 1, 0}); + } + + @Test + public void powerBrackets_default_noEnergyConsumers() { + mPowerProfile = new PowerProfile(mContext); + mPowerProfile.forceInitForTesting(mContext, R.xml.power_profile_test); + mockCpuScalingPolicies(2); + + CpuPowerStatsCollector collector = createCollector(3, 0); + + assertThat(new String[]{ + collector.getCpuPowerBracketDescription(0), + collector.getCpuPowerBracketDescription(1), + collector.getCpuPowerBracketDescription(2)}) + .isEqualTo(new String[]{ + "0/300(10.0)", + "0/1000(20.0), 0/2000(30.0), 4/300(25.0)", + "4/1000(35.0), 4/2500(50.0), 4/3000(60.0)"}); + assertThat(getScalingStepToPowerBracketMap(collector)) + .isEqualTo(new int[]{0, 1, 1, 1, 2, 2, 2}); + } + + @Test + public void powerBrackets_moreBracketsThanStates() { + mPowerProfile = new PowerProfile(mContext); + mPowerProfile.forceInitForTesting(mContext, R.xml.power_profile_test); + mockCpuScalingPolicies(2); + + CpuPowerStatsCollector collector = createCollector(8, 0); + + assertThat(getScalingStepToPowerBracketMap(collector)) + .isEqualTo(new int[]{0, 1, 2, 3, 4, 5, 6}); + } + + @Test + public void powerBrackets_energyConsumers() throws Exception { + mPowerProfile = new PowerProfile(mContext); + mPowerProfile.forceInitForTesting(mContext, R.xml.power_profile_test); + mockCpuScalingPolicies(2); + mockEnergyConsumers(); + + CpuPowerStatsCollector collector = createCollector(8, 2); + + assertThat(getScalingStepToPowerBracketMap(collector)) + .isEqualTo(new int[]{0, 1, 1, 2, 2, 3, 3}); + } + + @Test + public void powerStatsDescriptor() throws Exception { + mPowerProfile = new PowerProfile(mContext); + mPowerProfile.forceInitForTesting(mContext, R.xml.power_profile_test); + mockCpuScalingPolicies(2); + mockEnergyConsumers(); + + CpuPowerStatsCollector collector = createCollector(8, 2); + PowerStats.Descriptor descriptor = collector.getPowerStatsDescriptor(); + assertThat(descriptor.powerComponentId).isEqualTo(BatteryConsumer.POWER_COMPONENT_CPU); + assertThat(descriptor.name).isEqualTo("cpu"); + assertThat(descriptor.statsArrayLength).isEqualTo(13); + assertThat(descriptor.uidStatsArrayLength).isEqualTo(5); + CpuPowerStatsCollector.StatsArrayLayout layout = + new CpuPowerStatsCollector.StatsArrayLayout(); + layout.fromExtras(descriptor.extras); + + long[] deviceStats = new long[descriptor.statsArrayLength]; + layout.setTimeByScalingStep(deviceStats, 2, 42); + layout.setConsumedEnergy(deviceStats, 1, 43); + layout.setUptime(deviceStats, 44); + layout.setDevicePowerEstimate(deviceStats, 45); + + long[] uidStats = new long[descriptor.uidStatsArrayLength]; + layout.setUidTimeByPowerBracket(uidStats, 3, 46); + layout.setUidPowerEstimate(uidStats, 47); + + assertThat(layout.getCpuScalingStepCount()).isEqualTo(7); + assertThat(layout.getTimeByScalingStep(deviceStats, 2)).isEqualTo(42); + + assertThat(layout.getCpuClusterEnergyConsumerCount()).isEqualTo(2); + assertThat(layout.getConsumedEnergy(deviceStats, 1)).isEqualTo(43); + + assertThat(layout.getUptime(deviceStats)).isEqualTo(44); + + assertThat(layout.getDevicePowerEstimate(deviceStats)).isEqualTo(45); + + assertThat(layout.getScalingStepToPowerBracketMap()).isEqualTo( + new int[]{0, 1, 1, 2, 2, 3, 3}); + assertThat(layout.getCpuPowerBracketCount()).isEqualTo(4); + + assertThat(layout.getUidTimeByPowerBracket(uidStats, 3)).isEqualTo(46); + assertThat(layout.getUidPowerEstimate(uidStats)).isEqualTo(47); } @Test - public void collectStats() { - mockKernelCpuStats(new SparseArray<>() {{ - put(42, new long[]{100, 200}); - put(99, new long[]{300, 600}); - }}, 0, 1234); + public void collectStats() throws Exception { + mockCpuScalingPolicies(1); + mockPowerProfile(); + mockEnergyConsumers(); + + CpuPowerStatsCollector collector = createCollector(8, 0); + CpuPowerStatsCollector.StatsArrayLayout layout = + new CpuPowerStatsCollector.StatsArrayLayout(); + layout.fromExtras(collector.getPowerStatsDescriptor().extras); + + mockKernelCpuStats(new long[]{1111, 2222, 3333}, + new SparseArray<>() {{ + put(42, new long[]{100, 200}); + put(99, new long[]{300, 600}); + }}, 0, 1234); mMockClock.uptime = 1000; - mCollector.forceSchedule(); + collector.forceSchedule(); waitForIdle(); assertThat(mCollectedStats.durationMs).isEqualTo(1234); - assertThat(mCollectedStats.uidStats.get(42)).isEqualTo(new long[]{100, 200}); - assertThat(mCollectedStats.uidStats.get(99)).isEqualTo(new long[]{300, 600}); - mockKernelCpuStats(new SparseArray<>() {{ - put(42, new long[]{123, 234}); - put(99, new long[]{345, 678}); - }}, 1234, 3421); + assertThat(layout.getCpuScalingStepCount()).isEqualTo(3); + assertThat(layout.getTimeByScalingStep(mCollectedStats.stats, 0)).isEqualTo(1111); + assertThat(layout.getTimeByScalingStep(mCollectedStats.stats, 1)).isEqualTo(2222); + assertThat(layout.getTimeByScalingStep(mCollectedStats.stats, 2)).isEqualTo(3333); + + assertThat(layout.getConsumedEnergy(mCollectedStats.stats, 0)).isEqualTo(0); + assertThat(layout.getConsumedEnergy(mCollectedStats.stats, 1)).isEqualTo(0); + + assertThat(layout.getUidTimeByPowerBracket(mCollectedStats.uidStats.get(42), 0)) + .isEqualTo(100); + assertThat(layout.getUidTimeByPowerBracket(mCollectedStats.uidStats.get(42), 1)) + .isEqualTo(200); + assertThat(layout.getUidTimeByPowerBracket(mCollectedStats.uidStats.get(99), 0)) + .isEqualTo(300); + assertThat(layout.getUidTimeByPowerBracket(mCollectedStats.uidStats.get(99), 1)) + .isEqualTo(600); + + mockKernelCpuStats(new long[]{5555, 4444, 3333}, + new SparseArray<>() {{ + put(42, new long[]{123, 234}); + put(99, new long[]{345, 678}); + }}, 1234, 3421); mMockClock.uptime = 2000; - mCollector.forceSchedule(); + collector.forceSchedule(); waitForIdle(); assertThat(mCollectedStats.durationMs).isEqualTo(3421 - 1234); - assertThat(mCollectedStats.uidStats.get(42)).isEqualTo(new long[]{23, 34}); - assertThat(mCollectedStats.uidStats.get(99)).isEqualTo(new long[]{45, 78}); + + assertThat(layout.getTimeByScalingStep(mCollectedStats.stats, 0)).isEqualTo(4444); + assertThat(layout.getTimeByScalingStep(mCollectedStats.stats, 1)).isEqualTo(2222); + assertThat(layout.getTimeByScalingStep(mCollectedStats.stats, 2)).isEqualTo(0); + + // 500 * 1000 / 3500 + assertThat(layout.getConsumedEnergy(mCollectedStats.stats, 0)).isEqualTo(143); + // 700 * 1000 / 3500 + assertThat(layout.getConsumedEnergy(mCollectedStats.stats, 1)).isEqualTo(200); + + assertThat(layout.getUidTimeByPowerBracket(mCollectedStats.uidStats.get(42), 0)) + .isEqualTo(23); + assertThat(layout.getUidTimeByPowerBracket(mCollectedStats.uidStats.get(42), 1)) + .isEqualTo(34); + assertThat(layout.getUidTimeByPowerBracket(mCollectedStats.uidStats.get(99), 0)) + .isEqualTo(45); + assertThat(layout.getUidTimeByPowerBracket(mCollectedStats.uidStats.get(99), 1)) + .isEqualTo(78); + } + + private void mockCpuScalingPolicies(int clusterCount) { + SparseArray<int[]> cpus = new SparseArray<>(); + SparseArray<int[]> freqs = new SparseArray<>(); + cpus.put(0, new int[]{0, 1, 2, 3}); + freqs.put(0, new int[]{300000, 1000000, 2000000}); + if (clusterCount == 2) { + cpus.put(4, new int[]{4, 5}); + freqs.put(4, new int[]{300000, 1000000, 2500000, 3000000}); + } + mCpuScalingPolicies = new CpuScalingPolicies(cpus, freqs); + } + + private void mockPowerProfile() { + mPowerProfile = mock(PowerProfile.class); + when(mPowerProfile.getCpuPowerBracketCount()).thenReturn(2); + when(mPowerProfile.getCpuPowerBracketForScalingStep(0, 0)).thenReturn(0); + when(mPowerProfile.getCpuPowerBracketForScalingStep(0, 1)).thenReturn(1); + when(mPowerProfile.getCpuPowerBracketForScalingStep(0, 2)).thenReturn(1); } - private void mockKernelCpuStats(SparseArray<long[]> uidToCpuStats, + private CpuPowerStatsCollector createCollector(int defaultCpuPowerBrackets, + int defaultCpuPowerBracketsPerEnergyConsumer) { + CpuPowerStatsCollector collector = new CpuPowerStatsCollector(mCpuScalingPolicies, + mPowerProfile, mHandler, mMockKernelCpuStatsReader, () -> mPowerStatsInternal, + () -> 3500, 60_000, mMockClock, defaultCpuPowerBrackets, + defaultCpuPowerBracketsPerEnergyConsumer); + collector.addConsumer(stats -> mCollectedStats = stats); + collector.setEnabled(true); + return collector; + } + + private void mockKernelCpuStats(long[] deviceStats, SparseArray<long[]> uidToCpuStats, long expectedLastUpdateTimestampMs, long newLastUpdateTimestampMs) { when(mMockKernelCpuStatsReader.nativeReadCpuStats( any(CpuPowerStatsCollector.KernelCpuStatsCallback.class), - any(int[].class), anyLong(), any(long[].class))) + any(int[].class), anyLong(), any(long[].class), any(long[].class))) .thenAnswer(invocation -> { CpuPowerStatsCollector.KernelCpuStatsCallback callback = invocation.getArgument(0); int[] powerBucketIndexes = invocation.getArgument(1); long lastTimestamp = invocation.getArgument(2); - long[] tempStats = invocation.getArgument(3); + long[] cpuTimeByScalingStep = invocation.getArgument(3); + long[] tempStats = invocation.getArgument(4); - assertThat(powerBucketIndexes).isEqualTo(new int[]{0, 1}); + assertThat(powerBucketIndexes).isEqualTo(new int[]{0, 1, 1}); assertThat(lastTimestamp / 1000000L).isEqualTo(expectedLastUpdateTimestampMs); assertThat(tempStats).hasLength(2); + System.arraycopy(deviceStats, 0, cpuTimeByScalingStep, 0, + cpuTimeByScalingStep.length); + for (int i = 0; i < uidToCpuStats.size(); i++) { int uid = uidToCpuStats.keyAt(i); long[] cpuStats = uidToCpuStats.valueAt(i); @@ -129,6 +320,67 @@ public class CpuPowerStatsCollectorTest { }); } + @SuppressWarnings("unchecked") + private void mockEnergyConsumers() throws Exception { + when(mPowerStatsInternal.getEnergyConsumerInfo()) + .thenReturn(new EnergyConsumer[]{ + new EnergyConsumer() {{ + id = 1; + type = EnergyConsumerType.CPU_CLUSTER; + ordinal = 0; + name = "CPU0"; + }}, + new EnergyConsumer() {{ + id = 2; + type = EnergyConsumerType.CPU_CLUSTER; + ordinal = 1; + name = "CPU4"; + }}, + new EnergyConsumer() {{ + id = 3; + type = EnergyConsumerType.BLUETOOTH; + name = "BT"; + }}, + }); + + CompletableFuture<EnergyConsumerResult[]> future1 = mock(CompletableFuture.class); + when(future1.get(anyLong(), any(TimeUnit.class))) + .thenReturn(new EnergyConsumerResult[]{ + new EnergyConsumerResult() {{ + id = 1; + energyUWs = 1000; + }}, + new EnergyConsumerResult() {{ + id = 2; + energyUWs = 2000; + }} + }); + + CompletableFuture<EnergyConsumerResult[]> future2 = mock(CompletableFuture.class); + when(future2.get(anyLong(), any(TimeUnit.class))) + .thenReturn(new EnergyConsumerResult[]{ + new EnergyConsumerResult() {{ + id = 1; + energyUWs = 1500; + }}, + new EnergyConsumerResult() {{ + id = 2; + energyUWs = 2700; + }} + }); + + when(mPowerStatsInternal.getEnergyConsumedAsync(eq(new int[]{1, 2}))) + .thenReturn(future1) + .thenReturn(future2); + } + + private static int[] getScalingStepToPowerBracketMap(CpuPowerStatsCollector collector) { + CpuPowerStatsCollector.StatsArrayLayout layout = + new CpuPowerStatsCollector.StatsArrayLayout(); + layout.fromExtras(collector.getPowerStatsDescriptor().extras); + return layout.getScalingStepToPowerBracketMap(); + } + private void waitForIdle() { ConditionVariable done = new ConditionVariable(); mHandler.post(done::open); 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 index 30a731818f36..eb03a6c14f7d 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java @@ -180,7 +180,7 @@ public class MultiStateStatsTest { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw, true); - multiStateStats.dump(pw); + multiStateStats.dump(pw, Arrays::toString); assertThat(sw.toString()).isEqualTo( "plugged-in fg [25, 50]\n" + "on-battery fg [25, 50]\n" diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java index 71007f53f0e1..52a5d8f6d952 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java @@ -156,6 +156,7 @@ public class AccessibilityUserStateTest { mUserState.setTargetAssignedToAccessibilityButton(COMPONENT_NAME.flattenToString()); mUserState.setTouchExplorationEnabledLocked(true); mUserState.setMagnificationSingleFingerTripleTapEnabledLocked(true); + mUserState.setMagnificationTwoFingerTripleTapEnabledLocked(true); mUserState.setAutoclickEnabledLocked(true); mUserState.setUserNonInteractiveUiTimeoutLocked(30); mUserState.setUserInteractiveUiTimeoutLocked(30); @@ -178,6 +179,7 @@ public class AccessibilityUserStateTest { assertNull(mUserState.getTargetAssignedToAccessibilityButton()); assertFalse(mUserState.isTouchExplorationEnabledLocked()); assertFalse(mUserState.isMagnificationSingleFingerTripleTapEnabledLocked()); + assertFalse(mUserState.isMagnificationTwoFingerTripleTapEnabledLocked()); assertFalse(mUserState.isAutoclickEnabledLocked()); assertEquals(0, mUserState.getUserNonInteractiveUiTimeoutLocked()); assertEquals(0, mUserState.getUserInteractiveUiTimeoutLocked()); diff --git a/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java b/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java index 3ec6f425a37a..973ab846fa4a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java @@ -448,7 +448,6 @@ public class ZOrderingTests extends WindowTestsBase { mDisplayContent.updateImeParent(); // Ime should on top of the popup IME layering target window. - mDisplayContent.assignChildLayers(mTransaction); assertWindowHigher(mImeWindow, popupImeTargetWin); } diff --git a/telephony/java/android/telephony/ims/ImsCallSession.java b/telephony/java/android/telephony/ims/ImsCallSession.java index ecd703960d08..d30078d9cd97 100755 --- a/telephony/java/android/telephony/ims/ImsCallSession.java +++ b/telephony/java/android/telephony/ims/ImsCallSession.java @@ -29,6 +29,7 @@ import android.util.Log; import com.android.ims.internal.IImsCallSession; import com.android.ims.internal.IImsVideoCallProvider; +import com.android.internal.telephony.flags.Flags; import com.android.internal.telephony.util.TelephonyUtils; import java.util.ArrayList; @@ -545,6 +546,11 @@ public class ImsCallSession { try { iSession.setListener(mIImsCallSessionListenerProxy); } catch (RemoteException e) { + if (Flags.ignoreAlreadyTerminatedIncomingCallBeforeRegisteringListener()) { + // Registering listener failed, so other operations are not allowed. + Log.e(TAG, "ImsCallSession : " + e); + mClosed = true; + } } } else { mClosed = true; diff --git a/tools/aapt2/cmd/Link.h b/tools/aapt2/cmd/Link.h index 1b1e93bd480a..a08f385b2270 100644 --- a/tools/aapt2/cmd/Link.h +++ b/tools/aapt2/cmd/Link.h @@ -323,6 +323,7 @@ class LinkCommand : public Command { "should only be used together with the --static-lib flag.", &options_.merge_only); AddOptionalSwitch("-v", "Enables verbose logging.", &verbose_); + AddOptionalFlagList("--feature-flags", "Placeholder, to be implemented.", &feature_flags_args_); } int Action(const std::vector<std::string>& args) override; @@ -347,6 +348,7 @@ class LinkCommand : public Command { std::optional<std::string> stable_id_file_path_; std::vector<std::string> split_args_; std::optional<std::string> trace_folder_; + std::vector<std::string> feature_flags_args_; }; }// namespace aapt |