diff options
113 files changed, 2321 insertions, 671 deletions
diff --git a/Android.bp b/Android.bp index 78ffd6fb5e69..c1fb41f845e3 100644 --- a/Android.bp +++ b/Android.bp @@ -140,6 +140,9 @@ filegroup { // For the generated R.java and Manifest.java ":framework-res{.aapt.srcjar}", + // Java/AIDL sources to be moved out to CrashRecovery module + ":framework-crashrecovery-sources", + // etc. ":framework-javastream-protos", ":statslog-framework-java-gen", // FrameworkStatsLog.java diff --git a/core/api/system-current.txt b/core/api/system-current.txt index c282e4b6f3eb..01ca6d924605 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -3867,7 +3867,7 @@ package android.content.pm { public class PackageInstaller { method @NonNull public android.content.pm.PackageInstaller.InstallInfo readInstallInfo(@NonNull java.io.File, int) throws android.content.pm.PackageInstaller.PackageParsingException; - method @NonNull public android.content.pm.PackageInstaller.InstallInfo readInstallInfo(@NonNull android.os.ParcelFileDescriptor, @Nullable String, int) throws android.content.pm.PackageInstaller.PackageParsingException; + method @FlaggedApi("android.content.pm.read_install_info") @NonNull public android.content.pm.PackageInstaller.InstallInfo readInstallInfo(@NonNull android.os.ParcelFileDescriptor, @Nullable String, int) throws android.content.pm.PackageInstaller.PackageParsingException; method @FlaggedApi("android.content.pm.archiving") @RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES, android.Manifest.permission.REQUEST_INSTALL_PACKAGES}) public void reportUnarchivalStatus(int, int, long, @Nullable android.app.PendingIntent) throws android.content.pm.PackageManager.NameNotFoundException; method @FlaggedApi("android.content.pm.archiving") @RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES, android.Manifest.permission.REQUEST_DELETE_PACKAGES}) public void requestArchive(@NonNull String, @NonNull android.content.IntentSender) throws android.content.pm.PackageManager.NameNotFoundException; method @FlaggedApi("android.content.pm.archiving") @RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES, android.Manifest.permission.REQUEST_INSTALL_PACKAGES}) public void requestUnarchive(@NonNull String, @NonNull android.content.IntentSender) throws java.io.IOException, android.content.pm.PackageManager.NameNotFoundException; @@ -3902,7 +3902,7 @@ package android.content.pm { public static class PackageInstaller.InstallInfo { method public long calculateInstalledSize(@NonNull android.content.pm.PackageInstaller.SessionParams) throws java.io.IOException; - method public long calculateInstalledSize(@NonNull android.content.pm.PackageInstaller.SessionParams, @NonNull android.os.ParcelFileDescriptor) throws java.io.IOException; + method @FlaggedApi("android.content.pm.read_install_info") public long calculateInstalledSize(@NonNull android.content.pm.PackageInstaller.SessionParams, @NonNull android.os.ParcelFileDescriptor) throws java.io.IOException; method public int getInstallLocation(); method @NonNull public String getPackageName(); } diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 6c10f495e7bf..00432dcf152c 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -24,9 +24,7 @@ import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.inMultiWindowMode; import static android.os.Process.myUid; - import static com.android.sdksandbox.flags.Flags.sandboxActivitySdkBasedContext; - import static java.lang.Character.MIN_VALUE; import android.annotation.AnimRes; @@ -7604,17 +7602,15 @@ public class Activity extends ContextThemeWrapper * @param taskDescription The TaskDescription properties that describe the task with this activity */ public void setTaskDescription(ActivityManager.TaskDescription taskDescription) { - if (taskDescription == null || mTaskDescription.equals(taskDescription)) { - return; - } - - mTaskDescription.copyFromPreserveHiddenFields(taskDescription); - // Scale the icon down to something reasonable if it is provided - if (taskDescription.getIconFilename() == null && taskDescription.getIcon() != null) { - final int size = ActivityManager.getLauncherLargeIconSizeInner(this); - final Bitmap icon = Bitmap.createScaledBitmap(taskDescription.getIcon(), size, size, - true); - mTaskDescription.setIcon(Icon.createWithBitmap(icon)); + if (mTaskDescription != taskDescription) { + mTaskDescription.copyFromPreserveHiddenFields(taskDescription); + // Scale the icon down to something reasonable if it is provided + if (taskDescription.getIconFilename() == null && taskDescription.getIcon() != null) { + final int size = ActivityManager.getLauncherLargeIconSizeInner(this); + final Bitmap icon = Bitmap.createScaledBitmap(taskDescription.getIcon(), size, size, + true); + mTaskDescription.setIcon(Icon.createWithBitmap(icon)); + } } ActivityClient.getInstance().setTaskDescription(mToken, mTaskDescription); } diff --git a/core/java/android/app/LocaleConfig.java b/core/java/android/app/LocaleConfig.java index 369a78144fd3..b2be27f70ccc 100644 --- a/core/java/android/app/LocaleConfig.java +++ b/core/java/android/app/LocaleConfig.java @@ -201,6 +201,7 @@ public class LocaleConfig implements Parcelable { String defaultLocale = null; if (android.content.res.Flags.defaultLocale()) { + // Read the defaultLocale attribute of the LocaleConfig element TypedArray att = res.obtainAttributes( attrs, com.android.internal.R.styleable.LocaleConfig); defaultLocale = att.getString( diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index 4f0bfc7437c7..6df1f600c3ef 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -2337,6 +2337,7 @@ public class PackageInstaller { */ @SystemApi @NonNull + @FlaggedApi(Flags.FLAG_READ_INSTALL_INFO) public InstallInfo readInstallInfo(@NonNull ParcelFileDescriptor pfd, @Nullable String debugPathName, int flags) throws PackageParsingException { final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing(); @@ -2516,6 +2517,7 @@ public class PackageInstaller { * and all relevant native code. * @throws IOException when size of native binaries cannot be calculated. */ + @FlaggedApi(Flags.FLAG_READ_INSTALL_INFO) public long calculateInstalledSize(@NonNull SessionParams params, @NonNull ParcelFileDescriptor pfd) throws IOException { return InstallLocationUtils.calculateInstalledSize(mPkg, params.abiOverride, diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig index bb5fdb714761..a565f6825e7a 100644 --- a/core/java/android/content/pm/flags.aconfig +++ b/core/java/android/content/pm/flags.aconfig @@ -87,3 +87,10 @@ flag { description: "Feature flag to detect the invisible labels in Launcher Apps" bug: "299586370" } + +flag { + name: "read_install_info" + namespace: "package_manager_service" + description: "Feature flag to read install related information from an APK." + bug: "275658500" +} diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java index 42203d4b0d71..c716cd2e4a9c 100644 --- a/core/java/android/service/voice/VoiceInteractionService.java +++ b/core/java/android/service/voice/VoiceInteractionService.java @@ -387,7 +387,6 @@ public class VoiceInteractionService extends Service { VoiceInteractionService::onShutdownInternal, VoiceInteractionService.this)); }; - private void onShutdownInternal() { onShutdown(); // Stop any active recognitions when shutting down. @@ -1025,6 +1024,26 @@ public class VoiceInteractionService extends Service { } } + /** Set sandboxed detection training data egress op. + * + * <p> This method can be called by a preinstalled assistant to allow/disallow training data + * egress from trusted process. + * + * @return whether was able to update sandboxed detection op successfully. + * @throws SecurityException if assistant is not a preinstalled assistant. + * + * @hide + */ + @FlaggedApi(Flags.FLAG_ALLOW_TRAINING_DATA_EGRESS_FROM_HDS) + public boolean setSandboxedDetectionTrainingDataOp(int opMode) { + Log.i(TAG, "Setting training data egress op-mode to " + opMode); + try { + return mSystemService.setSandboxedDetectionTrainingDataOp(opMode); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** * Creates an {@link KeyphraseModelManager} to use for enrolling voice models outside of the * pre-bundled system voice models. diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl index 68e2b48c8f08..ea4fc3910d89 100644 --- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl +++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl @@ -388,4 +388,14 @@ interface IVoiceInteractionManagerService { oneway void notifyActivityEventChanged( in IBinder activityToken, int type); + + /** + * Sets the sandboxed detection training data egress op to provided op-mode. + * Caller must be the active assistant and a preinstalled assistant. + * + * @param opMode app-op mode to set training data egress op to. + * + * @return whether was able to successfully set training data egress op. + */ + boolean setSandboxedDetectionTrainingDataOp(int opMode); } diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/ProgramListTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/ProgramListTest.java index d4a88c49b38f..a3a1d3a5e7a2 100644 --- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/ProgramListTest.java +++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/ProgramListTest.java @@ -34,9 +34,7 @@ import android.content.pm.ApplicationInfo; import android.os.Build; import android.os.Parcel; import android.os.RemoteException; -import android.platform.test.annotations.RequiresFlagsEnabled; -import android.platform.test.flag.junit.CheckFlagsRule; -import android.platform.test.flag.junit.DeviceFlagsValueProvider; +import android.platform.test.flag.junit.SetFlagsRule; import android.util.ArraySet; import com.google.common.truth.Expect; @@ -150,7 +148,7 @@ public final class ProgramListTest { @Rule public final Expect mExpect = Expect.create(); @Rule - public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); @Test public void getIdentifierTypes_forFilter() { @@ -631,8 +629,8 @@ public final class ProgramListTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED) public void getProgramInfos() throws Exception { + mSetFlagsRule.enableFlags(Flags.FLAG_HD_RADIO_IMPROVED); createRadioTuner(); mProgramList = mRadioTuner.getDynamicProgramList(TEST_FILTER); registerListCallbacks(/* numCallbacks= */ 1); @@ -648,8 +646,8 @@ public final class ProgramListTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED) public void getProgramInfos_withIdNotFound() throws Exception { + mSetFlagsRule.enableFlags(Flags.FLAG_HD_RADIO_IMPROVED); createRadioTuner(); mProgramList = mRadioTuner.getDynamicProgramList(TEST_FILTER); registerListCallbacks(/* numCallbacks= */ 1); diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioManagerTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioManagerTest.java index 03de1430fec8..89464d14d1c7 100644 --- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioManagerTest.java +++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioManagerTest.java @@ -32,9 +32,7 @@ import android.content.Context; import android.content.pm.ApplicationInfo; import android.os.Parcel; import android.os.RemoteException; -import android.platform.test.annotations.RequiresFlagsEnabled; -import android.platform.test.flag.junit.CheckFlagsRule; -import android.platform.test.flag.junit.DeviceFlagsValueProvider; +import android.platform.test.flag.junit.SetFlagsRule; import android.util.ArrayMap; import org.junit.Rule; @@ -168,7 +166,7 @@ public final class RadioManagerTest { private ICloseHandle mCloseHandleMock; @Rule - public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); @Test public void getType_forBandDescriptor() { @@ -962,22 +960,25 @@ public final class RadioManagerTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED) public void isSignalAcquired_forProgramInfo() { + mSetFlagsRule.enableFlags(Flags.FLAG_HD_RADIO_IMPROVED); + assertWithMessage("Signal acquisition status for HD program info") .that(HD_PROGRAM_INFO.isSignalAcquired()).isTrue(); } @Test - @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED) public void isHdSisAvailable_forProgramInfo() { + mSetFlagsRule.enableFlags(Flags.FLAG_HD_RADIO_IMPROVED); + assertWithMessage("SIS information acquisition status for HD program") .that(HD_PROGRAM_INFO.isHdSisAvailable()).isTrue(); } @Test - @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED) public void isHdAudioAvailable_forProgramInfo() { + mSetFlagsRule.enableFlags(Flags.FLAG_HD_RADIO_IMPROVED); + assertWithMessage("Audio acquisition status for HD program") .that(HD_PROGRAM_INFO.isHdAudioAvailable()).isFalse(); } diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioMetadataTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioMetadataTest.java index 3891accbba44..7b9121eb6085 100644 --- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioMetadataTest.java +++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioMetadataTest.java @@ -22,10 +22,7 @@ import static org.junit.Assert.assertThrows; import android.graphics.Bitmap; import android.os.Parcel; -import android.platform.test.annotations.RequiresFlagsDisabled; -import android.platform.test.annotations.RequiresFlagsEnabled; -import android.platform.test.flag.junit.CheckFlagsRule; -import android.platform.test.flag.junit.DeviceFlagsValueProvider; +import android.platform.test.flag.junit.SetFlagsRule; import org.junit.Rule; import org.junit.Test; @@ -52,7 +49,7 @@ public final class RadioMetadataTest { private Bitmap mBitmapValue; @Rule - public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); @Test public void describeContents_forClock() { @@ -128,8 +125,8 @@ public final class RadioMetadataTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED) public void putStringArray_withIllegalKey_throwsException() { + mSetFlagsRule.enableFlags(Flags.FLAG_HD_RADIO_IMPROVED); String invalidStringArrayKey = RadioMetadata.METADATA_KEY_HD_STATION_NAME_LONG; IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () -> { @@ -142,8 +139,9 @@ public final class RadioMetadataTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED) public void putStringArray_withNullKey_throwsException() { + mSetFlagsRule.enableFlags(Flags.FLAG_HD_RADIO_IMPROVED); + NullPointerException thrown = assertThrows(NullPointerException.class, () -> { mBuilder.putStringArray(/* key= */ null, UFIDS_VALUE); }); @@ -153,8 +151,9 @@ public final class RadioMetadataTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED) public void putStringArray_withNullString_throwsException() { + mSetFlagsRule.enableFlags(Flags.FLAG_HD_RADIO_IMPROVED); + NullPointerException thrown = assertThrows(NullPointerException.class, () -> { mBuilder.putStringArray(RadioMetadata.METADATA_KEY_UFIDS, /* value= */ null); }); @@ -281,8 +280,8 @@ public final class RadioMetadataTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED) public void getStringArray_withKeyInMetadata() { + mSetFlagsRule.enableFlags(Flags.FLAG_HD_RADIO_IMPROVED); String key = RadioMetadata.METADATA_KEY_UFIDS; RadioMetadata metadata = mBuilder.putStringArray(key, UFIDS_VALUE).build(); @@ -291,8 +290,8 @@ public final class RadioMetadataTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED) public void getStringArray_withKeyNotInMetadata() { + mSetFlagsRule.enableFlags(Flags.FLAG_HD_RADIO_IMPROVED); String key = RadioMetadata.METADATA_KEY_UFIDS; RadioMetadata metadata = mBuilder.build(); @@ -305,8 +304,8 @@ public final class RadioMetadataTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED) public void getStringArray_withNullKey() { + mSetFlagsRule.enableFlags(Flags.FLAG_HD_RADIO_IMPROVED); RadioMetadata metadata = mBuilder.build(); NullPointerException thrown = assertThrows(NullPointerException.class, () -> { @@ -318,8 +317,8 @@ public final class RadioMetadataTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED) public void getStringArray_withInvalidKey() { + mSetFlagsRule.enableFlags(Flags.FLAG_HD_RADIO_IMPROVED); String invalidClockKey = RadioMetadata.METADATA_KEY_HD_STATION_NAME_LONG; RadioMetadata metadata = mBuilder.build(); @@ -413,7 +412,6 @@ public final class RadioMetadataTest { } @Test - @RequiresFlagsDisabled(Flags.FLAG_HD_RADIO_IMPROVED) public void writeToParcel_forRadioMetadata() { RadioMetadata metadataExpected = mBuilder .putInt(RadioMetadata.METADATA_KEY_RDS_PI, INT_KEY_VALUE) @@ -430,8 +428,8 @@ public final class RadioMetadataTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED) public void writeToParcel_forRadioMetadata_withStringArrayTypeMetadata() { + mSetFlagsRule.enableFlags(Flags.FLAG_HD_RADIO_IMPROVED); RadioMetadata metadataExpected = mBuilder .putInt(RadioMetadata.METADATA_KEY_RDS_PI, INT_KEY_VALUE) .putString(RadioMetadata.METADATA_KEY_ARTIST, ARTIST_KEY_VALUE) @@ -443,7 +441,7 @@ public final class RadioMetadataTest { parcel.setDataPosition(0); RadioMetadata metadataFromParcel = RadioMetadata.CREATOR.createFromParcel(parcel); - assertWithMessage("Radio metadata created from parcel") + assertWithMessage("Radio metadata created from parcel with string array type metadata") .that(metadataFromParcel).isEqualTo(metadataExpected); } } diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/TunerAdapterTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/TunerAdapterTest.java index 7ca806b49b68..4841711f712d 100644 --- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/TunerAdapterTest.java +++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/TunerAdapterTest.java @@ -36,10 +36,7 @@ import android.content.pm.ApplicationInfo; import android.graphics.Bitmap; import android.os.Build; import android.os.RemoteException; -import android.platform.test.annotations.RequiresFlagsDisabled; -import android.platform.test.annotations.RequiresFlagsEnabled; -import android.platform.test.flag.junit.CheckFlagsRule; -import android.platform.test.flag.junit.DeviceFlagsValueProvider; +import android.platform.test.flag.junit.SetFlagsRule; import org.junit.After; import org.junit.Before; @@ -84,7 +81,7 @@ public final class TunerAdapterTest { private RadioTuner.Callback mCallbackMock; @Rule - public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); @Before public void setUp() throws Exception { @@ -613,9 +610,9 @@ public final class TunerAdapterTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED) public void isConfigFlagSet_withForceAnalogWhenFmForceAnalogSupported() throws Exception { + mSetFlagsRule.enableFlags(Flags.FLAG_HD_RADIO_IMPROVED); when(mTunerMock.isConfigFlagSupported(anyInt())).thenReturn(true); when(mTunerMock.isConfigFlagSet(RadioManager.CONFIG_FORCE_ANALOG_FM)) .thenReturn(true); @@ -626,9 +623,9 @@ public final class TunerAdapterTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED) public void isConfigFlagSet_withForceAnalogWhenFmForceAnalogNotSupported() throws Exception { + mSetFlagsRule.enableFlags(Flags.FLAG_HD_RADIO_IMPROVED); when(mTunerMock.isConfigFlagSupported(RadioManager.CONFIG_FORCE_ANALOG_FM)) .thenReturn(false); when(mTunerMock.isConfigFlagSupported(RadioManager.CONFIG_FORCE_ANALOG)).thenReturn(true); @@ -640,9 +637,9 @@ public final class TunerAdapterTest { } @Test - @RequiresFlagsDisabled(Flags.FLAG_HD_RADIO_IMPROVED) public void isConfigFlagSet_withForceAnalogWhenHdRadioImprovedFeatureNotEnabled() throws Exception { + mSetFlagsRule.disableFlags(Flags.FLAG_HD_RADIO_IMPROVED); when(mTunerMock.isConfigFlagSupported(anyInt())).thenReturn(true); when(mTunerMock.isConfigFlagSet(RadioManager.CONFIG_FORCE_ANALOG)).thenReturn(false); @@ -683,8 +680,8 @@ public final class TunerAdapterTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED) public void setConfigFlag_withForceAnalogWhenFmForceAnalogSupported() throws Exception { + mSetFlagsRule.enableFlags(Flags.FLAG_HD_RADIO_IMPROVED); when(mTunerMock.isConfigFlagSupported(anyInt())).thenReturn(true); mRadioTuner.setConfigFlag(RadioManager.CONFIG_FORCE_ANALOG, /* value= */ false); @@ -695,8 +692,8 @@ public final class TunerAdapterTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED) public void setConfigFlag_withForceAnalogWhenFmForceAnalogNotSupported() throws Exception { + mSetFlagsRule.enableFlags(Flags.FLAG_HD_RADIO_IMPROVED); when(mTunerMock.isConfigFlagSupported(anyInt())).thenReturn(true); when(mTunerMock.isConfigFlagSupported(RadioManager.CONFIG_FORCE_ANALOG_FM)) .thenReturn(false); @@ -709,9 +706,9 @@ public final class TunerAdapterTest { } @Test - @RequiresFlagsDisabled(Flags.FLAG_HD_RADIO_IMPROVED) public void setConfigFlag_withForceAnalogWhenHdRadioImprovedFeatureNotEnabled() throws Exception { + mSetFlagsRule.disableFlags(Flags.FLAG_HD_RADIO_IMPROVED); when(mTunerMock.isConfigFlagSupported(anyInt())).thenReturn(true); mRadioTuner.setConfigFlag(RadioManager.CONFIG_FORCE_ANALOG, /* value= */ false); diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java index 49a7ba855908..15bb66b3e303 100644 --- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java +++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java @@ -40,10 +40,7 @@ import android.hardware.radio.RadioManager; import android.hardware.radio.RadioMetadata; import android.hardware.radio.UniqueProgramIdentifier; import android.os.ServiceSpecificException; -import android.platform.test.annotations.RequiresFlagsDisabled; -import android.platform.test.annotations.RequiresFlagsEnabled; -import android.platform.test.flag.junit.CheckFlagsRule; -import android.platform.test.flag.junit.DeviceFlagsValueProvider; +import android.platform.test.flag.junit.SetFlagsRule; import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder; import com.android.server.broadcastradio.ExtendedRadioMockitoTestCase; @@ -159,7 +156,7 @@ public final class ConversionUtilsTest extends ExtendedRadioMockitoTestCase { @Rule public final Expect expect = Expect.create(); @Rule - public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); @Override protected void initializeSession(StaticMockitoSessionBuilder builder) { @@ -317,9 +314,10 @@ public final class ConversionUtilsTest extends ExtendedRadioMockitoTestCase { } @Test - @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED) public void identifierToHalProgramIdentifier_withFlagEnabled() { + mSetFlagsRule.enableFlags(Flags.FLAG_HD_RADIO_IMPROVED); ProgramSelector.Identifier hdLocationId = createHdStationLocationIdWithFlagEnabled(); + ProgramIdentifier halHdLocationId = ConversionUtils.identifierToHalProgramIdentifier(hdLocationId); @@ -336,10 +334,11 @@ public final class ConversionUtilsTest extends ExtendedRadioMockitoTestCase { } @Test - @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED) public void identifierFromHalProgramIdentifier_withFlagEnabled() { + mSetFlagsRule.enableFlags(Flags.FLAG_HD_RADIO_IMPROVED); ProgramSelector.Identifier hdLocationIdExpected = createHdStationLocationIdWithFlagEnabled(); + ProgramSelector.Identifier hdLocationId = ConversionUtils.identifierFromHalProgramIdentifier(TEST_HAL_HD_STATION_LOCATION_ID); @@ -348,8 +347,9 @@ public final class ConversionUtilsTest extends ExtendedRadioMockitoTestCase { } @Test - @RequiresFlagsDisabled(Flags.FLAG_HD_RADIO_IMPROVED) public void identifierFromHalProgramIdentifier_withFlagDisabled_returnsNull() { + mSetFlagsRule.disableFlags(Flags.FLAG_HD_RADIO_IMPROVED); + ProgramSelector.Identifier hdLocationId = ConversionUtils.identifierFromHalProgramIdentifier(TEST_HAL_HD_STATION_LOCATION_ID); @@ -432,8 +432,8 @@ public final class ConversionUtilsTest extends ExtendedRadioMockitoTestCase { } @Test - @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED) public void programSelectorMeetsSdkVersionRequirement_withLowerVersionSecondaryId() { + mSetFlagsRule.enableFlags(Flags.FLAG_HD_RADIO_IMPROVED); ProgramSelector hdSelector = createHdSelectorWithFlagEnabled(); expect.withMessage("Selector %s with secondary id requiring higher-version SDK version", @@ -449,8 +449,8 @@ public final class ConversionUtilsTest extends ExtendedRadioMockitoTestCase { } @Test - @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED) public void programSelectorMeetsSdkVersionRequirement_withRequiredVersionAndFlagEnabled() { + mSetFlagsRule.enableFlags(Flags.FLAG_HD_RADIO_IMPROVED); ProgramSelector hdSelector = createHdSelectorWithFlagEnabled(); expect.withMessage("Selector %s with required SDK version and feature flag enabled", @@ -548,8 +548,8 @@ public final class ConversionUtilsTest extends ExtendedRadioMockitoTestCase { } @Test - @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED) public void configFlagMeetsSdkVersionRequirement_withRequiredSdkVersionAndFlagEnabled() { + mSetFlagsRule.enableFlags(Flags.FLAG_HD_RADIO_IMPROVED); int halForceAmAnalogFlag = ConfigFlag.FORCE_ANALOG_FM; expect.withMessage("Force Analog FM flag with required SDK version and feature flag" @@ -558,8 +558,8 @@ public final class ConversionUtilsTest extends ExtendedRadioMockitoTestCase { } @Test - @RequiresFlagsDisabled(Flags.FLAG_HD_RADIO_IMPROVED) public void configFlagMeetsSdkVersionRequirement_withRequiredSdkVersionAndFlagDisabled() { + mSetFlagsRule.disableFlags(Flags.FLAG_HD_RADIO_IMPROVED); int halForceAmAnalogFlag = ConfigFlag.FORCE_ANALOG_FM; expect.withMessage("Force Analog FM with required SDK version and with feature flag" @@ -586,8 +586,9 @@ public final class ConversionUtilsTest extends ExtendedRadioMockitoTestCase { } @Test - @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED) public void radioMetadataFromHalMetadata_withFlagEnabled() { + mSetFlagsRule.enableFlags(Flags.FLAG_HD_RADIO_IMPROVED); + RadioMetadata convertedMetadata = ConversionUtils.radioMetadataFromHalMetadata( new Metadata[]{TEST_HAL_SONG_TITLE, TEST_HAL_HD_SUBCHANNELS, TEST_HAL_ALBUM_ART}); @@ -605,8 +606,9 @@ public final class ConversionUtilsTest extends ExtendedRadioMockitoTestCase { } @Test - @RequiresFlagsDisabled(Flags.FLAG_HD_RADIO_IMPROVED) public void radioMetadataFromHalMetadata_withFlagDisabled() { + mSetFlagsRule.disableFlags(Flags.FLAG_HD_RADIO_IMPROVED); + RadioMetadata convertedMetadata = ConversionUtils.radioMetadataFromHalMetadata( new Metadata[]{TEST_HAL_SONG_TITLE, TEST_HAL_HD_SUBCHANNELS, TEST_HAL_ALBUM_ART}); diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java index 7bef5abc3d63..296c45136292 100644 --- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java +++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java @@ -53,10 +53,7 @@ import android.os.ParcelableException; import android.os.RemoteException; import android.os.ServiceSpecificException; import android.os.UserHandle; -import android.platform.test.annotations.RequiresFlagsDisabled; -import android.platform.test.annotations.RequiresFlagsEnabled; -import android.platform.test.flag.junit.CheckFlagsRule; -import android.platform.test.flag.junit.DeviceFlagsValueProvider; +import android.platform.test.flag.junit.SetFlagsRule; import android.util.ArrayMap; import android.util.ArraySet; @@ -164,7 +161,7 @@ public final class TunerSessionTest extends ExtendedRadioMockitoTestCase { @Rule public final Expect expect = Expect.create(); @Rule - public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); @Override protected void initializeSession(StaticMockitoSessionBuilder builder) { @@ -1231,8 +1228,8 @@ public final class TunerSessionTest extends ExtendedRadioMockitoTestCase { } @Test - @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED) public void onConfigFlagUpdated_withRequiredFlagEnabled_invokesCallbacks() throws Exception { + mSetFlagsRule.enableFlags(Flags.FLAG_HD_RADIO_IMPROVED); openAidlClients(/* numClients= */ 1); mHalTunerCallback.onConfigFlagUpdated(ConfigFlag.FORCE_ANALOG_FM, true); @@ -1242,9 +1239,9 @@ public final class TunerSessionTest extends ExtendedRadioMockitoTestCase { } @Test - @RequiresFlagsDisabled(Flags.FLAG_HD_RADIO_IMPROVED) public void onConfigFlagUpdated_withRequiredFlagDisabled_doesNotInvokeCallbacks() throws Exception { + mSetFlagsRule.disableFlags(Flags.FLAG_HD_RADIO_IMPROVED); openAidlClients(/* numClients= */ 1); mHalTunerCallback.onConfigFlagUpdated(ConfigFlag.FORCE_ANALOG_FM, true); diff --git a/graphics/java/android/graphics/YuvImage.java b/graphics/java/android/graphics/YuvImage.java index b3bed00ac6f6..ce35b55d526f 100644 --- a/graphics/java/android/graphics/YuvImage.java +++ b/graphics/java/android/graphics/YuvImage.java @@ -339,7 +339,8 @@ public class YuvImage { return nativeCompressToJpegR(mData, mColorSpace.getDataSpace(), sdr.getYuvData(), sdr.getColorSpace().getDataSpace(), mWidth, mHeight, quality, stream, - new byte[WORKING_COMPRESS_STORAGE], exif); + new byte[WORKING_COMPRESS_STORAGE], exif, + mStrides, sdr.getStrides()); } @@ -451,5 +452,6 @@ public class YuvImage { private static native boolean nativeCompressToJpegR(byte[] hdr, int hdrColorSpaceId, byte[] sdr, int sdrColorSpaceId, int width, int height, int quality, - OutputStream stream, byte[] tempStorage, byte[] exif); + OutputStream stream, byte[] tempStorage, byte[] exif, + int[] hdrStrides, int[] sdrStrides); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt index 75d27d99b9ac..95d7ad5c416f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt @@ -25,12 +25,14 @@ import android.window.TransitionRequestInfo import android.window.WindowContainerToken import android.window.WindowContainerTransaction import com.android.wm.shell.RootTaskDisplayAreaOrganizer +import com.android.wm.shell.protolog.ShellProtoLogGroup import com.android.wm.shell.splitscreen.SplitScreenController import com.android.wm.shell.transition.Transitions import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP import com.android.wm.shell.transition.Transitions.TransitionHandler +import com.android.wm.shell.util.KtProtoLog import com.android.wm.shell.util.TransitionUtil import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration import com.android.wm.shell.windowdecor.MoveToDesktopAnimator @@ -68,6 +70,10 @@ class DragToDesktopTransitionHandler( private var splitScreenController: SplitScreenController? = null private var transitionState: TransitionState? = null + /** Whether a drag-to-desktop transition is in progress. */ + val inProgress: Boolean + get() = transitionState != null + /** Sets a listener to receive callback about events during the transition animation. */ fun setDragToDesktopStateListener(listener: DragToDesktopStateListener) { dragToDesktopStateListener = listener @@ -92,19 +98,22 @@ class DragToDesktopTransitionHandler( dragToDesktopAnimator: MoveToDesktopAnimator, windowDecoration: DesktopModeWindowDecoration ) { - if (transitionState != null) { + if (inProgress) { error("A drag to desktop is already in progress") } val options = ActivityOptions.makeBasic().apply { setTransientLaunch() setSourceInfo(SourceInfo.TYPE_DESKTOP_ANIMATION, SystemClock.uptimeMillis()) + pendingIntentCreatorBackgroundActivityStartMode = + ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED } val pendingIntent = PendingIntent.getActivity( context, 0 /* requestCode */, launchHomeIntent, - FLAG_MUTABLE or FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT or FILL_IN_COMPONENT + FLAG_MUTABLE or FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT or FILL_IN_COMPONENT, + options.toBundle() ) val wct = WindowContainerTransaction() wct.sendPendingIntent(pendingIntent, launchHomeIntent, options.toBundle()) @@ -135,6 +144,12 @@ class DragToDesktopTransitionHandler( * inside the desktop drop zone. */ fun finishDragToDesktopTransition(wct: WindowContainerTransaction) { + if (requireTransitionState().startAborted) { + // Don't attempt to complete the drag-to-desktop since the start transition didn't + // succeed as expected. Just reset the state as if nothing happened. + clearState() + return + } transitions.startTransition(TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP, wct, this) } @@ -147,6 +162,12 @@ class DragToDesktopTransitionHandler( */ fun cancelDragToDesktopTransition() { val state = requireTransitionState() + if (state.startAborted) { + // Don't attempt to cancel the drag-to-desktop since the start transition didn't + // succeed as expected. Just reset the state as if nothing happened. + clearState() + return + } state.cancelled = true if (state.draggedTaskChange != null) { // Regular case, transient launch of Home happened as is waiting for the cancel @@ -409,6 +430,21 @@ class DragToDesktopTransitionHandler( return null } + override fun onTransitionConsumed( + transition: IBinder, + aborted: Boolean, + finishTransaction: SurfaceControl.Transaction? + ) { + val state = transitionState ?: return + if (aborted && state.startTransitionToken == transition) { + KtProtoLog.v( + ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE, + "DragToDesktop: onTransitionConsumed() start transition aborted" + ) + state.startAborted = true + } + } + private fun isHomeChange(change: Change): Boolean { return change.taskInfo?.activityType == ACTIVITY_TYPE_HOME } @@ -508,6 +544,7 @@ class DragToDesktopTransitionHandler( abstract var homeToken: WindowContainerToken? abstract var draggedTaskChange: Change? abstract var cancelled: Boolean + abstract var startAborted: Boolean data class FromFullscreen( override val draggedTaskId: Int, @@ -520,6 +557,7 @@ class DragToDesktopTransitionHandler( override var homeToken: WindowContainerToken? = null, override var draggedTaskChange: Change? = null, override var cancelled: Boolean = false, + override var startAborted: Boolean = false, ) : TransitionState() data class FromSplit( override val draggedTaskId: Int, @@ -532,6 +570,7 @@ class DragToDesktopTransitionHandler( override var homeToken: WindowContainerToken? = null, override var draggedTaskChange: Change? = null, override var cancelled: Boolean = false, + override var startAborted: Boolean = false, var splitRootChange: Change? = null, ) : TransitionState() } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java index e1d177af2331..6ec91e0e28dd 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java @@ -548,8 +548,10 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin */ private PointF offsetCaptionLocation(MotionEvent ev) { final PointF result = new PointF(ev.getX(), ev.getY()); - final Point positionInParent = mTaskOrganizer.getRunningTaskInfo(mTaskInfo.taskId) - .positionInParent; + final ActivityManager.RunningTaskInfo taskInfo = + mTaskOrganizer.getRunningTaskInfo(mTaskInfo.taskId); + if (taskInfo == null) return result; + final Point positionInParent = taskInfo.positionInParent; result.offset(-mRelayoutParams.mCaptionX, -mRelayoutParams.mCaptionY); result.offset(-positionInParent.x, -positionInParent.y); return result; diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt index b355ab0bf311..3bc90ade898e 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt @@ -13,6 +13,7 @@ import android.testing.TestableLooper.RunWithLooper import android.view.SurfaceControl import android.window.TransitionInfo import android.window.TransitionInfo.FLAG_IS_WALLPAPER +import android.window.WindowContainerTransaction import androidx.test.filters.SmallTest import com.android.wm.shell.RootTaskDisplayAreaOrganizer import com.android.wm.shell.ShellTestCase @@ -20,9 +21,11 @@ import com.android.wm.shell.TestRunningTaskInfoBuilder import com.android.wm.shell.splitscreen.SplitScreenController import com.android.wm.shell.transition.Transitions import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP +import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP import com.android.wm.shell.windowdecor.MoveToDesktopAnimator import java.util.function.Supplier +import junit.framework.Assert.assertFalse import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -113,6 +116,40 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { } @Test + fun startDragToDesktop_aborted_finishDropped() { + val task = createTask() + val dragAnimator = mock<MoveToDesktopAnimator>() + // Simulate transition is started. + val transition = startDragToDesktopTransition(task, dragAnimator) + // But the transition was aborted. + handler.onTransitionConsumed(transition, aborted = true, mock()) + + // Attempt to finish the failed drag start. + handler.finishDragToDesktopTransition(WindowContainerTransaction()) + + // Should not be attempted and state should be reset. + verify(transitions, never()) + .startTransition(eq(TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP), any(), any()) + assertFalse(handler.inProgress) + } + + @Test + fun startDragToDesktop_aborted_cancelDropped() { + val task = createTask() + val dragAnimator = mock<MoveToDesktopAnimator>() + // Simulate transition is started. + val transition = startDragToDesktopTransition(task, dragAnimator) + // But the transition was aborted. + handler.onTransitionConsumed(transition, aborted = true, mock()) + + // Attempt to finish the failed drag start. + handler.cancelDragToDesktopTransition() + + // Should not be attempted and state should be reset. + assertFalse(handler.inProgress) + } + + @Test fun cancelDragToDesktop_startWasReady_cancel() { val task = createTask() val dragAnimator = mock<MoveToDesktopAnimator>() diff --git a/libs/hwui/CanvasTransform.cpp b/libs/hwui/CanvasTransform.cpp index cd4fae86aa52..b667daf9c850 100644 --- a/libs/hwui/CanvasTransform.cpp +++ b/libs/hwui/CanvasTransform.cpp @@ -80,6 +80,19 @@ SkColor transformColorInverse(ColorTransform transform, SkColor color) { static void applyColorTransform(ColorTransform transform, SkPaint& paint) { if (transform == ColorTransform::None) return; + if (transform == ColorTransform::Invert) { + auto filter = SkHighContrastFilter::Make( + {/* grayscale= */ false, SkHighContrastConfig::InvertStyle::kInvertLightness, + /* contrast= */ 0.0f}); + + if (paint.getColorFilter()) { + paint.setColorFilter(SkColorFilters::Compose(filter, paint.refColorFilter())); + } else { + paint.setColorFilter(filter); + } + return; + } + SkColor newColor = transformColor(transform, paint.getColor()); paint.setColor(newColor); diff --git a/libs/hwui/CanvasTransform.h b/libs/hwui/CanvasTransform.h index 291f4cf7193b..288dca4de5c1 100644 --- a/libs/hwui/CanvasTransform.h +++ b/libs/hwui/CanvasTransform.h @@ -29,12 +29,15 @@ enum class UsageHint { Unknown = 0, Background = 1, Foreground = 2, + // Contains foreground (usually text), like a button or chip + Container = 3 }; enum class ColorTransform { None, Light, Dark, + Invert }; // True if the paint was modified, false otherwise diff --git a/libs/hwui/DisplayList.h b/libs/hwui/DisplayList.h index 8c180da9c84f..b1c5bf49ede5 100644 --- a/libs/hwui/DisplayList.h +++ b/libs/hwui/DisplayList.h @@ -145,6 +145,8 @@ public: return mImpl && mImpl->hasText(); } + [[nodiscard]] bool hasFill() const { return mImpl && mImpl->hasFill(); } + void applyColorTransform(ColorTransform transform) { if (mImpl) { mImpl->applyColorTransform(transform); diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp index ff0d8d74831c..3b694c5d399b 100644 --- a/libs/hwui/RecordingCanvas.cpp +++ b/libs/hwui/RecordingCanvas.cpp @@ -718,6 +718,27 @@ static constexpr inline bool is_power_of_two(int value) { return (value & (value - 1)) == 0; } +template <typename T> +constexpr bool doesPaintHaveFill(T& paint) { + using T1 = std::remove_cv_t<T>; + if constexpr (std::is_same_v<T1, SkPaint>) { + return paint.getStyle() != SkPaint::Style::kStroke_Style; + } else if constexpr (std::is_same_v<T1, SkPaint&>) { + return paint.getStyle() != SkPaint::Style::kStroke_Style; + } else if constexpr (std::is_same_v<T1, SkPaint*>) { + return paint && paint->getStyle() != SkPaint::Style::kStroke_Style; + } else if constexpr (std::is_same_v<T1, const SkPaint*>) { + return paint && paint->getStyle() != SkPaint::Style::kStroke_Style; + } + + return false; +} + +template <typename... Args> +constexpr bool hasPaintWithFill(Args&&... args) { + return (... || doesPaintHaveFill(args)); +} + template <typename T, typename... Args> void* DisplayListData::push(size_t pod, Args&&... args) { size_t skip = SkAlignPtr(sizeof(T) + pod); @@ -736,6 +757,14 @@ void* DisplayListData::push(size_t pod, Args&&... args) { new (op) T{std::forward<Args>(args)...}; op->type = (uint32_t)T::kType; op->skip = skip; + + // check if this is a fill op or not, in case we need to avoid messing with it with force invert + if constexpr (!std::is_same_v<T, DrawTextBlob>) { + if (hasPaintWithFill(args...)) { + mHasFill = true; + } + } + return op + 1; } diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h index 4f54ee286a56..afadbfda7471 100644 --- a/libs/hwui/RecordingCanvas.h +++ b/libs/hwui/RecordingCanvas.h @@ -110,7 +110,7 @@ class RecordingCanvas; class DisplayListData final { public: - DisplayListData() : mHasText(false) {} + DisplayListData() : mHasText(false), mHasFill(false) {} ~DisplayListData(); void draw(SkCanvas* canvas) const; @@ -121,6 +121,7 @@ public: void applyColorTransform(ColorTransform transform); bool hasText() const { return mHasText; } + bool hasFill() const { return mHasFill; } size_t usedSize() const { return fUsed; } size_t allocatedSize() const { return fReserved; } @@ -192,6 +193,7 @@ private: size_t fReserved = 0; bool mHasText : 1; + bool mHasFill : 1; }; class RecordingCanvas final : public SkCanvasVirtualEnforcer<SkNoDrawCanvas> { diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp index 3e131bc44d39..0b42c88aa448 100644 --- a/libs/hwui/RenderNode.cpp +++ b/libs/hwui/RenderNode.cpp @@ -427,7 +427,13 @@ void RenderNode::handleForceDark(android::uirenderer::TreeInfo *info) { children.push_back(node); }); if (mDisplayList.hasText()) { - usage = UsageHint::Foreground; + if (mDisplayList.hasFill()) { + // Handle a special case for custom views that draw both text and background in the + // same RenderNode, which would otherwise be altered to white-on-white text. + usage = UsageHint::Container; + } else { + usage = UsageHint::Foreground; + } } if (usage == UsageHint::Unknown) { if (children.size() > 1) { @@ -453,8 +459,13 @@ void RenderNode::handleForceDark(android::uirenderer::TreeInfo *info) { drawn.join(bounds); } } - mDisplayList.applyColorTransform( - usage == UsageHint::Background ? ColorTransform::Dark : ColorTransform::Light); + + if (usage == UsageHint::Container) { + mDisplayList.applyColorTransform(ColorTransform::Invert); + } else { + mDisplayList.applyColorTransform(usage == UsageHint::Background ? ColorTransform::Dark + : ColorTransform::Light); + } } void RenderNode::pushStagingDisplayListChanges(TreeObserver& observer, TreeInfo& info) { diff --git a/libs/hwui/jni/YuvToJpegEncoder.cpp b/libs/hwui/jni/YuvToJpegEncoder.cpp index 8c5cc30ba076..c55066af3612 100644 --- a/libs/hwui/jni/YuvToJpegEncoder.cpp +++ b/libs/hwui/jni/YuvToJpegEncoder.cpp @@ -332,7 +332,8 @@ ultrahdr_transfer_function P010Yuv420ToJpegREncoder::findHdrTransferFunction(JNI bool P010Yuv420ToJpegREncoder::encode(JNIEnv* env, SkWStream* stream, void* hdr, int hdrColorSpace, void* sdr, int sdrColorSpace, - int width, int height, int jpegQuality, ScopedByteArrayRO* jExif) { + int width, int height, int jpegQuality, ScopedByteArrayRO* jExif, + ScopedIntArrayRO* jHdrStrides, ScopedIntArrayRO* jSdrStrides) { // Check SDR color space. Now we only support SRGB transfer function if ((sdrColorSpace & ADataSpace::TRANSFER_MASK) != ADataSpace::TRANSFER_SRGB) { jclass IllegalArgumentException = env->FindClass("java/lang/IllegalArgumentException"); @@ -340,6 +341,19 @@ bool P010Yuv420ToJpegREncoder::encode(JNIEnv* env, "The requested SDR color space is not supported. Transfer function must be SRGB"); return false; } + // Check HDR and SDR strides length. + // HDR is YCBCR_P010 color format, and its strides length must be 2 (Y, chroma (Cb, Cr)). + // SDR is YUV_420_888 color format, and its strides length must be 3 (Y, Cb, Cr). + if (jHdrStrides->size() != 2) { + jclass IllegalArgumentException = env->FindClass("java/lang/IllegalArgumentException"); + env->ThrowNew(IllegalArgumentException, "HDR stride length must be 2."); + return false; + } + if (jSdrStrides->size() != 3) { + jclass IllegalArgumentException = env->FindClass("java/lang/IllegalArgumentException"); + env->ThrowNew(IllegalArgumentException, "SDR stride length must be 3."); + return false; + } ultrahdr_color_gamut hdrColorGamut = findColorGamut(env, hdrColorSpace); ultrahdr_color_gamut sdrColorGamut = findColorGamut(env, sdrColorSpace); @@ -351,18 +365,26 @@ bool P010Yuv420ToJpegREncoder::encode(JNIEnv* env, return false; } + const int* hdrStrides = reinterpret_cast<const int*>(jHdrStrides->get()); + const int* sdrStrides = reinterpret_cast<const int*>(jSdrStrides->get()); + JpegR jpegREncoder; jpegr_uncompressed_struct p010; p010.data = hdr; p010.width = width; p010.height = height; + // Divided by 2 because unit in libultrader is pixel and in YuvImage it is byte. + p010.luma_stride = (hdrStrides[0] + 1) / 2; + p010.chroma_stride = (hdrStrides[1] + 1) / 2; p010.colorGamut = hdrColorGamut; jpegr_uncompressed_struct yuv420; yuv420.data = sdr; yuv420.width = width; yuv420.height = height; + yuv420.luma_stride = sdrStrides[0]; + yuv420.chroma_stride = sdrStrides[1]; yuv420.colorGamut = sdrColorGamut; jpegr_exif_struct exif; @@ -420,22 +442,27 @@ static jboolean YuvImage_compressToJpeg(JNIEnv* env, jobject, jbyteArray inYuv, static jboolean YuvImage_compressToJpegR(JNIEnv* env, jobject, jbyteArray inHdr, jint hdrColorSpace, jbyteArray inSdr, jint sdrColorSpace, jint width, jint height, jint quality, jobject jstream, - jbyteArray jstorage, jbyteArray jExif) { + jbyteArray jstorage, jbyteArray jExif, + jintArray jHdrStrides, jintArray jSdrStrides) { jbyte* hdr = env->GetByteArrayElements(inHdr, NULL); jbyte* sdr = env->GetByteArrayElements(inSdr, NULL); ScopedByteArrayRO exif(env, jExif); + ScopedIntArrayRO hdrStrides(env, jHdrStrides); + ScopedIntArrayRO sdrStrides(env, jSdrStrides); SkWStream* strm = CreateJavaOutputStreamAdaptor(env, jstream, jstorage); P010Yuv420ToJpegREncoder encoder; jboolean result = JNI_FALSE; if (encoder.encode(env, strm, hdr, hdrColorSpace, sdr, sdrColorSpace, - width, height, quality, &exif)) { + width, height, quality, &exif, + &hdrStrides, &sdrStrides)) { result = JNI_TRUE; } env->ReleaseByteArrayElements(inHdr, hdr, 0); env->ReleaseByteArrayElements(inSdr, sdr, 0); + delete strm; return result; } @@ -444,7 +471,7 @@ static jboolean YuvImage_compressToJpegR(JNIEnv* env, jobject, jbyteArray inHdr, static const JNINativeMethod gYuvImageMethods[] = { { "nativeCompressToJpeg", "([BIII[I[IILjava/io/OutputStream;[B)Z", (void*)YuvImage_compressToJpeg }, - { "nativeCompressToJpegR", "([BI[BIIIILjava/io/OutputStream;[B[B)Z", + { "nativeCompressToJpegR", "([BI[BIIIILjava/io/OutputStream;[B[B[I[I)Z", (void*)YuvImage_compressToJpegR } }; diff --git a/libs/hwui/jni/YuvToJpegEncoder.h b/libs/hwui/jni/YuvToJpegEncoder.h index f3f2c658d1cd..a3a322453be7 100644 --- a/libs/hwui/jni/YuvToJpegEncoder.h +++ b/libs/hwui/jni/YuvToJpegEncoder.h @@ -92,11 +92,14 @@ public: * @param height Height of the Yuv data in terms of pixels. * @param jpegQuality Picture quality in [0, 100]. * @param exif Buffer holds EXIF package. + * @param hdrStrides The number of row bytes in each image plane of the HDR input. + * @param sdrStrides The number of row bytes in each image plane of the SDR input. * @return true if successfully compressed the stream. */ bool encode(JNIEnv* env, SkWStream* stream, void* hdr, int hdrColorSpace, void* sdr, int sdrColorSpace, - int width, int height, int jpegQuality, ScopedByteArrayRO* exif); + int width, int height, int jpegQuality, ScopedByteArrayRO* exif, + ScopedIntArrayRO* hdrStrides, ScopedIntArrayRO* sdrStrides); /** Map data space (defined in DataSpace.java and data_space.h) to the color gamut * used in JPEG/R diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.h b/libs/hwui/pipeline/skia/SkiaDisplayList.h index e5bd5c9b2a3b..b9dc1c49f09e 100644 --- a/libs/hwui/pipeline/skia/SkiaDisplayList.h +++ b/libs/hwui/pipeline/skia/SkiaDisplayList.h @@ -96,6 +96,8 @@ public: bool hasText() const { return mDisplayList.hasText(); } + bool hasFill() const { return mDisplayList.hasFill(); } + /** * Attempts to reset and reuse this DisplayList. * diff --git a/libs/hwui/tests/unit/RenderNodeTests.cpp b/libs/hwui/tests/unit/RenderNodeTests.cpp index 8273524167f9..e727ea899098 100644 --- a/libs/hwui/tests/unit/RenderNodeTests.cpp +++ b/libs/hwui/tests/unit/RenderNodeTests.cpp @@ -331,3 +331,31 @@ RENDERTHREAD_TEST(DISABLED_RenderNode, prepareTree_HwLayer_AVD_enqueueDamage) { EXPECT_EQ(uirenderer::Rect(0, 0, 200, 400), info.layerUpdateQueue->entries().at(0).damage); canvasContext->destroy(); } + +TEST(RenderNode, hasNoFill) { + auto rootNode = + TestUtils::createNode(0, 0, 200, 400, [](RenderProperties& props, Canvas& canvas) { + Paint paint; + paint.setStyle(SkPaint::Style::kStroke_Style); + canvas.drawRect(10, 10, 100, 100, paint); + }); + + TestUtils::syncHierarchyPropertiesAndDisplayList(rootNode); + + EXPECT_FALSE(rootNode.get()->getDisplayList().hasFill()); + EXPECT_FALSE(rootNode.get()->getDisplayList().hasText()); +} + +TEST(RenderNode, hasFill) { + auto rootNode = + TestUtils::createNode(0, 0, 200, 400, [](RenderProperties& props, Canvas& canvas) { + Paint paint; + paint.setStyle(SkPaint::kStrokeAndFill_Style); + canvas.drawRect(10, 10, 100, 100, paint); + }); + + TestUtils::syncHierarchyPropertiesAndDisplayList(rootNode); + + EXPECT_TRUE(rootNode.get()->getDisplayList().hasFill()); + EXPECT_FALSE(rootNode.get()->getDisplayList().hasText()); +} diff --git a/packages/CrashRecovery/OWNERS b/packages/CrashRecovery/OWNERS new file mode 100644 index 000000000000..daa02111f71f --- /dev/null +++ b/packages/CrashRecovery/OWNERS @@ -0,0 +1,3 @@ +ancr@google.com +harshitmahajan@google.com +robertogil@google.com diff --git a/packages/CrashRecovery/framework/Android.bp b/packages/CrashRecovery/framework/Android.bp new file mode 100644 index 000000000000..b2af315ef2c9 --- /dev/null +++ b/packages/CrashRecovery/framework/Android.bp @@ -0,0 +1,9 @@ +filegroup { + name: "framework-crashrecovery-sources", + srcs: [ + "java/**/*.java", + "java/**/*.aidl", + ], + path: "java", + visibility: ["//frameworks/base:__subpackages__"], +} diff --git a/core/java/android/service/watchdog/ExplicitHealthCheckService.java b/packages/CrashRecovery/framework/java/android/service/watchdog/ExplicitHealthCheckService.java index 7befbfb0f370..7befbfb0f370 100644 --- a/core/java/android/service/watchdog/ExplicitHealthCheckService.java +++ b/packages/CrashRecovery/framework/java/android/service/watchdog/ExplicitHealthCheckService.java diff --git a/core/java/android/service/watchdog/IExplicitHealthCheckService.aidl b/packages/CrashRecovery/framework/java/android/service/watchdog/IExplicitHealthCheckService.aidl index 78c0328d36f0..90965092ac2b 100644 --- a/core/java/android/service/watchdog/IExplicitHealthCheckService.aidl +++ b/packages/CrashRecovery/framework/java/android/service/watchdog/IExplicitHealthCheckService.aidl @@ -21,6 +21,7 @@ import android.os.RemoteCallback; /** * @hide */ +@PermissionManuallyEnforced oneway interface IExplicitHealthCheckService { void setCallback(in @nullable RemoteCallback callback); diff --git a/core/java/android/service/watchdog/OWNERS b/packages/CrashRecovery/framework/java/android/service/watchdog/OWNERS index 1c045e10c0ec..1c045e10c0ec 100644 --- a/core/java/android/service/watchdog/OWNERS +++ b/packages/CrashRecovery/framework/java/android/service/watchdog/OWNERS diff --git a/core/java/android/service/watchdog/PackageConfig.aidl b/packages/CrashRecovery/framework/java/android/service/watchdog/PackageConfig.aidl index 013158676f79..013158676f79 100644 --- a/core/java/android/service/watchdog/PackageConfig.aidl +++ b/packages/CrashRecovery/framework/java/android/service/watchdog/PackageConfig.aidl diff --git a/packages/CrashRecovery/services/Android.bp b/packages/CrashRecovery/services/Android.bp new file mode 100644 index 000000000000..27ddff93247e --- /dev/null +++ b/packages/CrashRecovery/services/Android.bp @@ -0,0 +1,9 @@ +filegroup { + name: "services-crashrecovery-sources", + srcs: [ + "java/**/*.java", + "java/**/*.aidl", + ], + path: "java", + visibility: ["//frameworks/base:__subpackages__"], +} diff --git a/services/core/java/com/android/server/ExplicitHealthCheckController.java b/packages/CrashRecovery/services/java/com/android/server/ExplicitHealthCheckController.java index 3d610d3747c9..3d610d3747c9 100644 --- a/services/core/java/com/android/server/ExplicitHealthCheckController.java +++ b/packages/CrashRecovery/services/java/com/android/server/ExplicitHealthCheckController.java diff --git a/services/core/java/com/android/server/PackageWatchdog.java b/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java index d256aead97e8..d256aead97e8 100644 --- a/services/core/java/com/android/server/PackageWatchdog.java +++ b/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java diff --git a/services/core/java/com/android/server/RescueParty.java b/packages/CrashRecovery/services/java/com/android/server/RescueParty.java index dd543349fc4d..dd543349fc4d 100644 --- a/services/core/java/com/android/server/RescueParty.java +++ b/packages/CrashRecovery/services/java/com/android/server/RescueParty.java diff --git a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/packages/CrashRecovery/services/java/com/android/server/rollback/RollbackPackageHealthObserver.java index 2007079ea5ca..2007079ea5ca 100644 --- a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java +++ b/packages/CrashRecovery/services/java/com/android/server/rollback/RollbackPackageHealthObserver.java diff --git a/services/core/java/com/android/server/rollback/WatchdogRollbackLogger.java b/packages/CrashRecovery/services/java/com/android/server/rollback/WatchdogRollbackLogger.java index f9ef994a523a..f9ef994a523a 100644 --- a/services/core/java/com/android/server/rollback/WatchdogRollbackLogger.java +++ b/packages/CrashRecovery/services/java/com/android/server/rollback/WatchdogRollbackLogger.java diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java index 9c67817cbef4..04c69daffc5a 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java @@ -176,7 +176,8 @@ public class UninstallerActivity extends Activity { try { mDialogInfo.appInfo = pm.getApplicationInfo(mPackageName, - PackageManager.ApplicationInfoFlags.of(PackageManager.MATCH_ANY_USER)); + PackageManager.ApplicationInfoFlags.of(PackageManager.MATCH_ANY_USER + | PackageManager.MATCH_ARCHIVED_PACKAGES)); } catch (PackageManager.NameNotFoundException e) { Log.e(TAG, "Unable to get packageName. Package manager is dead?"); } diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java index 4a93bf80ae39..d113878a3181 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java @@ -189,7 +189,8 @@ public class UninstallAlertDialogFragment extends DialogFragment implements boolean suggestToKeepAppData; try { - PackageInfo pkgInfo = pm.getPackageInfo(pkg, 0); + PackageInfo pkgInfo = pm.getPackageInfo(pkg, + PackageManager.PackageInfoFlags.of(PackageManager.MATCH_ARCHIVED_PACKAGES)); suggestToKeepAppData = pkgInfo.applicationInfo.hasFragileUserData(); } catch (PackageManager.NameNotFoundException e) { diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt index cc95a4b72731..30536547ae5c 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt @@ -144,8 +144,7 @@ private fun SceneScope.LockscreenScene( modifier = Modifier.fillMaxSize(), ) - val notificationStackPosition by - viewModel.keyguardRoot.notificationPositionOnLockscreen.collectAsState() + val notificationStackPosition by viewModel.keyguardRoot.notificationBounds.collectAsState() Layout( modifier = Modifier.fillMaxSize(), diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt index c9d31fdcb8e5..c49c19785624 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt @@ -72,8 +72,8 @@ object Notifications { @Composable fun SceneScope.HeadsUpNotificationSpace( viewModel: NotificationsPlaceholderViewModel, - isPeekFromBottom: Boolean = false, modifier: Modifier = Modifier, + isPeekFromBottom: Boolean = false, ) { NotificationPlaceholder( viewModel = viewModel, @@ -149,11 +149,11 @@ private fun SceneScope.NotificationPlaceholder( form: Form, modifier: Modifier = Modifier, ) { - val key = Notifications.Elements.NotificationPlaceholder + val elementKey = Notifications.Elements.NotificationPlaceholder Box( modifier = modifier - .element(key) + .element(elementKey) .debugBackground(viewModel) .onSizeChanged { size: IntSize -> debugLog(viewModel) { "STACK onSizeChanged: size=$size" } @@ -166,7 +166,7 @@ private fun SceneScope.NotificationPlaceholder( " bounds=${coordinates.boundsInWindow()}" } val boundsInWindow = coordinates.boundsInWindow() - viewModel.setPlaceholderPositionInWindow( + viewModel.onBoundsChanged( top = boundsInWindow.top, bottom = boundsInWindow.bottom, ) @@ -176,7 +176,7 @@ private fun SceneScope.NotificationPlaceholder( animateSharedFloatAsState( value = if (form == Form.HunFromTop) 0f else 1f, key = SharedExpansionValue, - element = key + element = elementKey ) debugLog(viewModel) { "STACK composed: expansion=$animatedExpansion" } if (viewModel.isPlaceholderTextVisible) { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapperTest.kt new file mode 100644 index 000000000000..820c0564c1a7 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapperTest.kt @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.impl.location.domain + +import android.graphics.drawable.Drawable +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.qs.tiles.impl.location.domain.model.LocationTileModel +import com.android.systemui.qs.tiles.impl.location.qsLocationTileConfig +import com.android.systemui.qs.tiles.viewmodel.QSTileState +import com.android.systemui.res.R +import com.android.systemui.util.mockito.mock +import com.google.common.truth.Truth +import junit.framework.Assert +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class LocationTileMapperTest : SysuiTestCase() { + private val kosmos = Kosmos() + private val qsTileConfig = kosmos.qsLocationTileConfig + private val mapper by lazy { LocationTileMapper(context) } + + @Test + fun mapsDisabledDataToInactiveState() { + val tileState: QSTileState = mapper.map(qsTileConfig, LocationTileModel(false)) + + val actualActivationState = tileState.activationState + Assert.assertEquals(QSTileState.ActivationState.INACTIVE, actualActivationState) + } + + @Test + fun mapsEnabledDataToActiveState() { + val tileState: QSTileState = mapper.map(qsTileConfig, LocationTileModel(true)) + + val actualActivationState = tileState.activationState + Assert.assertEquals(QSTileState.ActivationState.ACTIVE, actualActivationState) + } + + @Test + fun mapsEnabledDataToOnIconState() { + val fakeDrawable = mock<Drawable>() + context.orCreateTestableResources.addOverride(R.drawable.qs_location_icon_on, fakeDrawable) + val expectedIcon = Icon.Loaded(fakeDrawable, null) + + val tileState: QSTileState = mapper.map(qsTileConfig, LocationTileModel(true)) + + val actualIcon = tileState.icon() + Truth.assertThat(actualIcon).isEqualTo(expectedIcon) + } + + @Test + fun mapsDisabledDataToOffIconState() { + val fakeDrawable = mock<Drawable>() + context.orCreateTestableResources.addOverride(R.drawable.qs_location_icon_off, fakeDrawable) + val expectedIcon = Icon.Loaded(fakeDrawable, null) + + val tileState: QSTileState = mapper.map(qsTileConfig, LocationTileModel(false)) + + val actualIcon = tileState.icon() + Truth.assertThat(actualIcon).isEqualTo(expectedIcon) + } + + @Test + fun supportsClickAndLongClickActions() { + val dontCare = true + + val tileState: QSTileState = mapper.map(qsTileConfig, LocationTileModel(dontCare)) + + val supportedActions = tileState.supportedActions + Truth.assertThat(supportedActions) + .containsExactly(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/interactor/LocationTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/interactor/LocationTileDataInteractorTest.kt new file mode 100644 index 000000000000..8fdc93be4ba2 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/interactor/LocationTileDataInteractorTest.kt @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.impl.location.interactor + +import android.os.UserHandle +import android.testing.LeakCheck +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.coroutines.collectValues +import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger +import com.android.systemui.qs.tiles.impl.location.domain.interactor.LocationTileDataInteractor +import com.android.systemui.qs.tiles.impl.location.domain.model.LocationTileModel +import com.android.systemui.utils.leaks.FakeLocationController +import com.google.common.truth.Truth +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(AndroidJUnit4::class) +class LocationTileDataInteractorTest : SysuiTestCase() { + private lateinit var controller: FakeLocationController + private lateinit var underTest: LocationTileDataInteractor + + @Before + fun setup() { + controller = FakeLocationController(LeakCheck()) + underTest = LocationTileDataInteractor(controller) + } + + @Test + fun isAvailableRegardlessOfController() = runTest { + controller.setLocationEnabled(false) + + runCurrent() + val availability by collectLastValue(underTest.availability(TEST_USER)) + + Truth.assertThat(availability).isTrue() + } + + @Test + fun dataMatchesController() = runTest { + controller.setLocationEnabled(false) + val flowValues: List<LocationTileModel> by + collectValues(underTest.tileData(TEST_USER, flowOf(DataUpdateTrigger.InitialRequest))) + + runCurrent() + controller.setLocationEnabled(true) + runCurrent() + controller.setLocationEnabled(false) + runCurrent() + + Truth.assertThat(flowValues.size).isEqualTo(3) + Truth.assertThat(flowValues.map { it.isEnabled }) + .containsExactly(false, true, false) + .inOrder() + } + + private companion object { + val TEST_USER = UserHandle.of(1)!! + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/interactor/LocationTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/interactor/LocationTileUserActionInteractorTest.kt new file mode 100644 index 000000000000..0fb8ae697190 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/interactor/LocationTileUserActionInteractorTest.kt @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.impl.location.interactor + +import android.provider.Settings +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testScope +import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler +import com.android.systemui.qs.tiles.base.actions.intentInputs +import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx.click +import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx.longClick +import com.android.systemui.qs.tiles.impl.location.domain.interactor.LocationTileUserActionInteractor +import com.android.systemui.qs.tiles.impl.location.domain.model.LocationTileModel +import com.android.systemui.statusbar.phone.FakeKeyguardStateController +import com.android.systemui.statusbar.policy.LocationController +import com.google.common.truth.Truth.assertThat +import kotlin.coroutines.EmptyCoroutineContext +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(AndroidJUnit4::class) +class LocationTileUserActionInteractorTest : SysuiTestCase() { + + private val qsTileIntentUserActionHandler = FakeQSTileIntentUserInputHandler() + private val keyguardController = FakeKeyguardStateController() + + private lateinit var underTest: LocationTileUserActionInteractor + + @Mock private lateinit var locationController: LocationController + @Mock private lateinit var activityStarter: ActivityStarter + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + val kosmos = Kosmos() + underTest = + LocationTileUserActionInteractor( + EmptyCoroutineContext, + kosmos.testScope, + locationController, + qsTileIntentUserActionHandler, + activityStarter, + keyguardController, + ) + } + + @Test + fun handleClickToEnable() = runTest { + val stateBeforeClick = false + + underTest.handleInput(click(LocationTileModel(stateBeforeClick))) + + Mockito.verify(locationController).setLocationEnabled(!stateBeforeClick) + } + + @Test + fun handleClickToDisable() = runTest { + val stateBeforeClick = true + + underTest.handleInput(click(LocationTileModel(stateBeforeClick))) + + Mockito.verify(locationController).setLocationEnabled(!stateBeforeClick) + } + + @Test + fun handleLongClick() = runTest { + val dontCare = true + + underTest.handleInput(longClick(LocationTileModel(dontCare))) + + assertThat(qsTileIntentUserActionHandler.handledInputs).hasSize(1) + val intentInput = qsTileIntentUserActionHandler.intentInputs.last() + val actualIntentAction = intentInput.intent.action + val expectedIntentAction = Settings.ACTION_LOCATION_SOURCE_SETTINGS + assertThat(actualIntentAction).isEqualTo(expectedIntentAction) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt new file mode 100644 index 000000000000..f04dfd1b8fdb --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt @@ -0,0 +1,107 @@ +/* + * 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. + */ + +@file:OptIn(ExperimentalCoroutinesApi::class) + +package com.android.systemui.statusbar.notification + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.common.shared.model.NotificationContainerBounds +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.flags.Flags +import com.android.systemui.flags.featureFlagsClassic +import com.android.systemui.kosmos.testScope +import com.android.systemui.scene.domain.interactor.sceneInteractor +import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags +import com.android.systemui.scene.shared.flag.sceneContainerFlags +import com.android.systemui.scene.shared.model.ObservableTransitionState +import com.android.systemui.scene.shared.model.SceneKey +import com.android.systemui.scene.shared.model.SceneModel +import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationStackAppearanceViewModel +import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class NotificationStackAppearanceIntegrationTest : SysuiTestCase() { + + private val kosmos = + testKosmos().apply { + sceneContainerFlags = FakeSceneContainerFlags(enabled = true) + featureFlagsClassic.apply { + set(Flags.FULL_SCREEN_USER_SWITCHER, false) + set(Flags.NSSL_DEBUG_LINES, false) + } + } + private val testScope = kosmos.testScope + private val placeholderViewModel = kosmos.notificationsPlaceholderViewModel + private val appearanceViewModel = kosmos.notificationStackAppearanceViewModel + private val sceneInteractor = kosmos.sceneInteractor + + @Test + fun updateBounds() = + testScope.runTest { + val bounds by collectLastValue(appearanceViewModel.stackBounds) + + val top = 200f + val bottom = 550f + placeholderViewModel.onBoundsChanged(top, bottom) + assertThat(bounds).isEqualTo(NotificationContainerBounds(top = top, bottom = bottom)) + } + + @Test + fun updateShadeExpansion() = + testScope.runTest { + val expandFraction by collectLastValue(appearanceViewModel.expandFraction) + assertThat(expandFraction).isEqualTo(0f) + + val transitionState = + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Idle(scene = SceneKey.Lockscreen) + ) + sceneInteractor.setTransitionState(transitionState) + sceneInteractor.changeScene(SceneModel(SceneKey.Shade), "reason") + val transitionProgress = MutableStateFlow(0f) + transitionState.value = + ObservableTransitionState.Transition( + fromScene = SceneKey.Lockscreen, + toScene = SceneKey.Shade, + progress = transitionProgress, + isInitiatedByUserInput = false, + isUserInputOngoing = flowOf(false), + ) + val steps = 10 + repeat(steps) { repetition -> + val progress = (1f / steps) * (repetition + 1) + transitionProgress.value = progress + runCurrent() + assertThat(expandFraction).isWithin(0.01f).of(progress) + } + + sceneInteractor.onSceneChanged(SceneModel(SceneKey.Shade), "reason") + assertThat(expandFraction).isWithin(0.01f).of(1f) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorTest.kt new file mode 100644 index 000000000000..c7411cd78b78 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorTest.kt @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.stack.domain.interactor + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.common.shared.model.NotificationContainerBounds +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testScope +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class NotificationStackAppearanceInteractorTest : SysuiTestCase() { + + private val kosmos = Kosmos() + private val testScope = kosmos.testScope + private val underTest = kosmos.notificationStackAppearanceInteractor + + @Test + fun stackBounds() = + testScope.runTest { + val stackBounds by collectLastValue(underTest.stackBounds) + + val bounds1 = + NotificationContainerBounds( + top = 100f, + bottom = 200f, + isAnimated = true, + ) + underTest.setStackBounds(bounds1) + assertThat(stackBounds).isEqualTo(bounds1) + + val bounds2 = + NotificationContainerBounds( + top = 200f, + bottom = 300f, + isAnimated = false, + ) + underTest.setStackBounds(bounds2) + assertThat(stackBounds).isEqualTo(bounds2) + } + + @Test(expected = IllegalStateException::class) + fun setStackBounds_withImproperBounds_throwsException() = + testScope.runTest { + underTest.setStackBounds( + NotificationContainerBounds( + top = 100f, + bottom = 99f, + ) + ) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/common/shared/model/NotificationContainerBounds.kt b/packages/SystemUI/src/com/android/systemui/common/shared/model/NotificationContainerBounds.kt new file mode 100644 index 000000000000..fdd98bec0a2d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/common/shared/model/NotificationContainerBounds.kt @@ -0,0 +1,30 @@ +/* + * 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.common.shared.model + +/** Models the bounds of the notification container. */ +data class NotificationContainerBounds( + /** The position of the top of the container in its window coordinate system, in pixels. */ + val top: Float = 0f, + /** The position of the bottom of the container in its window coordinate system, in pixels. */ + val bottom: Float = 0f, + /** Whether any modifications to top/bottom should be smoothly animated. */ + val isAnimated: Boolean = false, +) { + /** The current height of the notification container. */ + val height: Float = bottom - top +} diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index e6f1a5482848..2a0d6a8a77d9 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -44,11 +44,11 @@ object Flags { // 100 - notification // TODO(b/297792660): Tracking Bug @JvmField val UNCLEARED_TRANSIENT_HUN_FIX = - unreleasedFlag("uncleared_transient_hun_fix", teamfood = false) + unreleasedFlag("uncleared_transient_hun_fix", teamfood = true) // TODO(b/298308067): Tracking Bug @JvmField val SWIPE_UNCLEARED_TRANSIENT_VIEW_FIX = - unreleasedFlag("swipe_uncleared_transient_view_fix", teamfood = false) + unreleasedFlag("swipe_uncleared_transient_view_fix", teamfood = true) // TODO(b/254512751): Tracking Bug val NOTIFICATION_PIPELINE_DEVELOPER_LOGGING = diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt index 91b671599eb0..e58d7710877b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt @@ -27,8 +27,8 @@ import com.android.keyguard.KeyguardClockSwitch.ClockSize import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.common.shared.model.NotificationContainerBounds import com.android.systemui.common.shared.model.Position -import com.android.systemui.common.shared.model.SharedNotificationContainerPosition import com.android.systemui.common.ui.data.repository.ConfigurationRepository import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.data.repository.KeyguardRepository @@ -82,15 +82,14 @@ constructor( sceneInteractorProvider: Provider<SceneInteractor>, ) { // TODO(b/296118689): move to a repository - private val _sharedNotificationContainerPosition = - MutableStateFlow(SharedNotificationContainerPosition()) + private val _sharedNotificationContainerBounds = MutableStateFlow(NotificationContainerBounds()) - /** Position information for the shared notification container. */ - val sharedNotificationContainerPosition: StateFlow<SharedNotificationContainerPosition> = - _sharedNotificationContainerPosition.asStateFlow() + /** Bounds of the notification container. */ + val notificationContainerBounds: StateFlow<NotificationContainerBounds> = + _sharedNotificationContainerBounds.asStateFlow() - fun setSharedNotificationContainerPosition(position: SharedNotificationContainerPosition) { - _sharedNotificationContainerPosition.value = position + fun setNotificationContainerBounds(position: NotificationContainerBounds) { + _sharedNotificationContainerBounds.value = position } /** diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt index 114fd94ebeb2..c0d3d336719e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt @@ -308,7 +308,7 @@ object KeyguardRootViewBinder { private class OnLayoutChange(private val viewModel: KeyguardRootViewModel) : OnLayoutChangeListener { override fun onLayoutChange( - v: View, + view: View, left: Int, top: Int, right: Int, @@ -318,18 +318,16 @@ object KeyguardRootViewBinder { oldRight: Int, oldBottom: Int ) { - val nsslPlaceholder = v.findViewById(R.id.nssl_placeholder) as View? - if (nsslPlaceholder != null) { + view.findViewById<View>(R.id.nssl_placeholder)?.let { notificationListPlaceholder -> // After layout, ensure the notifications are positioned correctly - viewModel.onSharedNotificationContainerPositionChanged( - nsslPlaceholder.top.toFloat(), - nsslPlaceholder.bottom.toFloat(), + viewModel.onNotificationContainerBoundsChanged( + notificationListPlaceholder.top.toFloat(), + notificationListPlaceholder.bottom.toFloat(), ) } - val ksv = v.findViewById(R.id.keyguard_status_view) as View? - if (ksv != null) { - viewModel.statusViewTop = ksv.top + view.findViewById<View>(R.id.keyguard_status_view)?.let { statusView -> + viewModel.statusViewTop = statusView.top } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt index b3c7d3790527..524fa1ede90a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt @@ -22,7 +22,7 @@ import android.util.MathUtils import android.view.View.VISIBLE import com.android.app.animation.Interpolators import com.android.systemui.Flags.newAodTransition -import com.android.systemui.common.shared.model.SharedNotificationContainerPosition +import com.android.systemui.common.shared.model.NotificationContainerBounds import com.android.systemui.dagger.SysUISingleton import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor import com.android.systemui.keyguard.domain.interactor.BurnInInteractor @@ -98,9 +98,9 @@ constructor( val goneToAodTransition = keyguardTransitionInteractor.goneToAodTransition - /** the shared notification container position *on the lockscreen* */ - val notificationPositionOnLockscreen: StateFlow<SharedNotificationContainerPosition> - get() = keyguardInteractor.sharedNotificationContainerPosition + /** the shared notification container bounds *on the lockscreen* */ + val notificationBounds: StateFlow<NotificationContainerBounds> = + keyguardInteractor.notificationContainerBounds /** An observable for the alpha level for the entire keyguard root view. */ val alpha: Flow<Float> = @@ -247,14 +247,13 @@ constructor( previewMode.value = PreviewMode(true) } - fun onSharedNotificationContainerPositionChanged(top: Float, bottom: Float) { + fun onNotificationContainerBoundsChanged(top: Float, bottom: Float) { // Notifications should not be visible in preview mode if (previewMode.value.isInPreviewMode) { return } - keyguardInteractor.setSharedNotificationContainerPosition( - SharedNotificationContainerPosition(top, bottom) - ) + + keyguardInteractor.setNotificationContainerBounds(NotificationContainerBounds(top, bottom)) } /** Is there an expanded pulse, are we animating in response? */ diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardMediaControllerLog.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardMediaControllerLog.kt new file mode 100644 index 000000000000..346f269edbb2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardMediaControllerLog.kt @@ -0,0 +1,25 @@ +/* + * 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 javax.inject.Qualifier + +/** A [com.android.systemui.log.LogBuffer] for KeyguardMediaController. */ +@Qualifier +@MustBeDocumented +@Retention(AnnotationRetention.RUNTIME) +annotation class KeyguardMediaControllerLog 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 0d81940cacbd..0b3bbb5c3d08 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java @@ -156,6 +156,14 @@ public class LogModule { return factory.create("NotifRemoteInputLog", 50 /* maxSize */, false /* systrace */); } + /** Provides a logging buffer for all logs related to keyguard media controller. */ + @Provides + @SysUISingleton + @KeyguardMediaControllerLog + public static LogBuffer provideKeyguardMediaControllerLogBuffer(LogBufferFactory factory) { + return factory.create("KeyguardMediaControllerLog", 50 /* maxSize */, false /* systrace */); + } + /** Provides a logging buffer for all logs related to unseen notifications. */ @Provides @SysUISingleton diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt index 773c292befcf..945bf9a5c0b2 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt @@ -23,7 +23,6 @@ import android.net.Uri import android.os.Handler import android.os.UserHandle import android.provider.Settings -import android.util.Log import android.view.View import android.view.ViewGroup import androidx.annotation.VisibleForTesting @@ -63,22 +62,21 @@ constructor( @Main private val handler: Handler, configurationController: ConfigurationController, private val splitShadeStateController: SplitShadeStateController, + private val logger: KeyguardMediaControllerLogger, dumpManager: DumpManager, ) : Dumpable { - /** It's added for debugging purpose to directly see last received StatusBarState. */ - private var lastReceivedStatusBarState = -1 + private var lastUsedStatusBarState = -1 init { dumpManager.registerDumpable(this) statusBarStateController.addCallback( object : StatusBarStateController.StateListener { override fun onStateChanged(newState: Int) { - lastReceivedStatusBarState = newState - refreshMediaPosition() + refreshMediaPosition(reason = "StatusBarState.onStateChanged") } override fun onDozingChanged(isDozing: Boolean) { - refreshMediaPosition() + refreshMediaPosition(reason = "StatusBarState.onDozingChanged") } } ) @@ -100,7 +98,7 @@ constructor( true, UserHandle.USER_CURRENT ) - refreshMediaPosition() + refreshMediaPosition(reason = "allowMediaPlayerOnLockScreen changed") } } } @@ -132,7 +130,7 @@ constructor( } field = value reattachHostView() - refreshMediaPosition() + refreshMediaPosition(reason = "useSplitShade changed") } /** Is the media player visible? */ @@ -147,7 +145,7 @@ constructor( var isDozeWakeUpAnimationWaiting: Boolean = false set(value) { field = value - refreshMediaPosition() + refreshMediaPosition(reason = "isDozeWakeUpAnimationWaiting changed") } /** single pane media container placed at the top of the notifications list */ @@ -181,7 +179,7 @@ constructor( /** Called whenever the media hosts visibility changes */ private fun onMediaHostVisibilityChanged(visible: Boolean) { - refreshMediaPosition() + refreshMediaPosition(reason = "onMediaHostVisibilityChanged") if (visible) { mediaHost.hostView.layoutParams.apply { height = ViewGroup.LayoutParams.WRAP_CONTENT @@ -194,7 +192,7 @@ constructor( fun attachSplitShadeContainer(container: ViewGroup) { splitShadeContainer = container reattachHostView() - refreshMediaPosition() + refreshMediaPosition(reason = "attachSplitShadeContainer") } private fun reattachHostView() { @@ -217,30 +215,41 @@ constructor( } } - fun refreshMediaPosition() { + fun refreshMediaPosition(reason: String) { val currentState = statusBarStateController.state - if (lastReceivedStatusBarState != -1 && currentState != lastReceivedStatusBarState) { - Log.wtfStack( - TAG, - "currentState[${StatusBarState.toString(currentState)}] is " + - "different from the last " + - "received one[${StatusBarState.toString(lastReceivedStatusBarState)}]." - ) - } val keyguardOrUserSwitcher = (currentState == StatusBarState.KEYGUARD) // mediaHost.visible required for proper animations handling + val isMediaHostVisible = mediaHost.visible + val isBypassNotEnabled = !bypassController.bypassEnabled + val currentAllowMediaPlayerOnLockScreen = allowMediaPlayerOnLockScreen + val useSplitShade = useSplitShade + val shouldBeVisibleForSplitShade = shouldBeVisibleForSplitShade() + visible = - mediaHost.visible && - !bypassController.bypassEnabled && + isMediaHostVisible && + isBypassNotEnabled && keyguardOrUserSwitcher && - allowMediaPlayerOnLockScreen && - shouldBeVisibleForSplitShade() + currentAllowMediaPlayerOnLockScreen && + shouldBeVisibleForSplitShade if (visible) { showMediaPlayer() } else { hideMediaPlayer() } + logger.logRefreshMediaPosition( + reason = reason, + visible = visible, + useSplitShade = useSplitShade, + currentState = currentState, + keyguardOrUserSwitcher = keyguardOrUserSwitcher, + mediaHostVisible = isMediaHostVisible, + bypassNotEnabled = isBypassNotEnabled, + currentAllowMediaPlayerOnLockScreen = currentAllowMediaPlayerOnLockScreen, + shouldBeVisibleForSplitShade = shouldBeVisibleForSplitShade + ) + + lastUsedStatusBarState = currentState } private fun shouldBeVisibleForSplitShade(): Boolean { @@ -298,10 +307,10 @@ constructor( println("isDozeWakeUpAnimationWaiting", isDozeWakeUpAnimationWaiting) println("singlePaneContainer", singlePaneContainer) println("splitShadeContainer", splitShadeContainer) - if (lastReceivedStatusBarState != -1) { + if (lastUsedStatusBarState != -1) { println( - "lastReceivedStatusBarState", - StatusBarState.toString(lastReceivedStatusBarState) + "lastUsedStatusBarState", + StatusBarState.toString(lastUsedStatusBarState) ) } println( @@ -311,8 +320,4 @@ constructor( } } } - - private companion object { - private const val TAG = "KeyguardMediaController" - } } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerLogger.kt new file mode 100644 index 000000000000..41fef88645a2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerLogger.kt @@ -0,0 +1,70 @@ +/* + * 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.media.controls.ui + +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.core.LogLevel.DEBUG +import com.android.systemui.log.dagger.KeyguardMediaControllerLog +import com.android.systemui.statusbar.StatusBarState +import javax.inject.Inject + +/** Logger class for [KeyguardMediaController]. */ +open class KeyguardMediaControllerLogger +@Inject +constructor(@KeyguardMediaControllerLog private val logBuffer: LogBuffer) { + + fun logRefreshMediaPosition( + reason: String, + visible: Boolean, + useSplitShade: Boolean, + currentState: Int, + keyguardOrUserSwitcher: Boolean, + mediaHostVisible: Boolean, + bypassNotEnabled: Boolean, + currentAllowMediaPlayerOnLockScreen: Boolean, + shouldBeVisibleForSplitShade: Boolean + ) = + logBuffer.log( + TAG, + DEBUG, + { + str1 = reason + bool1 = visible + bool2 = useSplitShade + int1 = currentState + bool3 = keyguardOrUserSwitcher + bool4 = mediaHostVisible + int2 = if (bypassNotEnabled) 1 else 0 + str2 = currentAllowMediaPlayerOnLockScreen.toString() + str3 = shouldBeVisibleForSplitShade.toString() + }, + { + "refreshMediaPosition(reason=$str1, " + + "currentState=${StatusBarState.toString(int1)}, " + + "visible=$bool1, useSplitShade=$bool2, " + + "keyguardOrUserSwitcher=$bool3, " + + "mediaHostVisible=$bool4, " + + "bypassNotEnabled=${int2 == 1}, " + + "currentAllowMediaPlayerOnLockScreen=$str2, " + + "shouldBeVisibleForSplitShade=$str3)" + } + ) + + private companion object { + private const val TAG = "KeyguardMediaControllerLog" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java index 5e3a166f5f35..f13add9b62b6 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java @@ -940,50 +940,47 @@ public class NavigationBar extends ViewController<NavigationBarView> implements private void orientSecondaryHomeHandle() { if (!canShowSecondaryHandle()) { + if (mStartingQuickSwitchRotation == -1) { + resetSecondaryHandle(); + } return; } - if (mStartingQuickSwitchRotation == -1) { - resetSecondaryHandle(); - } else { - int deltaRotation = deltaRotation(mCurrentRotation, mStartingQuickSwitchRotation); - if (mStartingQuickSwitchRotation == -1 || deltaRotation == -1) { - // Curious if starting quickswitch can change between the if check and our delta - Log.d(TAG, "secondary nav delta rotation: " + deltaRotation - + " current: " + mCurrentRotation - + " starting: " + mStartingQuickSwitchRotation); - } - int height = 0; - int width = 0; - Rect dispSize = mWindowManager.getCurrentWindowMetrics().getBounds(); - mOrientationHandle.setDeltaRotation(deltaRotation); - switch (deltaRotation) { - case Surface.ROTATION_90: - case Surface.ROTATION_270: - height = dispSize.height(); - width = mView.getHeight(); - break; - case Surface.ROTATION_180: - case Surface.ROTATION_0: - // TODO(b/152683657): Need to determine best UX for this - if (!mShowOrientedHandleForImmersiveMode) { - resetSecondaryHandle(); - return; - } - width = dispSize.width(); - height = mView.getHeight(); - break; - } - - mOrientationParams.gravity = - deltaRotation == Surface.ROTATION_0 ? Gravity.BOTTOM : - (deltaRotation == Surface.ROTATION_90 ? Gravity.LEFT : Gravity.RIGHT); - mOrientationParams.height = height; - mOrientationParams.width = width; - mWindowManager.updateViewLayout(mOrientationHandle, mOrientationParams); - mView.setVisibility(View.GONE); - mOrientationHandle.setVisibility(View.VISIBLE); + int deltaRotation = deltaRotation(mCurrentRotation, mStartingQuickSwitchRotation); + if (mStartingQuickSwitchRotation == -1 || deltaRotation == -1) { + // Curious if starting quickswitch can change between the if check and our delta + Log.d(TAG, "secondary nav delta rotation: " + deltaRotation + + " current: " + mCurrentRotation + + " starting: " + mStartingQuickSwitchRotation); + } + int height = 0; + int width = 0; + Rect dispSize = mWindowManager.getCurrentWindowMetrics().getBounds(); + mOrientationHandle.setDeltaRotation(deltaRotation); + switch (deltaRotation) { + case Surface.ROTATION_90, Surface.ROTATION_270: + height = dispSize.height(); + width = mView.getHeight(); + break; + case Surface.ROTATION_180, Surface.ROTATION_0: + // TODO(b/152683657): Need to determine best UX for this + if (!mShowOrientedHandleForImmersiveMode) { + resetSecondaryHandle(); + return; + } + width = dispSize.width(); + height = mView.getHeight(); + break; } + + mOrientationParams.gravity = + deltaRotation == Surface.ROTATION_0 ? Gravity.BOTTOM : + (deltaRotation == Surface.ROTATION_90 ? Gravity.LEFT : Gravity.RIGHT); + mOrientationParams.height = height; + mOrientationParams.width = width; + mWindowManager.updateViewLayout(mOrientationHandle, mOrientationParams); + mView.setVisibility(View.GONE); + mOrientationHandle.setVisibility(View.VISIBLE); } private void resetSecondaryHandle() { @@ -1786,7 +1783,8 @@ public class NavigationBar extends ViewController<NavigationBarView> implements } private boolean canShowSecondaryHandle() { - return mNavBarMode == NAV_BAR_MODE_GESTURAL && mOrientationHandle != null; + return mNavBarMode == NAV_BAR_MODE_GESTURAL && mOrientationHandle != null + && mStartingQuickSwitchRotation != -1; } private final UserTracker.Callback mUserChangedCallback = diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapper.kt new file mode 100644 index 000000000000..8e53723a5a6b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapper.kt @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.impl.location.domain + +import android.content.Context +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper +import com.android.systemui.qs.tiles.impl.location.domain.model.LocationTileModel +import com.android.systemui.qs.tiles.viewmodel.QSTileConfig +import com.android.systemui.qs.tiles.viewmodel.QSTileState +import com.android.systemui.res.R +import javax.inject.Inject + +/** Maps [LocationTileModel] to [QSTileState]. */ +class LocationTileMapper @Inject constructor(private val context: Context) : + QSTileDataToStateMapper<LocationTileModel> { + + override fun map(config: QSTileConfig, data: LocationTileModel): QSTileState = + QSTileState.build(context, config.uiConfig) { + val icon = + Icon.Loaded( + context.resources.getDrawable( + if (data.isEnabled) { + R.drawable.qs_location_icon_on + } else { + R.drawable.qs_location_icon_off + } + ), + contentDescription = null + ) + this.icon = { icon } + + this.label = context.resources.getString(R.string.quick_settings_location_label) + + if (data.isEnabled) { + activationState = QSTileState.ActivationState.ACTIVE + secondaryLabel = context.resources.getStringArray(R.array.tile_states_location)[2] + } else { + activationState = QSTileState.ActivationState.INACTIVE + secondaryLabel = context.resources.getStringArray(R.array.tile_states_location)[1] + } + contentDescription = label + supportedActions = + setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/interactor/LocationTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/interactor/LocationTileDataInteractor.kt new file mode 100644 index 000000000000..d1c80309a1cc --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/interactor/LocationTileDataInteractor.kt @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.impl.location.domain.interactor + +import android.os.UserHandle +import com.android.systemui.common.coroutine.ConflatedCallbackFlow +import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger +import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor +import com.android.systemui.qs.tiles.impl.location.domain.model.LocationTileModel +import com.android.systemui.statusbar.policy.LocationController +import javax.inject.Inject +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOf + +/** Observes location state changes providing the [LocationTileModel]. */ +class LocationTileDataInteractor +@Inject +constructor( + private val locationController: LocationController, +) : QSTileDataInteractor<LocationTileModel> { + + override fun tileData( + user: UserHandle, + triggers: Flow<DataUpdateTrigger> + ): Flow<LocationTileModel> = + ConflatedCallbackFlow.conflatedCallbackFlow { + val initialValue = locationController.isLocationEnabled + trySend(LocationTileModel(initialValue)) + + val callback = + object : LocationController.LocationChangeCallback { + override fun onLocationSettingsChanged(locationEnabled: Boolean) { + trySend(LocationTileModel(locationEnabled)) + } + } + locationController.addCallback(callback) + awaitClose { locationController.removeCallback(callback) } + } + + override fun availability(user: UserHandle): Flow<Boolean> = flowOf(true) +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/interactor/LocationTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/interactor/LocationTileUserActionInteractor.kt new file mode 100644 index 000000000000..66705ead3cf0 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/interactor/LocationTileUserActionInteractor.kt @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.impl.location.domain.interactor + +import android.content.Intent +import android.provider.Settings +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler +import com.android.systemui.qs.tiles.base.interactor.QSTileInput +import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor +import com.android.systemui.qs.tiles.impl.location.domain.model.LocationTileModel +import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction +import com.android.systemui.statusbar.policy.KeyguardStateController +import com.android.systemui.statusbar.policy.LocationController +import javax.inject.Inject +import kotlin.coroutines.CoroutineContext +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +/** Handles location tile clicks. */ +class LocationTileUserActionInteractor +@Inject +constructor( + @Background private val coroutineContext: CoroutineContext, + @Application private val applicationScope: CoroutineScope, + private val locationController: LocationController, + private val qsTileIntentUserActionHandler: QSTileIntentUserInputHandler, + private val activityStarter: ActivityStarter, + private val keyguardController: KeyguardStateController, +) : QSTileUserActionInteractor<LocationTileModel> { + override suspend fun handleInput(input: QSTileInput<LocationTileModel>): Unit = + with(input) { + when (action) { + is QSTileUserAction.Click -> { + val wasEnabled: Boolean = input.data.isEnabled + if (keyguardController.isMethodSecure() && keyguardController.isShowing()) { + activityStarter.postQSRunnableDismissingKeyguard { + CoroutineScope(applicationScope.coroutineContext).launch { + locationController.setLocationEnabled(!wasEnabled) + } + } + } else { + withContext(coroutineContext) { + locationController.setLocationEnabled(!wasEnabled) + } + } + } + is QSTileUserAction.LongClick -> { + qsTileIntentUserActionHandler.handle( + action.view, + Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS) + ) + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/model/LocationTileModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/model/LocationTileModel.kt new file mode 100644 index 000000000000..3095d7e77586 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/model/LocationTileModel.kt @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.impl.location.domain.model + +/** + * Location tile model. + * + * @param isEnabled is true when the location is enabled; + */ +@JvmInline value class LocationTileModel(val isEnabled: Boolean) diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index c06e9a443a7b..c925010ef4d5 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -1309,7 +1309,8 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mQsController.updateResources(); mNotificationsQSContainerController.updateResources(); updateKeyguardStatusViewAlignment(/* animate= */false); - mKeyguardMediaController.refreshMediaPosition(); + mKeyguardMediaController.refreshMediaPosition( + "NotificationPanelViewController.updateResources"); if (splitShadeChanged) { onSplitShadeEnabledChanged(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt index 7c10663bbb50..abf09ae9844c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt @@ -17,14 +17,14 @@ package com.android.systemui.statusbar.notification.stack.data.repository -import com.android.systemui.common.shared.model.SharedNotificationContainerPosition +import com.android.systemui.common.shared.model.NotificationContainerBounds import com.android.systemui.dagger.SysUISingleton import javax.inject.Inject import kotlinx.coroutines.flow.MutableStateFlow -/** A repository which holds state about and controlling the appearance of the NSSL */ +/** A repository which holds state about and controlling the appearance of the notification stack */ @SysUISingleton class NotificationStackAppearanceRepository @Inject constructor() { - /** The position of the notification stack in the current scene */ - val stackPosition = MutableStateFlow(SharedNotificationContainerPosition(0f, 0f)) + /** The bounds of the notification stack in the current scene. */ + val stackBounds = MutableStateFlow(NotificationContainerBounds(0f, 0f)) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt index 820fe0b1f11e..32e4e89c42c5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt @@ -17,7 +17,7 @@ package com.android.systemui.statusbar.notification.stack.domain.interactor -import com.android.systemui.common.shared.model.SharedNotificationContainerPosition +import com.android.systemui.common.shared.model.NotificationContainerBounds import com.android.systemui.dagger.SysUISingleton import com.android.systemui.statusbar.notification.stack.data.repository.NotificationStackAppearanceRepository import javax.inject.Inject @@ -31,13 +31,12 @@ class NotificationStackAppearanceInteractor constructor( private val repository: NotificationStackAppearanceRepository, ) { - /** The position of the notification stack in the current scene */ - val stackPosition: StateFlow<SharedNotificationContainerPosition> - get() = repository.stackPosition.asStateFlow() + /** The bounds of the notification stack in the current scene. */ + val stackBounds: StateFlow<NotificationContainerBounds> = repository.stackBounds.asStateFlow() - /** Sets the position of the notification stack in the current scene */ - fun setStackPosition(position: SharedNotificationContainerPosition) { - check(position.top <= position.bottom) { "Invalid position: $position" } - repository.stackPosition.value = position + /** Sets the position of the notification stack in the current scene. */ + fun setStackBounds(bounds: NotificationContainerBounds) { + check(bounds.top <= bounds.bottom) { "Invalid bounds: $bounds" } + repository.stackBounds.value = bounds } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt index 4d6a6ee98b7d..fa7a8fdb7495 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt @@ -40,17 +40,17 @@ object NotificationStackAppearanceViewBinder { view.repeatWhenAttached { repeatOnLifecycle(Lifecycle.State.CREATED) { launch { - viewModel.stackPosition.collect { + viewModel.stackBounds.collect { bounds -> controller.updateTopPadding( - it.top, + bounds.top, controller.isAddOrRemoveAnimationPending ) } } launch { - viewModel.expandFraction.collect { - ambientState.expansionFraction = it - controller.expandedHeight = it * controller.view.height + viewModel.expandFraction.collect { expandFraction -> + ambientState.expansionFraction = expandFraction + controller.expandedHeight = expandFraction * controller.view.height } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt index 44006fc3fd4e..5e60b5f4c707 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt @@ -73,8 +73,9 @@ object SharedNotificationContainerBinder { if (!sceneContainerFlags.flexiNotifsEnabled()) { launch { - viewModel.position.collect { - val animate = it.animate || controller.isAddOrRemoveAnimationPending + viewModel.bounds.collect { + val animate = + it.isAnimated || controller.isAddOrRemoveAnimationPending controller.updateTopPadding(it.top, animate) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt index b86993486097..f4c0e92b0e87 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt @@ -17,7 +17,7 @@ package com.android.systemui.statusbar.notification.stack.ui.viewmodel -import com.android.systemui.common.shared.model.SharedNotificationContainerPosition +import com.android.systemui.common.shared.model.NotificationContainerBounds import com.android.systemui.dagger.SysUISingleton import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor @@ -32,10 +32,9 @@ constructor( stackAppearanceInteractor: NotificationStackAppearanceInteractor, shadeInteractor: ShadeInteractor, ) { - /** The expansion fraction from the top of the notification shade */ + /** The expansion fraction from the top of the notification shade. */ val expandFraction: Flow<Float> = shadeInteractor.shadeExpansion - /** The position of the notification stack in the current scene */ - val stackPosition: Flow<SharedNotificationContainerPosition> = - stackAppearanceInteractor.stackPosition + /** The bounds of the notification stack in the current scene. */ + val stackBounds: Flow<NotificationContainerBounds> = stackAppearanceInteractor.stackBounds } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt index 7def6feedb41..c6fd98ea2223 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt @@ -16,7 +16,7 @@ package com.android.systemui.statusbar.notification.stack.ui.viewmodel -import com.android.systemui.common.shared.model.SharedNotificationContainerPosition +import com.android.systemui.common.shared.model.NotificationContainerBounds import com.android.systemui.dagger.SysUISingleton import com.android.systemui.flags.FeatureFlagsClassic import com.android.systemui.flags.Flags @@ -46,8 +46,18 @@ constructor( /** DEBUG: whether the debug logging should be output. */ val isDebugLoggingEnabled: Boolean = flags.flexiNotifsEnabled() - /** Sets the position of the notification stack in the current scene */ - fun setPlaceholderPositionInWindow(top: Float, bottom: Float) { - interactor.setStackPosition(SharedNotificationContainerPosition(top, bottom)) + /** + * Notifies that the bounds of the notification placeholder have changed. + * + * @param top The position of the top of the container in its window coordinate system, in + * pixels. + * @param bottom The position of the bottom of the container in its window coordinate system, in + * pixels. + */ + fun onBoundsChanged( + top: Float, + bottom: Float, + ) { + interactor.setStackBounds(NotificationContainerBounds(top, bottom)) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt index 09b4dfa31788..1febaf99b7b2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt @@ -15,9 +15,11 @@ * */ +@file:OptIn(ExperimentalCoroutinesApi::class) + package com.android.systemui.statusbar.notification.stack.ui.viewmodel -import com.android.systemui.common.shared.model.SharedNotificationContainerPosition +import com.android.systemui.common.shared.model.NotificationContainerBounds import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor @@ -27,6 +29,7 @@ import com.android.systemui.statusbar.notification.stack.domain.interactor.Share import com.android.systemui.util.kotlin.sample import javax.inject.Inject import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow @@ -35,7 +38,6 @@ import kotlinx.coroutines.flow.combineTransform import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.stateIn @@ -109,18 +111,18 @@ constructor( * * When the shade is expanding, the position is controlled by... the shade. */ - val position: StateFlow<SharedNotificationContainerPosition> = + val bounds: StateFlow<NotificationContainerBounds> = isOnLockscreenWithoutShade .flatMapLatest { onLockscreen -> if (onLockscreen) { combine( - keyguardInteractor.sharedNotificationContainerPosition, + keyguardInteractor.notificationContainerBounds, configurationBasedDimensions - ) { position, config -> + ) { bounds, config -> if (config.useSplitShade) { - position.copy(top = 0f) + bounds.copy(top = 0f) } else { - position + bounds } } } else { @@ -129,9 +131,9 @@ constructor( // When QS expansion > 0, it should directly set the top padding so do not // animate it val animate = qsExpansion == 0f - keyguardInteractor.sharedNotificationContainerPosition.value.copy( + keyguardInteractor.notificationContainerBounds.value.copy( top = top, - animate = animate + isAnimated = animate ) } } @@ -139,7 +141,7 @@ constructor( .stateIn( scope = applicationScope, started = SharingStarted.WhileSubscribed(), - initialValue = SharedNotificationContainerPosition(0f, 0f), + initialValue = NotificationContainerBounds(0f, 0f), ) /** @@ -169,7 +171,7 @@ constructor( // when the notification stack has changed internally val limitedNotifications = combine( - position, + bounds, interactor.notificationStackChanged.onStart { emit(Unit) }, ) { position, _ -> calculateSpace(position.bottom - position.top) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DevicePostureController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DevicePostureController.java index 87df180353b1..bbba19d61b5a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DevicePostureController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DevicePostureController.java @@ -73,11 +73,7 @@ public interface DevicePostureController extends CallbackController<Callback> { /** Callback to be notified about device posture changes. */ interface Callback { - /** - * Called when the posture changes. If there are multiple active displays ("concurrent"), - * this will report the physical posture of the device (also known as the base device - * state). - */ + /** Called when the posture changes. */ void onPostureChanged(@DevicePostureInt int posture); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DevicePostureControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DevicePostureControllerImpl.java index 8365e0fd41b5..a32a5ab13748 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DevicePostureControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DevicePostureControllerImpl.java @@ -36,11 +36,8 @@ import javax.inject.Inject; /** Implementation of {@link DevicePostureController} using the DeviceStateManager. */ @SysUISingleton public class DevicePostureControllerImpl implements DevicePostureController { - /** From androidx.window.common.COMMON_STATE_USE_BASE_STATE */ - private static final int COMMON_STATE_USE_BASE_STATE = 1000; private final List<Callback> mListeners = new ArrayList<>(); private int mCurrentDevicePosture = DEVICE_POSTURE_UNKNOWN; - private int mCurrentBasePosture = DEVICE_POSTURE_UNKNOWN; private final SparseIntArray mDeviceStateToPostureMap = new SparseIntArray(); @@ -73,25 +70,12 @@ public class DevicePostureControllerImpl implements DevicePostureController { mDeviceStateToPostureMap.put(deviceState, posture); } - deviceStateManager.registerCallback(executor, new DeviceStateManager.DeviceStateCallback() { - @Override - public void onStateChanged(int state) { - Assert.isMainThread(); - mCurrentDevicePosture = - mDeviceStateToPostureMap.get(state, DEVICE_POSTURE_UNKNOWN); + deviceStateManager.registerCallback(executor, state -> { + Assert.isMainThread(); + mCurrentDevicePosture = + mDeviceStateToPostureMap.get(state, DEVICE_POSTURE_UNKNOWN); - mListeners.forEach(l -> l.onPostureChanged(getDevicePosture())); - } - - @Override - public void onBaseStateChanged(int state) { - Assert.isMainThread(); - mCurrentBasePosture = mDeviceStateToPostureMap.get(state, DEVICE_POSTURE_UNKNOWN); - - if (useBaseState()) { - mListeners.forEach(l -> l.onPostureChanged(getDevicePosture())); - } - } + mListeners.forEach(l -> l.onPostureChanged(mCurrentDevicePosture)); }); } @@ -109,14 +93,6 @@ public class DevicePostureControllerImpl implements DevicePostureController { @Override public int getDevicePosture() { - if (useBaseState()) { - return mCurrentBasePosture; - } else { - return mCurrentDevicePosture; - } - } - - private boolean useBaseState() { - return mCurrentDevicePosture == COMMON_STATE_USE_BASE_STATE; + return mCurrentDevicePosture; } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt index 78f48bb511e5..75ae16ebab31 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt @@ -30,6 +30,10 @@ import com.android.systemui.qs.tiles.impl.flashlight.domain.FlashlightMapper import com.android.systemui.qs.tiles.impl.flashlight.domain.interactor.FlashlightTileDataInteractor import com.android.systemui.qs.tiles.impl.flashlight.domain.interactor.FlashlightTileUserActionInteractor import com.android.systemui.qs.tiles.impl.flashlight.domain.model.FlashlightTileModel +import com.android.systemui.qs.tiles.impl.location.domain.LocationTileMapper +import com.android.systemui.qs.tiles.impl.location.domain.interactor.LocationTileDataInteractor +import com.android.systemui.qs.tiles.impl.location.domain.interactor.LocationTileUserActionInteractor +import com.android.systemui.qs.tiles.impl.location.domain.model.LocationTileModel import com.android.systemui.qs.tiles.viewmodel.QSTileConfig import com.android.systemui.qs.tiles.viewmodel.QSTileUIConfig import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel @@ -54,8 +58,9 @@ interface PolicyModule { companion object { const val FLASHLIGHT_TILE_SPEC = "flashlight" + const val LOCATION_TILE_SPEC = "location" - /** Inject config */ + /** Inject flashlight config */ @Provides @IntoMap @StringKey(FLASHLIGHT_TILE_SPEC) @@ -86,6 +91,38 @@ interface PolicyModule { stateInteractor, mapper, ) + + /** Inject location config */ + @Provides + @IntoMap + @StringKey(LOCATION_TILE_SPEC) + fun provideLocationTileConfig(uiEventLogger: QsEventLogger): QSTileConfig = + QSTileConfig( + tileSpec = TileSpec.create(LOCATION_TILE_SPEC), + uiConfig = + QSTileUIConfig.Resource( + iconRes = R.drawable.qs_location_icon_off, + labelRes = R.string.quick_settings_location_label, + ), + instanceId = uiEventLogger.getNewInstanceId(), + ) + + /** Inject LocationTile into tileViewModelMap in QSModule */ + @Provides + @IntoMap + @StringKey(LOCATION_TILE_SPEC) + fun provideLocationTileViewModel( + factory: QSTileViewModelFactory.Static<LocationTileModel>, + mapper: LocationTileMapper, + stateInteractor: LocationTileDataInteractor, + userActionInteractor: LocationTileUserActionInteractor + ): QSTileViewModel = + factory.create( + TileSpec.create(LOCATION_TILE_SPEC), + userActionInteractor, + stateInteractor, + mapper, + ) } /** Inject FlashlightTile into tileMap in QSModule */ diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt index baac51385ba1..ba72b4c95a44 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt @@ -14,83 +14,56 @@ * limitations under the License. */ +@file:OptIn(ExperimentalCoroutinesApi::class) + package com.android.systemui.keyguard.ui.viewmodel import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import com.android.systemui.SysUITestComponent -import com.android.systemui.SysUITestModule import com.android.systemui.SysuiTestCase -import com.android.systemui.TestMocksModule -import com.android.systemui.collectLastValue -import com.android.systemui.collectValues -import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.flags.FakeFeatureFlagsClassicModule +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.coroutines.collectValues import com.android.systemui.flags.Flags -import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository -import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository +import com.android.systemui.flags.featureFlagsClassic +import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository +import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository +import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.StatusBarState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep -import com.android.systemui.runCurrent -import com.android.systemui.runTest -import com.android.systemui.shade.data.repository.FakeShadeRepository -import com.android.systemui.user.domain.UserDomainLayerModule +import com.android.systemui.kosmos.testScope +import com.android.systemui.shade.data.repository.shadeRepository +import com.android.systemui.testKosmos import com.google.common.collect.Range import com.google.common.truth.Truth.assertThat -import dagger.BindsInstance -import dagger.Component +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) class LockscreenToDreamingTransitionViewModelTest : SysuiTestCase() { - @SysUISingleton - @Component( - modules = - [ - SysUITestModule::class, - UserDomainLayerModule::class, - ] - ) - interface TestComponent : SysUITestComponent<LockscreenToDreamingTransitionViewModel> { - val repository: FakeKeyguardTransitionRepository - val keyguardRepository: FakeKeyguardRepository - val shadeRepository: FakeShadeRepository - - @Component.Factory - interface Factory { - fun create( - @BindsInstance test: SysuiTestCase, - featureFlags: FakeFeatureFlagsClassicModule, - mocks: TestMocksModule, - ): TestComponent - } - } - private fun TestComponent.shadeExpanded(expanded: Boolean) { - if (expanded) { - shadeRepository.setQsExpansion(1f) - } else { - keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD) - shadeRepository.setQsExpansion(0f) - shadeRepository.setLockscreenShadeExpansion(0f) + private val kosmos = + testKosmos().apply { + featureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) } } - } + private val testScope = kosmos.testScope + private val repository = kosmos.fakeKeyguardTransitionRepository + private val shadeRepository = kosmos.shadeRepository + private val keyguardRepository = kosmos.fakeKeyguardRepository + private val underTest = + LockscreenToDreamingTransitionViewModel( + interactor = kosmos.keyguardTransitionInteractor, + shadeDependentFlows = kosmos.shadeDependentFlows, + ) - private val testComponent: TestComponent = - DaggerLockscreenToDreamingTransitionViewModelTest_TestComponent.factory() - .create( - test = this, - featureFlags = - FakeFeatureFlagsClassicModule { set(Flags.FULL_SCREEN_USER_SWITCHER, true) }, - mocks = TestMocksModule(), - ) @Test fun lockscreenFadeOut() = - testComponent.runTest { + testScope.runTest { val values by collectValues(underTest.lockscreenAlpha) repository.sendTransitionSteps( steps = @@ -113,7 +86,7 @@ class LockscreenToDreamingTransitionViewModelTest : SysuiTestCase() { @Test fun lockscreenTranslationY() = - testComponent.runTest { + testScope.runTest { val pixels = 100 val values by collectValues(underTest.lockscreenTranslationY(pixels)) @@ -138,7 +111,7 @@ class LockscreenToDreamingTransitionViewModelTest : SysuiTestCase() { @Test fun deviceEntryParentViewAlpha_shadeExpanded() = - testComponent.runTest { + testScope.runTest { val values by collectValues(underTest.deviceEntryParentViewAlpha) shadeExpanded(true) runCurrent() @@ -162,7 +135,7 @@ class LockscreenToDreamingTransitionViewModelTest : SysuiTestCase() { @Test fun deviceEntryParentViewAlpha_shadeNotExpanded() = - testComponent.runTest { + testScope.runTest { val actual by collectLastValue(underTest.deviceEntryParentViewAlpha) shadeExpanded(false) runCurrent() @@ -194,4 +167,14 @@ class LockscreenToDreamingTransitionViewModelTest : SysuiTestCase() { ownerName = "LockscreenToDreamingTransitionViewModelTest" ) } + + private fun shadeExpanded(expanded: Boolean) { + if (expanded) { + shadeRepository.setQsExpansion(1f) + } else { + keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD) + shadeRepository.setQsExpansion(0f) + shadeRepository.setLockscreenShadeExpansion(0f) + } + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt index 29d8f082795a..3536d5c77c93 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt @@ -14,84 +14,56 @@ * limitations under the License. */ +@file:OptIn(ExperimentalCoroutinesApi::class) + package com.android.systemui.keyguard.ui.viewmodel import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import com.android.systemui.SysUITestComponent -import com.android.systemui.SysUITestModule import com.android.systemui.SysuiTestCase -import com.android.systemui.TestMocksModule -import com.android.systemui.collectLastValue -import com.android.systemui.collectValues -import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.flags.FakeFeatureFlagsClassicModule +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.coroutines.collectValues import com.android.systemui.flags.Flags -import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository -import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository +import com.android.systemui.flags.featureFlagsClassic +import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository +import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository +import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.StatusBarState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep -import com.android.systemui.runCurrent -import com.android.systemui.runTest -import com.android.systemui.shade.data.repository.FakeShadeRepository -import com.android.systemui.user.domain.UserDomainLayerModule +import com.android.systemui.kosmos.testScope +import com.android.systemui.shade.data.repository.shadeRepository +import com.android.systemui.testKosmos import com.google.common.collect.Range import com.google.common.truth.Truth.assertThat -import dagger.BindsInstance -import dagger.Component +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) class LockscreenToOccludedTransitionViewModelTest : SysuiTestCase() { - @SysUISingleton - @Component( - modules = - [ - SysUITestModule::class, - UserDomainLayerModule::class, - ] - ) - interface TestComponent : SysUITestComponent<LockscreenToOccludedTransitionViewModel> { - val repository: FakeKeyguardTransitionRepository - val keyguardRepository: FakeKeyguardRepository - val shadeRepository: FakeShadeRepository - - @Component.Factory - interface Factory { - fun create( - @BindsInstance test: SysuiTestCase, - featureFlags: FakeFeatureFlagsClassicModule, - mocks: TestMocksModule, - ): TestComponent - } - } - private fun TestComponent.shadeExpanded(expanded: Boolean) { - if (expanded) { - shadeRepository.setQsExpansion(1f) - } else { - keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD) - shadeRepository.setQsExpansion(0f) - shadeRepository.setLockscreenShadeExpansion(0f) + private val kosmos = + testKosmos().apply { + featureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) } } - } - - private val testComponent: TestComponent = - DaggerLockscreenToOccludedTransitionViewModelTest_TestComponent.factory() - .create( - test = this, - featureFlags = - FakeFeatureFlagsClassicModule { set(Flags.FULL_SCREEN_USER_SWITCHER, true) }, - mocks = TestMocksModule(), - ) + private val testScope = kosmos.testScope + private val repository = kosmos.fakeKeyguardTransitionRepository + private val shadeRepository = kosmos.shadeRepository + private val keyguardRepository = kosmos.fakeKeyguardRepository + private val underTest = + LockscreenToOccludedTransitionViewModel( + interactor = kosmos.keyguardTransitionInteractor, + shadeDependentFlows = kosmos.shadeDependentFlows, + ) @Test fun lockscreenFadeOut() = - testComponent.runTest { + testScope.runTest { val values by collectValues(underTest.lockscreenAlpha) repository.sendTransitionSteps( steps = @@ -113,7 +85,7 @@ class LockscreenToOccludedTransitionViewModelTest : SysuiTestCase() { @Test fun lockscreenTranslationY() = - testComponent.runTest { + testScope.runTest { val pixels = 100 val values by collectValues(underTest.lockscreenTranslationY(pixels)) repository.sendTransitionSteps( @@ -133,7 +105,7 @@ class LockscreenToOccludedTransitionViewModelTest : SysuiTestCase() { @Test fun lockscreenTranslationYIsCanceled() = - testComponent.runTest { + testScope.runTest { val pixels = 100 val values by collectValues(underTest.lockscreenTranslationY(pixels)) repository.sendTransitionSteps( @@ -155,7 +127,7 @@ class LockscreenToOccludedTransitionViewModelTest : SysuiTestCase() { @Test fun deviceEntryParentViewAlpha_shadeExpanded() = - testComponent.runTest { + testScope.runTest { val values by collectValues(underTest.deviceEntryParentViewAlpha) shadeExpanded(true) runCurrent() @@ -176,7 +148,7 @@ class LockscreenToOccludedTransitionViewModelTest : SysuiTestCase() { @Test fun deviceEntryParentViewAlpha_shadeNotExpanded() = - testComponent.runTest { + testScope.runTest { val actual by collectLastValue(underTest.deviceEntryParentViewAlpha) shadeExpanded(false) runCurrent() @@ -208,4 +180,14 @@ class LockscreenToOccludedTransitionViewModelTest : SysuiTestCase() { ownerName = "LockscreenToOccludedTransitionViewModelTest" ) } + + private fun shadeExpanded(expanded: Boolean) { + if (expanded) { + shadeRepository.setQsExpansion(1f) + } else { + keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD) + shadeRepository.setQsExpansion(0f) + shadeRepository.setLockscreenShadeExpansion(0f) + } + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerTest.kt index f4293f035cd1..50f0eb4eca2c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerTest.kt @@ -94,6 +94,7 @@ class KeyguardMediaControllerTest : SysuiTestCase() { fakeHandler, configurationController, ResourcesSplitShadeStateController(), + mock<KeyguardMediaControllerLogger>(), mock<DumpManager>() ) keyguardMediaController.attachSinglePaneContainer(mediaContainerView) @@ -104,7 +105,7 @@ class KeyguardMediaControllerTest : SysuiTestCase() { fun testHiddenWhenHostIsHidden() { whenever(mediaHost.visible).thenReturn(false) - keyguardMediaController.refreshMediaPosition() + keyguardMediaController.refreshMediaPosition(TEST_REASON) assertThat(mediaContainerView.visibility).isEqualTo(GONE) } @@ -118,7 +119,7 @@ class KeyguardMediaControllerTest : SysuiTestCase() { private fun testStateVisibility(state: Int, visibility: Int) { whenever(statusBarStateController.state).thenReturn(state) - keyguardMediaController.refreshMediaPosition() + keyguardMediaController.refreshMediaPosition(TEST_REASON) assertThat(mediaContainerView.visibility).isEqualTo(visibility) } @@ -126,7 +127,7 @@ class KeyguardMediaControllerTest : SysuiTestCase() { fun testHiddenOnKeyguard_whenMediaOnLockScreenDisabled() { settings.putInt(Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN, 0) - keyguardMediaController.refreshMediaPosition() + keyguardMediaController.refreshMediaPosition(TEST_REASON) assertThat(mediaContainerView.visibility).isEqualTo(GONE) } @@ -135,7 +136,7 @@ class KeyguardMediaControllerTest : SysuiTestCase() { fun testAvailableOnKeyguard_whenMediaOnLockScreenEnabled() { settings.putInt(Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN, 1) - keyguardMediaController.refreshMediaPosition() + keyguardMediaController.refreshMediaPosition(TEST_REASON) assertThat(mediaContainerView.visibility).isEqualTo(VISIBLE) } @@ -234,4 +235,8 @@ class KeyguardMediaControllerTest : SysuiTestCase() { whenever(statusBarStateController.isDozing).thenReturn(true) statusBarStateListener.onDozingChanged(true) } + + private companion object { + private const val TEST_REASON = "test reason" + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt index 3d7cff44322b..ac7c2aa98065 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt @@ -15,36 +15,36 @@ * */ +@file:OptIn(ExperimentalCoroutinesApi::class) + package com.android.systemui.statusbar.notification.stack.ui.viewmodel import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import com.android.systemui.SysUITestComponent -import com.android.systemui.SysUITestModule import com.android.systemui.SysuiTestCase -import com.android.systemui.TestMocksModule -import com.android.systemui.collectLastValue -import com.android.systemui.common.shared.model.SharedNotificationContainerPosition -import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository -import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.flags.FakeFeatureFlagsClassicModule +import com.android.systemui.common.shared.model.NotificationContainerBounds +import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository +import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.Flags -import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository -import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository -import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor +import com.android.systemui.flags.featureFlagsClassic +import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository +import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository +import com.android.systemui.keyguard.domain.interactor.keyguardInteractor import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.StatusBarState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep +import com.android.systemui.keyguard.ui.viewmodel.keyguardRootViewModel +import com.android.systemui.kosmos.testScope import com.android.systemui.res.R -import com.android.systemui.runCurrent -import com.android.systemui.runTest -import com.android.systemui.shade.data.repository.FakeShadeRepository -import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor -import com.android.systemui.user.domain.UserDomainLayerModule +import com.android.systemui.shade.data.repository.shadeRepository +import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor +import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat -import dagger.BindsInstance -import dagger.Component +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -52,45 +52,29 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class SharedNotificationContainerViewModelTest : SysuiTestCase() { - @SysUISingleton - @Component( - modules = - [ - SysUITestModule::class, - UserDomainLayerModule::class, - ] - ) - interface TestComponent : SysUITestComponent<SharedNotificationContainerViewModel> { - - val configurationRepository: FakeConfigurationRepository - val keyguardRepository: FakeKeyguardRepository - val keyguardInteractor: KeyguardInteractor - val keyguardTransitionRepository: FakeKeyguardTransitionRepository - val shadeRepository: FakeShadeRepository - val sharedNotificationContainerInteractor: SharedNotificationContainerInteractor - - @Component.Factory - interface Factory { - fun create( - @BindsInstance test: SysuiTestCase, - featureFlags: FakeFeatureFlagsClassicModule, - mocks: TestMocksModule, - ): TestComponent + val kosmos = + testKosmos().apply { + featureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) } } + val testScope = kosmos.testScope + val configurationRepository = kosmos.fakeConfigurationRepository + val keyguardRepository = kosmos.fakeKeyguardRepository + val keyguardInteractor = kosmos.keyguardInteractor + val keyguardRootViewModel = kosmos.keyguardRootViewModel + val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository + val shadeRepository = kosmos.shadeRepository + val sharedNotificationContainerInteractor = kosmos.sharedNotificationContainerInteractor + + val underTest = kosmos.sharedNotificationContainerViewModel + + @Before + fun setUp() { + overrideResource(R.bool.config_use_split_notification_shade, false) } - private val testComponent: TestComponent = - DaggerSharedNotificationContainerViewModelTest_TestComponent.factory() - .create( - test = this, - featureFlags = - FakeFeatureFlagsClassicModule { set(Flags.FULL_SCREEN_USER_SWITCHER, true) }, - mocks = TestMocksModule(), - ) - @Test fun validateMarginStartInSplitShade() = - testComponent.runTest { + testScope.runTest { overrideResource(R.bool.config_use_split_notification_shade, true) overrideResource(R.dimen.notification_panel_margin_horizontal, 20) @@ -103,7 +87,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { @Test fun validateMarginStart() = - testComponent.runTest { + testScope.runTest { overrideResource(R.bool.config_use_split_notification_shade, false) overrideResource(R.dimen.notification_panel_margin_horizontal, 20) @@ -116,7 +100,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { @Test fun validateMarginEnd() = - testComponent.runTest { + testScope.runTest { overrideResource(R.dimen.notification_panel_margin_horizontal, 50) val dimens by collectLastValue(underTest.configurationBasedDimensions) @@ -128,7 +112,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { @Test fun validateMarginBottom() = - testComponent.runTest { + testScope.runTest { overrideResource(R.dimen.notification_panel_margin_bottom, 50) val dimens by collectLastValue(underTest.configurationBasedDimensions) @@ -140,7 +124,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { @Test fun validateMarginTopWithLargeScreenHeader() = - testComponent.runTest { + testScope.runTest { overrideResource(R.bool.config_use_large_screen_shade_header, true) overrideResource(R.dimen.large_screen_shade_header_height, 50) overrideResource(R.dimen.notification_panel_margin_top, 0) @@ -154,7 +138,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { @Test fun validateMarginTop() = - testComponent.runTest { + testScope.runTest { overrideResource(R.bool.config_use_large_screen_shade_header, false) overrideResource(R.dimen.large_screen_shade_header_height, 50) overrideResource(R.dimen.notification_panel_margin_top, 0) @@ -168,7 +152,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { @Test fun isOnLockscreen() = - testComponent.runTest { + testScope.runTest { val isOnLockscreen by collectLastValue(underTest.isOnLockscreen) keyguardTransitionRepository.sendTransitionSteps( @@ -206,7 +190,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { @Test fun isOnLockscreenWithoutShade() = - testComponent.runTest { + testScope.runTest { val isOnLockscreenWithoutShade by collectLastValue(underTest.isOnLockscreenWithoutShade) // First on AOD @@ -242,8 +226,8 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { @Test fun positionOnLockscreenNotInSplitShade() = - testComponent.runTest { - val position by collectLastValue(underTest.position) + testScope.runTest { + val position by collectLastValue(underTest.bounds) // When not in split shade overrideResource(R.bool.config_use_split_notification_shade, false) @@ -253,18 +237,17 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { // Start on lockscreen showLockscreen() - keyguardInteractor.setSharedNotificationContainerPosition( - SharedNotificationContainerPosition(top = 1f, bottom = 2f) + keyguardInteractor.setNotificationContainerBounds( + NotificationContainerBounds(top = 1f, bottom = 2f) ) - assertThat(position) - .isEqualTo(SharedNotificationContainerPosition(top = 1f, bottom = 2f)) + assertThat(position).isEqualTo(NotificationContainerBounds(top = 1f, bottom = 2f)) } @Test fun positionOnLockscreenInSplitShade() = - testComponent.runTest { - val position by collectLastValue(underTest.position) + testScope.runTest { + val position by collectLastValue(underTest.bounds) // When in split shade overrideResource(R.bool.config_use_split_notification_shade, true) @@ -274,20 +257,19 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { // Start on lockscreen showLockscreen() - keyguardInteractor.setSharedNotificationContainerPosition( - SharedNotificationContainerPosition(top = 1f, bottom = 2f) + keyguardInteractor.setNotificationContainerBounds( + NotificationContainerBounds(top = 1f, bottom = 2f) ) runCurrent() // Top should be overridden to 0f - assertThat(position) - .isEqualTo(SharedNotificationContainerPosition(top = 0f, bottom = 2f)) + assertThat(position).isEqualTo(NotificationContainerBounds(top = 0f, bottom = 2f)) } @Test - fun positionOnShade() = - testComponent.runTest { - val position by collectLastValue(underTest.position) + fun boundsOnShade() = + testScope.runTest { + val bounds by collectLastValue(underTest.bounds) // Start on lockscreen with shade expanded showLockscreenWithShadeExpanded() @@ -295,16 +277,14 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { // When not in split shade sharedNotificationContainerInteractor.setTopPosition(10f) - assertThat(position) - .isEqualTo( - SharedNotificationContainerPosition(top = 10f, bottom = 0f, animate = true) - ) + assertThat(bounds) + .isEqualTo(NotificationContainerBounds(top = 10f, bottom = 0f, isAnimated = true)) } @Test - fun positionOnQS() = - testComponent.runTest { - val position by collectLastValue(underTest.position) + fun boundsOnQS() = + testScope.runTest { + val bounds by collectLastValue(underTest.bounds) // Start on lockscreen with shade expanded showLockscreenWithQSExpanded() @@ -312,15 +292,13 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { // When not in split shade sharedNotificationContainerInteractor.setTopPosition(10f) - assertThat(position) - .isEqualTo( - SharedNotificationContainerPosition(top = 10f, bottom = 0f, animate = false) - ) + assertThat(bounds) + .isEqualTo(NotificationContainerBounds(top = 10f, bottom = 0f, isAnimated = false)) } @Test fun maxNotificationsOnLockscreen() = - testComponent.runTest { + testScope.runTest { var notificationCount = 10 val maxNotifications by collectLastValue(underTest.getMaxNotifications { notificationCount }) @@ -329,8 +307,8 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { overrideResource(R.bool.config_use_split_notification_shade, false) configurationRepository.onAnyConfigurationChange() - keyguardInteractor.setSharedNotificationContainerPosition( - SharedNotificationContainerPosition(top = 1f, bottom = 2f) + keyguardInteractor.setNotificationContainerBounds( + NotificationContainerBounds(top = 1f, bottom = 2f) ) assertThat(maxNotifications).isEqualTo(10) @@ -343,7 +321,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { @Test fun maxNotificationsOnLockscreen_DoesNotUpdateWhenUserInteracting() = - testComponent.runTest { + testScope.runTest { var notificationCount = 10 val maxNotifications by collectLastValue(underTest.getMaxNotifications { notificationCount }) @@ -352,8 +330,8 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { overrideResource(R.bool.config_use_split_notification_shade, false) configurationRepository.onAnyConfigurationChange() - keyguardInteractor.setSharedNotificationContainerPosition( - SharedNotificationContainerPosition(top = 1f, bottom = 2f) + keyguardInteractor.setNotificationContainerBounds( + NotificationContainerBounds(top = 1f, bottom = 2f) ) assertThat(maxNotifications).isEqualTo(10) @@ -379,7 +357,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { @Test fun maxNotificationsOnShade() = - testComponent.runTest { + testScope.runTest { val maxNotifications by collectLastValue(underTest.getMaxNotifications { 10 }) // Show lockscreen with shade expanded @@ -387,15 +365,26 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { overrideResource(R.bool.config_use_split_notification_shade, false) configurationRepository.onAnyConfigurationChange() - keyguardInteractor.setSharedNotificationContainerPosition( - SharedNotificationContainerPosition(top = 1f, bottom = 2f) + keyguardInteractor.setNotificationContainerBounds( + NotificationContainerBounds(top = 1f, bottom = 2f) ) // -1 means No Limit assertThat(maxNotifications).isEqualTo(-1) } - private suspend fun TestComponent.showLockscreen() { + @Test + fun updateBounds_fromKeyguardRoot() = + testScope.runTest { + val bounds by collectLastValue(underTest.bounds) + + val top = 123f + val bottom = 456f + keyguardRootViewModel.onNotificationContainerBoundsChanged(top, bottom) + assertThat(bounds).isEqualTo(NotificationContainerBounds(top, bottom)) + } + + private suspend fun showLockscreen() { shadeRepository.setLockscreenShadeExpansion(0f) shadeRepository.setQsExpansion(0f) keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD) @@ -406,7 +395,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { ) } - private suspend fun TestComponent.showLockscreenWithShadeExpanded() { + private suspend fun showLockscreenWithShadeExpanded() { shadeRepository.setLockscreenShadeExpansion(1f) shadeRepository.setQsExpansion(0f) keyguardRepository.setStatusBarState(StatusBarState.SHADE_LOCKED) @@ -417,7 +406,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { ) } - private suspend fun TestComponent.showLockscreenWithQSExpanded() { + private suspend fun showLockscreenWithQSExpanded() { shadeRepository.setLockscreenShadeExpansion(0f) shadeRepository.setQsExpansion(1f) keyguardRepository.setStatusBarState(StatusBarState.SHADE_LOCKED) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DevicePostureControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DevicePostureControllerImplTest.kt deleted file mode 100644 index ce471705ed85..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DevicePostureControllerImplTest.kt +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.statusbar.policy - -import android.hardware.devicestate.DeviceStateManager -import android.testing.AndroidTestingRunner -import android.testing.TestableLooper -import android.testing.TestableResources -import androidx.test.filters.SmallTest -import com.android.systemui.SysuiTestCase -import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_CLOSED -import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_FLIPPED -import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_HALF_OPENED -import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_OPENED -import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_UNKNOWN -import com.android.systemui.statusbar.policy.DevicePostureController.SUPPORTED_POSTURES_SIZE -import com.android.systemui.util.concurrency.FakeExecutor -import com.android.systemui.util.mockito.eq -import com.android.systemui.util.time.FakeSystemClock -import com.google.common.truth.Truth.assertThat -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.ArgumentCaptor -import org.mockito.Captor -import org.mockito.Mock -import org.mockito.Mockito.verify -import org.mockito.MockitoAnnotations - -@SmallTest -@RunWith(AndroidTestingRunner::class) -@TestableLooper.RunWithLooper(setAsMainLooper = true) -class DevicePostureControllerImplTest : SysuiTestCase() { - private val useBaseStateDeviceState = SUPPORTED_POSTURES_SIZE - private val deviceStateToPostureMapping = - arrayOf( - "$DEVICE_POSTURE_UNKNOWN:$DEVICE_POSTURE_UNKNOWN", - "$DEVICE_POSTURE_CLOSED:$DEVICE_POSTURE_CLOSED", - "$DEVICE_POSTURE_HALF_OPENED:$DEVICE_POSTURE_HALF_OPENED", - "$DEVICE_POSTURE_OPENED:$DEVICE_POSTURE_OPENED", - "$DEVICE_POSTURE_FLIPPED:$DEVICE_POSTURE_FLIPPED", - "$useBaseStateDeviceState:1000" - ) - @Mock private lateinit var deviceStateManager: DeviceStateManager - @Captor - private lateinit var deviceStateCallback: ArgumentCaptor<DeviceStateManager.DeviceStateCallback> - - private lateinit var mainExecutor: FakeExecutor - private lateinit var testableResources: TestableResources - private lateinit var underTest: DevicePostureControllerImpl - - @Before - fun setUp() { - MockitoAnnotations.initMocks(this) - mainExecutor = FakeExecutor(FakeSystemClock()) - testableResources = context.getOrCreateTestableResources() - testableResources.addOverride( - com.android.internal.R.array.config_device_state_postures, - deviceStateToPostureMapping - ) - underTest = - DevicePostureControllerImpl( - context, - deviceStateManager, - mainExecutor, - ) - verifyRegistersForDeviceStateCallback() - } - - @Test - fun testPostureChanged_updates() { - var posture = -1 - underTest.addCallback { posture = it } - - deviceStateCallback.value.onStateChanged(DEVICE_POSTURE_UNKNOWN) - assertThat(posture).isEqualTo(DEVICE_POSTURE_UNKNOWN) - - deviceStateCallback.value.onStateChanged(DEVICE_POSTURE_CLOSED) - assertThat(posture).isEqualTo(DEVICE_POSTURE_CLOSED) - - deviceStateCallback.value.onStateChanged(DEVICE_POSTURE_HALF_OPENED) - assertThat(posture).isEqualTo(DEVICE_POSTURE_HALF_OPENED) - - deviceStateCallback.value.onStateChanged(DEVICE_POSTURE_OPENED) - assertThat(posture).isEqualTo(DEVICE_POSTURE_OPENED) - - deviceStateCallback.value.onStateChanged(DEVICE_POSTURE_FLIPPED) - assertThat(posture).isEqualTo(DEVICE_POSTURE_FLIPPED) - } - - @Test - fun testPostureChanged_useBaseUpdate() { - var posture = -1 - underTest.addCallback { posture = it } - - deviceStateCallback.value.onStateChanged(DEVICE_POSTURE_HALF_OPENED) - assertThat(posture).isEqualTo(DEVICE_POSTURE_HALF_OPENED) - - // base state change doesn't change the posture - deviceStateCallback.value.onBaseStateChanged(DEVICE_POSTURE_CLOSED) - assertThat(posture).isEqualTo(DEVICE_POSTURE_HALF_OPENED) - - // WHEN the display state maps to using the base state, then posture updates - deviceStateCallback.value.onStateChanged(useBaseStateDeviceState) - assertThat(posture).isEqualTo(DEVICE_POSTURE_CLOSED) - } - - @Test - fun baseStateChanges_doesNotUpdatePosture() { - var numPostureChanges = 0 - underTest.addCallback { numPostureChanges++ } - - deviceStateCallback.value.onStateChanged(DEVICE_POSTURE_HALF_OPENED) - assertThat(numPostureChanges).isEqualTo(1) - - // base state changes doesn't send another posture update since the device state isn't - // useBaseStateDeviceState - deviceStateCallback.value.onBaseStateChanged(DEVICE_POSTURE_CLOSED) - deviceStateCallback.value.onBaseStateChanged(DEVICE_POSTURE_HALF_OPENED) - deviceStateCallback.value.onBaseStateChanged(DEVICE_POSTURE_FLIPPED) - deviceStateCallback.value.onBaseStateChanged(DEVICE_POSTURE_OPENED) - deviceStateCallback.value.onBaseStateChanged(DEVICE_POSTURE_UNKNOWN) - assertThat(numPostureChanges).isEqualTo(1) - } - - private fun verifyRegistersForDeviceStateCallback() { - verify(deviceStateManager).registerCallback(eq(mainExecutor), deviceStateCallback.capture()) - } -} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCaseExt.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCaseExt.kt new file mode 100644 index 000000000000..46259a6964cd --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCaseExt.kt @@ -0,0 +1,22 @@ +/* + * 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 + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testCase + +fun SysuiTestCase.testKosmos(): Kosmos = Kosmos().apply { testCase = this@testKosmos } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepositoryKosmos.kt new file mode 100644 index 000000000000..8702e00b1e97 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepositoryKosmos.kt @@ -0,0 +1,22 @@ +/* + * 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.biometrics.data.repository + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.Kosmos.Fixture + +val Kosmos.fingerprintPropertyRepository by Fixture { FakeFingerprintPropertyRepository() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryUdfpsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryUdfpsInteractorKosmos.kt new file mode 100644 index 000000000000..b04161a7809f --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryUdfpsInteractorKosmos.kt @@ -0,0 +1,34 @@ +/* + * 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. + */ + +@file:OptIn(ExperimentalCoroutinesApi::class) + +package com.android.systemui.deviceentry.domain.interactor + +import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository +import com.android.systemui.keyguard.data.repository.biometricSettingsRepository +import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.Kosmos.Fixture +import kotlinx.coroutines.ExperimentalCoroutinesApi + +val Kosmos.deviceEntryUdfpsInteractor by Fixture { + DeviceEntryUdfpsInteractor( + fingerprintPropertyRepository = fingerprintPropertyRepository, + fingerprintAuthRepository = deviceEntryFingerprintAuthRepository, + biometricSettingsRepository = biometricSettingsRepository, + ) +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/doze/util/BurnInHelperWrapperKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/doze/util/BurnInHelperWrapperKosmos.kt new file mode 100644 index 000000000000..0f7945fb9846 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/doze/util/BurnInHelperWrapperKosmos.kt @@ -0,0 +1,22 @@ +/* + * 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.doze.util + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.Kosmos.Fixture + +val Kosmos.burnInHelperWrapper by Fixture { BurnInHelperWrapper() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryKosmos.kt new file mode 100644 index 000000000000..45d39b0d4bc4 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryKosmos.kt @@ -0,0 +1,22 @@ +/* + * 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.keyguard.data.repository + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.Kosmos.Fixture + +val Kosmos.biometricSettingsRepository by Fixture { FakeBiometricSettingsRepository() } diff --git a/packages/SystemUI/src/com/android/systemui/common/shared/model/SharedNotificationContainerPosition.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryKosmos.kt index 48d3fe000ff8..6437ef3e50f4 100644 --- a/packages/SystemUI/src/com/android/systemui/common/shared/model/SharedNotificationContainerPosition.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryKosmos.kt @@ -14,15 +14,11 @@ * limitations under the License. */ -package com.android.systemui.common.shared.model +package com.android.systemui.keyguard.data.repository -/** Positioning info for the shared notification container */ -data class SharedNotificationContainerPosition( - val top: Float = 0f, - val bottom: Float = 0f, +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.Kosmos.Fixture - /** Whether any modifications to top/bottom are smoothly animated */ - val animate: Boolean = false, -) { - val height: Float = bottom - top +val Kosmos.deviceEntryFingerprintAuthRepository by Fixture { + FakeDeviceEntryFingerprintAuthRepository() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorKosmos.kt new file mode 100644 index 000000000000..b0d941dc6c24 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorKosmos.kt @@ -0,0 +1,37 @@ +/* + * 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. + */ + +@file:OptIn(ExperimentalCoroutinesApi::class) + +package com.android.systemui.keyguard.domain.interactor + +import android.content.applicationContext +import com.android.systemui.common.ui.data.repository.configurationRepository +import com.android.systemui.doze.util.burnInHelperWrapper +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.Kosmos.Fixture +import com.android.systemui.kosmos.applicationCoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi + +val Kosmos.burnInInteractor by Fixture { + BurnInInteractor( + context = applicationContext, + burnInHelperWrapper = burnInHelperWrapper, + scope = applicationCoroutineScope, + configurationRepository = configurationRepository, + keyguardInteractor = keyguardInteractor, + ) +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelKosmos.kt new file mode 100644 index 000000000000..a31ab3ee1fc4 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelKosmos.kt @@ -0,0 +1,32 @@ +/* + * 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. + */ + +@file:OptIn(ExperimentalCoroutinesApi::class) + +package com.android.systemui.keyguard.ui.viewmodel + +import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsInteractor +import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.Kosmos.Fixture +import kotlinx.coroutines.ExperimentalCoroutinesApi + +val Kosmos.aodToLockscreenTransitionViewModel by Fixture { + AodToLockscreenTransitionViewModel( + interactor = keyguardTransitionInteractor, + deviceEntryUdfpsInteractor = deviceEntryUdfpsInteractor, + ) +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelKosmos.kt new file mode 100644 index 000000000000..5db95cf3ebc5 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelKosmos.kt @@ -0,0 +1,32 @@ +/* + * 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. + */ + +@file:OptIn(ExperimentalCoroutinesApi::class) + +package com.android.systemui.keyguard.ui.viewmodel + +import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsInteractor +import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.Kosmos.Fixture +import kotlinx.coroutines.ExperimentalCoroutinesApi + +val Kosmos.goneToAodTransitionViewModel by Fixture { + GoneToAodTransitionViewModel( + interactor = keyguardTransitionInteractor, + deviceEntryUdfpsInteractor = deviceEntryUdfpsInteractor + ) +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt new file mode 100644 index 000000000000..663b8450e690 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt @@ -0,0 +1,46 @@ +/* + * 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. + */ + +@file:OptIn(ExperimentalCoroutinesApi::class) + +package com.android.systemui.keyguard.ui.viewmodel + +import android.content.applicationContext +import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor +import com.android.systemui.keyguard.domain.interactor.burnInInteractor +import com.android.systemui.keyguard.domain.interactor.keyguardInteractor +import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.Kosmos.Fixture +import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationsKeyguardInteractor +import com.android.systemui.statusbar.phone.dozeParameters +import com.android.systemui.statusbar.phone.screenOffAnimationController +import kotlinx.coroutines.ExperimentalCoroutinesApi + +val Kosmos.keyguardRootViewModel by Fixture { + KeyguardRootViewModel( + context = applicationContext, + deviceEntryInteractor = deviceEntryInteractor, + dozeParameters = dozeParameters, + keyguardInteractor = keyguardInteractor, + keyguardTransitionInteractor = keyguardTransitionInteractor, + notificationsKeyguardInteractor = notificationsKeyguardInteractor, + burnInInteractor = burnInInteractor, + goneToAodTransitionViewModel = goneToAodTransitionViewModel, + aodToLockscreenTransitionViewModel = aodToLockscreenTransitionViewModel, + screenOffAnimationController = screenOffAnimationController, + ) +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/ShadeDependentFlowsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/ShadeDependentFlowsKosmos.kt new file mode 100644 index 000000000000..f533bcad755e --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/ShadeDependentFlowsKosmos.kt @@ -0,0 +1,29 @@ +/* + * 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.keyguard.ui.viewmodel + +import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.Kosmos.Fixture +import com.android.systemui.shade.domain.interactor.shadeInteractor + +val Kosmos.shadeDependentFlows by Fixture { + ShadeDependentFlows( + transitionInteractor = keyguardTransitionInteractor, + shadeInteractor = shadeInteractor, + ) +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/location/LocationTileKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/location/LocationTileKosmos.kt new file mode 100644 index 000000000000..f4c7ca7a21f7 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/location/LocationTileKosmos.kt @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.impl.location + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.qs.qsEventLogger +import com.android.systemui.statusbar.policy.PolicyModule + +val Kosmos.qsLocationTileConfig by + Kosmos.Fixture { PolicyModule.provideLocationTileConfig(qsEventLogger) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsKosmos.kt index a3ceef021c59..c2cdbed21abe 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsKosmos.kt @@ -18,4 +18,4 @@ package com.android.systemui.scene.shared.flag import com.android.systemui.kosmos.Kosmos -val Kosmos.sceneContainerFlags by Kosmos.Fixture { FakeSceneContainerFlags() } +var Kosmos.sceneContainerFlags by Kosmos.Fixture { FakeSceneContainerFlags() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepositoryKosmos.kt new file mode 100644 index 000000000000..407390296a6c --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepositoryKosmos.kt @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.stack.data.repository + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.Kosmos.Fixture + +val Kosmos.notificationStackAppearanceRepository by Fixture { + NotificationStackAppearanceRepository() +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorKosmos.kt new file mode 100644 index 000000000000..546a1e019c6b --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorKosmos.kt @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.stack.domain.interactor + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.Kosmos.Fixture +import com.android.systemui.statusbar.notification.stack.data.repository.notificationStackAppearanceRepository + +val Kosmos.notificationStackAppearanceInteractor by Fixture { + NotificationStackAppearanceInteractor( + repository = notificationStackAppearanceRepository, + ) +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationsKeyguardInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationsKeyguardInteractorKosmos.kt new file mode 100644 index 000000000000..61a38b864c40 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationsKeyguardInteractorKosmos.kt @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.stack.domain.interactor + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.Kosmos.Fixture +import com.android.systemui.statusbar.notification.data.repository.notificationsKeyguardViewStateRepository +import com.android.systemui.statusbar.notification.domain.interactor.NotificationsKeyguardInteractor + +val Kosmos.notificationsKeyguardInteractor by Fixture { + NotificationsKeyguardInteractor( + repository = notificationsKeyguardViewStateRepository, + ) +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModelKosmos.kt new file mode 100644 index 000000000000..f2f3a5a1ad72 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModelKosmos.kt @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.stack.ui.viewmodel + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.Kosmos.Fixture +import com.android.systemui.shade.domain.interactor.shadeInteractor +import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackAppearanceInteractor + +val Kosmos.notificationStackAppearanceViewModel by Fixture { + NotificationStackAppearanceViewModel( + stackAppearanceInteractor = notificationStackAppearanceInteractor, + shadeInteractor = shadeInteractor, + ) +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt new file mode 100644 index 000000000000..0dbade76979c --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.stack.ui.viewmodel + +import com.android.systemui.flags.featureFlagsClassic +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.Kosmos.Fixture +import com.android.systemui.scene.shared.flag.sceneContainerFlags +import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackAppearanceInteractor + +val Kosmos.notificationsPlaceholderViewModel by Fixture { + NotificationsPlaceholderViewModel( + interactor = notificationStackAppearanceInteractor, + flags = sceneContainerFlags, + featureFlags = featureFlagsClassic, + ) +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt new file mode 100644 index 000000000000..c17083c5fb1c --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.stack.ui.viewmodel + +import com.android.systemui.keyguard.domain.interactor.keyguardInteractor +import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.Kosmos.Fixture +import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.shade.domain.interactor.shadeInteractor +import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor + +val Kosmos.sharedNotificationContainerViewModel by Fixture { + SharedNotificationContainerViewModel( + interactor = sharedNotificationContainerInteractor, + applicationScope = applicationCoroutineScope, + keyguardInteractor = keyguardInteractor, + keyguardTransitionInteractor = keyguardTransitionInteractor, + shadeInteractor = shadeInteractor, + ) +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeLocationController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeLocationController.java index 838a2739f0b0..3c6327514f24 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeLocationController.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeLocationController.java @@ -19,8 +19,14 @@ import android.testing.LeakCheck; import com.android.systemui.statusbar.policy.LocationController; import com.android.systemui.statusbar.policy.LocationController.LocationChangeCallback; +import java.util.ArrayList; +import java.util.List; + public class FakeLocationController extends BaseLeakChecker<LocationChangeCallback> implements LocationController { + + private final List<LocationChangeCallback> mCallbacks = new ArrayList<>(); + public FakeLocationController(LeakCheck test) { super(test, "location"); } @@ -37,6 +43,19 @@ public class FakeLocationController extends BaseLeakChecker<LocationChangeCallba @Override public boolean setLocationEnabled(boolean enabled) { + mCallbacks.forEach(callback -> callback.onLocationSettingsChanged(enabled)); return false; } + + @Override + public void addCallback(LocationChangeCallback callback) { + super.addCallback(callback); + mCallbacks.add(callback); + } + + @Override + public void removeCallback(LocationChangeCallback callback) { + super.removeCallback(callback); + mCallbacks.remove(callback); + } } diff --git a/services/core/Android.bp b/services/core/Android.bp index 49457fbb94f5..3323d0ba64dd 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -125,6 +125,9 @@ java_library_static { "java/com/android/server/am/EventLogTags.logtags", "java/com/android/server/wm/EventLogTags.logtags", "java/com/android/server/policy/EventLogTags.logtags", + + // Java/AIDL sources to be moved out to CrashRecovery module + ":services-crashrecovery-sources", ], libs: [ diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java index 7e4cf4f35132..898b69310fe1 100644 --- a/services/core/java/android/content/pm/PackageManagerInternal.java +++ b/services/core/java/android/content/pm/PackageManagerInternal.java @@ -48,6 +48,7 @@ import com.android.internal.util.function.pooled.PooledLambda; import com.android.permission.persistence.RuntimePermissionsState; import com.android.server.pm.Installer.LegacyDexoptDisabledException; import com.android.server.pm.KnownPackages; +import com.android.server.pm.PackageArchiver; import com.android.server.pm.PackageList; import com.android.server.pm.PackageSetting; import com.android.server.pm.dex.DynamicCodeLogger; @@ -1442,4 +1443,10 @@ public abstract class PackageManagerInternal { */ public abstract void sendPackageDataClearedBroadcast(@NonNull String packageName, int uid, int userId, boolean isRestore, boolean isInstantApp); + + /** + * Returns an instance of {@link PackageArchiver} to be used for archiving related operations. + */ + @NonNull + public abstract PackageArchiver getPackageArchiver(); } diff --git a/services/core/java/com/android/server/PinnerService.java b/services/core/java/com/android/server/PinnerService.java index ce1a8756a849..0e7b4aa32b44 100644 --- a/services/core/java/com/android/server/PinnerService.java +++ b/services/core/java/com/android/server/PinnerService.java @@ -94,6 +94,7 @@ import sun.misc.Unsafe; * <p>Files to pin are specified in the config_defaultPinnerServiceFiles * overlay.</p> * <p>Pin the default camera application if specified in config_pinnerCameraApp.</p> + * <p>(Optional) Pin experimental carveout regions based on DeviceConfig flags.</p> */ public final class PinnerService extends SystemService { private static final boolean DEBUG = false; diff --git a/services/core/java/com/android/server/TEST_MAPPING b/services/core/java/com/android/server/TEST_MAPPING index 80c4c58cea97..708da19232e1 100644 --- a/services/core/java/com/android/server/TEST_MAPPING +++ b/services/core/java/com/android/server/TEST_MAPPING @@ -71,6 +71,18 @@ "file_patterns": ["BinaryTransparencyService\\.java"] }, { + "name": "FrameworksServicesTests", + "options": [ + { + "include-filter": "com.android.server.PinnerServiceTest" + }, + { + "exclude-annotation": "org.junit.Ignore" + } + ], + "file_patterns": ["PinnerService\\.java"] + }, + { "name": "BinaryTransparencyHostTest", "file_patterns": ["BinaryTransparencyService\\.java"] }, @@ -139,6 +151,18 @@ }, { "name": "CtsSuspendAppsTestCases" + }, + { + "name": "FrameworksServicesTests", + "options": [ + { + "include-filter": "com.android.server.PinnerServiceTest" + }, + { + "exclude-annotation": "org.junit.Ignore" + } + ], + "file_patterns": ["PinnerService\\.java"] } ] } diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index d46d55977d2f..3caff11e0a3a 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -3349,9 +3349,7 @@ final class InstallPackageHelper { if (disabledPs == null) { logCriticalInfo(Log.WARN, "System package " + packageName + " no longer exists; its data will be wiped"); - mInjector.getHandler().post( - () -> mRemovePackageHelper.removePackageData(ps, userIds)); - expectingBetter.put(ps.getPackageName(), ps.getPath()); + mRemovePackageHelper.removePackageData(ps, userIds); } else { // we still have a disabled system package, but, it still might have // been removed. check the code path still exists and check there's diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java index 3f4cc4a84ffd..b80c0094ffb9 100644 --- a/services/core/java/com/android/server/pm/LauncherAppsService.java +++ b/services/core/java/com/android/server/pm/LauncherAppsService.java @@ -1531,7 +1531,8 @@ public class LauncherAppsService extends SystemService { throw new ActivityNotFoundException("Activity could not be found"); } - final Intent launchIntent = getMainActivityLaunchIntent(component, user); + final Intent launchIntent = getMainActivityLaunchIntent(component, user, + false /* includeArchivedApps */); if (launchIntent == null) { throw new SecurityException("Attempt to launch activity without " + " category Intent.CATEGORY_LAUNCHER " + component); @@ -1577,7 +1578,8 @@ public class LauncherAppsService extends SystemService { return; } - Intent launchIntent = getMainActivityLaunchIntent(component, user); + Intent launchIntent = getMainActivityLaunchIntent(component, user, + true /* includeArchivedApps */); if (launchIntent == null) { throw new SecurityException("Attempt to launch activity without " + " category Intent.CATEGORY_LAUNCHER " + component); @@ -1593,7 +1595,8 @@ public class LauncherAppsService extends SystemService { /** * Returns the main activity launch intent for the given component package. */ - private Intent getMainActivityLaunchIntent(ComponentName component, UserHandle user) { + private Intent getMainActivityLaunchIntent(ComponentName component, UserHandle user, + boolean includeArchivedApps) { Intent launchIntent = new Intent(Intent.ACTION_MAIN); launchIntent.addCategory(Intent.CATEGORY_LAUNCHER); launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK @@ -1632,6 +1635,14 @@ public class LauncherAppsService extends SystemService { break; } } + if (!canLaunch + && includeArchivedApps + && Flags.archiving() + && getMatchingArchivedAppActivityInfo(component, user) != null) { + launchIntent.setPackage(null); + launchIntent.setComponent(component); + canLaunch = true; + } if (!canLaunch) { return null; } diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java index 968be5c2cf1c..c6e8a6463121 100644 --- a/services/core/java/com/android/server/pm/PackageArchiver.java +++ b/services/core/java/com/android/server/pm/PackageArchiver.java @@ -16,6 +16,10 @@ package com.android.server.pm; +import static android.app.ActivityManager.START_ABORTED; +import static android.app.ActivityManager.START_CLASS_NOT_FOUND; +import static android.app.ActivityManager.START_PERMISSION_DENIED; +import static android.app.ActivityManager.START_SUCCESS; import static android.app.AppOpsManager.MODE_IGNORED; import static android.app.ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED; import static android.content.pm.ArchivedActivityInfo.bytesFromBitmap; @@ -34,7 +38,10 @@ import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.AppOpsManager; import android.app.BroadcastOptions; +import android.content.ComponentName; import android.content.Context; +import android.content.IIntentReceiver; +import android.content.IIntentSender; import android.content.Intent; import android.content.IntentSender; import android.content.pm.ApplicationInfo; @@ -58,6 +65,7 @@ import android.graphics.drawable.LayerDrawable; import android.os.Binder; import android.os.Bundle; import android.os.Environment; +import android.os.IBinder; import android.os.ParcelableException; import android.os.Process; import android.os.SELinux; @@ -71,6 +79,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.server.pm.pkg.ArchiveState; import com.android.server.pm.pkg.ArchiveState.ArchiveActivityInfo; +import com.android.server.pm.pkg.PackageState; import com.android.server.pm.pkg.PackageStateInternal; import com.android.server.pm.pkg.PackageUserState; import com.android.server.pm.pkg.PackageUserStateInternal; @@ -185,6 +194,113 @@ public class PackageArchiver { }); } + /** + * Starts unarchival for the package corresponding to the startActivity intent. Note that this + * will work only if the caller is the default/Home Launcher or if activity is started via Shell + * identity. + */ + @NonNull + public int requestUnarchiveOnActivityStart(@Nullable Intent intent, + @Nullable String callerPackageName, int userId, int callingUid) { + String packageName = getPackageNameFromIntent(intent); + if (packageName == null) { + Slog.e(TAG, "packageName cannot be null for unarchival!"); + return START_CLASS_NOT_FOUND; + } + if (callerPackageName == null) { + Slog.e(TAG, "callerPackageName cannot be null for unarchival!"); + return START_CLASS_NOT_FOUND; + } + if (!isCallingPackageValid(callerPackageName, callingUid, userId)) { + // Return early as the calling UID does not match caller package's UID. + return START_CLASS_NOT_FOUND; + } + String currentLauncherPackageName = getCurrentLauncherPackageName(userId); + if ((currentLauncherPackageName == null || !callerPackageName.equals( + currentLauncherPackageName)) && callingUid != Process.SHELL_UID) { + // TODO(b/311619990): Remove dependency on SHELL_UID for testing + Slog.e(TAG, TextUtils.formatSimple( + "callerPackageName: %s does not qualify for archival of package: " + "%s!", + callerPackageName, packageName)); + return START_PERMISSION_DENIED; + } + // TODO(b/302114464): Handle edge cases & also divert to a dialog based on + // permissions + compat options + Slog.i(TAG, TextUtils.formatSimple("Unarchival is starting for: %s", packageName)); + try { + final IIntentSender.Stub mLocalSender = new IIntentSender.Stub() { + @Override + public void send(int code, Intent intent, String resolvedType, + IBinder allowlistToken, + IIntentReceiver finishedReceiver, String requiredPermission, + Bundle options) { + // TODO(b/302114464): Handle intent sender status codes + } + }; + + requestUnarchive(packageName, callerPackageName, + new IntentSender((IIntentSender) mLocalSender), UserHandle.of(userId)); + } catch (Throwable t) { + Slog.e(TAG, TextUtils.formatSimple( + "Unexpected error occurred while unarchiving package %s: %s.", packageName, + t.getLocalizedMessage())); + return START_ABORTED; + } + return START_SUCCESS; + } + + /** + * Returns true if the componentName targeted by the intent corresponds to that of an archived + * app. + */ + public boolean isIntentResolvedToArchivedApp(Intent intent, int userId) { + String packageName = getPackageNameFromIntent(intent); + if (packageName == null || intent.getComponent() == null) { + return false; + } + PackageState packageState = mPm.snapshotComputer().getPackageStateInternal(packageName); + if (packageState == null) { + return false; + } + PackageUserState userState = packageState.getUserStateOrDefault(userId); + if (!PackageArchiver.isArchived(userState)) { + return false; + } + List<ArchiveState.ArchiveActivityInfo> archiveActivityInfoList = + userState.getArchiveState().getActivityInfos(); + for (int i = 0; i < archiveActivityInfoList.size(); i++) { + if (archiveActivityInfoList.get(i) + .getOriginalComponentName().equals(intent.getComponent())) { + return true; + } + } + Slog.e(TAG, TextUtils.formatSimple( + "Package: %s is archived but component to start main activity" + + " cannot be found!", packageName)); + return false; + } + + @Nullable + private String getCurrentLauncherPackageName(int userId) { + ComponentName defaultLauncherComponent = mPm.snapshotComputer().getDefaultHomeActivity( + userId); + if (defaultLauncherComponent != null) { + return defaultLauncherComponent.getPackageName(); + } + return null; + } + + private boolean isCallingPackageValid(String callingPackage, int callingUid, int userId) { + int packageUid; + packageUid = mPm.snapshotComputer().getPackageUid(callingPackage, 0L, userId); + if (packageUid != callingUid) { + Slog.w(TAG, TextUtils.formatSimple("Calling package: %s does not belong to uid: %d", + callingPackage, callingUid)); + return false; + } + return true; + } + /** Creates archived state for the package and user. */ private CompletableFuture<ArchiveState> createArchiveState(String packageName, int userId) throws PackageManager.NameNotFoundException { @@ -403,7 +519,7 @@ public class PackageArchiver { Computer snapshot = mPm.snapshotComputer(); int userId = userHandle.getIdentifier(); int binderUid = Binder.getCallingUid(); - if (!PackageManagerServiceUtils.isRootOrShell(binderUid)) { + if (!PackageManagerServiceUtils.isSystemOrRootOrShell(binderUid)) { verifyCaller(snapshot.getPackageUid(callerPackageName, 0, userId), binderUid); } snapshot.enforceCrossUserPermission(binderUid, userId, true, true, @@ -780,6 +896,20 @@ public class PackageArchiver { return bytesFromBitmap(BitmapFactory.decodeFile(path.toString())); } + @Nullable + private static String getPackageNameFromIntent(@Nullable Intent intent) { + if (intent == null) { + return null; + } + if (intent.getPackage() != null) { + return intent.getPackage(); + } + if (intent.getComponent() != null) { + return intent.getComponent().getPackageName(); + } + return null; + } + /** * Creates serializable archived activities from existing ArchiveState. */ diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index c9663fc30ef5..882e05dd778b 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -1683,21 +1683,24 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements archivedPackageParcel); // Create and commit install archived session. - PackageInstallerSession session = null; - try { - var sessionId = createSessionInternal(params, installerPackageName, - null /*installerAttributionTag*/, Binder.getCallingUid(), userId); - session = openSessionInternal(sessionId); - session.addFile(LOCATION_DATA_APP, "base", 0 /*lengthBytes*/, metadata.toByteArray(), - null /*signature*/); - session.commit(statusReceiver, false /*forTransfer*/); - } catch (IOException e) { - throw ExceptionUtils.wrap(e); - } finally { - if (session != null) { - session.close(); + // Session belongs to the system_server and would not appear anywhere in the Public APIs. + Binder.withCleanCallingIdentity(() -> { + PackageInstallerSession session = null; + try { + var sessionId = createSessionInternal(params, installerPackageName, null + /*installerAttributionTag*/, Binder.getCallingUid(), userId); + session = openSessionInternal(sessionId); + session.addFile(LOCATION_DATA_APP, "base", 0 /*lengthBytes*/, + metadata.toByteArray(), null /*signature*/); + session.commit(statusReceiver, false /*forTransfer*/); + } catch (IOException e) { + throw ExceptionUtils.wrap(e); + } finally { + if (session != null) { + session.close(); + } } - } + }); } // TODO(b/307299702) Implement error dialog and propagate userActionIntent. diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index ec3823f365b6..e557f09467d4 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -6976,6 +6976,11 @@ public class PackageManagerService implements PackageSender, TestUtilityService } @Override + public PackageArchiver getPackageArchiver() { + return mInstallerService.mPackageArchiver; + } + + @Override public void sendPackageRestartedBroadcast(@NonNull String packageName, int uid, @Intent.Flags int flags) { final int userId = UserHandle.getUserId(uid); diff --git a/services/core/java/com/android/server/pm/RemovePackageHelper.java b/services/core/java/com/android/server/pm/RemovePackageHelper.java index 639d6d78f953..80f69a4897c2 100644 --- a/services/core/java/com/android/server/pm/RemovePackageHelper.java +++ b/services/core/java/com/android/server/pm/RemovePackageHelper.java @@ -395,11 +395,13 @@ final class RemovePackageHelper { mPm.mSettings.removeRenamedPackageLPw(deletedPs.getRealName()); } if (changedUsers.size() > 0) { - final PreferredActivityHelper preferredActivityHelper = - new PreferredActivityHelper(mPm, mBroadcastHelper); - preferredActivityHelper.updateDefaultHomeNotLocked(mPm.snapshotComputer(), - changedUsers); - mBroadcastHelper.sendPreferredActivityChangedBroadcast(UserHandle.USER_ALL); + mPm.mInjector.getBackgroundHandler().post(() -> { + final PreferredActivityHelper preferredActivityHelper = + new PreferredActivityHelper(mPm, mBroadcastHelper); + preferredActivityHelper.updateDefaultHomeNotLocked(mPm.snapshotComputer(), + changedUsers); + mBroadcastHelper.sendPreferredActivityChangedBroadcast(UserHandle.USER_ALL); + }); } } else if (!deletedPs.isSystem() && outInfo != null && !outInfo.mIsUpdate && outInfo.mRemovedUsers != null && !outInfo.mIsExternal) { diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java index 926e0188c624..7910edcb5959 100644 --- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java +++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java @@ -411,6 +411,9 @@ public class PackageInfoUtils { ai.overlayPaths = overlayPaths.getOverlayPaths().toArray(new String[0]); } ai.isArchived = PackageArchiver.isArchived(state); + if (ai.isArchived) { + ai.nonLocalizedLabel = state.getArchiveState().getActivityInfos().get(0).getTitle(); + } } @Nullable diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index 5e0a449ddd63..d6302e08fedb 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -103,6 +103,7 @@ import android.content.IntentSender; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.AuxiliaryResolveInfo; +import android.content.pm.Flags; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.ResolveInfo; @@ -128,6 +129,7 @@ import com.android.internal.app.IVoiceInteractor; import com.android.internal.protolog.common.ProtoLog; import com.android.server.am.PendingIntentRecord; import com.android.server.pm.InstantAppResolver; +import com.android.server.pm.PackageArchiver; import com.android.server.power.ShutdownCheckPoints; import com.android.server.statusbar.StatusBarManagerInternal; import com.android.server.uri.NeededUriGrants; @@ -958,6 +960,17 @@ class ActivityStarter { } } + if (Flags.archiving()) { + PackageArchiver packageArchiver = mService + .getPackageManagerInternalLocked() + .getPackageArchiver(); + if (packageArchiver.isIntentResolvedToArchivedApp(intent, mRequest.userId)) { + return packageArchiver + .requestUnarchiveOnActivityStart( + intent, callingPackage, mRequest.userId, realCallingUid); + } + } + final int launchFlags = intent.getFlags(); if ((launchFlags & Intent.FLAG_ACTIVITY_FORWARD_RESULT) != 0 && sourceRecord != null) { // Transfer the result target from the source activity to the new one being started, diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java index b77596391be1..7239ba9cd07e 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java @@ -24,6 +24,7 @@ import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.app.AppGlobals; +import android.app.AppOpsManager; import android.app.role.OnRoleHoldersChangedListener; import android.app.role.RoleManager; import android.content.ComponentName; @@ -1565,6 +1566,33 @@ public class VoiceInteractionManagerService extends SystemService { } } + @Override + public boolean setSandboxedDetectionTrainingDataOp(int opMode) { + synchronized (this) { + enforceIsCallerPreinstalledAssistant(); + + if (mImpl == null) { + Slog.w(TAG, "setSandboxedDetectionTrainingDataop without running" + + " voice interaction service"); + return false; + } + + int callingUid = Binder.getCallingUid(); + final long caller = Binder.clearCallingIdentity(); + try { + AppOpsManager appOpsManager = (AppOpsManager) + mContext.getSystemService(Context.APP_OPS_SERVICE); + appOpsManager.setUidMode( + AppOpsManager.OP_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA, + callingUid, opMode); + } finally { + Binder.restoreCallingIdentity(caller); + } + + return true; + } + } + //----------------- Model management APIs --------------------------------// @Override @@ -2219,6 +2247,13 @@ public class VoiceInteractionManagerService extends SystemService { } } + private void enforceIsCallerPreinstalledAssistant() { + if (!isCallerPreinstalledAssistant()) { + throw new + SecurityException("Caller is not the pre-installed assistant."); + } + } + private void enforceCallerAllowedToEnrollVoiceModel() { if (isCallerHoldingPermission(Manifest.permission.KEYPHRASE_ENROLLMENT_APPLICATION)) { return; @@ -2233,6 +2268,13 @@ public class VoiceInteractionManagerService extends SystemService { && mImpl.mInfo.getServiceInfo().applicationInfo.uid == Binder.getCallingUid(); } + private boolean isCallerPreinstalledAssistant() { + return mImpl != null + && mImpl.mInfo.getServiceInfo().applicationInfo.uid == Binder.getCallingUid() + && (mImpl.mInfo.getServiceInfo().applicationInfo.isSystemApp() + || mImpl.mInfo.getServiceInfo().applicationInfo.isUpdatedSystemApp()); + } + private void setImplLocked(VoiceInteractionManagerServiceImpl impl) { mImpl = impl; mAtmInternal.notifyActiveVoiceInteractionServiceChanged( diff --git a/telecomm/java/android/telecom/InCallService.java b/telecomm/java/android/telecom/InCallService.java index 13a045858ab1..bbd01d6bcd67 100644 --- a/telecomm/java/android/telecom/InCallService.java +++ b/telecomm/java/android/telecom/InCallService.java @@ -289,6 +289,11 @@ public abstract class InCallService extends Service { switch (msg.what) { case MSG_SET_IN_CALL_ADAPTER: + if (mPhone != null) { + Log.i(this, "mPhone is already instantiated, ignoring " + + "request to reset adapter."); + break; + } String callingPackage = getApplicationContext().getOpPackageName(); mPhone = new Phone(new InCallAdapter((IInCallAdapter) msg.obj), callingPackage, getApplicationContext().getApplicationInfo().targetSdkVersion); |