diff options
84 files changed, 1276 insertions, 433 deletions
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java index 8ee23aa8e67b..aba4b2c0d591 100644 --- a/core/java/android/app/TaskInfo.java +++ b/core/java/android/app/TaskInfo.java @@ -188,6 +188,14 @@ public class TaskInfo { public int launchIntoPipHostTaskId; /** + * The task id of the parent Task of the launch-into-pip Activity, i.e., if task have more than + * one activity it will create new task for this activity, this id is the origin task id and + * the pip activity will be reparent to origin task when it exit pip mode. + * @hide + */ + public int lastParentTaskIdBeforePip; + + /** * The {@link Rect} copied from {@link DisplayCutout#getSafeInsets()} if the cutout is not of * (LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES, LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS), * {@code null} otherwise. @@ -503,6 +511,7 @@ public class TaskInfo { pictureInPictureParams = source.readTypedObject(PictureInPictureParams.CREATOR); shouldDockBigOverlays = source.readBoolean(); launchIntoPipHostTaskId = source.readInt(); + lastParentTaskIdBeforePip = source.readInt(); displayCutoutInsets = source.readTypedObject(Rect.CREATOR); topActivityInfo = source.readTypedObject(ActivityInfo.CREATOR); isResizeable = source.readBoolean(); @@ -549,6 +558,7 @@ public class TaskInfo { dest.writeTypedObject(pictureInPictureParams, flags); dest.writeBoolean(shouldDockBigOverlays); dest.writeInt(launchIntoPipHostTaskId); + dest.writeInt(lastParentTaskIdBeforePip); dest.writeTypedObject(displayCutoutInsets, flags); dest.writeTypedObject(topActivityInfo, flags); dest.writeBoolean(isResizeable); @@ -589,6 +599,7 @@ public class TaskInfo { + " pictureInPictureParams=" + pictureInPictureParams + " shouldDockBigOverlays=" + shouldDockBigOverlays + " launchIntoPipHostTaskId=" + launchIntoPipHostTaskId + + " lastParentTaskIdBeforePip=" + lastParentTaskIdBeforePip + " displayCutoutSafeInsets=" + displayCutoutInsets + " topActivityInfo=" + topActivityInfo + " launchCookies=" + launchCookies diff --git a/core/java/android/hardware/camera2/extension/CameraOutputConfig.aidl b/core/java/android/hardware/camera2/extension/CameraOutputConfig.aidl index 34d016adbc06..7c54a9b01dde 100644 --- a/core/java/android/hardware/camera2/extension/CameraOutputConfig.aidl +++ b/core/java/android/hardware/camera2/extension/CameraOutputConfig.aidl @@ -36,4 +36,5 @@ parcelable CameraOutputConfig int surfaceGroupId; String physicalCameraId; List<CameraOutputConfig> sharedSurfaceConfigs; + boolean isMultiResolutionOutput; } diff --git a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java index c8dc2d0b0b91..77def20179ae 100644 --- a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java @@ -226,6 +226,9 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes OutputConfiguration cameraOutput = new OutputConfiguration(output.surfaceGroupId, outputSurface); + if (output.isMultiResolutionOutput) { + cameraOutput.setMultiResolutionOutput(); + } if ((output.sharedSurfaceConfigs != null) && !output.sharedSurfaceConfigs.isEmpty()) { cameraOutput.enableSurfaceSharing(); for (CameraOutputConfig sharedOutputConfig : output.sharedSurfaceConfigs) { diff --git a/core/java/android/hardware/camera2/params/OutputConfiguration.java b/core/java/android/hardware/camera2/params/OutputConfiguration.java index 90e92dbe2ab0..9868d87460e0 100644 --- a/core/java/android/hardware/camera2/params/OutputConfiguration.java +++ b/core/java/android/hardware/camera2/params/OutputConfiguration.java @@ -421,7 +421,7 @@ public final class OutputConfiguration implements Parcelable { * call, or no non-negative group ID has been set. * @hide */ - void setMultiResolutionOutput() { + public void setMultiResolutionOutput() { if (mIsShared) { throw new IllegalStateException("Multi-resolution output flag must not be set for " + "configuration with surface sharing"); diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index 76475f2142c0..4f49f12691d0 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -2808,6 +2808,15 @@ public abstract class BatteryStats implements Parcelable { public abstract long getMobileRadioMeasuredBatteryConsumptionUC(); /** + * Returns the battery consumption (in microcoulombs) of the phone calls, derived from on device + * power measurement data. + * Will return {@link #POWER_DATA_UNAVAILABLE} if data is unavailable. + * + * {@hide} + */ + public abstract long getPhoneEnergyConsumptionUC(); + + /** * Returns the battery consumption (in microcoulombs) of the screen while on, derived from on * device power measurement data. * Will return {@link #POWER_DATA_UNAVAILABLE} if data is unavailable. diff --git a/core/java/android/preference/SeekBarVolumizer.java b/core/java/android/preference/SeekBarVolumizer.java index 36e0dc35cb8e..b02e123e5254 100644 --- a/core/java/android/preference/SeekBarVolumizer.java +++ b/core/java/android/preference/SeekBarVolumizer.java @@ -141,12 +141,15 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba private int mRingerMode; private int mZenMode; private boolean mPlaySample; + private final boolean mDeviceHasProductStrategies; private static final int MSG_SET_STREAM_VOLUME = 0; private static final int MSG_START_SAMPLE = 1; private static final int MSG_STOP_SAMPLE = 2; private static final int MSG_INIT_SAMPLE = 3; + private static final int MSG_UPDATE_SLIDER_MAYBE_LATER = 4; private static final int CHECK_RINGTONE_PLAYBACK_DELAY_MS = 1000; + private static final int CHECK_UPDATE_SLIDER_LATER_MS = 500; private static final long SET_STREAM_VOLUME_DELAY_MS = TimeUnit.MILLISECONDS.toMillis(500); private static final long START_SAMPLE_DELAY_MS = TimeUnit.MILLISECONDS.toMillis(500); private static final long DURATION_TO_START_DELAYING = TimeUnit.MILLISECONDS.toMillis(2000); @@ -170,6 +173,7 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba boolean playSample) { mContext = context; mAudioManager = context.getSystemService(AudioManager.class); + mDeviceHasProductStrategies = hasAudioProductStrategies(); mNotificationManager = context.getSystemService(NotificationManager.class); mNotificationPolicy = mNotificationManager.getConsolidatedNotificationPolicy(); mAllowAlarms = (mNotificationPolicy.priorityCategories & NotificationManager.Policy @@ -186,7 +190,7 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba } mZenMode = mNotificationManager.getZenMode(); - if (hasAudioProductStrategies()) { + if (mDeviceHasProductStrategies) { mVolumeGroupId = getVolumeGroupIdForLegacyStreamType(mStreamType); mAttributes = getAudioAttributesForLegacyStreamType( mStreamType); @@ -213,6 +217,12 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba mDefaultUri = defaultUri; } + /** + * DO NOT CALL every time this is needed, use once in constructor, + * read mDeviceHasProductStrategies instead + * @return true if stream types are used for volume management, false if volume groups are + * used for volume management + */ private boolean hasAudioProductStrategies() { return AudioManager.getAudioProductStrategies().size() > 0; } @@ -330,6 +340,9 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba onInitSample(); } break; + case MSG_UPDATE_SLIDER_MAYBE_LATER: + onUpdateSliderMaybeLater(); + break; default: Log.e(TAG, "invalid SeekBarVolumizer message: "+msg.what); } @@ -353,6 +366,21 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba : isDelay() ? START_SAMPLE_DELAY_MS : 0); } + private void onUpdateSliderMaybeLater() { + if (isDelay()) { + postUpdateSliderMaybeLater(); + return; + } + updateSlider(); + } + + private void postUpdateSliderMaybeLater() { + if (mHandler == null) return; + mHandler.removeMessages(MSG_UPDATE_SLIDER_MAYBE_LATER); + mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_UPDATE_SLIDER_MAYBE_LATER), + CHECK_UPDATE_SLIDER_LATER_MS); + } + // After stop volume it needs to add a small delay when playing volume or set stream. // It is because the call volume is from the earpiece and the alarm/ring/media // is from the speaker. If play the alarm volume or set alarm stream right after stop @@ -422,7 +450,7 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba postStopSample(); mContext.getContentResolver().unregisterContentObserver(mVolumeObserver); mReceiver.setListening(false); - if (hasAudioProductStrategies()) { + if (mDeviceHasProductStrategies) { unregisterVolumeGroupCb(); } mSeekBar.setOnSeekBarChangeListener(null); @@ -442,7 +470,7 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba System.getUriFor(System.VOLUME_SETTINGS_INT[mStreamType]), false, mVolumeObserver); mReceiver.setListening(true); - if (hasAudioProductStrategies()) { + if (mDeviceHasProductStrategies) { registerVolumeGroupCb(); } } @@ -466,6 +494,7 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba mLastProgress = progress; mHandler.removeMessages(MSG_SET_STREAM_VOLUME); mHandler.removeMessages(MSG_START_SAMPLE); + mHandler.removeMessages(MSG_UPDATE_SLIDER_MAYBE_LATER); mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SET_STREAM_VOLUME), isDelay() ? SET_STREAM_VOLUME_DELAY_MS : 0); } @@ -608,7 +637,7 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba if (AudioManager.VOLUME_CHANGED_ACTION.equals(action)) { int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1); int streamValue = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, -1); - if (hasAudioProductStrategies() && !isDelay()) { + if (mDeviceHasProductStrategies && !isDelay()) { updateVolumeSlider(streamType, streamValue); } } else if (AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION.equals(action)) { @@ -620,9 +649,16 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba } } else if (AudioManager.STREAM_DEVICES_CHANGED_ACTION.equals(action)) { int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1); - if (hasAudioProductStrategies() && !isDelay()) { - int streamVolume = mAudioManager.getStreamVolume(streamType); - updateVolumeSlider(streamType, streamVolume); + + if (mDeviceHasProductStrategies) { + if (isDelay()) { + // not the right time to update the sliders, try again later + postUpdateSliderMaybeLater(); + } else { + int streamVolume = mAudioManager.getStreamVolume(streamType); + updateVolumeSlider(streamType, streamVolume); + } + } else { int volumeGroup = getVolumeGroupIdForLegacyStreamType(streamType); if (volumeGroup != AudioVolumeGroup.DEFAULT_VOLUME_GROUP diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index 6fed26c4a81d..e1e57de15346 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -166,8 +166,8 @@ public class BatteryStatsImpl extends BatteryStats { // In-memory Parcel magic number, used to detect attempts to unmarshall bad data private static final int MAGIC = 0xBA757475; // 'BATSTATS' - // Current on-disk Parcel version - static final int VERSION = 210; + // Current on-disk Parcel version. Must be updated when the format of the parcelable changes + public static final int VERSION = 211; // The maximum number of names wakelocks we will keep track of // per uid; once the limit is reached, we batch the remaining wakelocks @@ -6491,6 +6491,9 @@ public class BatteryStatsImpl extends BatteryStats { addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs); mPhoneOn = true; mPhoneOnTimer.startRunningLocked(elapsedRealtimeMs); + if (mConstants.PHONE_ON_EXTERNAL_STATS_COLLECTION) { + scheduleSyncExternalStatsLocked("phone-on", ExternalStatsSync.UPDATE_RADIO); + } } } @@ -6509,6 +6512,7 @@ public class BatteryStatsImpl extends BatteryStats { addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs); mPhoneOn = false; mPhoneOnTimer.stopRunningLocked(elapsedRealtimeMs); + scheduleSyncExternalStatsLocked("phone-off", ExternalStatsSync.UPDATE_RADIO); } } @@ -8475,6 +8479,12 @@ public class BatteryStatsImpl extends BatteryStats { @GuardedBy("this") @Override + public long getPhoneEnergyConsumptionUC() { + return getPowerBucketConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_PHONE); + } + + @GuardedBy("this") + @Override public long getScreenOnMeasuredBatteryConsumptionUC() { return getPowerBucketConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_SCREEN_ON); } @@ -13717,18 +13727,36 @@ public class BatteryStatsImpl extends BatteryStats { } synchronized (this) { + final long totalRadioDurationMs = + mMobileRadioActiveTimer.getTimeSinceMarkLocked( + elapsedRealtimeMs * 1000) / 1000; + mMobileRadioActiveTimer.setMark(elapsedRealtimeMs); + final long phoneOnDurationMs = Math.min(totalRadioDurationMs, + mPhoneOnTimer.getTimeSinceMarkLocked(elapsedRealtimeMs * 1000) / 1000); + mPhoneOnTimer.setMark(elapsedRealtimeMs); + if (!mOnBatteryInternal || mIgnoreNextExternalStats) { return; } final SparseDoubleArray uidEstimatedConsumptionMah; + final long dataConsumedChargeUC; if (consumedChargeUC > 0 && mMobileRadioPowerCalculator != null && mGlobalMeasuredEnergyStats != null) { + // Crudely attribute power consumption. Added (totalRadioDurationMs / 2) to the + // numerator for long rounding. + final long phoneConsumedChargeUC = + (consumedChargeUC * phoneOnDurationMs + totalRadioDurationMs / 2) + / totalRadioDurationMs; + dataConsumedChargeUC = consumedChargeUC - phoneConsumedChargeUC; mGlobalMeasuredEnergyStats.updateStandardBucket( - MeasuredEnergyStats.POWER_BUCKET_MOBILE_RADIO, consumedChargeUC); + MeasuredEnergyStats.POWER_BUCKET_PHONE, phoneConsumedChargeUC); + mGlobalMeasuredEnergyStats.updateStandardBucket( + MeasuredEnergyStats.POWER_BUCKET_MOBILE_RADIO, dataConsumedChargeUC); uidEstimatedConsumptionMah = new SparseDoubleArray(); } else { uidEstimatedConsumptionMah = null; + dataConsumedChargeUC = POWER_DATA_UNAVAILABLE; } if (deltaInfo != null) { @@ -13888,14 +13916,9 @@ public class BatteryStatsImpl extends BatteryStats { // Update the MeasuredEnergyStats information. if (uidEstimatedConsumptionMah != null) { double totalEstimatedConsumptionMah = 0.0; - - // Estimate total active radio power consumption since last mark. - final long totalRadioTimeMs = mMobileRadioActiveTimer.getTimeSinceMarkLocked( - elapsedRealtimeMs * 1000) / 1000; - mMobileRadioActiveTimer.setMark(elapsedRealtimeMs); totalEstimatedConsumptionMah += mMobileRadioPowerCalculator.calcPowerFromRadioActiveDurationMah( - totalRadioTimeMs); + totalRadioDurationMs); // Estimate idle power consumption at each signal strength level final int numSignalStrengthLevels = mPhoneSignalStrengthsTimer.length; @@ -13919,7 +13942,7 @@ public class BatteryStatsImpl extends BatteryStats { mMobileRadioPowerCalculator.calcScanTimePowerMah(scanTimeMs); distributeEnergyToUidsLocked(MeasuredEnergyStats.POWER_BUCKET_MOBILE_RADIO, - consumedChargeUC, uidEstimatedConsumptionMah, + dataConsumedChargeUC, uidEstimatedConsumptionMah, totalEstimatedConsumptionMah, elapsedRealtimeMs); } @@ -16651,6 +16674,8 @@ public class BatteryStatsImpl extends BatteryStats { public static final String KEY_MAX_HISTORY_BUFFER_KB = "max_history_buffer_kb"; public static final String KEY_BATTERY_CHARGED_DELAY_MS = "battery_charged_delay_ms"; + public static final String KEY_PHONE_ON_EXTERNAL_STATS_COLLECTION = + "phone_on_external_stats_collection"; private static final boolean DEFAULT_TRACK_CPU_ACTIVE_CLUSTER_TIME = true; private static final long DEFAULT_KERNEL_UID_READERS_THROTTLE_TIME = 1_000; @@ -16663,6 +16688,7 @@ public class BatteryStatsImpl extends BatteryStats { private static final int DEFAULT_MAX_HISTORY_FILES_LOW_RAM_DEVICE = 64; private static final int DEFAULT_MAX_HISTORY_BUFFER_LOW_RAM_DEVICE_KB = 64; /*Kilo Bytes*/ private static final int DEFAULT_BATTERY_CHARGED_DELAY_MS = 900000; /* 15 min */ + private static final boolean DEFAULT_PHONE_ON_EXTERNAL_STATS_COLLECTION = true; public boolean TRACK_CPU_ACTIVE_CLUSTER_TIME = DEFAULT_TRACK_CPU_ACTIVE_CLUSTER_TIME; /* Do not set default value for KERNEL_UID_READERS_THROTTLE_TIME. Need to trigger an @@ -16678,6 +16704,8 @@ public class BatteryStatsImpl extends BatteryStats { public int MAX_HISTORY_FILES; public int MAX_HISTORY_BUFFER; /*Bytes*/ public int BATTERY_CHARGED_DELAY_MS = DEFAULT_BATTERY_CHARGED_DELAY_MS; + public boolean PHONE_ON_EXTERNAL_STATS_COLLECTION = + DEFAULT_PHONE_ON_EXTERNAL_STATS_COLLECTION; private ContentResolver mResolver; private final KeyValueListParser mParser = new KeyValueListParser(','); @@ -16754,6 +16782,11 @@ public class BatteryStatsImpl extends BatteryStats { DEFAULT_MAX_HISTORY_BUFFER_LOW_RAM_DEVICE_KB : DEFAULT_MAX_HISTORY_BUFFER_KB) * 1024; + + PHONE_ON_EXTERNAL_STATS_COLLECTION = mParser.getBoolean( + KEY_PHONE_ON_EXTERNAL_STATS_COLLECTION, + DEFAULT_PHONE_ON_EXTERNAL_STATS_COLLECTION); + updateBatteryChargedDelayMsLocked(); } } @@ -16808,6 +16841,8 @@ public class BatteryStatsImpl extends BatteryStats { pw.println(MAX_HISTORY_BUFFER/1024); pw.print(KEY_BATTERY_CHARGED_DELAY_MS); pw.print("="); pw.println(BATTERY_CHARGED_DELAY_MS); + pw.print(KEY_PHONE_ON_EXTERNAL_STATS_COLLECTION); pw.print("="); + pw.println(PHONE_ON_EXTERNAL_STATS_COLLECTION); } } diff --git a/core/java/com/android/internal/os/PhonePowerCalculator.java b/core/java/com/android/internal/os/PhonePowerCalculator.java index cb893defab14..f1c4ffe07788 100644 --- a/core/java/com/android/internal/os/PhonePowerCalculator.java +++ b/core/java/com/android/internal/os/PhonePowerCalculator.java @@ -40,14 +40,27 @@ public class PhonePowerCalculator extends PowerCalculator { @Override public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats, long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) { + final long energyConsumerUC = batteryStats.getPhoneEnergyConsumptionUC(); + final int powerModel = getPowerModel(energyConsumerUC, query); + final long phoneOnTimeMs = batteryStats.getPhoneOnTime(rawRealtimeUs, BatteryStats.STATS_SINCE_CHARGED) / 1000; - final double phoneOnPower = mPowerEstimator.calculatePower(phoneOnTimeMs); - if (phoneOnPower != 0) { - builder.getAggregateBatteryConsumerBuilder( - BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE) - .setConsumedPower(BatteryConsumer.POWER_COMPONENT_PHONE, phoneOnPower) - .setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_PHONE, phoneOnTimeMs); + final double phoneOnPower; + switch (powerModel) { + case BatteryConsumer.POWER_MODEL_MEASURED_ENERGY: + phoneOnPower = uCtoMah(energyConsumerUC); + break; + case BatteryConsumer.POWER_MODEL_POWER_PROFILE: + default: + phoneOnPower = mPowerEstimator.calculatePower(phoneOnTimeMs); } + + if (phoneOnPower == 0.0) return; + + builder.getAggregateBatteryConsumerBuilder( + BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE) + .setConsumedPower(BatteryConsumer.POWER_COMPONENT_PHONE, phoneOnPower, powerModel) + .setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_PHONE, phoneOnTimeMs); + } } diff --git a/core/java/com/android/internal/power/MeasuredEnergyStats.java b/core/java/com/android/internal/power/MeasuredEnergyStats.java index 7fb8696a217d..5bfdd62592c8 100644 --- a/core/java/com/android/internal/power/MeasuredEnergyStats.java +++ b/core/java/com/android/internal/power/MeasuredEnergyStats.java @@ -59,7 +59,9 @@ public class MeasuredEnergyStats { public static final int POWER_BUCKET_BLUETOOTH = 5; public static final int POWER_BUCKET_GNSS = 6; public static final int POWER_BUCKET_MOBILE_RADIO = 7; - public static final int NUMBER_STANDARD_POWER_BUCKETS = 8; // Buckets above this are custom. + public static final int POWER_BUCKET_CAMERA = 8; + public static final int POWER_BUCKET_PHONE = 9; + public static final int NUMBER_STANDARD_POWER_BUCKETS = 10; // Buckets above this are custom. @IntDef(prefix = {"POWER_BUCKET_"}, value = { POWER_BUCKET_UNKNOWN, diff --git a/core/tests/coretests/src/com/android/internal/os/MobileRadioPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/MobileRadioPowerCalculatorTest.java index 00ac1985f897..0bdf491e6377 100644 --- a/core/tests/coretests/src/com/android/internal/os/MobileRadioPowerCalculatorTest.java +++ b/core/tests/coretests/src/com/android/internal/os/MobileRadioPowerCalculatorTest.java @@ -245,6 +245,8 @@ public class MobileRadioPowerCalculatorTest { stats.noteNetworkInterfaceForTransports("cellular", new int[]{NetworkCapabilities.TRANSPORT_CELLULAR}); + stats.notePhoneOnLocked(9800, 9800); + // Note application network activity NetworkStats networkStats = new NetworkStats(10000, 1) .addEntry(new NetworkStats.Entry("cellular", APP_UID, 0, 0, @@ -257,27 +259,33 @@ public class MobileRadioPowerCalculatorTest { mStatsRule.setTime(12_000, 12_000); - MobileRadioPowerCalculator calculator = + MobileRadioPowerCalculator mobileRadioPowerCalculator = new MobileRadioPowerCalculator(mStatsRule.getPowerProfile()); - - mStatsRule.apply(calculator); + PhonePowerCalculator phonePowerCalculator = + new PhonePowerCalculator(mStatsRule.getPowerProfile()); + mStatsRule.apply(mobileRadioPowerCalculator, phonePowerCalculator); UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID); assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO)) - .isWithin(PRECISION).of(1.53934); + .isWithin(PRECISION).of(1.38541); assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO)) .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY); BatteryConsumer deviceConsumer = mStatsRule.getDeviceBatteryConsumer(); // 10_000_000 micro-Coulomb * 1/1000 milli/micro * 1/3600 hour/second = 2.77778 mAh assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO)) - .isWithin(PRECISION).of(2.77778); + .isWithin(PRECISION).of(2.5); assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO)) .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY); + assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_PHONE)) + .isWithin(PRECISION).of(0.27778); + assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_PHONE)) + .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY); + BatteryConsumer appsConsumer = mStatsRule.getAppsBatteryConsumer(); assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO)) - .isWithin(PRECISION).of(1.53934); + .isWithin(PRECISION).of(1.38541); assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO)) .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY); } diff --git a/libs/WindowManager/Shell/res/color/unfold_background.xml b/libs/WindowManager/Shell/res/color/unfold_background.xml new file mode 100644 index 000000000000..e33eb126012d --- /dev/null +++ b/libs/WindowManager/Shell/res/color/unfold_background.xml @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="utf-8"?> +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:color="@android:color/system_neutral1_500" android:lStar="5" /> +</selector>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java index 6831a47c3f1a..1dd2ef94a959 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java @@ -16,6 +16,7 @@ package com.android.wm.shell.pip; +import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; @@ -538,6 +539,15 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, mPipTransitionController.startExitTransition(TRANSIT_EXIT_PIP, wct, destinationBounds); return; } + if (mSplitScreenOptional.isPresent()) { + // If pip activity will reparent to origin task case and if the origin task still under + // split root, just exit split screen here to ensure it could expand to fullscreen. + SplitScreenController split = mSplitScreenOptional.get(); + if (split.isTaskInSplitScreen(mTaskInfo.lastParentTaskIdBeforePip)) { + split.exitSplitScreen(INVALID_TASK_ID, + SplitScreenController.EXIT_REASON_APP_FINISHED); + } + } mSyncTransactionQueue.queue(wct); mSyncTransactionQueue.runInSync(t -> { // Make sure to grab the latest source hint rect as it could have been @@ -1481,9 +1491,13 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, applyFinishBoundsResize(wct, direction, false); } } else { - final boolean isPipTopLeft = - direction == TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN && isPipToTopLeft(); - applyFinishBoundsResize(wct, direction, isPipTopLeft); + applyFinishBoundsResize(wct, direction, isPipToTopLeft()); + // Use sync transaction to apply finish transaction for enter split case. + if (direction == TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN) { + mSyncTransactionQueue.runInSync(t -> { + t.merge(tx); + }); + } } finishResizeForMenu(destinationBounds); @@ -1520,7 +1534,10 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, mSurfaceTransactionHelper.round(tx, mLeash, isInPip()); wct.setBounds(mToken, taskBounds); - wct.setBoundsChangeTransaction(mToken, tx); + // Pip to split should use sync transaction to sync split bounds change. + if (direction != TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN) { + wct.setBoundsChangeTransaction(mToken, tx); + } } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java index 979b7c7dc31f..167c0321d3ad 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java @@ -282,7 +282,8 @@ public class PipMenuView extends FrameLayout { public void onFocusTaskChanged(ActivityManager.RunningTaskInfo taskInfo) { final boolean isSplitScreen = mSplitScreenControllerOptional.isPresent() - && mSplitScreenControllerOptional.get().isTaskInSplitScreen(taskInfo.taskId); + && mSplitScreenControllerOptional.get().isTaskInSplitScreenForeground( + taskInfo.taskId); mFocusedTaskAllowSplitScreen = isSplitScreen || (taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN && taskInfo.supportsMultiWindow diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java index 94b9e907fa76..7d5ab8428a3e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java @@ -31,7 +31,6 @@ import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSIT import static com.android.wm.shell.common.split.SplitScreenUtils.isValidToSplit; import static com.android.wm.shell.common.split.SplitScreenUtils.reverseSplitPosition; import static com.android.wm.shell.common.split.SplitScreenUtils.samePackage; -import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE; import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED; import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_SPLIT_SCREEN; import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS; @@ -89,7 +88,6 @@ import com.android.wm.shell.draganddrop.DragAndDropController; import com.android.wm.shell.draganddrop.DragAndDropPolicy; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.recents.RecentTasksController; -import com.android.wm.shell.splitscreen.SplitScreen.StageType; import com.android.wm.shell.sysui.KeyguardChangeListener; import com.android.wm.shell.sysui.ShellCommandHandler; import com.android.wm.shell.sysui.ShellController; @@ -329,9 +327,14 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, return mTaskOrganizer.getRunningTaskInfo(taskId); } + /** Check task is under split or not by taskId. */ public boolean isTaskInSplitScreen(int taskId) { - return isSplitScreenVisible() - && mStageCoordinator.getStageOfTask(taskId) != STAGE_TYPE_UNDEFINED; + return mStageCoordinator.getStageOfTask(taskId) != STAGE_TYPE_UNDEFINED; + } + + /** Check split is foreground and task is under split or not by taskId. */ + public boolean isTaskInSplitScreenForeground(int taskId) { + return isTaskInSplitScreen(taskId) && isSplitScreenVisible(); } public @SplitPosition int getSplitPosition(int taskId) { @@ -339,8 +342,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, } public boolean moveToSideStage(int taskId, @SplitPosition int sideStagePosition) { - return moveToStage(taskId, STAGE_TYPE_SIDE, sideStagePosition, - new WindowContainerTransaction()); + return moveToStage(taskId, sideStagePosition, new WindowContainerTransaction()); } /** @@ -351,13 +353,13 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, mStageCoordinator.updateSurfaces(transaction); } - private boolean moveToStage(int taskId, @StageType int stageType, - @SplitPosition int stagePosition, WindowContainerTransaction wct) { + private boolean moveToStage(int taskId, @SplitPosition int stagePosition, + WindowContainerTransaction wct) { final ActivityManager.RunningTaskInfo task = mTaskOrganizer.getRunningTaskInfo(taskId); if (task == null) { throw new IllegalArgumentException("Unknown taskId" + taskId); } - return mStageCoordinator.moveToStage(task, stageType, stagePosition, wct); + return mStageCoordinator.moveToStage(task, stagePosition, wct); } public boolean removeFromSideStage(int taskId) { @@ -382,10 +384,9 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, } public void enterSplitScreen(int taskId, boolean leftOrTop, WindowContainerTransaction wct) { - final int stageType = isSplitScreenVisible() ? STAGE_TYPE_UNDEFINED : STAGE_TYPE_SIDE; final int stagePosition = leftOrTop ? SPLIT_POSITION_TOP_OR_LEFT : SPLIT_POSITION_BOTTOM_OR_RIGHT; - moveToStage(taskId, stageType, stagePosition, wct); + moveToStage(taskId, stagePosition, wct); } public void exitSplitScreen(int toTopTaskId, @ExitReason int exitReason) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index 71ee690146f9..e2d7a7764808 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -399,56 +399,43 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, return STAGE_TYPE_UNDEFINED; } - boolean moveToStage(ActivityManager.RunningTaskInfo task, @StageType int stageType, - @SplitPosition int stagePosition, WindowContainerTransaction wct) { + boolean moveToStage(ActivityManager.RunningTaskInfo task, @SplitPosition int stagePosition, + WindowContainerTransaction wct) { StageTaskListener targetStage; int sideStagePosition; - if (stageType == STAGE_TYPE_MAIN) { - targetStage = mMainStage; - sideStagePosition = reverseSplitPosition(stagePosition); - } else if (stageType == STAGE_TYPE_SIDE) { + if (isSplitScreenVisible()) { + // If the split screen is foreground, retrieves target stage based on position. + targetStage = stagePosition == mSideStagePosition ? mSideStage : mMainStage; + sideStagePosition = mSideStagePosition; + } else { targetStage = mSideStage; sideStagePosition = stagePosition; - } else { - if (isSplitScreenVisible()) { - // If the split screen is activated, retrieves target stage based on position. - targetStage = stagePosition == mSideStagePosition ? mSideStage : mMainStage; - sideStagePosition = mSideStagePosition; - } else { - // Exit split if it running background. - exitSplitScreen(null /* childrenToTop */, EXIT_REASON_RECREATE_SPLIT); - - targetStage = mSideStage; - sideStagePosition = stagePosition; - } } if (!isSplitActive()) { - // prevent the fling divider to center transitioni if split screen didn't active. - mIsDropEntering = true; - } - - setSideStagePosition(sideStagePosition, wct); - final WindowContainerTransaction evictWct = new WindowContainerTransaction(); - targetStage.evictAllChildren(evictWct); - - // Apply surface bounds before animation start. - SurfaceControl.Transaction startT = mTransactionPool.acquire(); - if (startT != null) { - updateSurfaceBounds(mSplitLayout, startT, false /* applyResizingOffset */); - startT.apply(); - mTransactionPool.release(startT); + mSplitLayout.init(); + prepareEnterSplitScreen(wct, task, stagePosition); + mSyncQueue.queue(wct); + mSyncQueue.runInSync(t -> { + updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */); + }); + } else { + setSideStagePosition(sideStagePosition, wct); + targetStage.addTask(task, wct); + targetStage.evictAllChildren(wct); + if (!isSplitScreenVisible()) { + final StageTaskListener anotherStage = targetStage == mMainStage + ? mSideStage : mMainStage; + anotherStage.reparentTopTask(wct); + anotherStage.evictAllChildren(wct); + wct.reorder(mRootTaskInfo.token, true); + } + setRootForceTranslucent(false, wct); + mSyncQueue.queue(wct); } - // reparent the task to an invisible split root will make the activity invisible. Reorder - // the root task to front to make the entering transition from pip to split smooth. - wct.reorder(mRootTaskInfo.token, true); - wct.reorder(targetStage.mRootTaskInfo.token, true); - targetStage.addTask(task, wct); - if (!evictWct.isEmpty()) { - wct.merge(evictWct, true /* transfer */); - } - mTaskOrganizer.applyTransaction(wct); + // Due to drag already pip task entering split by this method so need to reset flag here. + mIsDropEntering = false; return true; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldBackgroundController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldBackgroundController.java index 86ca292399cb..fe0a3fb7b9dc 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldBackgroundController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldBackgroundController.java @@ -79,7 +79,7 @@ public class UnfoldBackgroundController { } private float[] getBackgroundColor(Context context) { - int colorInt = context.getResources().getColor(R.color.taskbar_background); + int colorInt = context.getResources().getColor(R.color.unfold_background); return new float[]{ (float) red(colorInt) / 255.0F, (float) green(colorInt) / 255.0F, diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java index 0bb809d354dc..0b528ba3a9ed 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java @@ -144,39 +144,48 @@ public class StageCoordinatorTests extends ShellTestCase { } @Test - public void testMoveToStage() { + public void testMoveToStage_splitActiveBackground() { + when(mStageCoordinator.isSplitActive()).thenReturn(true); + + final ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder().build(); + final WindowContainerTransaction wct = new WindowContainerTransaction(); + + mStageCoordinator.moveToStage(task, SPLIT_POSITION_BOTTOM_OR_RIGHT, wct); + verify(mSideStage).addTask(eq(task), eq(wct)); + assertEquals(SPLIT_POSITION_BOTTOM_OR_RIGHT, mStageCoordinator.getSideStagePosition()); + assertEquals(SPLIT_POSITION_TOP_OR_LEFT, mStageCoordinator.getMainStagePosition()); + } + + @Test + public void testMoveToStage_splitActiveForeground() { + when(mStageCoordinator.isSplitActive()).thenReturn(true); + when(mStageCoordinator.isSplitScreenVisible()).thenReturn(true); + // Assume current side stage is top or left. + mStageCoordinator.setSideStagePosition(SPLIT_POSITION_TOP_OR_LEFT, null); + final ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder().build(); + final WindowContainerTransaction wct = new WindowContainerTransaction(); - mStageCoordinator.moveToStage(task, STAGE_TYPE_MAIN, SPLIT_POSITION_BOTTOM_OR_RIGHT, - new WindowContainerTransaction()); - verify(mMainStage).addTask(eq(task), any(WindowContainerTransaction.class)); + mStageCoordinator.moveToStage(task, SPLIT_POSITION_BOTTOM_OR_RIGHT, wct); + verify(mMainStage).addTask(eq(task), eq(wct)); assertEquals(SPLIT_POSITION_BOTTOM_OR_RIGHT, mStageCoordinator.getMainStagePosition()); + assertEquals(SPLIT_POSITION_TOP_OR_LEFT, mStageCoordinator.getSideStagePosition()); - mStageCoordinator.moveToStage(task, STAGE_TYPE_SIDE, SPLIT_POSITION_BOTTOM_OR_RIGHT, - new WindowContainerTransaction()); - verify(mSideStage).addTask(eq(task), any(WindowContainerTransaction.class)); - assertEquals(SPLIT_POSITION_BOTTOM_OR_RIGHT, mStageCoordinator.getSideStagePosition()); + mStageCoordinator.moveToStage(task, SPLIT_POSITION_TOP_OR_LEFT, wct); + verify(mSideStage).addTask(eq(task), eq(wct)); + assertEquals(SPLIT_POSITION_TOP_OR_LEFT, mStageCoordinator.getSideStagePosition()); + assertEquals(SPLIT_POSITION_BOTTOM_OR_RIGHT, mStageCoordinator.getMainStagePosition()); } @Test - public void testMoveToUndefinedStage() { + public void testMoveToStage_splitInctive() { final ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder().build(); + final WindowContainerTransaction wct = new WindowContainerTransaction(); - // Verify move to undefined stage while split screen not activated moves task to side stage. - when(mStageCoordinator.isSplitScreenVisible()).thenReturn(false); - mStageCoordinator.setSideStagePosition(SPLIT_POSITION_TOP_OR_LEFT, null); - mStageCoordinator.moveToStage(task, STAGE_TYPE_UNDEFINED, SPLIT_POSITION_BOTTOM_OR_RIGHT, - new WindowContainerTransaction()); - verify(mSideStage).addTask(eq(task), any(WindowContainerTransaction.class)); + mStageCoordinator.moveToStage(task, SPLIT_POSITION_BOTTOM_OR_RIGHT, wct); + verify(mStageCoordinator).prepareEnterSplitScreen(eq(wct), eq(task), + eq(SPLIT_POSITION_BOTTOM_OR_RIGHT)); assertEquals(SPLIT_POSITION_BOTTOM_OR_RIGHT, mStageCoordinator.getSideStagePosition()); - - // Verify move to undefined stage after split screen activated moves task based on position. - when(mStageCoordinator.isSplitScreenVisible()).thenReturn(true); - assertEquals(SPLIT_POSITION_TOP_OR_LEFT, mStageCoordinator.getMainStagePosition()); - mStageCoordinator.moveToStage(task, STAGE_TYPE_UNDEFINED, SPLIT_POSITION_TOP_OR_LEFT, - new WindowContainerTransaction()); - verify(mMainStage).addTask(eq(task), any(WindowContainerTransaction.class)); - assertEquals(SPLIT_POSITION_TOP_OR_LEFT, mStageCoordinator.getMainStagePosition()); } @Test diff --git a/packages/SystemUI/res/layout/chipbar.xml b/packages/SystemUI/res/layout/chipbar.xml index a3171784903f..762dcdced9c4 100644 --- a/packages/SystemUI/res/layout/chipbar.xml +++ b/packages/SystemUI/res/layout/chipbar.xml @@ -55,7 +55,7 @@ android:layout_height="wrap_content" android:layout_weight="1" android:textSize="@dimen/chipbar_text_size" - android:textColor="@android:color/system_accent2_900" + android:textColor="@color/chipbar_text_and_icon_color" android:alpha="0.0" /> diff --git a/packages/SystemUI/res/layout/status_bar_user_chip_container.xml b/packages/SystemUI/res/layout/status_bar_user_chip_container.xml index b374074958cb..80f5d87bec00 100644 --- a/packages/SystemUI/res/layout/status_bar_user_chip_container.xml +++ b/packages/SystemUI/res/layout/status_bar_user_chip_container.xml @@ -24,7 +24,7 @@ android:orientation="horizontal" android:layout_marginEnd="@dimen/status_bar_user_chip_end_margin" android:background="@drawable/status_bar_user_chip_bg" - android:visibility="visible" > + android:visibility="gone" > <ImageView android:id="@+id/current_user_avatar" android:layout_width="@dimen/status_bar_user_chip_avatar_size" android:layout_height="@dimen/status_bar_user_chip_avatar_size" diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml index 3763b1747197..9d00a27442ca 100644 --- a/packages/SystemUI/res/values/colors.xml +++ b/packages/SystemUI/res/values/colors.xml @@ -230,6 +230,9 @@ <color name="people_tile_background">@color/material_dynamic_secondary95</color> + <!-- Chipbar --> + <color name="chipbar_text_and_icon_color">@android:color/system_accent2_900</color> + <!-- Internet Dialog --> <!-- Material next state on color--> <color name="settingslib_state_on_color">@color/settingslib_state_on</color> diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt index 4aaa566eb852..3b9060ad0ac3 100644 --- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt +++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt @@ -259,7 +259,7 @@ constructor( largeTimeListener?.update(shouldTimeListenerRun) } - override fun onTimeFormatChanged(timeFormat: String) { + override fun onTimeFormatChanged(timeFormat: String?) { clock?.events?.onTimeFormatChanged(DateFormat.is24HourFormat(context)) } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java index b85b2b8314ed..ba217804c96e 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java @@ -22,6 +22,7 @@ import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; import static com.android.keyguard.KeyguardClockSwitch.LARGE; import static com.android.keyguard.KeyguardClockSwitch.SMALL; +import android.annotation.Nullable; import android.database.ContentObserver; import android.os.UserHandle; import android.provider.Settings; @@ -458,6 +459,7 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS mView.setClock(clock, mStatusBarStateController.getState()); } + @Nullable private ClockController getClock() { return mClockEventController.getClock(); } @@ -510,8 +512,10 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS } /** Gets the animations for the current clock. */ + @Nullable public ClockAnimations getClockAnimations() { - return getClock().getAnimations(); + ClockController clock = getClock(); + return clock == null ? null : clock.getAnimations(); } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java index c098d4ca286d..061bab8a7006 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java @@ -260,6 +260,14 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard */ @Override public void finish(boolean strongAuth, int targetUserId) { + if (mFeatureFlags.isEnabled(Flags.PREVENT_BYPASS_KEYGUARD) + && !mKeyguardStateController.canDismissLockScreen() && !strongAuth) { + Log.e(TAG, + "Tried to dismiss keyguard when lockscreen is not dismissible and user " + + "was not authenticated with a primary security method " + + "(pin/password/pattern)."); + return; + } // If there's a pending runnable because the user interacted with a widget // and we're leaving keyguard, then run it. boolean deferKeyguardDone = false; diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 1ccde322628a..e154695e56e3 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -68,6 +68,7 @@ import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_STARTED_WAK import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_STRONG_AUTH_CHANGED; import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_USER_SWITCHING; import static com.android.systemui.DejankUtils.whitelistIpcs; +import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_OPENED; import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_UNKNOWN; import android.annotation.AnyThread; @@ -1601,7 +1602,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab requestActiveUnlock( ActiveUnlockConfig.ActiveUnlockRequestOrigin.ASSISTANT, "assistant", - false); + /* dismissKeyguard */ true); } } @@ -1862,6 +1863,11 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab updateFaceListeningState(BIOMETRIC_ACTION_STOP, FACE_AUTH_UPDATED_POSTURE_CHANGED); } + if (mPostureState == DEVICE_POSTURE_OPENED) { + mLogger.d("Posture changed to open - attempting to request active unlock"); + requestActiveUnlockFromWakeReason(PowerManager.WAKE_REASON_UNFOLD_DEVICE, + false); + } } }; @@ -1988,26 +1994,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab FACE_AUTH_UPDATED_STARTED_WAKING_UP.setExtraInfo(pmWakeReason); updateFaceListeningState(BIOMETRIC_ACTION_UPDATE, FACE_AUTH_UPDATED_STARTED_WAKING_UP); - - final ActiveUnlockConfig.ActiveUnlockRequestOrigin requestOrigin = - mActiveUnlockConfig.isWakeupConsideredUnlockIntent(pmWakeReason) - ? ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT - : ActiveUnlockConfig.ActiveUnlockRequestOrigin.WAKE; - final String reason = "wakingUp - " + PowerManager.wakeReasonToString(pmWakeReason); - if (mActiveUnlockConfig.shouldWakeupForceDismissKeyguard(pmWakeReason)) { - requestActiveUnlockDismissKeyguard( - requestOrigin, - reason - ); - } else { - requestActiveUnlock( - requestOrigin, - reason - ); - } } else { mLogger.logSkipUpdateFaceListeningOnWakeup(pmWakeReason); } + requestActiveUnlockFromWakeReason(pmWakeReason, true); for (int i = 0; i < mCallbacks.size(); i++) { KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); @@ -2640,6 +2630,32 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } } + private void requestActiveUnlockFromWakeReason(@PowerManager.WakeReason int wakeReason, + boolean powerManagerWakeup) { + if (!mFaceWakeUpTriggersConfig.shouldTriggerFaceAuthOnWakeUpFrom(wakeReason)) { + mLogger.logActiveUnlockRequestSkippedForWakeReasonDueToFaceConfig(wakeReason); + return; + } + + final ActiveUnlockConfig.ActiveUnlockRequestOrigin requestOrigin = + mActiveUnlockConfig.isWakeupConsideredUnlockIntent(wakeReason) + ? ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT + : ActiveUnlockConfig.ActiveUnlockRequestOrigin.WAKE; + final String reason = "wakingUp - " + PowerManager.wakeReasonToString(wakeReason) + + " powerManagerWakeup=" + powerManagerWakeup; + if (mActiveUnlockConfig.shouldWakeupForceDismissKeyguard(wakeReason)) { + requestActiveUnlockDismissKeyguard( + requestOrigin, + reason + ); + } else { + requestActiveUnlock( + requestOrigin, + reason + ); + } + } + /** * Attempts to trigger active unlock from trust agent. */ diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt index 51628076953b..16618064f249 100644 --- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt +++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt @@ -62,6 +62,16 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) { ) } + fun logActiveUnlockRequestSkippedForWakeReasonDueToFaceConfig(wakeReason: Int) { + logBuffer.log( + "ActiveUnlock", + DEBUG, + { int1 = wakeReason }, + { "Skip requesting active unlock from wake reason that doesn't trigger face auth" + + " reason=${PowerManager.wakeReasonToString(int1)}" } + ) + } + fun logAuthInterruptDetected(active: Boolean) { logBuffer.log(TAG, DEBUG, { bool1 = active }, { "onAuthInterruptDetected($bool1)" }) } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt index 61d039bf53c6..c98a62f36656 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt @@ -195,7 +195,7 @@ constructor( scope.launch { alternateBouncerInteractor.isVisible.collect { isVisible: Boolean -> if (isVisible) { - show(SideFpsUiRequestSource.ALTERNATE_BOUNCER) + show(SideFpsUiRequestSource.ALTERNATE_BOUNCER, REASON_AUTH_KEYGUARD) } else { hide(SideFpsUiRequestSource.ALTERNATE_BOUNCER) } @@ -436,13 +436,17 @@ private fun LottieAnimationView.addOverlayDynamicColor( @BiometricOverlayConstants.ShowReason reason: Int ) { fun update() { - val c = context.getColor(R.color.biometric_dialog_accent) - val chevronFill = context.getColor(R.color.sfps_chevron_fill) val isKeyguard = reason == REASON_AUTH_KEYGUARD if (isKeyguard) { + val color = context.getColor(R.color.numpad_key_color_secondary) // match bouncer color + val chevronFill = + com.android.settingslib.Utils.getColorAttrDefaultColor( + context, + android.R.attr.textColorPrimaryInverse + ) for (key in listOf(".blue600", ".blue400")) { addValueCallback(KeyPath(key, "**"), LottieProperty.COLOR_FILTER) { - PorterDuffColorFilter(c, PorterDuff.Mode.SRC_ATOP) + PorterDuffColorFilter(color, PorterDuff.Mode.SRC_ATOP) } } addValueCallback(KeyPath(".black", "**"), LottieProperty.COLOR_FILTER) { diff --git a/packages/SystemUI/src/com/android/systemui/common/shared/model/TintedIcon.kt b/packages/SystemUI/src/com/android/systemui/common/shared/model/TintedIcon.kt index 5dabbbb81701..6a6c3eb05399 100644 --- a/packages/SystemUI/src/com/android/systemui/common/shared/model/TintedIcon.kt +++ b/packages/SystemUI/src/com/android/systemui/common/shared/model/TintedIcon.kt @@ -16,10 +16,10 @@ package com.android.systemui.common.shared.model -import androidx.annotation.AttrRes +import androidx.annotation.ColorRes /** Models an icon with a specific tint. */ data class TintedIcon( val icon: Icon, - @AttrRes val tintAttr: Int?, + @ColorRes val tint: Int?, ) diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/binder/TintedIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/common/ui/binder/TintedIconViewBinder.kt index dea8cfda80c3..bcc5932dcf30 100644 --- a/packages/SystemUI/src/com/android/systemui/common/ui/binder/TintedIconViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/common/ui/binder/TintedIconViewBinder.kt @@ -17,15 +17,14 @@ package com.android.systemui.common.ui.binder import android.widget.ImageView -import com.android.settingslib.Utils import com.android.systemui.common.shared.model.TintedIcon object TintedIconViewBinder { /** * Binds the given tinted icon to the view. * - * [TintedIcon.tintAttr] will always be applied, meaning that if it is null, then the tint - * *will* be reset to null. + * [TintedIcon.tint] will always be applied, meaning that if it is null, then the tint *will* be + * reset to null. */ fun bind( tintedIcon: TintedIcon, @@ -33,8 +32,8 @@ object TintedIconViewBinder { ) { IconViewBinder.bind(tintedIcon.icon, view) view.imageTintList = - if (tintedIcon.tintAttr != null) { - Utils.getColorAttr(view.context, tintedIcon.tintAttr) + if (tintedIcon.tint != null) { + view.resources.getColorStateList(tintedIcon.tint, view.context.theme) } else { null } diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt index 163990ec7d4b..d2833796d2a5 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt @@ -608,6 +608,7 @@ class ControlsUiControllerImpl @Inject constructor ( if (items.size == 1) { spinner.setBackground(null) anchor.setOnClickListener(null) + anchor.isClickable = false return } else { spinner.background = parent.context.resources diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamMonitor.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamMonitor.java index 055cd52b23d6..7f567aa334a6 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamMonitor.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamMonitor.java @@ -23,6 +23,7 @@ import android.util.Log; import com.android.systemui.CoreStartable; import com.android.systemui.dreams.callbacks.DreamStatusBarStateCallback; import com.android.systemui.dreams.conditions.DreamCondition; +import com.android.systemui.flags.RestartDozeListener; import com.android.systemui.shared.condition.Monitor; import com.android.systemui.util.condition.ConditionalCoreStartable; @@ -39,17 +40,19 @@ public class DreamMonitor extends ConditionalCoreStartable { private final Monitor mConditionMonitor; private final DreamCondition mDreamCondition; private final DreamStatusBarStateCallback mCallback; + private RestartDozeListener mRestartDozeListener; @Inject public DreamMonitor(Monitor monitor, DreamCondition dreamCondition, @Named(DREAM_PRETEXT_MONITOR) Monitor pretextMonitor, - DreamStatusBarStateCallback callback) { + DreamStatusBarStateCallback callback, + RestartDozeListener restartDozeListener) { super(pretextMonitor); mConditionMonitor = monitor; mDreamCondition = dreamCondition; mCallback = callback; - + mRestartDozeListener = restartDozeListener; } @Override @@ -61,5 +64,8 @@ public class DreamMonitor extends ConditionalCoreStartable { mConditionMonitor.addSubscription(new Monitor.Subscription.Builder(mCallback) .addCondition(mDreamCondition) .build()); + + mRestartDozeListener.init(); + mRestartDozeListener.maybeRestartSleep(); } } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/conditions/DreamCondition.java b/packages/SystemUI/src/com/android/systemui/dreams/conditions/DreamCondition.java index 2befce7065ec..5bbfbda82944 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/conditions/DreamCondition.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/conditions/DreamCondition.java @@ -15,6 +15,7 @@ */ package com.android.systemui.dreams.conditions; +import android.app.DreamManager; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -30,6 +31,7 @@ import javax.inject.Inject; */ public class DreamCondition extends Condition { private final Context mContext; + private final DreamManager mDreamManager; private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override @@ -39,8 +41,10 @@ public class DreamCondition extends Condition { }; @Inject - public DreamCondition(Context context) { + public DreamCondition(Context context, + DreamManager dreamManager) { mContext = context; + mDreamManager = dreamManager; } private void processIntent(Intent intent) { @@ -62,8 +66,8 @@ public class DreamCondition extends Condition { final IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_DREAMING_STARTED); filter.addAction(Intent.ACTION_DREAMING_STOPPED); - final Intent stickyIntent = mContext.registerReceiver(mReceiver, filter); - processIntent(stickyIntent); + mContext.registerReceiver(mReceiver, filter); + updateCondition(mDreamManager.isDreaming()); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt index 06ca0adfa928..28c45b874b24 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt @@ -22,7 +22,6 @@ import com.android.systemui.broadcast.BroadcastSender import com.android.systemui.dump.DumpManager import com.android.systemui.statusbar.commandline.CommandRegistry import com.android.systemui.util.InitializationChecker -import com.android.systemui.util.concurrency.DelayableExecutor import dagger.Binds import dagger.Module import dagger.multibindings.ClassKey @@ -38,8 +37,6 @@ constructor( private val featureFlags: FeatureFlagsDebug, private val broadcastSender: BroadcastSender, private val initializationChecker: InitializationChecker, - private val restartDozeListener: RestartDozeListener, - private val delayableExecutor: DelayableExecutor ) : CoreStartable { init { @@ -55,9 +52,6 @@ constructor( // protected broadcast should only be sent for the main process val intent = Intent(FlagManager.ACTION_SYSUI_STARTED) broadcastSender.sendBroadcast(intent) - - restartDozeListener.init() - delayableExecutor.executeDelayed({ restartDozeListener.maybeRestartSleep() }, 1000) } } } diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseStartable.kt b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseStartable.kt index 133e67f2822b..f97112d384be 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseStartable.kt @@ -18,8 +18,6 @@ package com.android.systemui.flags import com.android.systemui.CoreStartable import com.android.systemui.dump.DumpManager -import com.android.systemui.util.InitializationChecker -import com.android.systemui.util.concurrency.DelayableExecutor import dagger.Binds import dagger.Module import dagger.multibindings.ClassKey @@ -31,9 +29,6 @@ class FeatureFlagsReleaseStartable constructor( dumpManager: DumpManager, featureFlags: FeatureFlags, - private val initializationChecker: InitializationChecker, - private val restartDozeListener: RestartDozeListener, - private val delayableExecutor: DelayableExecutor ) : CoreStartable { init { @@ -42,12 +37,7 @@ constructor( } } - override fun start() { - if (initializationChecker.initializeComponents()) { - restartDozeListener.init() - delayableExecutor.executeDelayed({ restartDozeListener.maybeRestartSleep() }, 1000) - } - } + override fun start() {} } @Module diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index 30e99161d38f..5c78e973e056 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -235,6 +235,11 @@ object Flags { @JvmField val ASYNC_INFLATE_BOUNCER = unreleasedFlag(229, "async_inflate_bouncer", teamfood = true) + /** Whether to inflate the bouncer view on a background thread. */ + // TODO(b/273341787): Tracking Bug + @JvmField + val PREVENT_BYPASS_KEYGUARD = unreleasedFlag(230, "prevent_bypass_keyguard") + // 300 - power menu // TODO(b/254512600): Tracking Bug @JvmField val POWER_MENU_LITE = releasedFlag(300, "power_menu_lite") @@ -253,20 +258,12 @@ object Flags { // TODO(b/270223352): Tracking Bug @JvmField val HIDE_SMARTSPACE_ON_DREAM_OVERLAY = - unreleasedFlag( - 404, - "hide_smartspace_on_dream_overlay", - teamfood = true - ) + releasedFlag(404, "hide_smartspace_on_dream_overlay") // TODO(b/271460958): Tracking Bug @JvmField val SHOW_WEATHER_COMPLICATION_ON_DREAM_OVERLAY = - unreleasedFlag( - 405, - "show_weather_complication_on_dream_overlay", - teamfood = true - ) + releasedFlag(405, "show_weather_complication_on_dream_overlay") // 500 - quick settings @@ -420,7 +417,7 @@ object Flags { @JvmField val ROUNDED_BOX_RIPPLE = releasedFlag(1002, "rounded_box_ripple") // TODO(b/270882464): Tracking Bug - val ENABLE_DOCK_SETUP_V2 = unreleasedFlag(1005, "enable_dock_setup_v2", teamfood = true) + val ENABLE_DOCK_SETUP_V2 = releasedFlag(1005, "enable_dock_setup_v2") // TODO(b/265045965): Tracking Bug val SHOW_LOWLIGHT_ON_DIRECT_BOOT = releasedFlag(1003, "show_lowlight_on_direct_boot") diff --git a/packages/SystemUI/src/com/android/systemui/flags/RestartDozeListener.kt b/packages/SystemUI/src/com/android/systemui/flags/RestartDozeListener.kt index bd74f4e5daab..b49d60dbdde1 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/RestartDozeListener.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/RestartDozeListener.kt @@ -20,7 +20,9 @@ import android.os.PowerManager import android.util.Log import com.android.internal.annotations.VisibleForTesting import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.util.concurrency.DelayableExecutor import com.android.systemui.util.settings.SecureSettings import com.android.systemui.util.time.SystemClock import javax.inject.Inject @@ -33,6 +35,7 @@ constructor( private val statusBarStateController: StatusBarStateController, private val powerManager: PowerManager, private val systemClock: SystemClock, + @Background val bgExecutor: DelayableExecutor, ) { companion object { @@ -44,7 +47,7 @@ constructor( val listener = object : StatusBarStateController.StateListener { override fun onDreamingChanged(isDreaming: Boolean) { - settings.putBool(RESTART_NAP_KEY, isDreaming) + storeSleepState(isDreaming) } } @@ -62,11 +65,19 @@ constructor( } fun maybeRestartSleep() { - if (settings.getBool(RESTART_NAP_KEY, false)) { - Log.d("RestartDozeListener", "Restarting sleep state") - powerManager.wakeUp(systemClock.uptimeMillis()) - powerManager.goToSleep(systemClock.uptimeMillis()) - settings.putBool(RESTART_NAP_KEY, false) - } + bgExecutor.executeDelayed( + { + if (settings.getBool(RESTART_NAP_KEY, false)) { + Log.d("RestartDozeListener", "Restarting sleep state") + powerManager.wakeUp(systemClock.uptimeMillis()) + powerManager.goToSleep(systemClock.uptimeMillis()) + } + }, + 1000 + ) + } + + private fun storeSleepState(sleeping: Boolean) { + bgExecutor.execute { settings.putBool(RESTART_NAP_KEY, sleeping) } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractor.kt index eae40d61cdb6..9a90bb3c9b1e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractor.kt @@ -23,6 +23,7 @@ import com.android.systemui.flags.Flags import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository +import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager.LegacyAlternateBouncer import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.time.SystemClock @@ -34,6 +35,7 @@ import kotlinx.coroutines.flow.Flow class AlternateBouncerInteractor @Inject constructor( + private val statusBarStateController: StatusBarStateController, private val keyguardStateController: KeyguardStateController, private val bouncerRepository: KeyguardBouncerRepository, private val biometricSettingsRepository: BiometricSettingsRepository, @@ -48,6 +50,17 @@ constructor( val isVisible: Flow<Boolean> = bouncerRepository.alternateBouncerVisible + private val keyguardStateControllerCallback: KeyguardStateController.Callback = + object : KeyguardStateController.Callback { + override fun onUnlockedChanged() { + maybeHide() + } + } + + init { + keyguardStateController.addCallback(keyguardStateControllerCallback) + } + /** * Sets the correct bouncer states to show the alternate bouncer if it can show. * @@ -107,7 +120,8 @@ constructor( biometricSettingsRepository.isStrongBiometricAllowed.value && biometricSettingsRepository.isFingerprintEnabledByDevicePolicy.value && !deviceEntryFingerprintAuthRepository.isLockedOut.value && - !keyguardStateController.isUnlocked + !keyguardStateController.isUnlocked && + !statusBarStateController.isDozing } else { legacyAlternateBouncer != null && keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(true) @@ -127,6 +141,12 @@ constructor( } } + private fun maybeHide() { + if (isVisibleState() && !canShowAlternateBouncerForFingerprint()) { + hide() + } + } + companion object { private const val MIN_VISIBILITY_DURATION_UNTIL_TOUCHES_DISMISS_ALTERNATE_BOUNCER_MS = 200L private const val NOT_VISIBLE = -1L diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java index 98b6d70317b8..60192b8c33df 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java @@ -152,6 +152,14 @@ public class LogModule { return factory.create("QSLog", 700 /* maxSize */, false /* systrace */); } + /** Provides a logging buffer for logs related to Quick Settings configuration. */ + @Provides + @SysUISingleton + @QSConfigLog + public static LogBuffer provideQSConfigLogBuffer(LogBufferFactory factory) { + return factory.create("QSConfigLog", 100 /* maxSize */, true /* systrace */); + } + /** Provides a logging buffer for {@link com.android.systemui.broadcast.BroadcastDispatcher} */ @Provides @SysUISingleton diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/QSConfigLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/QSConfigLog.java new file mode 100644 index 000000000000..295bf88d498f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/QSConfigLog.java @@ -0,0 +1,33 @@ +/* + * 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.log.dagger; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import com.android.systemui.plugins.log.LogBuffer; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; + +import javax.inject.Qualifier; + +/** A {@link LogBuffer} for QS configuration changed messages. */ +@Qualifier +@Documented +@Retention(RUNTIME) +public @interface QSConfigLog { +} diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt index ee93c3788243..dbc2a5ec4e0a 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt @@ -19,12 +19,13 @@ package com.android.systemui.media.taptotransfer.common import android.content.Context import android.content.pm.PackageManager import android.graphics.drawable.Drawable -import androidx.annotation.AttrRes +import androidx.annotation.ColorRes import androidx.annotation.DrawableRes import com.android.systemui.R import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon import com.android.systemui.common.shared.model.TintedIcon +import com.android.systemui.temporarydisplay.chipbar.ChipbarInfo.Companion.DEFAULT_ICON_TINT /** Utility methods for media tap-to-transfer. */ class MediaTttUtils { @@ -78,7 +79,7 @@ class MediaTttUtils { return IconInfo( contentDescription, MediaTttIcon.Loaded(packageManager.getApplicationIcon(appPackageName)), - tintAttr = null, + tint = null, isAppIcon = true ) } catch (e: PackageManager.NameNotFoundException) { @@ -96,7 +97,7 @@ class MediaTttUtils { ) }, MediaTttIcon.Resource(R.drawable.ic_cast), - tintAttr = android.R.attr.textColorPrimary, + tint = DEFAULT_ICON_TINT, isAppIcon = false ) } @@ -107,7 +108,7 @@ class MediaTttUtils { data class IconInfo( val contentDescription: ContentDescription, val icon: MediaTttIcon, - @AttrRes val tintAttr: Int?, + @ColorRes val tint: Int?, /** * True if [drawable] is the app's icon, and false if [drawable] is some generic default icon. */ @@ -120,7 +121,7 @@ data class IconInfo( is MediaTttIcon.Loaded -> Icon.Loaded(icon.drawable, contentDescription) is MediaTttIcon.Resource -> Icon.Resource(icon.res, contentDescription) } - return TintedIcon(iconOutput, tintAttr) + return TintedIcon(iconOutput, tint) } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java index 2668d2e36731..fdab9b16c7a1 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java @@ -91,16 +91,19 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr new QSPanel.OnConfigurationChangedListener() { @Override public void onConfigurationChange(Configuration newConfig) { - mQSLogger.logOnConfigurationChanged( - /* lastOrientation= */ mLastOrientation, - /* newOrientation= */ newConfig.orientation, - /* containerName= */ mView.getDumpableTag()); - - boolean previousSplitShadeState = mShouldUseSplitNotificationShade; + final boolean previousSplitShadeState = mShouldUseSplitNotificationShade; + final int previousOrientation = mLastOrientation; mShouldUseSplitNotificationShade = - LargeScreenUtils.shouldUseSplitNotificationShade(getResources()); + LargeScreenUtils.shouldUseSplitNotificationShade(getResources()); mLastOrientation = newConfig.orientation; + mQSLogger.logOnConfigurationChanged( + /* oldOrientation= */ previousOrientation, + /* newOrientation= */ mLastOrientation, + /* oldShouldUseSplitShade= */ previousSplitShadeState, + /* newShouldUseSplitShade= */ mShouldUseSplitNotificationShade, + /* containerName= */ mView.getDumpableTag()); + switchTileLayoutIfNeeded(); onConfigurationChanged(); if (previousSplitShadeState != mShouldUseSplitNotificationShade) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java index adc71657e680..91ecaea02c47 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java +++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java @@ -132,9 +132,8 @@ public class TileServices extends IQSService.Stub { mServices.remove(tile); mTokenMap.remove(service.getToken()); mTiles.remove(tile.getComponent()); - final String slot = tile.getComponent().getClassName(); - // TileServices doesn't know how to add more than 1 icon per slot, so remove all - mMainHandler.post(() -> mStatusBarIconController.removeAllIconsForSlot(slot)); + final String slot = getStatusBarIconSlotName(tile.getComponent()); + mMainHandler.post(() -> mStatusBarIconController.removeIconForTile(slot)); } } @@ -308,12 +307,11 @@ public class TileServices extends IQSService.Stub { ? new StatusBarIcon(userHandle, packageName, icon, 0, 0, contentDescription) : null; + final String slot = getStatusBarIconSlotName(componentName); mMainHandler.post(new Runnable() { @Override public void run() { - StatusBarIconController iconController = mStatusBarIconController; - iconController.setIcon(componentName.getClassName(), statusIcon); - iconController.setExternalIcon(componentName.getClassName()); + mStatusBarIconController.setIconFromTile(slot, statusIcon); } }); } @@ -373,6 +371,12 @@ public class TileServices extends IQSService.Stub { mCommandQueue.removeCallback(mRequestListeningCallback); } + /** Returns the slot name that should be used when adding or removing status bar icons. */ + private String getStatusBarIconSlotName(ComponentName componentName) { + return componentName.getClassName(); + } + + private final CommandQueue.Callbacks mRequestListeningCallback = new CommandQueue.Callbacks() { @Override public void requestTileServiceListeningState(@NonNull ComponentName componentName) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt index 23c41db6d5a6..5b461a6d8bad 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt @@ -16,8 +16,12 @@ package com.android.systemui.qs.logging +import android.content.res.Configuration.ORIENTATION_LANDSCAPE +import android.content.res.Configuration.ORIENTATION_PORTRAIT +import android.content.res.Configuration.Orientation import android.service.quicksettings.Tile import android.view.View +import com.android.systemui.log.dagger.QSConfigLog import com.android.systemui.log.dagger.QSLog import com.android.systemui.plugins.log.ConstantStringsLogger import com.android.systemui.plugins.log.ConstantStringsLoggerImpl @@ -32,8 +36,12 @@ import javax.inject.Inject private const val TAG = "QSLog" -class QSLogger @Inject constructor(@QSLog private val buffer: LogBuffer) : - ConstantStringsLogger by ConstantStringsLoggerImpl(buffer, TAG) { +class QSLogger +@Inject +constructor( + @QSLog private val buffer: LogBuffer, + @QSConfigLog private val configChangedBuffer: LogBuffer, +) : ConstantStringsLogger by ConstantStringsLoggerImpl(buffer, TAG) { fun logException(@CompileTimeConstant logMsg: String, ex: Exception) { buffer.log(TAG, ERROR, {}, { logMsg }, exception = ex) @@ -264,19 +272,28 @@ class QSLogger @Inject constructor(@QSLog private val buffer: LogBuffer) : } fun logOnConfigurationChanged( - lastOrientation: Int, - newOrientation: Int, + @Orientation oldOrientation: Int, + @Orientation newOrientation: Int, + newShouldUseSplitShade: Boolean, + oldShouldUseSplitShade: Boolean, containerName: String ) { - buffer.log( + configChangedBuffer.log( TAG, DEBUG, { str1 = containerName - int1 = lastOrientation + int1 = oldOrientation int2 = newOrientation + bool1 = oldShouldUseSplitShade + bool2 = newShouldUseSplitShade }, - { "configuration change: $str1 orientation was $int1, now $int2" } + { + "config change: " + + "$str1 orientation=${toOrientationString(int2)} " + + "(was ${toOrientationString(int1)}), " + + "splitShade=$bool2 (was $bool1)" + } ) } @@ -353,3 +370,11 @@ class QSLogger @Inject constructor(@QSLog private val buffer: LogBuffer) : } } } + +private inline fun toOrientationString(@Orientation orientation: Int): String { + return when (orientation) { + ORIENTATION_LANDSCAPE -> "land" + ORIENTATION_PORTRAIT -> "port" + else -> "undefined" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 6ab7bfd68999..c4784d6211bb 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -158,6 +158,7 @@ import com.android.systemui.model.SysUiState; import com.android.systemui.navigationbar.NavigationBarController; import com.android.systemui.navigationbar.NavigationBarView; import com.android.systemui.navigationbar.NavigationModeController; +import com.android.systemui.plugins.ClockAnimations; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.FalsingManager.FalsingTapListener; import com.android.systemui.plugins.qs.QS; @@ -1574,10 +1575,9 @@ public final class NotificationPanelViewController implements Dumpable { transition.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); transition.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD); - boolean customClockAnimation = - mKeyguardStatusViewController.getClockAnimations() != null - && mKeyguardStatusViewController.getClockAnimations() - .getHasCustomPositionUpdatedAnimation(); + ClockAnimations clockAnims = mKeyguardStatusViewController.getClockAnimations(); + boolean customClockAnimation = clockAnims != null + && clockAnims.getHasCustomPositionUpdatedAnimation(); if (mFeatureFlags.isEnabled(Flags.STEP_CLOCK_ANIMATION) && customClockAnimation) { // Find the clock, so we can exclude it from this transition. @@ -3556,7 +3556,13 @@ public final class NotificationPanelViewController implements Dumpable { : (mKeyguardStateController.canDismissLockScreen() ? UNLOCK : BOUNCER_UNLOCK); - fling(vel, expand, isFalseTouch(x, y, interactionType)); + // don't fling while in keyguard to avoid jump in shade expand animation; + // touch has been intercepted already so flinging here is redundant + if (mBarState == KEYGUARD && mExpandedFraction >= 1.0) { + mShadeLog.d("NPVC endMotionEvent - skipping fling on keyguard"); + } else { + fling(vel, expand, isFalseTouch(x, y, interactionType)); + } onTrackingStopped(expand); mUpdateFlingOnLayout = expand && mPanelClosedOnDown && !mHasLayoutedSinceDown; if (mUpdateFlingOnLayout) { @@ -5027,9 +5033,14 @@ public final class NotificationPanelViewController implements Dumpable { Rect from = (Rect) startValues.values.get(PROP_BOUNDS); Rect to = (Rect) endValues.values.get(PROP_BOUNDS); - anim.addUpdateListener( - animation -> mController.getClockAnimations().onPositionUpdated( - from, to, animation.getAnimatedFraction())); + anim.addUpdateListener(animation -> { + ClockAnimations clockAnims = mController.getClockAnimations(); + if (clockAnims == null) { + return; + } + + clockAnims.onPositionUpdated(from, to, animation.getAnimatedFraction()); + }); return anim; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java index 04cc8ce792d1..30d2295206d8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java @@ -81,28 +81,22 @@ public interface StatusBarIconController { void refreshIconGroup(IconManager iconManager); /** - * Adds or updates an icon for a given slot for a **tile service icon**. + * Adds or updates an icon that comes from an active tile service. * - * TODO(b/265307726): Merge with {@link #setIcon(String, StatusBarIcon)} or make this method - * much more clearly distinct from that method. + * If the icon is null, the icon will be removed. */ - void setExternalIcon(String slot); + void setIconFromTile(String slot, @Nullable StatusBarIcon icon); + + /** Removes an icon that had come from an active tile service. */ + void removeIconForTile(String slot); /** * Adds or updates an icon for the given slot for **internal system icons**. * - * TODO(b/265307726): Rename to `setInternalIcon`, or merge this appropriately with the - * {@link #setIcon(String, StatusBarIcon)} method. + * TODO(b/265307726): Re-name this to `setInternalIcon`. */ void setIcon(String slot, int resourceId, CharSequence contentDescription); - /** - * Adds or updates an icon for the given slot for an **externally-provided icon**. - * - * TODO(b/265307726): Rename to `setExternalIcon` or something similar. - */ - void setIcon(String slot, StatusBarIcon icon); - /** */ void setWifiIcon(String slot, WifiIconState state); @@ -152,15 +146,10 @@ public interface StatusBarIconController { */ void removeIcon(String slot, int tag); - /** */ - void removeAllIconsForSlot(String slot); - /** - * Removes all the icons for the given slot. - * - * Only use this for icons that have come from **an external process**. + * TODO(b/265307726): Re-name this to `removeAllIconsForInternalSlot`. */ - void removeAllIconsForExternalSlot(String slot); + void removeAllIconsForSlot(String slot); // TODO: See if we can rename this tunable name. String ICON_HIDE_LIST = "icon_blacklist"; @@ -618,13 +607,6 @@ public interface StatusBarIconController { mGroup.removeAllViews(); } - protected void onIconExternal(int viewIndex, int height) { - ImageView imageView = (ImageView) mGroup.getChildAt(viewIndex); - imageView.setScaleType(ImageView.ScaleType.FIT_CENTER); - imageView.setAdjustViewBounds(true); - setHeightAndCenter(imageView, height); - } - protected void onDensityOrFontScaleChanged() { for (int i = 0; i < mGroup.getChildCount(); i++) { View child = mGroup.getChildAt(i); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java index 0727c5af0dc7..3a184239ac43 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java @@ -32,7 +32,6 @@ import androidx.annotation.VisibleForTesting; import com.android.internal.statusbar.StatusBarIcon; import com.android.systemui.Dumpable; -import com.android.systemui.R; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.demomode.DemoMode; import com.android.systemui.demomode.DemoModeController; @@ -62,7 +61,7 @@ import javax.inject.Inject; */ @SysUISingleton public class StatusBarIconControllerImpl implements Tunable, - ConfigurationListener, Dumpable, CommandQueue.Callbacks, StatusBarIconController, DemoMode { + ConfigurationListener, Dumpable, StatusBarIconController, DemoMode { private static final String TAG = "StatusBarIconController"; // Use this suffix to prevent external icon slot names from unintentionally overriding our @@ -93,7 +92,7 @@ public class StatusBarIconControllerImpl implements Tunable, mStatusBarPipelineFlags = statusBarPipelineFlags; configurationController.addCallback(this); - commandQueue.addCallback(this); + commandQueue.addCallback(mCommandQueueCallbacks); tunerService.addTunable(this, ICON_HIDE_LIST); demoModeController.addCallback(this); dumpManager.registerDumpable(getClass().getSimpleName(), this); @@ -350,26 +349,35 @@ public class StatusBarIconControllerImpl implements Tunable, } } + private final CommandQueue.Callbacks mCommandQueueCallbacks = new CommandQueue.Callbacks() { + @Override + public void setIcon(String slot, StatusBarIcon icon) { + // Icons that come from CommandQueue are from external services. + setExternalIcon(slot, icon); + } + + @Override + public void removeIcon(String slot) { + removeAllIconsForExternalSlot(slot); + } + }; + @Override - public void setExternalIcon(String slot) { - String slotName = createExternalSlotName(slot); - int viewIndex = mStatusBarIconList.getViewIndex(slotName, 0); - int height = mContext.getResources().getDimensionPixelSize( - R.dimen.status_bar_icon_drawing_size); - mIconGroups.forEach(l -> l.onIconExternal(viewIndex, height)); + public void setIconFromTile(String slot, StatusBarIcon icon) { + setExternalIcon(slot, icon); } - // Override for *both* CommandQueue.Callbacks AND StatusBarIconController. - // TODO(b/265307726): Pull out the CommandQueue callbacks into a member variable to - // differentiate between those callback methods and StatusBarIconController methods. @Override - public void setIcon(String slot, StatusBarIcon icon) { - String slotName = createExternalSlotName(slot); + public void removeIconForTile(String slot) { + removeAllIconsForExternalSlot(slot); + } + + private void setExternalIcon(String slot, StatusBarIcon icon) { if (icon == null) { - removeAllIconsForSlot(slotName); + removeAllIconsForExternalSlot(slot); return; } - + String slotName = createExternalSlotName(slot); StatusBarIconHolder holder = StatusBarIconHolder.fromIcon(icon); setIcon(slotName, holder); } @@ -417,14 +425,6 @@ public class StatusBarIconControllerImpl implements Tunable, } } - // CommandQueue.Callbacks override - // TODO(b/265307726): Pull out the CommandQueue callbacks into a member variable to - // differentiate between those callback methods and StatusBarIconController methods. - @Override - public void removeIcon(String slot) { - removeAllIconsForExternalSlot(slot); - } - /** */ @Override public void removeIcon(String slot, int tag) { @@ -444,8 +444,7 @@ public class StatusBarIconControllerImpl implements Tunable, mIconGroups.forEach(l -> l.onRemoveIcon(viewIndex)); } - @Override - public void removeAllIconsForExternalSlot(String slotName) { + private void removeAllIconsForExternalSlot(String slotName) { removeAllIconsForSlot(createExternalSlotName(slotName)); } 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 2fe714533f5a..4a15000d54e0 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 @@ -453,9 +453,6 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue protected int adjustDisableFlags(int state) { boolean headsUpVisible = mStatusBarFragmentComponent.getHeadsUpAppearanceController().shouldBeVisible(); - if (headsUpVisible) { - state |= DISABLE_CLOCK; - } if (!mKeyguardStateController.isLaunchTransitionFadingAway() && !mKeyguardStateController.isKeyguardFadingAway() @@ -473,6 +470,13 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue state |= DISABLE_ONGOING_CALL_CHIP; } + if (headsUpVisible) { + // Disable everything on the left side of the status bar, since the app name for the + // heads up notification appears there instead. + state |= DISABLE_CLOCK; + state |= DISABLE_ONGOING_CALL_CHIP; + } + return state; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt index 4156fc152602..73bf188857c9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt @@ -179,6 +179,10 @@ constructor( fun logDefaultMobileIconGroup(group: SignalIcon.MobileIconGroup) { buffer.log(TAG, LogLevel.INFO, { str1 = group.name }, { "defaultMobileIconGroup: $str1" }) } + + fun logOnSubscriptionsChanged() { + buffer.log(TAG, LogLevel.INFO, {}, { "onSubscriptionsChanged" }) + } } private const val TAG = "MobileInputLog" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt index f866d65e2d2d..d0c6215a55d8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt @@ -105,10 +105,14 @@ class MobileConnectionRepositoryImpl( * The reason we need to do this is because TelephonyManager limits the number of registered * listeners per-process, so we don't want to create a new listener for every callback. * - * A note on the design for back pressure here: We use the [coalesce] operator here to change - * the backpressure strategy to store exactly the last callback event of _each type_ here, as - * opposed to the default strategy which is to drop the oldest event (regardless of type). This - * means that we should never miss any single event as long as the flow has been started. + * A note on the design for back pressure here: We don't control _which_ telephony callback + * comes in first, since we register every relevant bit of information as a batch. E.g., if a + * downstream starts collecting on a field which is backed by + * [TelephonyCallback.ServiceStateListener], it's not possible for us to guarantee that _that_ + * callback comes in -- the first callback could very well be + * [TelephonyCallback.DataActivityListener], which would promptly be dropped if we didn't keep + * it tracked. We use the [scan] operator here to track the most recent callback of _each type_ + * here. See [TelephonyCallbackState] to see how the callbacks are stored. */ private val callbackEvents: StateFlow<TelephonyCallbackState> = run { val initial = TelephonyCallbackState() diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt index b7da3f27c70a..991b7868439a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt @@ -129,6 +129,7 @@ constructor( val callback = object : SubscriptionManager.OnSubscriptionsChangedListener() { override fun onSubscriptionsChanged() { + logger.logOnSubscriptionsChanged() trySend(Unit) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java index 2ee52325ca4a..654ba04eba7a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java @@ -57,6 +57,8 @@ import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicReference; +import javax.annotation.concurrent.GuardedBy; + /** * Default implementation of a {@link BatteryController}. This controller monitors for battery * level change events that are broadcasted by the system. @@ -94,7 +96,10 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC private boolean mTestMode = false; @VisibleForTesting boolean mHasReceivedBattery = false; + @GuardedBy("mEstimateLock") private Estimate mEstimate; + private final Object mEstimateLock = new Object(); + private boolean mFetchingEstimate = false; // Use AtomicReference because we may request it from a different thread @@ -321,7 +326,7 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC @Nullable private String generateTimeRemainingString() { - synchronized (mFetchCallbacks) { + synchronized (mEstimateLock) { if (mEstimate == null) { return null; } @@ -340,7 +345,7 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC mFetchingEstimate = true; mBgHandler.post(() -> { // Only fetch the estimate if they are enabled - synchronized (mFetchCallbacks) { + synchronized (mEstimateLock) { mEstimate = null; if (mEstimates.isHybridNotificationEnabled()) { updateEstimate(); @@ -363,6 +368,7 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC } @WorkerThread + @GuardedBy("mEstimateLock") private void updateEstimate() { Assert.isNotMainThread(); // if the estimate has been cached we can just use that, otherwise get a new one and diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java index 805368cc1f0a..f1269f2b012a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java @@ -398,6 +398,7 @@ public class KeyguardStateControllerImpl implements KeyguardStateController, Dum pw.println(" mFaceAuthEnabled: " + mFaceAuthEnabled); pw.println(" isKeyguardFadingAway: " + isKeyguardFadingAway()); pw.println(" isKeyguardGoingAway: " + isKeyguardGoingAway()); + pw.println(" isLaunchTransitionFadingAway: " + isLaunchTransitionFadingAway()); } private class UpdateMonitorCallback extends KeyguardUpdateMonitorCallback { diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt index 125cc761d400..6e58f2296c86 100644 --- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt +++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt @@ -18,7 +18,8 @@ package com.android.systemui.temporarydisplay.chipbar import android.os.VibrationEffect import android.view.View -import androidx.annotation.AttrRes +import androidx.annotation.ColorRes +import com.android.systemui.R import com.android.systemui.common.shared.model.Text import com.android.systemui.common.shared.model.TintedIcon import com.android.systemui.temporarydisplay.TemporaryViewInfo @@ -48,7 +49,7 @@ data class ChipbarInfo( override val priority: ViewPriority, ) : TemporaryViewInfo() { companion object { - @AttrRes const val DEFAULT_ICON_TINT_ATTR = android.R.attr.textColorPrimary + @ColorRes val DEFAULT_ICON_TINT = R.color.chipbar_text_and_icon_color } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java index a5f90f8441b2..b15ac39dc57d 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java @@ -17,6 +17,7 @@ package com.android.keyguard; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.eq; @@ -365,6 +366,12 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase { assertEquals(View.VISIBLE, mFakeWeatherView.getVisibility()); } + @Test + public void testGetClockAnimations_nullClock_returnsNull() { + when(mClockEventController.getClock()).thenReturn(null); + assertNull(mController.getClockAnimations()); + } + private void verifyAttachment(VerificationMode times) { verify(mClockRegistry, times).registerClockChangeListener( any(ClockRegistry.ClockChangeListener.class)); diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java index 26d20c24cb84..d49275862757 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java @@ -196,6 +196,7 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { .thenReturn(mKeyguardMessageAreaController); when(mKeyguardPasswordView.getWindowInsetsController()).thenReturn(mWindowInsetsController); when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(SecurityMode.PIN); + when(mKeyguardStateController.canDismissLockScreen()).thenReturn(true); mKeyguardPasswordViewController = new KeyguardPasswordViewController( (KeyguardPasswordView) mKeyguardPasswordView, mKeyguardUpdateMonitor, SecurityMode.Password, mLockPatternUtils, null, @@ -552,6 +553,22 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { } @Test + public void testSecurityCallbackFinish() { + when(mKeyguardStateController.canDismissLockScreen()).thenReturn(true); + when(mKeyguardUpdateMonitor.isUserUnlocked(0)).thenReturn(true); + mKeyguardSecurityContainerController.finish(true, 0); + verify(mViewMediatorCallback).keyguardDone(anyBoolean(), anyInt()); + } + + @Test + public void testSecurityCallbackFinish_cannotDismissLockScreenAndNotStrongAuth() { + when(mFeatureFlags.isEnabled(Flags.PREVENT_BYPASS_KEYGUARD)).thenReturn(true); + when(mKeyguardStateController.canDismissLockScreen()).thenReturn(false); + mKeyguardSecurityContainerController.finish(false, 0); + verify(mViewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt()); + } + + @Test public void testOnStartingToHide() { mKeyguardSecurityContainerController.onStartingToHide(); verify(mInputViewController).onStartingToHide(); diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java index 4c92eddd66fb..b1051af4ad15 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -2245,6 +2245,26 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { } @Test + public void assistantVisible_requestActiveUnlock() { + // GIVEN active unlock requests from the assistant are allowed + when(mActiveUnlockConfig.shouldAllowActiveUnlockFromOrigin( + ActiveUnlockConfig.ActiveUnlockRequestOrigin.ASSISTANT)).thenReturn(true); + + // GIVEN should trigger active unlock + keyguardIsVisible(); + keyguardNotGoingAway(); + statusBarShadeIsNotLocked(); + when(mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser())).thenReturn(true); + + // WHEN the assistant is visible + mKeyguardUpdateMonitor.setAssistantVisible(true); + + // THEN request unlock with keyguard dismissal + verify(mTrustManager).reportUserRequestedUnlock(eq(KeyguardUpdateMonitor.getCurrentUser()), + eq(true)); + } + + @Test public void fingerprintFailure_requestActiveUnlock_dismissKeyguard() throws RemoteException { // GIVEN shouldTriggerActiveUnlock @@ -2433,6 +2453,57 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { } @Test + public void unfoldFromPostureChange_requestActiveUnlock_forceDismissKeyguard() + throws RemoteException { + // GIVEN shouldTriggerActiveUnlock + keyguardIsVisible(); + when(mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser())).thenReturn(true); + + // GIVEN active unlock triggers on wakeup + when(mActiveUnlockConfig.shouldAllowActiveUnlockFromOrigin( + ActiveUnlockConfig.ActiveUnlockRequestOrigin.WAKE)) + .thenReturn(true); + + // GIVEN an unfold should force dismiss the keyguard + when(mActiveUnlockConfig.shouldWakeupForceDismissKeyguard( + PowerManager.WAKE_REASON_UNFOLD_DEVICE)).thenReturn(true); + + // WHEN device posture changes to unfold + deviceInPostureStateOpened(); + mTestableLooper.processAllMessages(); + + // THEN request unlock with a keyguard dismissal + verify(mTrustManager).reportUserRequestedUnlock(eq(KeyguardUpdateMonitor.getCurrentUser()), + eq(true)); + } + + + @Test + public void unfoldFromPostureChange_requestActiveUnlock_noDismissKeyguard() + throws RemoteException { + // GIVEN shouldTriggerActiveUnlock on wake from UNFOLD_DEVICE + keyguardIsVisible(); + when(mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser())).thenReturn(true); + + // GIVEN active unlock triggers on wakeup + when(mActiveUnlockConfig.shouldAllowActiveUnlockFromOrigin( + ActiveUnlockConfig.ActiveUnlockRequestOrigin.WAKE)) + .thenReturn(true); + + // GIVEN an unfold should NOT force dismiss the keyguard + when(mActiveUnlockConfig.shouldWakeupForceDismissKeyguard( + PowerManager.WAKE_REASON_UNFOLD_DEVICE)).thenReturn(false); + + // WHEN device posture changes to unfold + deviceInPostureStateOpened(); + mTestableLooper.processAllMessages(); + + // THEN request unlock WITHOUT a keyguard dismissal + verify(mTrustManager).reportUserRequestedUnlock(eq(KeyguardUpdateMonitor.getCurrentUser()), + eq(false)); + } + + @Test public void detectFingerprint_onTemporaryLockoutReset_authenticateFingerprint() { ArgumentCaptor<FingerprintManager.LockoutResetCallback> fpLockoutResetCallbackCaptor = ArgumentCaptor.forClass(FingerprintManager.LockoutResetCallback.class); diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt index 21191db3e8b9..0ab675cd6873 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt @@ -62,6 +62,7 @@ import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsReposi import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor +import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.recents.OverviewProxyService import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.concurrency.FakeExecutor @@ -98,7 +99,6 @@ class SideFpsControllerTest : SysuiTestCase() { @JvmField @Rule var rule = MockitoJUnit.rule() - @Mock lateinit var keyguardStateController: KeyguardStateController @Mock lateinit var layoutInflater: LayoutInflater @Mock lateinit var fingerprintManager: FingerprintManager @Mock lateinit var windowManager: WindowManager @@ -138,7 +138,8 @@ class SideFpsControllerTest : SysuiTestCase() { keyguardBouncerRepository = FakeKeyguardBouncerRepository() alternateBouncerInteractor = AlternateBouncerInteractor( - keyguardStateController, + mock(StatusBarStateController::class.java), + mock(KeyguardStateController::class.java), keyguardBouncerRepository, FakeBiometricSettingsRepository(), FakeDeviceEntryFingerprintAuthRepository(), diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt index 786cb01621bb..cefa9b129262 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt @@ -35,6 +35,7 @@ import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInt import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants import com.android.systemui.log.table.TableLogBuffer +import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.StatusBarState import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.statusbar.policy.KeyguardStateController @@ -93,6 +94,7 @@ class UdfpsKeyguardViewControllerWithCoroutinesTest : UdfpsKeyguardViewControlle ) mAlternateBouncerInteractor = AlternateBouncerInteractor( + mock(StatusBarStateController::class.java), mock(KeyguardStateController::class.java), keyguardBouncerRepository, mock(BiometricSettingsRepository::class.java), diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt index 9d5b94be3885..6ca4dcccfca0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt @@ -201,6 +201,56 @@ class ControlsUiControllerImplTest : SysuiTestCase() { } @Test + fun testSingleAppHeaderIsNotClickable() { + mockLayoutInflater() + val packageName = "pkg" + `when`(authorizedPanelsRepository.getAuthorizedPanels()).thenReturn(setOf(packageName)) + val panel = SelectedItem.PanelItem("App name", ComponentName(packageName, "cls")) + val serviceInfo = setUpPanel(panel) + + underTest.show(parent, {}, context) + + val captor = argumentCaptor<ControlsListingController.ControlsListingCallback>() + + verify(controlsListingController).addCallback(capture(captor)) + + captor.value.onServicesUpdated(listOf(serviceInfo)) + FakeExecutor.exhaustExecutors(uiExecutor, bgExecutor) + + val header: View = parent.requireViewById(R.id.controls_header) + assertThat(header.isClickable).isFalse() + assertThat(header.hasOnClickListeners()).isFalse() + } + + @Test + fun testMultipleAppHeaderIsClickable() { + mockLayoutInflater() + val packageName1 = "pkg" + val panel1 = SelectedItem.PanelItem("App name 1", ComponentName(packageName1, "cls")) + val serviceInfo1 = setUpPanel(panel1) + + val packageName2 = "pkg" + val panel2 = SelectedItem.PanelItem("App name 2", ComponentName(packageName2, "cls")) + val serviceInfo2 = setUpPanel(panel2) + + `when`(authorizedPanelsRepository.getAuthorizedPanels()) + .thenReturn(setOf(packageName1, packageName2)) + + underTest.show(parent, {}, context) + + val captor = argumentCaptor<ControlsListingController.ControlsListingCallback>() + + verify(controlsListingController).addCallback(capture(captor)) + + captor.value.onServicesUpdated(listOf(serviceInfo1, serviceInfo2)) + FakeExecutor.exhaustExecutors(uiExecutor, bgExecutor) + + val header: View = parent.requireViewById(R.id.controls_header) + assertThat(header.isClickable).isTrue() + assertThat(header.hasOnClickListeners()).isTrue() + } + + @Test fun testPanelControllerStartActivityWithCorrectArguments() { mockLayoutInflater() val packageName = "pkg" diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/DreamConditionTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/DreamConditionTest.java index 19347c768524..58eb7d4f3ea7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/DreamConditionTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/DreamConditionTest.java @@ -21,9 +21,11 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.app.DreamManager; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -50,6 +52,9 @@ public class DreamConditionTest extends SysuiTestCase { @Mock Condition.Callback mCallback; + @Mock + DreamManager mDreamManager; + @Before public void setup() { MockitoAnnotations.initMocks(this); @@ -59,29 +64,39 @@ public class DreamConditionTest extends SysuiTestCase { * Ensure a dreaming state immediately triggers the condition. */ @Test - public void testInitialState() { - final Intent intent = new Intent(Intent.ACTION_DREAMING_STARTED); - when(mContext.registerReceiver(any(), any())).thenReturn(intent); - final DreamCondition condition = new DreamCondition(mContext); + public void testInitialDreamingState() { + when(mDreamManager.isDreaming()).thenReturn(true); + final DreamCondition condition = new DreamCondition(mContext, mDreamManager); condition.addCallback(mCallback); - condition.start(); verify(mCallback).onConditionChanged(eq(condition)); assertThat(condition.isConditionMet()).isTrue(); } /** + * Ensure a non-dreaming state does not trigger the condition. + */ + @Test + public void testInitialNonDreamingState() { + when(mDreamManager.isDreaming()).thenReturn(false); + final DreamCondition condition = new DreamCondition(mContext, mDreamManager); + condition.addCallback(mCallback); + + verify(mCallback, never()).onConditionChanged(eq(condition)); + assertThat(condition.isConditionMet()).isFalse(); + } + + /** * Ensure that changing dream state triggers condition. */ @Test public void testChange() { - final Intent intent = new Intent(Intent.ACTION_DREAMING_STARTED); final ArgumentCaptor<BroadcastReceiver> receiverCaptor = ArgumentCaptor.forClass(BroadcastReceiver.class); - when(mContext.registerReceiver(receiverCaptor.capture(), any())).thenReturn(intent); - final DreamCondition condition = new DreamCondition(mContext); + when(mDreamManager.isDreaming()).thenReturn(true); + final DreamCondition condition = new DreamCondition(mContext, mDreamManager); condition.addCallback(mCallback); - condition.start(); + verify(mContext).registerReceiver(receiverCaptor.capture(), any()); clearInvocations(mCallback); receiverCaptor.getValue().onReceive(mContext, new Intent(Intent.ACTION_DREAMING_STOPPED)); verify(mCallback).onConditionChanged(eq(condition)); diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/RestartDozeListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/RestartDozeListenerTest.kt index de0e5113571f..2db4596c80a7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/flags/RestartDozeListenerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/flags/RestartDozeListenerTest.kt @@ -20,6 +20,7 @@ import android.os.PowerManager import android.test.suitebuilder.annotation.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.settings.FakeSettings import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat @@ -41,13 +42,14 @@ class RestartDozeListenerTest : SysuiTestCase() { @Mock lateinit var statusBarStateController: StatusBarStateController @Mock lateinit var powerManager: PowerManager val clock = FakeSystemClock() + val executor = FakeExecutor(clock) lateinit var listener: StatusBarStateController.StateListener @Before fun setup() { MockitoAnnotations.initMocks(this) restartDozeListener = - RestartDozeListener(settings, statusBarStateController, powerManager, clock) + RestartDozeListener(settings, statusBarStateController, powerManager, clock, executor) val captor = ArgumentCaptor.forClass(StatusBarStateController.StateListener::class.java) restartDozeListener.init() @@ -58,12 +60,14 @@ class RestartDozeListenerTest : SysuiTestCase() { @Test fun testStoreDreamState_onDreamingStarted() { listener.onDreamingChanged(true) + executor.runAllReady() assertThat(settings.getBool(RestartDozeListener.RESTART_NAP_KEY)).isTrue() } @Test fun testStoreDreamState_onDreamingStopped() { listener.onDreamingChanged(false) + executor.runAllReady() assertThat(settings.getBool(RestartDozeListener.RESTART_NAP_KEY)).isFalse() } @@ -71,6 +75,8 @@ class RestartDozeListenerTest : SysuiTestCase() { fun testRestoreDreamState_dreamingShouldStart() { settings.putBool(RestartDozeListener.RESTART_NAP_KEY, true) restartDozeListener.maybeRestartSleep() + executor.advanceClockToLast() + executor.runAllReady() verify(powerManager).wakeUp(clock.uptimeMillis()) verify(powerManager).goToSleep(clock.uptimeMillis()) } @@ -79,6 +85,8 @@ class RestartDozeListenerTest : SysuiTestCase() { fun testRestoreDreamState_dreamingShouldNot() { settings.putBool(RestartDozeListener.RESTART_NAP_KEY, false) restartDozeListener.maybeRestartSleep() + executor.advanceClockToLast() + executor.runAllReady() verify(powerManager, never()).wakeUp(anyLong()) verify(powerManager, never()).goToSleep(anyLong()) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt index 1365132d6dac..86246f7af033 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt @@ -28,6 +28,7 @@ import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintA import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepositoryImpl import com.android.systemui.log.table.TableLogBuffer +import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.mockito.whenever import com.android.systemui.util.time.FakeSystemClock @@ -39,8 +40,10 @@ import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor import org.mockito.Mock import org.mockito.Mockito.mock +import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @OptIn(ExperimentalCoroutinesApi::class) @@ -52,6 +55,7 @@ class AlternateBouncerInteractorTest : SysuiTestCase() { private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository private lateinit var deviceEntryFingerprintAuthRepository: FakeDeviceEntryFingerprintAuthRepository + @Mock private lateinit var statusBarStateController: StatusBarStateController @Mock private lateinit var keyguardStateController: KeyguardStateController @Mock private lateinit var systemClock: SystemClock @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor @@ -73,6 +77,7 @@ class AlternateBouncerInteractorTest : SysuiTestCase() { featureFlags = FakeFeatureFlags().apply { this.set(Flags.MODERN_ALTERNATE_BOUNCER, true) } underTest = AlternateBouncerInteractor( + statusBarStateController, keyguardStateController, bouncerRepository, biometricSettingsRepository, @@ -130,6 +135,14 @@ class AlternateBouncerInteractorTest : SysuiTestCase() { } @Test + fun canShowAlternateBouncerForFingerprint_isDozing() { + givenCanShowAlternateBouncer() + whenever(statusBarStateController.isDozing).thenReturn(true) + + assertFalse(underTest.canShowAlternateBouncerForFingerprint()) + } + + @Test fun show_whenCanShow() { givenCanShowAlternateBouncer() @@ -169,6 +182,42 @@ class AlternateBouncerInteractorTest : SysuiTestCase() { assertFalse(bouncerRepository.alternateBouncerVisible.value) } + @Test + fun onUnlockedIsFalse_doesNotHide() { + // GIVEN alternate bouncer is showing + bouncerRepository.setAlternateVisible(true) + + val keyguardStateControllerCallbackCaptor = + ArgumentCaptor.forClass(KeyguardStateController.Callback::class.java) + verify(keyguardStateController).addCallback(keyguardStateControllerCallbackCaptor.capture()) + + // WHEN isUnlocked=false + givenCanShowAlternateBouncer() + whenever(keyguardStateController.isUnlocked).thenReturn(false) + keyguardStateControllerCallbackCaptor.value.onUnlockedChanged() + + // THEN the alternate bouncer is still visible + assertTrue(bouncerRepository.alternateBouncerVisible.value) + } + + @Test + fun onUnlockedChangedIsTrue_hide() { + // GIVEN alternate bouncer is showing + bouncerRepository.setAlternateVisible(true) + + val keyguardStateControllerCallbackCaptor = + ArgumentCaptor.forClass(KeyguardStateController.Callback::class.java) + verify(keyguardStateController).addCallback(keyguardStateControllerCallbackCaptor.capture()) + + // WHEN isUnlocked=true + givenCanShowAlternateBouncer() + whenever(keyguardStateController.isUnlocked).thenReturn(true) + keyguardStateControllerCallbackCaptor.value.onUnlockedChanged() + + // THEN the alternate bouncer is hidden + assertFalse(bouncerRepository.alternateBouncerVisible.value) + } + private fun givenCanShowAlternateBouncer() { bouncerRepository.setAlternateBouncerUIAvailable(true) biometricSettingsRepository.setFingerprintEnrolled(true) @@ -176,6 +225,7 @@ class AlternateBouncerInteractorTest : SysuiTestCase() { biometricSettingsRepository.setFingerprintEnabledByDevicePolicy(true) deviceEntryFingerprintAuthRepository.setLockedOut(false) whenever(keyguardStateController.isUnlocked).thenReturn(false) + whenever(statusBarStateController.isDozing).thenReturn(false) } private fun givenCannotShowAlternateBouncer() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt index 85e8d072bd99..6c3d6f533ace 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt @@ -25,6 +25,7 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription import com.android.systemui.common.shared.model.Icon +import com.android.systemui.temporarydisplay.chipbar.ChipbarInfo.Companion.DEFAULT_ICON_TINT import com.android.systemui.util.mockito.any import com.google.common.truth.Truth.assertThat import org.junit.Before @@ -140,6 +141,7 @@ class MediaTttUtilsTest : SysuiTestCase() { context.getString(R.string.media_transfer_receiver_content_description_unknown_app) ) assertThat(iconInfo.icon).isEqualTo(MediaTttIcon.Resource(R.drawable.ic_cast)) + assertThat(iconInfo.tint).isEqualTo(DEFAULT_ICON_TINT) } @Test @@ -232,40 +234,40 @@ class MediaTttUtilsTest : SysuiTestCase() { fun iconInfo_toTintedIcon_loaded() { val contentDescription = ContentDescription.Loaded("test") val drawable = context.getDrawable(R.drawable.ic_cake)!! - val tintAttr = android.R.attr.textColorTertiary + val tint = R.color.GM2_blue_500 val iconInfo = IconInfo( contentDescription, MediaTttIcon.Loaded(drawable), - tintAttr, + tint, isAppIcon = false, ) val tinted = iconInfo.toTintedIcon() assertThat(tinted.icon).isEqualTo(Icon.Loaded(drawable, contentDescription)) - assertThat(tinted.tintAttr).isEqualTo(tintAttr) + assertThat(tinted.tint).isEqualTo(tint) } @Test fun iconInfo_toTintedIcon_resource() { val contentDescription = ContentDescription.Loaded("test") val drawableRes = R.drawable.ic_cake - val tintAttr = android.R.attr.textColorTertiary + val tint = R.color.GM2_blue_500 val iconInfo = IconInfo( contentDescription, MediaTttIcon.Resource(drawableRes), - tintAttr, + tint, isAppIcon = false ) val tinted = iconInfo.toTintedIcon() assertThat(tinted.icon).isEqualTo(Icon.Resource(drawableRes, contentDescription)) - assertThat(tinted.tintAttr).isEqualTo(tintAttr) + assertThat(tinted.tint).isEqualTo(tint) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewTest.java index 3108ed9e7b98..fe1205161e8d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewTest.java @@ -21,6 +21,7 @@ import static com.google.common.truth.Truth.assertThat; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.LayoutInflater; +import android.view.View; import androidx.test.filters.SmallTest; @@ -49,6 +50,13 @@ public class KeyguardStatusBarViewTest extends SysuiTestCase { } @Test + public void userSwitcherChip_defaultVisibilityIsGone() { + assertThat(mKeyguardStatusBarView.findViewById( + R.id.user_switcher_container).getVisibility()).isEqualTo( + View.GONE); + } + + @Test public void setTopClipping_clippingUpdated() { int topClipping = 40; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImplTest.kt index 3bc288a2f823..08e89fbef486 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImplTest.kt @@ -20,13 +20,17 @@ import android.os.UserHandle import androidx.test.filters.SmallTest import com.android.internal.statusbar.StatusBarIcon import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.phone.StatusBarIconController.TAG_PRIMARY import com.android.systemui.statusbar.phone.StatusBarIconControllerImpl.EXTERNAL_SLOT_SUFFIX +import com.android.systemui.util.mockito.kotlinArgumentCaptor import com.android.systemui.util.mockito.mock import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test +import org.mockito.Mock import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations @SmallTest class StatusBarIconControllerImplTest : SysuiTestCase() { @@ -34,15 +38,19 @@ class StatusBarIconControllerImplTest : SysuiTestCase() { private lateinit var underTest: StatusBarIconControllerImpl private lateinit var iconList: StatusBarIconList + private lateinit var commandQueueCallbacks: CommandQueue.Callbacks private val iconGroup: StatusBarIconController.IconManager = mock() + @Mock private lateinit var commandQueue: CommandQueue + @Before fun setUp() { + MockitoAnnotations.initMocks(this) iconList = StatusBarIconList(arrayOf()) underTest = StatusBarIconControllerImpl( context, - mock(), + commandQueue, mock(), mock(), mock(), @@ -51,11 +59,42 @@ class StatusBarIconControllerImplTest : SysuiTestCase() { mock(), ) underTest.addIconGroup(iconGroup) + val commandQueueCallbacksCaptor = kotlinArgumentCaptor<CommandQueue.Callbacks>() + verify(commandQueue).addCallback(commandQueueCallbacksCaptor.capture()) + commandQueueCallbacks = commandQueueCallbacksCaptor.value + } + + /** Regression test for b/255428281. */ + @Test + fun internalAndExternalIconWithSameName_externalFromTile_bothDisplayed() { + val slotName = "mute" + + // Internal + underTest.setIcon(slotName, /* resourceId= */ 10, "contentDescription") + + // External + val externalIcon = + StatusBarIcon( + "external.package", + UserHandle.ALL, + /* iconId= */ 2, + /* iconLevel= */ 0, + /* number= */ 0, + "contentDescription", + ) + underTest.setIconFromTile(slotName, externalIcon) + + assertThat(iconList.slots).hasSize(2) + // Whichever was added last comes first + assertThat(iconList.slots[0].name).isEqualTo(slotName + EXTERNAL_SLOT_SUFFIX) + assertThat(iconList.slots[1].name).isEqualTo(slotName) + assertThat(iconList.slots[0].hasIconsInSlot()).isTrue() + assertThat(iconList.slots[1].hasIconsInSlot()).isTrue() } /** Regression test for b/255428281. */ @Test - fun internalAndExternalIconWithSameName_bothDisplayed() { + fun internalAndExternalIconWithSameName_externalFromCommandQueue_bothDisplayed() { val slotName = "mute" // Internal @@ -71,7 +110,7 @@ class StatusBarIconControllerImplTest : SysuiTestCase() { /* number= */ 0, "contentDescription", ) - underTest.setIcon(slotName, externalIcon) + commandQueueCallbacks.setIcon(slotName, externalIcon) assertThat(iconList.slots).hasSize(2) // Whichever was added last comes first @@ -83,17 +122,17 @@ class StatusBarIconControllerImplTest : SysuiTestCase() { /** Regression test for b/255428281. */ @Test - fun internalAndExternalIconWithSameName_externalRemoved_viaRemoveIcon_internalStays() { + fun internalAndExternalIconWithSameName_externalRemoved_fromCommandQueue_internalStays() { val slotName = "mute" // Internal underTest.setIcon(slotName, /* resourceId= */ 10, "contentDescription") // External - underTest.setIcon(slotName, createExternalIcon()) + commandQueueCallbacks.setIcon(slotName, createExternalIcon()) - // WHEN the external icon is removed via #removeIcon - underTest.removeIcon(slotName) + // WHEN the external icon is removed via CommandQueue.Callbacks#removeIcon + commandQueueCallbacks.removeIcon(slotName) // THEN the external icon is removed but the internal icon remains // Note: [StatusBarIconList] never removes slots from its list, it just sets the holder for @@ -109,17 +148,17 @@ class StatusBarIconControllerImplTest : SysuiTestCase() { /** Regression test for b/255428281. */ @Test - fun internalAndExternalIconWithSameName_externalRemoved_viaRemoveAll_internalStays() { + fun internalAndExternalIconWithSameName_externalRemoved_fromTileRemove_internalStays() { val slotName = "mute" // Internal underTest.setIcon(slotName, /* resourceId= */ 10, "contentDescription") // External - underTest.setIcon(slotName, createExternalIcon()) + underTest.setIconFromTile(slotName, createExternalIcon()) - // WHEN the external icon is removed via #removeAllIconsForExternalSlot - underTest.removeAllIconsForExternalSlot(slotName) + // WHEN the external icon is removed via #removeIconForTile + underTest.removeIconForTile(slotName) // THEN the external icon is removed but the internal icon remains assertThat(iconList.slots).hasSize(2) @@ -133,17 +172,17 @@ class StatusBarIconControllerImplTest : SysuiTestCase() { /** Regression test for b/255428281. */ @Test - fun internalAndExternalIconWithSameName_externalRemoved_viaSetNull_internalStays() { + fun internalAndExternalIconWithSameName_externalRemoved_fromTileSetNull_internalStays() { val slotName = "mute" // Internal underTest.setIcon(slotName, /* resourceId= */ 10, "contentDescription") // External - underTest.setIcon(slotName, createExternalIcon()) + underTest.setIconFromTile(slotName, createExternalIcon()) - // WHEN the external icon is removed via a #setIcon(null) - underTest.setIcon(slotName, /* icon= */ null) + // WHEN the external icon is removed via a #setIconFromTile(null) + underTest.setIconFromTile(slotName, /* icon= */ null) // THEN the external icon is removed but the internal icon remains assertThat(iconList.slots).hasSize(2) @@ -164,12 +203,12 @@ class StatusBarIconControllerImplTest : SysuiTestCase() { underTest.setIcon(slotName, /* resourceId= */ 10, "contentDescription") // External - underTest.setIcon(slotName, createExternalIcon()) + underTest.setIconFromTile(slotName, createExternalIcon()) // WHEN the internal icon is removed via #removeIcon underTest.removeIcon(slotName, /* tag= */ 0) - // THEN the external icon is removed but the internal icon remains + // THEN the internal icon is removed but the external icon remains assertThat(iconList.slots).hasSize(2) assertThat(iconList.slots[0].name).isEqualTo(slotName + EXTERNAL_SLOT_SUFFIX) assertThat(iconList.slots[1].name).isEqualTo(slotName) @@ -188,12 +227,12 @@ class StatusBarIconControllerImplTest : SysuiTestCase() { underTest.setIcon(slotName, /* resourceId= */ 10, "contentDescription") // External - underTest.setIcon(slotName, createExternalIcon()) + underTest.setIconFromTile(slotName, createExternalIcon()) // WHEN the internal icon is removed via #removeAllIconsForSlot underTest.removeAllIconsForSlot(slotName) - // THEN the external icon is removed but the internal icon remains + // THEN the internal icon is removed but the external icon remains assertThat(iconList.slots).hasSize(2) assertThat(iconList.slots[0].name).isEqualTo(slotName + EXTERNAL_SLOT_SUFFIX) assertThat(iconList.slots[1].name).isEqualTo(slotName) @@ -221,7 +260,7 @@ class StatusBarIconControllerImplTest : SysuiTestCase() { /* number= */ 0, "externalDescription", ) - underTest.setIcon(slotName, startingExternalIcon) + underTest.setIconFromTile(slotName, startingExternalIcon) // WHEN the internal icon is updated underTest.setIcon(slotName, /* resourceId= */ 11, "newContentDescription") @@ -243,7 +282,54 @@ class StatusBarIconControllerImplTest : SysuiTestCase() { /** Regression test for b/255428281. */ @Test - fun internalAndExternalIconWithSameName_externalUpdatedIndependently() { + fun internalAndExternalIconWithSameName_fromTile_externalUpdatedIndependently() { + val slotName = "mute" + + // Internal + underTest.setIcon(slotName, /* resourceId= */ 10, "contentDescription") + + // External + val startingExternalIcon = + StatusBarIcon( + "external.package", + UserHandle.ALL, + /* iconId= */ 20, + /* iconLevel= */ 0, + /* number= */ 0, + "externalDescription", + ) + underTest.setIconFromTile(slotName, startingExternalIcon) + + // WHEN the external icon is updated + val newExternalIcon = + StatusBarIcon( + "external.package", + UserHandle.ALL, + /* iconId= */ 21, + /* iconLevel= */ 0, + /* number= */ 0, + "newExternalDescription", + ) + underTest.setIconFromTile(slotName, newExternalIcon) + + // THEN only the external slot gets the updates + val externalSlot = iconList.slots[0] + val externalHolder = externalSlot.getHolderForTag(TAG_PRIMARY)!! + assertThat(externalSlot.name).isEqualTo(slotName + EXTERNAL_SLOT_SUFFIX) + assertThat(externalHolder.icon!!.contentDescription).isEqualTo("newExternalDescription") + assertThat(externalHolder.icon!!.icon.resId).isEqualTo(21) + + // And the internal slot has its own values + val internalSlot = iconList.slots[1] + val internalHolder = internalSlot.getHolderForTag(TAG_PRIMARY)!! + assertThat(internalSlot.name).isEqualTo(slotName) + assertThat(internalHolder.icon!!.contentDescription).isEqualTo("contentDescription") + assertThat(internalHolder.icon!!.icon.resId).isEqualTo(10) + } + + /** Regression test for b/255428281. */ + @Test + fun internalAndExternalIconWithSameName_fromCommandQueue_externalUpdatedIndependently() { val slotName = "mute" // Internal @@ -259,7 +345,7 @@ class StatusBarIconControllerImplTest : SysuiTestCase() { /* number= */ 0, "externalDescription", ) - underTest.setIcon(slotName, startingExternalIcon) + commandQueueCallbacks.setIcon(slotName, startingExternalIcon) // WHEN the external icon is updated val newExternalIcon = @@ -271,7 +357,7 @@ class StatusBarIconControllerImplTest : SysuiTestCase() { /* number= */ 0, "newExternalDescription", ) - underTest.setIcon(slotName, newExternalIcon) + commandQueueCallbacks.setIcon(slotName, newExternalIcon) // THEN only the external slot gets the updates val externalSlot = iconList.slots[0] @@ -289,8 +375,16 @@ class StatusBarIconControllerImplTest : SysuiTestCase() { } @Test - fun externalSlot_alreadyEndsWithSuffix_suffixNotAddedTwice() { - underTest.setIcon("myslot$EXTERNAL_SLOT_SUFFIX", createExternalIcon()) + fun externalSlot_fromTile_alreadyEndsWithSuffix_suffixNotAddedTwice() { + underTest.setIconFromTile("myslot$EXTERNAL_SLOT_SUFFIX", createExternalIcon()) + + assertThat(iconList.slots).hasSize(1) + assertThat(iconList.slots[0].name).isEqualTo("myslot$EXTERNAL_SLOT_SUFFIX") + } + + @Test + fun externalSlot_fromCommandQueue_alreadyEndsWithSuffix_suffixNotAddedTwice() { + commandQueueCallbacks.setIcon("myslot$EXTERNAL_SLOT_SUFFIX", createExternalIcon()) assertThat(iconList.slots).hasSize(1) assertThat(iconList.slots[0].name).isEqualTo("myslot$EXTERNAL_SLOT_SUFFIX") 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 1e5782b91be8..49c50d670fe4 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 @@ -294,6 +294,13 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test + public void userChip_defaultVisibilityIsGone() { + CollapsedStatusBarFragment fragment = resumeAndGetFragment(); + + assertEquals(View.GONE, getUserChipView().getVisibility()); + } + + @Test public void disable_noOngoingCall_chipHidden() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -333,6 +340,19 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test + public void disable_hasOngoingCallButAlsoHun_chipHidden() { + CollapsedStatusBarFragment fragment = resumeAndGetFragment(); + + when(mOngoingCallController.hasOngoingCall()).thenReturn(true); + when(mHeadsUpAppearanceController.shouldBeVisible()).thenReturn(true); + + fragment.disable(DEFAULT_DISPLAY, 0, 0, false); + + assertEquals(View.GONE, + mFragment.getView().findViewById(R.id.ongoing_call_chip).getVisibility()); + } + + @Test public void disable_ongoingCallEnded_chipHidden() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -558,6 +578,10 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { return (CollapsedStatusBarFragment) mFragment; } + private View getUserChipView() { + return mFragment.getView().findViewById(R.id.user_switcher_container); + } + private View getClockView() { return mFragment.getView().findViewById(R.id.clock); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt index 542b688b162d..934e1c64c6da 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt @@ -25,7 +25,6 @@ import android.telephony.ServiceState.STATE_OUT_OF_SERVICE import android.telephony.TelephonyCallback import android.telephony.TelephonyCallback.DataActivityListener import android.telephony.TelephonyCallback.ServiceStateListener -import android.telephony.TelephonyDisplayInfo import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_CA import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE import android.telephony.TelephonyManager @@ -68,6 +67,7 @@ import com.android.systemui.statusbar.pipeline.mobile.data.model.toNetworkNameMo import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileTelephonyHelpers.signalStrength +import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileTelephonyHelpers.telephonyDisplayInfo import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel @@ -392,13 +392,17 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { val job = underTest.resolvedNetworkType.onEach { latest = it }.launchIn(this) val callback = getTelephonyCallbackForType<TelephonyCallback.DisplayInfoListener>() - val type = NETWORK_TYPE_UNKNOWN - val expected = UnknownNetworkType - val ti = mock<TelephonyDisplayInfo>().also { whenever(it.networkType).thenReturn(type) } + val ti = + telephonyDisplayInfo( + networkType = NETWORK_TYPE_UNKNOWN, + overrideNetworkType = NETWORK_TYPE_UNKNOWN, + ) + callback.onDisplayInfoChanged(ti) + val expected = UnknownNetworkType assertThat(latest).isEqualTo(expected) - assertThat(latest!!.lookupKey).isEqualTo(MobileMappings.toIconKey(type)) + assertThat(latest!!.lookupKey).isEqualTo(MobileMappings.toIconKey(NETWORK_TYPE_UNKNOWN)) job.cancel() } @@ -412,14 +416,10 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { val callback = getTelephonyCallbackForType<TelephonyCallback.DisplayInfoListener>() val overrideType = OVERRIDE_NETWORK_TYPE_NONE val type = NETWORK_TYPE_LTE - val expected = DefaultNetworkType(mobileMappings.toIconKey(type)) - val ti = - mock<TelephonyDisplayInfo>().also { - whenever(it.overrideNetworkType).thenReturn(overrideType) - whenever(it.networkType).thenReturn(type) - } + val ti = telephonyDisplayInfo(networkType = type, overrideNetworkType = overrideType) callback.onDisplayInfoChanged(ti) + val expected = DefaultNetworkType(mobileMappings.toIconKey(type)) assertThat(latest).isEqualTo(expected) job.cancel() @@ -433,14 +433,10 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { val callback = getTelephonyCallbackForType<TelephonyCallback.DisplayInfoListener>() val type = OVERRIDE_NETWORK_TYPE_LTE_CA - val expected = OverrideNetworkType(mobileMappings.toIconKeyOverride(type)) - val ti = - mock<TelephonyDisplayInfo>().also { - whenever(it.networkType).thenReturn(type) - whenever(it.overrideNetworkType).thenReturn(type) - } + val ti = telephonyDisplayInfo(networkType = type, overrideNetworkType = type) callback.onDisplayInfoChanged(ti) + val expected = OverrideNetworkType(mobileMappings.toIconKeyOverride(type)) assertThat(latest).isEqualTo(expected) job.cancel() @@ -455,14 +451,10 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { val callback = getTelephonyCallbackForType<TelephonyCallback.DisplayInfoListener>() val unknown = NETWORK_TYPE_UNKNOWN val type = OVERRIDE_NETWORK_TYPE_LTE_CA - val expected = OverrideNetworkType(mobileMappings.toIconKeyOverride(type)) - val ti = - mock<TelephonyDisplayInfo>().also { - whenever(it.networkType).thenReturn(unknown) - whenever(it.overrideNetworkType).thenReturn(type) - } + val ti = telephonyDisplayInfo(unknown, type) callback.onDisplayInfoChanged(ti) + val expected = OverrideNetworkType(mobileMappings.toIconKeyOverride(type)) assertThat(latest).isEqualTo(expected) job.cancel() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt index bbf04ed28fd7..9da9ff72d380 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt @@ -64,10 +64,14 @@ import org.mockito.MockitoAnnotations * * Kind of like an interaction test case build just for [TelephonyCallback] * - * The list of telephony callbacks we use is: [TelephonyCallback.CarrierNetworkListener] - * [TelephonyCallback.DataActivityListener] [TelephonyCallback.DataConnectionStateListener] - * [TelephonyCallback.DataEnabledListener] [TelephonyCallback.DisplayInfoListener] - * [TelephonyCallback.ServiceStateListener] [TelephonyCallback.SignalStrengthsListener] + * The list of telephony callbacks we use is: + * - [TelephonyCallback.CarrierNetworkListener] + * - [TelephonyCallback.DataActivityListener] + * - [TelephonyCallback.DataConnectionStateListener] + * - [TelephonyCallback.DataEnabledListener] + * - [TelephonyCallback.DisplayInfoListener] + * - [TelephonyCallback.ServiceStateListener] + * - [TelephonyCallback.SignalStrengthsListener] * * Because each of these callbacks comes in on the same callbackFlow, collecting on a field backed * by only a single callback can immediately create backpressure on the other fields related to a @@ -201,7 +205,6 @@ class MobileConnectionTelephonySmokeTests : SysuiTestCase() { 200 /* unused */ ) - // Send a bunch of events that we don't care about, to overrun the replay buffer flipActivity(100, activityCallback) val connectionJob = underTest.dataConnectionState.onEach { latest = it }.launchIn(this) @@ -225,7 +228,6 @@ class MobileConnectionTelephonySmokeTests : SysuiTestCase() { enabledCallback.onDataEnabledChanged(true, 1 /* unused */) - // Send a bunch of events that we don't care about, to overrun the replay buffer flipActivity(100, activityCallback) val job = underTest.dataEnabled.onEach { latest = it }.launchIn(this) @@ -252,7 +254,6 @@ class MobileConnectionTelephonySmokeTests : SysuiTestCase() { val ti = mock<TelephonyDisplayInfo>().also { whenever(it.networkType).thenReturn(type) } displayInfoCallback.onDisplayInfoChanged(ti) - // Send a bunch of events that we don't care about, to overrun the replay buffer flipActivity(100, activityCallback) val job = underTest.resolvedNetworkType.onEach { latest = it }.launchIn(this) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileTelephonyHelpers.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileTelephonyHelpers.kt index d07b96f6609e..cf815c27a0bf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileTelephonyHelpers.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileTelephonyHelpers.kt @@ -19,6 +19,7 @@ package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod import android.telephony.CellSignalStrengthCdma import android.telephony.SignalStrength import android.telephony.TelephonyCallback +import android.telephony.TelephonyDisplayInfo import android.telephony.TelephonyManager import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.argumentCaptor @@ -48,6 +49,12 @@ object MobileTelephonyHelpers { return signalStrength } + fun telephonyDisplayInfo(networkType: Int, overrideNetworkType: Int) = + mock<TelephonyDisplayInfo>().also { + whenever(it.networkType).thenReturn(networkType) + whenever(it.overrideNetworkType).thenReturn(overrideNetworkType) + } + inline fun <reified T> getTelephonyCallbackForType(mockTelephonyManager: TelephonyManager): T { val cbs = getTelephonyCallbacks(mockTelephonyManager).filterIsInstance<T>() assertThat(cbs.size).isEqualTo(1) diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt index 586bdc6c8215..6e24941ac937 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt @@ -685,7 +685,7 @@ class ChipbarCoordinatorTest : SysuiTestCase() { allowSwipeToDismiss: Boolean = false, ): ChipbarInfo { return ChipbarInfo( - TintedIcon(startIcon, tintAttr = null), + TintedIcon(startIcon, tint = null), text, endItem, vibrationEffect, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java index 926c6c56a862..c664c99cf2a7 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java @@ -47,17 +47,17 @@ public class FakeStatusBarIconController extends BaseLeakChecker<IconManager> } @Override - public void setExternalIcon(String slot) { + public void setIconFromTile(String slot, StatusBarIcon icon) { } @Override - public void setIcon(String slot, int resourceId, CharSequence contentDescription) { + public void removeIconForTile(String slot) { } @Override - public void setIcon(String slot, StatusBarIcon icon) { + public void setIcon(String slot, int resourceId, CharSequence contentDescription) { } @@ -98,10 +98,6 @@ public class FakeStatusBarIconController extends BaseLeakChecker<IconManager> } @Override - public void removeAllIconsForExternalSlot(String slot) { - } - - @Override public void setIconAccessibilityLiveRegion(String slot, int mode) { } diff --git a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java index 3d24588c9763..aafcd9dcbff9 100644 --- a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java +++ b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java @@ -19,7 +19,6 @@ import android.app.Service; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; -import android.graphics.Camera; import android.graphics.GraphicBuffer; import android.graphics.Rect; import android.hardware.HardwareBuffer; @@ -1855,6 +1854,7 @@ public class CameraExtensionsProxyService extends Service { ret.outputId.id = output.getId(); ret.physicalCameraId = output.getPhysicalCameraId(); ret.surfaceGroupId = output.getSurfaceGroupId(); + ret.isMultiResolutionOutput = false; if (output instanceof SurfaceOutputConfigImpl) { SurfaceOutputConfigImpl surfaceConfig = (SurfaceOutputConfigImpl) output; ret.type = CameraOutputConfig.TYPE_SURFACE; @@ -1874,6 +1874,7 @@ public class CameraExtensionsProxyService extends Service { ret.type = CameraOutputConfig.TYPE_MULTIRES_IMAGEREADER; ret.imageFormat = multiResReaderConfig.getImageFormat(); ret.capacity = multiResReaderConfig.getMaxImages(); + ret.isMultiResolutionOutput = true; } else { throw new IllegalStateException("Unknown output config type!"); } diff --git a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java index 702526a4beab..dc1ef7eee0b6 100644 --- a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java +++ b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java @@ -891,6 +891,7 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync { break; case EnergyConsumerType.MOBILE_RADIO: buckets[MeasuredEnergyStats.POWER_BUCKET_MOBILE_RADIO] = true; + buckets[MeasuredEnergyStats.POWER_BUCKET_PHONE] = true; break; case EnergyConsumerType.DISPLAY: buckets[MeasuredEnergyStats.POWER_BUCKET_SCREEN_ON] = true; diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index ff18167ee86f..158492056fb5 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -1017,9 +1017,14 @@ public class AudioService extends IAudioService.Stub mSfxHelper = new SoundEffectsHelper(mContext, playerBase -> ignorePlayerLogs(playerBase)); - final boolean headTrackingDefault = mContext.getResources().getBoolean( + final boolean binauralEnabledDefault = SystemProperties.getBoolean( + "ro.audio.spatializer_binaural_enabled_default", true); + final boolean transauralEnabledDefault = SystemProperties.getBoolean( + "ro.audio.spatializer_transaural_enabled_default", true); + final boolean headTrackingEnabledDefault = mContext.getResources().getBoolean( com.android.internal.R.bool.config_spatial_audio_head_tracking_enabled_default); - mSpatializerHelper = new SpatializerHelper(this, mAudioSystem, headTrackingDefault); + mSpatializerHelper = new SpatializerHelper(this, mAudioSystem, + binauralEnabledDefault, transauralEnabledDefault, headTrackingEnabledDefault); mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); mHasVibrator = mVibrator == null ? false : mVibrator.hasVibrator(); diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java index 2b566668a7c7..a9e7d4b0bac8 100644 --- a/services/core/java/com/android/server/audio/SpatializerHelper.java +++ b/services/core/java/com/android/server/audio/SpatializerHelper.java @@ -171,13 +171,17 @@ public class SpatializerHelper { // initialization @SuppressWarnings("StaticAssignmentInConstructor") SpatializerHelper(@NonNull AudioService mother, @NonNull AudioSystemAdapter asa, - boolean headTrackingEnabledByDefault) { + boolean binauralEnabledDefault, + boolean transauralEnabledDefault, + boolean headTrackingEnabledDefault) { mAudioService = mother; mASA = asa; // "StaticAssignmentInConstructor" warning is suppressed as the SpatializerHelper being // constructed here is the factory for SADeviceState, thus SADeviceState and its // private static field sHeadTrackingEnabledDefault should never be accessed directly. - SADeviceState.sHeadTrackingEnabledDefault = headTrackingEnabledByDefault; + SADeviceState.sBinauralEnabledDefault = binauralEnabledDefault; + SADeviceState.sTransauralEnabledDefault = transauralEnabledDefault; + SADeviceState.sHeadTrackingEnabledDefault = headTrackingEnabledDefault; } synchronized void initForTest(boolean hasBinaural, boolean hasTransaural) { @@ -1539,10 +1543,12 @@ public class SpatializerHelper { } /*package*/ static final class SADeviceState { + private static boolean sBinauralEnabledDefault = true; + private static boolean sTransauralEnabledDefault = true; private static boolean sHeadTrackingEnabledDefault = false; final @AudioDeviceInfo.AudioDeviceType int mDeviceType; final @NonNull String mDeviceAddress; - boolean mEnabled = true; // by default, SA is enabled on any device + boolean mEnabled; boolean mHasHeadTracker = false; boolean mHeadTrackerEnabled; static final String SETTING_FIELD_SEPARATOR = ","; @@ -1558,6 +1564,12 @@ public class SpatializerHelper { SADeviceState(@AudioDeviceInfo.AudioDeviceType int deviceType, @Nullable String address) { mDeviceType = deviceType; mDeviceAddress = isWireless(deviceType) ? Objects.requireNonNull(address) : ""; + final int spatMode = SPAT_MODE_FOR_DEVICE_TYPE.get(deviceType, Integer.MIN_VALUE); + mEnabled = spatMode == SpatializationMode.SPATIALIZER_BINAURAL + ? sBinauralEnabledDefault + : spatMode == SpatializationMode.SPATIALIZER_TRANSAURAL + ? sTransauralEnabledDefault + : false; mHeadTrackerEnabled = sHeadTrackingEnabledDefault; } diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java index 1bbdc20c81e5..8c83be3ea8dd 100644 --- a/services/core/java/com/android/server/display/DisplayModeDirector.java +++ b/services/core/java/com/android/server/display/DisplayModeDirector.java @@ -1230,13 +1230,12 @@ public class DisplayModeDirector { } public void onDeviceConfigDefaultPeakRefreshRateChanged(Float defaultPeakRefreshRate) { - if (defaultPeakRefreshRate == null) { - defaultPeakRefreshRate = (float) mContext.getResources().getInteger( - R.integer.config_defaultPeakRefreshRate); - } - - if (mDefaultPeakRefreshRate != defaultPeakRefreshRate) { - synchronized (mLock) { + synchronized (mLock) { + if (defaultPeakRefreshRate == null) { + setDefaultPeakRefreshRate(mDefaultDisplayDeviceConfig, + /* attemptLoadingFromDeviceConfig= */ false); + updateRefreshRateSettingLocked(); + } else if (mDefaultPeakRefreshRate != defaultPeakRefreshRate) { mDefaultPeakRefreshRate = defaultPeakRefreshRate; updateRefreshRateSettingLocked(); } @@ -1869,11 +1868,20 @@ public class DisplayModeDirector { mLowDisplayBrightnessThresholds = displayThresholds; mLowAmbientBrightnessThresholds = ambientThresholds; } else { - // Invalid or empty. Use device default. - mLowDisplayBrightnessThresholds = mContext.getResources().getIntArray( - R.array.config_brightnessThresholdsOfPeakRefreshRate); - mLowAmbientBrightnessThresholds = mContext.getResources().getIntArray( - R.array.config_ambientThresholdsOfPeakRefreshRate); + DisplayDeviceConfig displayDeviceConfig; + synchronized (mLock) { + displayDeviceConfig = mDefaultDisplayDeviceConfig; + } + mLowDisplayBrightnessThresholds = loadBrightnessThresholds( + () -> mDeviceConfigDisplaySettings.getLowDisplayBrightnessThresholds(), + () -> displayDeviceConfig.getLowDisplayBrightnessThresholds(), + R.array.config_brightnessThresholdsOfPeakRefreshRate, + displayDeviceConfig, /* attemptLoadingFromDeviceConfig= */ false); + mLowAmbientBrightnessThresholds = loadBrightnessThresholds( + () -> mDeviceConfigDisplaySettings.getLowAmbientBrightnessThresholds(), + () -> displayDeviceConfig.getLowAmbientBrightnessThresholds(), + R.array.config_ambientThresholdsOfPeakRefreshRate, + displayDeviceConfig, /* attemptLoadingFromDeviceConfig= */ false); } restartObserver(); } @@ -1883,24 +1891,41 @@ public class DisplayModeDirector { * DeviceConfig properties. */ public void onDeviceConfigRefreshRateInLowZoneChanged(int refreshRate) { - if (refreshRate != mRefreshRateInLowZone) { + if (refreshRate == -1) { + // Given there is no value available in DeviceConfig, lets not attempt loading it + // from there. + synchronized (mLock) { + loadRefreshRateInLowZone(mDefaultDisplayDeviceConfig, + /* attemptLoadingFromDeviceConfig= */ false); + } + restartObserver(); + } else if (refreshRate != mRefreshRateInLowZone) { mRefreshRateInLowZone = refreshRate; restartObserver(); } } - public void onDeviceConfigHighBrightnessThresholdsChanged(int[] displayThresholds, + private void onDeviceConfigHighBrightnessThresholdsChanged(int[] displayThresholds, int[] ambientThresholds) { if (displayThresholds != null && ambientThresholds != null && displayThresholds.length == ambientThresholds.length) { mHighDisplayBrightnessThresholds = displayThresholds; mHighAmbientBrightnessThresholds = ambientThresholds; } else { - // Invalid or empty. Use device default. - mHighDisplayBrightnessThresholds = mContext.getResources().getIntArray( - R.array.config_highDisplayBrightnessThresholdsOfFixedRefreshRate); - mHighAmbientBrightnessThresholds = mContext.getResources().getIntArray( - R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate); + DisplayDeviceConfig displayDeviceConfig; + synchronized (mLock) { + displayDeviceConfig = mDefaultDisplayDeviceConfig; + } + mHighDisplayBrightnessThresholds = loadBrightnessThresholds( + () -> mDeviceConfigDisplaySettings.getHighDisplayBrightnessThresholds(), + () -> displayDeviceConfig.getHighDisplayBrightnessThresholds(), + R.array.config_highDisplayBrightnessThresholdsOfFixedRefreshRate, + displayDeviceConfig, /* attemptLoadingFromDeviceConfig= */ false); + mHighAmbientBrightnessThresholds = loadBrightnessThresholds( + () -> mDeviceConfigDisplaySettings.getHighAmbientBrightnessThresholds(), + () -> displayDeviceConfig.getHighAmbientBrightnessThresholds(), + R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate, + displayDeviceConfig, /* attemptLoadingFromDeviceConfig= */ false); } restartObserver(); } @@ -1910,7 +1935,15 @@ public class DisplayModeDirector { * DeviceConfig properties. */ public void onDeviceConfigRefreshRateInHighZoneChanged(int refreshRate) { - if (refreshRate != mRefreshRateInHighZone) { + if (refreshRate == -1) { + // Given there is no value available in DeviceConfig, lets not attempt loading it + // from there. + synchronized (mLock) { + loadRefreshRateInHighZone(mDefaultDisplayDeviceConfig, + /* attemptLoadingFromDeviceConfig= */ false); + } + restartObserver(); + } else if (refreshRate != mRefreshRateInHighZone) { mRefreshRateInHighZone = refreshRate; restartObserver(); } @@ -2870,10 +2903,8 @@ public class DisplayModeDirector { new Pair<>(lowDisplayBrightnessThresholds, lowAmbientBrightnessThresholds)) .sendToTarget(); - if (refreshRateInLowZone != -1) { - mHandler.obtainMessage(MSG_REFRESH_RATE_IN_LOW_ZONE_CHANGED, refreshRateInLowZone, - 0).sendToTarget(); - } + mHandler.obtainMessage(MSG_REFRESH_RATE_IN_LOW_ZONE_CHANGED, refreshRateInLowZone, + 0).sendToTarget(); int[] highDisplayBrightnessThresholds = getHighDisplayBrightnessThresholds(); int[] highAmbientBrightnessThresholds = getHighAmbientBrightnessThresholds(); @@ -2883,10 +2914,8 @@ public class DisplayModeDirector { new Pair<>(highDisplayBrightnessThresholds, highAmbientBrightnessThresholds)) .sendToTarget(); - if (refreshRateInHighZone != -1) { - mHandler.obtainMessage(MSG_REFRESH_RATE_IN_HIGH_ZONE_CHANGED, refreshRateInHighZone, - 0).sendToTarget(); - } + mHandler.obtainMessage(MSG_REFRESH_RATE_IN_HIGH_ZONE_CHANGED, refreshRateInHighZone, + 0).sendToTarget(); synchronized (mLock) { final int refreshRateInHbmSunlight = diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index eedb4b0a3bde..be4fe09d593c 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -1508,7 +1508,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { private void interceptScreenshotChord(int source, long pressDelay) { mHandler.removeMessages(MSG_SCREENSHOT_CHORD); - mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SCREENSHOT_CHORD, source), + // arg2 is unused, but necessary to insure we call the correct method signature + // since the screenshot source is read from message.arg1 + mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SCREENSHOT_CHORD, source, 0), pressDelay); } diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index b946e7e06044..7ada2135212c 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -8307,6 +8307,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A * requested in the config or via an ADB command. For more context see {@link * LetterboxUiController#getHorizontalPositionMultiplier(Configuration)} and * {@link LetterboxUiController#getVerticalPositionMultiplier(Configuration)} + * <p> + * Note that this is the final step that can change the resolved bounds. After this method + * is called, the position of the bounds will be moved to app space as sandboxing if the + * activity has a size compat scale. */ private void updateResolvedBoundsPosition(Configuration newParentConfiguration) { final Configuration resolvedConfig = getResolvedOverrideConfiguration(); @@ -8368,6 +8372,18 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // Since bounds has changed, the configuration needs to be computed accordingly. getTaskFragment().computeConfigResourceOverrides(resolvedConfig, newParentConfiguration); + + // The position of configuration bounds were calculated in screen space because that is + // easier to resolve the relative position in parent container. However, if the activity is + // scaled, the position should follow the scale because the configuration will be sent to + // the client which is expected to be in a scaled environment. + if (mSizeCompatScale != 1f) { + final int screenPosX = resolvedBounds.left; + final int screenPosY = resolvedBounds.top; + final int dx = (int) (screenPosX / mSizeCompatScale + 0.5f) - screenPosX; + final int dy = (int) (screenPosY / mSizeCompatScale + 0.5f) - screenPosY; + offsetBounds(resolvedConfig, dx, dy); + } } void recomputeConfiguration() { diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java index 3404279d2c59..c0ed08d2607d 100644 --- a/services/core/java/com/android/server/wm/DisplayRotation.java +++ b/services/core/java/com/android/server/wm/DisplayRotation.java @@ -88,6 +88,10 @@ import java.util.Set; public class DisplayRotation { private static final String TAG = TAG_WITH_CLASS_NAME ? "DisplayRotation" : TAG_WM; + // Delay in milliseconds when updating config due to folding events. This prevents + // config changes and unexpected jumps while folding the device to closed state. + private static final int FOLDING_RECOMPUTE_CONFIG_DELAY_MS = 800; + private static class RotationAnimationPair { @AnimRes int mEnter; @@ -1618,6 +1622,7 @@ public class DisplayRotation { private boolean mInHalfFoldTransition = false; private final boolean mIsDisplayAlwaysSeparatingHinge; private final Set<Integer> mTabletopRotations; + private final Runnable mActivityBoundsUpdateCallback; FoldController() { mTabletopRotations = new ArraySet<>(); @@ -1652,6 +1657,26 @@ public class DisplayRotation { } mIsDisplayAlwaysSeparatingHinge = mContext.getResources().getBoolean( R.bool.config_isDisplayHingeAlwaysSeparating); + + mActivityBoundsUpdateCallback = new Runnable() { + public void run() { + if (mDeviceState == DeviceStateController.DeviceState.OPEN + || mDeviceState == DeviceStateController.DeviceState.HALF_FOLDED) { + synchronized (mLock) { + final Task topFullscreenTask = + mDisplayContent.getTask( + t -> t.getWindowingMode() == WINDOWING_MODE_FULLSCREEN); + if (topFullscreenTask != null) { + final ActivityRecord top = + topFullscreenTask.topRunningActivity(); + if (top != null) { + top.recomputeConfiguration(); + } + } + } + } + } + }; } boolean isDeviceInPosture(DeviceStateController.DeviceState state, boolean isTabletop) { @@ -1723,14 +1748,9 @@ public class DisplayRotation { false /* forceRelayout */); } // Alert the activity of possible new bounds. - final Task topFullscreenTask = - mDisplayContent.getTask(t -> t.getWindowingMode() == WINDOWING_MODE_FULLSCREEN); - if (topFullscreenTask != null) { - final ActivityRecord top = topFullscreenTask.topRunningActivity(); - if (top != null) { - top.recomputeConfiguration(); - } - } + UiThread.getHandler().removeCallbacks(mActivityBoundsUpdateCallback); + UiThread.getHandler().postDelayed(mActivityBoundsUpdateCallback, + FOLDING_RECOMPUTE_CONFIG_DELAY_MS); } } diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java index 0db76bfe347f..6bffc4c95e64 100644 --- a/services/core/java/com/android/server/wm/LetterboxUiController.java +++ b/services/core/java/com/android/server/wm/LetterboxUiController.java @@ -849,7 +849,7 @@ final class LetterboxUiController { int dividerInsets = getResources().getDimensionPixelSize(R.dimen.docked_stack_divider_insets); int dividerSize = dividerWindowWidth - dividerInsets * 2; - final Rect bounds = new Rect(displayContent.getBounds()); + final Rect bounds = new Rect(displayContent.getWindowConfiguration().getAppBounds()); if (bounds.width() >= bounds.height()) { bounds.inset(/* dx */ dividerSize / 2, /* dy */ 0); bounds.right = bounds.centerX(); @@ -1504,7 +1504,7 @@ final class LetterboxUiController { } private void inheritConfiguration(ActivityRecord firstOpaque) { - // To avoid wrong behaviour, we're not forcing a specific aspet ratio to activities + // To avoid wrong behaviour, we're not forcing a specific aspect ratio to activities // which are not already providing one (e.g. permission dialogs) and presumably also // not resizable. if (mActivityRecord.getMinAspectRatio() != UNDEFINED_ASPECT_RATIO) { diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 3461bc27b40c..18d6dea927c1 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -3448,6 +3448,8 @@ class Task extends TaskFragment { && info.pictureInPictureParams.isLaunchIntoPip() && top.getLastParentBeforePip() != null) ? top.getLastParentBeforePip().mTaskId : INVALID_TASK_ID; + info.lastParentTaskIdBeforePip = top != null && top.getLastParentBeforePip() != null + ? top.getLastParentBeforePip().mTaskId : INVALID_TASK_ID; info.shouldDockBigOverlays = top != null && top.shouldDockBigOverlays; info.mTopActivityLocusId = top != null ? top.getLocusId() : null; diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 41e0fd715889..2d849b3a6b95 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -2049,16 +2049,19 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP /** * Like isOnScreen(), but we don't return true if the window is part - * of a transition that has not yet been started. + * of a transition but has not yet started animating. */ boolean isReadyForDisplay() { - if (mToken.waitingToShow && getDisplayContent().mAppTransition.isTransitionSet()) { + if (!mHasSurface || mDestroying || !isVisibleByPolicy()) { + return false; + } + if (mToken.waitingToShow && getDisplayContent().mAppTransition.isTransitionSet() + && !isAnimating(TRANSITION | PARENTS, ANIMATION_TYPE_APP_TRANSITION)) { return false; } final boolean parentAndClientVisible = !isParentWindowHidden() && mViewVisibility == View.VISIBLE && mToken.isVisible(); - return mHasSurface && isVisibleByPolicy() && !mDestroying - && (parentAndClientVisible || isAnimating(TRANSITION | PARENTS)); + return parentAndClientVisible || isAnimating(TRANSITION | PARENTS, ANIMATION_TYPE_ALL); } boolean isFullyTransparent() { diff --git a/services/tests/servicestests/src/com/android/server/audio/SpatializerHelperTest.java b/services/tests/servicestests/src/com/android/server/audio/SpatializerHelperTest.java index 428eaff9e5bc..dea31d764522 100644 --- a/services/tests/servicestests/src/com/android/server/audio/SpatializerHelperTest.java +++ b/services/tests/servicestests/src/com/android/server/audio/SpatializerHelperTest.java @@ -56,7 +56,9 @@ public class SpatializerHelperTest { mSpyAudioSystem = spy(new NoOpAudioSystemAdapter()); mSpatHelper = new SpatializerHelper(mMockAudioService, mSpyAudioSystem, - false /*headTrackingEnabledByDefault*/); + true /*binauralEnabledDefault*/, + true /*transauralEnabledDefault*/, + false /*headTrackingEnabledDefault*/); } /** diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java index ff37564f46a4..58f3db959afe 100644 --- a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java +++ b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java @@ -1905,7 +1905,7 @@ public class DisplayModeDirectorTest { // We don't expect any interaction with DeviceConfig when the director is initialized // because we explicitly avoid doing this as this can lead to a latency spike in the // startup of DisplayManagerService - // Verify all the loaded values are from DisplayDeviceConfig + // Verify all the loaded values are from config.xml assertEquals(director.getSettingsObserver().getDefaultRefreshRate(), 45, 0.0); assertEquals(director.getSettingsObserver().getDefaultPeakRefreshRate(), 75, 0.0); @@ -1937,6 +1937,7 @@ public class DisplayModeDirectorTest { when(displayDeviceConfig.getDefaultRefreshRateInHbmSunlight()).thenReturn(75); director.defaultDisplayDeviceUpdated(displayDeviceConfig); + // Verify the new values are from the freshly loaded DisplayDeviceConfig. assertEquals(director.getSettingsObserver().getDefaultRefreshRate(), 60, 0.0); assertEquals(director.getSettingsObserver().getDefaultPeakRefreshRate(), 65, 0.0); @@ -1966,6 +1967,7 @@ public class DisplayModeDirectorTest { config.setRefreshRateInHbmSunlight(80); director.defaultDisplayDeviceUpdated(displayDeviceConfig); + // Verify the values are loaded from the DeviceConfig. assertEquals(director.getSettingsObserver().getDefaultRefreshRate(), 60, 0.0); assertEquals(director.getSettingsObserver().getDefaultPeakRefreshRate(), 60, 0.0); @@ -1981,6 +1983,35 @@ public class DisplayModeDirectorTest { new int[]{20}); assertEquals(director.getHbmObserver().getRefreshRateInHbmHdr(), 70); assertEquals(director.getHbmObserver().getRefreshRateInHbmSunlight(), 80); + + // Reset the DeviceConfig + config.setDefaultPeakRefreshRate(null); + config.setRefreshRateInHighZone(null); + config.setRefreshRateInLowZone(null); + config.setLowAmbientBrightnessThresholds(new int[]{}); + config.setLowDisplayBrightnessThresholds(new int[]{}); + config.setHighDisplayBrightnessThresholds(new int[]{}); + config.setHighAmbientBrightnessThresholds(new int[]{}); + config.setRefreshRateInHbmHdr(null); + config.setRefreshRateInHbmSunlight(null); + waitForIdleSync(); + + // verify the new values now fallback to DisplayDeviceConfig + assertEquals(director.getSettingsObserver().getDefaultRefreshRate(), 60, 0.0); + assertEquals(director.getSettingsObserver().getDefaultPeakRefreshRate(), 65, + 0.0); + assertEquals(director.getBrightnessObserver().getRefreshRateInHighZone(), 55); + assertEquals(director.getBrightnessObserver().getRefreshRateInLowZone(), 50); + assertArrayEquals(director.getBrightnessObserver().getHighDisplayBrightnessThreshold(), + new int[]{210}); + assertArrayEquals(director.getBrightnessObserver().getHighAmbientBrightnessThreshold(), + new int[]{2100}); + assertArrayEquals(director.getBrightnessObserver().getLowDisplayBrightnessThreshold(), + new int[]{25}); + assertArrayEquals(director.getBrightnessObserver().getLowAmbientBrightnessThreshold(), + new int[]{30}); + assertEquals(director.getHbmObserver().getRefreshRateInHbmHdr(), 65); + assertEquals(director.getHbmObserver().getRefreshRateInHbmSunlight(), 75); } @Test @@ -2140,18 +2171,18 @@ public class DisplayModeDirectorTest { super.addOnPropertiesChangedListener(namespace, executor, listener); } - void setRefreshRateInLowZone(int fps) { + void setRefreshRateInLowZone(Integer fps) { putPropertyAndNotify( DeviceConfig.NAMESPACE_DISPLAY_MANAGER, KEY_REFRESH_RATE_IN_LOW_ZONE, String.valueOf(fps)); } - void setRefreshRateInHbmSunlight(int fps) { + void setRefreshRateInHbmSunlight(Integer fps) { putPropertyAndNotify(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, KEY_REFRESH_RATE_IN_HBM_SUNLIGHT, String.valueOf(fps)); } - void setRefreshRateInHbmHdr(int fps) { + void setRefreshRateInHbmHdr(Integer fps) { putPropertyAndNotify(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, KEY_REFRESH_RATE_IN_HBM_HDR, String.valueOf(fps)); } @@ -2187,13 +2218,13 @@ public class DisplayModeDirectorTest { thresholds); } - void setRefreshRateInHighZone(int fps) { + void setRefreshRateInHighZone(Integer fps) { putPropertyAndNotify( DeviceConfig.NAMESPACE_DISPLAY_MANAGER, KEY_REFRESH_RATE_IN_HIGH_ZONE, String.valueOf(fps)); } - void setDefaultPeakRefreshRate(int fps) { + void setDefaultPeakRefreshRate(Integer fps) { putPropertyAndNotify( DeviceConfig.NAMESPACE_DISPLAY_MANAGER, KEY_PEAK_REFRESH_RATE_DEFAULT, String.valueOf(fps)); diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java index 3ab9ea906128..75d65b3a3f8f 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -105,7 +105,6 @@ import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges; import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges; import org.junit.After; -import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -574,11 +573,11 @@ public class SizeCompatTests extends WindowTestsBase { // The scale is 2000/2500=0.8. The horizontal centered offset is (1000-(1000*0.8))/2=100. final float scale = (float) display.mBaseDisplayHeight / currentBounds.height(); final int offsetX = (int) (display.mBaseDisplayWidth - (origBounds.width() * scale)) / 2; - assertEquals(offsetX, currentBounds.left); + final int screenX = mActivity.getBounds().left; + assertEquals(offsetX, screenX); - // The position of configuration bounds should be the same as compat bounds. - assertEquals(mActivity.getBounds().left, currentBounds.left); - assertEquals(mActivity.getBounds().top, currentBounds.top); + // The position of configuration bounds should be in app space. + assertEquals(screenX, (int) (currentBounds.left * scale + 0.5f)); // Activity is sandboxed to the offset size compat bounds. assertActivityMaxBoundsSandboxed(); @@ -608,7 +607,7 @@ public class SizeCompatTests extends WindowTestsBase { // The size should still be in portrait [100, 0 - 1100, 2500] = 1000x2500. assertEquals(origBounds.width(), currentBounds.width()); assertEquals(origBounds.height(), currentBounds.height()); - assertEquals(offsetX, currentBounds.left); + assertEquals(offsetX, mActivity.getBounds().left); assertScaled(); // Activity is sandboxed due to size compat mode. assertActivityMaxBoundsSandboxed(); @@ -771,9 +770,11 @@ public class SizeCompatTests extends WindowTestsBase { assertEquals(origAppBounds.height(), appBounds.height()); // The activity is 1000x1400 and the display is 2500x1000. assertScaled(); - // The position in configuration should be global coordinates. - assertEquals(mActivity.getBounds().left, currentBounds.left); - assertEquals(mActivity.getBounds().top, currentBounds.top); + final float scale = mActivity.getCompatScale(); + // The position in configuration should be in app coordinates. + final Rect screenBounds = mActivity.getBounds(); + assertEquals(screenBounds.left, (int) (currentBounds.left * scale + 0.5f)); + assertEquals(screenBounds.top, (int) (currentBounds.top * scale + 0.5f)); // Activity max bounds are sandboxed due to size compat mode. assertActivityMaxBoundsSandboxed(); @@ -1989,7 +1990,7 @@ public class SizeCompatTests extends WindowTestsBase { float expectedAspectRatio = 1f * displayWidth / getExpectedSplitSize(displayHeight); final Rect afterBounds = activity.getBounds(); final float afterAspectRatio = (float) (afterBounds.height()) / afterBounds.width(); - Assert.assertEquals(expectedAspectRatio, afterAspectRatio, 0.001f); + assertEquals(expectedAspectRatio, afterAspectRatio, 0.001f); } @Test @@ -2014,7 +2015,7 @@ public class SizeCompatTests extends WindowTestsBase { float expectedAspectRatio = 1f * displayHeight / getExpectedSplitSize(displayWidth); final Rect afterBounds = activity.getBounds(); final float afterAspectRatio = (float) (afterBounds.height()) / afterBounds.width(); - Assert.assertEquals(expectedAspectRatio, afterAspectRatio, 0.001f); + assertEquals(expectedAspectRatio, afterAspectRatio, 0.001f); } @Test @@ -2040,7 +2041,7 @@ public class SizeCompatTests extends WindowTestsBase { float expectedAspectRatio = 1f * displayWidth / getExpectedSplitSize(displayHeight); final Rect afterBounds = activity.getBounds(); final float afterAspectRatio = (float) (afterBounds.width()) / afterBounds.height(); - Assert.assertEquals(expectedAspectRatio, afterAspectRatio, 0.001f); + assertEquals(expectedAspectRatio, afterAspectRatio, 0.001f); } @Test @@ -2066,7 +2067,89 @@ public class SizeCompatTests extends WindowTestsBase { float expectedAspectRatio = 1f * displayHeight / getExpectedSplitSize(displayWidth); final Rect afterBounds = activity.getBounds(); final float afterAspectRatio = (float) (afterBounds.width()) / afterBounds.height(); - Assert.assertEquals(expectedAspectRatio, afterAspectRatio, 0.001f); + assertEquals(expectedAspectRatio, afterAspectRatio, 0.001f); + } + + @Test + @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO, + ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_TO_ALIGN_WITH_SPLIT_SCREEN}) + public void testOverrideSplitScreenAspectRatio_splitScreenActivityInPortrait_notLetterboxed() { + mAtm.mDevEnableNonResizableMultiWindow = true; + final int screenWidth = 1800; + final int screenHeight = 1000; + setUpDisplaySizeWithApp(screenWidth, screenHeight); + final ActivityRecord activity = new ActivityBuilder(mAtm) + .setTask(mTask) + .setComponent(ComponentName.createRelative(mContext, + SizeCompatTests.class.getName())) + .setUid(android.os.Process.myUid()) + .build(); + + activity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + // Simulate real display with top insets. + final int topInset = 30; + activity.mDisplayContent.getWindowConfiguration() + .setAppBounds(0, topInset, screenWidth, screenHeight); + + final TestSplitOrganizer organizer = + new TestSplitOrganizer(mAtm, activity.getDisplayContent()); + // Move activity to split screen which takes half of the screen. + mTask.reparent(organizer.mPrimary, POSITION_TOP, /* moveParents= */ false , "test"); + organizer.mPrimary.setBounds(0, 0, getExpectedSplitSize(screenWidth), screenHeight); + assertEquals(WINDOWING_MODE_MULTI_WINDOW, mTask.getWindowingMode()); + assertEquals(WINDOWING_MODE_MULTI_WINDOW, activity.getWindowingMode()); + + // Unresizable portrait-only activity. + prepareUnresizable(activity, 3f, SCREEN_ORIENTATION_PORTRAIT); + + // Activity should have the aspect ratio of a split screen activity and occupy exactly one + // half of the screen, so there is no letterbox + float expectedAspectRatio = 1f * screenHeight / getExpectedSplitSize(screenWidth); + final Rect afterBounds = activity.getBounds(); + final float afterAspectRatio = (float) (afterBounds.height()) / afterBounds.width(); + assertEquals(expectedAspectRatio, afterAspectRatio, 0.001f); + assertFalse(activity.areBoundsLetterboxed()); + } + + @Test + @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO, + ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_TO_ALIGN_WITH_SPLIT_SCREEN}) + public void testOverrideSplitScreenAspectRatio_splitScreenActivityInLandscape_notLetterboxed() { + mAtm.mDevEnableNonResizableMultiWindow = true; + final int screenWidth = 1000; + final int screenHeight = 1800; + setUpDisplaySizeWithApp(screenWidth, screenHeight); + final ActivityRecord activity = new ActivityBuilder(mAtm) + .setTask(mTask) + .setComponent(ComponentName.createRelative(mContext, + SizeCompatTests.class.getName())) + .setUid(android.os.Process.myUid()) + .build(); + + activity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + // Simulate real display with top insets. + final int leftInset = 30; + activity.mDisplayContent.getWindowConfiguration() + .setAppBounds(leftInset, 0, screenWidth, screenHeight); + + final TestSplitOrganizer organizer = + new TestSplitOrganizer(mAtm, activity.getDisplayContent()); + // Move activity to split screen which takes half of the screen. + mTask.reparent(organizer.mPrimary, POSITION_TOP, /* moveParents= */ false , "test"); + organizer.mPrimary.setBounds(0, 0, screenWidth, getExpectedSplitSize(screenHeight)); + assertEquals(WINDOWING_MODE_MULTI_WINDOW, mTask.getWindowingMode()); + assertEquals(WINDOWING_MODE_MULTI_WINDOW, activity.getWindowingMode()); + + // Unresizable landscape-only activity. + prepareUnresizable(activity, 3f, SCREEN_ORIENTATION_LANDSCAPE); + + // Activity should have the aspect ratio of a split screen activity and occupy exactly one + // half of the screen, so there is no letterbox + float expectedAspectRatio = 1f * screenWidth / getExpectedSplitSize(screenHeight); + final Rect afterBounds = activity.getBounds(); + final float afterAspectRatio = (float) (afterBounds.width()) / afterBounds.height(); + assertEquals(expectedAspectRatio, afterAspectRatio, 0.001f); + assertFalse(activity.areBoundsLetterboxed()); } @Test |