summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/api/current.txt6
-rw-r--r--core/api/system-current.txt3
-rw-r--r--core/java/android/app/ApplicationPackageManager.java28
-rw-r--r--core/java/android/content/pm/ApplicationInfo.java105
-rw-r--r--core/java/android/content/pm/IPackageManager.aidl6
-rw-r--r--core/java/android/content/pm/PackageManager.java63
-rw-r--r--core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java19
-rw-r--r--core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java4
-rw-r--r--core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java8
-rw-r--r--core/java/com/android/server/pm/pkg/AndroidPackage.java8
-rw-r--r--core/proto/android/content/package_item_info.proto1
-rw-r--r--core/res/res/values/attrs_manifest.xml8
-rw-r--r--core/res/res/values/strings.xml15
-rw-r--r--core/res/res/values/symbols.xml5
-rw-r--r--errorprone/Android.bp8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java31
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java21
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java1
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/Android.bp21
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/PipTransition.kt6
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/nonmatchparent/BottomHalfExitPipToAppTransition.kt96
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/nonmatchparent/BottomHalfExitPipToAppViaExpandButtonTest.kt70
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/nonmatchparent/BottomHalfExitPipToAppViaIntentTest.kt68
-rw-r--r--media/java/android/media/AudioPlaybackConfiguration.java21
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/activity/data/repository/ActivityManagerRepositoryTest.kt143
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduUiCoordinatorTest.kt19
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractorTest.kt207
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractorTest.kt259
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt29
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt9
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/activity/ActivityManagerModule.kt30
-rw-r--r--packages/SystemUI/src/com/android/systemui/activity/data/repository/ActivityManagerRepository.kt118
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/education/ui/view/ContextualEduUiCoordinator.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/StatusBarChipsModule.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractor.kt115
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractor.kt103
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/model/NotificationChipModel.kt (renamed from tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BottomHalfPipLaunchingActivity.java)17
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt23
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt16
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/activity/data/repository/ActivityManagerRepositoryKosmos.kt51
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractorFactoryKosmos.kt33
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractorKosmos.kt12
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelKosmos.kt7
-rw-r--r--ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRunnerState.java162
-rw-r--r--ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodMultipleRuleTest.java57
-rw-r--r--ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRuleValidationTest.java103
-rw-r--r--ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerConfigValidationTest.java484
-rw-r--r--ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/IdentityTest.java13
-rw-r--r--ravenwood/tests/services-test/test/com/android/ravenwoodtest/servicestest/RavenwoodServicesTest.java8
-rw-r--r--services/core/java/com/android/server/BatteryService.java54
-rw-r--r--services/core/java/com/android/server/TEST_MAPPING4
-rw-r--r--services/core/java/com/android/server/audio/PlaybackActivityMonitor.java6
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java61
-rw-r--r--services/core/java/com/android/server/pm/PackageSetting.java91
-rw-r--r--services/core/java/com/android/server/pm/Settings.java15
-rw-r--r--services/core/java/com/android/server/pm/pkg/PackageState.java16
-rw-r--r--services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java10
-rw-r--r--services/core/java/com/android/server/pm/pkg/mutate/PackageStateWrite.java4
-rw-r--r--services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt1
-rw-r--r--services/tests/mockingservicestests/Android.bp7
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/BatteryServiceTest.java87
-rw-r--r--tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/BottomHalfPipAppHelper.kt41
-rw-r--r--tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt12
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml21
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml6
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java15
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BottomHalfPipActivity.java71
72 files changed, 1932 insertions, 1301 deletions
diff --git a/core/api/current.txt b/core/api/current.txt
index 55e1aeccd0fd..af7d6f1a5da1 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -13225,8 +13225,8 @@ package android.content.pm {
public abstract class PackageManager {
ctor @Deprecated public PackageManager();
method @Deprecated public abstract void addPackageToPreferred(@NonNull String);
- method public abstract boolean addPermission(@NonNull android.content.pm.PermissionInfo);
- method public abstract boolean addPermissionAsync(@NonNull android.content.pm.PermissionInfo);
+ method @Deprecated @FlaggedApi("android.permission.flags.permission_tree_apis_deprecated") public abstract boolean addPermission(@NonNull android.content.pm.PermissionInfo);
+ method @Deprecated @FlaggedApi("android.permission.flags.permission_tree_apis_deprecated") public abstract boolean addPermissionAsync(@NonNull android.content.pm.PermissionInfo);
method @Deprecated public abstract void addPreferredActivity(@NonNull android.content.IntentFilter, int, @Nullable android.content.ComponentName[], @NonNull android.content.ComponentName);
method @RequiresPermission(value="android.permission.WHITELIST_RESTRICTED_PERMISSIONS", conditional=true) public boolean addWhitelistedRestrictedPermission(@NonNull String, @NonNull String, int);
method public boolean canPackageQuery(@NonNull String, @NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
@@ -13367,7 +13367,7 @@ package android.content.pm {
method @NonNull public java.util.List<android.content.pm.PackageManager.Property> queryServiceProperty(@NonNull String);
method public void relinquishUpdateOwnership(@NonNull String);
method @Deprecated public abstract void removePackageFromPreferred(@NonNull String);
- method public abstract void removePermission(@NonNull String);
+ method @Deprecated @FlaggedApi("android.permission.flags.permission_tree_apis_deprecated") public abstract void removePermission(@NonNull String);
method @RequiresPermission(value="android.permission.WHITELIST_RESTRICTED_PERMISSIONS", conditional=true) public boolean removeWhitelistedRestrictedPermission(@NonNull String, @NonNull String, int);
method public void requestChecksums(@NonNull String, boolean, int, @NonNull java.util.List<java.security.cert.Certificate>, @NonNull android.content.pm.PackageManager.OnChecksumsReadyListener) throws java.security.cert.CertificateEncodingException, android.content.pm.PackageManager.NameNotFoundException;
method @Nullable public abstract android.content.pm.ResolveInfo resolveActivity(@NonNull android.content.Intent, int);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 5de46b530620..83699ac30939 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -7637,10 +7637,11 @@ package android.media {
method public boolean isActive();
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean isMuted();
method public boolean isSpatialized();
- field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_APP_OPS = 8; // 0x8
+ field @Deprecated @FlaggedApi("android.media.audio.muted_by_port_volume_api") @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_APP_OPS = 8; // 0x8
field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_CLIENT_VOLUME = 16; // 0x10
field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_MASTER = 1; // 0x1
field @FlaggedApi("android.media.audio.muted_by_port_volume_api") @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_OP_CONTROL_AUDIO = 128; // 0x80
+ field @FlaggedApi("android.media.audio.muted_by_port_volume_api") @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_OP_PLAY_AUDIO = 8; // 0x8
field @FlaggedApi("android.media.audio.muted_by_port_volume_api") @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_PORT_VOLUME = 64; // 0x40
field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_STREAM_MUTED = 4; // 0x4
field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_STREAM_VOLUME = 2; // 0x2
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 7e0a9b69b7bd..3cbea87e135e 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -130,7 +130,6 @@ import android.util.Slog;
import android.util.Xml;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.Immutable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.SomeArgs;
import com.android.internal.pm.RoSystemFeatures;
@@ -1020,6 +1019,33 @@ public class ApplicationPackageManager extends PackageManager {
}
}
+ @Override
+ public void setPageSizeAppCompatFlagsSettingsOverride(String packageName, boolean enabled) {
+ try {
+ mPM.setPageSizeAppCompatFlagsSettingsOverride(packageName, enabled);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @Override
+ public boolean isPageSizeCompatEnabled(String packageName) {
+ try {
+ return mPM.isPageSizeCompatEnabled(packageName);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @Override
+ public String getPageSizeCompatWarningMessage(String packageName) {
+ try {
+ return mPM.getPageSizeCompatWarningMessage(packageName);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
private static List<byte[]> encodeCertificates(List<Certificate> certs) throws
CertificateEncodingException {
if (certs == null) {
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index cccfdb0938e5..94784227049d 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -1449,6 +1449,97 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
}
}
+ /**
+ * Use this to report any errors during alignment checks
+ *
+ * @hide
+ */
+ public static final int PAGE_SIZE_APP_COMPAT_FLAG_ERROR = -1;
+
+ /**
+ * Initial value for mPageSizeAppCompatFlags
+ *
+ * @hide
+ */
+ public static final int PAGE_SIZE_APP_COMPAT_FLAG_UNDEFINED = 0;
+
+ /**
+ * if set, extract libs forcefully for 16 KB device and show warning dialog.
+ *
+ * @hide
+ */
+ public static final int PAGE_SIZE_APP_COMPAT_FLAG_UNCOMPRESSED_LIBS_NOT_ALIGNED = 1 << 1;
+
+ /**
+ * if set, load 4 KB aligned ELFs on 16 KB device in compat mode and show warning dialog.
+ *
+ * @hide
+ */
+ public static final int PAGE_SIZE_APP_COMPAT_FLAG_ELF_NOT_ALIGNED = 1 << 2;
+
+ /**
+ * Run in 16 KB app compat mode. This flag will be set explicitly through settings. If set, 16
+ * KB app compat warning dialogs will still show up.
+ *
+ * @hide
+ */
+ public static final int PAGE_SIZE_APP_COMPAT_FLAG_SETTINGS_OVERRIDE_ENABLED = 1 << 3;
+
+ /**
+ * Disable 16 KB app compat mode through settings. It should only affect ELF loading as app is
+ * already installed.
+ *
+ * @hide
+ */
+ public static final int PAGE_SIZE_APP_COMPAT_FLAG_SETTINGS_OVERRIDE_DISABLED = 1 << 4;
+
+ /**
+ * Run in 16 KB app compat mode. This flag will be set explicitly through manifest. If set, hide
+ * the 16 KB app compat warning dialogs. This has the highest priority to enable compat mode.
+ *
+ * @hide
+ */
+ public static final int PAGE_SIZE_APP_COMPAT_FLAG_MANIFEST_OVERRIDE_ENABLED = 1 << 5;
+
+ /**
+ * Disable 16 KB app compat mode. This has the highest priority to disable compat mode.
+ *
+ * @hide
+ */
+ public static final int PAGE_SIZE_APP_COMPAT_FLAG_MANIFEST_OVERRIDE_DISABLED = 1 << 6;
+
+ /**
+ * Max value for page size app compat
+ *
+ * @hide
+ */
+ public static final int PAGE_SIZE_APP_COMPAT_FLAG_MAX = 1 << 7;
+
+ /**
+ * 16 KB app compat status for the app. App can have native shared libs which are not page
+ * aligned, LOAD segments inside the shared libs have to be page aligned. Apps can specify the
+ * override in manifest file as well.
+ */
+ private @PageSizeAppCompatFlags int mPageSizeAppCompatFlags =
+ ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_UNDEFINED;
+
+ /** {@hide} */
+ @IntDef(
+ prefix = {"PAGE_SIZE_APP_COMPAT_FLAG_"},
+ value = {
+ PAGE_SIZE_APP_COMPAT_FLAG_ERROR,
+ PAGE_SIZE_APP_COMPAT_FLAG_UNDEFINED,
+ PAGE_SIZE_APP_COMPAT_FLAG_UNCOMPRESSED_LIBS_NOT_ALIGNED,
+ PAGE_SIZE_APP_COMPAT_FLAG_ELF_NOT_ALIGNED,
+ PAGE_SIZE_APP_COMPAT_FLAG_MANIFEST_OVERRIDE_ENABLED,
+ PAGE_SIZE_APP_COMPAT_FLAG_MANIFEST_OVERRIDE_DISABLED,
+ PAGE_SIZE_APP_COMPAT_FLAG_SETTINGS_OVERRIDE_ENABLED,
+ PAGE_SIZE_APP_COMPAT_FLAG_SETTINGS_OVERRIDE_DISABLED,
+ PAGE_SIZE_APP_COMPAT_FLAG_MAX,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface PageSizeAppCompatFlags {}
+
/** @hide */
public String classLoaderName;
@@ -1777,7 +1868,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
pw.println(prefix + "enableOnBackInvokedCallback=" + isOnBackInvokedCallbackEnabled());
pw.println(prefix + "allowCrossUidActivitySwitchFromBelow="
+ allowCrossUidActivitySwitchFromBelow);
-
+ pw.println(prefix + "mPageSizeAppCompatFlags=" + mPageSizeAppCompatFlags);
}
pw.println(prefix + "createTimestamp=" + createTimestamp);
if (mKnownActivityEmbeddingCerts != null) {
@@ -1897,6 +1988,10 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
}
proto.write(ApplicationInfoProto.Detail.ALLOW_CROSS_UID_ACTIVITY_SWITCH_FROM_BELOW,
allowCrossUidActivitySwitchFromBelow);
+
+ proto.write(ApplicationInfoProto.Detail.ENABLE_PAGE_SIZE_APP_COMPAT,
+ mPageSizeAppCompatFlags);
+
proto.end(detailToken);
}
if (!ArrayUtils.isEmpty(mKnownActivityEmbeddingCerts)) {
@@ -2024,6 +2119,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
localeConfigRes = orig.localeConfigRes;
allowCrossUidActivitySwitchFromBelow = orig.allowCrossUidActivitySwitchFromBelow;
createTimestamp = SystemClock.uptimeMillis();
+ mPageSizeAppCompatFlags = orig.mPageSizeAppCompatFlags;
}
public String toString() {
@@ -2128,6 +2224,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
}
dest.writeInt(localeConfigRes);
dest.writeInt(allowCrossUidActivitySwitchFromBelow ? 1 : 0);
+ dest.writeInt(mPageSizeAppCompatFlags);
sForStringSet.parcel(mKnownActivityEmbeddingCerts, dest, flags);
}
@@ -2228,6 +2325,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
}
localeConfigRes = source.readInt();
allowCrossUidActivitySwitchFromBelow = source.readInt() != 0;
+ mPageSizeAppCompatFlags = source.readInt();
mKnownActivityEmbeddingCerts = sForStringSet.unparcel(source);
if (mKnownActivityEmbeddingCerts.isEmpty()) {
@@ -2765,6 +2863,11 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
requestRawExternalStorageAccess = value;
}
+ /** {@hide} */
+ public void setPageSizeAppCompatFlags(@PageSizeAppCompatFlags int value) {
+ mPageSizeAppCompatFlags |= value;
+ }
+
/**
* Replaces {@link #mAppClassNamesByProcess}. This takes over the ownership of the passed map.
* Do not modify the argument at the callsite.
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 5d4babb8a36d..57c12403dec8 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -848,4 +848,10 @@ interface IPackageManager {
int getAppMetadataSource(String packageName, int userId);
ComponentName getDomainVerificationAgent(int userId);
+
+ void setPageSizeAppCompatFlagsSettingsOverride(in String packageName, boolean enabled);
+
+ boolean isPageSizeCompatEnabled(in String packageName);
+
+ String getPageSizeCompatWarningMessage(in String packageName);
}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index d2b43b9bd2b4..23d3693628e7 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -804,7 +804,6 @@ public abstract class PackageManager {
@Deprecated
private void __metadata() {}
-
//@formatter:on
// End of generated code
@@ -6729,6 +6728,11 @@ public abstract class PackageManager {
* If the given permission already exists, the info you supply here
* will be used to update it.
*
+ * @deprecated Support for dynamic permissions is going to be removed, and apps that use dynamic
+ * permissions should declare their permissions statically inside their app manifest instead.
+ * This method will become a no-op in a future Android release and eventually be removed from
+ * the SDK.
+ *
* @param info Description of the permission to be added.
*
* @return Returns true if a new permission was created, false if an
@@ -6739,7 +6743,9 @@ public abstract class PackageManager {
*
* @see #removePermission(String)
*/
- //@Deprecated
+ @SuppressWarnings("HiddenAbstractMethod")
+ @FlaggedApi(android.permission.flags.Flags.FLAG_PERMISSION_TREE_APIS_DEPRECATED)
+ @Deprecated
public abstract boolean addPermission(@NonNull PermissionInfo info);
/**
@@ -6748,8 +6754,15 @@ public abstract class PackageManager {
* allowing it to return quicker and batch a series of adds at the
* expense of no guarantee the added permission will be retained if
* the device is rebooted before it is written.
+ *
+ * @deprecated Support for dynamic permissions is going to be removed, and apps that use dynamic
+ * permissions should declare their permissions statically inside their app manifest instead.
+ * This method will become a no-op in a future Android release and eventually be removed from
+ * the SDK.
*/
- //@Deprecated
+ @SuppressWarnings("HiddenAbstractMethod")
+ @FlaggedApi(android.permission.flags.Flags.FLAG_PERMISSION_TREE_APIS_DEPRECATED)
+ @Deprecated
public abstract boolean addPermissionAsync(@NonNull PermissionInfo info);
/**
@@ -6758,6 +6771,11 @@ public abstract class PackageManager {
* -- you are only allowed to remove permissions that you are allowed
* to add.
*
+ * @deprecated Support for dynamic permissions is going to be removed, and apps that use dynamic
+ * permissions should declare their permissions statically inside their app manifest instead.
+ * This method will become a no-op in a future Android release and eventually be removed from
+ * the SDK.
+ *
* @param permName The name of the permission to remove.
*
* @throws SecurityException if you are not allowed to remove the
@@ -6765,7 +6783,9 @@ public abstract class PackageManager {
*
* @see #addPermission(PermissionInfo)
*/
- //@Deprecated
+ @SuppressWarnings("HiddenAbstractMethod")
+ @FlaggedApi(android.permission.flags.Flags.FLAG_PERMISSION_TREE_APIS_DEPRECATED)
+ @Deprecated
public abstract void removePermission(@NonNull String permName);
/**
@@ -10987,6 +11007,41 @@ public abstract class PackageManager {
}
/**
+ * Set the page compat mode override for given package
+ *
+ * @hide
+ */
+ @FlaggedApi(android.content.pm.Flags.FLAG_APP_COMPAT_OPTION_16KB)
+ public void setPageSizeAppCompatFlagsSettingsOverride(@NonNull String packageName,
+ boolean enabled) {
+ throw new UnsupportedOperationException(
+ "setPageSizeAppCompatFlagsSettingsOverride not implemented in subclass");
+ }
+
+ /**
+ * Check whether page size app compat mode is enabled for given package
+ *
+ * @hide
+ */
+ @FlaggedApi(android.content.pm.Flags.FLAG_APP_COMPAT_OPTION_16KB)
+ public boolean isPageSizeCompatEnabled(@NonNull String packageName) {
+ throw new UnsupportedOperationException(
+ "isPageSizeCompatEnabled not implemented in subclass");
+ }
+
+ /**
+ * Get the page size app compat warning dialog to show at app launch time
+ *
+ * @hide
+ */
+ @Nullable
+ @FlaggedApi(android.content.pm.Flags.FLAG_APP_COMPAT_OPTION_16KB)
+ public String getPageSizeCompatWarningMessage(@NonNull String packageName) {
+ throw new UnsupportedOperationException(
+ "getPageSizeCompatWarningMessage not implemented in subclass");
+ }
+
+ /**
* Returns the harmful app warning string for the given app, or null if there is none set.
*
* @param packageName The full name of the desired package.
diff --git a/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java b/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java
index 48d0d6c777de..5ec5762c0533 100644
--- a/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java
+++ b/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java
@@ -392,6 +392,10 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal,
private int memtagMode;
@ApplicationInfo.NativeHeapZeroInitialized
private int nativeHeapZeroInitialized;
+
+ @ApplicationInfo.PageSizeAppCompatFlags private int mPageSizeAppCompatFlags =
+ ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_UNDEFINED;
+
@Nullable
@DataClass.ParcelWith(Parcelling.BuiltIn.ForBoolean.class)
private Boolean requestRawExternalStorageAccess;
@@ -1118,6 +1122,12 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal,
return nativeHeapZeroInitialized;
}
+ @ApplicationInfo.PageSizeAppCompatFlags
+ @Override
+ public int getPageSizeAppCompatFlags() {
+ return mPageSizeAppCompatFlags;
+ }
+
@Override
public int getNetworkSecurityConfigResourceId() {
return networkSecurityConfigRes;
@@ -2221,6 +2231,12 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal,
}
@Override
+ public PackageImpl setPageSizeAppCompatFlags(@ApplicationInfo.PageSizeAppCompatFlags int flag) {
+ mPageSizeAppCompatFlags = flag;
+ return this;
+ }
+
+ @Override
public PackageImpl setNetworkSecurityConfigResourceId(int value) {
networkSecurityConfigRes = value;
return this;
@@ -2703,6 +2719,7 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal,
appInfo.setKnownActivityEmbeddingCerts(mKnownActivityEmbeddingCerts);
}
appInfo.allowCrossUidActivitySwitchFromBelow = mAllowCrossUidActivitySwitchFromBelow;
+ appInfo.setPageSizeAppCompatFlags(mPageSizeAppCompatFlags);
return appInfo;
}
@@ -3305,6 +3322,7 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal,
dest.writeInt(this.mIntentMatchingFlags);
dest.writeIntArray(this.mAlternateLauncherIconResIds);
dest.writeIntArray(this.mAlternateLauncherLabelResIds);
+ dest.writeInt(this.mPageSizeAppCompatFlags);
}
private void writeFeatureFlagState(@NonNull Parcel dest) {
@@ -3499,6 +3517,7 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal,
this.mIntentMatchingFlags = in.readInt();
this.mAlternateLauncherIconResIds = in.createIntArray();
this.mAlternateLauncherLabelResIds = in.createIntArray();
+ this.mPageSizeAppCompatFlags = in.readInt();
assignDerivedFields();
assignDerivedFields2();
diff --git a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java
index 67b985a61455..5062d58d4dca 100644
--- a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java
+++ b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java
@@ -31,7 +31,6 @@ import android.util.ArraySet;
import android.util.SparseArray;
import android.util.SparseIntArray;
-import com.android.internal.R;
import com.android.internal.pm.parsing.pkg.ParsedPackage;
import com.android.internal.pm.pkg.component.ParsedActivity;
import com.android.internal.pm.pkg.component.ParsedApexSystemService;
@@ -280,6 +279,9 @@ public interface ParsingPackage {
ParsingPackage setNativeHeapZeroInitialized(
@ApplicationInfo.NativeHeapZeroInitialized int nativeHeapZeroInitialized);
+ /** Manifest option pageSizeCompat will populate this field */
+ ParsingPackage setPageSizeAppCompatFlags(@ApplicationInfo.PageSizeAppCompatFlags int value);
+
ParsingPackage setRequestRawExternalStorageAccess(
@Nullable Boolean requestRawExternalStorageAccess);
diff --git a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
index 5fc1276dd9f9..0f93e6e8109b 100644
--- a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
+++ b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
@@ -541,6 +541,7 @@ public class ParsingPackageUtils {
pkg.setGwpAsanMode(-1);
pkg.setMemtagMode(-1);
+ pkg.setPageSizeAppCompatFlags(ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_UNDEFINED);
afterParseBaseApplication(pkg);
@@ -2182,6 +2183,13 @@ public class ParsingPackageUtils {
pkg.setGwpAsanMode(sa.getInt(R.styleable.AndroidManifestApplication_gwpAsanMode, -1));
pkg.setMemtagMode(sa.getInt(R.styleable.AndroidManifestApplication_memtagMode, -1));
+
+ if (Flags.appCompatOption16kb()) {
+ pkg.setPageSizeAppCompatFlags(
+ sa.getInt(R.styleable.AndroidManifestApplication_pageSizeCompat,
+ ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_UNDEFINED));
+ }
+
if (sa.hasValue(R.styleable.AndroidManifestApplication_nativeHeapZeroInitialized)) {
final boolean v = sa.getBoolean(
R.styleable.AndroidManifestApplication_nativeHeapZeroInitialized, false);
diff --git a/core/java/com/android/server/pm/pkg/AndroidPackage.java b/core/java/com/android/server/pm/pkg/AndroidPackage.java
index d05f5e3950b4..70dd10f2c371 100644
--- a/core/java/com/android/server/pm/pkg/AndroidPackage.java
+++ b/core/java/com/android/server/pm/pkg/AndroidPackage.java
@@ -875,6 +875,14 @@ public interface AndroidPackage {
int getMemtagMode();
/**
+ * @see ApplicationInfo#getPageSizeAppCompatFlags()
+ * @see R.styleable#AndroidManifestApplication_pageSizeCompat
+ * @hide
+ */
+ @ApplicationInfo.PageSizeAppCompatFlags
+ int getPageSizeAppCompatFlags();
+
+ /**
* TODO(b/135203078): Make all the Bundles immutable (and non-null by shared empty reference?)
* @see R.styleable#AndroidManifestMetaData
* @hide
diff --git a/core/proto/android/content/package_item_info.proto b/core/proto/android/content/package_item_info.proto
index b7408a4da381..facadeedd1f8 100644
--- a/core/proto/android/content/package_item_info.proto
+++ b/core/proto/android/content/package_item_info.proto
@@ -114,6 +114,7 @@ message ApplicationInfoProto {
optional int32 enable_memtag = 20;
optional bool native_heap_zero_init = 21;
optional bool allow_cross_uid_activity_switch_from_below = 22;
+ optional int32 enable_page_size_app_compat = 23;
}
optional Detail detail = 17;
repeated string overlay_paths = 18;
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 7ef539492aa4..cdf184c9c944 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1867,8 +1867,12 @@
16 KB device. 4 KB natives libs will be loaded app-compat mode if they are eligible.
@FlaggedApi(android.content.pm.Flags.FLAG_APP_COMPAT_OPTION_16KB) -->
<attr name="pageSizeCompat">
- <enum name="enabled" value="5" />
- <enum name="disabled" value="6" />
+ <!-- value for enabled must match with
+ ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_MANIFEST_OVERRIDE_ENABLED -->
+ <enum name="enabled" value="32" />
+ <!-- value for disabled must match with
+ ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_MANIFEST_OVERRIDE_DISABLED -->
+ <enum name="disabled" value="64" />
</attr>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 413f0c3e0c58..d498b9191559 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -199,6 +199,21 @@
<!-- Displayed to confirm to the user that caller ID will not be restricted on the next call or in general. -->
<string name="CLIRDefaultOffNextCallOff">Caller ID defaults to not restricted. Next call: Not restricted</string>
+ <!-- Message displayed in dialog when APK is not 16 KB aligned. [CHAR LIMIT=NONE] -->
+ <string name="page_size_compat_apk_warning">This app isn’t 16 KB compatible. APK alignment check failed.
+ This app will be run using page size compatible mode. For best compatibility, please recompile the application with 16 KB support.
+ For more information, see &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt; </string>
+
+ <!-- Message displayed in dialog when ELF is not 16 KB aligned. [CHAR LIMIT=NONE] -->
+ <string name="page_size_compat_elf_warning">This app isn’t 16 KB compatible. ELF alignment check failed.
+ This app will be run using page size compatible mode. For best compatibility, please recompile the application with 16 KB support.
+ For more information, see &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;</string>
+
+ <!-- Message displayed in dialog when APK and ELF are not 16 KB aligned. [CHAR LIMIT=NONE] -->
+ <string name="page_size_compat_apk_and_elf_warning">This app isn’t 16 KB compatible. APK and ELF alignment checks failed.
+ This app will be run using page size compatible mode. For best compatibility, please recompile the application with 16 KB support.
+ For more information, see &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;</string>
+
<!-- Displayed to tell the user that caller ID is not provisioned for their SIM. -->
<string name="serviceNotProvisioned">Service not provisioned.</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 0622d7224411..2f82011726ec 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3312,6 +3312,11 @@
<java-symbol type="string" name="language_selection_title" />
<java-symbol type="string" name="search_language_hint" />
+ <!-- Strings for page size app compat dialog -->
+ <java-symbol type="string" name="page_size_compat_apk_warning" />
+ <java-symbol type="string" name="page_size_compat_elf_warning" />
+ <java-symbol type="string" name="page_size_compat_apk_and_elf_warning" />
+
<!-- Work profile unlaunchable app alert dialog-->
<java-symbol type="style" name="AlertDialogWithEmergencyButton"/>
<java-symbol type="string" name="work_mode_emergency_call_button" />
diff --git a/errorprone/Android.bp b/errorprone/Android.bp
index b559a15c3a60..1428b8965473 100644
--- a/errorprone/Android.bp
+++ b/errorprone/Android.bp
@@ -31,6 +31,14 @@ java_library_host {
"//external/auto:auto_service_annotations",
],
+ javacflags: [
+ // These exports are needed because this errorprone plugin access some private classes
+ // of the java compiler.
+ "--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED",
+ "--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED",
+ "--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED",
+ ],
+
plugins: [
"//external/auto:auto_service_plugin",
],
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
index 55e90e74a404..5276d9d6a4df 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
@@ -324,8 +324,6 @@ public class PipAnimationController {
private final @AnimationType int mAnimationType;
private final Rect mDestinationBounds = new Rect();
- private final Point mLeashOffset = new Point();
-
private T mBaseValue;
protected T mCurrentValue;
protected T mStartValue;
@@ -340,22 +338,13 @@ public class PipAnimationController {
// Flag to avoid double-end
private boolean mHasRequestedEnd;
- private PipTransitionAnimator(@NonNull TaskInfo taskInfo, @NonNull SurfaceControl leash,
- @AnimationType int animationType, @NonNull Rect destinationBounds,
- @NonNull T baseValue, @NonNull T startValue, @NonNull T endValue) {
- this(taskInfo, leash, animationType, destinationBounds, new Point(), baseValue,
- startValue, endValue);
- }
-
- private PipTransitionAnimator(@NonNull TaskInfo taskInfo, @NonNull SurfaceControl leash,
- @AnimationType int animationType, @NonNull Rect destinationBounds,
- @NonNull Point leashOffset, @NonNull T baseValue, @NonNull T startValue,
- @NonNull T endValue) {
+ private PipTransitionAnimator(TaskInfo taskInfo, SurfaceControl leash,
+ @AnimationType int animationType,
+ Rect destinationBounds, T baseValue, T startValue, T endValue) {
mTaskInfo = taskInfo;
mLeash = leash;
mAnimationType = animationType;
mDestinationBounds.set(destinationBounds);
- mLeashOffset.set(leashOffset);
mBaseValue = baseValue;
mStartValue = startValue;
mEndValue = endValue;
@@ -507,15 +496,6 @@ public class PipAnimationController {
}
}
- /**
- * Returns the offset of the {@link #mLeash}.
- */
- @NonNull
- Point getLeashOffset() {
- // Use copy to prevent the leash to be modified unexpectedly.
- return new Point(mLeashOffset);
- }
-
void setCurrentValue(T value) {
mCurrentValue = value;
}
@@ -712,8 +692,8 @@ public class PipAnimationController {
final Rect zeroInsets = new Rect(0, 0, 0, 0);
// construct new Rect instances in case they are recycled
- return new PipTransitionAnimator<Rect>(taskInfo, leash, ANIM_TYPE_BOUNDS, endBounds,
- leashOffset, new Rect(baseBounds), new Rect(startBounds), new Rect(endBounds)) {
+ return new PipTransitionAnimator<Rect>(taskInfo, leash, ANIM_TYPE_BOUNDS,
+ endBounds, new Rect(baseBounds), new Rect(startBounds), new Rect(endBounds)) {
private final RectEvaluator mRectEvaluator = new RectEvaluator(new Rect());
private final RectEvaluator mInsetsEvaluator = new RectEvaluator(new Rect());
@@ -740,7 +720,6 @@ public class PipAnimationController {
// Use the bounds relative to the task leash in case the leash does not
// start from (0, 0).
final Rect relativeEndBounds = new Rect(end);
- final Point leashOffset = getLeashOffset();
relativeEndBounds.offset(-leashOffset.x, -leashOffset.y);
getSurfaceTransactionHelper()
.crop(tx, leash, relativeEndBounds)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index 0042ec954f32..72c1ef06806a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -507,8 +507,8 @@ public class PipTransition extends PipTransitionController {
}
@Override
- public void onFinishResize(@NonNull TaskInfo taskInfo, @NonNull Rect destinationBounds,
- @NonNull Point leashOffset, @PipAnimationController.TransitionDirection int direction,
+ public void onFinishResize(TaskInfo taskInfo, Rect destinationBounds,
+ @PipAnimationController.TransitionDirection int direction,
@NonNull SurfaceControl.Transaction tx) {
final boolean enteringPip = isInPipDirection(direction);
if (enteringPip) {
@@ -531,16 +531,12 @@ public class PipTransition extends PipTransitionController {
if (mFixedRotationState != FIXED_ROTATION_TRANSITION
&& mFinishTransaction != null) {
mFinishTransaction.merge(tx);
- // Set crop and position to destination bounds to avoid flickering.
+ // Set window crop and position to destination bounds to avoid flickering.
if (hasValidLeash) {
- final Rect relativeDestinationBounds = new Rect(destinationBounds);
- relativeDestinationBounds.offset(-leashOffset.x, -leashOffset.y);
- mFinishTransaction
- .setCrop(leash, relativeDestinationBounds)
- // Note that we should set the position to the start position of
- // leash then the visible region will be at the same place even if
- // the crop region doesn't start at (0, 0).
- .setPosition(leash, leashOffset.x, leashOffset.y);
+ mFinishTransaction.setWindowCrop(leash, destinationBounds.width(),
+ destinationBounds.height());
+ mFinishTransaction.setPosition(leash, destinationBounds.left,
+ destinationBounds.top);
}
}
} else {
@@ -1271,8 +1267,7 @@ public class PipTransition extends PipTransitionController {
mPipBoundsState.setBounds(destinationBounds);
final SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
- onFinishResize(pipTaskInfo, destinationBounds, animator.getLeashOffset(),
- TRANSITION_DIRECTION_TO_PIP, tx);
+ onFinishResize(pipTaskInfo, destinationBounds, TRANSITION_DIRECTION_TO_PIP, tx);
sendOnPipTransitionFinished(TRANSITION_DIRECTION_TO_PIP);
if (swipePipToHomeOverlay != null) {
mPipOrganizer.fadeOutAndRemoveOverlay(swipePipToHomeOverlay,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
index 6129651dc271..a273822759f6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
@@ -31,7 +31,6 @@ import android.app.PictureInPictureUiState;
import android.app.TaskInfo;
import android.content.ComponentName;
import android.content.pm.ActivityInfo;
-import android.graphics.Point;
import android.graphics.Rect;
import android.os.IBinder;
import android.os.RemoteException;
@@ -93,8 +92,7 @@ public abstract class PipTransitionController implements Transitions.TransitionH
mPipOrganizer.fadeOutAndRemoveOverlay(mPipOrganizer.mPipOverlay,
null /* callback */, true /* withStartDelay*/);
}
- onFinishResize(taskInfo, animator.getDestinationBounds(),
- animator.getLeashOffset(), direction, tx);
+ onFinishResize(taskInfo, animator.getDestinationBounds(), direction, tx);
sendOnPipTransitionFinished(direction);
}
@@ -114,9 +112,9 @@ public abstract class PipTransitionController implements Transitions.TransitionH
* Called when transition is about to finish. This is usually for performing tasks such as
* applying WindowContainerTransaction to finalize the PiP bounds and send to the framework.
*/
- public void onFinishResize(@NonNull TaskInfo taskInfo, @NonNull Rect destinationBounds,
- @NonNull Point leashOffset, @PipAnimationController.TransitionDirection int direction,
- @NonNull SurfaceControl.Transaction tx) {
+ public void onFinishResize(TaskInfo taskInfo, Rect destinationBounds,
+ @PipAnimationController.TransitionDirection int direction,
+ SurfaceControl.Transaction tx) {
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 4f0f6760a951..6e0e696e15fe 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -582,6 +582,7 @@ public class SplitScreenController implements SplitDragPolicy.Starter,
@Nullable WindowContainerToken hideTaskToken) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
"Legacy startTask does not support hide task token");
+ if (isTaskInSplitScreenForeground(taskId)) return;
final int[] result = new int[1];
IRemoteAnimationRunner wrapper = new IRemoteAnimationRunner.Stub() {
@Override
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/Android.bp b/libs/WindowManager/Shell/tests/flicker/pip/Android.bp
index f40edaebec5e..ddbc681f7cac 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/Android.bp
+++ b/libs/WindowManager/Shell/tests/flicker/pip/Android.bp
@@ -266,26 +266,5 @@ test_module_config {
test_suites: ["device-tests"],
}
-test_module_config {
- name: "WMShellFlickerTestsPip-nonMatchParent",
- base: "WMShellFlickerTestsPip",
- include_filters: ["com.android.wm.shell.flicker.pip.nonmatchparent.*"],
- test_suites: ["device-tests"],
-}
-
-test_module_config {
- name: "WMShellFlickerTestsPip-BottomHalfExitPipToAppViaExpandButtonTest",
- base: "WMShellFlickerTestsPip",
- include_filters: ["com.android.wm.shell.flicker.pip.nonmatchparent.BottomHalfExitPipToAppViaExpandButtonTest"],
- test_suites: ["device-tests"],
-}
-
-test_module_config {
- name: "WMShellFlickerTestsPip-BottomHalfExitPipToAppViaIntentTest",
- base: "WMShellFlickerTestsPip",
- include_filters: ["com.android.wm.shell.flicker.pip.nonmatchparent.BottomHalfExitPipToAppViaIntentTest"],
- test_suites: ["device-tests"],
-}
-
// End breakdowns for WMShellFlickerTestsPip module
////////////////////////////////////////////////////////////////////////////////
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/PipTransition.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/PipTransition.kt
index 7861d201cd29..c37bf3579e93 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/PipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/PipTransition.kt
@@ -40,6 +40,7 @@ abstract class PipTransition(flicker: LegacyFlickerTest) : BaseTest(flicker) {
@Rule
val checkFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
+ protected val pipApp = PipAppHelper(instrumentation)
protected val displayBounds = WindowUtils.getDisplayBounds(flicker.scenario.startRotation)
protected val broadcastActionTrigger = BroadcastActionTrigger(instrumentation)
@@ -62,11 +63,6 @@ abstract class PipTransition(flicker: LegacyFlickerTest) : BaseTest(flicker) {
}
}
- /**
- * Defines the test app to run PIP flicker test.
- */
- protected open val pipApp = PipAppHelper(instrumentation)
-
/** Defines the transition used to run the test */
protected open val thisTransition: FlickerBuilder.() -> Unit = {}
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/nonmatchparent/BottomHalfExitPipToAppTransition.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/nonmatchparent/BottomHalfExitPipToAppTransition.kt
deleted file mode 100644
index a6f29fc3452b..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/nonmatchparent/BottomHalfExitPipToAppTransition.kt
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.flicker.pip.nonmatchparent
-
-import android.platform.test.annotations.Presubmit
-import android.platform.test.annotations.RequiresFlagsEnabled
-import android.tools.flicker.legacy.LegacyFlickerTest
-import android.tools.traces.component.ComponentNameMatcher
-import com.android.server.wm.flicker.helpers.BottomHalfPipAppHelper
-import com.android.server.wm.flicker.helpers.PipAppHelper
-import com.android.window.flags.Flags
-import com.android.wm.shell.flicker.pip.common.ExitPipToAppTransition
-import org.junit.Test
-
-/**
- * Base test class to verify PIP exit animation with an activity layout to the bottom half of
- * the container.
- */
-@RequiresFlagsEnabled(Flags.FLAG_BETTER_SUPPORT_NON_MATCH_PARENT_ACTIVITY)
-abstract class BottomHalfExitPipToAppTransition(flicker: LegacyFlickerTest) :
- ExitPipToAppTransition(flicker) {
-
- override val pipApp: PipAppHelper = BottomHalfPipAppHelper(instrumentation)
-
- @Presubmit
- @Test
- override fun showBothAppLayersThenHidePip() {
- // Disabled since the BottomHalfPipActivity just covers half of the simple activity.
- }
-
- @Presubmit
- @Test
- override fun showBothAppWindowsThenHidePip() {
- // Disabled since the BottomHalfPipActivity just covers half of the simple activity.
- }
-
- @Presubmit
- @Test
- override fun pipAppCoversFullScreenAtEnd() {
- // Disabled since the BottomHalfPipActivity just covers half of the simple activity.
- }
-
- /**
- * Checks that the [testApp] and [pipApp] are always visible since the [pipApp] only covers
- * half of screen.
- */
- @Presubmit
- @Test
- fun showBothAppLayersDuringPipTransition() {
- flicker.assertLayers {
- isVisible(testApp)
- .isVisible(pipApp.or(ComponentNameMatcher.TRANSITION_SNAPSHOT))
- }
- }
-
- /**
- * Checks that the [testApp] and [pipApp] are always visible since the [pipApp] only covers
- * half of screen.
- */
- @Presubmit
- @Test
- fun showBothAppWindowsDuringPipTransition() {
- flicker.assertWm {
- isAppWindowVisible(testApp)
- .isAppWindowOnTop(pipApp)
- .isAppWindowVisible(pipApp)
- }
- }
-
- /**
- * Verify that the [testApp] and [pipApp] covers the entire screen at the end of PIP exit
- * animation since the [pipApp] will use a bottom half layout.
- */
- @Presubmit
- @Test
- fun testPlusPipAppCoversWindowFrameAtEnd() {
- flicker.assertLayersEnd {
- val pipRegion = visibleRegion(pipApp).region
- visibleRegion(testApp).plus(pipRegion).coversExactly(displayBounds)
- }
- }
-} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/nonmatchparent/BottomHalfExitPipToAppViaExpandButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/nonmatchparent/BottomHalfExitPipToAppViaExpandButtonTest.kt
deleted file mode 100644
index f492bd444702..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/nonmatchparent/BottomHalfExitPipToAppViaExpandButtonTest.kt
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-package com.android.wm.shell.flicker.pip.nonmatchparent
-
-import android.tools.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.flicker.legacy.FlickerBuilder
-import android.tools.flicker.legacy.LegacyFlickerTest
-import org.junit.FixMethodOrder
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test expanding a pip window back to bottom half layout via the expand button
- *
- * To run this test: `atest WMShellFlickerTestsPip:BottomHalfExitPipToAppViaExpandButtonTest`
- *
- * Actions:
- * ```
- * Launch an app in pip mode [bottomHalfPipApp],
- * Launch another full screen mode [testApp]
- * Expand [bottomHalfPipApp] app to bottom half layout by clicking on the pip window and
- * then on the expand button
- * ```
- *
- * Notes:
- * ```
- * 1. Some default assertions (e.g., nav bar, status bar and screen covered)
- * are inherited [PipTransition]
- * 2. Part of the test setup occurs automatically via
- * [android.tools.flicker.legacy.runner.TransitionRunner],
- * including configuring navigation mode, initial orientation and ensuring no
- * apps are running before setup
- * ```
- */
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class BottomHalfExitPipToAppViaExpandButtonTest(flicker: LegacyFlickerTest) :
- BottomHalfExitPipToAppTransition(flicker)
-{
- override val thisTransition: FlickerBuilder.() -> Unit = {
- setup {
- // launch an app behind the pip one
- testApp.launchViaIntent(wmHelper)
- }
- transitions {
- // This will bring PipApp to fullscreen
- pipApp.expandPipWindowToApp(wmHelper)
- // Wait until the transition idle and test and pip app still shows.
- wmHelper.StateSyncBuilder().withLayerVisible(testApp).withLayerVisible(pipApp)
- .withAppTransitionIdle().waitForAndVerify()
- }
- }
-} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/nonmatchparent/BottomHalfExitPipToAppViaIntentTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/nonmatchparent/BottomHalfExitPipToAppViaIntentTest.kt
deleted file mode 100644
index a76a647348a1..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/nonmatchparent/BottomHalfExitPipToAppViaIntentTest.kt
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.flicker.pip.nonmatchparent
-
-import android.tools.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.flicker.legacy.FlickerBuilder
-import android.tools.flicker.legacy.LegacyFlickerTest
-import org.junit.FixMethodOrder
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test expanding a pip window back to bottom half layout via an intent
- *
- * To run this test: `atest WMShellFlickerTestsPip:BottomHalfExitPipToAppViaIntentTest`
- *
- * Actions:
- * ```
- * Launch an app in pip mode [bottomHalfPipApp],
- * Launch another full screen mode [testApp]
- * Expand [bottomHalfPipApp] app to bottom half layout via an intent
- * ```
- *
- * Notes:
- * ```
- * 1. Some default assertions (e.g., nav bar, status bar and screen covered)
- * are inherited from [PipTransition]
- * 2. Part of the test setup occurs automatically via
- * [android.tools.flicker.legacy.runner.TransitionRunner],
- * including configuring navigation mode, initial orientation and ensuring no
- * apps are running before setup
- * ```
- */
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class BottomHalfExitPipToAppViaIntentTest(flicker: LegacyFlickerTest) :
- BottomHalfExitPipToAppTransition(flicker)
-{
- override val thisTransition: FlickerBuilder.() -> Unit = {
- setup {
- // launch an app behind the pip one
- testApp.launchViaIntent(wmHelper)
- }
- transitions {
- // This will bring PipApp to fullscreen
- pipApp.exitPipToFullScreenViaIntent(wmHelper)
- // Wait until the transition idle and test and pip app still shows.
- wmHelper.StateSyncBuilder().withLayerVisible(testApp).withLayerVisible(pipApp)
- .withAppTransitionIdle().waitForAndVerify()
- }
- }
-} \ No newline at end of file
diff --git a/media/java/android/media/AudioPlaybackConfiguration.java b/media/java/android/media/AudioPlaybackConfiguration.java
index 66da03144a7d..dba9cc95d902 100644
--- a/media/java/android/media/AudioPlaybackConfiguration.java
+++ b/media/java/android/media/AudioPlaybackConfiguration.java
@@ -283,8 +283,19 @@ public final class AudioPlaybackConfiguration implements Parcelable {
* Flag used when playback is muted by AppOpsManager#OP_PLAY_AUDIO.
*/
@SystemApi
+ @FlaggedApi(FLAG_MUTED_BY_PORT_VOLUME_API)
+ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+ public static final int MUTED_BY_OP_PLAY_AUDIO = (1 << 3);
+ /**
+ * @hide
+ * Flag used when playback is muted by AppOpsManager#OP_PLAY_AUDIO.
+ * @deprecated see {@link MUTED_BY_OP_PLAY_AUDIO}
+ */
+ @SystemApi
+ @Deprecated
+ @FlaggedApi(FLAG_MUTED_BY_PORT_VOLUME_API)
@RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
- public static final int MUTED_BY_APP_OPS = (1 << 3);
+ public static final int MUTED_BY_APP_OPS = MUTED_BY_OP_PLAY_AUDIO;
/**
* @hide
* Flag used when muted by client volume.
@@ -324,7 +335,7 @@ public final class AudioPlaybackConfiguration implements Parcelable {
@IntDef(
flag = true,
value = {MUTED_BY_MASTER, MUTED_BY_STREAM_VOLUME, MUTED_BY_STREAM_MUTED,
- MUTED_BY_APP_OPS, MUTED_BY_CLIENT_VOLUME, MUTED_BY_VOLUME_SHAPER,
+ MUTED_BY_OP_PLAY_AUDIO, MUTED_BY_CLIENT_VOLUME, MUTED_BY_VOLUME_SHAPER,
MUTED_BY_PORT_VOLUME, MUTED_BY_OP_CONTROL_AUDIO})
@Retention(RetentionPolicy.SOURCE)
public @interface PlayerMuteEvent {
@@ -770,7 +781,7 @@ public final class AudioPlaybackConfiguration implements Parcelable {
private boolean isMuteAffectingActiveState() {
return (mMutedState & MUTED_BY_CLIENT_VOLUME) != 0
|| (mMutedState & MUTED_BY_VOLUME_SHAPER) != 0
- || (mMutedState & MUTED_BY_APP_OPS) != 0;
+ || (mMutedState & MUTED_BY_OP_PLAY_AUDIO) != 0;
}
/**
@@ -911,8 +922,8 @@ public final class AudioPlaybackConfiguration implements Parcelable {
if ((mMutedState & MUTED_BY_STREAM_MUTED) != 0) {
apcToString.append("streamMute ");
}
- if ((mMutedState & MUTED_BY_APP_OPS) != 0) {
- apcToString.append("appOps ");
+ if ((mMutedState & MUTED_BY_OP_PLAY_AUDIO) != 0) {
+ apcToString.append("opPlayAudio ");
}
if ((mMutedState & MUTED_BY_CLIENT_VOLUME) != 0) {
apcToString.append("clientVolume ");
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/activity/data/repository/ActivityManagerRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/activity/data/repository/ActivityManagerRepositoryTest.kt
new file mode 100644
index 000000000000..d6ba98d65d15
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/activity/data/repository/ActivityManagerRepositoryTest.kt
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2024 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.activity.data.repository
+
+import android.app.ActivityManager
+import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND
+import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE
+import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_TOP_SLEEPING
+import android.app.activityManager
+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.collectLastValue
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.log.core.Logger
+import com.android.systemui.log.logcatLogBuffer
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ActivityManagerRepositoryTest : SysuiTestCase() {
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
+ private val logger = Logger(logcatLogBuffer("ActivityManagerRepositoryTest"), "tag")
+
+ private val Kosmos.underTest by Kosmos.Fixture { realActivityManagerRepository }
+
+ @Test
+ fun createIsAppVisibleFlow_fetchesInitialValue_true() =
+ kosmos.runTest {
+ whenever(activityManager.getUidImportance(THIS_UID)).thenReturn(IMPORTANCE_FOREGROUND)
+
+ val latest by
+ collectLastValue(underTest.createIsAppVisibleFlow(THIS_UID, logger, LOG_TAG))
+
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ fun createIsAppVisibleFlow_fetchesInitialValue_false() =
+ kosmos.runTest {
+ whenever(activityManager.getUidImportance(THIS_UID)).thenReturn(IMPORTANCE_GONE)
+
+ val latest by
+ collectLastValue(underTest.createIsAppVisibleFlow(THIS_UID, logger, LOG_TAG))
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun createIsAppVisibleFlow_getsImportanceUpdates() =
+ kosmos.runTest {
+ val latest by
+ collectLastValue(underTest.createIsAppVisibleFlow(THIS_UID, logger, LOG_TAG))
+
+ val listenerCaptor = argumentCaptor<ActivityManager.OnUidImportanceListener>()
+ verify(activityManager).addOnUidImportanceListener(listenerCaptor.capture(), any())
+ val listener = listenerCaptor.firstValue
+
+ listener.onUidImportance(THIS_UID, IMPORTANCE_GONE)
+ assertThat(latest).isFalse()
+
+ listener.onUidImportance(THIS_UID, IMPORTANCE_FOREGROUND)
+ assertThat(latest).isTrue()
+
+ listener.onUidImportance(THIS_UID, IMPORTANCE_TOP_SLEEPING)
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun createIsAppVisibleFlow_ignoresUpdatesForOtherUids() =
+ kosmos.runTest {
+ val latest by
+ collectLastValue(underTest.createIsAppVisibleFlow(THIS_UID, logger, LOG_TAG))
+
+ val listenerCaptor = argumentCaptor<ActivityManager.OnUidImportanceListener>()
+ verify(activityManager).addOnUidImportanceListener(listenerCaptor.capture(), any())
+ val listener = listenerCaptor.firstValue
+
+ listener.onUidImportance(THIS_UID, IMPORTANCE_GONE)
+ assertThat(latest).isFalse()
+
+ // WHEN another UID becomes foreground
+ listener.onUidImportance(THIS_UID + 2, IMPORTANCE_FOREGROUND)
+
+ // THEN this UID still stays not visible
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun createIsAppVisibleFlow_securityExceptionOnUidRegistration_ok() =
+ kosmos.runTest {
+ whenever(activityManager.getUidImportance(THIS_UID)).thenReturn(IMPORTANCE_GONE)
+ whenever(activityManager.addOnUidImportanceListener(any(), any()))
+ .thenThrow(SecurityException())
+
+ val latest by
+ collectLastValue(underTest.createIsAppVisibleFlow(THIS_UID, logger, LOG_TAG))
+
+ // Verify no crash, and we get a value emitted
+ assertThat(latest).isFalse()
+ }
+
+ /** Regression test for b/216248574. */
+ @Test
+ fun createIsAppVisibleFlow_getUidImportanceThrowsException_ok() =
+ kosmos.runTest {
+ whenever(activityManager.getUidImportance(any())).thenThrow(SecurityException())
+
+ val latest by
+ collectLastValue(underTest.createIsAppVisibleFlow(THIS_UID, logger, LOG_TAG))
+
+ // Verify no crash, and we get a value emitted
+ assertThat(latest).isFalse()
+ }
+
+ companion object {
+ private const val THIS_UID = 558
+ private const val LOG_TAG = "LogTag"
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduUiCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduUiCoordinatorTest.kt
index dd8370231ef0..e288522ec212 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduUiCoordinatorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduUiCoordinatorTest.kt
@@ -25,6 +25,7 @@ import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.contextualeducation.GestureType
import com.android.systemui.contextualeducation.GestureType.BACK
+import com.android.systemui.contextualeducation.GestureType.HOME
import com.android.systemui.education.data.repository.fakeEduClock
import com.android.systemui.education.domain.interactor.KeyboardTouchpadEduInteractor
import com.android.systemui.education.domain.interactor.contextualEducationInteractor
@@ -52,6 +53,7 @@ import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
import org.mockito.junit.MockitoJUnit
import org.mockito.kotlin.any
+import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
@@ -66,6 +68,7 @@ class ContextualEduUiCoordinatorTest : SysuiTestCase() {
private val minDurationForNextEdu =
KeyboardTouchpadEduInteractor.minIntervalBetweenEdu + 1.seconds
private lateinit var underTest: ContextualEduUiCoordinator
+ private lateinit var previousDialog: Dialog
@Mock private lateinit var dialog: Dialog
@Mock private lateinit var notificationManager: NotificationManager
@Mock private lateinit var accessibilityManagerWrapper: AccessibilityManagerWrapper
@@ -95,9 +98,11 @@ class ContextualEduUiCoordinatorTest : SysuiTestCase() {
kosmos.applicationCoroutineScope,
viewModel,
kosmos.applicationContext,
- notificationManager
+ notificationManager,
) { model ->
toastContent = model.message
+ previousDialog = dialog
+ dialog = mock<Dialog>()
dialog
}
underTest.start()
@@ -129,6 +134,14 @@ class ContextualEduUiCoordinatorTest : SysuiTestCase() {
}
@Test
+ fun dismissPreviousDialogOnNewDialog() =
+ testScope.runTest {
+ triggerEducation(BACK)
+ triggerEducation(HOME)
+ verify(previousDialog).dismiss()
+ }
+
+ @Test
fun verifyBackEduToastContent() =
testScope.runTest {
triggerEducation(BACK)
@@ -149,14 +162,14 @@ class ContextualEduUiCoordinatorTest : SysuiTestCase() {
verifyNotificationContent(
R.string.back_edu_notification_title,
R.string.back_edu_notification_content,
- notificationCaptor.value
+ notificationCaptor.value,
)
}
private fun verifyNotificationContent(
titleResId: Int,
contentResId: Int,
- notification: Notification
+ notification: Notification,
) {
val expectedContent = context.getString(contentResId)
val expectedTitle = context.getString(titleResId)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractorTest.kt
new file mode 100644
index 000000000000..7fed47a4653e
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractorTest.kt
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2024 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.chips.notification.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.activity.data.repository.activityManagerRepository
+import com.android.systemui.activity.data.repository.fake
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.statusbar.StatusBarIconView
+import com.android.systemui.statusbar.notification.data.model.activeNotificationModel
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class SingleNotificationChipInteractorTest : SysuiTestCase() {
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
+ val factory = kosmos.singleNotificationChipInteractorFactory
+
+ @Test
+ fun notificationChip_startsWithStartingModel() =
+ kosmos.runTest {
+ val icon = mock<StatusBarIconView>()
+ val startingNotif = activeNotificationModel(key = "notif1", statusBarChipIcon = icon)
+
+ val underTest = factory.create(startingNotif)
+
+ val latest by collectLastValue(underTest.notificationChip)
+
+ assertThat(latest!!.key).isEqualTo("notif1")
+ assertThat(latest!!.statusBarChipIconView).isEqualTo(icon)
+ }
+
+ @Test
+ fun notificationChip_updatesAfterSet() =
+ kosmos.runTest {
+ val originalIconView = mock<StatusBarIconView>()
+ val underTest =
+ factory.create(
+ activeNotificationModel(key = "notif1", statusBarChipIcon = originalIconView)
+ )
+
+ val latest by collectLastValue(underTest.notificationChip)
+
+ val newIconView = mock<StatusBarIconView>()
+ underTest.setNotification(
+ activeNotificationModel(key = "notif1", statusBarChipIcon = newIconView)
+ )
+
+ assertThat(latest!!.key).isEqualTo("notif1")
+ assertThat(latest!!.statusBarChipIconView).isEqualTo(newIconView)
+ }
+
+ @Test
+ fun notificationChip_ignoresSetWithDifferentKey() =
+ kosmos.runTest {
+ val originalIconView = mock<StatusBarIconView>()
+ val underTest =
+ factory.create(
+ activeNotificationModel(key = "notif1", statusBarChipIcon = originalIconView)
+ )
+
+ val latest by collectLastValue(underTest.notificationChip)
+
+ val newIconView = mock<StatusBarIconView>()
+ underTest.setNotification(
+ activeNotificationModel(key = "other_notif", statusBarChipIcon = newIconView)
+ )
+
+ assertThat(latest!!.key).isEqualTo("notif1")
+ assertThat(latest!!.statusBarChipIconView).isEqualTo(originalIconView)
+ }
+
+ @Test
+ fun notificationChip_missingStatusBarIconChipView_inConstructor_emitsNull() =
+ kosmos.runTest {
+ val underTest =
+ factory.create(activeNotificationModel(key = "notif1", statusBarChipIcon = null))
+
+ val latest by collectLastValue(underTest.notificationChip)
+
+ assertThat(latest).isNull()
+ }
+
+ @Test
+ fun notificationChip_missingStatusBarIconChipView_inSet_emitsNull() =
+ kosmos.runTest {
+ val startingNotif = activeNotificationModel(key = "notif1", statusBarChipIcon = mock())
+ val underTest = factory.create(startingNotif)
+ val latest by collectLastValue(underTest.notificationChip)
+ assertThat(latest).isNotNull()
+
+ underTest.setNotification(
+ activeNotificationModel(key = "notif1", statusBarChipIcon = null)
+ )
+
+ assertThat(latest).isNull()
+ }
+
+ @Test
+ fun notificationChip_appIsVisibleOnCreation_emitsNull() =
+ kosmos.runTest {
+ activityManagerRepository.fake.startingIsAppVisibleValue = true
+
+ val underTest =
+ factory.create(
+ activeNotificationModel(key = "notif", uid = UID, statusBarChipIcon = mock())
+ )
+
+ val latest by collectLastValue(underTest.notificationChip)
+
+ assertThat(latest).isNull()
+ }
+
+ @Test
+ fun notificationChip_appNotVisibleOnCreation_emitsValue() =
+ kosmos.runTest {
+ activityManagerRepository.fake.startingIsAppVisibleValue = false
+
+ val underTest =
+ factory.create(
+ activeNotificationModel(key = "notif", uid = UID, statusBarChipIcon = mock())
+ )
+
+ val latest by collectLastValue(underTest.notificationChip)
+
+ assertThat(latest).isNotNull()
+ }
+
+ @Test
+ fun notificationChip_hidesWhenAppIsVisible() =
+ kosmos.runTest {
+ val underTest =
+ factory.create(
+ activeNotificationModel(key = "notif", uid = UID, statusBarChipIcon = mock())
+ )
+
+ val latest by collectLastValue(underTest.notificationChip)
+
+ activityManagerRepository.fake.setIsAppVisible(UID, false)
+ assertThat(latest).isNotNull()
+
+ activityManagerRepository.fake.setIsAppVisible(UID, true)
+ assertThat(latest).isNull()
+
+ activityManagerRepository.fake.setIsAppVisible(UID, false)
+ assertThat(latest).isNotNull()
+ }
+
+ // Note: This test is theoretically impossible because the notification key should contain the
+ // UID, so if the UID changes then the key would also change and a new interactor would be
+ // created. But, test it just in case.
+ @Test
+ fun notificationChip_updatedUid_rechecksAppVisibility_oldObserverUnregistered() =
+ kosmos.runTest {
+ activityManagerRepository.fake.startingIsAppVisibleValue = false
+
+ val hiddenUid = 100
+ val shownUid = 101
+
+ val underTest =
+ factory.create(
+ activeNotificationModel(
+ key = "notif",
+ uid = hiddenUid,
+ statusBarChipIcon = mock(),
+ )
+ )
+ val latest by collectLastValue(underTest.notificationChip)
+ assertThat(latest).isNotNull()
+
+ // WHEN the notif gets a new UID that starts as visible
+ activityManagerRepository.fake.startingIsAppVisibleValue = true
+ underTest.setNotification(
+ activeNotificationModel(key = "notif", uid = shownUid, statusBarChipIcon = mock())
+ )
+
+ // THEN we re-fetch the app visibility state with the new UID, and since that UID is
+ // visible, we hide the chip
+ assertThat(latest).isNull()
+ }
+
+ companion object {
+ private const val UID = 885
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractorTest.kt
index 19ed6a57d2f0..702e101d2d39 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractorTest.kt
@@ -16,30 +16,277 @@
package com.android.systemui.statusbar.chips.notification.domain.interactor
+import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
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.kosmos.testScope
import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
+import com.android.systemui.statusbar.notification.data.model.activeNotificationModel
+import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore
+import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
+import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlin.test.Test
import kotlinx.coroutines.test.runTest
import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
@SmallTest
@RunWith(AndroidJUnit4::class)
-@EnableFlags(StatusBarNotifChips.FLAG_NAME)
class StatusBarNotificationChipsInteractorTest : SysuiTestCase() {
private val kosmos = testKosmos().useUnconfinedTestDispatcher()
private val testScope = kosmos.testScope
+ private val activeNotificationListRepository = kosmos.activeNotificationListRepository
- private val underTest = kosmos.statusBarNotificationChipsInteractor
+ private val underTest by lazy {
+ kosmos.statusBarNotificationChipsInteractor.also { it.start() }
+ }
@Test
+ @DisableFlags(StatusBarNotifChips.FLAG_NAME)
+ fun notificationChips_flagOff_noNotifs() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.notificationChips)
+
+ setNotifs(
+ listOf(
+ activeNotificationModel(
+ key = "notif",
+ statusBarChipIcon = mock<StatusBarIconView>(),
+ isPromoted = true,
+ )
+ )
+ )
+
+ assertThat(latest).isEmpty()
+ }
+
+ @Test
+ @EnableFlags(StatusBarNotifChips.FLAG_NAME)
+ fun notificationChips_noNotifs_empty() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.notificationChips)
+
+ setNotifs(emptyList())
+
+ assertThat(latest).isEmpty()
+ }
+
+ @Test
+ @EnableFlags(StatusBarNotifChips.FLAG_NAME)
+ fun notificationChips_notifMissingStatusBarChipIconView_empty() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.notificationChips)
+
+ setNotifs(
+ listOf(
+ activeNotificationModel(
+ key = "notif",
+ statusBarChipIcon = null,
+ isPromoted = true,
+ )
+ )
+ )
+
+ assertThat(latest).isEmpty()
+ }
+
+ @Test
+ @EnableFlags(StatusBarNotifChips.FLAG_NAME)
+ fun notificationChips_onePromotedNotif_statusBarIconViewMatches() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.notificationChips)
+
+ val icon = mock<StatusBarIconView>()
+ setNotifs(
+ listOf(
+ activeNotificationModel(
+ key = "notif",
+ statusBarChipIcon = icon,
+ isPromoted = true,
+ )
+ )
+ )
+
+ assertThat(latest).hasSize(1)
+ assertThat(latest!![0].key).isEqualTo("notif")
+ assertThat(latest!![0].statusBarChipIconView).isEqualTo(icon)
+ }
+
+ @Test
+ @EnableFlags(StatusBarNotifChips.FLAG_NAME)
+ fun notificationChips_onlyForPromotedNotifs() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.notificationChips)
+
+ val firstIcon = mock<StatusBarIconView>()
+ val secondIcon = mock<StatusBarIconView>()
+ setNotifs(
+ listOf(
+ activeNotificationModel(
+ key = "notif1",
+ statusBarChipIcon = firstIcon,
+ isPromoted = true,
+ ),
+ activeNotificationModel(
+ key = "notif2",
+ statusBarChipIcon = secondIcon,
+ isPromoted = true,
+ ),
+ activeNotificationModel(
+ key = "notif3",
+ statusBarChipIcon = mock<StatusBarIconView>(),
+ isPromoted = false,
+ ),
+ )
+ )
+
+ assertThat(latest).hasSize(2)
+ assertThat(latest!![0].key).isEqualTo("notif1")
+ assertThat(latest!![0].statusBarChipIconView).isEqualTo(firstIcon)
+ assertThat(latest!![1].key).isEqualTo("notif2")
+ assertThat(latest!![1].statusBarChipIconView).isEqualTo(secondIcon)
+ }
+
+ @Test
+ @EnableFlags(StatusBarNotifChips.FLAG_NAME)
+ fun notificationChips_notifUpdatesGoThrough() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.notificationChips)
+
+ val firstIcon = mock<StatusBarIconView>()
+ val secondIcon = mock<StatusBarIconView>()
+ val thirdIcon = mock<StatusBarIconView>()
+
+ setNotifs(
+ listOf(
+ activeNotificationModel(
+ key = "notif",
+ statusBarChipIcon = firstIcon,
+ isPromoted = true,
+ )
+ )
+ )
+ assertThat(latest).hasSize(1)
+ assertThat(latest!![0].key).isEqualTo("notif")
+ assertThat(latest!![0].statusBarChipIconView).isEqualTo(firstIcon)
+
+ setNotifs(
+ listOf(
+ activeNotificationModel(
+ key = "notif",
+ statusBarChipIcon = secondIcon,
+ isPromoted = true,
+ )
+ )
+ )
+ assertThat(latest).hasSize(1)
+ assertThat(latest!![0].key).isEqualTo("notif")
+ assertThat(latest!![0].statusBarChipIconView).isEqualTo(secondIcon)
+
+ setNotifs(
+ listOf(
+ activeNotificationModel(
+ key = "notif",
+ statusBarChipIcon = thirdIcon,
+ isPromoted = true,
+ )
+ )
+ )
+ assertThat(latest).hasSize(1)
+ assertThat(latest!![0].key).isEqualTo("notif")
+ assertThat(latest!![0].statusBarChipIconView).isEqualTo(thirdIcon)
+ }
+
+ @Test
+ @EnableFlags(StatusBarNotifChips.FLAG_NAME)
+ fun notificationChips_promotedNotifDisappearsThenReappears() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.notificationChips)
+
+ setNotifs(
+ listOf(
+ activeNotificationModel(
+ key = "notif",
+ statusBarChipIcon = mock(),
+ isPromoted = true,
+ )
+ )
+ )
+ assertThat(latest).hasSize(1)
+ assertThat(latest!![0].key).isEqualTo("notif")
+
+ setNotifs(
+ listOf(
+ activeNotificationModel(
+ key = "notif",
+ statusBarChipIcon = mock(),
+ isPromoted = false,
+ )
+ )
+ )
+ assertThat(latest).isEmpty()
+
+ setNotifs(
+ listOf(
+ activeNotificationModel(
+ key = "notif",
+ statusBarChipIcon = mock(),
+ isPromoted = true,
+ )
+ )
+ )
+ assertThat(latest).hasSize(1)
+ assertThat(latest!![0].key).isEqualTo("notif")
+ }
+
+ @Test
+ @EnableFlags(StatusBarNotifChips.FLAG_NAME)
+ fun notificationChips_notifChangesKey() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.notificationChips)
+
+ val firstIcon = mock<StatusBarIconView>()
+ val secondIcon = mock<StatusBarIconView>()
+ setNotifs(
+ listOf(
+ activeNotificationModel(
+ key = "notif|uid1",
+ statusBarChipIcon = firstIcon,
+ isPromoted = true,
+ )
+ )
+ )
+ assertThat(latest).hasSize(1)
+ assertThat(latest!![0].key).isEqualTo("notif|uid1")
+ assertThat(latest!![0].statusBarChipIconView).isEqualTo(firstIcon)
+
+ // WHEN a notification changes UID, which is a key change
+ setNotifs(
+ listOf(
+ activeNotificationModel(
+ key = "notif|uid2",
+ statusBarChipIcon = secondIcon,
+ isPromoted = true,
+ )
+ )
+ )
+
+ // THEN we correctly update
+ assertThat(latest).hasSize(1)
+ assertThat(latest!![0].key).isEqualTo("notif|uid2")
+ assertThat(latest!![0].statusBarChipIconView).isEqualTo(secondIcon)
+ }
+
+ @Test
+ @EnableFlags(StatusBarNotifChips.FLAG_NAME)
fun onPromotedNotificationChipTapped_emitsKeys() =
testScope.runTest {
val latest by collectValues(underTest.promotedNotificationChipTapEvent)
@@ -56,6 +303,7 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() {
}
@Test
+ @EnableFlags(StatusBarNotifChips.FLAG_NAME)
fun onPromotedNotificationChipTapped_sameKeyTwice_emitsTwice() =
testScope.runTest {
val latest by collectValues(underTest.promotedNotificationChipTapEvent)
@@ -67,4 +315,11 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() {
assertThat(latest[0]).isEqualTo("fakeKey")
assertThat(latest[1]).isEqualTo("fakeKey")
}
+
+ private fun setNotifs(notifs: List<ActiveNotificationModel>) {
+ activeNotificationListRepository.activeNotifications.value =
+ ActiveNotificationsStore.Builder()
+ .apply { notifs.forEach { addIndividualNotif(it) } }
+ .build()
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt
index 1b4132910555..16376c5b3850 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt
@@ -22,7 +22,9 @@ 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.kosmos.testScope
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.chips.notification.domain.interactor.statusBarNotificationChipsInteractor
import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
@@ -34,26 +36,28 @@ import com.android.systemui.statusbar.notification.shared.ActiveNotificationMode
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlin.test.Test
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
+import org.junit.Before
import org.junit.runner.RunWith
import org.mockito.kotlin.mock
@SmallTest
@RunWith(AndroidJUnit4::class)
-@OptIn(ExperimentalCoroutinesApi::class)
@EnableFlags(StatusBarNotifChips.FLAG_NAME)
class NotifChipsViewModelTest : SysuiTestCase() {
- private val kosmos = testKosmos()
- private val testScope = kosmos.testScope
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
private val activeNotificationListRepository = kosmos.activeNotificationListRepository
- private val underTest = kosmos.notifChipsViewModel
+ private val underTest by lazy { kosmos.notifChipsViewModel }
+
+ @Before
+ fun setUp() {
+ kosmos.statusBarNotificationChipsInteractor.start()
+ }
@Test
fun chips_noNotifs_empty() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.chips)
setNotifs(emptyList())
@@ -63,7 +67,7 @@ class NotifChipsViewModelTest : SysuiTestCase() {
@Test
fun chips_notifMissingStatusBarChipIconView_empty() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.chips)
setNotifs(
@@ -81,7 +85,7 @@ class NotifChipsViewModelTest : SysuiTestCase() {
@Test
fun chips_onePromotedNotif_statusBarIconViewMatches() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.chips)
val icon = mock<StatusBarIconView>()
@@ -103,7 +107,7 @@ class NotifChipsViewModelTest : SysuiTestCase() {
@Test
fun chips_onlyForPromotedNotifs() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.chips)
val firstIcon = mock<StatusBarIconView>()
@@ -135,7 +139,7 @@ class NotifChipsViewModelTest : SysuiTestCase() {
@Test
fun chips_clickingChipNotifiesInteractor() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.chips)
val latestChipTap by
collectLastValue(
@@ -163,7 +167,6 @@ class NotifChipsViewModelTest : SysuiTestCase() {
ActiveNotificationsStore.Builder()
.apply { notifs.forEach { addIndividualNotif(it) } }
.build()
- testScope.runCurrent()
}
companion object {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt
index 25d5ce50e03f..eb0978eff24b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt
@@ -27,6 +27,7 @@ import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.mediaprojection.data.model.MediaProjectionState
import com.android.systemui.mediaprojection.data.repository.fakeMediaProjectionRepository
import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager.Companion.createTask
@@ -38,6 +39,7 @@ import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.Me
import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractorTest.Companion.setUpPackageManagerForMediaProjection
import com.android.systemui.statusbar.chips.notification.demo.ui.viewmodel.DemoNotifChipViewModelTest.Companion.addDemoNotifChip
import com.android.systemui.statusbar.chips.notification.demo.ui.viewmodel.demoNotifChipViewModel
+import com.android.systemui.statusbar.chips.notification.domain.interactor.statusBarNotificationChipsInteractor
import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
import com.android.systemui.statusbar.chips.notification.ui.viewmodel.NotifChipsViewModelTest.Companion.assertIsNotifChip
import com.android.systemui.statusbar.chips.ui.model.MultipleOngoingActivityChipsModel
@@ -67,6 +69,7 @@ import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
+import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.any
@@ -79,7 +82,7 @@ import org.mockito.kotlin.whenever
@OptIn(ExperimentalCoroutinesApi::class)
@EnableFlags(StatusBarNotifChips.FLAG_NAME)
class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
- private val kosmos = testKosmos()
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
private val testScope = kosmos.testScope
private val systemClock = kosmos.fakeSystemClock
private val commandRegistry = kosmos.commandRegistry
@@ -103,12 +106,13 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
.thenReturn(chipBackgroundView)
}
- private val underTest = kosmos.ongoingActivityChipsViewModel
+ private val underTest by lazy { kosmos.ongoingActivityChipsViewModel }
@Before
fun setUp() {
setUpPackageManagerForMediaProjection(kosmos)
kosmos.demoNotifChipViewModel.start()
+ kosmos.statusBarNotificationChipsInteractor.start()
val icon =
BitmapDrawable(
context.resources,
@@ -616,6 +620,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
}
@Test
+ @Ignore("b/364653005") // We'll need to re-do the animation story when we implement RON chips
fun primaryChip_screenRecordStoppedViaDialog_chipHiddenWithoutAnimation() =
testScope.runTest {
screenRecordState.value = ScreenRecordModel.Recording
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
index e5d2cf65a389..1d7f25784327 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
@@ -83,7 +83,9 @@ import org.mockito.MockitoAnnotations
class HeadsUpCoordinatorTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
- private val statusBarNotificationChipsInteractor = kosmos.statusBarNotificationChipsInteractor
+ private val statusBarNotificationChipsInteractor by lazy {
+ kosmos.statusBarNotificationChipsInteractor
+ }
private val notifCollection = kosmos.mockNotifCollection
private lateinit var coordinator: HeadsUpCoordinator
diff --git a/packages/SystemUI/src/com/android/systemui/activity/ActivityManagerModule.kt b/packages/SystemUI/src/com/android/systemui/activity/ActivityManagerModule.kt
new file mode 100644
index 000000000000..db315e4e0bf7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/activity/ActivityManagerModule.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2024 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.activity
+
+import com.android.systemui.activity.data.repository.ActivityManagerRepository
+import com.android.systemui.activity.data.repository.ActivityManagerRepositoryImpl
+import com.android.systemui.dagger.SysUISingleton
+import dagger.Binds
+import dagger.Module
+
+@Module
+interface ActivityManagerModule {
+ @Binds
+ @SysUISingleton
+ fun activityManagerRepository(impl: ActivityManagerRepositoryImpl): ActivityManagerRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/activity/data/repository/ActivityManagerRepository.kt b/packages/SystemUI/src/com/android/systemui/activity/data/repository/ActivityManagerRepository.kt
new file mode 100644
index 000000000000..94614b70beda
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/activity/data/repository/ActivityManagerRepository.kt
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2024 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.activity.data.repository
+
+import android.app.ActivityManager
+import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.log.core.Logger
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
+import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.onStart
+
+/** Repository for interfacing with [ActivityManager]. */
+interface ActivityManagerRepository {
+ /**
+ * Given a UID, creates a flow that emits true when the process with the given UID is visible to
+ * the user and false otherwise.
+ *
+ * @param identifyingLogTag a tag identifying who created this flow, used for logging.
+ */
+ fun createIsAppVisibleFlow(
+ creationUid: Int,
+ logger: Logger,
+ identifyingLogTag: String,
+ ): Flow<Boolean>
+}
+
+@SysUISingleton
+class ActivityManagerRepositoryImpl
+@Inject
+constructor(
+ @Background private val backgroundContext: CoroutineContext,
+ private val activityManager: ActivityManager,
+) : ActivityManagerRepository {
+ override fun createIsAppVisibleFlow(
+ creationUid: Int,
+ logger: Logger,
+ identifyingLogTag: String,
+ ): Flow<Boolean> {
+ return conflatedCallbackFlow {
+ val listener =
+ object : ActivityManager.OnUidImportanceListener {
+ override fun onUidImportance(uid: Int, importance: Int) {
+ if (uid != creationUid) {
+ return
+ }
+ val isAppVisible = isAppVisibleToUser(importance)
+ logger.d({
+ "$str1: #onUidImportance. importance=$int1, isAppVisible=$bool1"
+ }) {
+ str1 = identifyingLogTag
+ int1 = importance
+ bool1 = isAppVisible
+ }
+ trySend(isAppVisible)
+ }
+ }
+ try {
+ // TODO(b/286258140): Replace this with the #addOnUidImportanceListener
+ // overload that filters to certain UIDs.
+ activityManager.addOnUidImportanceListener(listener, IMPORTANCE_CUTPOINT)
+ } catch (e: SecurityException) {
+ logger.e({ "$str1: Security exception on #addOnUidImportanceListener" }, e) {
+ str1 = identifyingLogTag
+ }
+ }
+
+ awaitClose { activityManager.removeOnUidImportanceListener(listener) }
+ }
+ .distinctUntilChanged()
+ .onStart {
+ try {
+ val isVisibleOnStart =
+ isAppVisibleToUser(activityManager.getUidImportance(creationUid))
+ logger.d({ "$str1: Starting UID observation. isAppVisible=$bool1" }) {
+ str1 = identifyingLogTag
+ bool1 = isVisibleOnStart
+ }
+ emit(isVisibleOnStart)
+ } catch (e: SecurityException) {
+ logger.e({ "$str1: Security exception on #getUidImportance" }, e) {
+ str1 = identifyingLogTag
+ }
+ emit(false)
+ }
+ }
+ .flowOn(backgroundContext)
+ }
+
+ /** Returns true if the given [importance] represents an app that's visible to the user. */
+ private fun isAppVisibleToUser(importance: Int): Boolean {
+ return importance <= IMPORTANCE_CUTPOINT
+ }
+
+ companion object {
+ private const val IMPORTANCE_CUTPOINT = IMPORTANCE_FOREGROUND
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 6a777ee7417b..d6f8957ace33 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -32,6 +32,7 @@ import com.android.systemui.BootCompleteCacheImpl;
import com.android.systemui.CameraProtectionModule;
import com.android.systemui.CoreStartable;
import com.android.systemui.SystemUISecondaryUserService;
+import com.android.systemui.activity.ActivityManagerModule;
import com.android.systemui.ambient.dagger.AmbientModule;
import com.android.systemui.appops.dagger.AppOpsModule;
import com.android.systemui.assist.AssistModule;
@@ -198,6 +199,7 @@ import javax.inject.Named;
* may not appreciate that.
*/
@Module(includes = {
+ ActivityManagerModule.class,
AmbientModule.class,
AppOpsModule.class,
AssistModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/education/ui/view/ContextualEduUiCoordinator.kt b/packages/SystemUI/src/com/android/systemui/education/ui/view/ContextualEduUiCoordinator.kt
index 2a3729b1aeab..c49ba80c660b 100644
--- a/packages/SystemUI/src/com/android/systemui/education/ui/view/ContextualEduUiCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/ui/view/ContextualEduUiCoordinator.kt
@@ -27,6 +27,7 @@ import android.os.Bundle
import android.os.UserHandle
import android.view.accessibility.AccessibilityManager
import androidx.core.app.NotificationCompat
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
@@ -39,7 +40,6 @@ import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutoria
import com.android.systemui.res.R
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
-import com.android.app.tracing.coroutines.launchTraced as launch
/**
* A class to show contextual education on UI based on the edu produced from
@@ -107,6 +107,7 @@ constructor(
}
private fun showDialog(model: ContextualEduToastViewModel) {
+ dialog?.dismiss()
dialog = createDialog(model)
dialog?.show()
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
index 49ceba834dd4..31780a56f7f0 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
@@ -37,9 +37,11 @@ import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LifecycleRegistry
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.compose.theme.PlatformTheme
import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.Flags
+import com.android.systemui.Flags.communalHubOnMobile
import com.android.systemui.ambient.touch.TouchMonitor
import com.android.systemui.ambient.touch.dagger.AmbientTouchComponent
import com.android.systemui.communal.dagger.Communal
@@ -70,7 +72,6 @@ import java.util.function.Consumer
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
-import com.android.app.tracing.coroutines.launchTraced as launch
/**
* Controller that's responsible for the glanceable hub container view and its touch handling.
@@ -513,14 +514,19 @@ constructor(
val touchOnUmo = keyguardMediaController.isWithinMediaViewBounds(ev.x.toInt(), ev.y.toInt())
val touchOnSmartspace =
lockscreenSmartspaceController.isWithinSmartspaceBounds(ev.x.toInt(), ev.y.toInt())
- if (!hubShowing && (touchOnNotifications || touchOnUmo || touchOnSmartspace)) {
+ val glanceableHubV2 = communalHubOnMobile()
+ if (
+ !hubShowing &&
+ (touchOnNotifications || touchOnUmo || touchOnSmartspace || glanceableHubV2)
+ ) {
logger.d({
"Lockscreen touch ignored: touchOnNotifications: $bool1, touchOnUmo: $bool2, " +
- "touchOnSmartspace: $bool3"
+ "touchOnSmartspace: $bool3, glanceableHubV2: $bool4"
}) {
bool1 = touchOnNotifications
bool2 = touchOnUmo
bool3 = touchOnSmartspace
+ bool4 = glanceableHubV2
}
return false
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/StatusBarChipsModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/StatusBarChipsModule.kt
index 8ce0dbf8e171..6db610bbc3a6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/StatusBarChipsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/StatusBarChipsModule.kt
@@ -21,7 +21,10 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogBufferFactory
import com.android.systemui.statusbar.chips.notification.demo.ui.viewmodel.DemoNotifChipViewModel
+import com.android.systemui.statusbar.chips.notification.domain.interactor.StatusBarNotificationChipsInteractor
+import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
import dagger.Binds
+import dagger.Lazy
import dagger.Module
import dagger.Provides
import dagger.multibindings.ClassKey
@@ -41,5 +44,19 @@ abstract class StatusBarChipsModule {
fun provideChipsLogBuffer(factory: LogBufferFactory): LogBuffer {
return factory.create("StatusBarChips", 200)
}
+
+ @Provides
+ @SysUISingleton
+ @IntoMap
+ @ClassKey(StatusBarNotificationChipsInteractor::class)
+ fun statusBarNotificationChipsInteractorAsCoreStartable(
+ interactorLazy: Lazy<StatusBarNotificationChipsInteractor>
+ ): CoreStartable {
+ return if (StatusBarNotifChips.isEnabled) {
+ interactorLazy.get()
+ } else {
+ CoreStartable.NOP
+ }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractor.kt
new file mode 100644
index 000000000000..087b51032fcf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractor.kt
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2024 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.chips.notification.domain.interactor
+
+import com.android.systemui.activity.data.repository.ActivityManagerRepository
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.Logger
+import com.android.systemui.statusbar.chips.StatusBarChipLogTags.pad
+import com.android.systemui.statusbar.chips.StatusBarChipsLog
+import com.android.systemui.statusbar.chips.notification.domain.model.NotificationChipModel
+import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.map
+
+/**
+ * Interactor representing a single notification's status bar chip.
+ *
+ * [startingModel.key] dictates which notification this interactor corresponds to - all updates sent
+ * to this interactor via [setNotification] should only be for the notification with the same key.
+ *
+ * [StatusBarNotificationChipsInteractor] will collect all the individual instances of this
+ * interactor and send all the necessary information to the UI layer.
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+class SingleNotificationChipInteractor
+@AssistedInject
+constructor(
+ @Assisted startingModel: ActiveNotificationModel,
+ private val activityManagerRepository: ActivityManagerRepository,
+ @StatusBarChipsLog private val logBuffer: LogBuffer,
+) {
+ private val key = startingModel.key
+ private val logger = Logger(logBuffer, "Notif".pad())
+ // [StatusBarChipLogTag] recommends a max tag length of 20, so [extraLogTag] should NOT be the
+ // top-level tag. It should instead be provided as the first string in each log message.
+ private val extraLogTag = "SingleChipInteractor[key=$key]"
+
+ private val _notificationModel = MutableStateFlow(startingModel)
+
+ /**
+ * Sets the new notification info corresponding to this interactor. The key on [model] *must*
+ * match the key on the original [startingModel], otherwise the update won't be processed.
+ */
+ fun setNotification(model: ActiveNotificationModel) {
+ if (model.key != this.key) {
+ logger.w({ "$str1: received model for different key $str2" }) {
+ str1 = extraLogTag
+ str2 = model.key
+ }
+ return
+ }
+ _notificationModel.value = model
+ }
+
+ private val uid: Flow<Int> = _notificationModel.map { it.uid }
+
+ /** True if the application managing the notification is visible to the user. */
+ private val isAppVisible: Flow<Boolean> =
+ uid.flatMapLatest { currentUid ->
+ activityManagerRepository.createIsAppVisibleFlow(currentUid, logger, extraLogTag)
+ }
+
+ /**
+ * Emits this notification's status bar chip, or null if this notification shouldn't show a
+ * status bar chip.
+ */
+ val notificationChip: Flow<NotificationChipModel?> =
+ combine(_notificationModel, isAppVisible) { notif, isAppVisible ->
+ if (isAppVisible) {
+ // If the app that posted this notification is visible, we want to hide the chip
+ // because information between the status bar chip and the app itself could be
+ // out-of-sync (like a timer that's slightly off)
+ null
+ } else {
+ notif.toNotificationChipModel()
+ }
+ }
+
+ private fun ActiveNotificationModel.toNotificationChipModel(): NotificationChipModel? {
+ val statusBarChipIconView = this.statusBarChipIconView
+ if (statusBarChipIconView == null) {
+ logger.w({ "$str1: Can't show chip because status bar chip icon view is null" }) {
+ str1 = extraLogTag
+ }
+ return null
+ }
+ return NotificationChipModel(key, statusBarChipIconView)
+ }
+
+ @AssistedFactory
+ fun interface Factory {
+ fun create(startingModel: ActiveNotificationModel): SingleNotificationChipInteractor
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractor.kt
index 9e09671bc7bf..e8cb35b06999 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractor.kt
@@ -17,16 +17,42 @@
package com.android.systemui.statusbar.chips.notification.domain.interactor
import android.annotation.SuppressLint
+import com.android.app.tracing.coroutines.launchTraced as launch
+import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.Logger
+import com.android.systemui.statusbar.chips.StatusBarChipLogTags.pad
+import com.android.systemui.statusbar.chips.StatusBarChipsLog
+import com.android.systemui.statusbar.chips.notification.domain.model.NotificationChipModel
import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
+import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
+import com.android.systemui.util.kotlin.pairwise
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.asSharedFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
/** An interactor for the notification chips shown in the status bar. */
@SysUISingleton
-class StatusBarNotificationChipsInteractor @Inject constructor() {
+@OptIn(ExperimentalCoroutinesApi::class)
+class StatusBarNotificationChipsInteractor
+@Inject
+constructor(
+ @Background private val backgroundScope: CoroutineScope,
+ private val activeNotificationsInteractor: ActiveNotificationsInteractor,
+ private val singleNotificationChipInteractorFactory: SingleNotificationChipInteractor.Factory,
+ @StatusBarChipsLog private val logBuffer: LogBuffer,
+) : CoreStartable {
+ private val logger = Logger(logBuffer, "AllNotifs".pad())
// Each chip tap is an individual event, *not* a state, which is why we're using SharedFlow not
// StateFlow. There shouldn't be multiple updates per frame, which should avoid performance
@@ -45,4 +71,79 @@ class StatusBarNotificationChipsInteractor @Inject constructor() {
StatusBarNotifChips.assertInNewMode()
_promotedNotificationChipTapEvent.emit(key)
}
+
+ /**
+ * A cache of interactors. Each currently-promoted notification should have a corresponding
+ * interactor in this map.
+ */
+ private val promotedNotificationInteractorMap =
+ mutableMapOf<String, SingleNotificationChipInteractor>()
+
+ /**
+ * A list of interactors. Each currently-promoted notification should have a corresponding
+ * interactor in this list.
+ */
+ private val promotedNotificationInteractors =
+ MutableStateFlow<List<SingleNotificationChipInteractor>>(emptyList())
+
+ override fun start() {
+ if (!StatusBarNotifChips.isEnabled) {
+ return
+ }
+
+ backgroundScope.launch("StatusBarNotificationChipsInteractor") {
+ activeNotificationsInteractor.promotedOngoingNotifications
+ .pairwise(initialValue = emptyList())
+ .collect { (oldNotifs, currentNotifs) ->
+ val removedNotifs = oldNotifs.minus(currentNotifs.toSet())
+ removedNotifs.forEach { removedNotif ->
+ val wasRemoved = promotedNotificationInteractorMap.remove(removedNotif.key)
+ if (wasRemoved == null) {
+ logger.w({
+ "Attempted to remove $str1 from interactor map but it wasn't present"
+ }) {
+ str1 = removedNotif.key
+ }
+ }
+ }
+ currentNotifs.forEach { notif ->
+ val interactor =
+ promotedNotificationInteractorMap.computeIfAbsent(notif.key) {
+ singleNotificationChipInteractorFactory.create(notif)
+ }
+ interactor.setNotification(notif)
+ }
+ logger.d({ "Interactors: $str1" }) {
+ str1 =
+ promotedNotificationInteractorMap.keys.joinToString(separator = " /// ")
+ }
+ promotedNotificationInteractors.value =
+ promotedNotificationInteractorMap.values.toList()
+ }
+ }
+ }
+
+ /**
+ * A flow modeling the notifications that should be shown as chips in the status bar. Emits an
+ * empty list if there are no notifications that should show a status bar chip.
+ */
+ val notificationChips: Flow<List<NotificationChipModel>> =
+ if (StatusBarNotifChips.isEnabled) {
+ // For all our current interactors...
+ promotedNotificationInteractors.flatMapLatest { interactors ->
+ if (interactors.isNotEmpty()) {
+ // Combine each interactor's [notificationChip] flow...
+ val allNotificationChips: List<Flow<NotificationChipModel?>> =
+ interactors.map { interactor -> interactor.notificationChip }
+ combine(allNotificationChips) {
+ // ... and emit just the non-null chips
+ it.filterNotNull()
+ }
+ } else {
+ flowOf(emptyList())
+ }
+ }
+ } else {
+ flowOf(emptyList())
+ }
}
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BottomHalfPipLaunchingActivity.java b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/model/NotificationChipModel.kt
index d9d4361411bb..5698ee6d1917 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BottomHalfPipLaunchingActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/model/NotificationChipModel.kt
@@ -14,18 +14,9 @@
* limitations under the License.
*/
-package com.android.server.wm.flicker.testapp;
+package com.android.systemui.statusbar.chips.notification.domain.model
-import android.content.Intent;
-import android.os.Bundle;
+import com.android.systemui.statusbar.StatusBarIconView
-public class BottomHalfPipLaunchingActivity extends SimpleActivity {
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- final Intent intent = new Intent(this, BottomHalfPipActivity.class);
- startActivity(intent);
- }
-}
+/** Modeling all the data needed to render a status bar notification chip. */
+data class NotificationChipModel(val key: String, val statusBarChipIconView: StatusBarIconView)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt
index 752674854e2d..9eff627c8714 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt
@@ -20,11 +20,10 @@ import android.view.View
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.statusbar.chips.notification.domain.interactor.StatusBarNotificationChipsInteractor
+import com.android.systemui.statusbar.chips.notification.domain.model.NotificationChipModel
import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
import com.android.systemui.statusbar.chips.ui.model.ColorsModel
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
-import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
-import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
@@ -37,7 +36,6 @@ class NotifChipsViewModel
@Inject
constructor(
@Application private val applicationScope: CoroutineScope,
- activeNotificationsInteractor: ActiveNotificationsInteractor,
private val notifChipsInteractor: StatusBarNotificationChipsInteractor,
) {
/**
@@ -45,19 +43,14 @@ constructor(
* no notifications that should show a status bar chip.
*/
val chips: Flow<List<OngoingActivityChipModel.Shown>> =
- activeNotificationsInteractor.promotedOngoingNotifications.map { notifications ->
- notifications.mapNotNull { it.toChipModel() }
+ notifChipsInteractor.notificationChips.map { notifications ->
+ notifications.map { it.toActivityChipModel() }
}
- /**
- * Converts the notification to the [OngoingActivityChipModel] object. Returns null if the
- * notification has invalid data such that it can't be displayed as a chip.
- */
- private fun ActiveNotificationModel.toChipModel(): OngoingActivityChipModel.Shown? {
+ /** Converts the notification to the [OngoingActivityChipModel] object. */
+ private fun NotificationChipModel.toActivityChipModel(): OngoingActivityChipModel.Shown {
StatusBarNotifChips.assertInNewMode()
- // TODO(b/364653005): Log error if there's no icon view.
- val rawIcon = this.statusBarChipIconView ?: return null
- val icon = OngoingActivityChipModel.ChipIcon.StatusBarView(rawIcon)
+ val icon = OngoingActivityChipModel.ChipIcon.StatusBarView(this.statusBarChipIconView)
// TODO(b/364653005): Use the notification color if applicable.
val colors = ColorsModel.Themed
val onClickListener =
@@ -65,7 +58,9 @@ constructor(
// The notification pipeline needs everything to run on the main thread, so keep
// this event on the main thread.
applicationScope.launch {
- notifChipsInteractor.onPromotedNotificationChipTapped(this@toChipModel.key)
+ notifChipsInteractor.onPromotedNotificationChipTapped(
+ this@toActivityChipModel.key
+ )
}
}
return OngoingActivityChipModel.Shown.IconOnly(icon, colors, onClickListener)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
index aac2cd1755d0..78926c78a368 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
@@ -25,6 +25,7 @@ import android.app.UidObserver
import android.content.Context
import android.view.View
import androidx.annotation.VisibleForTesting
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.internal.jank.InteractionJankMonitor
import com.android.systemui.CoreStartable
import com.android.systemui.Dumpable
@@ -58,7 +59,6 @@ import java.io.PrintWriter
import java.util.concurrent.Executor
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
-import com.android.app.tracing.coroutines.launchTraced as launch
/** A controller to handle the ongoing call chip in the collapsed status bar. */
@SysUISingleton
@@ -122,9 +122,9 @@ constructor(
entry.sbn.uid,
entry.sbn.notification.extras.getInt(
Notification.EXTRA_CALL_TYPE,
- -1
+ -1,
) == CALL_TYPE_ONGOING,
- statusBarSwipedAway = callNotificationInfo?.statusBarSwipedAway ?: false
+ statusBarSwipedAway = callNotificationInfo?.statusBarSwipedAway ?: false,
)
if (newOngoingCallInfo == callNotificationInfo) {
return
@@ -236,7 +236,7 @@ constructor(
bool1 = Flags.statusBarCallChipNotificationIcon()
bool2 = currentInfo.notificationIconView != null
},
- { "Creating OngoingCallModel.InCall. notifIconFlag=$bool1 hasIcon=$bool2" }
+ { "Creating OngoingCallModel.InCall. notifIconFlag=$bool1 hasIcon=$bool2" },
)
val icon =
if (Flags.statusBarCallChipNotificationIcon()) {
@@ -288,7 +288,7 @@ constructor(
str1 = notifModel.callType.name
bool1 = notifModel.statusBarChipIconView != null
},
- { "NotifInteractorCallModel: key=$str1 when=$long1 callType=$str2 hasIcon=$bool1" }
+ { "NotifInteractorCallModel: key=$str1 when=$long1 callType=$str2 hasIcon=$bool1" },
)
val newOngoingCallInfo =
@@ -299,7 +299,7 @@ constructor(
notifModel.contentIntent,
notifModel.uid,
isOngoing = true,
- statusBarSwipedAway = callNotificationInfo?.statusBarSwipedAway ?: false
+ statusBarSwipedAway = callNotificationInfo?.statusBarSwipedAway ?: false,
)
if (newOngoingCallInfo == callNotificationInfo) {
return
@@ -378,7 +378,7 @@ constructor(
ActivityTransitionAnimator.Controller.fromView(
backgroundView,
InteractionJankMonitor.CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP,
- )
+ ),
)
}
}
@@ -455,7 +455,7 @@ constructor(
/** True if the call is currently ongoing (as opposed to incoming, screening, etc.). */
val isOngoing: Boolean,
/** True if the user has swiped away the status bar while in this phone call. */
- val statusBarSwipedAway: Boolean
+ val statusBarSwipedAway: Boolean,
) {
/**
* Returns true if the notification information has a valid call start time. See
@@ -472,6 +472,9 @@ constructor(
/**
* Observer to tell us when the app that posted the ongoing call notification is visible so that
* we don't show the call chip at the same time (since the timers could be out-of-sync).
+ *
+ * For a more recommended architecture implementation, see
+ * [com.android.systemui.activity.data.repository.ActivityManagerRepository].
*/
inner class CallAppUidObserver : UidObserver() {
/** True if the application managing the call is visible to the user. */
@@ -512,7 +515,7 @@ constructor(
uidObserver,
ActivityManager.UID_OBSERVER_PROCSTATE,
ActivityManager.PROCESS_STATE_UNKNOWN,
- context.opPackageName
+ context.opPackageName,
)
isRegistered = true
} catch (se: SecurityException) {
@@ -537,7 +540,7 @@ constructor(
uid: Int,
procState: Int,
procStateSeq: Long,
- capability: Int
+ capability: Int,
) {
val currentCallAppUid = callAppUid ?: return
if (uid != currentCallAppUid) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
index 859f84edda39..e7fb470cfa76 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
@@ -33,6 +33,7 @@ import androidx.lifecycle.LifecycleOwner
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.SceneKey
import com.android.systemui.Flags
+import com.android.systemui.Flags.FLAG_COMMUNAL_HUB_ON_MOBILE
import com.android.systemui.Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX
import com.android.systemui.SysuiTestCase
import com.android.systemui.ambient.touch.TouchHandler
@@ -630,6 +631,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() {
}
}
+ @DisableFlags(FLAG_COMMUNAL_HUB_ON_MOBILE)
@Test
fun onTouchEvent_shadeInteracting_movesNotDispatched() =
with(kosmos) {
@@ -686,6 +688,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() {
}
}
+ @DisableFlags(FLAG_COMMUNAL_HUB_ON_MOBILE)
@Test
fun onTouchEvent_bouncerInteracting_movesNotDispatched() =
with(kosmos) {
@@ -718,6 +721,19 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() {
}
}
+ @EnableFlags(FLAG_COMMUNAL_HUB_ON_MOBILE)
+ @Test
+ fun onTouchEvent_onLockscreenAndGlanceableHubV2_touchIgnored() =
+ with(kosmos) {
+ testScope.runTest {
+ // On lockscreen.
+ goToScene(CommunalScenes.Blank)
+
+ assertThat(underTest.onTouchEvent(DOWN_EVENT)).isFalse()
+ verify(containerView, never()).onTouchEvent(DOWN_EVENT)
+ }
+ }
+
@Test
fun disposeView_destroysTouchMonitor() {
clearInvocations(touchMonitor)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/activity/data/repository/ActivityManagerRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/activity/data/repository/ActivityManagerRepositoryKosmos.kt
new file mode 100644
index 000000000000..a6e71333c816
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/activity/data/repository/ActivityManagerRepositoryKosmos.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2024 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.activity.data.repository
+
+import android.app.activityManager
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.log.core.Logger
+import kotlinx.coroutines.flow.MutableStateFlow
+
+val Kosmos.activityManagerRepository by Kosmos.Fixture { FakeActivityManagerRepository() }
+
+val Kosmos.realActivityManagerRepository by
+ Kosmos.Fixture { ActivityManagerRepositoryImpl(testDispatcher, activityManager) }
+
+class FakeActivityManagerRepository : ActivityManagerRepository {
+ private val uidFlows = mutableMapOf<Int, MutableList<MutableStateFlow<Boolean>>>()
+
+ var startingIsAppVisibleValue = false
+
+ override fun createIsAppVisibleFlow(
+ creationUid: Int,
+ logger: Logger,
+ identifyingLogTag: String,
+ ): MutableStateFlow<Boolean> {
+ val newFlow = MutableStateFlow(startingIsAppVisibleValue)
+ uidFlows.computeIfAbsent(creationUid) { mutableListOf() }.add(newFlow)
+ return newFlow
+ }
+
+ fun setIsAppVisible(uid: Int, isAppVisible: Boolean) {
+ uidFlows[uid]?.forEach { stateFlow -> stateFlow.value = isAppVisible }
+ }
+}
+
+val ActivityManagerRepository.fake
+ get() = this as FakeActivityManagerRepository
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractorFactoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractorFactoryKosmos.kt
new file mode 100644
index 000000000000..1c095e11dffa
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractorFactoryKosmos.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 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.chips.notification.domain.interactor
+
+import com.android.systemui.activity.data.repository.activityManagerRepository
+import com.android.systemui.activity.data.repository.fake
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.chips.statusBarChipsLogger
+
+val Kosmos.singleNotificationChipInteractorFactory: SingleNotificationChipInteractor.Factory by
+ Kosmos.Fixture {
+ SingleNotificationChipInteractor.Factory { startingModel ->
+ SingleNotificationChipInteractor(
+ startingModel,
+ activityManagerRepository.fake,
+ logBuffer = statusBarChipsLogger,
+ )
+ }
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractorKosmos.kt
index 74c7611a6392..03e9f3d52ca3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractorKosmos.kt
@@ -17,6 +17,16 @@
package com.android.systemui.statusbar.chips.notification.domain.interactor
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.statusbar.chips.statusBarChipsLogger
+import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
val Kosmos.statusBarNotificationChipsInteractor: StatusBarNotificationChipsInteractor by
- Kosmos.Fixture { StatusBarNotificationChipsInteractor() }
+ Kosmos.Fixture {
+ StatusBarNotificationChipsInteractor(
+ testScope.backgroundScope,
+ activeNotificationsInteractor,
+ singleNotificationChipInteractorFactory,
+ logBuffer = statusBarChipsLogger,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelKosmos.kt
index 68b28adb4b3a..4bcce8601d64 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelKosmos.kt
@@ -19,13 +19,8 @@ package com.android.systemui.statusbar.chips.notification.ui.viewmodel
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.statusbar.chips.notification.domain.interactor.statusBarNotificationChipsInteractor
-import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
val Kosmos.notifChipsViewModel: NotifChipsViewModel by
Kosmos.Fixture {
- NotifChipsViewModel(
- applicationCoroutineScope,
- activeNotificationsInteractor,
- statusBarNotificationChipsInteractor,
- )
+ NotifChipsViewModel(applicationCoroutineScope, statusBarNotificationChipsInteractor)
}
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRunnerState.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRunnerState.java
index 4ab1fa1579e1..110de989b524 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRunnerState.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRunnerState.java
@@ -15,22 +15,10 @@
*/
package android.platform.test.ravenwood;
-import static com.android.ravenwood.common.RavenwoodCommonUtils.ensureIsPublicMember;
-
-import static org.junit.Assert.fail;
-
-import android.annotation.Nullable;
import android.util.Log;
-import com.android.ravenwood.common.RavenwoodRuntimeException;
-
-import org.junit.ClassRule;
-import org.junit.Rule;
-import org.junit.rules.TestRule;
import org.junit.runner.Description;
-import java.lang.reflect.Field;
-
/**
* Used to store various states associated with the current test runner that's inly needed
* in junit-impl.
@@ -52,30 +40,10 @@ public final class RavenwoodRunnerState {
mRunner = runner;
}
- /**
- * The RavenwoodConfig declared in the test class
- */
- private RavenwoodConfig mConfig;
- /**
- * The RavenwoodRule currently in effect, declared in the test class
- */
- private RavenwoodRule mRule;
- private boolean mHasRavenwoodRule;
private Description mMethodDescription;
public void enterTestRunner() {
Log.i(TAG, "enterTestRunner: " + mRunner);
-
- mHasRavenwoodRule = hasRavenwoodRule(mRunner.mTestJavaClass);
- mConfig = extractConfiguration(mRunner.mTestJavaClass);
-
- if (mConfig != null) {
- if (mHasRavenwoodRule) {
- fail("RavenwoodConfig and RavenwoodRule cannot be used in the same class."
- + " Suggest migrating to RavenwoodConfig.");
- }
- }
-
RavenwoodRuntimeEnvironmentController.initForRunner();
}
@@ -85,12 +53,7 @@ public final class RavenwoodRunnerState {
public void exitTestClass() {
Log.i(TAG, "exitTestClass: " + mRunner.mTestJavaClass.getName());
- try {
- RavenwoodRuntimeEnvironmentController.exitTestClass();
- } finally {
- mConfig = null;
- mRule = null;
- }
+ RavenwoodRuntimeEnvironmentController.exitTestClass();
}
public void enterTestMethod(Description description) {
@@ -103,132 +66,9 @@ public final class RavenwoodRunnerState {
}
public void enterRavenwoodRule(RavenwoodRule rule) {
- if (!mHasRavenwoodRule) {
- fail("If you have a RavenwoodRule in your test, make sure the field type is"
- + " RavenwoodRule so Ravenwood can detect it.");
- }
- if (mRule != null) {
- fail("Multiple nesting RavenwoodRule's are detected in the same class,"
- + " which is not supported.");
- }
- mRule = rule;
RavenwoodRuntimeEnvironmentController.setSystemProperties(rule.mSystemProperties);
}
public void exitRavenwoodRule(RavenwoodRule rule) {
- if (mRule != rule) {
- fail("RavenwoodRule did not take effect.");
- }
- mRule = null;
- }
-
- /**
- * @return a configuration from a test class, if any.
- */
- @Nullable
- private static RavenwoodConfig extractConfiguration(Class<?> testClass) {
- var field = findConfigurationField(testClass);
- if (field == null) {
- return null;
- }
-
- try {
- return (RavenwoodConfig) field.get(null);
- } catch (IllegalAccessException e) {
- throw new RavenwoodRuntimeException("Failed to fetch from the configuration field", e);
- }
- }
-
- /**
- * @return true if the current target class (or its super classes) has any @Rule / @ClassRule
- * fields of type RavenwoodRule.
- *
- * Note, this check won't detect cases where a Rule is of type
- * {@link TestRule} and still be a {@link RavenwoodRule}. But that'll be detected at runtime
- * as a failure, in {@link #enterRavenwoodRule}.
- */
- private static boolean hasRavenwoodRule(Class<?> testClass) {
- for (var field : testClass.getDeclaredFields()) {
- if (!field.isAnnotationPresent(Rule.class)
- && !field.isAnnotationPresent(ClassRule.class)) {
- continue;
- }
- if (field.getType().equals(RavenwoodRule.class)) {
- return true;
- }
- }
- // JUnit supports rules as methods, so we need to check them too.
- for (var method : testClass.getDeclaredMethods()) {
- if (!method.isAnnotationPresent(Rule.class)
- && !method.isAnnotationPresent(ClassRule.class)) {
- continue;
- }
- if (method.getReturnType().equals(RavenwoodRule.class)) {
- return true;
- }
- }
- // Look into the super class.
- if (!testClass.getSuperclass().equals(Object.class)) {
- return hasRavenwoodRule(testClass.getSuperclass());
- }
- return false;
- }
-
- /**
- * Find and return a field with @RavenwoodConfig.Config, which must be of type
- * RavenwoodConfig.
- */
- @Nullable
- private static Field findConfigurationField(Class<?> testClass) {
- Field foundField = null;
-
- for (var field : testClass.getDeclaredFields()) {
- final var hasAnot = field.isAnnotationPresent(RavenwoodConfig.Config.class);
- final var isType = field.getType().equals(RavenwoodConfig.class);
-
- if (hasAnot) {
- if (isType) {
- // Good, use this field.
- if (foundField != null) {
- fail(String.format(
- "Class %s has multiple fields with %s",
- testClass.getCanonicalName(),
- "@RavenwoodConfig.Config"));
- }
- // Make sure it's static public
- ensureIsPublicMember(field, true);
-
- foundField = field;
- } else {
- fail(String.format(
- "Field %s.%s has %s but type is not %s",
- testClass.getCanonicalName(),
- field.getName(),
- "@RavenwoodConfig.Config",
- "RavenwoodConfig"));
- return null; // unreachable
- }
- } else {
- if (isType) {
- fail(String.format(
- "Field %s.%s does not have %s but type is %s",
- testClass.getCanonicalName(),
- field.getName(),
- "@RavenwoodConfig.Config",
- "RavenwoodConfig"));
- return null; // unreachable
- } else {
- // Unrelated field, ignore.
- continue;
- }
- }
- }
- if (foundField != null) {
- return foundField;
- }
- if (!testClass.getSuperclass().equals(Object.class)) {
- return findConfigurationField(testClass.getSuperclass());
- }
- return null;
}
}
diff --git a/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodMultipleRuleTest.java b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodMultipleRuleTest.java
deleted file mode 100644
index c25d2b4cbc4d..000000000000
--- a/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodMultipleRuleTest.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2024 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.ravenwoodtest.bivalenttest;
-
-import android.platform.test.ravenwood.RavenwoodConfig;
-import android.platform.test.ravenwood.RavenwoodRule;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
-import org.junit.Assume;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.junit.runner.RunWith;
-
-/**
- * Make sure having multiple RavenwoodRule's is detected.
- * (But only when running on ravenwod. Otherwise it'll be ignored.)
- */
-@RunWith(AndroidJUnit4.class)
-public class RavenwoodMultipleRuleTest {
-
- @Rule(order = Integer.MIN_VALUE)
- public final ExpectedException mExpectedException = ExpectedException.none();
-
- @Rule
- public final RavenwoodRule mRavenwood1 = new RavenwoodRule();
-
- @Rule
- public final RavenwoodRule mRavenwood2 = new RavenwoodRule();
-
- public RavenwoodMultipleRuleTest() {
- // We can't call it within the test method because the exception happens before
- // calling the method, so set it up here.
- if (RavenwoodConfig.isOnRavenwood()) {
- mExpectedException.expectMessage("Multiple nesting RavenwoodRule");
- }
- }
-
- @Test
- public void testMultipleRulesNotAllowed() {
- Assume.assumeTrue(RavenwoodConfig.isOnRavenwood());
- }
-}
diff --git a/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRuleValidationTest.java b/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRuleValidationTest.java
new file mode 100644
index 000000000000..f9e73db23740
--- /dev/null
+++ b/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRuleValidationTest.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2024 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.ravenwoodtest.runnercallbacktests;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.SystemProperties;
+import android.platform.test.annotations.NoRavenizer;
+import android.platform.test.ravenwood.RavenwoodRule;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runner.RunWith;
+
+/**
+ * Test for RavenwoodRule.
+ */
+@NoRavenizer // This class shouldn't be executed with RavenwoodAwareTestRunner.
+public class RavenwoodRuleValidationTest extends RavenwoodRunnerTestBase {
+
+ public static class RuleInBaseClass {
+ static String PROPERTY_KEY = "debug.ravenwood.prop.in.base";
+ static String PROPERTY_VAL = "ravenwood";
+ @Rule
+ public final RavenwoodRule mRavenwood1 = new RavenwoodRule.Builder()
+ .setSystemPropertyImmutable(PROPERTY_KEY, PROPERTY_VAL).build();
+ }
+
+ /**
+ * Make sure that RavenwoodRule in a base class takes effect.
+ */
+ @RunWith(AndroidJUnit4.class)
+ // CHECKSTYLE:OFF
+ @Expected("""
+ testRunStarted: classes
+ testSuiteStarted: classes
+ testSuiteStarted: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRuleValidationTest$RuleInBaseClassSuccessTest
+ testStarted: testRuleInBaseClass(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRuleValidationTest$RuleInBaseClassSuccessTest)
+ testFinished: testRuleInBaseClass(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRuleValidationTest$RuleInBaseClassSuccessTest)
+ testSuiteFinished: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRuleValidationTest$RuleInBaseClassSuccessTest
+ testSuiteFinished: classes
+ testRunFinished: 1,0,0,0
+ """)
+ // CHECKSTYLE:ON
+ public static class RuleInBaseClassSuccessTest extends RuleInBaseClass {
+
+ @Test
+ public void testRuleInBaseClass() {
+ assertThat(SystemProperties.get(PROPERTY_KEY)).isEqualTo(PROPERTY_VAL);
+ }
+ }
+
+ /**
+ * Same as {@link RuleInBaseClass}, but the type of the rule field is not {@link RavenwoodRule}.
+ */
+ public abstract static class RuleWithDifferentTypeInBaseClass {
+ static String PROPERTY_KEY = "debug.ravenwood.prop.in.base.different.type";
+ static String PROPERTY_VAL = "ravenwood";
+ @Rule
+ public final TestRule mRavenwood1 = new RavenwoodRule.Builder()
+ .setSystemPropertyImmutable(PROPERTY_KEY, PROPERTY_VAL).build();
+ }
+
+ /**
+ * Make sure that RavenwoodRule in a base class takes effect, even if the field type is not
+ */
+ @RunWith(AndroidJUnit4.class)
+ // CHECKSTYLE:OFF
+ @Expected("""
+ testRunStarted: classes
+ testSuiteStarted: classes
+ testSuiteStarted: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRuleValidationTest$RuleWithDifferentTypeInBaseClassSuccessTest
+ testStarted: testRuleInBaseClass(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRuleValidationTest$RuleWithDifferentTypeInBaseClassSuccessTest)
+ testFinished: testRuleInBaseClass(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRuleValidationTest$RuleWithDifferentTypeInBaseClassSuccessTest)
+ testSuiteFinished: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRuleValidationTest$RuleWithDifferentTypeInBaseClassSuccessTest
+ testSuiteFinished: classes
+ testRunFinished: 1,0,0,0
+ """)
+ // CHECKSTYLE:ON
+ public static class RuleWithDifferentTypeInBaseClassSuccessTest extends RuleWithDifferentTypeInBaseClass {
+
+ @Test
+ public void testRuleInBaseClass() {
+ assertThat(SystemProperties.get(PROPERTY_KEY)).isEqualTo(PROPERTY_VAL);
+ }
+ }
+}
diff --git a/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerConfigValidationTest.java b/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerConfigValidationTest.java
deleted file mode 100644
index f94b98bc1fb8..000000000000
--- a/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerConfigValidationTest.java
+++ /dev/null
@@ -1,484 +0,0 @@
-/*
- * Copyright (C) 2024 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.ravenwoodtest.runnercallbacktests;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.platform.test.annotations.NoRavenizer;
-import android.platform.test.ravenwood.RavenwoodConfig;
-import android.platform.test.ravenwood.RavenwoodRule;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.platform.app.InstrumentationRegistry;
-
-import org.junit.Ignore;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TestRule;
-import org.junit.runner.RunWith;
-
-
-/**
- * Test for @Config field extraction and validation.
- *
- * TODO(b/377765941) Most of the tests here will be obsolete and deleted with b/377765941, but
- * some of the tests may need to be re-implemented one way or another. (e.g. the package name
- * test.) Until that happens, we'll keep all tests here but add an {@code @Ignore} instead.
- */
-@NoRavenizer // This class shouldn't be executed with RavenwoodAwareTestRunner.
-public class RavenwoodRunnerConfigValidationTest extends RavenwoodRunnerTestBase {
- public abstract static class ConfigInBaseClass {
- static String PACKAGE_NAME = "com.ConfigInBaseClass";
-
- @RavenwoodConfig.Config
- public static RavenwoodConfig sConfig = new RavenwoodConfig.Builder()
- .setPackageName(PACKAGE_NAME).build();
- }
-
- /**
- * Make sure a config in the base class is detected.
- */
- @RunWith(AndroidJUnit4.class)
- // CHECKSTYLE:OFF
- @Expected("""
- testRunStarted: classes
- testSuiteStarted: classes
- testSuiteStarted: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ConfigInBaseClassTest
- testStarted: test(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ConfigInBaseClassTest)
- testFinished: test(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ConfigInBaseClassTest)
- testSuiteFinished: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ConfigInBaseClassTest
- testSuiteFinished: classes
- testRunFinished: 1,0,0,0
- """)
- // CHECKSTYLE:ON
- @Ignore // Package name is no longer set via config.
- public static class ConfigInBaseClassTest extends ConfigInBaseClass {
- @Test
- public void test() {
- assertThat(InstrumentationRegistry.getInstrumentation().getContext().getPackageName())
- .isEqualTo(PACKAGE_NAME);
- }
- }
-
- /**
- * Make sure a config in the base class is detected.
- */
- @RunWith(AndroidJUnit4.class)
- // CHECKSTYLE:OFF
- @Expected("""
- testRunStarted: classes
- testSuiteStarted: classes
- testSuiteStarted: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ConfigOverridingTest
- testStarted: test(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ConfigOverridingTest)
- testFinished: test(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ConfigOverridingTest)
- testSuiteFinished: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ConfigOverridingTest
- testSuiteFinished: classes
- testRunFinished: 1,0,0,0
- """)
- // CHECKSTYLE:ON
- @Ignore // Package name is no longer set via config.
- public static class ConfigOverridingTest extends ConfigInBaseClass {
- static String PACKAGE_NAME_OVERRIDE = "com.ConfigOverridingTest";
-
- @RavenwoodConfig.Config
- public static RavenwoodConfig sConfig = new RavenwoodConfig.Builder()
- .setPackageName(PACKAGE_NAME_OVERRIDE).build();
-
- @Test
- public void test() {
- assertThat(InstrumentationRegistry.getInstrumentation().getContext().getPackageName())
- .isEqualTo(PACKAGE_NAME_OVERRIDE);
- }
- }
-
- /**
- * Test to make sure that if a test has a config error, the failure would be reported from
- * each test method.
- */
- @RunWith(AndroidJUnit4.class)
- // CHECKSTYLE:OFF
- @Expected("""
- testRunStarted: classes
- testSuiteStarted: classes
- testStarted: initializationError(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ErrorMustBeReportedFromEachTest)
- testFailure: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ErrorMustBeReportedFromEachTest.sConfig expected to be public static
- testFinished: initializationError(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ErrorMustBeReportedFromEachTest)
- testSuiteFinished: classes
- testRunFinished: 1,1,0,0
- """)
- // CHECKSTYLE:ON
- public static class ErrorMustBeReportedFromEachTest {
- @RavenwoodConfig.Config
- private static RavenwoodConfig sConfig = // Invalid because it's private.
- new RavenwoodConfig.Builder().build();
-
- @Test
- public void testMethod1() {
- }
-
- @Test
- public void testMethod2() {
- }
-
- @Test
- public void testMethod3() {
- }
- }
-
- /**
- * Invalid because there are two @Config's.
- */
- @RunWith(AndroidJUnit4.class)
- // CHECKSTYLE:OFF
- @Expected("""
- testRunStarted: classes
- testSuiteStarted: classes
- testStarted: initializationError(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$DuplicateConfigTest)
- testFailure: Class com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest.DuplicateConfigTest has multiple fields with @RavenwoodConfig.Config
- testFinished: initializationError(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$DuplicateConfigTest)
- testSuiteFinished: classes
- testRunFinished: 1,1,0,0
- """)
- // CHECKSTYLE:ON
- public static class DuplicateConfigTest {
-
- @RavenwoodConfig.Config
- public static RavenwoodConfig sConfig1 =
- new RavenwoodConfig.Builder().build();
-
- @RavenwoodConfig.Config
- public static RavenwoodConfig sConfig2 =
- new RavenwoodConfig.Builder().build();
-
- @Test
- public void testConfig() {
- }
- }
-
- /**
- * @Config's must be static.
- */
- @RunWith(AndroidJUnit4.class)
- // CHECKSTYLE:OFF
- @Expected("""
- testRunStarted: classes
- testSuiteStarted: classes
- testStarted: initializationError(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$NonStaticConfigTest)
- testFailure: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$NonStaticConfigTest.sConfig expected to be public static
- testFinished: initializationError(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$NonStaticConfigTest)
- testSuiteFinished: classes
- testRunFinished: 1,1,0,0
- """)
- // CHECKSTYLE:ON
- public static class NonStaticConfigTest {
-
- @RavenwoodConfig.Config
- public RavenwoodConfig sConfig =
- new RavenwoodConfig.Builder().build();
-
- @Test
- public void testConfig() {
- }
- }
-
- /**
- * @Config's must be public.
- */
- @RunWith(AndroidJUnit4.class)
- // CHECKSTYLE:OFF
- @Expected("""
- testRunStarted: classes
- testSuiteStarted: classes
- testStarted: initializationError(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$NonPublicConfigTest)
- testFailure: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$NonPublicConfigTest.sConfig expected to be public static
- testFinished: initializationError(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$NonPublicConfigTest)
- testSuiteFinished: classes
- testRunFinished: 1,1,0,0
- """)
- // CHECKSTYLE:ON
- public static class NonPublicConfigTest {
-
- @RavenwoodConfig.Config
- RavenwoodConfig sConfig =
- new RavenwoodConfig.Builder().build();
-
- @Test
- public void testConfig() {
- }
- }
-
- /**
- * @Config's must be of type RavenwoodConfig.
- */
- @RunWith(AndroidJUnit4.class)
- // CHECKSTYLE:OFF
- @Expected("""
- testRunStarted: classes
- testSuiteStarted: classes
- testStarted: initializationError(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$WrongTypeConfigTest)
- testFailure: Field com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest.WrongTypeConfigTest.sConfig has @RavenwoodConfig.Config but type is not RavenwoodConfig
- testFinished: initializationError(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$WrongTypeConfigTest)
- testSuiteFinished: classes
- testRunFinished: 1,1,0,0
- """)
- // CHECKSTYLE:ON
- public static class WrongTypeConfigTest {
-
- @RavenwoodConfig.Config
- public static Object sConfig =
- new RavenwoodConfig.Builder().build();
-
- @Test
- public void testConfig() {
- }
-
- }
-
- /**
- * @Rule must be of type RavenwoodRule.
- */
- @RunWith(AndroidJUnit4.class)
- // CHECKSTYLE:OFF
- @Expected("""
- testRunStarted: classes
- testSuiteStarted: classes
- testSuiteStarted: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$WrongTypeRuleTest
- testStarted: testConfig(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$WrongTypeRuleTest)
- testFailure: If you have a RavenwoodRule in your test, make sure the field type is RavenwoodRule so Ravenwood can detect it.
- testFinished: testConfig(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$WrongTypeRuleTest)
- testSuiteFinished: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$WrongTypeRuleTest
- testSuiteFinished: classes
- testRunFinished: 1,1,0,0
- """)
- // CHECKSTYLE:ON
- public static class WrongTypeRuleTest {
-
- @Rule
- public TestRule mRule = new RavenwoodRule.Builder().build();
-
- @Test
- public void testConfig() {
- }
-
- }
-
- /**
- * Config can't be used with a (instance) Rule.
- */
- @RunWith(AndroidJUnit4.class)
- // CHECKSTYLE:OFF
- @Expected("""
- testRunStarted: classes
- testSuiteStarted: classes
- testStarted: initializationError(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$WithInstanceRuleTest)
- testFailure: RavenwoodConfig and RavenwoodRule cannot be used in the same class. Suggest migrating to RavenwoodConfig.
- testFinished: initializationError(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$WithInstanceRuleTest)
- testSuiteFinished: classes
- testRunFinished: 1,1,0,0
- """)
- // CHECKSTYLE:ON
- public static class WithInstanceRuleTest {
-
- @RavenwoodConfig.Config
- public static RavenwoodConfig sConfig =
- new RavenwoodConfig.Builder().build();
-
- @Rule
- public RavenwoodRule mRule = new RavenwoodRule.Builder().build();
-
- @Test
- public void testConfig() {
- }
- }
-
- /**
- * Config can't be used with a (static) Rule.
- */
- @RunWith(AndroidJUnit4.class)
- // CHECKSTYLE:OFF
- @Expected("""
- testRunStarted: classes
- testSuiteStarted: classes
- testStarted: initializationError(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$WithStaticRuleTest)
- testFailure: Failed to instantiate class androidx.test.ext.junit.runners.AndroidJUnit4
- testFinished: initializationError(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$WithStaticRuleTest)
- testSuiteFinished: classes
- testRunFinished: 1,1,0,0
- """)
- // CHECKSTYLE:ON
- public static class WithStaticRuleTest {
-
- @RavenwoodConfig.Config
- public static RavenwoodConfig sConfig =
- new RavenwoodConfig.Builder().build();
-
- @Rule
- public static RavenwoodRule sRule = new RavenwoodRule.Builder().build();
-
- @Test
- public void testConfig() {
- }
- }
-
- @RunWith(AndroidJUnit4.class)
- // CHECKSTYLE:OFF
- @Expected("""
- testRunStarted: classes
- testSuiteStarted: classes
- testSuiteStarted: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$DuplicateRulesTest
- testStarted: testMultipleRulesNotAllowed(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$DuplicateRulesTest)
- testFailure: Multiple nesting RavenwoodRule's are detected in the same class, which is not supported.
- testFinished: testMultipleRulesNotAllowed(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$DuplicateRulesTest)
- testSuiteFinished: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$DuplicateRulesTest
- testSuiteFinished: classes
- testRunFinished: 1,1,0,0
- """)
- // CHECKSTYLE:ON
- public static class DuplicateRulesTest {
-
- @Rule
- public final RavenwoodRule mRavenwood1 = new RavenwoodRule();
-
- @Rule
- public final RavenwoodRule mRavenwood2 = new RavenwoodRule();
-
- @Test
- public void testMultipleRulesNotAllowed() {
- }
- }
-
- public static class RuleInBaseClass {
- static String PACKAGE_NAME = "com.RuleInBaseClass";
- @Rule
- public final RavenwoodRule mRavenwood1 = new RavenwoodRule.Builder()
- .setPackageName(PACKAGE_NAME).build();
- }
-
- /**
- * Make sure that RavenwoodRule in a base class takes effect.
- */
- @RunWith(AndroidJUnit4.class)
- // CHECKSTYLE:OFF
- @Expected("""
- testRunStarted: classes
- testSuiteStarted: classes
- testSuiteStarted: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$RuleInBaseClassSuccessTest
- testStarted: testRuleInBaseClass(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$RuleInBaseClassSuccessTest)
- testFinished: testRuleInBaseClass(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$RuleInBaseClassSuccessTest)
- testSuiteFinished: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$RuleInBaseClassSuccessTest
- testSuiteFinished: classes
- testRunFinished: 1,0,0,0
- """)
- // CHECKSTYLE:ON
- @Ignore // Package name is no longer set via config.
- public static class RuleInBaseClassSuccessTest extends RuleInBaseClass {
-
- @Test
- public void testRuleInBaseClass() {
- assertThat(InstrumentationRegistry.getInstrumentation().getContext().getPackageName())
- .isEqualTo(PACKAGE_NAME);
- }
- }
-
- /**
- * Make sure that having a config and a rule in a base class should fail.
- * RavenwoodRule.
- */
- @RunWith(AndroidJUnit4.class)
- // CHECKSTYLE:OFF
- @Expected("""
- testRunStarted: classes
- testSuiteStarted: classes
- testStarted: initializationError(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ConfigWithRuleInBaseClassTest)
- testFailure: RavenwoodConfig and RavenwoodRule cannot be used in the same class. Suggest migrating to RavenwoodConfig.
- testFinished: initializationError(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ConfigWithRuleInBaseClassTest)
- testSuiteFinished: classes
- testRunFinished: 1,1,0,0
- """)
- // CHECKSTYLE:ON
- public static class ConfigWithRuleInBaseClassTest extends RuleInBaseClass {
- @RavenwoodConfig.Config
- public static RavenwoodConfig sConfig = new RavenwoodConfig.Builder().build();
-
- @Test
- public void test() {
- }
- }
-
- /**
- * Same as {@link RuleInBaseClass}, but the type of the rule field is not {@link RavenwoodRule}.
- */
- public abstract static class RuleWithDifferentTypeInBaseClass {
- static String PACKAGE_NAME = "com.RuleWithDifferentTypeInBaseClass";
- @Rule
- public final TestRule mRavenwood1 = new RavenwoodRule.Builder()
- .setPackageName(PACKAGE_NAME).build();
- }
-
- /**
- * Make sure that RavenwoodRule in a base class takes effect, even if the field type is not
- */
- @RunWith(AndroidJUnit4.class)
- // CHECKSTYLE:OFF
- @Expected("""
- testRunStarted: classes
- testSuiteStarted: classes
- testSuiteStarted: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$RuleWithDifferentTypeInBaseClassSuccessTest
- testStarted: testRuleInBaseClass(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$RuleWithDifferentTypeInBaseClassSuccessTest)
- testFailure: If you have a RavenwoodRule in your test, make sure the field type is RavenwoodRule so Ravenwood can detect it.
- testFinished: testRuleInBaseClass(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$RuleWithDifferentTypeInBaseClassSuccessTest)
- testSuiteFinished: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$RuleWithDifferentTypeInBaseClassSuccessTest
- testSuiteFinished: classes
- testRunFinished: 1,1,0,0
- """)
- // CHECKSTYLE:ON
- @Ignore // Package name is no longer set via config.
- public static class RuleWithDifferentTypeInBaseClassSuccessTest extends RuleWithDifferentTypeInBaseClass {
-
- @Test
- public void testRuleInBaseClass() {
- assertThat(InstrumentationRegistry.getInstrumentation().getContext().getPackageName())
- .isEqualTo(PACKAGE_NAME);
- }
- }
-
- /**
- * Make sure that having a config and a rule in a base class should fail, even if the field type is not
- * RavenwoodRule.
- */
- @RunWith(AndroidJUnit4.class)
- // CHECKSTYLE:OFF
- @Expected("""
- testRunStarted: classes
- testSuiteStarted: classes
- testSuiteStarted: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ConfigWithRuleWithDifferentTypeInBaseClassTest
- testStarted: test(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ConfigWithRuleWithDifferentTypeInBaseClassTest)
- testFailure: If you have a RavenwoodRule in your test, make sure the field type is RavenwoodRule so Ravenwood can detect it.
- testFinished: test(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ConfigWithRuleWithDifferentTypeInBaseClassTest)
- testSuiteFinished: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ConfigWithRuleWithDifferentTypeInBaseClassTest
- testSuiteFinished: classes
- testRunFinished: 1,1,0,0
- """)
- // CHECKSTYLE:ON
- public static class ConfigWithRuleWithDifferentTypeInBaseClassTest extends RuleWithDifferentTypeInBaseClass {
- @RavenwoodConfig.Config
- public static RavenwoodConfig sConfig = new RavenwoodConfig.Builder().build();
-
- @Test
- public void test() {
- }
- }
-}
diff --git a/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/IdentityTest.java b/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/IdentityTest.java
index 8e04b698c9d9..271c27f6ae93 100644
--- a/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/IdentityTest.java
+++ b/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/IdentityTest.java
@@ -15,7 +15,6 @@
*/
package com.android.ravenwoodtest.runtimetest;
-import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
import static android.os.Process.FIRST_APPLICATION_UID;
import static org.junit.Assert.assertEquals;
@@ -23,7 +22,6 @@ import static org.junit.Assert.assertEquals;
import android.os.Binder;
import android.os.Build;
import android.os.Process;
-import android.platform.test.ravenwood.RavenwoodConfig;
import android.system.Os;
import com.android.ravenwood.RavenwoodRuntimeState;
@@ -34,13 +32,6 @@ import org.junit.Test;
public class IdentityTest {
- @RavenwoodConfig.Config
- public static final RavenwoodConfig sConfig =
- new RavenwoodConfig.Builder()
- .setTargetSdkLevel(UPSIDE_DOWN_CAKE)
- .setProcessApp()
- .build();
-
@Test
public void testUid() {
assertEquals(FIRST_APPLICATION_UID, RavenwoodRuntimeState.sUid);
@@ -60,7 +51,7 @@ public class IdentityTest {
@Test
public void testTargetSdkLevel() {
assertEquals(Build.VERSION_CODES.CUR_DEVELOPMENT, RavenwoodRuntimeState.CUR_DEVELOPMENT);
- assertEquals(UPSIDE_DOWN_CAKE, RavenwoodRuntimeState.sTargetSdkLevel);
- assertEquals(UPSIDE_DOWN_CAKE, VMRuntime.getRuntime().getTargetSdkVersion());
+ assertEquals(RavenwoodRuntimeState.sTargetSdkLevel,
+ VMRuntime.getRuntime().getTargetSdkVersion());
}
}
diff --git a/ravenwood/tests/services-test/test/com/android/ravenwoodtest/servicestest/RavenwoodServicesTest.java b/ravenwood/tests/services-test/test/com/android/ravenwoodtest/servicestest/RavenwoodServicesTest.java
index 4aae1e11b72e..e83a247bd769 100644
--- a/ravenwood/tests/services-test/test/com/android/ravenwoodtest/servicestest/RavenwoodServicesTest.java
+++ b/ravenwood/tests/services-test/test/com/android/ravenwoodtest/servicestest/RavenwoodServicesTest.java
@@ -24,8 +24,6 @@ import static org.junit.Assert.fail;
import android.content.Context;
import android.hardware.SerialManager;
import android.hardware.SerialManagerInternal;
-import android.platform.test.ravenwood.RavenwoodConfig;
-import android.platform.test.ravenwood.RavenwoodConfig.Config;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
@@ -42,12 +40,6 @@ import org.junit.runner.RunWith;
public class RavenwoodServicesTest {
private static final String TEST_VIRTUAL_PORT = "virtual:example";
- @Config
- public static final RavenwoodConfig sRavenwood = new RavenwoodConfig.Builder()
- .setProcessSystem()
- .setServicesRequired(SerialManager.class)
- .build();
-
private Context mContext;
@Before
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index 3dcca1433dec..4cf17ae3984d 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -69,6 +69,7 @@ import android.service.battery.BatteryServiceDumpProto;
import android.sysprop.PowerProperties;
import android.util.EventLog;
import android.util.Slog;
+import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.VisibleForTesting;
@@ -303,6 +304,17 @@ public final class BatteryService extends SystemService {
*/
@VisibleForTesting
public long mLastBroadcastVoltageUpdateTime;
+ /**
+ * Time when the max charging current was updated last by HAL and we sent the
+ * {@link Intent#ACTION_BATTERY_CHANGED} broadcast.
+ * Note: This value is used to rate limit the {@link Intent#ACTION_BATTERY_CHANGED} broadcast
+ * so it is possible that max current was updated but we did not send the broadcast so in that
+ * case we do not update the time.
+ */
+ @VisibleForTesting
+ public long mLastBroadcastMaxChargingCurrentUpdateTime;
+
+ private boolean mIsFirstBatteryChangedUpdate = true;
private Led mLed;
@@ -350,16 +362,21 @@ public final class BatteryService extends SystemService {
private static final int ABSOLUTE_DECI_CELSIUS_DIFF_FOR_TEMP_UPDATE = 10;
/**
* This value is used to rate limit the {@link Intent#ACTION_BATTERY_CHANGED} broadcast. We
- * only send the broadcast if the last voltage was updated at least 20s seconds back and has a
+ * only send the broadcast if the last voltage was updated at least 20 seconds back and has a
* fluctuation of at least 1%.
*/
private static final int TIME_DIFF_FOR_VOLTAGE_UPDATE_MS = 20000;
/**
* The value is used to rate limit the {@link Intent#ACTION_BATTERY_CHANGED} broadcast. We
- * only send the broadcast if the last voltage was updated at least 20s seconds back and has a
+ * only send the broadcast if the last voltage was updated at least 20 seconds back and has a
* fluctuation of at least 1%.
*/
private static final float BASE_POINT_DIFF_FOR_VOLTAGE_UPDATE = 0.01f;
+ /**
+ * This value is used to rate limit the {@link Intent#ACTION_BATTERY_CHANGED} broadcast. We
+ * only send the broadcast if the last max charging current was updated at least 5 seconds back.
+ */
+ private static final int TIME_DIFF_FOR_MAX_CHARGING_CURRENT_UPDATE_MS = 5000;
private final Handler.Callback mLocalCallback = msg -> {
switch (msg.what) {
@@ -1252,8 +1269,10 @@ public final class BatteryService extends SystemService {
if (!com.android.server.flags.Flags.rateLimitBatteryChangedBroadcast()) {
return false;
}
- if (mLastBroadcastBatteryVoltage == 0 || mLastBroadcastBatteryTemperature == 0) {
+ if (mIsFirstBatteryChangedUpdate) {
mLastBroadcastVoltageUpdateTime = SystemClock.elapsedRealtime();
+ mLastBroadcastMaxChargingCurrentUpdateTime = SystemClock.elapsedRealtime();
+ mIsFirstBatteryChangedUpdate = false;
return false;
}
@@ -1261,13 +1280,14 @@ public final class BatteryService extends SystemService {
mLastBroadcastBatteryVoltage != mHealthInfo.batteryVoltageMillivolts;
final boolean temperatureUpdated =
mLastBroadcastBatteryTemperature != mHealthInfo.batteryTemperatureTenthsCelsius;
+ final boolean maxChargingCurrentUpdated =
+ mLastBroadcastMaxChargingCurrent != mHealthInfo.maxChargingCurrentMicroamps;
final boolean otherStatesUpdated = forceUpdate
|| mHealthInfo.batteryStatus != mLastBroadcastBatteryStatus
|| mHealthInfo.batteryHealth != mLastBroadcastBatteryHealth
|| mHealthInfo.batteryPresent != mLastBroadcastBatteryPresent
|| mHealthInfo.batteryLevel != mLastBroadcastBatteryLevel
|| mPlugType != mLastBroadcastPlugType
- || mHealthInfo.maxChargingCurrentMicroamps != mLastBroadcastMaxChargingCurrent
|| mHealthInfo.maxChargingVoltageMicrovolts != mLastBroadcastMaxChargingVoltage
|| mInvalidCharger != mLastBroadcastInvalidCharger
|| mHealthInfo.batteryCycleCount != mLastBroadcastBatteryCycleCount
@@ -1280,6 +1300,9 @@ public final class BatteryService extends SystemService {
if (voltageUpdated) {
mLastBroadcastVoltageUpdateTime = SystemClock.elapsedRealtime();
}
+ if (maxChargingCurrentUpdated) {
+ mLastBroadcastMaxChargingCurrentUpdateTime = SystemClock.elapsedRealtime();
+ }
return false;
}
@@ -1295,6 +1318,9 @@ public final class BatteryService extends SystemService {
>= TIME_DIFF_FOR_VOLTAGE_UPDATE_MS) {
mLastBroadcastVoltageUpdateTime = SystemClock.elapsedRealtime();
+ if (maxChargingCurrentUpdated) {
+ mLastBroadcastMaxChargingCurrentUpdateTime = SystemClock.elapsedRealtime();
+ }
return false;
}
@@ -1307,6 +1333,20 @@ public final class BatteryService extends SystemService {
if (voltageUpdated) {
mLastBroadcastVoltageUpdateTime = SystemClock.elapsedRealtime();
}
+ if (maxChargingCurrentUpdated) {
+ mLastBroadcastMaxChargingCurrentUpdateTime = SystemClock.elapsedRealtime();
+ }
+ return false;
+ }
+
+ if (maxChargingCurrentUpdated
+ && SystemClock.elapsedRealtime() - mLastBroadcastMaxChargingCurrentUpdateTime
+ >= TIME_DIFF_FOR_MAX_CHARGING_CURRENT_UPDATE_MS) {
+ mLastBroadcastMaxChargingCurrentUpdateTime = SystemClock.elapsedRealtime();
+
+ if (voltageUpdated) {
+ mLastBroadcastVoltageUpdateTime = SystemClock.elapsedRealtime();
+ }
return false;
}
@@ -1615,6 +1655,9 @@ public final class BatteryService extends SystemService {
pw.println(" Wireless powered: " + mHealthInfo.chargerWirelessOnline);
pw.println(" Dock powered: " + mHealthInfo.chargerDockOnline);
pw.println(" Max charging current: " + mHealthInfo.maxChargingCurrentMicroamps);
+ pw.println(" Time when the latest updated value of the Max charging current was"
+ + " sent via battery changed broadcast: "
+ + TimeUtils.formatDuration(mLastBroadcastMaxChargingCurrentUpdateTime));
pw.println(" Max charging voltage: " + mHealthInfo.maxChargingVoltageMicrovolts);
pw.println(" Charge counter: " + mHealthInfo.batteryChargeCounterUah);
pw.println(" status: " + mHealthInfo.batteryStatus);
@@ -1624,7 +1667,8 @@ public final class BatteryService extends SystemService {
pw.println(" scale: " + BATTERY_SCALE);
pw.println(" voltage: " + mHealthInfo.batteryVoltageMillivolts);
pw.println(" Time when the latest updated value of the voltage was sent via "
- + "battery changed broadcast: " + mLastBroadcastVoltageUpdateTime);
+ + "battery changed broadcast: "
+ + TimeUtils.formatDuration(mLastBroadcastVoltageUpdateTime));
pw.println(" The last voltage value sent via the battery changed broadcast: "
+ mLastBroadcastBatteryVoltage);
pw.println(" temperature: " + mHealthInfo.batteryTemperatureTenthsCelsius);
diff --git a/services/core/java/com/android/server/TEST_MAPPING b/services/core/java/com/android/server/TEST_MAPPING
index ce66dc3c76cb..8da835896bd3 100644
--- a/services/core/java/com/android/server/TEST_MAPPING
+++ b/services/core/java/com/android/server/TEST_MAPPING
@@ -176,6 +176,10 @@
"include-filter": "com.android.server.wm.BackgroundActivityStart*"
}
]
+ },
+ {
+ "name": "FrameworksMockingServicesTests_service_batteryServiceTest",
+ "file_patterns": ["BatteryService\\.java"]
}
]
}
diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
index a2200c9f8bf5..1c01fb9f19e0 100644
--- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
@@ -17,7 +17,7 @@
package com.android.server.audio;
import static android.media.AudioPlaybackConfiguration.EXTRA_PLAYER_EVENT_MUTE;
-import static android.media.AudioPlaybackConfiguration.MUTED_BY_APP_OPS;
+import static android.media.AudioPlaybackConfiguration.MUTED_BY_OP_PLAY_AUDIO;
import static android.media.AudioPlaybackConfiguration.MUTED_BY_CLIENT_VOLUME;
import static android.media.AudioPlaybackConfiguration.MUTED_BY_MASTER;
import static android.media.AudioPlaybackConfiguration.MUTED_BY_PORT_VOLUME;
@@ -1377,8 +1377,8 @@ public final class PlaybackActivityMonitor
if ((eventValue & MUTED_BY_STREAM_MUTED) != 0) {
builder.append("streamMute ");
}
- if ((eventValue & MUTED_BY_APP_OPS) != 0) {
- builder.append("appOps ");
+ if ((eventValue & MUTED_BY_OP_PLAY_AUDIO) != 0) {
+ builder.append("opPlayAudio ");
}
if ((eventValue & MUTED_BY_CLIENT_VOLUME) != 0) {
builder.append("clientVolume ");
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 715633410575..442db10ab039 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -5875,6 +5875,67 @@ public class PackageManagerService implements PackageSender, TestUtilityService
userId, callingPackage);
}
+ @Override
+ public void setPageSizeAppCompatFlagsSettingsOverride(String packageName, boolean enabled) {
+ final int callingUid = Binder.getCallingUid();
+ final int callingAppId = UserHandle.getAppId(callingUid);
+
+ if (!PackageManagerServiceUtils.isSystemOrRoot(callingAppId)) {
+ throw new SecurityException("Caller must be the system or root.");
+ }
+
+ int settingsMode = enabled
+ ? ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_SETTINGS_OVERRIDE_ENABLED
+ : ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_SETTINGS_OVERRIDE_DISABLED;
+ PackageStateMutator.Result result =
+ commitPackageStateMutation(
+ null,
+ packageName,
+ packageState ->
+ packageState
+ .setPageSizeAppCompatFlags(settingsMode));
+ if (result.isSpecificPackageNull()) {
+ throw new IllegalArgumentException("Unknown package: " + packageName);
+ }
+ scheduleWriteSettings();
+ }
+
+ @Override
+ public boolean isPageSizeCompatEnabled(String packageName) {
+ final int callingUid = Binder.getCallingUid();
+ final int callingAppId = UserHandle.getAppId(callingUid);
+ final int userId = UserHandle.getCallingUserId();
+
+ if (!PackageManagerServiceUtils.isSystemOrRoot(callingAppId)) {
+ throw new SecurityException("Caller must be the system or root.");
+ }
+
+ PackageStateInternal packageState =
+ snapshotComputer().getPackageStateForInstalledAndFiltered(
+ packageName, callingUid, userId);
+
+ return packageState == null ? false : packageState.isPageSizeAppCompatEnabled();
+ }
+
+ @Override
+ public String getPageSizeCompatWarningMessage(String packageName) {
+ final int callingUid = Binder.getCallingUid();
+ final int callingAppId = UserHandle.getAppId(callingUid);
+ final int userId = UserHandle.getCallingUserId();
+
+ if (!PackageManagerServiceUtils.isSystemOrRoot(callingAppId)) {
+ throw new SecurityException("Caller must be the system or root.");
+ }
+
+ PackageStateInternal packageState =
+ snapshotComputer().getPackageStateForInstalledAndFiltered(
+ packageName, callingUid, userId);
+
+ return packageState == null
+ ? null
+ : packageState.getPageSizeCompatWarningMessage(mContext);
+ }
+
@android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_USERS)
@Override
public boolean setApplicationHiddenSettingAsUser(String packageName, boolean hidden,
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 9428de700385..fb16b862b275 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -27,6 +27,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.content.ComponentName;
+import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.SharedLibraryInfo;
@@ -221,6 +222,8 @@ public class PackageSetting extends SettingBase implements PackageStateInternal
/** @see PackageState#getCategoryOverride() */
private int categoryOverride = ApplicationInfo.CATEGORY_UNDEFINED;
+ private int mPageSizeAppCompatFlags = ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_UNDEFINED;
+
@NonNull
private final PackageStateUnserialized pkgState = new PackageStateUnserialized(this);
@@ -863,6 +866,8 @@ public class PackageSetting extends SettingBase implements PackageStateInternal
}
copyMimeGroups(other.mimeGroups);
+ mPageSizeAppCompatFlags = other.mPageSizeAppCompatFlags;
+
pkgState.updateFrom(other.pkgState);
onChanged();
}
@@ -1617,6 +1622,34 @@ public class PackageSetting extends SettingBase implements PackageStateInternal
return this;
}
+ /**
+ * @see Set page size app compat mode.
+ */
+ public PackageSetting setPageSizeAppCompatFlags(int mode) {
+ if (mode < 0 || mode >= ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_MAX) {
+ throw new IllegalArgumentException("Invalid page size compat mode specified");
+ }
+
+ // OR assignment is used here to avoid overriding the mode set by the manifest.
+ this.mPageSizeAppCompatFlags |= mode;
+
+ // Only one bit of the following can be set at same time. Both are needed to detect app
+ // compat 'disabled' state from settings vs bit was never set.
+ if (ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_SETTINGS_OVERRIDE_ENABLED == mode) {
+ this.mPageSizeAppCompatFlags &=
+ ~ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_SETTINGS_OVERRIDE_DISABLED;
+ } else if (ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_SETTINGS_OVERRIDE_DISABLED == mode) {
+ this.mPageSizeAppCompatFlags &=
+ ~ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_SETTINGS_OVERRIDE_ENABLED;
+ }
+ onChanged();
+ return this;
+ }
+
+ public int getPageSizeAppCompatFlags() {
+ return mPageSizeAppCompatFlags;
+ }
+
public PackageSetting setLegacyNativeLibraryPath(
String legacyNativeLibraryPathString) {
this.legacyNativeLibraryPath = legacyNativeLibraryPathString;
@@ -1787,6 +1820,63 @@ public class PackageSetting extends SettingBase implements PackageStateInternal
return getBoolean(Booleans.SCANNED_AS_STOPPED_SYSTEM_APP);
}
+ /** Returns true if ELF files will be loaded in Page size compatibility mode */
+ @Override
+ public boolean isPageSizeAppCompatEnabled() {
+ // If manifest or settings has disabled the compat mode, don't run app in compat mode.
+ boolean manifestOverrideDisabled = (mPageSizeAppCompatFlags
+ & ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_MANIFEST_OVERRIDE_DISABLED) != 0;
+ boolean settingsOverrideDisabled = (mPageSizeAppCompatFlags
+ & ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_SETTINGS_OVERRIDE_DISABLED) != 0;
+ if (manifestOverrideDisabled || settingsOverrideDisabled) {
+ return false;
+ }
+
+ int mask =
+ ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_ELF_NOT_ALIGNED
+ | ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_MANIFEST_OVERRIDE_ENABLED
+ | ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_SETTINGS_OVERRIDE_ENABLED;
+ return (mPageSizeAppCompatFlags & mask) != 0;
+ }
+
+ /**
+ * Returns dialog string based on alignment of uncompressed shared libs inside the APK and ELF
+ * alignment.
+ */
+ @Override
+ public String getPageSizeCompatWarningMessage(Context context) {
+ boolean manifestOverrideEnabled = (mPageSizeAppCompatFlags
+ & ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_MANIFEST_OVERRIDE_ENABLED) != 0;
+ boolean settingsOverrideEnabled = (mPageSizeAppCompatFlags
+ & ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_MANIFEST_OVERRIDE_ENABLED) != 0;
+ if (manifestOverrideEnabled || settingsOverrideEnabled) {
+ return null;
+ }
+
+ boolean uncompressedLibsNotAligned = (mPageSizeAppCompatFlags
+ & ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_UNCOMPRESSED_LIBS_NOT_ALIGNED) != 0;
+ boolean elfNotAligned = (mPageSizeAppCompatFlags
+ & ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_ELF_NOT_ALIGNED) != 0;
+
+ if (uncompressedLibsNotAligned && elfNotAligned) {
+ return context.getText(
+ com.android.internal.R.string.page_size_compat_apk_and_elf_warning)
+ .toString();
+ }
+
+ if (uncompressedLibsNotAligned) {
+ return context.getText(com.android.internal.R.string.page_size_compat_apk_warning)
+ .toString();
+ }
+
+ if (elfNotAligned) {
+ return context.getText(com.android.internal.R.string.page_size_compat_elf_warning)
+ .toString();
+ }
+
+ return null;
+ }
+
// Code below generated by codegen v1.0.23.
@@ -1952,7 +2042,6 @@ public class PackageSetting extends SettingBase implements PackageStateInternal
@Deprecated
private void __metadata() {}
-
//@formatter:on
// End of generated code
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 1f672a093b38..485a28070bc5 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -3313,6 +3313,11 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile
if (pkg.getBaseRevisionCode() != 0) {
serializer.attributeInt(null, "baseRevisionCode", pkg.getBaseRevisionCode());
}
+ if (pkg.getPageSizeAppCompatFlags()
+ != ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_UNDEFINED) {
+ serializer.attributeInt(null, "pageSizeCompat", pkg.getPageSizeAppCompatFlags());
+ }
+
serializer.attributeFloat(null, "loadingProgress", pkg.getLoadingProgress());
serializer.attributeLongHex(null, "loadingCompletedTime", pkg.getLoadingCompletedTime());
@@ -4129,6 +4134,7 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile
boolean isScannedAsStoppedSystemApp = false;
boolean isSdkLibrary = false;
int baseRevisionCode = 0;
+ int PageSizeCompat = 0;
try {
name = parser.getAttributeValue(null, ATTR_NAME);
realName = parser.getAttributeValue(null, "realName");
@@ -4175,6 +4181,8 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile
appMetadataSource = parser.getAttributeInt(null, "appMetadataSource",
PackageManager.APP_METADATA_SOURCE_UNKNOWN);
baseRevisionCode = parser.getAttributeInt(null, "baseRevisionCode", 0);
+ PageSizeCompat = parser.getAttributeInt(null, "pageSizeCompat",
+ ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_UNDEFINED);
isScannedAsStoppedSystemApp = parser.getAttributeBoolean(null,
"scannedAsStoppedSystemApp", false);
@@ -4330,7 +4338,8 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile
.setTargetSdkVersion(targetSdkVersion)
.setBaseRevisionCode(baseRevisionCode)
.setRestrictUpdateHash(restrictUpdateHash)
- .setScannedAsStoppedSystemApp(isScannedAsStoppedSystemApp);
+ .setScannedAsStoppedSystemApp(isScannedAsStoppedSystemApp)
+ .setPageSizeAppCompatFlags(PageSizeCompat);
// Handle legacy string here for single-user mode
final String enabledStr = parser.getAttributeValue(null, ATTR_ENABLED);
if (enabledStr != null) {
@@ -5211,6 +5220,10 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile
pw.print(" (override=true)");
}
pw.println();
+ pw.print(prefix);
+ pw.print(" pageSizeCompat=");
+ pw.print(ps.getPageSizeAppCompatFlags());
+ pw.println();
if (!ps.getPkg().getQueriesPackages().isEmpty()) {
pw.append(prefix).append(" queriesPackages=")
.println(ps.getPkg().getQueriesPackages());
diff --git a/services/core/java/com/android/server/pm/pkg/PackageState.java b/services/core/java/com/android/server/pm/pkg/PackageState.java
index bbc17c83cfac..33fc066a62ee 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageState.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageState.java
@@ -22,6 +22,7 @@ import android.annotation.Nullable;
import android.annotation.Size;
import android.annotation.SystemApi;
import android.annotation.UserIdInt;
+import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
@@ -198,6 +199,21 @@ public interface PackageState {
int getCategoryOverride();
/**
+ * Returns true if ELF files will be loaded in Page size compatibility mode
+ *
+ * @hide
+ */
+ boolean isPageSizeAppCompatEnabled();
+
+ /**
+ * Returns dialog string based on alignment of uncompressed shared libs inside the APK and ELF
+ * alignment.
+ *
+ * @hide
+ */
+ String getPageSizeCompatWarningMessage(Context context);
+
+ /**
* The install time CPU override, if any. This value is written at install time
* and doesn't change during the life of an install. If non-null,
* {@link #getPrimaryCpuAbiLegacy()} will also contain the same value.
diff --git a/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java b/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java
index 253eb4006122..a46c4a695d60 100644
--- a/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java
+++ b/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java
@@ -257,6 +257,16 @@ public class PackageStateMutator {
@NonNull
@Override
+ public PackageStateWrite setPageSizeAppCompatFlags(
+ @ApplicationInfo.PageSizeAppCompatFlags int mode) {
+ if (mState != null) {
+ mState.setPageSizeAppCompatFlags(mode);
+ }
+ return this;
+ }
+
+ @NonNull
+ @Override
public PackageStateWrite setUpdateAvailable(boolean updateAvailable) {
if (mState != null) {
mState.setUpdateAvailable(updateAvailable);
diff --git a/services/core/java/com/android/server/pm/pkg/mutate/PackageStateWrite.java b/services/core/java/com/android/server/pm/pkg/mutate/PackageStateWrite.java
index 55d96f3aee08..f8f8695b2832 100644
--- a/services/core/java/com/android/server/pm/pkg/mutate/PackageStateWrite.java
+++ b/services/core/java/com/android/server/pm/pkg/mutate/PackageStateWrite.java
@@ -46,6 +46,10 @@ public interface PackageStateWrite {
@NonNull
PackageStateWrite setCategoryOverride(@ApplicationInfo.Category int category);
+ /** set 16Kb App compat mode. @see ApplicationInfo.PageSizeAppCompatFlags */
+ @NonNull
+ PackageStateWrite setPageSizeAppCompatFlags(@ApplicationInfo.PageSizeAppCompatFlags int mode);
+
@NonNull
PackageStateWrite setUpdateAvailable(boolean updateAvailable);
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
index 3fdb53f5ab59..31f03704a756 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
@@ -289,6 +289,7 @@ class AndroidPackageTest : ParcelableComponentTest(AndroidPackage::class, Packag
AndroidPackage::getEmergencyInstaller,
AndroidPackage::isAllowCrossUidActivitySwitchFromBelow,
AndroidPackage::getIntentMatchingFlags,
+ AndroidPackage::getPageSizeAppCompatFlags,
)
override fun extraParams() = listOf(
diff --git a/services/tests/mockingservicestests/Android.bp b/services/tests/mockingservicestests/Android.bp
index 993569fb17fe..0d25426700a6 100644
--- a/services/tests/mockingservicestests/Android.bp
+++ b/services/tests/mockingservicestests/Android.bp
@@ -392,3 +392,10 @@ test_module_config {
],
include_filters: ["com.android.server.StorageManagerServiceTest"],
}
+
+test_module_config {
+ name: "FrameworksMockingServicesTests_service_batteryServiceTest",
+ base: "FrameworksMockingServicesTests",
+ test_suites: ["device-tests"],
+ include_filters: ["com.android.server.BatteryServiceTest"],
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/BatteryServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/BatteryServiceTest.java
index 5e2f80bf8311..1fbd53a27a4f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/BatteryServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/BatteryServiceTest.java
@@ -79,6 +79,8 @@ public class BatteryServiceTest {
private static final int UPDATED_BATTERY_HEALTH = 3;
private static final int CURRENT_CHARGE_COUNTER = 4680000;
private static final int UPDATED_CHARGE_COUNTER = 4218000;
+ private static final int CURRENT_MAX_CHARGING_CURRENT = 298125;
+ private static final int UPDATED_MAX_CHARGING_CURRENT = 398125;
private static final int HANDLER_IDLE_TIME_MS = 5000;
@Rule
public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this)
@@ -143,7 +145,7 @@ public class BatteryServiceTest {
@EnableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST)
public void onlyVoltageUpdated_lessThenOnePercent_broadcastNotSent() {
mBatteryService.update(createHealthInfo(VOLTAGE_LESS_THEN_ONE_PERCENT, CURRENT_BATTERY_TEMP,
- CURRENT_CHARGE_COUNTER, CURRENT_BATTERY_HEALTH));
+ CURRENT_CHARGE_COUNTER, CURRENT_BATTERY_HEALTH, CURRENT_MAX_CHARGING_CURRENT));
waitForHandlerToExecute();
@@ -156,7 +158,8 @@ public class BatteryServiceTest {
mBatteryService.update(
createHealthInfo(VOLTAGE_MORE_THEN_ONE_PERCENT, CURRENT_BATTERY_TEMP,
CURRENT_CHARGE_COUNTER,
- CURRENT_BATTERY_HEALTH));
+ CURRENT_BATTERY_HEALTH,
+ CURRENT_MAX_CHARGING_CURRENT));
waitForHandlerToExecute();
@@ -165,13 +168,17 @@ public class BatteryServiceTest {
@Test
@EnableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST)
- public void onlyVoltageUpdated_broadcastSent() {
+ public void voltageUpdated_withUpdateInChargingCurrent_broadcastSent() {
mBatteryService.mLastBroadcastVoltageUpdateTime = SystemClock.elapsedRealtime() - 20000;
+ long lastChargingCurrentUpdateTime =
+ mBatteryService.mLastBroadcastMaxChargingCurrentUpdateTime;
mBatteryService.update(createHealthInfo(VOLTAGE_MORE_THEN_ONE_PERCENT, CURRENT_BATTERY_TEMP,
- CURRENT_CHARGE_COUNTER, CURRENT_BATTERY_HEALTH));
+ CURRENT_CHARGE_COUNTER, CURRENT_BATTERY_HEALTH, UPDATED_MAX_CHARGING_CURRENT));
waitForHandlerToExecute();
+ assertTrue(lastChargingCurrentUpdateTime
+ < mBatteryService.mLastBroadcastMaxChargingCurrentUpdateTime);
verifyNumberOfTimesBroadcastSent(1);
}
@@ -180,7 +187,8 @@ public class BatteryServiceTest {
public void onlyTempUpdated_lessThenOneDegreeCelsius_broadcastNotSent() {
mBatteryService.update(
createHealthInfo(CURRENT_BATTERY_VOLTAGE, TEMP_LESS_THEN_ONE_DEGREE_CELSIUS,
- CURRENT_CHARGE_COUNTER, CURRENT_BATTERY_HEALTH));
+ CURRENT_CHARGE_COUNTER, CURRENT_BATTERY_HEALTH,
+ CURRENT_MAX_CHARGING_CURRENT));
waitForHandlerToExecute();
@@ -191,23 +199,31 @@ public class BatteryServiceTest {
@EnableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST)
public void tempUpdated_broadcastSent() {
long lastVoltageUpdateTime = mBatteryService.mLastBroadcastVoltageUpdateTime;
+ long lastChargingCurrentUpdateTime =
+ mBatteryService.mLastBroadcastMaxChargingCurrentUpdateTime;
mBatteryService.update(
createHealthInfo(VOLTAGE_LESS_THEN_ONE_PERCENT, TEMP_MORE_THEN_ONE_DEGREE_CELSIUS,
- UPDATED_CHARGE_COUNTER, CURRENT_BATTERY_HEALTH));
+ UPDATED_CHARGE_COUNTER, CURRENT_BATTERY_HEALTH,
+ UPDATED_MAX_CHARGING_CURRENT));
waitForHandlerToExecute();
assertTrue(lastVoltageUpdateTime < mBatteryService.mLastBroadcastVoltageUpdateTime);
+ assertTrue(lastChargingCurrentUpdateTime
+ < mBatteryService.mLastBroadcastMaxChargingCurrentUpdateTime);
verifyNumberOfTimesBroadcastSent(1);
}
@Test
@EnableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST)
- public void batteryHealthUpdated_voltageAndTempConst_broadcastSent() {
+ public void batteryHealthUpdated_withOtherExtrasConstant_broadcastSent() {
+ long lastVoltageUpdateTime = mBatteryService.mLastBroadcastVoltageUpdateTime;
+ long lastChargingCurrentUpdateTime =
+ mBatteryService.mLastBroadcastMaxChargingCurrentUpdateTime;
mBatteryService.update(
- createHealthInfo(CURRENT_BATTERY_VOLTAGE, CURRENT_BATTERY_TEMP,
+ createHealthInfo(VOLTAGE_LESS_THEN_ONE_PERCENT, CURRENT_BATTERY_TEMP,
CURRENT_CHARGE_COUNTER,
- UPDATED_BATTERY_HEALTH));
+ UPDATED_BATTERY_HEALTH, UPDATED_MAX_CHARGING_CURRENT));
waitForHandlerToExecute();
@@ -217,10 +233,13 @@ public class BatteryServiceTest {
mBatteryService.update(
createHealthInfo(CURRENT_BATTERY_VOLTAGE, CURRENT_BATTERY_TEMP,
UPDATED_CHARGE_COUNTER,
- UPDATED_BATTERY_HEALTH));
+ UPDATED_BATTERY_HEALTH, CURRENT_MAX_CHARGING_CURRENT));
waitForHandlerToExecute();
+ assertTrue(lastVoltageUpdateTime < mBatteryService.mLastBroadcastVoltageUpdateTime);
+ assertTrue(lastChargingCurrentUpdateTime
+ < mBatteryService.mLastBroadcastMaxChargingCurrentUpdateTime);
verifyNumberOfTimesBroadcastSent(1);
}
@@ -228,7 +247,7 @@ public class BatteryServiceTest {
@DisableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST)
public void voltageUpdated_lessThanOnePercent_flagDisabled_broadcastSent() {
mBatteryService.update(createHealthInfo(VOLTAGE_LESS_THEN_ONE_PERCENT, CURRENT_BATTERY_TEMP,
- CURRENT_CHARGE_COUNTER, CURRENT_BATTERY_HEALTH));
+ CURRENT_CHARGE_COUNTER, CURRENT_BATTERY_HEALTH, CURRENT_MAX_CHARGING_CURRENT));
waitForHandlerToExecute();
@@ -241,7 +260,7 @@ public class BatteryServiceTest {
mBatteryService.update(
createHealthInfo(CURRENT_BATTERY_VOLTAGE, CURRENT_BATTERY_TEMP,
UPDATED_CHARGE_COUNTER,
- CURRENT_BATTERY_HEALTH));
+ CURRENT_BATTERY_HEALTH, CURRENT_MAX_CHARGING_CURRENT));
waitForHandlerToExecute();
@@ -254,7 +273,7 @@ public class BatteryServiceTest {
mBatteryService.update(
createHealthInfo(CURRENT_BATTERY_VOLTAGE, TEMP_LESS_THEN_ONE_DEGREE_CELSIUS,
UPDATED_CHARGE_COUNTER,
- CURRENT_BATTERY_HEALTH));
+ CURRENT_BATTERY_HEALTH, CURRENT_MAX_CHARGING_CURRENT));
waitForHandlerToExecute();
@@ -267,10 +286,42 @@ public class BatteryServiceTest {
mBatteryService.update(
createHealthInfo(CURRENT_BATTERY_VOLTAGE, CURRENT_BATTERY_TEMP,
UPDATED_CHARGE_COUNTER,
- CURRENT_BATTERY_HEALTH));
+ CURRENT_BATTERY_HEALTH, CURRENT_MAX_CHARGING_CURRENT));
+
+ waitForHandlerToExecute();
+
+ verifyNumberOfTimesBroadcastSent(1);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST)
+ public void onlyMaxChargingCurrentUpdated_beforeFiveSeconds_broadcastNotSent() {
+ mBatteryService.update(
+ createHealthInfo(CURRENT_BATTERY_VOLTAGE, CURRENT_BATTERY_TEMP,
+ CURRENT_CHARGE_COUNTER,
+ CURRENT_BATTERY_HEALTH,
+ UPDATED_MAX_CHARGING_CURRENT));
waitForHandlerToExecute();
+ verifyNumberOfTimesBroadcastSent(0);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST)
+ public void maxChargingCurrentUpdated_afterFiveSeconds_broadcastSent() {
+ mBatteryService.mLastBroadcastMaxChargingCurrentUpdateTime =
+ SystemClock.elapsedRealtime() - 5000;
+ long lastVoltageUpdateTime = mBatteryService.mLastBroadcastVoltageUpdateTime;
+ mBatteryService.update(
+ createHealthInfo(VOLTAGE_MORE_THEN_ONE_PERCENT, CURRENT_BATTERY_TEMP,
+ CURRENT_CHARGE_COUNTER,
+ CURRENT_BATTERY_HEALTH,
+ UPDATED_MAX_CHARGING_CURRENT));
+
+ waitForHandlerToExecute();
+
+ assertTrue(lastVoltageUpdateTime < mBatteryService.mLastBroadcastVoltageUpdateTime);
verifyNumberOfTimesBroadcastSent(1);
}
@@ -278,7 +329,8 @@ public class BatteryServiceTest {
int batteryVoltage,
int batteryTemperature,
int batteryChargeCounter,
- int batteryHealth) {
+ int batteryHealth,
+ int maxChargingCurrent) {
HealthInfo h = new HealthInfo();
h.batteryVoltageMillivolts = batteryVoltage;
h.batteryTemperatureTenthsCelsius = batteryTemperature;
@@ -287,7 +339,7 @@ public class BatteryServiceTest {
h.batteryHealth = batteryHealth;
h.batteryPresent = true;
h.batteryLevel = 100;
- h.maxChargingCurrentMicroamps = 298125;
+ h.maxChargingCurrentMicroamps = maxChargingCurrent;
h.batteryCurrentAverageMicroamps = -2812;
h.batteryCurrentMicroamps = 298125;
h.maxChargingVoltageMicrovolts = 3000;
@@ -308,7 +360,8 @@ public class BatteryServiceTest {
mBatteryService.update(
createHealthInfo(CURRENT_BATTERY_VOLTAGE, CURRENT_BATTERY_TEMP,
CURRENT_CHARGE_COUNTER,
- CURRENT_BATTERY_HEALTH));
+ CURRENT_BATTERY_HEALTH,
+ CURRENT_MAX_CHARGING_CURRENT));
waitForHandlerToExecute();
}
diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/BottomHalfPipAppHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/BottomHalfPipAppHelper.kt
deleted file mode 100644
index 6573c2c83f20..000000000000
--- a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/BottomHalfPipAppHelper.kt
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm.flicker.helpers
-
-import android.app.Instrumentation
-import android.content.Intent
-import android.tools.traces.parsers.toFlickerComponent
-import com.android.server.wm.flicker.testapp.ActivityOptions
-
-class BottomHalfPipAppHelper(
- instrumentation: Instrumentation,
- private val useLaunchingActivity: Boolean = false,
-) : PipAppHelper(
- instrumentation,
- appName = ActivityOptions.BottomHalfPip.LABEL,
- componentNameMatcher = ActivityOptions.BottomHalfPip.COMPONENT
- .toFlickerComponent()
-) {
- override val openAppIntent: Intent
- get() = super.openAppIntent.apply {
- component = if (useLaunchingActivity) {
- ActivityOptions.BottomHalfPip.LAUNCHING_APP_COMPONENT
- } else {
- ActivityOptions.BottomHalfPip.COMPONENT
- }
- }
-} \ No newline at end of file
diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
index 2fe84b00b040..db4838ee6092 100644
--- a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
+++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
@@ -27,7 +27,6 @@ import android.tools.device.apphelpers.StandardAppHelper
import android.tools.helpers.FIND_TIMEOUT
import android.tools.helpers.SYSTEMUI_PACKAGE
import android.tools.traces.ConditionsFactory
-import android.tools.traces.component.ComponentNameMatcher
import android.tools.traces.component.IComponentMatcher
import android.tools.traces.parsers.WindowManagerStateHelper
import android.tools.traces.parsers.toFlickerComponent
@@ -36,11 +35,12 @@ import androidx.test.uiautomator.By
import androidx.test.uiautomator.Until
import com.android.server.wm.flicker.testapp.ActivityOptions
-open class PipAppHelper(
- instrumentation: Instrumentation,
- appName: String = ActivityOptions.Pip.LABEL,
- componentNameMatcher: ComponentNameMatcher = ActivityOptions.Pip.COMPONENT.toFlickerComponent(),
-) : StandardAppHelper(instrumentation, appName, componentNameMatcher) {
+open class PipAppHelper(instrumentation: Instrumentation) :
+ StandardAppHelper(
+ instrumentation,
+ ActivityOptions.Pip.LABEL,
+ ActivityOptions.Pip.COMPONENT.toFlickerComponent()
+ ) {
private val mediaSessionManager: MediaSessionManager
get() =
context.getSystemService(MediaSessionManager::class.java)
diff --git a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
index 7c24a4adca3d..9ce8e807f612 100644
--- a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
@@ -347,27 +347,6 @@
<category android:name="android.intent.category.LEANBACK_LAUNCHER"/>
</intent-filter>
</activity>
- <activity android:name=".BottomHalfPipLaunchingActivity"
- android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
- android:taskAffinity="com.android.server.wm.flicker.testapp.BottomHalfPipLaunchingActivity"
- android:theme="@style/CutoutShortEdges"
- android:label="BottomHalfPipLaunchingActivity"
- android:exported="true">
- <intent-filter>
- <action android:name="android.intent.action.MAIN"/>
- <category android:name="android.intent.category.LAUNCHER"/>
- </intent-filter>
- </activity>
- <activity
- android:name=".BottomHalfPipActivity"
- android:resizeableActivity="true"
- android:supportsPictureInPicture="true"
- android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
- android:taskAffinity="com.android.server.wm.flicker.testapp.BottomHalfPipLaunchingActivity"
- android:theme="@style/TranslucentTheme"
- android:label="BottomHalfPipActivity"
- android:exported="true">
- </activity>
<activity android:name=".SplitScreenActivity"
android:resizeableActivity="true"
android:taskAffinity="com.android.server.wm.flicker.testapp.SplitScreenActivity"
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml b/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml
index 837d050b73ff..47d113717ae0 100644
--- a/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml
@@ -62,12 +62,6 @@
<item name="android:backgroundDimEnabled">false</item>
</style>
- <style name="TranslucentTheme" parent="@style/OptOutEdgeToEdge">
- <item name="android:windowIsTranslucent">true</item>
- <item name="android:windowContentOverlay">@null</item>
- <item name="android:backgroundDimEnabled">false</item>
- </style>
-
<style name="no_starting_window" parent="@style/OptOutEdgeToEdge">
<item name="android:windowDisablePreview">true</item>
</style>
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
index 0c1ac9951d32..73625da9dfa5 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
@@ -241,21 +241,6 @@ public class ActivityOptions {
FLICKER_APP_PACKAGE + ".PipActivity");
}
- public static class BottomHalfPip {
- public static final String LAUNCHING_APP_LABEL = "BottomHalfPipLaunchingActivity";
- // Test App > Bottom Half PIP Activity
- public static final String LABEL = "BottomHalfPipActivity";
-
- // Use the bottom half layout for PIP Activity
- public static final String EXTRA_BOTTOM_HALF_LAYOUT = "bottom_half";
-
- public static final ComponentName LAUNCHING_APP_COMPONENT = new ComponentName(
- FLICKER_APP_PACKAGE, FLICKER_APP_PACKAGE + ".BottomHalfPipLaunchingActivity");
-
- public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
- FLICKER_APP_PACKAGE + ".BottomHalfPipActivity");
- }
-
public static class SplitScreen {
public static class Primary {
public static final String LABEL = "SplitScreenPrimaryActivity";
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BottomHalfPipActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BottomHalfPipActivity.java
deleted file mode 100644
index 3d4865572486..000000000000
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BottomHalfPipActivity.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm.flicker.testapp;
-
-import android.app.Activity;
-import android.content.res.Configuration;
-import android.os.Bundle;
-import android.view.ViewGroup.LayoutParams;
-import android.view.WindowManager;
-
-import androidx.annotation.NonNull;
-
-public class BottomHalfPipActivity extends PipActivity {
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setTheme(R.style.TranslucentTheme);
- updateLayout();
- }
-
- @Override
- public void onConfigurationChanged(@NonNull Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
-
- updateLayout();
- }
-
- /**
- * Sets to match parent layout if the activity is
- * {@link Activity#isInPictureInPictureMode()}. Otherwise, set to bottom half
- * layout.
- *
- * @see #setToBottomHalfMode(boolean)
- */
- private void updateLayout() {
- setToBottomHalfMode(!isInPictureInPictureMode());
- }
-
- /**
- * Sets `useBottomHalfLayout` to `true` to use the bottom half layout. Use the
- * [LayoutParams.MATCH_PARENT] layout.
- */
- private void setToBottomHalfMode(boolean useBottomHalfLayout) {
- final WindowManager.LayoutParams attrs = getWindow().getAttributes();
- if (useBottomHalfLayout) {
- final int taskHeight = getWindowManager().getCurrentWindowMetrics().getBounds()
- .height();
- attrs.y = taskHeight / 2;
- attrs.height = taskHeight / 2;
- } else {
- attrs.y = 0;
- attrs.height = LayoutParams.MATCH_PARENT;
- }
- getWindow().setAttributes(attrs);
- }
-}