Merge "Don't expand notifications when bouncer visible" into main
diff --git a/Android.bp b/Android.bp
index f1a3af2..57a5a3c 100644
--- a/Android.bp
+++ b/Android.bp
@@ -621,7 +621,6 @@
"--api-lint-ignore-prefix org. " +
"--error NoSettingsProvider " +
"--error UnhiddenSystemApi " +
- "--error UnflaggedApi " +
"--force-convert-to-warning-nullability-annotations +*:-android.*:+android.icu.*:-dalvik.* " +
"--hide BroadcastBehavior " +
"--hide CallbackInterface " +
diff --git a/OWNERS b/OWNERS
index 6c25324..4e5c7d8 100644
--- a/OWNERS
+++ b/OWNERS
@@ -28,7 +28,7 @@
# Support bulk translation updates
per-file */res*/values*/*.xml = byi@google.com, delphij@google.com
-per-file **.bp,**.mk = hansson@google.com, joeo@google.com
+per-file **.bp,**.mk = hansson@google.com, joeo@google.com, lamontjones@google.com
per-file TestProtoLibraries.bp = file:platform/platform_testing:/libraries/health/OWNERS
per-file TestProtoLibraries.bp = file:platform/tools/tradefederation:/OWNERS
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/Agent.java b/apex/jobscheduler/service/java/com/android/server/tare/Agent.java
index dcc324d..5c60562 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/Agent.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/Agent.java
@@ -75,7 +75,7 @@
private static final String ALARM_TAG_AFFORDABILITY_CHECK = "*tare.affordability_check*";
private final Object mLock;
- private final Handler mHandler;
+ private final AgentHandler mHandler;
private final Analyst mAnalyst;
private final InternalResourceService mIrs;
private final Scribe mScribe;
@@ -992,6 +992,7 @@
void tearDownLocked() {
mCurrentOngoingEvents.clear();
mBalanceThresholdAlarmQueue.removeAllAlarms();
+ mHandler.removeAllMessages();
}
@VisibleForTesting
@@ -1290,6 +1291,11 @@
break;
}
}
+
+ void removeAllMessages() {
+ removeMessages(MSG_CHECK_ALL_AFFORDABILITY);
+ removeMessages(MSG_CHECK_INDIVIDUAL_AFFORDABILITY);
+ }
}
@GuardedBy("mLock")
diff --git a/api/StubLibraries.bp b/api/StubLibraries.bp
index c59a833..2f84df7 100644
--- a/api/StubLibraries.bp
+++ b/api/StubLibraries.bp
@@ -33,7 +33,7 @@
"android-non-updatable-stubs-defaults",
"module-classpath-stubs-defaults",
],
- args: metalava_framework_docs_args,
+ args: metalava_framework_docs_args + "--error UnflaggedApi ",
check_api: {
current: {
api_file: ":non-updatable-current.txt",
diff --git a/core/api/current.txt b/core/api/current.txt
index 8de8ab8..d92c693 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -15096,7 +15096,7 @@
method public int getByteCount();
method @NonNull public android.graphics.Color getColor(int, int);
method @Nullable public android.graphics.ColorSpace getColorSpace();
- method @NonNull public android.graphics.Bitmap.Config getConfig();
+ method @Nullable public android.graphics.Bitmap.Config getConfig();
method public int getDensity();
method @Nullable public android.graphics.Gainmap getGainmap();
method public int getGenerationId();
@@ -38645,7 +38645,7 @@
public final class FileIntegrityManager {
method @FlaggedApi(Flags.FLAG_FSVERITY_API) @Nullable public byte[] getFsVerityDigest(@NonNull java.io.File) throws java.io.IOException;
method public boolean isApkVeritySupported();
- method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES, android.Manifest.permission.REQUEST_INSTALL_PACKAGES}) public boolean isAppSourceCertificateTrusted(@NonNull java.security.cert.X509Certificate) throws java.security.cert.CertificateEncodingException;
+ method @RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES, android.Manifest.permission.REQUEST_INSTALL_PACKAGES}) public boolean isAppSourceCertificateTrusted(@NonNull java.security.cert.X509Certificate) throws java.security.cert.CertificateEncodingException;
method @FlaggedApi(Flags.FLAG_FSVERITY_API) public void setupFsVerity(@NonNull java.io.File) throws java.io.IOException;
}
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index ff44a1b..eba1fbe 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -3192,6 +3192,7 @@
public static class VirtualDeviceManager.VirtualDevice implements java.lang.AutoCloseable {
method public void addActivityListener(@NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.VirtualDeviceManager.ActivityListener);
+ method @FlaggedApi(Flags.FLAG_DYNAMIC_POLICY) @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void addActivityPolicyExemption(@NonNull android.content.ComponentName);
method public void addSoundEffectListener(@NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.VirtualDeviceManager.SoundEffectListener);
method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void close();
method @NonNull public android.content.Context createContext();
@@ -3212,6 +3213,7 @@
method public void launchPendingIntent(int, @NonNull android.app.PendingIntent, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer);
method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void registerIntentInterceptor(@NonNull android.content.IntentFilter, @NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.VirtualDeviceManager.IntentInterceptorCallback);
method public void removeActivityListener(@NonNull android.companion.virtual.VirtualDeviceManager.ActivityListener);
+ method @FlaggedApi(Flags.FLAG_DYNAMIC_POLICY) @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void removeActivityPolicyExemption(@NonNull android.content.ComponentName);
method public void removeSoundEffectListener(@NonNull android.companion.virtual.VirtualDeviceManager.SoundEffectListener);
method @FlaggedApi(Flags.FLAG_DYNAMIC_POLICY) @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void setDevicePolicy(int, int);
method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void setShowPointerIcon(boolean);
@@ -3243,6 +3245,7 @@
field public static final int LOCK_STATE_DEFAULT = 0; // 0x0
field public static final int NAVIGATION_POLICY_DEFAULT_ALLOWED = 0; // 0x0
field public static final int NAVIGATION_POLICY_DEFAULT_BLOCKED = 1; // 0x1
+ field @FlaggedApi(Flags.FLAG_DYNAMIC_POLICY) public static final int POLICY_TYPE_ACTIVITY = 3; // 0x3
field public static final int POLICY_TYPE_AUDIO = 1; // 0x1
field public static final int POLICY_TYPE_RECENTS = 2; // 0x2
field public static final int POLICY_TYPE_SENSORS = 0; // 0x0
@@ -12654,7 +12657,7 @@
method @NonNull public android.service.voice.HotwordTrainingAudio build();
method @NonNull public android.service.voice.HotwordTrainingAudio.Builder setAudioFormat(@NonNull android.media.AudioFormat);
method @NonNull public android.service.voice.HotwordTrainingAudio.Builder setAudioType(@NonNull int);
- method @NonNull public android.service.voice.HotwordTrainingAudio.Builder setHotwordAudio(@NonNull byte...);
+ method @NonNull public android.service.voice.HotwordTrainingAudio.Builder setHotwordAudio(@NonNull byte[]);
method @NonNull public android.service.voice.HotwordTrainingAudio.Builder setHotwordOffsetMillis(int);
}
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 39589fa..f28b4b4 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -36,7 +36,6 @@
import static android.window.ConfigurationHelper.freeTextLayoutCachesIfNeeded;
import static android.window.ConfigurationHelper.isDifferentDisplay;
import static android.window.ConfigurationHelper.shouldUpdateResources;
-
import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
import static com.android.internal.os.SafeZipPathValidatorCallback.VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL;
@@ -1286,8 +1285,13 @@
}
private void updateCompatOverrideScale(CompatibilityInfo info) {
- CompatibilityInfo.setOverrideInvertedScale(
- info.hasOverrideScaling() ? info.applicationInvertedScale : 1f);
+ if (info.hasOverrideScaling()) {
+ CompatibilityInfo.setOverrideInvertedScale(info.applicationInvertedScale,
+ info.applicationDensityInvertedScale);
+ } else {
+ CompatibilityInfo.setOverrideInvertedScale(/* invertScale */ 1f,
+ /* densityInvertScale */1f);
+ }
}
public final void runIsolatedEntryPoint(String entryPoint, String[] entryPointArgs) {
diff --git a/core/java/android/companion/virtual/IVirtualDevice.aidl b/core/java/android/companion/virtual/IVirtualDevice.aidl
index c58561d..bf00a5a 100644
--- a/core/java/android/companion/virtual/IVirtualDevice.aidl
+++ b/core/java/android/companion/virtual/IVirtualDevice.aidl
@@ -23,6 +23,7 @@
import android.companion.virtual.sensor.VirtualSensor;
import android.companion.virtual.sensor.VirtualSensorConfig;
import android.companion.virtual.sensor.VirtualSensorEvent;
+import android.content.ComponentName;
import android.content.IntentFilter;
import android.graphics.Point;
import android.graphics.PointF;
@@ -86,6 +87,18 @@
void setDevicePolicy(int policyType, int devicePolicy);
/**
+ * Adds an exemption to the default activity launch policy.
+ */
+ @EnforcePermission("CREATE_VIRTUAL_DEVICE")
+ void addActivityPolicyExemption(in ComponentName exemption);
+
+ /**
+ * Removes an exemption to the default activity launch policy.
+ */
+ @EnforcePermission("CREATE_VIRTUAL_DEVICE")
+ void removeActivityPolicyExemption(in ComponentName exemption);
+
+ /**
* Notifies that an audio session being started.
*/
@EnforcePermission("CREATE_VIRTUAL_DEVICE")
diff --git a/core/java/android/companion/virtual/VirtualDeviceInternal.java b/core/java/android/companion/virtual/VirtualDeviceInternal.java
index 2e5c0f7..7bf2e91 100644
--- a/core/java/android/companion/virtual/VirtualDeviceInternal.java
+++ b/core/java/android/companion/virtual/VirtualDeviceInternal.java
@@ -247,6 +247,22 @@
}
}
+ void addActivityPolicyExemption(@NonNull ComponentName componentName) {
+ try {
+ mVirtualDevice.addActivityPolicyExemption(componentName);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ void removeActivityPolicyExemption(@NonNull ComponentName componentName) {
+ try {
+ mVirtualDevice.removeActivityPolicyExemption(componentName);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
@NonNull
VirtualDpad createVirtualDpad(@NonNull VirtualDpadConfig config) {
try {
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index 7b81031..d338d17 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -62,7 +62,6 @@
import android.view.Surface;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.util.AnnotationValidations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
@@ -624,19 +623,62 @@
* @param devicePolicy the value of the policy, i.e. how to interpret the device behavior.
*
* @see VirtualDeviceParams#POLICY_TYPE_RECENTS
+ * @see VirtualDeviceParams#POLICY_TYPE_ACTIVITY
*/
@FlaggedApi(Flags.FLAG_DYNAMIC_POLICY)
@RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public void setDevicePolicy(@VirtualDeviceParams.DynamicPolicyType int policyType,
@VirtualDeviceParams.DevicePolicy int devicePolicy) {
- AnnotationValidations.validate(
- VirtualDeviceParams.DynamicPolicyType.class, null, policyType);
- AnnotationValidations.validate(
- VirtualDeviceParams.DevicePolicy.class, null, devicePolicy);
mVirtualDeviceInternal.setDevicePolicy(policyType, devicePolicy);
}
/**
+ * Specifies a component name to be exempt from the current activity launch policy.
+ *
+ * <p>If the current {@link VirtualDeviceParams#POLICY_TYPE_ACTIVIY} allows activity
+ * launches by default, (i.e. it is {@link VirtualDeviceParams#DEVICE_POLICY_DEFAULT},
+ * then the specified component will be blocked from launching.
+ * If the current {@link VirtualDeviceParams#POLICY_TYPE_ACTIVITY} blocks activity
+ * launches by default, (i.e. it is {@link VirtualDeviceParams#DEVICE_POLICY_CUSTOM}, then
+ * the specified component will be allowed to launch.</p>
+ *
+ * <p>Note that changing the activity launch policy will not affect current set of exempt
+ * components and it needs to be updated separately.</p>
+ *
+ * @see #removeActivityPolicyExemption
+ * @see #setDevicePolicy
+ */
+ @FlaggedApi(Flags.FLAG_DYNAMIC_POLICY)
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ public void addActivityPolicyExemption(@NonNull ComponentName componentName) {
+ mVirtualDeviceInternal.addActivityPolicyExemption(
+ Objects.requireNonNull(componentName));
+ }
+
+ /**
+ * Makes the specified component name to adhere to the default activity launch policy.
+ *
+ * <p>If the current {@link VirtualDeviceParams#POLICY_TYPE_ACTIVIY} allows activity
+ * launches by default, (i.e. it is {@link VirtualDeviceParams#DEVICE_POLICY_DEFAULT},
+ * then the specified component will be allowed to launch.
+ * If the current {@link VirtualDeviceParams#POLICY_TYPE_ACTIVITY} blocks activity
+ * launches by default, (i.e. it is {@link VirtualDeviceParams#DEVICE_POLICY_CUSTOM}, then
+ * the specified component will be blocked from launching.</p>
+ *
+ * <p>Note that changing the activity launch policy will not affect current set of exempt
+ * components and it needs to be updated separately.</p>
+ *
+ * @see #addActivityPolicyExemption
+ * @see #setDevicePolicy
+ */
+ @FlaggedApi(Flags.FLAG_DYNAMIC_POLICY)
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ public void removeActivityPolicyExemption(@NonNull ComponentName componentName) {
+ mVirtualDeviceInternal.removeActivityPolicyExemption(
+ Objects.requireNonNull(componentName));
+ }
+
+ /**
* Creates a virtual dpad.
*
* @param config the configurations of the virtual dpad.
diff --git a/core/java/android/companion/virtual/VirtualDeviceParams.java b/core/java/android/companion/virtual/VirtualDeviceParams.java
index 51df257..b4c740ec 100644
--- a/core/java/android/companion/virtual/VirtualDeviceParams.java
+++ b/core/java/android/companion/virtual/VirtualDeviceParams.java
@@ -22,12 +22,14 @@
import static java.util.concurrent.TimeUnit.MICROSECONDS;
import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
+import android.companion.virtual.flags.Flags;
import android.companion.virtual.sensor.IVirtualSensorCallback;
import android.companion.virtual.sensor.VirtualSensor;
import android.companion.virtual.sensor.VirtualSensorCallback;
@@ -144,7 +146,7 @@
* @hide
*/
@IntDef(prefix = "POLICY_TYPE_", value = {POLICY_TYPE_SENSORS, POLICY_TYPE_AUDIO,
- POLICY_TYPE_RECENTS})
+ POLICY_TYPE_RECENTS, POLICY_TYPE_ACTIVITY})
@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
public @interface PolicyType {}
@@ -155,7 +157,7 @@
* @see VirtualDeviceManager.VirtualDevice#setDevicePolicy
* @hide
*/
- @IntDef(prefix = "POLICY_TYPE_", value = {POLICY_TYPE_RECENTS})
+ @IntDef(prefix = "POLICY_TYPE_", value = {POLICY_TYPE_RECENTS, POLICY_TYPE_ACTIVITY})
@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
public @interface DynamicPolicyType {}
@@ -195,19 +197,35 @@
* <li>{@link #DEVICE_POLICY_DEFAULT}: Activities launched on VirtualDisplays owned by this
* device will appear in the host device recents.
* <li>{@link #DEVICE_POLICY_CUSTOM}: Activities launched on VirtualDisplays owned by this
- * * device will not appear in recents.
+ * device will not appear in recents.
* </ul>
*/
public static final int POLICY_TYPE_RECENTS = 2;
+ /**
+ * Tells the activity manager what the default launch behavior for activities on this device is.
+ *
+ * <ul>
+ * <li>{@link #DEVICE_POLICY_DEFAULT}: Activities are allowed to be launched on displays
+ * owned by this device, unless explicitly blocked by the device.
+ * <li>{@link #DEVICE_POLICY_CUSTOM}: Activities are blocked from launching on displays
+ * owned by this device, unless explicitly allowed by the device.
+ * </ul>
+ *
+ * @see VirtualDeviceManager.VirtualDevice#addActivityPolicyExemption
+ * @see VirtualDeviceManager.VirtualDevice#removeActivityPolicyExemption
+ */
+ @FlaggedApi(Flags.FLAG_DYNAMIC_POLICY)
+ public static final int POLICY_TYPE_ACTIVITY = 3;
+
private final int mLockState;
@NonNull private final ArraySet<UserHandle> mUsersWithMatchingAccounts;
@NavigationPolicy
private final int mDefaultNavigationPolicy;
- @NonNull private final ArraySet<ComponentName> mCrossTaskNavigationExceptions;
+ @NonNull private final ArraySet<ComponentName> mCrossTaskNavigationExemptions;
@ActivityPolicy
private final int mDefaultActivityPolicy;
- @NonNull private final ArraySet<ComponentName> mActivityPolicyExceptions;
+ @NonNull private final ArraySet<ComponentName> mActivityPolicyExemptions;
@Nullable private final String mName;
// Mapping of @PolicyType to @DevicePolicy
@NonNull private final SparseIntArray mDevicePolicies;
@@ -220,9 +238,9 @@
@LockState int lockState,
@NonNull Set<UserHandle> usersWithMatchingAccounts,
@NavigationPolicy int defaultNavigationPolicy,
- @NonNull Set<ComponentName> crossTaskNavigationExceptions,
+ @NonNull Set<ComponentName> crossTaskNavigationExemptions,
@ActivityPolicy int defaultActivityPolicy,
- @NonNull Set<ComponentName> activityPolicyExceptions,
+ @NonNull Set<ComponentName> activityPolicyExemptions,
@Nullable String name,
@NonNull SparseIntArray devicePolicies,
@NonNull List<VirtualSensorConfig> virtualSensorConfigs,
@@ -233,11 +251,11 @@
mUsersWithMatchingAccounts =
new ArraySet<>(Objects.requireNonNull(usersWithMatchingAccounts));
mDefaultNavigationPolicy = defaultNavigationPolicy;
- mCrossTaskNavigationExceptions =
- new ArraySet<>(Objects.requireNonNull(crossTaskNavigationExceptions));
+ mCrossTaskNavigationExemptions =
+ new ArraySet<>(Objects.requireNonNull(crossTaskNavigationExemptions));
mDefaultActivityPolicy = defaultActivityPolicy;
- mActivityPolicyExceptions =
- new ArraySet<>(Objects.requireNonNull(activityPolicyExceptions));
+ mActivityPolicyExemptions =
+ new ArraySet<>(Objects.requireNonNull(activityPolicyExemptions));
mName = name;
mDevicePolicies = Objects.requireNonNull(devicePolicies);
mVirtualSensorConfigs = Objects.requireNonNull(virtualSensorConfigs);
@@ -251,9 +269,9 @@
mLockState = parcel.readInt();
mUsersWithMatchingAccounts = (ArraySet<UserHandle>) parcel.readArraySet(null);
mDefaultNavigationPolicy = parcel.readInt();
- mCrossTaskNavigationExceptions = (ArraySet<ComponentName>) parcel.readArraySet(null);
+ mCrossTaskNavigationExemptions = (ArraySet<ComponentName>) parcel.readArraySet(null);
mDefaultActivityPolicy = parcel.readInt();
- mActivityPolicyExceptions = (ArraySet<ComponentName>) parcel.readArraySet(null);
+ mActivityPolicyExemptions = (ArraySet<ComponentName>) parcel.readArraySet(null);
mName = parcel.readString8();
mDevicePolicies = parcel.readSparseIntArray();
mVirtualSensorConfigs = new ArrayList<>();
@@ -295,7 +313,7 @@
public Set<ComponentName> getAllowedCrossTaskNavigations() {
return mDefaultNavigationPolicy == NAVIGATION_POLICY_DEFAULT_ALLOWED
? Collections.emptySet()
- : Collections.unmodifiableSet(mCrossTaskNavigationExceptions);
+ : Collections.unmodifiableSet(mCrossTaskNavigationExemptions);
}
/**
@@ -310,7 +328,7 @@
public Set<ComponentName> getBlockedCrossTaskNavigations() {
return mDefaultNavigationPolicy == NAVIGATION_POLICY_DEFAULT_BLOCKED
? Collections.emptySet()
- : Collections.unmodifiableSet(mCrossTaskNavigationExceptions);
+ : Collections.unmodifiableSet(mCrossTaskNavigationExemptions);
}
/**
@@ -336,7 +354,7 @@
public Set<ComponentName> getAllowedActivities() {
return mDefaultActivityPolicy == ACTIVITY_POLICY_DEFAULT_ALLOWED
? Collections.emptySet()
- : Collections.unmodifiableSet(mActivityPolicyExceptions);
+ : Collections.unmodifiableSet(mActivityPolicyExemptions);
}
/**
@@ -349,7 +367,7 @@
public Set<ComponentName> getBlockedActivities() {
return mDefaultActivityPolicy == ACTIVITY_POLICY_DEFAULT_BLOCKED
? Collections.emptySet()
- : Collections.unmodifiableSet(mActivityPolicyExceptions);
+ : Collections.unmodifiableSet(mActivityPolicyExemptions);
}
/**
@@ -440,9 +458,9 @@
dest.writeInt(mLockState);
dest.writeArraySet(mUsersWithMatchingAccounts);
dest.writeInt(mDefaultNavigationPolicy);
- dest.writeArraySet(mCrossTaskNavigationExceptions);
+ dest.writeArraySet(mCrossTaskNavigationExemptions);
dest.writeInt(mDefaultActivityPolicy);
- dest.writeArraySet(mActivityPolicyExceptions);
+ dest.writeArraySet(mActivityPolicyExemptions);
dest.writeString8(mName);
dest.writeSparseIntArray(mDevicePolicies);
dest.writeTypedList(mVirtualSensorConfigs);
@@ -476,9 +494,9 @@
return mLockState == that.mLockState
&& mUsersWithMatchingAccounts.equals(that.mUsersWithMatchingAccounts)
&& Objects.equals(
- mCrossTaskNavigationExceptions, that.mCrossTaskNavigationExceptions)
+ mCrossTaskNavigationExemptions, that.mCrossTaskNavigationExemptions)
&& mDefaultNavigationPolicy == that.mDefaultNavigationPolicy
- && Objects.equals(mActivityPolicyExceptions, that.mActivityPolicyExceptions)
+ && Objects.equals(mActivityPolicyExemptions, that.mActivityPolicyExemptions)
&& mDefaultActivityPolicy == that.mDefaultActivityPolicy
&& Objects.equals(mName, that.mName)
&& mAudioPlaybackSessionId == that.mAudioPlaybackSessionId
@@ -488,8 +506,8 @@
@Override
public int hashCode() {
int hashCode = Objects.hash(
- mLockState, mUsersWithMatchingAccounts, mCrossTaskNavigationExceptions,
- mDefaultNavigationPolicy, mActivityPolicyExceptions, mDefaultActivityPolicy, mName,
+ mLockState, mUsersWithMatchingAccounts, mCrossTaskNavigationExemptions,
+ mDefaultNavigationPolicy, mActivityPolicyExemptions, mDefaultActivityPolicy, mName,
mDevicePolicies, mAudioPlaybackSessionId, mAudioRecordingSessionId);
for (int i = 0; i < mDevicePolicies.size(); i++) {
hashCode = 31 * hashCode + mDevicePolicies.keyAt(i);
@@ -505,9 +523,9 @@
+ " mLockState=" + mLockState
+ " mUsersWithMatchingAccounts=" + mUsersWithMatchingAccounts
+ " mDefaultNavigationPolicy=" + mDefaultNavigationPolicy
- + " mCrossTaskNavigationExceptions=" + mCrossTaskNavigationExceptions
+ + " mCrossTaskNavigationExemptions=" + mCrossTaskNavigationExemptions
+ " mDefaultActivityPolicy=" + mDefaultActivityPolicy
- + " mActivityPolicyExceptions=" + mActivityPolicyExceptions
+ + " mActivityPolicyExemptions=" + mActivityPolicyExemptions
+ " mName=" + mName
+ " mDevicePolicies=" + mDevicePolicies
+ " mAudioPlaybackSessionId=" + mAudioPlaybackSessionId
@@ -524,9 +542,9 @@
pw.println(prefix + "mLockState=" + mLockState);
pw.println(prefix + "mUsersWithMatchingAccounts=" + mUsersWithMatchingAccounts);
pw.println(prefix + "mDefaultNavigationPolicy=" + mDefaultNavigationPolicy);
- pw.println(prefix + "mCrossTaskNavigationExceptions=" + mCrossTaskNavigationExceptions);
+ pw.println(prefix + "mCrossTaskNavigationExemptions=" + mCrossTaskNavigationExemptions);
pw.println(prefix + "mDefaultActivityPolicy=" + mDefaultActivityPolicy);
- pw.println(prefix + "mActivityPolicyExceptions=" + mActivityPolicyExceptions);
+ pw.println(prefix + "mActivityPolicyExemptions=" + mActivityPolicyExemptions);
pw.println(prefix + "mDevicePolicies=" + mDevicePolicies);
pw.println(prefix + "mVirtualSensorConfigs=" + mVirtualSensorConfigs);
pw.println(prefix + "mAudioPlaybackSessionId=" + mAudioPlaybackSessionId);
@@ -552,11 +570,11 @@
private @LockState int mLockState = LOCK_STATE_DEFAULT;
@NonNull private Set<UserHandle> mUsersWithMatchingAccounts = Collections.emptySet();
- @NonNull private Set<ComponentName> mCrossTaskNavigationExceptions = Collections.emptySet();
+ @NonNull private Set<ComponentName> mCrossTaskNavigationExemptions = Collections.emptySet();
@NavigationPolicy
private int mDefaultNavigationPolicy = NAVIGATION_POLICY_DEFAULT_ALLOWED;
private boolean mDefaultNavigationPolicyConfigured = false;
- @NonNull private Set<ComponentName> mActivityPolicyExceptions = Collections.emptySet();
+ @NonNull private Set<ComponentName> mActivityPolicyExemptions = Collections.emptySet();
@ActivityPolicy
private int mDefaultActivityPolicy = ACTIVITY_POLICY_DEFAULT_ALLOWED;
private boolean mDefaultActivityPolicyConfigured = false;
@@ -700,7 +718,7 @@
}
mDefaultNavigationPolicy = NAVIGATION_POLICY_DEFAULT_BLOCKED;
mDefaultNavigationPolicyConfigured = true;
- mCrossTaskNavigationExceptions = Objects.requireNonNull(allowedCrossTaskNavigations);
+ mCrossTaskNavigationExemptions = Objects.requireNonNull(allowedCrossTaskNavigations);
return this;
}
@@ -731,7 +749,7 @@
}
mDefaultNavigationPolicy = NAVIGATION_POLICY_DEFAULT_ALLOWED;
mDefaultNavigationPolicyConfigured = true;
- mCrossTaskNavigationExceptions = Objects.requireNonNull(blockedCrossTaskNavigations);
+ mCrossTaskNavigationExemptions = Objects.requireNonNull(blockedCrossTaskNavigations);
return this;
}
@@ -757,7 +775,7 @@
}
mDefaultActivityPolicy = ACTIVITY_POLICY_DEFAULT_BLOCKED;
mDefaultActivityPolicyConfigured = true;
- mActivityPolicyExceptions = Objects.requireNonNull(allowedActivities);
+ mActivityPolicyExemptions = Objects.requireNonNull(allowedActivities);
return this;
}
@@ -783,7 +801,7 @@
}
mDefaultActivityPolicy = ACTIVITY_POLICY_DEFAULT_ALLOWED;
mDefaultActivityPolicyConfigured = true;
- mActivityPolicyExceptions = Objects.requireNonNull(blockedActivities);
+ mActivityPolicyExemptions = Objects.requireNonNull(blockedActivities);
return this;
}
@@ -956,6 +974,35 @@
mVirtualSensorDirectChannelCallback);
}
+ if (Flags.dynamicPolicy()) {
+ switch (mDevicePolicies.get(POLICY_TYPE_ACTIVITY, -1)) {
+ case DEVICE_POLICY_DEFAULT:
+ if (mDefaultActivityPolicyConfigured
+ && mDefaultActivityPolicy == ACTIVITY_POLICY_DEFAULT_BLOCKED) {
+ throw new IllegalArgumentException(
+ "DEVICE_POLICY_DEFAULT is explicitly configured for "
+ + "POLICY_TYPE_ACTIVITY, which is exclusive with "
+ + "setAllowedActivities.");
+ }
+ break;
+ case DEVICE_POLICY_CUSTOM:
+ if (mDefaultActivityPolicyConfigured
+ && mDefaultActivityPolicy == ACTIVITY_POLICY_DEFAULT_ALLOWED) {
+ throw new IllegalArgumentException(
+ "DEVICE_POLICY_CUSTOM is explicitly configured for "
+ + "POLICY_TYPE_ACTIVITY, which is exclusive with "
+ + "setBlockedActivities.");
+ }
+ break;
+ default:
+ if (mDefaultActivityPolicyConfigured
+ && mDefaultActivityPolicy == ACTIVITY_POLICY_DEFAULT_BLOCKED) {
+ mDevicePolicies.put(POLICY_TYPE_ACTIVITY, DEVICE_POLICY_CUSTOM);
+ }
+ break;
+ }
+ }
+
if ((mAudioPlaybackSessionId != AUDIO_SESSION_ID_GENERATE
|| mAudioRecordingSessionId != AUDIO_SESSION_ID_GENERATE)
&& mDevicePolicies.get(POLICY_TYPE_AUDIO, DEVICE_POLICY_DEFAULT)
@@ -964,7 +1011,7 @@
+ "required for configuration of device-specific audio session ids.");
}
- SparseArray<Set<String>> sensorNameByType = new SparseArray();
+ SparseArray<Set<String>> sensorNameByType = new SparseArray<>();
for (int i = 0; i < mVirtualSensorConfigs.size(); ++i) {
VirtualSensorConfig config = mVirtualSensorConfigs.get(i);
Set<String> sensorNames = sensorNameByType.get(config.getType(), new ArraySet<>());
@@ -979,9 +1026,9 @@
mLockState,
mUsersWithMatchingAccounts,
mDefaultNavigationPolicy,
- mCrossTaskNavigationExceptions,
+ mCrossTaskNavigationExemptions,
mDefaultActivityPolicy,
- mActivityPolicyExceptions,
+ mActivityPolicyExemptions,
mName,
mDevicePolicies,
mVirtualSensorConfigs,
diff --git a/core/java/android/companion/virtual/flags.aconfig b/core/java/android/companion/virtual/flags.aconfig
index 3a3ab24..ee36f18 100644
--- a/core/java/android/companion/virtual/flags.aconfig
+++ b/core/java/android/companion/virtual/flags.aconfig
@@ -27,4 +27,3 @@
description: "Enable Virtual Camera"
bug: "270352264"
}
-
diff --git a/core/java/android/content/res/CompatibilityInfo.java b/core/java/android/content/res/CompatibilityInfo.java
index 08ba5b6..f929c1f 100644
--- a/core/java/android/content/res/CompatibilityInfo.java
+++ b/core/java/android/content/res/CompatibilityInfo.java
@@ -100,7 +100,7 @@
* The effective screen density we have selected for this application.
*/
public final int applicationDensity;
-
+
/**
* Application's scale.
*/
@@ -112,9 +112,27 @@
*/
public final float applicationInvertedScale;
+ /**
+ * Application's density scale.
+ *
+ * <p>In most cases this is equal to {@link #applicationScale}, but in some cases e.g.
+ * Automotive the requirement is to just scale the density and keep the resolution the same.
+ * This is used for artificially making apps look zoomed in to compensate for the user distance
+ * from the screen.
+ */
+ public final float applicationDensityScale;
+
+ /**
+ * Application's density inverted scale.
+ */
+ public final float applicationDensityInvertedScale;
+
/** The process level override inverted scale. See {@link #HAS_OVERRIDE_SCALING}. */
private static float sOverrideInvertedScale = 1f;
+ /** The process level override inverted density scale. See {@link #HAS_OVERRIDE_SCALING}. */
+ private static float sOverrideDensityInvertScale = 1f;
+
@UnsupportedAppUsage
@Deprecated
public CompatibilityInfo(ApplicationInfo appInfo, int screenLayout, int sw,
@@ -123,17 +141,24 @@
}
public CompatibilityInfo(ApplicationInfo appInfo, int screenLayout, int sw,
- boolean forceCompat, float overrideScale) {
+ boolean forceCompat, float scaleFactor) {
+ this(appInfo, screenLayout, sw, forceCompat, scaleFactor, scaleFactor);
+ }
+
+ public CompatibilityInfo(ApplicationInfo appInfo, int screenLayout, int sw,
+ boolean forceCompat, float scaleFactor, float densityScaleFactor) {
int compatFlags = 0;
if (appInfo.targetSdkVersion < VERSION_CODES.O) {
compatFlags |= NEEDS_COMPAT_RES;
}
- if (overrideScale != 1.0f) {
- applicationScale = overrideScale;
- applicationInvertedScale = 1.0f / overrideScale;
+ if (scaleFactor != 1f || densityScaleFactor != 1f) {
+ applicationScale = scaleFactor;
+ applicationInvertedScale = 1f / scaleFactor;
+ applicationDensityScale = densityScaleFactor;
+ applicationDensityInvertedScale = 1f / densityScaleFactor;
applicationDensity = (int) ((DisplayMetrics.DENSITY_DEVICE_STABLE
- * applicationInvertedScale) + .5f);
+ * applicationDensityInvertedScale) + .5f);
mCompatibilityFlags = NEVER_NEEDS_COMPAT | HAS_OVERRIDE_SCALING;
// Override scale has the highest priority. So ignore other compatibility attributes.
return;
@@ -181,7 +206,8 @@
applicationDensity = DisplayMetrics.DENSITY_DEVICE;
applicationScale = 1.0f;
applicationInvertedScale = 1.0f;
-
+ applicationDensityScale = 1.0f;
+ applicationDensityInvertedScale = 1.0f;
} else {
/**
* Has the application said that its UI is expandable? Based on the
@@ -271,11 +297,16 @@
applicationDensity = DisplayMetrics.DENSITY_DEVICE;
applicationScale = 1.0f;
applicationInvertedScale = 1.0f;
+ applicationDensityScale = 1.0f;
+ applicationDensityInvertedScale = 1.0f;
} else {
applicationDensity = DisplayMetrics.DENSITY_DEFAULT;
applicationScale = DisplayMetrics.DENSITY_DEVICE
/ (float) DisplayMetrics.DENSITY_DEFAULT;
applicationInvertedScale = 1.0f / applicationScale;
+ applicationDensityScale = DisplayMetrics.DENSITY_DEVICE
+ / (float) DisplayMetrics.DENSITY_DEFAULT;
+ applicationDensityInvertedScale = 1f / applicationDensityScale;
compatFlags |= SCALING_REQUIRED;
}
}
@@ -289,6 +320,8 @@
applicationDensity = dens;
applicationScale = scale;
applicationInvertedScale = invertedScale;
+ applicationDensityScale = (float) DisplayMetrics.DENSITY_DEVICE_STABLE / dens;
+ applicationDensityInvertedScale = 1f / applicationDensityScale;
}
@UnsupportedAppUsage
@@ -528,7 +561,8 @@
/** Applies the compatibility adjustment to the display metrics. */
public void applyDisplayMetricsIfNeeded(DisplayMetrics inoutDm, boolean applyToSize) {
if (hasOverrideScale()) {
- scaleDisplayMetrics(sOverrideInvertedScale, inoutDm, applyToSize);
+ scaleDisplayMetrics(sOverrideInvertedScale, sOverrideDensityInvertScale, inoutDm,
+ applyToSize);
return;
}
if (!equals(DEFAULT_COMPATIBILITY_INFO)) {
@@ -548,15 +582,17 @@
}
if (isScalingRequired()) {
- scaleDisplayMetrics(applicationInvertedScale, inoutDm, true /* applyToSize */);
+ scaleDisplayMetrics(applicationInvertedScale, applicationDensityInvertedScale, inoutDm,
+ true /* applyToSize */);
}
}
/** Scales the density of the given display metrics. */
- private static void scaleDisplayMetrics(float invertedRatio, DisplayMetrics inoutDm,
- boolean applyToSize) {
- inoutDm.density = inoutDm.noncompatDensity * invertedRatio;
- inoutDm.densityDpi = (int) ((inoutDm.noncompatDensityDpi * invertedRatio) + .5f);
+ private static void scaleDisplayMetrics(float invertScale, float densityInvertScale,
+ DisplayMetrics inoutDm, boolean applyToSize) {
+ inoutDm.density = inoutDm.noncompatDensity * densityInvertScale;
+ inoutDm.densityDpi = (int) ((inoutDm.noncompatDensityDpi
+ * densityInvertScale) + .5f);
// Note: since this is changing the scaledDensity, you might think we also need to change
// inoutDm.fontScaleConverter to accurately calculate non-linear font scaling. But we're not
// going to do that, for a couple of reasons (see b/265695259 for details):
@@ -570,12 +606,12 @@
// b. Sometime later by WindowManager in onResume or other windowing events. In this case
// the DisplayMetrics object is never used by the app/resources, so it's ok if
// fontScaleConverter is null because it's not being used to scale fonts anyway.
- inoutDm.scaledDensity = inoutDm.noncompatScaledDensity * invertedRatio;
- inoutDm.xdpi = inoutDm.noncompatXdpi * invertedRatio;
- inoutDm.ydpi = inoutDm.noncompatYdpi * invertedRatio;
+ inoutDm.scaledDensity = inoutDm.noncompatScaledDensity * densityInvertScale;
+ inoutDm.xdpi = inoutDm.noncompatXdpi * densityInvertScale;
+ inoutDm.ydpi = inoutDm.noncompatYdpi * densityInvertScale;
if (applyToSize) {
- inoutDm.widthPixels = (int) (inoutDm.widthPixels * invertedRatio + 0.5f);
- inoutDm.heightPixels = (int) (inoutDm.heightPixels * invertedRatio + 0.5f);
+ inoutDm.widthPixels = (int) (inoutDm.widthPixels * invertScale + 0.5f);
+ inoutDm.heightPixels = (int) (inoutDm.heightPixels * invertScale + 0.5f);
}
}
@@ -594,38 +630,55 @@
}
inoutConfig.densityDpi = displayDensity;
if (isScalingRequired()) {
- scaleConfiguration(applicationInvertedScale, inoutConfig);
+ scaleConfiguration(applicationInvertedScale, applicationDensityInvertedScale,
+ inoutConfig);
}
}
/** Scales the density and bounds of the given configuration. */
- public static void scaleConfiguration(float invertedRatio, Configuration inoutConfig) {
- inoutConfig.densityDpi = (int) ((inoutConfig.densityDpi * invertedRatio) + .5f);
- inoutConfig.windowConfiguration.scale(invertedRatio);
+ public static void scaleConfiguration(float invertScale, Configuration inoutConfig) {
+ scaleConfiguration(invertScale, invertScale, inoutConfig);
+ }
+
+ /** Scales the density and bounds of the given configuration. */
+ public static void scaleConfiguration(float invertScale, float densityInvertScale,
+ Configuration inoutConfig) {
+ inoutConfig.densityDpi = (int) ((inoutConfig.densityDpi
+ * densityInvertScale) + .5f);
+ inoutConfig.windowConfiguration.scale(invertScale);
}
/** @see #sOverrideInvertedScale */
public static void applyOverrideScaleIfNeeded(Configuration config) {
if (!hasOverrideScale()) return;
- scaleConfiguration(sOverrideInvertedScale, config);
+ scaleConfiguration(sOverrideInvertedScale, sOverrideDensityInvertScale, config);
}
/** @see #sOverrideInvertedScale */
public static void applyOverrideScaleIfNeeded(MergedConfiguration mergedConfig) {
if (!hasOverrideScale()) return;
- scaleConfiguration(sOverrideInvertedScale, mergedConfig.getGlobalConfiguration());
- scaleConfiguration(sOverrideInvertedScale, mergedConfig.getOverrideConfiguration());
- scaleConfiguration(sOverrideInvertedScale, mergedConfig.getMergedConfiguration());
+ scaleConfiguration(sOverrideInvertedScale, sOverrideDensityInvertScale,
+ mergedConfig.getGlobalConfiguration());
+ scaleConfiguration(sOverrideInvertedScale, sOverrideDensityInvertScale,
+ mergedConfig.getOverrideConfiguration());
+ scaleConfiguration(sOverrideInvertedScale, sOverrideDensityInvertScale,
+ mergedConfig.getMergedConfiguration());
}
/** Returns {@code true} if this process is in a environment with override scale. */
private static boolean hasOverrideScale() {
- return sOverrideInvertedScale != 1f;
+ return sOverrideInvertedScale != 1f || sOverrideDensityInvertScale != 1f;
}
/** @see #sOverrideInvertedScale */
- public static void setOverrideInvertedScale(float invertedRatio) {
- sOverrideInvertedScale = invertedRatio;
+ public static void setOverrideInvertedScale(float invertScale) {
+ setOverrideInvertedScale(invertScale, invertScale);
+ }
+
+ /** @see #sOverrideInvertedScale */
+ public static void setOverrideInvertedScale(float invertScale, float densityInvertScale) {
+ sOverrideInvertedScale = invertScale;
+ sOverrideDensityInvertScale = densityInvertScale;
}
/** @see #sOverrideInvertedScale */
@@ -633,6 +686,11 @@
return sOverrideInvertedScale;
}
+ /** @see #sOverrideDensityInvertScale */
+ public static float getOverrideDensityInvertedScale() {
+ return sOverrideDensityInvertScale;
+ }
+
/**
* Compute the frame Rect for applications runs under compatibility mode.
*
@@ -693,6 +751,8 @@
if (applicationDensity != oc.applicationDensity) return false;
if (applicationScale != oc.applicationScale) return false;
if (applicationInvertedScale != oc.applicationInvertedScale) return false;
+ if (applicationDensityScale != oc.applicationDensityScale) return false;
+ if (applicationDensityInvertedScale != oc.applicationDensityInvertedScale) return false;
return true;
} catch (ClassCastException e) {
return false;
@@ -713,6 +773,8 @@
if (hasOverrideScaling()) {
sb.append(" overrideInvScale=");
sb.append(applicationInvertedScale);
+ sb.append(" overrideDensityInvScale=");
+ sb.append(applicationDensityInvertedScale);
}
if (!supportsScreen()) {
sb.append(" resizing");
@@ -734,6 +796,8 @@
result = 31 * result + applicationDensity;
result = 31 * result + Float.floatToIntBits(applicationScale);
result = 31 * result + Float.floatToIntBits(applicationInvertedScale);
+ result = 31 * result + Float.floatToIntBits(applicationDensityScale);
+ result = 31 * result + Float.floatToIntBits(applicationDensityInvertedScale);
return result;
}
@@ -748,6 +812,8 @@
dest.writeInt(applicationDensity);
dest.writeFloat(applicationScale);
dest.writeFloat(applicationInvertedScale);
+ dest.writeFloat(applicationDensityScale);
+ dest.writeFloat(applicationDensityInvertedScale);
}
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
@@ -769,5 +835,61 @@
applicationDensity = source.readInt();
applicationScale = source.readFloat();
applicationInvertedScale = source.readFloat();
+ applicationDensityScale = source.readFloat();
+ applicationDensityInvertedScale = source.readFloat();
+ }
+
+ /**
+ * A data class for holding scale factor for width, height, and density.
+ */
+ public static final class CompatScale {
+
+ public final float mScaleFactor;
+ public final float mDensityScaleFactor;
+
+ public CompatScale(float scaleFactor) {
+ this(scaleFactor, scaleFactor);
+ }
+
+ public CompatScale(float scaleFactor, float densityScaleFactor) {
+ mScaleFactor = scaleFactor;
+ mDensityScaleFactor = densityScaleFactor;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof CompatScale)) {
+ return false;
+ }
+ try {
+ CompatScale oc = (CompatScale) o;
+ if (mScaleFactor != oc.mScaleFactor) return false;
+ if (mDensityScaleFactor != oc.mDensityScaleFactor) return false;
+ return true;
+ } catch (ClassCastException e) {
+ return false;
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder(128);
+ sb.append("mScaleFactor= ");
+ sb.append(mScaleFactor);
+ sb.append(" mDensityScaleFactor= ");
+ sb.append(mDensityScaleFactor);
+ return sb.toString();
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result = 31 * result + Float.floatToIntBits(mScaleFactor);
+ result = 31 * result + Float.floatToIntBits(mDensityScaleFactor);
+ return result;
+ }
}
}
diff --git a/core/java/android/hardware/radio/ProgramList.java b/core/java/android/hardware/radio/ProgramList.java
index b2dfd85..4f07acf 100644
--- a/core/java/android/hardware/radio/ProgramList.java
+++ b/core/java/android/hardware/radio/ProgramList.java
@@ -23,6 +23,7 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.util.ArrayMap;
+import android.util.ArraySet;
import com.android.internal.annotations.GuardedBy;
@@ -34,7 +35,6 @@
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.Executor;
-import java.util.stream.Collectors;
/**
* @hide
@@ -45,8 +45,8 @@
private final Object mLock = new Object();
@GuardedBy("mLock")
- private final Map<ProgramSelector.Identifier, RadioManager.ProgramInfo> mPrograms =
- new ArrayMap<>();
+ private final Map<ProgramSelector.Identifier, Map<UniqueProgramIdentifier,
+ RadioManager.ProgramInfo>> mPrograms = new ArrayMap<>();
@GuardedBy("mLock")
private final List<ListCallback> mListCallbacks = new ArrayList<>();
@@ -193,7 +193,7 @@
void apply(Chunk chunk) {
List<ProgramSelector.Identifier> removedList = new ArrayList<>();
- List<ProgramSelector.Identifier> changedList = new ArrayList<>();
+ Set<ProgramSelector.Identifier> changedSet = new ArraySet<>();
List<ProgramList.ListCallback> listCallbacksCopied;
List<OnCompleteListener> onCompleteListenersCopied = new ArrayList<>();
synchronized (mLock) {
@@ -203,19 +203,27 @@
listCallbacksCopied = new ArrayList<>(mListCallbacks);
if (chunk.isPurge()) {
- Iterator<Map.Entry<ProgramSelector.Identifier, RadioManager.ProgramInfo>>
- programsIterator = mPrograms.entrySet().iterator();
+ Iterator<Map.Entry<ProgramSelector.Identifier, Map<UniqueProgramIdentifier,
+ RadioManager.ProgramInfo>>> programsIterator =
+ mPrograms.entrySet().iterator();
while (programsIterator.hasNext()) {
- RadioManager.ProgramInfo removed = programsIterator.next().getValue();
- if (removed != null) {
- removedList.add(removed.getSelector().getPrimaryId());
+ Map.Entry<ProgramSelector.Identifier, Map<UniqueProgramIdentifier,
+ RadioManager.ProgramInfo>> removed = programsIterator.next();
+ if (removed.getValue() != null) {
+ removedList.add(removed.getKey());
}
programsIterator.remove();
}
}
- chunk.getRemoved().stream().forEach(id -> removeLocked(id, removedList));
- chunk.getModified().stream().forEach(info -> putLocked(info, changedList));
+ Iterator<UniqueProgramIdentifier> removedIterator = chunk.getRemoved().iterator();
+ while (removedIterator.hasNext()) {
+ removeLocked(removedIterator.next(), removedList);
+ }
+ Iterator<RadioManager.ProgramInfo> modifiedIterator = chunk.getModified().iterator();
+ while (modifiedIterator.hasNext()) {
+ putLocked(modifiedIterator.next(), changedSet);
+ }
if (chunk.isComplete()) {
mIsComplete = true;
@@ -228,9 +236,11 @@
listCallbacksCopied.get(cbIndex).onItemRemoved(removedList.get(i));
}
}
- for (int i = 0; i < changedList.size(); i++) {
+ Iterator<ProgramSelector.Identifier> changedIterator = changedSet.iterator();
+ while (changedIterator.hasNext()) {
+ ProgramSelector.Identifier changedId = changedIterator.next();
for (int cbIndex = 0; cbIndex < listCallbacksCopied.size(); cbIndex++) {
- listCallbacksCopied.get(cbIndex).onItemChanged(changedList.get(i));
+ listCallbacksCopied.get(cbIndex).onItemChanged(changedId);
}
}
if (chunk.isComplete()) {
@@ -242,20 +252,31 @@
@GuardedBy("mLock")
private void putLocked(RadioManager.ProgramInfo value,
- List<ProgramSelector.Identifier> changedIdentifierList) {
- ProgramSelector.Identifier key = value.getSelector().getPrimaryId();
- mPrograms.put(Objects.requireNonNull(key), value);
- ProgramSelector.Identifier sel = value.getSelector().getPrimaryId();
- changedIdentifierList.add(sel);
+ Set<ProgramSelector.Identifier> changedIdentifierSet) {
+ UniqueProgramIdentifier key = new UniqueProgramIdentifier(
+ value.getSelector());
+ ProgramSelector.Identifier primaryKey = Objects.requireNonNull(key.getPrimaryId());
+ if (!mPrograms.containsKey(primaryKey)) {
+ mPrograms.put(primaryKey, new ArrayMap<>());
+ }
+ mPrograms.get(primaryKey).put(key, value);
+ changedIdentifierSet.add(primaryKey);
}
@GuardedBy("mLock")
- private void removeLocked(ProgramSelector.Identifier key,
+ private void removeLocked(UniqueProgramIdentifier key,
List<ProgramSelector.Identifier> removedIdentifierList) {
- RadioManager.ProgramInfo removed = mPrograms.remove(Objects.requireNonNull(key));
+ ProgramSelector.Identifier primaryKey = Objects.requireNonNull(key.getPrimaryId());
+ if (!mPrograms.containsKey(primaryKey)) {
+ return;
+ }
+ Map<UniqueProgramIdentifier, RadioManager.ProgramInfo> entries = mPrograms
+ .get(primaryKey);
+ RadioManager.ProgramInfo removed = entries.remove(Objects.requireNonNull(key));
if (removed == null) return;
- ProgramSelector.Identifier sel = removed.getSelector().getPrimaryId();
- removedIdentifierList.add(sel);
+ if (entries.size() == 0) {
+ removedIdentifierList.add(primaryKey);
+ }
}
/**
@@ -264,9 +285,20 @@
* @return the new List<> object; it won't receive any further updates
*/
public @NonNull List<RadioManager.ProgramInfo> toList() {
+ List<RadioManager.ProgramInfo> list = new ArrayList<>();
synchronized (mLock) {
- return mPrograms.values().stream().collect(Collectors.toList());
+ Iterator<Map.Entry<ProgramSelector.Identifier, Map<UniqueProgramIdentifier,
+ RadioManager.ProgramInfo>>> listIterator = mPrograms.entrySet().iterator();
+ while (listIterator.hasNext()) {
+ Iterator<Map.Entry<UniqueProgramIdentifier,
+ RadioManager.ProgramInfo>> prorgramsIterator = listIterator.next()
+ .getValue().entrySet().iterator();
+ while (prorgramsIterator.hasNext()) {
+ list.add(prorgramsIterator.next().getValue());
+ }
+ }
}
+ return list;
}
/**
@@ -276,9 +308,15 @@
* @return the program info, or null if there is no such program on the list
*/
public @Nullable RadioManager.ProgramInfo get(@NonNull ProgramSelector.Identifier id) {
+ Map<UniqueProgramIdentifier, RadioManager.ProgramInfo> entries;
synchronized (mLock) {
- return mPrograms.get(Objects.requireNonNull(id));
+ entries = mPrograms.get(Objects.requireNonNull(id,
+ "Primary identifier can not be null"));
}
+ if (entries == null) {
+ return null;
+ }
+ return entries.entrySet().iterator().next().getValue();
}
/**
@@ -404,7 +442,7 @@
* Checks, if non-tunable entries that define tree structure on the
* program list (i.e. DAB ensembles) should be included.
*
- * @see {@link ProgramSelector.Identifier#isCategory()}
+ * @see ProgramSelector.Identifier#isCategoryType()
*/
public boolean areCategoriesIncluded() {
return mIncludeCategories;
@@ -459,11 +497,11 @@
private final boolean mPurge;
private final boolean mComplete;
private final @NonNull Set<RadioManager.ProgramInfo> mModified;
- private final @NonNull Set<ProgramSelector.Identifier> mRemoved;
+ private final @NonNull Set<UniqueProgramIdentifier> mRemoved;
public Chunk(boolean purge, boolean complete,
@Nullable Set<RadioManager.ProgramInfo> modified,
- @Nullable Set<ProgramSelector.Identifier> removed) {
+ @Nullable Set<UniqueProgramIdentifier> removed) {
mPurge = purge;
mComplete = complete;
mModified = (modified != null) ? modified : Collections.emptySet();
@@ -474,7 +512,7 @@
mPurge = in.readByte() != 0;
mComplete = in.readByte() != 0;
mModified = Utils.createSet(in, RadioManager.ProgramInfo.CREATOR);
- mRemoved = Utils.createSet(in, ProgramSelector.Identifier.CREATOR);
+ mRemoved = Utils.createSet(in, UniqueProgramIdentifier.CREATOR);
}
@Override
@@ -512,7 +550,7 @@
return mModified;
}
- public @NonNull Set<ProgramSelector.Identifier> getRemoved() {
+ public @NonNull Set<UniqueProgramIdentifier> getRemoved() {
return mRemoved;
}
diff --git a/core/java/android/security/FileIntegrityManager.java b/core/java/android/security/FileIntegrityManager.java
index d6f3bf3..132700d 100644
--- a/core/java/android/security/FileIntegrityManager.java
+++ b/core/java/android/security/FileIntegrityManager.java
@@ -133,13 +133,11 @@
* also use this API to download the best signature on the running device.
*
* @return whether the certificate is trusted in the system
- * @deprecated The feature is no longer supported, and this API now always returns false.
*/
@RequiresPermission(anyOf = {
android.Manifest.permission.INSTALL_PACKAGES,
android.Manifest.permission.REQUEST_INSTALL_PACKAGES
})
- @Deprecated
public boolean isAppSourceCertificateTrusted(@NonNull X509Certificate certificate)
throws CertificateEncodingException {
try {
diff --git a/core/java/android/security/flags.aconfig b/core/java/android/security/flags.aconfig
index b27dac2..b6c2b83 100644
--- a/core/java/android/security/flags.aconfig
+++ b/core/java/android/security/flags.aconfig
@@ -6,3 +6,10 @@
description: "Feature flag for fs-verity API"
bug: "285185747"
}
+
+flag {
+ name: "fix_unlocked_device_required_keys"
+ namespace: "hardware_backed_security"
+ description: "Fix bugs in behavior of UnlockedDeviceRequired keystore keys"
+ bug: "296464083"
+}
diff --git a/core/java/android/service/voice/HotwordTrainingAudio.java b/core/java/android/service/voice/HotwordTrainingAudio.java
index 895b0c0..91e34dc 100644
--- a/core/java/android/service/voice/HotwordTrainingAudio.java
+++ b/core/java/android/service/voice/HotwordTrainingAudio.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.media.AudioFormat;
import android.os.Parcel;
@@ -25,6 +26,8 @@
import com.android.internal.util.DataClass;
+import java.util.Objects;
+
/**
* Represents audio supporting hotword model training.
*
@@ -43,7 +46,10 @@
/** Represents unset value for the hotword offset. */
public static final int HOTWORD_OFFSET_UNSET = -1;
- /** Buffer of hotword audio data for training models. */
+ /**
+ * Buffer of hotword audio data for training models. The data format is expected to match
+ * {@link #getAudioFormat()}.
+ */
@NonNull
private final byte[] mHotwordAudio;
@@ -74,6 +80,24 @@
*/
private int mHotwordOffsetMillis = HOTWORD_OFFSET_UNSET;
+ @DataClass.Suppress("setHotwordAudio")
+ abstract static class BaseBuilder {
+
+ /**
+ * Buffer of hotword audio data for training models. The data format is expected to match
+ * {@link #getAudioFormat()}.
+ */
+ @SuppressLint("UnflaggedApi")
+ public @NonNull HotwordTrainingAudio.Builder setHotwordAudio(@NonNull byte[] value) {
+ Objects.requireNonNull(value, "value should not be null");
+ final HotwordTrainingAudio.Builder builder = (HotwordTrainingAudio.Builder) this;
+ // If the code gen flag in build() is changed, we must update the flag e.g. 0x1 here.
+ builder.mBuilderFieldsSet |= 0x1;
+ builder.mHotwordAudio = value;
+ return builder;
+ }
+ }
+
// Code below generated by codegen v1.0.23.
@@ -110,7 +134,8 @@
}
/**
- * Buffer of hotword audio data for training models.
+ * Buffer of hotword audio data for training models. The data format is expected to match
+ * {@link #getAudioFormat()}.
*/
@DataClass.Generated.Member
public @NonNull byte[] getHotwordAudio() {
@@ -171,7 +196,7 @@
//noinspection PointlessBooleanExpression
return true
&& java.util.Arrays.equals(mHotwordAudio, that.mHotwordAudio)
- && java.util.Objects.equals(mAudioFormat, that.mAudioFormat)
+ && Objects.equals(mAudioFormat, that.mAudioFormat)
&& mAudioType == that.mAudioType
&& mHotwordOffsetMillis == that.mHotwordOffsetMillis;
}
@@ -184,7 +209,7 @@
int _hash = 1;
_hash = 31 * _hash + java.util.Arrays.hashCode(mHotwordAudio);
- _hash = 31 * _hash + java.util.Objects.hashCode(mAudioFormat);
+ _hash = 31 * _hash + Objects.hashCode(mAudioFormat);
_hash = 31 * _hash + mAudioType;
_hash = 31 * _hash + mHotwordOffsetMillis;
return _hash;
@@ -251,7 +276,7 @@
*/
@SuppressWarnings("WeakerAccess")
@DataClass.Generated.Member
- public static final class Builder {
+ public static final class Builder extends BaseBuilder {
private @NonNull byte[] mHotwordAudio;
private @NonNull AudioFormat mAudioFormat;
@@ -264,7 +289,8 @@
* Creates a new Builder.
*
* @param hotwordAudio
- * Buffer of hotword audio data for training models.
+ * Buffer of hotword audio data for training models. The data format is expected to match
+ * {@link #getAudioFormat()}.
* @param audioFormat
* The {@link AudioFormat} of the {@link HotwordTrainingAudio#mHotwordAudio}.
*/
@@ -280,17 +306,6 @@
}
/**
- * Buffer of hotword audio data for training models.
- */
- @DataClass.Generated.Member
- public @NonNull Builder setHotwordAudio(@NonNull byte... value) {
- checkNotUsed();
- mBuilderFieldsSet |= 0x1;
- mHotwordAudio = value;
- return this;
- }
-
- /**
* The {@link AudioFormat} of the {@link HotwordTrainingAudio#mHotwordAudio}.
*/
@DataClass.Generated.Member
@@ -353,10 +368,10 @@
}
@DataClass.Generated(
- time = 1692837160437L,
+ time = 1694193905346L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/service/voice/HotwordTrainingAudio.java",
- inputSignatures = "public static final int HOTWORD_OFFSET_UNSET\nprivate final @android.annotation.NonNull byte[] mHotwordAudio\nprivate final @android.annotation.NonNull android.media.AudioFormat mAudioFormat\nprivate final @android.annotation.NonNull int mAudioType\nprivate int mHotwordOffsetMillis\nprivate java.lang.String hotwordAudioToString()\nprivate static int defaultAudioType()\nclass HotwordTrainingAudio extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genHiddenConstDefs=true, genParcelable=true, genToString=true)")
+ inputSignatures = "public static final int HOTWORD_OFFSET_UNSET\nprivate final @android.annotation.NonNull byte[] mHotwordAudio\nprivate final @android.annotation.NonNull android.media.AudioFormat mAudioFormat\nprivate final @android.annotation.NonNull int mAudioType\nprivate int mHotwordOffsetMillis\nprivate java.lang.String hotwordAudioToString()\nprivate static int defaultAudioType()\nclass HotwordTrainingAudio extends java.lang.Object implements [android.os.Parcelable]\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.service.voice.HotwordTrainingAudio.Builder setHotwordAudio(byte[])\nclass BaseBuilder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genHiddenConstDefs=true, genParcelable=true, genToString=true)\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.service.voice.HotwordTrainingAudio.Builder setHotwordAudio(byte[])\nclass BaseBuilder extends java.lang.Object implements []")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index b8385c6..e64274e 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -3139,15 +3139,6 @@
public static final int PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS = 1 << 10;
/**
- * Flag to force the status bar window to be visible all the time. If the bar is hidden when
- * this flag is set it will be shown again.
- * This can only be set by {@link LayoutParams#TYPE_STATUS_BAR}.
- *
- * {@hide}
- */
- public static final int PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR = 1 << 11;
-
- /**
* Flag to indicate that the window frame should be the requested frame adding the display
* cutout frame. This will only be applied if a specific size smaller than the parent frame
* is given, and the window is covering the display cutout. The extended frame will not be
@@ -3238,15 +3229,6 @@
public static final int PRIVATE_FLAG_NOT_MAGNIFIABLE = 1 << 22;
/**
- * Flag to indicate that the status bar window is in a state such that it forces showing
- * the navigation bar unless the navigation bar window is explicitly set to
- * {@link View#GONE}.
- * It only takes effects if this is set by {@link LayoutParams#TYPE_STATUS_BAR}.
- * @hide
- */
- public static final int PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION = 1 << 23;
-
- /**
* Flag to indicate that the window is color space agnostic, and the color can be
* interpreted to any color space.
* @hide
@@ -3334,7 +3316,6 @@
PRIVATE_FLAG_SYSTEM_ERROR,
PRIVATE_FLAG_OPTIMIZE_MEASURE,
PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS,
- PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR,
PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT,
PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY,
PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME,
@@ -3345,7 +3326,6 @@
PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY,
PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION,
PRIVATE_FLAG_NOT_MAGNIFIABLE,
- PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION,
PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC,
PRIVATE_FLAG_USE_BLAST,
PRIVATE_FLAG_APPEARANCE_CONTROLLED,
@@ -3401,10 +3381,6 @@
equals = PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS,
name = "DISABLE_WALLPAPER_TOUCH_EVENTS"),
@ViewDebug.FlagToString(
- mask = PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR,
- equals = PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR,
- name = "FORCE_STATUS_BAR_VISIBLE"),
- @ViewDebug.FlagToString(
mask = PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT,
equals = PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT,
name = "LAYOUT_SIZE_EXTENDED_BY_CUTOUT"),
@@ -3445,10 +3421,6 @@
equals = PRIVATE_FLAG_NOT_MAGNIFIABLE,
name = "NOT_MAGNIFIABLE"),
@ViewDebug.FlagToString(
- mask = PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION,
- equals = PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION,
- name = "STATUS_FORCE_SHOW_NAVIGATION"),
- @ViewDebug.FlagToString(
mask = PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC,
equals = PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC,
name = "COLOR_SPACE_AGNOSTIC"),
@@ -4412,6 +4384,16 @@
public InsetsFrameProvider[] providedInsets;
/**
+ * Specifies which {@link InsetsType}s should be forcibly shown. The types shown by this
+ * method won't affect the app's layout. This field only takes effects if the caller has
+ * {@link android.Manifest.permission#STATUS_BAR_SERVICE} or the caller has the same uid as
+ * the recents component.
+ *
+ * @hide
+ */
+ public @InsetsType int forciblyShownTypes;
+
+ /**
* {@link LayoutParams} to be applied to the window when layout with a assigned rotation.
* This will make layout during rotation change smoothly.
*
@@ -4869,6 +4851,7 @@
out.writeInt(mBlurBehindRadius);
out.writeBoolean(mWallpaperTouchEventsEnabled);
out.writeTypedArray(providedInsets, 0 /* parcelableFlags */);
+ out.writeInt(forciblyShownTypes);
checkNonRecursiveParams();
out.writeTypedArray(paramsForRotation, 0 /* parcelableFlags */);
out.writeInt(mDisplayFlags);
@@ -4940,6 +4923,7 @@
mBlurBehindRadius = in.readInt();
mWallpaperTouchEventsEnabled = in.readBoolean();
providedInsets = in.createTypedArray(InsetsFrameProvider.CREATOR);
+ forciblyShownTypes = in.readInt();
paramsForRotation = in.createTypedArray(LayoutParams.CREATOR);
mDisplayFlags = in.readInt();
}
@@ -5245,6 +5229,11 @@
changes |= LAYOUT_CHANGED;
}
+ if (forciblyShownTypes != o.forciblyShownTypes) {
+ forciblyShownTypes = o.forciblyShownTypes;
+ changes |= PRIVATE_FLAGS_CHANGED;
+ }
+
if (paramsForRotation != o.paramsForRotation) {
if ((changes & LAYOUT_CHANGED) == 0) {
if (paramsForRotation != null && o.paramsForRotation != null
@@ -5482,6 +5471,11 @@
sb.append(prefix).append(" ").append(providedInsets[i]);
}
}
+ if (forciblyShownTypes != 0) {
+ sb.append(System.lineSeparator());
+ sb.append(prefix).append(" forciblyShownTypes=").append(
+ WindowInsets.Type.toString(forciblyShownTypes));
+ }
if (paramsForRotation != null && paramsForRotation.length != 0) {
sb.append(System.lineSeparator());
sb.append(prefix).append(" paramsForRotation:");
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 32fe4e3..3180ffb 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1466,10 +1466,9 @@
<!-- Allows an application to initiate a phone call without going through
the Dialer user interface for the user to confirm the call.
- <p>
- <em>Note: An app holding this permission can also call carrier MMI codes to change settings
- such as call forwarding or call waiting preferences.
- <p>Protection level: dangerous
+ <p class="note"><b>Note:</b> An app holding this permission can also call carrier MMI
+ codes to change settings such as call forwarding or call waiting preferences.</p>
+ <p>Protection level: dangerous</p>
-->
<permission android:name="android.permission.CALL_PHONE"
android:permissionGroup="android.permission-group.UNDEFINED"
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 7d2690e..e7764d8 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -5717,13 +5717,13 @@
<!-- Blur radius for the Option 3 in R.integer.config_letterboxBackgroundType. Values < 0 are
ignored and 0 is used. -->
- <dimen name="config_letterboxBackgroundWallpaperBlurRadius">24dp</dimen>
+ <dimen name="config_letterboxBackgroundWallpaperBlurRadius">38dp</dimen>
<!-- Alpha of a black translucent scrim showed over wallpaper letterbox background when
the Option 3 is selected for R.integer.config_letterboxBackgroundType.
Values < 0 or >= 1 are ignored and 0.0 (transparent) is used instead. -->
<item name="config_letterboxBackgroundWallaperDarkScrimAlpha" format="float" type="dimen">
- 0.75
+ 0.54
</item>
<!-- Corners appearance of the letterbox background.
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/ProgramListTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/ProgramListTest.java
index 7c3d2f2..d638fed 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/ProgramListTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/ProgramListTest.java
@@ -74,18 +74,45 @@
private static final ProgramSelector.Identifier DAB_ENSEMBLE_IDENTIFIER =
new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_ENSEMBLE,
/* value= */ 0x1013);
+ private static final ProgramSelector.Identifier DAB_FREQUENCY_IDENTIFIER_1 =
+ new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY,
+ /* value= */ 222_064);
+ private static final ProgramSelector.Identifier DAB_FREQUENCY_IDENTIFIER_2 =
+ new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY,
+ /* value= */ 220_352);
+
+ private static final ProgramSelector DAB_SELECTOR_1 = new ProgramSelector(
+ ProgramSelector.PROGRAM_TYPE_DAB, DAB_DMB_SID_EXT_IDENTIFIER,
+ new ProgramSelector.Identifier[]{DAB_ENSEMBLE_IDENTIFIER, DAB_FREQUENCY_IDENTIFIER_1},
+ /* vendorIds= */ null);
+ private static final ProgramSelector DAB_SELECTOR_2 = new ProgramSelector(
+ ProgramSelector.PROGRAM_TYPE_DAB, DAB_DMB_SID_EXT_IDENTIFIER,
+ new ProgramSelector.Identifier[]{DAB_ENSEMBLE_IDENTIFIER, DAB_FREQUENCY_IDENTIFIER_2},
+ /* vendorIds= */ null);
+
+ private static final UniqueProgramIdentifier RDS_UNIQUE_IDENTIFIER =
+ new UniqueProgramIdentifier(RDS_IDENTIFIER);
+ private static final UniqueProgramIdentifier DAB_UNIQUE_IDENTIFIER_1 =
+ new UniqueProgramIdentifier(DAB_SELECTOR_1);
+ private static final UniqueProgramIdentifier DAB_UNIQUE_IDENTIFIER_2 =
+ new UniqueProgramIdentifier(DAB_SELECTOR_2);
+
private static final RadioManager.ProgramInfo FM_PROGRAM_INFO = createFmProgramInfo(
createProgramSelector(ProgramSelector.PROGRAM_TYPE_FM, FM_IDENTIFIER));
- private static final RadioManager.ProgramInfo RDS_PROGRAM_INFO = createFmProgramInfo(
- createProgramSelector(ProgramSelector.PROGRAM_TYPE_FM, RDS_IDENTIFIER));
+ private static final RadioManager.ProgramInfo DAB_PROGRAM_INFO_1 = createDabProgramInfo(
+ DAB_SELECTOR_1);
+ private static final RadioManager.ProgramInfo DAB_PROGRAM_INFO_2 = createDabProgramInfo(
+ DAB_SELECTOR_2);
private static final Set<Integer> FILTER_IDENTIFIER_TYPES = Set.of(
- ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY, ProgramSelector.IDENTIFIER_TYPE_RDS_PI);
- private static final Set<ProgramSelector.Identifier> FILTER_IDENTIFIERS = Set.of(FM_IDENTIFIER);
+ ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY,
+ ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT);
+ private static final Set<ProgramSelector.Identifier> FILTER_IDENTIFIERS = Set.of(
+ FM_IDENTIFIER, DAB_DMB_SID_EXT_IDENTIFIER);
- private static final ProgramList.Chunk FM_RDS_ADD_CHUNK = new ProgramList.Chunk(IS_PURGE,
- IS_COMPLETE, Set.of(FM_PROGRAM_INFO, RDS_PROGRAM_INFO),
- Set.of(DAB_DMB_SID_EXT_IDENTIFIER, DAB_ENSEMBLE_IDENTIFIER));
+ private static final ProgramList.Chunk FM_DAB_ADD_CHUNK = new ProgramList.Chunk(IS_PURGE,
+ IS_COMPLETE, Set.of(FM_PROGRAM_INFO, DAB_PROGRAM_INFO_1, DAB_PROGRAM_INFO_2),
+ Set.of(RDS_UNIQUE_IDENTIFIER));
private static final ProgramList.Chunk FM_ADD_INCOMPLETE_CHUNK = new ProgramList.Chunk(IS_PURGE,
/* complete= */ false, Set.of(FM_PROGRAM_INFO), new ArraySet<>());
private static final ProgramList.Filter TEST_FILTER = new ProgramList.Filter(
@@ -213,58 +240,44 @@
@Test
public void isPurge_forChunk() {
- ProgramList.Chunk chunk = new ProgramList.Chunk(IS_PURGE, IS_COMPLETE,
- Set.of(FM_PROGRAM_INFO, RDS_PROGRAM_INFO),
- Set.of(DAB_DMB_SID_EXT_IDENTIFIER, DAB_ENSEMBLE_IDENTIFIER));
-
- assertWithMessage("Puring chunk").that(chunk.isPurge()).isEqualTo(IS_PURGE);
+ assertWithMessage("Puring chunk").that(FM_DAB_ADD_CHUNK.isPurge()).isEqualTo(IS_PURGE);
}
@Test
public void isComplete_forChunk() {
- ProgramList.Chunk chunk = new ProgramList.Chunk(IS_PURGE, IS_COMPLETE,
- Set.of(FM_PROGRAM_INFO, RDS_PROGRAM_INFO),
- Set.of(DAB_DMB_SID_EXT_IDENTIFIER, DAB_ENSEMBLE_IDENTIFIER));
-
- assertWithMessage("Complete chunk").that(chunk.isComplete()).isEqualTo(IS_COMPLETE);
+ assertWithMessage("Complete chunk").that(FM_DAB_ADD_CHUNK.isComplete())
+ .isEqualTo(IS_COMPLETE);
}
@Test
public void getModified_forChunk() {
- ProgramList.Chunk chunk = new ProgramList.Chunk(IS_PURGE, IS_COMPLETE,
- Set.of(FM_PROGRAM_INFO, RDS_PROGRAM_INFO),
- Set.of(DAB_DMB_SID_EXT_IDENTIFIER, DAB_ENSEMBLE_IDENTIFIER));
-
assertWithMessage("Modified program info in chunk")
- .that(chunk.getModified()).containsExactly(FM_PROGRAM_INFO, RDS_PROGRAM_INFO);
+ .that(FM_DAB_ADD_CHUNK.getModified())
+ .containsExactly(FM_PROGRAM_INFO, DAB_PROGRAM_INFO_1, DAB_PROGRAM_INFO_2);
}
@Test
public void getRemoved_forChunk() {
- ProgramList.Chunk chunk = new ProgramList.Chunk(IS_PURGE, IS_COMPLETE,
- Set.of(FM_PROGRAM_INFO, RDS_PROGRAM_INFO),
- Set.of(DAB_DMB_SID_EXT_IDENTIFIER, DAB_ENSEMBLE_IDENTIFIER));
-
- assertWithMessage("Removed program identifiers in chunk").that(chunk.getRemoved())
- .containsExactly(DAB_DMB_SID_EXT_IDENTIFIER, DAB_ENSEMBLE_IDENTIFIER);
+ assertWithMessage("Removed program identifiers in chunk")
+ .that(FM_DAB_ADD_CHUNK.getRemoved()).containsExactly(RDS_UNIQUE_IDENTIFIER);
}
@Test
public void describeContents_forChunk() {
- assertWithMessage("Chunk contents").that(FM_RDS_ADD_CHUNK.describeContents()).isEqualTo(0);
+ assertWithMessage("Chunk contents").that(FM_DAB_ADD_CHUNK.describeContents()).isEqualTo(0);
}
@Test
public void writeToParcel_forChunk() {
Parcel parcel = Parcel.obtain();
- FM_RDS_ADD_CHUNK.writeToParcel(parcel, /* flags= */ 0);
+ FM_DAB_ADD_CHUNK.writeToParcel(parcel, /* flags= */ 0);
parcel.setDataPosition(0);
ProgramList.Chunk chunkFromParcel =
ProgramList.Chunk.CREATOR.createFromParcel(parcel);
assertWithMessage("Chunk created from parcel")
- .that(chunkFromParcel).isEqualTo(FM_RDS_ADD_CHUNK);
+ .that(chunkFromParcel).isEqualTo(FM_DAB_ADD_CHUNK);
}
@Test
@@ -336,37 +349,78 @@
}
@Test
- public void onProgramListUpdated_withNewIdsAdded_invokesMockedCallbacks() throws Exception {
+ public void onProgramListUpdated_withNewIdsAdded_invokesCallbacks() throws Exception {
createRadioTuner();
mProgramList = mRadioTuner.getDynamicProgramList(TEST_FILTER);
registerListCallbacks(/* numCallbacks= */ 1);
addOnCompleteListeners(/* numListeners= */ 1);
- mTunerCallback.onProgramListUpdated(FM_RDS_ADD_CHUNK);
+ mTunerCallback.onProgramListUpdated(FM_DAB_ADD_CHUNK);
verify(mListCallbackMocks[0], CALLBACK_TIMEOUT).onItemChanged(FM_IDENTIFIER);
- verify(mListCallbackMocks[0], CALLBACK_TIMEOUT).onItemChanged(RDS_IDENTIFIER);
+ verify(mListCallbackMocks[0], CALLBACK_TIMEOUT).onItemChanged(DAB_DMB_SID_EXT_IDENTIFIER);
verify(mOnCompleteListenerMocks[0], CALLBACK_TIMEOUT).onComplete();
- assertWithMessage("Program info in program list after adding FM and RDS info")
- .that(mProgramList.toList()).containsExactly(FM_PROGRAM_INFO, RDS_PROGRAM_INFO);
+ assertWithMessage("Program info in program list after adding FM and DAB info")
+ .that(mProgramList.toList()).containsExactly(FM_PROGRAM_INFO, DAB_PROGRAM_INFO_1,
+ DAB_PROGRAM_INFO_2);
}
@Test
- public void onProgramListUpdated_withIdsRemoved_invokesMockedCallbacks() throws Exception {
+ public void onProgramListUpdated_withFmIdsRemoved_invokesCallbacks() throws Exception {
+ UniqueProgramIdentifier fmUniqueId = new UniqueProgramIdentifier(FM_IDENTIFIER);
ProgramList.Chunk fmRemovedChunk = new ProgramList.Chunk(/* purge= */ false,
- /* complete= */ false, new ArraySet<>(), Set.of(FM_IDENTIFIER));
+ /* complete= */ false, new ArraySet<>(), Set.of(fmUniqueId));
createRadioTuner();
mProgramList = mRadioTuner.getDynamicProgramList(TEST_FILTER);
registerListCallbacks(/* numCallbacks= */ 1);
- mTunerCallback.onProgramListUpdated(FM_RDS_ADD_CHUNK);
+ mTunerCallback.onProgramListUpdated(FM_DAB_ADD_CHUNK);
mTunerCallback.onProgramListUpdated(fmRemovedChunk);
verify(mListCallbackMocks[0], CALLBACK_TIMEOUT).onItemRemoved(FM_IDENTIFIER);
assertWithMessage("Program info in program list after removing FM id")
- .that(mProgramList.toList()).containsExactly(RDS_PROGRAM_INFO);
- assertWithMessage("Program info FM identifier")
- .that(mProgramList.get(RDS_IDENTIFIER)).isEqualTo(RDS_PROGRAM_INFO);
+ .that(mProgramList.toList()).containsExactly(DAB_PROGRAM_INFO_1,
+ DAB_PROGRAM_INFO_2);
+ }
+
+ @Test
+ public void onProgramListUpdated_withPartOfDabIdsRemoved_doesNotInvokeCallbacks()
+ throws Exception {
+ ProgramList.Chunk dabRemovedChunk1 = new ProgramList.Chunk(/* purge= */ false,
+ /* complete= */ false, new ArraySet<>(), Set.of(DAB_UNIQUE_IDENTIFIER_1));
+ createRadioTuner();
+ mProgramList = mRadioTuner.getDynamicProgramList(TEST_FILTER);
+ registerListCallbacks(/* numCallbacks= */ 1);
+ mTunerCallback.onProgramListUpdated(FM_DAB_ADD_CHUNK);
+
+ mTunerCallback.onProgramListUpdated(dabRemovedChunk1);
+
+ verify(mListCallbackMocks[0], after(TIMEOUT_MS).never()).onItemRemoved(
+ DAB_DMB_SID_EXT_IDENTIFIER);
+ assertWithMessage("Program info in program list after removing part of DAB ids")
+ .that(mProgramList.toList()).containsExactly(FM_PROGRAM_INFO, DAB_PROGRAM_INFO_2);
+ }
+
+ @Test
+ public void onProgramListUpdated_withAllDabIdsRemoved_invokesCallbacks()
+ throws Exception {
+ ProgramList.Chunk dabRemovedChunk1 = new ProgramList.Chunk(/* purge= */ false,
+ /* complete= */ false, new ArraySet<>(), Set.of(DAB_UNIQUE_IDENTIFIER_1));
+ ProgramList.Chunk dabRemovedChunk2 = new ProgramList.Chunk(/* purge= */ false,
+ /* complete= */ false, new ArraySet<>(), Set.of(DAB_UNIQUE_IDENTIFIER_2));
+ createRadioTuner();
+ mProgramList = mRadioTuner.getDynamicProgramList(TEST_FILTER);
+ registerListCallbacks(/* numCallbacks= */ 1);
+ mTunerCallback.onProgramListUpdated(FM_DAB_ADD_CHUNK);
+ mTunerCallback.onProgramListUpdated(dabRemovedChunk1);
+ verify(mListCallbackMocks[0], after(TIMEOUT_MS).never()).onItemRemoved(
+ DAB_DMB_SID_EXT_IDENTIFIER);
+
+ mTunerCallback.onProgramListUpdated(dabRemovedChunk2);
+
+ verify(mListCallbackMocks[0], CALLBACK_TIMEOUT).onItemRemoved(DAB_DMB_SID_EXT_IDENTIFIER);
+ assertWithMessage("Program info in program list after removing all DAB ids")
+ .that(mProgramList.toList()).containsExactly(FM_PROGRAM_INFO);
}
@Test
@@ -388,18 +442,18 @@
createRadioTuner();
mProgramList = mRadioTuner.getDynamicProgramList(TEST_FILTER);
registerListCallbacks(/* numCallbacks= */ 1);
- mTunerCallback.onProgramListUpdated(FM_RDS_ADD_CHUNK);
+ mTunerCallback.onProgramListUpdated(FM_DAB_ADD_CHUNK);
mTunerCallback.onProgramListUpdated(purgeChunk);
verify(mListCallbackMocks[0], CALLBACK_TIMEOUT).onItemRemoved(FM_IDENTIFIER);
- verify(mListCallbackMocks[0], CALLBACK_TIMEOUT).onItemRemoved(RDS_IDENTIFIER);
+ verify(mListCallbackMocks[0], CALLBACK_TIMEOUT).onItemRemoved(DAB_DMB_SID_EXT_IDENTIFIER);
assertWithMessage("Program list after purge chunk applied")
.that(mProgramList.toList()).isEmpty();
}
@Test
- public void onProgramListUpdated_afterProgramListClosed_notInvokeMockedCallbacks()
+ public void onProgramListUpdated_afterProgramListClosed_notInvokeCallbacks()
throws Exception {
createRadioTuner();
mProgramList = mRadioTuner.getDynamicProgramList(TEST_FILTER);
@@ -407,7 +461,7 @@
addOnCompleteListeners(/* numListeners= */ 1);
mProgramList.close();
- mTunerCallback.onProgramListUpdated(FM_RDS_ADD_CHUNK);
+ mTunerCallback.onProgramListUpdated(FM_DAB_ADD_CHUNK);
verify(mListCallbackMocks[0], after(TIMEOUT_MS).never()).onItemChanged(any());
verify(mListCallbackMocks[0], never()).onItemChanged(any());
@@ -462,7 +516,7 @@
throws Exception {
createRadioTuner();
mProgramList = mRadioTuner.getDynamicProgramList(TEST_FILTER);
- mTunerCallback.onProgramListUpdated(FM_RDS_ADD_CHUNK);
+ mTunerCallback.onProgramListUpdated(FM_DAB_ADD_CHUNK);
mTunerCallback.onBackgroundScanComplete();
@@ -487,7 +541,7 @@
mProgramList = mRadioTuner.getDynamicProgramList(TEST_FILTER);
mTunerCallback.onBackgroundScanComplete();
- mTunerCallback.onProgramListUpdated(FM_RDS_ADD_CHUNK);
+ mTunerCallback.onProgramListUpdated(FM_DAB_ADD_CHUNK);
verify(mTunerCallbackMock, CALLBACK_TIMEOUT).onBackgroundScanComplete();
}
@@ -512,7 +566,7 @@
mock(ProgramList.OnCompleteListener.class);
mProgramList.addOnCompleteListener(mExecutor, onCompleteListenerMock);
- mTunerCallback.onProgramListUpdated(FM_RDS_ADD_CHUNK);
+ mTunerCallback.onProgramListUpdated(FM_DAB_ADD_CHUNK);
verify(onCompleteListenerMock, CALLBACK_TIMEOUT).onComplete();
}
@@ -524,7 +578,7 @@
mProgramList = mRadioTuner.getDynamicProgramList(TEST_FILTER);
addOnCompleteListeners(numListeners);
- mTunerCallback.onProgramListUpdated(FM_RDS_ADD_CHUNK);
+ mTunerCallback.onProgramListUpdated(FM_DAB_ADD_CHUNK);
for (int index = 0; index < numListeners; index++) {
verify(mOnCompleteListenerMocks[index], CALLBACK_TIMEOUT).onComplete();
@@ -538,7 +592,7 @@
addOnCompleteListeners(/* numListeners= */ 1);
mProgramList.removeOnCompleteListener(mOnCompleteListenerMocks[0]);
- mTunerCallback.onProgramListUpdated(FM_RDS_ADD_CHUNK);
+ mTunerCallback.onProgramListUpdated(FM_DAB_ADD_CHUNK);
verify(mOnCompleteListenerMocks[0], after(TIMEOUT_MS).never()).onComplete();
}
@@ -566,6 +620,13 @@
/* vendorInfo= */ null);
}
+ private static RadioManager.ProgramInfo createDabProgramInfo(ProgramSelector selector) {
+ return new RadioManager.ProgramInfo(selector, selector.getPrimaryId(),
+ DAB_ENSEMBLE_IDENTIFIER, /* relatedContents= */ null, /* infoFlags= */ 0,
+ /* signalQuality= */ 1, new RadioMetadata.Builder().build(),
+ /* vendorInfo= */ null);
+ }
+
private void createRadioTuner() throws Exception {
mApplicationInfo.targetSdkVersion = TEST_TARGET_SDK_VERSION;
when(mContextMock.getApplicationInfo()).thenReturn(mApplicationInfo);
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/AidlTestUtils.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/AidlTestUtils.java
index 6c70192..4f469bb 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/AidlTestUtils.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/AidlTestUtils.java
@@ -25,6 +25,7 @@
import android.hardware.radio.ProgramSelector;
import android.hardware.radio.RadioManager;
import android.hardware.radio.RadioMetadata;
+import android.hardware.radio.UniqueProgramIdentifier;
import android.os.RemoteException;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -149,12 +150,12 @@
static ProgramList.Chunk makeChunk(boolean purge, boolean complete,
List<RadioManager.ProgramInfo> modified,
- List<ProgramSelector.Identifier> removed) throws RemoteException {
+ List<UniqueProgramIdentifier> removed) throws RemoteException {
ArraySet<RadioManager.ProgramInfo> modifiedSet = new ArraySet<>();
if (modified != null) {
modifiedSet.addAll(modified);
}
- ArraySet<ProgramSelector.Identifier> removedSet = new ArraySet<>();
+ ArraySet<UniqueProgramIdentifier> removedSet = new ArraySet<>();
if (removed != null) {
removedSet.addAll(removed);
}
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java
index 2ef923d..89b91cf 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java
@@ -25,7 +25,6 @@
import android.hardware.broadcastradio.IdentifierType;
import android.hardware.broadcastradio.ProgramIdentifier;
import android.hardware.broadcastradio.ProgramInfo;
-import android.hardware.broadcastradio.ProgramListChunk;
import android.hardware.broadcastradio.Properties;
import android.hardware.broadcastradio.Result;
import android.hardware.broadcastradio.VendorKeyValue;
@@ -33,6 +32,7 @@
import android.hardware.radio.ProgramList;
import android.hardware.radio.ProgramSelector;
import android.hardware.radio.RadioManager;
+import android.hardware.radio.UniqueProgramIdentifier;
import android.os.ServiceSpecificException;
import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
@@ -103,12 +103,6 @@
private static final ProgramIdentifier TEST_HAL_DAB_FREQUENCY_ID =
AidlTestUtils.makeHalIdentifier(IdentifierType.DAB_FREQUENCY_KHZ,
TEST_DAB_FREQUENCY_VALUE);
- private static final ProgramIdentifier TEST_HAL_FM_FREQUENCY_ID =
- AidlTestUtils.makeHalIdentifier(IdentifierType.AMFM_FREQUENCY_KHZ,
- TEST_FM_FREQUENCY_VALUE);
- private static final ProgramIdentifier TEST_HAL_VENDOR_ID =
- AidlTestUtils.makeHalIdentifier(IdentifierType.VENDOR_START,
- TEST_VENDOR_ID_VALUE);
private static final ProgramSelector TEST_DAB_SELECTOR = new ProgramSelector(
ProgramSelector.PROGRAM_TYPE_DAB, TEST_DAB_SID_EXT_ID,
@@ -117,6 +111,12 @@
private static final ProgramSelector TEST_FM_SELECTOR =
AidlTestUtils.makeFmSelector(TEST_FM_FREQUENCY_VALUE);
+ private static final UniqueProgramIdentifier TEST_DAB_UNIQUE_ID = new UniqueProgramIdentifier(
+ TEST_DAB_SELECTOR);
+
+ private static final UniqueProgramIdentifier TEST_VENDOR_UNIQUE_ID =
+ new UniqueProgramIdentifier(TEST_VENDOR_ID);
+
private static final int TEST_ENABLED_TYPE = Announcement.TYPE_EMERGENCY;
private static final int TEST_ANNOUNCEMENT_FREQUENCY = FM_LOWER_LIMIT + FM_SPACING;
@@ -326,57 +326,6 @@
}
@Test
- public void chunkFromHalProgramListChunk_withValidChunk() {
- boolean purge = false;
- boolean complete = true;
- android.hardware.broadcastradio.ProgramSelector halDabSelector =
- AidlTestUtils.makeHalSelector(TEST_HAL_DAB_SID_EXT_ID, new ProgramIdentifier[]{
- TEST_HAL_DAB_ENSEMBLE_ID, TEST_HAL_DAB_FREQUENCY_ID});
- ProgramInfo halDabInfo = AidlTestUtils.makeHalProgramInfo(halDabSelector,
- TEST_HAL_DAB_SID_EXT_ID, TEST_HAL_DAB_FREQUENCY_ID, TEST_SIGNAL_QUALITY);
- RadioManager.ProgramInfo dabInfo =
- ConversionUtils.programInfoFromHalProgramInfo(halDabInfo);
- ProgramListChunk halChunk = AidlTestUtils.makeHalChunk(purge, complete,
- new ProgramInfo[]{halDabInfo},
- new ProgramIdentifier[]{TEST_HAL_VENDOR_ID, TEST_HAL_FM_FREQUENCY_ID});
-
- ProgramList.Chunk chunk = ConversionUtils.chunkFromHalProgramListChunk(halChunk);
-
- expect.withMessage("Purged state of the converted valid program list chunk")
- .that(chunk.isPurge()).isEqualTo(purge);
- expect.withMessage("Completion state of the converted valid program list chunk")
- .that(chunk.isComplete()).isEqualTo(complete);
- expect.withMessage("Modified program info in the converted valid program list chunk")
- .that(chunk.getModified()).containsExactly(dabInfo);
- expect.withMessage("Removed program ides in the converted valid program list chunk")
- .that(chunk.getRemoved()).containsExactly(TEST_VENDOR_ID, TEST_FM_FREQUENCY_ID);
- }
-
- @Test
- public void chunkFromHalProgramListChunk_withInvalidModifiedProgramInfo() {
- boolean purge = true;
- boolean complete = false;
- android.hardware.broadcastradio.ProgramSelector halDabSelector =
- AidlTestUtils.makeHalSelector(TEST_HAL_DAB_SID_EXT_ID, new ProgramIdentifier[]{
- TEST_HAL_DAB_ENSEMBLE_ID, TEST_HAL_DAB_FREQUENCY_ID});
- ProgramInfo halDabInfo = AidlTestUtils.makeHalProgramInfo(halDabSelector,
- TEST_HAL_DAB_SID_EXT_ID, TEST_HAL_DAB_ENSEMBLE_ID, TEST_SIGNAL_QUALITY);
- ProgramListChunk halChunk = AidlTestUtils.makeHalChunk(purge, complete,
- new ProgramInfo[]{halDabInfo}, new ProgramIdentifier[]{TEST_HAL_FM_FREQUENCY_ID});
-
- ProgramList.Chunk chunk = ConversionUtils.chunkFromHalProgramListChunk(halChunk);
-
- expect.withMessage("Purged state of the converted invalid program list chunk")
- .that(chunk.isPurge()).isEqualTo(purge);
- expect.withMessage("Completion state of the converted invalid program list chunk")
- .that(chunk.isComplete()).isEqualTo(complete);
- expect.withMessage("Modified program info in the converted invalid program list chunk")
- .that(chunk.getModified()).isEmpty();
- expect.withMessage("Removed program ids in the converted invalid program list chunk")
- .that(chunk.getRemoved()).containsExactly(TEST_FM_FREQUENCY_ID);
- }
-
- @Test
public void programSelectorMeetsSdkVersionRequirement_withLowerVersionId_returnsFalse() {
expect.withMessage("Selector %s without required SDK version", TEST_DAB_SELECTOR)
.that(ConversionUtils.programSelectorMeetsSdkVersionRequirement(TEST_DAB_SELECTOR,
@@ -418,7 +367,7 @@
TEST_SIGNAL_QUALITY);
ProgramList.Chunk chunk = new ProgramList.Chunk(/* purge= */ true,
/* complete= */ true, Set.of(dabProgramInfo, fmProgramInfo),
- Set.of(TEST_DAB_SID_EXT_ID, TEST_DAB_ENSEMBLE_ID, TEST_VENDOR_ID));
+ Set.of(TEST_DAB_UNIQUE_ID, TEST_VENDOR_UNIQUE_ID));
ProgramList.Chunk convertedChunk = ConversionUtils.convertChunkToTargetSdkVersion(chunk,
T_APP_UID);
@@ -434,8 +383,7 @@
.that(convertedChunk.getModified()).containsExactly(fmProgramInfo);
expect.withMessage(
"Removed program ids in the converted program list chunk with lower SDK version")
- .that(convertedChunk.getRemoved())
- .containsExactly(TEST_DAB_ENSEMBLE_ID, TEST_VENDOR_ID);
+ .that(convertedChunk.getRemoved()).containsExactly(TEST_VENDOR_UNIQUE_ID);
}
@Test
@@ -446,7 +394,7 @@
TEST_SIGNAL_QUALITY);
ProgramList.Chunk chunk = new ProgramList.Chunk(/* purge= */ true,
/* complete= */ true, Set.of(dabProgramInfo, fmProgramInfo),
- Set.of(TEST_DAB_SID_EXT_ID, TEST_DAB_ENSEMBLE_ID, TEST_VENDOR_ID));
+ Set.of(TEST_DAB_UNIQUE_ID, TEST_VENDOR_UNIQUE_ID));
ProgramList.Chunk convertedChunk = ConversionUtils.convertChunkToTargetSdkVersion(chunk,
U_APP_UID);
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ProgramInfoCacheTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ProgramInfoCacheTest.java
index d54397e..ce27bc1 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ProgramInfoCacheTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ProgramInfoCacheTest.java
@@ -22,6 +22,7 @@
import android.hardware.radio.ProgramList;
import android.hardware.radio.ProgramSelector;
import android.hardware.radio.RadioManager;
+import android.hardware.radio.UniqueProgramIdentifier;
import android.os.RemoteException;
import android.util.ArraySet;
@@ -32,6 +33,7 @@
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
+import java.util.ArrayList;
import java.util.List;
import java.util.Set;
@@ -43,6 +45,9 @@
private static final int TEST_SIGNAL_QUALITY = 90;
+ private static final int TEST_MAX_NUM_MODIFIED_PER_CHUNK = 2;
+ private static final int TEST_MAX_NUM_REMOVED_PER_CHUNK = 2;
+
private static final ProgramSelector.Identifier TEST_FM_FREQUENCY_ID =
new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY,
/* value= */ 88_500);
@@ -58,6 +63,8 @@
private static final ProgramSelector.Identifier TEST_AM_FREQUENCY_ID =
new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY,
/* value= */ 1_700);
+ private static final UniqueProgramIdentifier TEST_AM_UNIQUE_ID = new UniqueProgramIdentifier(
+ TEST_AM_FREQUENCY_ID);
private static final RadioManager.ProgramInfo TEST_AM_INFO = AidlTestUtils.makeProgramInfo(
AidlTestUtils.makeProgramSelector(ProgramSelector.PROGRAM_TYPE_FM,
TEST_AM_FREQUENCY_ID), TEST_AM_FREQUENCY_ID, TEST_AM_FREQUENCY_ID,
@@ -66,6 +73,8 @@
private static final ProgramSelector.Identifier TEST_RDS_PI_ID =
new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_RDS_PI,
/* value= */ 15_019);
+ private static final UniqueProgramIdentifier TEST_RDS_PI_UNIQUE_ID =
+ new UniqueProgramIdentifier(TEST_RDS_PI_ID);
private static final RadioManager.ProgramInfo TEST_RDS_INFO = AidlTestUtils.makeProgramInfo(
AidlTestUtils.makeProgramSelector(ProgramSelector.PROGRAM_TYPE_FM, TEST_RDS_PI_ID),
TEST_RDS_PI_ID, new ProgramSelector.Identifier(
@@ -81,11 +90,27 @@
private static final ProgramSelector.Identifier TEST_DAB_FREQUENCY_ID =
new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY,
/* value= */ 220_352);
- private static final RadioManager.ProgramInfo TEST_DAB_INFO = AidlTestUtils.makeProgramInfo(
- new ProgramSelector(ProgramSelector.PROGRAM_TYPE_DAB, TEST_DAB_DMB_SID_EXT_ID,
- new ProgramSelector.Identifier[]{TEST_DAB_FREQUENCY_ID, TEST_DAB_ENSEMBLE_ID},
- /* vendorIds= */ null), TEST_DAB_DMB_SID_EXT_ID, TEST_DAB_FREQUENCY_ID,
- TEST_SIGNAL_QUALITY);
+ private static final ProgramSelector.Identifier TEST_DAB_FREQUENCY_ID_ALTERNATIVE =
+ new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY,
+ /* value= */ 220_064);
+ private static final ProgramSelector TEST_DAB_SELECTOR = new ProgramSelector(
+ ProgramSelector.PROGRAM_TYPE_DAB, TEST_DAB_DMB_SID_EXT_ID,
+ new ProgramSelector.Identifier[]{TEST_DAB_FREQUENCY_ID, TEST_DAB_ENSEMBLE_ID},
+ /* vendorIds= */ null);
+ private static final ProgramSelector TEST_DAB_SELECTOR_ALTERNATIVE = new ProgramSelector(
+ ProgramSelector.PROGRAM_TYPE_DAB, TEST_DAB_DMB_SID_EXT_ID,
+ new ProgramSelector.Identifier[]{TEST_DAB_FREQUENCY_ID_ALTERNATIVE,
+ TEST_DAB_ENSEMBLE_ID}, /* vendorIds= */ null);
+ private static final UniqueProgramIdentifier TEST_DAB_UNIQUE_ID = new UniqueProgramIdentifier(
+ TEST_DAB_SELECTOR);
+ private static final UniqueProgramIdentifier TEST_DAB_UNIQUE_ID_ALTERNATIVE =
+ new UniqueProgramIdentifier(TEST_DAB_SELECTOR_ALTERNATIVE);
+ private static final RadioManager.ProgramInfo TEST_DAB_INFO =
+ AidlTestUtils.makeProgramInfo(TEST_DAB_SELECTOR, TEST_DAB_DMB_SID_EXT_ID,
+ TEST_DAB_FREQUENCY_ID, TEST_SIGNAL_QUALITY);
+ private static final RadioManager.ProgramInfo TEST_DAB_INFO_ALTERNATIVE =
+ AidlTestUtils.makeProgramInfo(TEST_DAB_SELECTOR_ALTERNATIVE, TEST_DAB_DMB_SID_EXT_ID,
+ TEST_DAB_FREQUENCY_ID_ALTERNATIVE, TEST_SIGNAL_QUALITY);
private static final ProgramSelector.Identifier TEST_VENDOR_ID =
new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_VENDOR_START,
@@ -95,8 +120,8 @@
TEST_VENDOR_ID), TEST_VENDOR_ID, TEST_VENDOR_ID, TEST_SIGNAL_QUALITY);
private static final ProgramInfoCache FULL_PROGRAM_INFO_CACHE = new ProgramInfoCache(
- /* filter= */ null, /* complete= */ true,
- TEST_FM_INFO, TEST_AM_INFO, TEST_RDS_INFO, TEST_DAB_INFO, TEST_VENDOR_INFO);
+ /* filter= */ null, /* complete= */ true, TEST_FM_INFO, TEST_AM_INFO, TEST_RDS_INFO,
+ TEST_DAB_INFO, TEST_DAB_INFO_ALTERNATIVE, TEST_VENDOR_INFO);
@Rule
public final Expect expect = Expect.create();
@@ -163,6 +188,22 @@
}
@Test
+ public void updateFromHalProgramListChunk_withInvalidChunk() {
+ RadioManager.ProgramInfo invalidDabInfo = AidlTestUtils.makeProgramInfo(TEST_DAB_SELECTOR,
+ TEST_DAB_DMB_SID_EXT_ID, TEST_DAB_ENSEMBLE_ID, TEST_SIGNAL_QUALITY);
+ ProgramInfoCache cache = new ProgramInfoCache(/* filter= */ null,
+ /* complete= */ false);
+ ProgramListChunk chunk = AidlTestUtils.makeHalChunk(/* purge= */ false,
+ /* complete= */ true, new ProgramInfo[]{AidlTestUtils.programInfoToHalProgramInfo(
+ invalidDabInfo)}, new ProgramIdentifier[]{});
+
+ cache.updateFromHalProgramListChunk(chunk);
+
+ expect.withMessage("Program cache updated with invalid chunk")
+ .that(cache.toProgramInfoList()).isEmpty();
+ }
+
+ @Test
public void filterAndUpdateFromInternal_withNullFilter() {
ProgramInfoCache cache = new ProgramInfoCache(/* filter= */ null,
/* complete= */ true);
@@ -172,7 +213,7 @@
expect.withMessage("Program cache filtered by null filter")
.that(cache.toProgramInfoList())
.containsExactly(TEST_FM_INFO, TEST_AM_INFO, TEST_RDS_INFO, TEST_DAB_INFO,
- TEST_VENDOR_INFO);
+ TEST_DAB_INFO_ALTERNATIVE, TEST_VENDOR_INFO);
}
@Test
@@ -186,21 +227,21 @@
expect.withMessage("Program cache filtered by empty filter")
.that(cache.toProgramInfoList())
.containsExactly(TEST_FM_INFO, TEST_AM_INFO, TEST_RDS_INFO, TEST_DAB_INFO,
- TEST_VENDOR_INFO);
+ TEST_DAB_INFO_ALTERNATIVE, TEST_VENDOR_INFO);
}
@Test
public void filterAndUpdateFromInternal_withFilterByIdentifierType() {
ProgramInfoCache cache = new ProgramInfoCache(
new ProgramList.Filter(Set.of(ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY,
- ProgramSelector.IDENTIFIER_TYPE_RDS_PI), new ArraySet<>(),
+ ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT), new ArraySet<>(),
/* includeCategories= */ true, /* excludeModifications= */ false));
cache.filterAndUpdateFromInternal(FULL_PROGRAM_INFO_CACHE, /* purge= */ false);
expect.withMessage("Program cache filtered by identifier type")
- .that(cache.toProgramInfoList())
- .containsExactly(TEST_FM_INFO, TEST_AM_INFO, TEST_RDS_INFO);
+ .that(cache.toProgramInfoList()).containsExactly(TEST_FM_INFO, TEST_AM_INFO,
+ TEST_DAB_INFO, TEST_DAB_INFO_ALTERNATIVE);
}
@Test
@@ -208,20 +249,60 @@
ProgramInfoCache cache = new ProgramInfoCache(new ProgramList.Filter(
new ArraySet<>(), Set.of(TEST_FM_FREQUENCY_ID, TEST_DAB_DMB_SID_EXT_ID),
/* includeCategories= */ true, /* excludeModifications= */ false));
- int maxNumModifiedPerChunk = 2;
- int maxNumRemovedPerChunk = 2;
List<ProgramList.Chunk> programListChunks = cache.filterAndUpdateFromInternal(
- FULL_PROGRAM_INFO_CACHE, /* purge= */ true, maxNumModifiedPerChunk,
- maxNumRemovedPerChunk);
+ FULL_PROGRAM_INFO_CACHE, /* purge= */ false, TEST_MAX_NUM_MODIFIED_PER_CHUNK,
+ TEST_MAX_NUM_REMOVED_PER_CHUNK);
expect.withMessage("Program cache filtered by identifier")
- .that(cache.toProgramInfoList()).containsExactly(TEST_FM_INFO, TEST_DAB_INFO);
+ .that(cache.toProgramInfoList()).containsExactly(TEST_FM_INFO, TEST_DAB_INFO,
+ TEST_DAB_INFO_ALTERNATIVE);
verifyChunkListPurge(programListChunks, /* purge= */ true);
- verifyChunkListComplete(programListChunks, FULL_PROGRAM_INFO_CACHE.isComplete());
+ verifyChunkListComplete(programListChunks, cache.isComplete());
+ verifyChunkListModified(programListChunks, TEST_MAX_NUM_MODIFIED_PER_CHUNK, TEST_FM_INFO,
+ TEST_DAB_INFO, TEST_DAB_INFO_ALTERNATIVE);
+ verifyChunkListRemoved(programListChunks, TEST_MAX_NUM_REMOVED_PER_CHUNK);
+ }
+
+ @Test
+ public void filterAndUpdateFromInternal_withPurging() {
+ ProgramInfoCache cache = new ProgramInfoCache(new ProgramList.Filter(new ArraySet<>(),
+ new ArraySet<>(), /* includeCategories= */ true, /* excludeModifications= */ false),
+ /* complete= */ true, TEST_RDS_INFO, TEST_DAB_INFO);
+ ProgramInfoCache otherCache = new ProgramInfoCache(/* filter= */ null, /* complete= */ true,
+ TEST_FM_INFO, TEST_RDS_INFO, TEST_DAB_INFO_ALTERNATIVE);
+
+ List<ProgramList.Chunk> programListChunks = cache.filterAndUpdateFromInternal(otherCache,
+ /* purge= */ true, TEST_MAX_NUM_MODIFIED_PER_CHUNK, TEST_MAX_NUM_REMOVED_PER_CHUNK);
+
+ expect.withMessage("Program cache filtered with purging").that(cache.toProgramInfoList())
+ .containsExactly(TEST_FM_INFO, TEST_RDS_INFO, TEST_DAB_INFO_ALTERNATIVE);
+ verifyChunkListPurge(programListChunks, /* purge= */ true);
+ verifyChunkListModified(programListChunks, TEST_MAX_NUM_MODIFIED_PER_CHUNK, TEST_FM_INFO,
+ TEST_RDS_INFO, TEST_DAB_INFO_ALTERNATIVE);
+ verifyChunkListRemoved(programListChunks, TEST_MAX_NUM_REMOVED_PER_CHUNK);
+ }
+
+ @Test
+ public void filterAndUpdateFromInternal_withoutPurging() {
+ ProgramInfoCache cache = new ProgramInfoCache(new ProgramList.Filter(new ArraySet<>(),
+ new ArraySet<>(), /* includeCategories= */ true, /* excludeModifications= */ false),
+ /* complete= */ true, TEST_RDS_INFO, TEST_DAB_INFO);
+ ProgramInfoCache otherCache = new ProgramInfoCache(/* filter= */ null, /* complete= */ true,
+ TEST_FM_INFO, TEST_RDS_INFO, TEST_DAB_INFO_ALTERNATIVE);
+ int maxNumModifiedPerChunk = 1;
+
+ List<ProgramList.Chunk> programListChunks = cache.filterAndUpdateFromInternal(otherCache,
+ /* purge= */ false, maxNumModifiedPerChunk, TEST_MAX_NUM_REMOVED_PER_CHUNK);
+
+ expect.withMessage("Program cache filtered without puring").that(cache.toProgramInfoList())
+ .containsExactly(TEST_FM_INFO, TEST_RDS_INFO, TEST_DAB_INFO_ALTERNATIVE);
+ verifyChunkListPurge(programListChunks, /* purge= */ false);
+ verifyChunkListComplete(programListChunks, cache.isComplete());
verifyChunkListModified(programListChunks, maxNumModifiedPerChunk, TEST_FM_INFO,
- TEST_DAB_INFO);
- verifyChunkListRemoved(programListChunks, maxNumRemovedPerChunk);
+ TEST_DAB_INFO_ALTERNATIVE);
+ verifyChunkListRemoved(programListChunks, TEST_MAX_NUM_REMOVED_PER_CHUNK,
+ TEST_DAB_UNIQUE_ID);
}
@Test
@@ -230,20 +311,19 @@
new ArraySet<>(), /* includeCategories= */ false,
/* excludeModifications= */ false));
int maxNumModifiedPerChunk = 3;
- int maxNumRemovedPerChunk = 2;
List<ProgramList.Chunk> programListChunks = cache.filterAndUpdateFromInternal(
FULL_PROGRAM_INFO_CACHE, /* purge= */ false, maxNumModifiedPerChunk,
- maxNumRemovedPerChunk);
+ TEST_MAX_NUM_REMOVED_PER_CHUNK);
expect.withMessage("Program cache filtered by excluding categories")
- .that(cache.toProgramInfoList())
- .containsExactly(TEST_FM_INFO, TEST_AM_INFO, TEST_RDS_INFO, TEST_DAB_INFO);
+ .that(cache.toProgramInfoList()).containsExactly(TEST_FM_INFO, TEST_AM_INFO,
+ TEST_RDS_INFO, TEST_DAB_INFO, TEST_DAB_INFO_ALTERNATIVE);
verifyChunkListPurge(programListChunks, /* purge= */ true);
- verifyChunkListComplete(programListChunks, FULL_PROGRAM_INFO_CACHE.isComplete());
+ verifyChunkListComplete(programListChunks, cache.isComplete());
verifyChunkListModified(programListChunks, maxNumModifiedPerChunk, TEST_FM_INFO,
- TEST_AM_INFO, TEST_RDS_INFO, TEST_DAB_INFO);
- verifyChunkListRemoved(programListChunks, maxNumRemovedPerChunk);
+ TEST_AM_INFO, TEST_RDS_INFO, TEST_DAB_INFO, TEST_DAB_INFO_ALTERNATIVE);
+ verifyChunkListRemoved(programListChunks, TEST_MAX_NUM_REMOVED_PER_CHUNK);
}
@Test
@@ -254,21 +334,21 @@
ProgramInfoCache cache = new ProgramInfoCache(filterExcludingModifications,
/* complete= */ true, TEST_FM_INFO, TEST_RDS_INFO, TEST_AM_INFO, TEST_DAB_INFO);
ProgramInfoCache halCache = new ProgramInfoCache(/* filter= */ null, /* complete= */ false,
- TEST_FM_INFO_MODIFIED, TEST_VENDOR_INFO);
- int maxNumModifiedPerChunk = 2;
- int maxNumRemovedPerChunk = 2;
+ TEST_FM_INFO_MODIFIED, TEST_DAB_INFO_ALTERNATIVE, TEST_VENDOR_INFO);
List<ProgramList.Chunk> programListChunks = cache.filterAndUpdateFromInternal(halCache,
- /* purge= */ false, maxNumModifiedPerChunk, maxNumRemovedPerChunk);
+ /* purge= */ false, TEST_MAX_NUM_MODIFIED_PER_CHUNK,
+ TEST_MAX_NUM_REMOVED_PER_CHUNK);
expect.withMessage("Program cache filtered by excluding modifications")
.that(cache.toProgramInfoList())
- .containsExactly(TEST_FM_INFO, TEST_VENDOR_INFO);
+ .containsExactly(TEST_FM_INFO, TEST_DAB_INFO_ALTERNATIVE, TEST_VENDOR_INFO);
verifyChunkListPurge(programListChunks, /* purge= */ false);
verifyChunkListComplete(programListChunks, halCache.isComplete());
- verifyChunkListModified(programListChunks, maxNumModifiedPerChunk, TEST_VENDOR_INFO);
- verifyChunkListRemoved(programListChunks, maxNumRemovedPerChunk, TEST_RDS_PI_ID,
- TEST_AM_FREQUENCY_ID, TEST_DAB_DMB_SID_EXT_ID);
+ verifyChunkListModified(programListChunks, TEST_MAX_NUM_MODIFIED_PER_CHUNK,
+ TEST_VENDOR_INFO, TEST_DAB_INFO_ALTERNATIVE);
+ verifyChunkListRemoved(programListChunks, TEST_MAX_NUM_REMOVED_PER_CHUNK,
+ TEST_RDS_PI_UNIQUE_ID, TEST_AM_UNIQUE_ID, TEST_DAB_UNIQUE_ID);
}
@Test
@@ -276,69 +356,88 @@
ProgramInfoCache cache = new ProgramInfoCache(new ProgramList.Filter(new ArraySet<>(),
new ArraySet<>(), /* includeCategories= */ true,
/* excludeModifications= */ false),
- /* complete= */ true, TEST_FM_INFO, TEST_RDS_INFO);
+ /* complete= */ true, TEST_FM_INFO, TEST_RDS_INFO, TEST_DAB_INFO);
ProgramInfoCache halCache = new ProgramInfoCache(/* filter= */ null, /* complete= */ false,
- TEST_FM_INFO_MODIFIED, TEST_DAB_INFO, TEST_VENDOR_INFO);
- int maxNumModifiedPerChunk = 2;
- int maxNumRemovedPerChunk = 2;
+ TEST_FM_INFO_MODIFIED, TEST_DAB_INFO_ALTERNATIVE, TEST_VENDOR_INFO);
List<ProgramList.Chunk> programListChunks = cache.filterAndUpdateFromInternal(halCache,
- /* purge= */ true, maxNumModifiedPerChunk, maxNumRemovedPerChunk);
+ /* purge= */ true, TEST_MAX_NUM_MODIFIED_PER_CHUNK, TEST_MAX_NUM_REMOVED_PER_CHUNK);
expect.withMessage("Purged program cache").that(cache.toProgramInfoList())
- .containsExactly(TEST_FM_INFO_MODIFIED, TEST_DAB_INFO, TEST_VENDOR_INFO);
+ .containsExactly(TEST_FM_INFO_MODIFIED, TEST_DAB_INFO_ALTERNATIVE,
+ TEST_VENDOR_INFO);
verifyChunkListPurge(programListChunks, /* purge= */ true);
verifyChunkListComplete(programListChunks, halCache.isComplete());
- verifyChunkListModified(programListChunks, maxNumModifiedPerChunk, TEST_FM_INFO_MODIFIED,
- TEST_DAB_INFO, TEST_VENDOR_INFO);
- verifyChunkListRemoved(programListChunks, maxNumRemovedPerChunk);
+ verifyChunkListModified(programListChunks, TEST_MAX_NUM_MODIFIED_PER_CHUNK,
+ TEST_FM_INFO_MODIFIED, TEST_DAB_INFO_ALTERNATIVE, TEST_VENDOR_INFO);
+ verifyChunkListRemoved(programListChunks, TEST_MAX_NUM_REMOVED_PER_CHUNK);
}
@Test
- public void filterAndApplyChunkInternal_withPurgingIncompleteChunk() throws RemoteException {
+ public void filterAndApplyChunkInternal_withPurgingAndIncompleteChunk() throws RemoteException {
ProgramInfoCache cache = new ProgramInfoCache(/* filter= */ null,
- /* complete= */ false, TEST_FM_INFO, TEST_DAB_INFO);
- ProgramList.Chunk chunk = AidlTestUtils.makeChunk(/* purge= */ true, /* complete= */ false,
- List.of(TEST_FM_INFO_MODIFIED, TEST_RDS_INFO, TEST_VENDOR_INFO),
- List.of(TEST_DAB_DMB_SID_EXT_ID));
- int maxNumModifiedPerChunk = 2;
- int maxNumRemovedPerChunk = 2;
+ /* complete= */ false, TEST_FM_INFO, TEST_RDS_INFO, TEST_DAB_INFO);
+ ProgramListChunk halChunk = AidlTestUtils.makeHalChunk(/* purge= */ true,
+ /* complete= */ false, List.of(TEST_FM_INFO_MODIFIED,
+ TEST_DAB_INFO_ALTERNATIVE, TEST_VENDOR_INFO), new ArrayList<>());
- List<ProgramList.Chunk> programListChunks = cache.filterAndApplyChunkInternal(chunk,
- maxNumModifiedPerChunk, maxNumRemovedPerChunk);
+ List<ProgramList.Chunk> programListChunks = cache.filterAndApplyChunkInternal(halChunk,
+ TEST_MAX_NUM_MODIFIED_PER_CHUNK, TEST_MAX_NUM_REMOVED_PER_CHUNK);
- expect.withMessage("Program cache applied with non-purging and complete chunk")
- .that(cache.toProgramInfoList())
- .containsExactly(TEST_FM_INFO_MODIFIED, TEST_RDS_INFO, TEST_VENDOR_INFO);
+ expect.withMessage("Program cache applied with purge-enabled and complete chunk")
+ .that(cache.toProgramInfoList()).containsExactly(TEST_FM_INFO_MODIFIED,
+ TEST_DAB_INFO_ALTERNATIVE, TEST_VENDOR_INFO);
verifyChunkListPurge(programListChunks, /* purge= */ true);
verifyChunkListComplete(programListChunks, /* complete= */ false);
- verifyChunkListModified(programListChunks, maxNumModifiedPerChunk, TEST_FM_INFO_MODIFIED,
- TEST_RDS_INFO, TEST_VENDOR_INFO);
- verifyChunkListRemoved(programListChunks, maxNumRemovedPerChunk);
+ verifyChunkListModified(programListChunks, TEST_MAX_NUM_MODIFIED_PER_CHUNK,
+ TEST_FM_INFO_MODIFIED, TEST_DAB_INFO_ALTERNATIVE, TEST_VENDOR_INFO);
+ verifyChunkListRemoved(programListChunks, TEST_MAX_NUM_REMOVED_PER_CHUNK);
}
@Test
- public void filterAndApplyChunk_withNonPurgingCompleteChunk() throws RemoteException {
- ProgramInfoCache cache = new ProgramInfoCache(/* filter= */ null,
- /* complete= */ false, TEST_FM_INFO, TEST_RDS_INFO, TEST_AM_INFO, TEST_DAB_INFO);
- ProgramList.Chunk chunk = AidlTestUtils.makeChunk(/* purge= */ false, /* complete= */ true,
- List.of(TEST_FM_INFO_MODIFIED, TEST_VENDOR_INFO),
+ public void filterAndApplyChunk_withNonPurgingAndIncompleteChunk() throws RemoteException {
+ ProgramInfoCache cache = new ProgramInfoCache(/* filter= */ null, /* complete= */ false,
+ TEST_FM_INFO, TEST_RDS_INFO, TEST_AM_INFO, TEST_DAB_INFO);
+ ProgramListChunk halChunk = AidlTestUtils.makeHalChunk(/* purge= */ false,
+ /* complete= */ false, List.of(TEST_FM_INFO_MODIFIED, TEST_DAB_INFO_ALTERNATIVE,
+ TEST_VENDOR_INFO), List.of(TEST_RDS_PI_ID, TEST_AM_FREQUENCY_ID));
+
+ List<ProgramList.Chunk> programListChunks = cache.filterAndApplyChunkInternal(halChunk,
+ TEST_MAX_NUM_MODIFIED_PER_CHUNK, TEST_MAX_NUM_REMOVED_PER_CHUNK);
+
+ expect.withMessage("Program cache applied with non-purging and incomplete chunk")
+ .that(cache.toProgramInfoList()).containsExactly(TEST_DAB_INFO,
+ TEST_DAB_INFO_ALTERNATIVE, TEST_FM_INFO_MODIFIED, TEST_VENDOR_INFO);
+ verifyChunkListPurge(programListChunks, /* purge= */ false);
+ verifyChunkListComplete(programListChunks, /* complete= */ false);
+ verifyChunkListModified(programListChunks, TEST_MAX_NUM_MODIFIED_PER_CHUNK,
+ TEST_FM_INFO_MODIFIED, TEST_DAB_INFO_ALTERNATIVE, TEST_VENDOR_INFO);
+ verifyChunkListRemoved(programListChunks, TEST_MAX_NUM_REMOVED_PER_CHUNK,
+ TEST_RDS_PI_UNIQUE_ID, TEST_AM_UNIQUE_ID);
+ }
+
+ @Test
+ public void filterAndApplyChunk_withNonPurgingAndCompleteChunk() throws RemoteException {
+ ProgramInfoCache cache = new ProgramInfoCache(/* filter= */ null, /* complete= */ false,
+ TEST_FM_INFO, TEST_RDS_INFO, TEST_AM_INFO, TEST_DAB_INFO,
+ TEST_DAB_INFO_ALTERNATIVE);
+ ProgramListChunk halChunk = AidlTestUtils.makeHalChunk(/* purge= */ false,
+ /* complete= */ true, List.of(TEST_FM_INFO_MODIFIED, TEST_VENDOR_INFO),
List.of(TEST_RDS_PI_ID, TEST_AM_FREQUENCY_ID, TEST_DAB_DMB_SID_EXT_ID));
- int maxNumModifiedPerChunk = 2;
- int maxNumRemovedPerChunk = 2;
- List<ProgramList.Chunk> programListChunks = cache.filterAndApplyChunkInternal(chunk,
- maxNumModifiedPerChunk, maxNumRemovedPerChunk);
+ List<ProgramList.Chunk> programListChunks = cache.filterAndApplyChunkInternal(halChunk,
+ TEST_MAX_NUM_MODIFIED_PER_CHUNK, TEST_MAX_NUM_REMOVED_PER_CHUNK);
- expect.withMessage("Program cache applied with purge-enabled complete chunk")
+ expect.withMessage("Program cache applied with non-purging and complete chunk")
.that(cache.toProgramInfoList())
.containsExactly(TEST_FM_INFO_MODIFIED, TEST_VENDOR_INFO);
verifyChunkListPurge(programListChunks, /* purge= */ false);
verifyChunkListComplete(programListChunks, /* complete= */ true);
- verifyChunkListModified(programListChunks, maxNumModifiedPerChunk, TEST_FM_INFO_MODIFIED,
- TEST_VENDOR_INFO);
- verifyChunkListRemoved(programListChunks, maxNumRemovedPerChunk, TEST_RDS_PI_ID,
- TEST_AM_FREQUENCY_ID, TEST_DAB_DMB_SID_EXT_ID);
+ verifyChunkListModified(programListChunks, TEST_MAX_NUM_MODIFIED_PER_CHUNK,
+ TEST_FM_INFO_MODIFIED, TEST_VENDOR_INFO);
+ verifyChunkListRemoved(programListChunks, TEST_MAX_NUM_REMOVED_PER_CHUNK,
+ TEST_RDS_PI_UNIQUE_ID, TEST_AM_UNIQUE_ID, TEST_DAB_UNIQUE_ID,
+ TEST_DAB_UNIQUE_ID_ALTERNATIVE);
}
private void verifyChunkListPurge(List<ProgramList.Chunk> chunks, boolean purge) {
@@ -387,17 +486,17 @@
.that(actualSet).containsExactlyElementsIn(expectedProgramInfos);
}
- private void verifyChunkListRemoved(List<ProgramList.Chunk> chunks,
- int maxRemovedPerChunk, ProgramSelector.Identifier... expectedIdentifiers) {
+ private void verifyChunkListRemoved(List<ProgramList.Chunk> chunks, int maxRemovedPerChunk,
+ UniqueProgramIdentifier... expectedIdentifiers) {
if (chunks.isEmpty()) {
expect.withMessage("Empty program info list")
.that(expectedIdentifiers.length).isEqualTo(0);
return;
}
- ArraySet<ProgramSelector.Identifier> actualSet = new ArraySet<>();
+ ArraySet<UniqueProgramIdentifier> actualSet = new ArraySet<>();
for (int i = 0; i < chunks.size(); i++) {
- Set<ProgramSelector.Identifier> chunkRemoved = chunks.get(i).getRemoved();
+ Set<UniqueProgramIdentifier> chunkRemoved = chunks.get(i).getRemoved();
actualSet.addAll(chunkRemoved);
expect.withMessage("Chunk %s removed identifier array size ", i)
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java
index 84aa864..a195228 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java
@@ -18,8 +18,6 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
-import static com.google.common.truth.Truth.assertWithMessage;
-
import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -47,6 +45,7 @@
import android.hardware.radio.ProgramSelector;
import android.hardware.radio.RadioManager;
import android.hardware.radio.RadioTuner;
+import android.hardware.radio.UniqueProgramIdentifier;
import android.os.Binder;
import android.os.ParcelableException;
import android.os.RemoteException;
@@ -59,13 +58,18 @@
import com.android.server.broadcastradio.ExtendedRadioMockitoTestCase;
import com.android.server.broadcastradio.RadioServiceUserController;
+import com.google.common.truth.Expect;
+
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.verification.VerificationWithTimeout;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -94,10 +98,6 @@
private static final ProgramSelector.Identifier TEST_FM_FREQUENCY_ID =
new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY,
/* value= */ 88_500);
- private static final ProgramSelector.Identifier TEST_RDS_PI_ID =
- new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_RDS_PI,
- /* value= */ 15_019);
-
private static final RadioManager.ProgramInfo TEST_FM_INFO = AidlTestUtils.makeProgramInfo(
AidlTestUtils.makeProgramSelector(ProgramSelector.PROGRAM_TYPE_FM,
TEST_FM_FREQUENCY_ID), TEST_FM_FREQUENCY_ID, TEST_FM_FREQUENCY_ID,
@@ -106,11 +106,37 @@
AidlTestUtils.makeProgramInfo(AidlTestUtils.makeProgramSelector(
ProgramSelector.PROGRAM_TYPE_FM, TEST_FM_FREQUENCY_ID), TEST_FM_FREQUENCY_ID,
TEST_FM_FREQUENCY_ID, /* signalQuality= */ 100);
- private static final RadioManager.ProgramInfo TEST_RDS_INFO = AidlTestUtils.makeProgramInfo(
- AidlTestUtils.makeProgramSelector(ProgramSelector.PROGRAM_TYPE_FM, TEST_RDS_PI_ID),
- TEST_RDS_PI_ID, new ProgramSelector.Identifier(
- ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY, /* value= */ 89_500),
- SIGNAL_QUALITY);
+
+ private static final ProgramSelector.Identifier TEST_DAB_FREQUENCY_ID =
+ new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY,
+ /* value= */ 220_352);
+ private static final ProgramSelector.Identifier TEST_DAB_FREQUENCY_ID_ALT =
+ new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY,
+ /* value= */ 220_064);
+ private static final ProgramSelector.Identifier TEST_DAB_SID_EXT_ID =
+ new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT,
+ /* value= */ 0xA000000111L);
+ private static final ProgramSelector.Identifier TEST_DAB_ENSEMBLE_ID =
+ new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_ENSEMBLE,
+ /* value= */ 0x1001);
+ private static final ProgramSelector TEST_DAB_SELECTOR = new ProgramSelector(
+ ProgramSelector.PROGRAM_TYPE_DAB, TEST_DAB_SID_EXT_ID,
+ new ProgramSelector.Identifier[]{TEST_DAB_FREQUENCY_ID, TEST_DAB_ENSEMBLE_ID},
+ /* vendorIds= */ null);
+ private static final ProgramSelector TEST_DAB_SELECTOR_ALT = new ProgramSelector(
+ ProgramSelector.PROGRAM_TYPE_DAB, TEST_DAB_SID_EXT_ID,
+ new ProgramSelector.Identifier[]{TEST_DAB_FREQUENCY_ID_ALT, TEST_DAB_ENSEMBLE_ID},
+ /* vendorIds= */ null);
+ private static final UniqueProgramIdentifier TEST_DAB_UNIQUE_ID = new UniqueProgramIdentifier(
+ TEST_DAB_SELECTOR);
+ private static final UniqueProgramIdentifier TEST_DAB_UNIQUE_ID_ALT =
+ new UniqueProgramIdentifier(TEST_DAB_SELECTOR_ALT);
+ private static final RadioManager.ProgramInfo TEST_DAB_INFO =
+ AidlTestUtils.makeProgramInfo(TEST_DAB_SELECTOR, TEST_DAB_SID_EXT_ID,
+ TEST_DAB_FREQUENCY_ID, SIGNAL_QUALITY);
+ private static final RadioManager.ProgramInfo TEST_DAB_INFO_ALT =
+ AidlTestUtils.makeProgramInfo(TEST_DAB_SELECTOR_ALT, TEST_DAB_SID_EXT_ID,
+ TEST_DAB_FREQUENCY_ID_ALT, SIGNAL_QUALITY);
// Mocks
@Mock
@@ -129,6 +155,9 @@
private TunerSession[] mTunerSessions;
+ @Rule
+ public final Expect expect = Expect.create();
+
@Override
protected void initializeSession(StaticMockitoSessionBuilder builder) {
builder.spyStatic(RadioServiceUserController.class).spyStatic(CompatChanges.class)
@@ -225,7 +254,7 @@
openAidlClients(numSessions);
for (int index = 0; index < numSessions; index++) {
- assertWithMessage("Session of index %s close state", index)
+ expect.withMessage("Session of index %s close state", index)
.that(mTunerSessions[index].isClosed()).isFalse();
}
}
@@ -257,7 +286,7 @@
RadioManager.BandConfig config = mTunerSessions[0].getConfiguration();
- assertWithMessage("Session configuration").that(config)
+ expect.withMessage("Session configuration").that(config)
.isEqualTo(FM_BAND_CONFIG);
}
@@ -267,7 +296,7 @@
mTunerSessions[0].setMuted(/* mute= */ false);
- assertWithMessage("Session mute state after setting unmuted")
+ expect.withMessage("Session mute state after setting unmuted")
.that(mTunerSessions[0].isMuted()).isFalse();
}
@@ -277,7 +306,7 @@
mTunerSessions[0].setMuted(/* mute= */ true);
- assertWithMessage("Session mute state after setting muted")
+ expect.withMessage("Session mute state after setting muted")
.that(mTunerSessions[0].isMuted()).isTrue();
}
@@ -287,7 +316,7 @@
mTunerSessions[0].close();
- assertWithMessage("Close state of broadcast radio service session")
+ expect.withMessage("Close state of broadcast radio service session")
.that(mTunerSessions[0].isClosed()).isTrue();
}
@@ -301,11 +330,11 @@
for (int index = 0; index < numSessions; index++) {
if (index == closeIdx) {
- assertWithMessage(
+ expect.withMessage(
"Close state of broadcast radio service session of index %s", index)
.that(mTunerSessions[index].isClosed()).isTrue();
} else {
- assertWithMessage(
+ expect.withMessage(
"Close state of broadcast radio service session of index %s", index)
.that(mTunerSessions[index].isClosed()).isFalse();
}
@@ -320,7 +349,7 @@
mTunerSessions[0].close(errorCode);
verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onError(errorCode);
- assertWithMessage("Close state of broadcast radio service session")
+ expect.withMessage("Close state of broadcast radio service session")
.that(mTunerSessions[0].isClosed()).isTrue();
}
@@ -334,7 +363,7 @@
for (int index = 0; index < numSessions; index++) {
verify(mAidlTunerCallbackMocks[index], CALLBACK_TIMEOUT).onError(errorCode);
- assertWithMessage("Close state of broadcast radio service session of index %s", index)
+ expect.withMessage("Close state of broadcast radio service session of index %s", index)
.that(mTunerSessions[index].isClosed()).isTrue();
}
}
@@ -383,22 +412,12 @@
@Test
public void tune_withUnsupportedSelector_throwsException() throws Exception {
- ProgramSelector.Identifier dabPrimaryId =
- new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT,
- /* value= */ 0xA000000111L);
- ProgramSelector.Identifier[] dabSecondaryIds = new ProgramSelector.Identifier[]{
- new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_ENSEMBLE,
- /* value= */ 1337),
- new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY,
- /* value= */ 225648)};
- ProgramSelector unsupportedSelector = new ProgramSelector(ProgramSelector.PROGRAM_TYPE_DAB,
- dabPrimaryId, dabSecondaryIds, /* vendorIds= */ null);
openAidlClients(/* numClients= */ 1);
UnsupportedOperationException thrown = assertThrows(UnsupportedOperationException.class,
- () -> mTunerSessions[0].tune(unsupportedSelector));
+ () -> mTunerSessions[0].tune(TEST_DAB_SELECTOR));
- assertWithMessage("Exception for tuning on unsupported program selector")
+ expect.withMessage("Exception for tuning on unsupported program selector")
.that(thrown).hasMessageThat().contains("tune: NOT_SUPPORTED");
}
@@ -413,7 +432,7 @@
IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class,
() -> mTunerSessions[0].tune(invalidSel));
- assertWithMessage("Exception for tuning on DAB selector without DAB_SID_EXT primary id")
+ expect.withMessage("Exception for tuning on DAB selector without DAB_SID_EXT primary id")
.that(thrown).hasMessageThat().contains("tune: INVALID_ARGUMENTS");
}
@@ -457,7 +476,7 @@
mTunerSessions[0].tune(sel);
});
- assertWithMessage("Unknown error HAL exception when tuning")
+ expect.withMessage("Unknown error HAL exception when tuning")
.that(thrown).hasMessageThat().contains("UNKNOWN_ERROR");
}
@@ -520,7 +539,7 @@
mTunerSessions[0].step(/* directionDown= */ true, /* skipSubChannel= */ false);
});
- assertWithMessage("Exception for stepping when HAL is in invalid state")
+ expect.withMessage("Exception for stepping when HAL is in invalid state")
.that(thrown).hasMessageThat().contains("INVALID_STATE");
}
@@ -599,7 +618,7 @@
mTunerSessions[0].seek(/* directionDown= */ true, /* skipSubChannel= */ false);
});
- assertWithMessage("Internal error HAL exception when seeking")
+ expect.withMessage("Internal error HAL exception when seeking")
.that(thrown).hasMessageThat().contains("INTERNAL_ERROR");
}
@@ -636,7 +655,7 @@
mTunerSessions[0].cancel();
});
- assertWithMessage("Exception for canceling when HAL throws remote exception")
+ expect.withMessage("Exception for canceling when HAL throws remote exception")
.that(thrown).hasMessageThat().contains(exceptionMessage);
}
@@ -649,7 +668,7 @@
mTunerSessions[0].getImage(imageId);
});
- assertWithMessage("Get image exception")
+ expect.withMessage("Get image exception")
.that(thrown).hasMessageThat().contains("Image ID is missing");
}
@@ -660,7 +679,7 @@
Bitmap imageTest = mTunerSessions[0].getImage(imageId);
- assertWithMessage("Null image").that(imageTest).isEqualTo(null);
+ expect.withMessage("Null image").that(imageTest).isEqualTo(null);
}
@Test
@@ -674,7 +693,7 @@
mTunerSessions[0].getImage(/* id= */ 1);
});
- assertWithMessage("Exception for getting image when HAL throws remote exception")
+ expect.withMessage("Exception for getting image when HAL throws remote exception")
.that(thrown).hasMessageThat().contains(exceptionMessage);
}
@@ -702,18 +721,19 @@
openAidlClients(/* numClients= */ 1);
ProgramList.Filter filter = new ProgramList.Filter(new ArraySet<>(), new ArraySet<>(),
/* includeCategories= */ true, /* excludeModifications= */ false);
- ProgramFilter halFilter = ConversionUtils.filterToHalProgramFilter(filter);
- List<RadioManager.ProgramInfo> modified = List.of(TEST_FM_INFO, TEST_RDS_INFO);
- List<ProgramSelector.Identifier> removed = new ArrayList<>();
+ List<RadioManager.ProgramInfo> modified = List.of(TEST_FM_INFO, TEST_DAB_INFO,
+ TEST_DAB_INFO_ALT);
+ List<ProgramSelector.Identifier> halRemoved = new ArrayList<>();
+ List<UniqueProgramIdentifier> removed = new ArrayList<>();
ProgramListChunk halProgramList = AidlTestUtils.makeHalChunk(/* purge= */ true,
- /* complete= */ true, modified, removed);
+ /* complete= */ true, modified, halRemoved);
ProgramList.Chunk expectedProgramList =
AidlTestUtils.makeChunk(/* purge= */ true, /* complete= */ true, modified, removed);
mTunerSessions[0].startProgramListUpdates(filter);
mHalTunerCallback.onProgramListUpdated(halProgramList);
- verify(mBroadcastRadioMock).startProgramListUpdates(halFilter);
+ verifyHalProgramListUpdatesInvocation(filter);
verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT)
.onProgramListUpdated(expectedProgramList);
}
@@ -723,19 +743,23 @@
openAidlClients(/* numClients= */ 1);
ProgramList.Filter filter = new ProgramList.Filter(new ArraySet<>(), new ArraySet<>(),
/* includeCategories= */ true, /* excludeModifications= */ false);
+ List<RadioManager.ProgramInfo> modifiedInfo = List.of(TEST_FM_INFO, TEST_DAB_INFO,
+ TEST_DAB_INFO_ALT);
mTunerSessions[0].startProgramListUpdates(filter);
mHalTunerCallback.onProgramListUpdated(AidlTestUtils.makeHalChunk(/* purge= */ true,
- /* complete= */ true, List.of(TEST_FM_INFO, TEST_RDS_INFO), new ArrayList<>()));
+ /* complete= */ true, modifiedInfo, new ArrayList<>()));
verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onProgramListUpdated(
- AidlTestUtils.makeChunk(/* purge= */ true, /* complete= */ true,
- List.of(TEST_FM_INFO, TEST_RDS_INFO), new ArrayList<>()));
+ AidlTestUtils.makeChunk(/* purge= */ true, /* complete= */ true, modifiedInfo,
+ new ArrayList<>()));
mHalTunerCallback.onProgramListUpdated(AidlTestUtils.makeHalChunk(/* purge= */ false,
- /* complete= */ true, List.of(TEST_FM_INFO_MODIFIED), List.of(TEST_RDS_PI_ID)));
+ /* complete= */ true, List.of(TEST_FM_INFO_MODIFIED),
+ List.of(TEST_DAB_SID_EXT_ID)));
verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onProgramListUpdated(
AidlTestUtils.makeChunk(/* purge= */ false, /* complete= */ true,
- List.of(TEST_FM_INFO_MODIFIED), List.of(TEST_RDS_PI_ID)));
+ List.of(TEST_FM_INFO_MODIFIED),
+ List.of(TEST_DAB_UNIQUE_ID, TEST_DAB_UNIQUE_ID_ALT)));
}
@Test
@@ -743,17 +767,21 @@
openAidlClients(/* numClients= */ 1);
ProgramList.Filter filter = new ProgramList.Filter(new ArraySet<>(), new ArraySet<>(),
/* includeCategories= */ true, /* excludeModifications= */ false);
+ List<RadioManager.ProgramInfo> modifiedInfo = List.of(TEST_FM_INFO, TEST_DAB_INFO,
+ TEST_DAB_INFO_ALT);
mTunerSessions[0].startProgramListUpdates(filter);
mHalTunerCallback.onProgramListUpdated(AidlTestUtils.makeHalChunk(/* purge= */ true,
- /* complete= */ true, List.of(TEST_FM_INFO, TEST_RDS_INFO), new ArrayList<>()));
+ /* complete= */ true, modifiedInfo, new ArrayList<>()));
verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onProgramListUpdated(
- AidlTestUtils.makeChunk(/* purge= */ true, /* complete= */ true,
- List.of(TEST_FM_INFO, TEST_RDS_INFO), new ArrayList<>()));
+ AidlTestUtils.makeChunk(/* purge= */ true, /* complete= */ true, modifiedInfo,
+ new ArrayList<>()));
mHalTunerCallback.onProgramListUpdated(AidlTestUtils.makeHalChunk(/* purge= */ false,
- /* complete= */ true, List.of(TEST_FM_INFO_MODIFIED), List.of(TEST_RDS_PI_ID)));
+ /* complete= */ true, List.of(TEST_FM_INFO_MODIFIED),
+ List.of(TEST_DAB_SID_EXT_ID)));
verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onProgramListUpdated(
AidlTestUtils.makeChunk(/* purge= */ false, /* complete= */ true,
- List.of(TEST_FM_INFO_MODIFIED), List.of(TEST_RDS_PI_ID)));
+ List.of(TEST_FM_INFO_MODIFIED),
+ List.of(TEST_DAB_UNIQUE_ID, TEST_DAB_UNIQUE_ID_ALT)));
mTunerSessions[0].startProgramListUpdates(filter);
@@ -766,40 +794,44 @@
@Test
public void startProgramListUpdates_withNullFilter() throws Exception {
openAidlClients(/* numClients= */ 1);
+ List<RadioManager.ProgramInfo> modifiedInfo = List.of(TEST_FM_INFO, TEST_DAB_INFO,
+ TEST_DAB_INFO_ALT);
mTunerSessions[0].startProgramListUpdates(/* filter= */ null);
mHalTunerCallback.onProgramListUpdated(AidlTestUtils.makeHalChunk(/* purge= */ true,
- /* complete= */ true, List.of(TEST_FM_INFO, TEST_RDS_INFO), new ArrayList<>()));
+ /* complete= */ true, modifiedInfo, new ArrayList<>()));
verify(mBroadcastRadioMock).startProgramListUpdates(any());
verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onProgramListUpdated(
- AidlTestUtils.makeChunk(/* purge= */ true, /* complete= */ true,
- List.of(TEST_FM_INFO, TEST_RDS_INFO), new ArrayList<>()));
+ AidlTestUtils.makeChunk(/* purge= */ true, /* complete= */ true, modifiedInfo,
+ new ArrayList<>()));
mHalTunerCallback.onProgramListUpdated(AidlTestUtils.makeHalChunk(/* purge= */ false,
- /* complete= */ true, List.of(TEST_FM_INFO_MODIFIED), List.of(TEST_RDS_PI_ID)));
+ /* complete= */ true, List.of(TEST_FM_INFO_MODIFIED),
+ List.of(TEST_DAB_SID_EXT_ID)));
verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onProgramListUpdated(
AidlTestUtils.makeChunk(/* purge= */ false, /* complete= */ true,
- List.of(TEST_FM_INFO_MODIFIED), List.of(TEST_RDS_PI_ID)));
+ List.of(TEST_FM_INFO_MODIFIED),
+ List.of(TEST_DAB_UNIQUE_ID, TEST_DAB_UNIQUE_ID_ALT)));
}
@Test
public void startProgramListUpdates_withIdFilter() throws Exception {
openAidlClients(/* numClients= */ 1);
ProgramList.Filter idFilter = new ProgramList.Filter(new ArraySet<>(),
- Set.of(TEST_RDS_PI_ID), /* includeCategories= */ true,
+ Set.of(TEST_DAB_SID_EXT_ID), /* includeCategories= */ true,
/* excludeModifications= */ true);
- ProgramFilter halFilter = ConversionUtils.filterToHalProgramFilter(idFilter);
mTunerSessions[0].startProgramListUpdates(idFilter);
mHalTunerCallback.onProgramListUpdated(AidlTestUtils.makeHalChunk(/* purge= */ false,
- /* complete= */ true, List.of(TEST_RDS_INFO), new ArrayList<>()));
+ /* complete= */ true, List.of(TEST_DAB_INFO, TEST_DAB_INFO_ALT),
+ new ArrayList<>()));
- verify(mBroadcastRadioMock).startProgramListUpdates(halFilter);
+ verifyHalProgramListUpdatesInvocation(idFilter);
verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onProgramListUpdated(
AidlTestUtils.makeChunk(/* purge= */ false, /* complete= */ true,
- List.of(TEST_RDS_INFO), new ArrayList<>()));
+ List.of(TEST_DAB_INFO, TEST_DAB_INFO_ALT), new ArrayList<>()));
mHalTunerCallback.onProgramListUpdated(AidlTestUtils.makeHalChunk(/* purge= */ false,
/* complete= */ true, List.of(TEST_FM_INFO), new ArrayList<>()));
@@ -811,50 +843,52 @@
public void startProgramListUpdates_withFilterExcludingModifications() throws Exception {
openAidlClients(/* numClients= */ 1);
ProgramList.Filter filterExcludingModifications = new ProgramList.Filter(
- Set.of(ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY), new ArraySet<>(),
+ Set.of(ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT,
+ ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY), new ArraySet<>(),
/* includeCategories= */ true, /* excludeModifications= */ true);
- ProgramFilter halFilter =
- ConversionUtils.filterToHalProgramFilter(filterExcludingModifications);
mTunerSessions[0].startProgramListUpdates(filterExcludingModifications);
mHalTunerCallback.onProgramListUpdated(AidlTestUtils.makeHalChunk(/* purge= */ false,
- /* complete= */ true, List.of(TEST_FM_INFO), new ArrayList<>()));
+ /* complete= */ true, List.of(TEST_FM_INFO, TEST_DAB_INFO), new ArrayList<>()));
- verify(mBroadcastRadioMock).startProgramListUpdates(halFilter);
+ verifyHalProgramListUpdatesInvocation(filterExcludingModifications);
verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onProgramListUpdated(
AidlTestUtils.makeChunk(/* purge= */ false, /* complete= */ true,
- List.of(TEST_FM_INFO), new ArrayList<>()));
+ List.of(TEST_FM_INFO, TEST_DAB_INFO), new ArrayList<>()));
mHalTunerCallback.onProgramListUpdated(AidlTestUtils.makeHalChunk(/* purge= */ false,
- /* complete= */ true, List.of(TEST_FM_INFO_MODIFIED), new ArrayList<>()));
+ /* complete= */ true, List.of(TEST_FM_INFO_MODIFIED, TEST_DAB_INFO_ALT),
+ new ArrayList<>()));
- verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onProgramListUpdated(any());
+ verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onProgramListUpdated(
+ AidlTestUtils.makeChunk(/* purge= */ false, /* complete= */ true,
+ List.of(TEST_DAB_INFO_ALT), new ArrayList<>()));
}
@Test
public void startProgramListUpdates_withFilterIncludingModifications() throws Exception {
openAidlClients(/* numClients= */ 1);
ProgramList.Filter filterIncludingModifications = new ProgramList.Filter(
- Set.of(ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY), new ArraySet<>(),
+ Set.of(ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT,
+ ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY), new ArraySet<>(),
/* includeCategories= */ true, /* excludeModifications= */ false);
- ProgramFilter halFilter =
- ConversionUtils.filterToHalProgramFilter(filterIncludingModifications);
mTunerSessions[0].startProgramListUpdates(filterIncludingModifications);
mHalTunerCallback.onProgramListUpdated(AidlTestUtils.makeHalChunk(/* purge= */ false,
- /* complete= */ true, List.of(TEST_FM_INFO), new ArrayList<>()));
+ /* complete= */ true, List.of(TEST_FM_INFO, TEST_DAB_INFO), new ArrayList<>()));
- verify(mBroadcastRadioMock).startProgramListUpdates(halFilter);
+ verifyHalProgramListUpdatesInvocation(filterIncludingModifications);
verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onProgramListUpdated(
AidlTestUtils.makeChunk(/* purge= */ false, /* complete= */ true,
- List.of(TEST_FM_INFO), new ArrayList<>()));
+ List.of(TEST_FM_INFO, TEST_DAB_INFO), new ArrayList<>()));
mHalTunerCallback.onProgramListUpdated(AidlTestUtils.makeHalChunk(/* purge= */ false,
- /* complete= */ true, List.of(TEST_FM_INFO_MODIFIED), new ArrayList<>()));
+ /* complete= */ true, List.of(TEST_FM_INFO_MODIFIED, TEST_DAB_INFO_ALT),
+ new ArrayList<>()));
verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onProgramListUpdated(
AidlTestUtils.makeChunk(/* purge= */ false, /* complete= */ true,
- List.of(TEST_FM_INFO_MODIFIED), new ArrayList<>()));
+ List.of(TEST_FM_INFO_MODIFIED, TEST_DAB_INFO_ALT), new ArrayList<>()));
}
@Test
@@ -910,7 +944,7 @@
int numSessions = 3;
openAidlClients(numSessions);
List<ProgramList.Filter> filters = List.of(new ProgramList.Filter(
- Set.of(ProgramSelector.IDENTIFIER_TYPE_RDS_PI), new ArraySet<>(),
+ Set.of(ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT), new ArraySet<>(),
/* includeCategories= */ true, /* excludeModifications= */ false),
new ProgramList.Filter(new ArraySet<>(), Set.of(TEST_FM_FREQUENCY_ID),
/* includeCategories= */ false, /* excludeModifications= */ true),
@@ -922,18 +956,20 @@
}
mHalTunerCallback.onProgramListUpdated(AidlTestUtils.makeHalChunk(/* purge= */ false,
- /* complete= */ true, List.of(TEST_FM_INFO, TEST_RDS_INFO), new ArrayList<>()));
+ /* complete= */ true, List.of(TEST_FM_INFO, TEST_DAB_INFO, TEST_DAB_INFO_ALT),
+ new ArrayList<>()));
verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT)
.onProgramListUpdated(AidlTestUtils.makeChunk(/* purge= */ false,
- /* complete= */ true, List.of(TEST_RDS_INFO), new ArrayList<>()));
+ /* complete= */ true, List.of(TEST_DAB_INFO, TEST_DAB_INFO_ALT),
+ new ArrayList<>()));
verify(mAidlTunerCallbackMocks[1], CALLBACK_TIMEOUT)
.onProgramListUpdated(AidlTestUtils.makeChunk(/* purge= */ false,
/* complete= */ true, List.of(TEST_FM_INFO), new ArrayList<>()));
verify(mAidlTunerCallbackMocks[2], CALLBACK_TIMEOUT)
.onProgramListUpdated(AidlTestUtils.makeChunk(/* purge= */ false,
- /* complete= */ true, List.of(TEST_RDS_INFO, TEST_FM_INFO),
- new ArrayList<>()));
+ /* complete= */ true, List.of(TEST_DAB_INFO, TEST_DAB_INFO_ALT,
+ TEST_FM_INFO), new ArrayList<>()));
}
@Test
@@ -958,7 +994,7 @@
mTunerSessions[0].startProgramListUpdates(/* filter= */ null);
});
- assertWithMessage("Unknown error HAL exception when updating program list")
+ expect.withMessage("Unknown error HAL exception when updating program list")
.that(thrown).hasMessageThat().contains("UNKNOWN_ERROR");
}
@@ -995,7 +1031,7 @@
boolean isSupported = mTunerSessions[0].isConfigFlagSupported(flag);
verify(mBroadcastRadioMock).isConfigFlagSet(flag);
- assertWithMessage("Config flag %s is supported", flag).that(isSupported).isFalse();
+ expect.withMessage("Config flag %s is supported", flag).that(isSupported).isFalse();
}
@Test
@@ -1006,7 +1042,7 @@
boolean isSupported = mTunerSessions[0].isConfigFlagSupported(flag);
verify(mBroadcastRadioMock).isConfigFlagSet(flag);
- assertWithMessage("Config flag %s is supported", flag).that(isSupported).isTrue();
+ expect.withMessage("Config flag %s is supported", flag).that(isSupported).isTrue();
}
@Test
@@ -1018,7 +1054,7 @@
mTunerSessions[0].setConfigFlag(flag, /* value= */ true);
});
- assertWithMessage("Exception for setting unsupported flag %s", flag)
+ expect.withMessage("Exception for setting unsupported flag %s", flag)
.that(thrown).hasMessageThat().contains("setConfigFlag: NOT_SUPPORTED");
}
@@ -1063,7 +1099,7 @@
mTunerSessions[0].isConfigFlagSet(flag);
});
- assertWithMessage("Exception for checking if unsupported flag %s is set", flag)
+ expect.withMessage("Exception for checking if unsupported flag %s is set", flag)
.that(thrown).hasMessageThat().contains("isConfigFlagSet: NOT_SUPPORTED");
}
@@ -1076,7 +1112,7 @@
boolean isSet = mTunerSessions[0].isConfigFlagSet(flag);
- assertWithMessage("Config flag %s is set", flag)
+ expect.withMessage("Config flag %s is set", flag)
.that(isSet).isEqualTo(expectedConfigFlagValue);
}
@@ -1090,7 +1126,7 @@
mTunerSessions[0].isConfigFlagSet(flag);
});
- assertWithMessage("Exception for checking config flag when HAL throws remote exception")
+ expect.withMessage("Exception for checking config flag when HAL throws remote exception")
.that(thrown).hasMessageThat().contains("Failed to check flag");
}
@@ -1131,7 +1167,7 @@
mTunerSessions[0].setParameters(parametersSet);
});
- assertWithMessage("Exception for setting parameters when HAL throws remote exception")
+ expect.withMessage("Exception for setting parameters when HAL throws remote exception")
.that(thrown).hasMessageThat().contains(exceptionMessage);
}
@@ -1157,7 +1193,7 @@
mTunerSessions[0].getParameters(parameterKeys);
});
- assertWithMessage("Exception for getting parameters when HAL throws remote exception")
+ expect.withMessage("Exception for getting parameters when HAL throws remote exception")
.that(thrown).hasMessageThat().contains(exceptionMessage);
}
@@ -1264,4 +1300,24 @@
}
return seekFrequency;
}
+
+ private void verifyHalProgramListUpdatesInvocation(ProgramList.Filter filter) throws Exception {
+ ProgramFilter halFilterExpected = ConversionUtils.filterToHalProgramFilter(filter);
+ ArgumentCaptor<ProgramFilter> halFilterCaptor = ArgumentCaptor.forClass(
+ ProgramFilter.class);
+ verify(mBroadcastRadioMock).startProgramListUpdates(halFilterCaptor.capture());
+ ProgramFilter halFilterInvoked = halFilterCaptor.getValue();
+ expect.withMessage("Filtered identifier types").that(
+ halFilterInvoked.identifierTypes).asList().containsExactlyElementsIn(Arrays.stream(
+ halFilterExpected.identifierTypes).boxed().toArray(Integer[]::new));
+ expect.withMessage("Filtered identifiers").that(
+ halFilterInvoked.identifiers).asList()
+ .containsExactlyElementsIn(halFilterExpected.identifiers);
+ expect.withMessage("Categories-included filter")
+ .that(halFilterInvoked.includeCategories)
+ .isEqualTo(halFilterExpected.includeCategories);
+ expect.withMessage("Modifications-excluded filter")
+ .that(halFilterInvoked.excludeModifications)
+ .isEqualTo(halFilterExpected.excludeModifications);
+ }
}
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/ProgramInfoCacheTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/ProgramInfoCacheTest.java
index ec55ddb..fbb446b 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/ProgramInfoCacheTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/ProgramInfoCacheTest.java
@@ -21,12 +21,16 @@
import android.hardware.radio.ProgramList;
import android.hardware.radio.ProgramSelector;
import android.hardware.radio.RadioManager;
+import android.hardware.radio.UniqueProgramIdentifier;
import android.test.suitebuilder.annotation.MediumTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.server.broadcastradio.ExtendedRadioMockitoTestCase;
+import com.google.common.truth.Expect;
+
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -40,188 +44,221 @@
@RunWith(AndroidJUnit4.class)
@MediumTest
public class ProgramInfoCacheTest extends ExtendedRadioMockitoTestCase {
- private static final String TAG = "BroadcastRadioTests.ProgramInfoCache";
+ private static final int TEST_QUALITY = 1;
- private final ProgramSelector.Identifier mAmFmIdentifier =
- new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY, 88500);
- private final RadioManager.ProgramInfo mAmFmInfo = TestUtils.makeProgramInfo(
- ProgramSelector.PROGRAM_TYPE_FM, mAmFmIdentifier, 0);
+ private static final ProgramSelector.Identifier TEST_AM_FM_ID = new ProgramSelector.Identifier(
+ ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY, /* value= */ 88500);
+ private static final ProgramSelector TEST_AM_FM_SELECTOR = TestUtils.makeProgramSelector(
+ ProgramSelector.PROGRAM_TYPE_FM, TEST_AM_FM_ID);
+ private static final RadioManager.ProgramInfo TEST_AM_FM_INFO = TestUtils.makeProgramInfo(
+ TEST_AM_FM_SELECTOR, TEST_QUALITY);
- private final ProgramSelector.Identifier mRdsIdentifier =
- new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_RDS_PI, 15019);
- private final RadioManager.ProgramInfo mRdsInfo = TestUtils.makeProgramInfo(
- ProgramSelector.PROGRAM_TYPE_FM, mRdsIdentifier, 0);
+ private static final ProgramSelector.Identifier TEST_RDS_ID = new ProgramSelector.Identifier(
+ ProgramSelector.IDENTIFIER_TYPE_RDS_PI, /* value= */ 15019);
+ private static final ProgramSelector TEST_RDS_SELECTOR = TestUtils.makeProgramSelector(
+ ProgramSelector.PROGRAM_TYPE_FM, TEST_RDS_ID);
+ private static final RadioManager.ProgramInfo TEST_RDS_INFO = TestUtils.makeProgramInfo(
+ TEST_RDS_SELECTOR, TEST_QUALITY);
- private final ProgramSelector.Identifier mDabEnsembleIdentifier =
- new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_ENSEMBLE, 1337);
- private final RadioManager.ProgramInfo mDabEnsembleInfo = TestUtils.makeProgramInfo(
- ProgramSelector.PROGRAM_TYPE_DAB, mDabEnsembleIdentifier, 0);
+ private static final ProgramSelector TEST_HD_SELECTOR = TestUtils.makeProgramSelector(
+ ProgramSelector.PROGRAM_TYPE_FM_HD, new ProgramSelector.Identifier(
+ ProgramSelector.IDENTIFIER_TYPE_HD_STATION_ID_EXT,
+ /* value= */ 0x17C14100000001L));
- private final ProgramSelector.Identifier mVendorCustomIdentifier =
- new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_VENDOR_START, 9001);
- private final RadioManager.ProgramInfo mVendorCustomInfo = TestUtils.makeProgramInfo(
- ProgramSelector.PROGRAM_TYPE_VENDOR_START, mVendorCustomIdentifier, 0);
+ private static final ProgramSelector.Identifier TEST_DAB_SID_ID =
+ new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_SID_EXT,
+ /* value= */ 0xA000000111L);
+ private static final ProgramSelector.Identifier TEST_DAB_ENSEMBLE_ID =
+ new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_ENSEMBLE,
+ /* value= */ 0x1001);
+ private static final ProgramSelector.Identifier TEST_DAB_FREQUENCY_ID =
+ new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY,
+ /* value= */ 220_352);
+ private static final ProgramSelector TEST_DAB_SELECTOR = TestUtils.makeProgramSelector(
+ ProgramSelector.PROGRAM_TYPE_DAB, TEST_DAB_SID_ID,
+ new ProgramSelector.Identifier[]{TEST_DAB_FREQUENCY_ID, TEST_DAB_ENSEMBLE_ID});
+ private static final UniqueProgramIdentifier TEST_DAB_UNIQUE_ID =
+ new UniqueProgramIdentifier(TEST_DAB_SELECTOR);
+ private static final RadioManager.ProgramInfo TEST_DAB_INFO = TestUtils.makeProgramInfo(
+ TEST_DAB_SELECTOR, TEST_QUALITY);
+ private static final ProgramSelector.Identifier TEST_VENDOR_ID = new ProgramSelector.Identifier(
+ ProgramSelector.IDENTIFIER_TYPE_VENDOR_START, /* value= */ 9001);
+ private static final ProgramSelector TEST_VENDOR_SELECTOR = TestUtils.makeProgramSelector(
+ ProgramSelector.PROGRAM_TYPE_VENDOR_START, TEST_VENDOR_ID);
+ private static final UniqueProgramIdentifier TEST_VENDOR_UNIQUE_ID =
+ new UniqueProgramIdentifier(TEST_VENDOR_SELECTOR);
+ private static final RadioManager.ProgramInfo TEST_VENDOR_INFO = TestUtils.makeProgramInfo(
+ TEST_VENDOR_SELECTOR, TEST_QUALITY);
- // HAL-side ProgramInfoCache containing all of the above ProgramInfos.
- private final ProgramInfoCache mAllProgramInfos = new ProgramInfoCache(null, true, mAmFmInfo,
- mRdsInfo, mDabEnsembleInfo, mVendorCustomInfo);
+ private static final ProgramInfoCache FULL_PROGRAM_INFO_CACHE = new ProgramInfoCache(
+ /* filter= */ null, /* complete= */ true, TEST_AM_FM_INFO, TEST_RDS_INFO, TEST_DAB_INFO,
+ TEST_VENDOR_INFO);
+
+ @Rule
+ public final Expect expect = Expect.create();
@Test
public void testUpdateFromHal() {
// First test updating an incomplete cache with a purging, complete chunk.
- ProgramInfoCache cache = new ProgramInfoCache(null, false, mAmFmInfo);
+ ProgramInfoCache cache = new ProgramInfoCache(null, false, TEST_AM_FM_INFO);
ProgramListChunk chunk = new ProgramListChunk();
chunk.purge = true;
chunk.complete = true;
- chunk.modified.add(TestUtils.programInfoToHal(mRdsInfo));
- chunk.modified.add(TestUtils.programInfoToHal(mDabEnsembleInfo));
+ chunk.modified.add(TestUtils.programInfoToHal(TEST_RDS_INFO));
+ chunk.modified.add(TestUtils.programInfoToHal(TEST_DAB_INFO));
cache.updateFromHalProgramListChunk(chunk);
- assertTrue(cache.programInfosAreExactly(mRdsInfo, mDabEnsembleInfo));
+ expect.withMessage("Program info cache updated with a purging complete chunk")
+ .that(cache.toProgramInfoList()).containsExactly(TEST_RDS_INFO, TEST_DAB_INFO);
assertTrue(cache.isComplete());
// Then test a non-purging, incomplete chunk.
chunk.purge = false;
chunk.complete = false;
chunk.modified.clear();
- RadioManager.ProgramInfo updatedRdsInfo = TestUtils.makeProgramInfo(
- ProgramSelector.PROGRAM_TYPE_FM, mRdsIdentifier, 1);
+ RadioManager.ProgramInfo updatedRdsInfo = TestUtils.makeProgramInfo(TEST_RDS_SELECTOR, 1);
chunk.modified.add(TestUtils.programInfoToHal(updatedRdsInfo));
- chunk.modified.add(TestUtils.programInfoToHal(mVendorCustomInfo));
- chunk.removed.add(Convert.programIdentifierToHal(mDabEnsembleIdentifier));
+ chunk.modified.add(TestUtils.programInfoToHal(TEST_VENDOR_INFO));
+ chunk.removed.add(Convert.programIdentifierToHal(TEST_DAB_SID_ID));
cache.updateFromHalProgramListChunk(chunk);
- assertTrue(cache.programInfosAreExactly(updatedRdsInfo, mVendorCustomInfo));
+ expect.withMessage("Program info cache updated with non-puring incomplete chunk")
+ .that(cache.toProgramInfoList()).containsExactly(updatedRdsInfo, TEST_VENDOR_INFO);
assertFalse(cache.isComplete());
}
@Test
public void testNullFilter() {
ProgramInfoCache cache = new ProgramInfoCache(null, true);
- cache.filterAndUpdateFrom(mAllProgramInfos, false);
- assertTrue(cache.programInfosAreExactly(mAmFmInfo, mRdsInfo, mDabEnsembleInfo,
- mVendorCustomInfo));
+ cache.filterAndUpdateFrom(FULL_PROGRAM_INFO_CACHE, false);
+ expect.withMessage("Program info cache with null filter")
+ .that(cache.toProgramInfoList()).containsExactly(TEST_AM_FM_INFO, TEST_RDS_INFO,
+ TEST_DAB_INFO, TEST_VENDOR_INFO);
}
@Test
public void testEmptyFilter() {
ProgramInfoCache cache = new ProgramInfoCache(new ProgramList.Filter(new HashSet<Integer>(),
new HashSet<ProgramSelector.Identifier>(), true, false));
- cache.filterAndUpdateFrom(mAllProgramInfos, false);
- assertTrue(cache.programInfosAreExactly(mAmFmInfo, mRdsInfo, mDabEnsembleInfo,
- mVendorCustomInfo));
+ cache.filterAndUpdateFrom(FULL_PROGRAM_INFO_CACHE, false);
+ expect.withMessage("Program info cache with empty filter")
+ .that(cache.toProgramInfoList()).containsExactly(TEST_AM_FM_INFO, TEST_RDS_INFO,
+ TEST_DAB_INFO, TEST_VENDOR_INFO);
}
@Test
public void testFilterByType() {
HashSet<Integer> filterTypes = new HashSet<>();
filterTypes.add(ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY);
- filterTypes.add(ProgramSelector.IDENTIFIER_TYPE_DAB_ENSEMBLE);
+ filterTypes.add(ProgramSelector.IDENTIFIER_TYPE_DAB_SID_EXT);
ProgramInfoCache cache = new ProgramInfoCache(new ProgramList.Filter(filterTypes,
new HashSet<ProgramSelector.Identifier>(), true, false));
- cache.filterAndUpdateFrom(mAllProgramInfos, false);
- assertTrue(cache.programInfosAreExactly(mAmFmInfo, mDabEnsembleInfo));
+ cache.filterAndUpdateFrom(FULL_PROGRAM_INFO_CACHE, false);
+ expect.withMessage("Program info cache with type filter")
+ .that(cache.toProgramInfoList()).containsExactly(TEST_AM_FM_INFO, TEST_DAB_INFO);
}
@Test
public void testFilterByIdentifier() {
HashSet<ProgramSelector.Identifier> filterIds = new HashSet<>();
- filterIds.add(mRdsIdentifier);
- filterIds.add(mVendorCustomIdentifier);
+ filterIds.add(TEST_RDS_ID);
+ filterIds.add(TEST_VENDOR_ID);
ProgramInfoCache cache = new ProgramInfoCache(new ProgramList.Filter(new HashSet<Integer>(),
filterIds, true, false));
- cache.filterAndUpdateFrom(mAllProgramInfos, false);
- assertTrue(cache.programInfosAreExactly(mRdsInfo, mVendorCustomInfo));
+ cache.filterAndUpdateFrom(FULL_PROGRAM_INFO_CACHE, false);
+ expect.withMessage("Program info cache with identifier filter")
+ .that(cache.toProgramInfoList()).containsExactly(TEST_RDS_INFO, TEST_VENDOR_INFO);
}
@Test
public void testFilterExcludeCategories() {
ProgramInfoCache cache = new ProgramInfoCache(new ProgramList.Filter(new HashSet<Integer>(),
new HashSet<ProgramSelector.Identifier>(), false, false));
- cache.filterAndUpdateFrom(mAllProgramInfos, false);
- assertTrue(cache.programInfosAreExactly(mAmFmInfo, mRdsInfo));
+ cache.filterAndUpdateFrom(FULL_PROGRAM_INFO_CACHE, false);
+ expect.withMessage("Program info cache with filter excluding categories")
+ .that(cache.toProgramInfoList()).containsExactly(TEST_AM_FM_INFO, TEST_RDS_INFO,
+ TEST_DAB_INFO);
}
@Test
public void testPurgeUpdateChunks() {
- ProgramInfoCache cache = new ProgramInfoCache(null, false, mAmFmInfo);
+ ProgramInfoCache cache = new ProgramInfoCache(null, false, TEST_AM_FM_INFO);
List<ProgramList.Chunk> chunks =
- cache.filterAndUpdateFromInternal(mAllProgramInfos, true, 3, 3);
+ cache.filterAndUpdateFromInternal(FULL_PROGRAM_INFO_CACHE, true, 3, 3);
assertEquals(2, chunks.size());
verifyChunkListFlags(chunks, true, true);
- verifyChunkListModified(chunks, 3, mAmFmInfo, mRdsInfo, mDabEnsembleInfo,
- mVendorCustomInfo);
+ verifyChunkListModified(chunks, 3, TEST_AM_FM_INFO, TEST_RDS_INFO, TEST_DAB_INFO,
+ TEST_VENDOR_INFO);
verifyChunkListRemoved(chunks, 0);
}
@Test
public void testDeltaUpdateChunksModificationsIncluded() {
// Create a cache with a filter that allows modifications, and set its contents to
- // mAmFmInfo, mRdsInfo, mDabEnsembleInfo, and mVendorCustomInfo.
- ProgramInfoCache cache = new ProgramInfoCache(null, true, mAmFmInfo, mRdsInfo,
- mDabEnsembleInfo, mVendorCustomInfo);
+ // TEST_AM_FM_INFO, TEST_RDS_INFO, TEST_DAB_INFO, and TEST_VENDOR_INFO.
+ ProgramInfoCache cache = new ProgramInfoCache(null, true, TEST_AM_FM_INFO, TEST_RDS_INFO,
+ TEST_DAB_INFO, TEST_VENDOR_INFO);
// Create a HAL cache that:
// - Is complete.
- // - Retains mAmFmInfo.
- // - Replaces mRdsInfo with updatedRdsInfo.
- // - Drops mDabEnsembleInfo and mVendorCustomInfo.
- // - Introduces a new SXM info.
- RadioManager.ProgramInfo updatedRdsInfo = TestUtils.makeProgramInfo(
- ProgramSelector.PROGRAM_TYPE_FM, mRdsIdentifier, 1);
- RadioManager.ProgramInfo newSxmInfo = TestUtils.makeProgramInfo(
- ProgramSelector.PROGRAM_TYPE_SXM,
- new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_SXM_CHANNEL, 12345),
- 0);
- ProgramInfoCache halCache = new ProgramInfoCache(null, true, mAmFmInfo, updatedRdsInfo,
- newSxmInfo);
+ // - Retains TEST_AM_FM_INFO.
+ // - Replaces TEST_RDS_INFO with updatedRdsInfo.
+ // - Drops TEST_DAB_INFO and TEST_VENDOR_INFO.
+ // - Introduces a new HD info.
+ RadioManager.ProgramInfo updatedRdsInfo = TestUtils.makeProgramInfo(TEST_RDS_SELECTOR,
+ TEST_QUALITY + 1);
+ RadioManager.ProgramInfo newHdInfo = TestUtils.makeProgramInfo(TEST_HD_SELECTOR,
+ TEST_QUALITY);
+ ProgramInfoCache halCache = new ProgramInfoCache(null, true, TEST_AM_FM_INFO,
+ updatedRdsInfo, newHdInfo);
// Update the cache and verify:
// - The final chunk's complete flag is set.
- // - mAmFmInfo is retained and not reported in the chunks.
- // - updatedRdsInfo should appear as an update to mRdsInfo.
- // - newSxmInfo should appear as a new entry.
- // - mDabEnsembleInfo and mVendorCustomInfo should be reported as removed.
+ // - TEST_AM_FM_INFO is retained and not reported in the chunks.
+ // - updatedRdsInfo should appear as an update to TEST_RDS_INFO.
+ // - newHdInfo should appear as a new entry.
+ // - TEST_DAB_INFO and TEST_VENDOR_INFO should be reported as removed.
List<ProgramList.Chunk> chunks = cache.filterAndUpdateFromInternal(halCache, false, 5, 1);
- assertTrue(cache.programInfosAreExactly(mAmFmInfo, updatedRdsInfo, newSxmInfo));
+ expect.withMessage("Program info cache with modification included")
+ .that(cache.toProgramInfoList()).containsExactly(TEST_AM_FM_INFO, updatedRdsInfo,
+ newHdInfo);
assertEquals(2, chunks.size());
verifyChunkListFlags(chunks, false, true);
- verifyChunkListModified(chunks, 5, updatedRdsInfo, newSxmInfo);
- verifyChunkListRemoved(chunks, 1, mDabEnsembleIdentifier, mVendorCustomIdentifier);
+ verifyChunkListModified(chunks, 5, updatedRdsInfo, newHdInfo);
+ verifyChunkListRemoved(chunks, 1, TEST_DAB_UNIQUE_ID, TEST_VENDOR_UNIQUE_ID);
}
@Test
public void testDeltaUpdateChunksModificationsExcluded() {
// Create a cache with a filter that excludes modifications, and set its contents to
- // mAmFmInfo, mRdsInfo, mDabEnsembleInfo, and mVendorCustomInfo.
+ // TEST_AM_FM_INFO, TEST_RDS_INFO, TEST_DAB_INFO, and TEST_VENDOR_INFO.
ProgramInfoCache cache = new ProgramInfoCache(new ProgramList.Filter(new HashSet<Integer>(),
- new HashSet<ProgramSelector.Identifier>(), true, true), true, mAmFmInfo, mRdsInfo,
- mDabEnsembleInfo, mVendorCustomInfo);
+ new HashSet<ProgramSelector.Identifier>(), true, true), true,
+ TEST_AM_FM_INFO, TEST_RDS_INFO, TEST_DAB_INFO, TEST_VENDOR_INFO);
// Create a HAL cache that:
// - Is incomplete.
- // - Retains mAmFmInfo.
- // - Replaces mRdsInfo with updatedRdsInfo.
- // - Drops mDabEnsembleInfo and mVendorCustomInfo.
- // - Introduces a new SXM info.
- RadioManager.ProgramInfo updatedRdsInfo = TestUtils.makeProgramInfo(
- ProgramSelector.PROGRAM_TYPE_FM, mRdsIdentifier, 1);
- RadioManager.ProgramInfo newSxmInfo = TestUtils.makeProgramInfo(
- ProgramSelector.PROGRAM_TYPE_SXM,
- new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_SXM_CHANNEL, 12345),
- 0);
- ProgramInfoCache halCache = new ProgramInfoCache(null, false, mAmFmInfo, updatedRdsInfo,
- newSxmInfo);
+ // - Retains TEST_AM_FM_INFO.
+ // - Replaces TEST_RDS_INFO with updatedRdsInfo.
+ // - Drops TEST_DAB_INFO and TEST_VENDOR_INFO.
+ // - Introduces a new HD info.
+ RadioManager.ProgramInfo updatedRdsInfo = TestUtils.makeProgramInfo(TEST_RDS_SELECTOR, 1);
+ RadioManager.ProgramInfo newHdInfo = TestUtils.makeProgramInfo(TEST_HD_SELECTOR,
+ TEST_QUALITY);
+ ProgramInfoCache halCache = new ProgramInfoCache(null, false, TEST_AM_FM_INFO,
+ updatedRdsInfo, newHdInfo);
// Update the cache and verify:
// - All complete flags are false.
- // - mAmFmInfo and mRdsInfo are retained and not reported in the chunks.
- // - newSxmInfo should appear as a new entry.
- // - mDabEnsembleInfo and mVendorCustomInfo should be reported as removed.
+ // - TEST_AM_FM_INFO and TEST_RDS_INFO are retained and not reported in the chunks.
+ // - newHdInfo should appear as a new entry.
+ // - TEST_DAB_INFO and TEST_VENDOR_INFO should be reported as removed.
List<ProgramList.Chunk> chunks = cache.filterAndUpdateFromInternal(halCache, false, 5, 1);
- assertTrue(cache.programInfosAreExactly(mAmFmInfo, mRdsInfo, newSxmInfo));
+ expect.withMessage("Program info cache with modification excluded")
+ .that(cache.toProgramInfoList()).containsExactly(TEST_AM_FM_INFO, TEST_RDS_INFO,
+ newHdInfo);
assertEquals(2, chunks.size());
verifyChunkListFlags(chunks, false, false);
- verifyChunkListModified(chunks, 5, newSxmInfo);
- verifyChunkListRemoved(chunks, 1, mDabEnsembleIdentifier, mVendorCustomIdentifier);
+ verifyChunkListModified(chunks, 5, newHdInfo);
+ verifyChunkListRemoved(chunks, 1, TEST_DAB_UNIQUE_ID, TEST_VENDOR_UNIQUE_ID);
}
// Verifies that:
@@ -271,20 +308,21 @@
// - Each chunk's removed array has a similar number of elements.
// - Each element of expectedIdentifiers appears in a chunk.
private static void verifyChunkListRemoved(List<ProgramList.Chunk> chunks,
- int maxRemovedPerChunk, ProgramSelector.Identifier... expectedIdentifiers) {
+ int maxRemovedPerChunk,
+ UniqueProgramIdentifier... expectedIdentifiers) {
if (chunks.isEmpty()) {
assertEquals(0, expectedIdentifiers.length);
return;
}
- HashSet<ProgramSelector.Identifier> expectedSet = new HashSet<>();
- for (ProgramSelector.Identifier identifier : expectedIdentifiers) {
+ HashSet<UniqueProgramIdentifier> expectedSet = new HashSet<>();
+ for (UniqueProgramIdentifier identifier : expectedIdentifiers) {
expectedSet.add(identifier);
}
- HashSet<ProgramSelector.Identifier> actualSet = new HashSet<>();
+ HashSet<UniqueProgramIdentifier> actualSet = new HashSet<>();
int chunk0NumRemoved = chunks.get(0).getRemoved().size();
for (ProgramList.Chunk chunk : chunks) {
- Set<ProgramSelector.Identifier> chunkRemoved = chunk.getRemoved();
+ Set<UniqueProgramIdentifier> chunkRemoved = chunk.getRemoved();
assertTrue(chunkRemoved.size() <= maxRemovedPerChunk);
assertTrue(Math.abs(chunkRemoved.size() - chunk0NumRemoved) <= 1);
actualSet.addAll(chunkRemoved);
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/StartProgramListUpdatesFanoutTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/StartProgramListUpdatesFanoutTest.java
index 7d604d4..8c16d79 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/StartProgramListUpdatesFanoutTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/StartProgramListUpdatesFanoutTest.java
@@ -35,6 +35,7 @@
import android.hardware.radio.ProgramList;
import android.hardware.radio.ProgramSelector;
import android.hardware.radio.RadioManager;
+import android.hardware.radio.UniqueProgramIdentifier;
import android.os.RemoteException;
import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
@@ -72,22 +73,42 @@
private TunerSession[] mTunerSessions;
// Data objects used during tests
- private final ProgramSelector.Identifier mAmFmIdentifier =
- new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY, 88500);
- private final RadioManager.ProgramInfo mAmFmInfo = TestUtils.makeProgramInfo(
- ProgramSelector.PROGRAM_TYPE_FM, mAmFmIdentifier, 0);
- private final RadioManager.ProgramInfo mModifiedAmFmInfo = TestUtils.makeProgramInfo(
- ProgramSelector.PROGRAM_TYPE_FM, mAmFmIdentifier, 1);
+ private static final int TEST_QUALITY = 0;
+ private static final ProgramSelector.Identifier TEST_AM_FM_ID =
+ new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY,
+ /* value= */ 88_500);
+ private static final ProgramSelector TEST_AM_FM_SELECTOR = TestUtils.makeProgramSelector(
+ ProgramSelector.PROGRAM_TYPE_FM, TEST_AM_FM_ID);
+ private static final RadioManager.ProgramInfo TEST_AM_FM_INFO = TestUtils.makeProgramInfo(
+ TEST_AM_FM_SELECTOR, TEST_QUALITY);
+ private static final RadioManager.ProgramInfo TEST_AM_FM_MODIFIED_INFO =
+ TestUtils.makeProgramInfo(TEST_AM_FM_SELECTOR, TEST_QUALITY + 1);
- private final ProgramSelector.Identifier mRdsIdentifier =
- new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_RDS_PI, 15019);
- private final RadioManager.ProgramInfo mRdsInfo = TestUtils.makeProgramInfo(
- ProgramSelector.PROGRAM_TYPE_FM, mRdsIdentifier, 0);
+ private static final ProgramSelector.Identifier TEST_RDS_ID =
+ new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_RDS_PI,
+ /* value= */ 15_019);
+ private static final ProgramSelector TEST_RDS_SELECTOR = TestUtils.makeProgramSelector(
+ ProgramSelector.PROGRAM_TYPE_FM, TEST_RDS_ID);
- private final ProgramSelector.Identifier mDabEnsembleIdentifier =
- new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_ENSEMBLE, 1337);
- private final RadioManager.ProgramInfo mDabEnsembleInfo = TestUtils.makeProgramInfo(
- ProgramSelector.PROGRAM_TYPE_DAB, mDabEnsembleIdentifier, 0);
+ private static final UniqueProgramIdentifier TEST_RDS_UNIQUE_ID = new UniqueProgramIdentifier(
+ TEST_RDS_ID);
+ private static final RadioManager.ProgramInfo TEST_RDS_INFO = TestUtils.makeProgramInfo(
+ TEST_RDS_SELECTOR, TEST_QUALITY);
+
+ private static final ProgramSelector.Identifier TEST_DAB_SID_ID =
+ new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_SID_EXT,
+ /* value= */ 0xA000000111L);
+ private static final ProgramSelector.Identifier TEST_DAB_ENSEMBLE_ID =
+ new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_ENSEMBLE,
+ /* value= */ 0x1001);
+ private static final ProgramSelector.Identifier TEST_DAB_FREQUENCY_ID =
+ new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY,
+ /* value= */ 220_352);
+ private static final ProgramSelector TEST_DAB_SELECTOR = TestUtils.makeProgramSelector(
+ ProgramSelector.PROGRAM_TYPE_DAB, TEST_DAB_SID_ID,
+ new ProgramSelector.Identifier[]{TEST_DAB_FREQUENCY_ID, TEST_DAB_ENSEMBLE_ID});
+ private static final RadioManager.ProgramInfo TEST_DAB_INFO = TestUtils.makeProgramInfo(
+ TEST_DAB_SELECTOR, TEST_QUALITY);
@Override
protected void initializeSession(StaticMockitoSessionBuilder builder) {
@@ -126,18 +147,18 @@
// Initiate a program list update from the HAL side and verify both connected AIDL clients
// receive the update.
- updateHalProgramInfo(true, Arrays.asList(mAmFmInfo, mRdsInfo), null);
+ updateHalProgramInfo(true, Arrays.asList(TEST_AM_FM_INFO, TEST_RDS_INFO), null);
for (int i = 0; i < 2; i++) {
verifyAidlClientReceivedChunk(mAidlTunerCallbackMocks[i], true, Arrays.asList(
- mAmFmInfo, mRdsInfo), null);
+ TEST_AM_FM_INFO, TEST_RDS_INFO), null);
}
// Repeat with a non-purging update.
- updateHalProgramInfo(false, Arrays.asList(mModifiedAmFmInfo),
- Arrays.asList(mRdsIdentifier));
+ updateHalProgramInfo(false, Arrays.asList(TEST_AM_FM_MODIFIED_INFO),
+ Arrays.asList(TEST_RDS_ID));
for (int i = 0; i < 2; i++) {
verifyAidlClientReceivedChunk(mAidlTunerCallbackMocks[i], false,
- Arrays.asList(mModifiedAmFmInfo), Arrays.asList(mRdsIdentifier));
+ Arrays.asList(TEST_AM_FM_MODIFIED_INFO), Arrays.asList(TEST_RDS_UNIQUE_ID));
}
// Now start updates on the 3rd client. Verify the HAL function has not been called again
@@ -145,19 +166,19 @@
mTunerSessions[2].startProgramListUpdates(aidlFilter);
verify(mHalTunerSessionMock, times(1)).startProgramListUpdates(any());
verifyAidlClientReceivedChunk(mAidlTunerCallbackMocks[2], true,
- Arrays.asList(mModifiedAmFmInfo), null);
+ Arrays.asList(TEST_AM_FM_MODIFIED_INFO), null);
}
@Test
public void testFiltering() throws RemoteException {
// Open 4 clients that will use the following filters:
- // [0]: ID mRdsIdentifier, modifications excluded
+ // [0]: ID TEST_RDS_ID, modifications excluded
// [1]: No categories, modifications excluded
// [2]: Type IDENTIFIER_TYPE_AMFM_FREQUENCY, modifications excluded
// [3]: Type IDENTIFIER_TYPE_AMFM_FREQUENCY, modifications included
openAidlClients(4);
ProgramList.Filter idFilter = new ProgramList.Filter(new HashSet<Integer>(),
- new HashSet<ProgramSelector.Identifier>(Arrays.asList(mRdsIdentifier)), true, true);
+ new HashSet<ProgramSelector.Identifier>(Arrays.asList(TEST_RDS_ID)), true, true);
ProgramList.Filter categoryFilter = new ProgramList.Filter(new HashSet<Integer>(),
new HashSet<ProgramSelector.Identifier>(), false, true);
ProgramList.Filter typeFilterWithoutModifications = new ProgramList.Filter(
@@ -188,41 +209,40 @@
halFilter.excludeModifications = false;
verify(mHalTunerSessionMock, times(1)).startProgramListUpdates(halFilter);
- // Adding mRdsInfo should update clients [0] and [1].
- updateHalProgramInfo(false, Arrays.asList(mRdsInfo), null);
- verifyAidlClientReceivedChunk(mAidlTunerCallbackMocks[0], false, Arrays.asList(mRdsInfo),
- null);
- verifyAidlClientReceivedChunk(mAidlTunerCallbackMocks[1], false, Arrays.asList(mRdsInfo),
- null);
+ // Adding TEST_RDS_INFO should update clients [0] and [1].
+ updateHalProgramInfo(false, Arrays.asList(TEST_RDS_INFO), null);
+ verifyAidlClientReceivedChunk(mAidlTunerCallbackMocks[0], false,
+ Arrays.asList(TEST_RDS_INFO), null);
+ verifyAidlClientReceivedChunk(mAidlTunerCallbackMocks[1], false,
+ Arrays.asList(TEST_RDS_INFO), null);
- // Adding mAmFmInfo should update clients [1], [2], and [3].
- updateHalProgramInfo(false, Arrays.asList(mAmFmInfo), null);
- verifyAidlClientReceivedChunk(mAidlTunerCallbackMocks[1], false, Arrays.asList(mAmFmInfo),
- null);
- verifyAidlClientReceivedChunk(mAidlTunerCallbackMocks[2], false, Arrays.asList(mAmFmInfo),
- null);
- verifyAidlClientReceivedChunk(mAidlTunerCallbackMocks[3], false, Arrays.asList(mAmFmInfo),
- null);
-
- // Modifying mAmFmInfo to mModifiedAmFmInfo should update only [3].
- updateHalProgramInfo(false, Arrays.asList(mModifiedAmFmInfo), null);
+ // Adding TEST_AM_FM_INFO should update clients [1], [2], and [3].
+ updateHalProgramInfo(false, Arrays.asList(TEST_AM_FM_INFO), null);
+ verifyAidlClientReceivedChunk(mAidlTunerCallbackMocks[1], false,
+ Arrays.asList(TEST_AM_FM_INFO), null);
+ verifyAidlClientReceivedChunk(mAidlTunerCallbackMocks[2], false,
+ Arrays.asList(TEST_AM_FM_INFO), null);
verifyAidlClientReceivedChunk(mAidlTunerCallbackMocks[3], false,
- Arrays.asList(mModifiedAmFmInfo), null);
+ Arrays.asList(TEST_AM_FM_INFO), null);
- // Adding mDabEnsembleInfo should not update any client.
- updateHalProgramInfo(false, Arrays.asList(mDabEnsembleInfo), null);
+ // Modifying TEST_AM_FM_INFO to TEST_AM_FM_MODIFIED_INFO should update only [3].
+ updateHalProgramInfo(false, Arrays.asList(TEST_AM_FM_MODIFIED_INFO), null);
+ verifyAidlClientReceivedChunk(mAidlTunerCallbackMocks[3], false,
+ Arrays.asList(TEST_AM_FM_MODIFIED_INFO), null);
+
+ updateHalProgramInfo(false, Arrays.asList(TEST_DAB_INFO), null);
verify(mAidlTunerCallbackMocks[0], CB_TIMEOUT.times(1)).onProgramListUpdated(any());
- verify(mAidlTunerCallbackMocks[1], CB_TIMEOUT.times(2)).onProgramListUpdated(any());
+ verify(mAidlTunerCallbackMocks[1], CB_TIMEOUT.times(3)).onProgramListUpdated(any());
verify(mAidlTunerCallbackMocks[2], CB_TIMEOUT.times(2)).onProgramListUpdated(any());
verify(mAidlTunerCallbackMocks[3], CB_TIMEOUT.times(2)).onProgramListUpdated(any());
}
@Test
public void testClientClosing() throws RemoteException {
- // Open 2 clients that use different filters that are both sensitive to mAmFmIdentifier.
+ // Open 2 clients that use different filters that are both sensitive to TEST_AM_FM_ID.
openAidlClients(2);
ProgramList.Filter idFilter = new ProgramList.Filter(new HashSet<Integer>(),
- new HashSet<ProgramSelector.Identifier>(Arrays.asList(mAmFmIdentifier)), true,
+ new HashSet<ProgramSelector.Identifier>(Arrays.asList(TEST_AM_FM_ID)), true,
false);
ProgramList.Filter typeFilter = new ProgramList.Filter(
new HashSet<Integer>(Arrays.asList(ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY)),
@@ -237,23 +257,24 @@
halFilter.identifiers.clear();
verify(mHalTunerSessionMock, times(1)).startProgramListUpdates(halFilter);
- // Update the HAL with mAmFmInfo, and verify both clients are updated.
- updateHalProgramInfo(true, Arrays.asList(mAmFmInfo), null);
- verifyAidlClientReceivedChunk(mAidlTunerCallbackMocks[0], true, Arrays.asList(mAmFmInfo),
- null);
- verifyAidlClientReceivedChunk(mAidlTunerCallbackMocks[1], true, Arrays.asList(mAmFmInfo),
- null);
+ // Update the HAL with TEST_AM_FM_INFO, and verify both clients are updated.
+ updateHalProgramInfo(true, Arrays.asList(TEST_AM_FM_INFO), null);
+ verifyAidlClientReceivedChunk(mAidlTunerCallbackMocks[0], true,
+ Arrays.asList(TEST_AM_FM_INFO), null);
+ verifyAidlClientReceivedChunk(mAidlTunerCallbackMocks[1], true,
+ Arrays.asList(TEST_AM_FM_INFO), null);
// Stop updates on the first client and verify the HAL filter is updated.
mTunerSessions[0].stopProgramListUpdates();
verify(mHalTunerSessionMock, times(1)).startProgramListUpdates(Convert.programFilterToHal(
typeFilter));
- // Update the HAL with mModifiedAmFmInfo, and verify only the remaining client is updated.
- updateHalProgramInfo(true, Arrays.asList(mModifiedAmFmInfo), null);
+ // Update the HAL with TEST_AM_FM_MODIFIED_INFO, and verify only the remaining client is
+ // updated.
+ updateHalProgramInfo(true, Arrays.asList(TEST_AM_FM_MODIFIED_INFO), null);
verify(mAidlTunerCallbackMocks[0], CB_TIMEOUT.times(1)).onProgramListUpdated(any());
verifyAidlClientReceivedChunk(mAidlTunerCallbackMocks[1], true,
- Arrays.asList(mModifiedAmFmInfo), null);
+ Arrays.asList(TEST_AM_FM_MODIFIED_INFO), null);
// Close the other client without explicitly stopping updates, and verify HAL updates are
// stopped as well.
@@ -269,15 +290,15 @@
// Verify the AIDL client receives all types of updates (e.g. a new program, an update to
// that program, and a category).
- updateHalProgramInfo(true, Arrays.asList(mAmFmInfo, mRdsInfo), null);
+ updateHalProgramInfo(true, Arrays.asList(TEST_AM_FM_INFO, TEST_RDS_INFO), null);
verifyAidlClientReceivedChunk(mAidlTunerCallbackMocks[0], true, Arrays.asList(
- mAmFmInfo, mRdsInfo), null);
- updateHalProgramInfo(false, Arrays.asList(mModifiedAmFmInfo), null);
+ TEST_AM_FM_INFO, TEST_RDS_INFO), null);
+ updateHalProgramInfo(false, Arrays.asList(TEST_AM_FM_MODIFIED_INFO), null);
verifyAidlClientReceivedChunk(mAidlTunerCallbackMocks[0], false,
- Arrays.asList(mModifiedAmFmInfo), null);
- updateHalProgramInfo(false, Arrays.asList(mDabEnsembleInfo), null);
+ Arrays.asList(TEST_AM_FM_MODIFIED_INFO), null);
+ updateHalProgramInfo(false, Arrays.asList(TEST_DAB_INFO), null);
verifyAidlClientReceivedChunk(mAidlTunerCallbackMocks[0], false,
- Arrays.asList(mDabEnsembleInfo), null);
+ Arrays.asList(TEST_DAB_INFO), null);
// Verify closing the AIDL session also stops HAL updates.
mTunerSessions[0].close();
@@ -313,12 +334,12 @@
private void verifyAidlClientReceivedChunk(android.hardware.radio.ITunerCallback clientMock,
boolean purge, List<RadioManager.ProgramInfo> modified,
- List<ProgramSelector.Identifier> removed) throws RemoteException {
+ List<UniqueProgramIdentifier> removed) throws RemoteException {
HashSet<RadioManager.ProgramInfo> modifiedSet = new HashSet<>();
if (modified != null) {
modifiedSet.addAll(modified);
}
- HashSet<ProgramSelector.Identifier> removedSet = new HashSet<>();
+ HashSet<UniqueProgramIdentifier> removedSet = new HashSet<>();
if (removed != null) {
removedSet.addAll(removed);
}
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TestUtils.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TestUtils.java
index d4ca8d4..0b16141 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TestUtils.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TestUtils.java
@@ -45,6 +45,23 @@
static RadioManager.ProgramInfo makeProgramInfo(ProgramSelector selector,
ProgramSelector.Identifier logicallyTunedTo,
ProgramSelector.Identifier physicallyTunedTo, int signalQuality) {
+ if (logicallyTunedTo == null) {
+ logicallyTunedTo = selector.getPrimaryId();
+ }
+ if (physicallyTunedTo == null) {
+ if (selector.getPrimaryId().getType()
+ == ProgramSelector.IDENTIFIER_TYPE_DAB_SID_EXT) {
+ for (int i = 0; i < selector.getSecondaryIds().length; i++) {
+ if (selector.getSecondaryIds()[i].getType()
+ == ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY) {
+ physicallyTunedTo = selector.getSecondaryIds()[i];
+ break;
+ }
+ }
+ } else {
+ physicallyTunedTo = selector.getPrimaryId();
+ }
+ }
return new RadioManager.ProgramInfo(selector,
logicallyTunedTo, physicallyTunedTo, /* relatedContents= */ null,
/* infoFlags= */ 0, signalQuality,
@@ -52,14 +69,8 @@
}
static RadioManager.ProgramInfo makeProgramInfo(ProgramSelector selector, int signalQuality) {
- return makeProgramInfo(selector, selector.getPrimaryId(), selector.getPrimaryId(),
- signalQuality);
- }
-
- static RadioManager.ProgramInfo makeProgramInfo(int programType,
- ProgramSelector.Identifier identifier, int signalQuality) {
- return makeProgramInfo(makeProgramSelector(programType, identifier),
- /* logicallyTunedTo= */ null, /* physicallyTunedTo= */ null, signalQuality);
+ return makeProgramInfo(selector, /* logicallyTunedTo= */ null,
+ /* physicallyTunedTo= */ null, signalQuality);
}
static ProgramSelector makeFmSelector(long freq) {
@@ -70,8 +81,12 @@
static ProgramSelector makeProgramSelector(int programType,
ProgramSelector.Identifier identifier) {
- return new ProgramSelector(programType, identifier, /* secondaryIds= */ null,
- /* vendorIds= */ null);
+ return makeProgramSelector(programType, identifier, /* secondaryIds= */ null);
+ }
+
+ static ProgramSelector makeProgramSelector(int programType,
+ ProgramSelector.Identifier primaryId, ProgramSelector.Identifier[] secondaryIds) {
+ return new ProgramSelector(programType, primaryId, secondaryIds, /* vendorIds= */ null);
}
static ProgramInfo programInfoToHal(RadioManager.ProgramInfo info) {
@@ -79,6 +94,21 @@
// function only copies fields that are set by makeProgramInfo().
ProgramInfo hwInfo = new ProgramInfo();
hwInfo.selector = Convert.programSelectorToHal(info.getSelector());
+ hwInfo.logicallyTunedTo = hwInfo.selector.primaryId;
+ if (info.getSelector().getPrimaryId().getType()
+ == ProgramSelector.IDENTIFIER_TYPE_DAB_SID_EXT) {
+ for (int i = 0; i < info.getSelector().getSecondaryIds().length; i++) {
+ if (info.getSelector().getSecondaryIds()[i].getType()
+ == ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY) {
+ hwInfo.physicallyTunedTo = Convert.programIdentifierToHal(info.getSelector()
+ .getSecondaryIds()[i]);
+ break;
+ }
+ }
+ } else {
+ hwInfo.physicallyTunedTo = Convert.programIdentifierToHal(info.getSelector()
+ .getPrimaryId());
+ }
hwInfo.signalQuality = info.getSignalStrength();
return hwInfo;
}
diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
index 0778311..819178f 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
@@ -99,7 +99,7 @@
// The first sequence number to try with. Use a large number to avoid conflicts with the first a
// few sequence numbers the framework used to launch the test activity.
- private static final int BASE_SEQ = 10000;
+ private static final int BASE_SEQ = 10000000;
@Rule
public final ActivityTestRule<TestActivity> mActivityTestRule =
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 6057852..721a2db 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -3067,6 +3067,12 @@
"group": "WM_DEBUG_REMOTE_ANIMATIONS",
"at": "com\/android\/server\/wm\/RemoteAnimationController.java"
},
+ "643263584": {
+ "message": "Content Recording: Apply transformations of shift %d x %d, scale %f, crop %d x %d for display %d",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_CONTENT_RECORDING",
+ "at": "com\/android\/server\/wm\/ContentRecorder.java"
+ },
"644675193": {
"message": "Real start recents",
"level": "DEBUG",
diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java
index a4c655c8c..1ff5a3d 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -1779,7 +1779,7 @@
* If the bitmap's internal config is in one of the public formats, return
* that config, otherwise return null.
*/
- @NonNull
+ @Nullable
public final Config getConfig() {
if (mRecycled) {
Log.w(TAG, "Called getConfig() on a recycle()'d bitmap! This is undefined behavior!");
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
index 9b80063..4640106 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
@@ -245,8 +245,8 @@
private boolean shouldShowBackdrop(@NonNull TransitionInfo info,
@NonNull TransitionInfo.Change change) {
- final Animation a = loadAttributeAnimation(info.getType(), info, change,
- WALLPAPER_TRANSITION_NONE, mTransitionAnimation, false);
+ final Animation a = loadAttributeAnimation(info, change, WALLPAPER_TRANSITION_NONE,
+ mTransitionAnimation, false);
return a != null && a.getShowBackdrop();
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index 093ecb1..d10de83 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -131,6 +131,7 @@
private static final int EXPANDED_VIEW_ALPHA_ANIMATION_DURATION = 150;
+ /** Should be kept in sync with value in TaskbarScrimViewController. */
private static final float SCRIM_ALPHA = 0.32f;
/** Minimum alpha value for scrim when alpha is being changed via drag */
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 f90ee58..991b699 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
@@ -43,6 +43,7 @@
import android.app.ActivityTaskManager;
import android.app.PendingIntent;
import android.app.TaskInfo;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ShortcutInfo;
@@ -814,21 +815,22 @@
final String packageName1 = SplitScreenUtils.getPackageName(intent);
final String packageName2 = getPackageName(reverseSplitPosition(position));
final int userId2 = getUserId(reverseSplitPosition(position));
+ final ComponentName component = intent.getIntent().getComponent();
+
+ // To prevent accumulating large number of instances in the background, reuse task
+ // in the background. If we don't explicitly reuse, new may be created even if the app
+ // isn't multi-instance because WM won't automatically remove/reuse the previous instance
+ final ActivityManager.RecentTaskInfo taskInfo = mRecentTasksOptional
+ .map(recentTasks -> recentTasks.findTaskInBackground(component, userId1))
+ .orElse(null);
+ if (taskInfo != null) {
+ startTask(taskInfo.taskId, position, options);
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+ "Start task in background");
+ return;
+ }
if (samePackage(packageName1, packageName2, userId1, userId2)) {
if (supportMultiInstancesSplit(packageName1)) {
- // To prevent accumulating large number of instances in the background, reuse task
- // in the background with priority.
- final ActivityManager.RecentTaskInfo taskInfo = mRecentTasksOptional
- .map(recentTasks -> recentTasks.findTaskInBackground(
- intent.getIntent().getComponent(), userId1))
- .orElse(null);
- if (taskInfo != null) {
- startTask(taskInfo.taskId, position, options);
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
- "Start task in background");
- return;
- }
-
// Flag with MULTIPLE_TASK if this is launching the same activity into both sides of
// the split and there is no reusable background task.
fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index d310ae3..7df658e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -37,12 +37,8 @@
import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS;
import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED;
import static android.view.WindowManager.TRANSIT_CHANGE;
-import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE;
-import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_RELAUNCH;
-import static android.view.WindowManager.TRANSIT_TO_FRONT;
-import static android.window.TransitionInfo.FLAGS_IS_NON_APP_WINDOW;
import static android.window.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED;
import static android.window.TransitionInfo.FLAG_CROSS_PROFILE_OWNER_THUMBNAIL;
import static android.window.TransitionInfo.FLAG_CROSS_PROFILE_WORK_THUMBNAIL;
@@ -338,10 +334,6 @@
boolean isDisplayRotationAnimationStarted = false;
final boolean isDreamTransition = isDreamTransition(info);
final boolean isOnlyTranslucent = isOnlyTranslucent(info);
- final boolean isActivityReplace = checkActivityReplacement(info, startTransaction);
- // Some patterns (eg. activity "replacement") require us to re-interpret the type
- @WindowManager.TransitionType final int transitType =
- isActivityReplace ? TRANSIT_OPEN : info.getType();
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
final TransitionInfo.Change change = info.getChanges().get(i);
@@ -438,8 +430,7 @@
// Don't animate anything that isn't independent.
if (!TransitionInfo.isIndependent(change, info)) continue;
- Animation a = loadAnimation(transitType, info, change, wallpaperTransit,
- isDreamTransition);
+ Animation a = loadAnimation(info, change, wallpaperTransit, isDreamTransition);
if (a != null) {
if (isTask) {
final boolean isTranslucent = (change.getFlags() & FLAG_TRANSLUCENT) != 0;
@@ -613,53 +604,6 @@
return (translucentOpen + translucentClose) > 0;
}
- /**
- * Checks for an edge-case where an activity calls finish() followed immediately by
- * startActivity() to "replace" itself. If in this case, it will swap the layer of the
- * close/open activities and return `true`. This way, we pretend like we are just "opening"
- * the new activity.
- */
- private static boolean checkActivityReplacement(@NonNull TransitionInfo info,
- SurfaceControl.Transaction t) {
- if (info.getType() != TRANSIT_CLOSE) {
- return false;
- }
- int closing = -1;
- int opening = -1;
- for (int i = info.getChanges().size() - 1; i >= 0; --i) {
- final TransitionInfo.Change change = info.getChanges().get(i);
- if ((change.getTaskInfo() != null || change.hasFlags(FLAG_IS_DISPLAY))
- && !TransitionUtil.isOrderOnly(change)) {
- // This isn't an activity-level transition.
- return false;
- }
- if (change.getTaskInfo() != null
- && change.hasFlags(FLAG_IS_DISPLAY | FLAGS_IS_NON_APP_WINDOW)) {
- // Ignore non-activity containers.
- continue;
- }
- if (TransitionUtil.isClosingType(change.getMode())) {
- closing = i;
- } else if (change.getMode() == TRANSIT_OPEN) {
- // OPEN implies that it is a new launch. If going "back" the opening app will be
- // TO_FRONT
- opening = i;
- } else if (change.getMode() == TRANSIT_TO_FRONT) {
- // Normal "going back", so not a replacement.
- return false;
- }
- }
- if (closing < 0 || opening < 0) {
- return false;
- }
- // Swap the opening and closing z-orders since we're swapping the transit type.
- final int numChanges = info.getChanges().size();
- final int zSplitLine = numChanges + 1;
- t.setLayer(info.getChanges().get(opening).getLeash(), zSplitLine + numChanges - opening);
- t.setLayer(info.getChanges().get(closing).getLeash(), zSplitLine - closing);
- return true;
- }
-
@Override
public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
@@ -712,11 +656,12 @@
}
@Nullable
- private Animation loadAnimation(int type, @NonNull TransitionInfo info,
+ private Animation loadAnimation(@NonNull TransitionInfo info,
@NonNull TransitionInfo.Change change, int wallpaperTransit,
boolean isDreamTransition) {
Animation a;
+ final int type = info.getType();
final int flags = info.getFlags();
final int changeMode = change.getMode();
final int changeFlags = change.getFlags();
@@ -771,8 +716,8 @@
// If there's a scene-transition, then jump-cut.
return null;
} else {
- a = loadAttributeAnimation(type, info, change, wallpaperTransit, mTransitionAnimation,
- isDreamTransition);
+ a = loadAttributeAnimation(
+ info, change, wallpaperTransit, mTransitionAnimation, isDreamTransition);
}
if (a != null) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
index c99911d..d07d2b7b6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
@@ -45,7 +45,6 @@
import android.graphics.Shader;
import android.view.Surface;
import android.view.SurfaceControl;
-import android.view.WindowManager;
import android.view.animation.Animation;
import android.view.animation.Transformation;
import android.window.ScreenCapture;
@@ -62,10 +61,10 @@
/** Loads the animation that is defined through attribute id for the given transition. */
@Nullable
- public static Animation loadAttributeAnimation(@WindowManager.TransitionType int type,
- @NonNull TransitionInfo info, @NonNull TransitionInfo.Change change,
- int wallpaperTransit, @NonNull TransitionAnimation transitionAnimation,
- boolean isDreamTransition) {
+ public static Animation loadAttributeAnimation(@NonNull TransitionInfo info,
+ @NonNull TransitionInfo.Change change, int wallpaperTransit,
+ @NonNull TransitionAnimation transitionAnimation, boolean isDreamTransition) {
+ final int type = info.getType();
final int changeMode = change.getMode();
final int changeFlags = change.getFlags();
final boolean enter = TransitionUtil.isOpeningType(changeMode);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
index 568db91..99cd4f3 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
@@ -36,6 +36,7 @@
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -218,8 +219,7 @@
}
@Test
- public void startIntent_multiInstancesSupported_startTaskInBackgroundBeforeSplitActivated() {
- doReturn(true).when(mSplitScreenController).supportMultiInstancesSplit(any());
+ public void startIntent_multiInstancesNotSupported_startTaskInBackgroundBeforeSplitActivated() {
doNothing().when(mSplitScreenController).startTask(anyInt(), anyInt(), any());
Intent startIntent = createStartIntent("startActivity");
PendingIntent pendingIntent =
@@ -237,6 +237,8 @@
verify(mSplitScreenController).startTask(anyInt(), eq(SPLIT_POSITION_TOP_OR_LEFT),
isNull());
+ verify(mSplitScreenController, never()).supportMultiInstancesSplit(any());
+ verify(mStageCoordinator, never()).switchSplitPosition(any());
}
@Test
diff --git a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java
index e46db75..33907ec 100644
--- a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java
+++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java
@@ -344,7 +344,9 @@
continue;
}
final ProviderInfo providerInfo = resolved.providerInfo;
- final List<Bundle> entryData = getEntryDataFromProvider(context,
+ final List<Bundle> entryData = getEntryDataFromProvider(
+ // Build new context so the entry data is retrieved for the queried user.
+ context.createContextAsUser(user, 0 /* flags */),
providerInfo.authority);
if (entryData == null || entryData.isEmpty()) {
continue;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java
index 2086466..a8063e8 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java
@@ -35,6 +35,7 @@
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -52,6 +53,7 @@
import android.content.pm.ProviderInfo;
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
+import android.net.Uri;
import android.os.Bundle;
import android.os.UserHandle;
import android.os.UserManager;
@@ -69,6 +71,8 @@
import org.robolectric.annotation.Config;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
+import org.robolectric.shadow.api.Shadow;
+import org.robolectric.util.ReflectionHelpers;
import java.util.ArrayList;
import java.util.Arrays;
@@ -88,6 +92,10 @@
private UserManager mUserManager;
@Mock
private ContentResolver mContentResolver;
+ @Mock
+ private Context mUserContext;
+ @Mock
+ private ContentResolver mUserContentResolver;
private static final String URI_GET_SUMMARY = "content://authority/text/summary";
private static final String URI_GET_ICON = "content://authority/icon/my_icon";
@@ -104,6 +112,8 @@
mContentResolver = spy(application.getContentResolver());
when(mContext.getContentResolver()).thenReturn(mContentResolver);
when(mContext.getPackageName()).thenReturn("com.android.settings");
+ when(mUserContext.getContentResolver()).thenReturn(mUserContentResolver);
+ ShadowTileUtils.sCallRealEntryDataFromProvider = false;
}
@Test
@@ -375,6 +385,30 @@
}
@Test
+ public void loadTilesForAction_forUserProvider_getEntryDataFromProvider_inContextOfGivenUser() {
+ ShadowTileUtils.sCallRealEntryDataFromProvider = true;
+ UserHandle userHandle = new UserHandle(10);
+
+ doReturn(mUserContext).when(mContext).createContextAsUser(eq(userHandle), anyInt());
+
+ Map<Pair<String, String>, Tile> addedCache = new ArrayMap<>();
+ List<Tile> outTiles = new ArrayList<>();
+ List<ResolveInfo> info = new ArrayList<>();
+ ResolveInfo resolveInfo = newInfo(true, null /* category */, null, URI_GET_ICON,
+ URI_GET_SUMMARY, null, 123, PROFILE_ALL);
+ info.add(resolveInfo);
+
+ when(mPackageManager.queryIntentContentProvidersAsUser(any(Intent.class), anyInt(),
+ anyInt())).thenReturn(info);
+
+ TileUtils.loadTilesForAction(mContext, userHandle, IA_SETTINGS_ACTION,
+ addedCache, null /* defaultCategory */, outTiles, false /* requiresSettings */);
+
+ verify(mUserContentResolver, atLeastOnce())
+ .acquireUnstableProvider(any(Uri.class));
+ }
+
+ @Test
public void loadTilesForAction_withPendingIntent_updatesPendingIntentMap() {
Map<Pair<String, String>, Tile> addedCache = new ArrayMap<>();
List<Tile> outTiles = new ArrayList<>();
@@ -472,8 +506,17 @@
private static Bundle sMetaData;
+ private static boolean sCallRealEntryDataFromProvider;
+
@Implementation
protected static List<Bundle> getEntryDataFromProvider(Context context, String authority) {
+ if (sCallRealEntryDataFromProvider) {
+ return Shadow.directlyOn(
+ TileUtils.class,
+ "getEntryDataFromProvider",
+ ReflectionHelpers.ClassParameter.from(Context.class, context),
+ ReflectionHelpers.ClassParameter.from(String.class, authority));
+ }
return Arrays.asList(sMetaData);
}
diff --git a/packages/SystemUI/OWNERS b/packages/SystemUI/OWNERS
index a892269..78da5a6 100644
--- a/packages/SystemUI/OWNERS
+++ b/packages/SystemUI/OWNERS
@@ -6,6 +6,7 @@
aaliomer@google.com
aaronjli@google.com
+achalke@google.com
acul@google.com
adamcohen@google.com
aioana@google.com
@@ -72,6 +73,7 @@
patmanning@google.com
peanutbutter@google.com
peskal@google.com
+petrcermak@google.com
pinyaoting@google.com
pixel@google.com
pomini@google.com
@@ -82,13 +84,17 @@
shanh@google.com
snoeberger@google.com
steell@google.com
+stevenckng@google.com
stwu@google.com
syeonlee@google.com
sunnygoyal@google.com
thiruram@google.com
+tkachenkoi@google.com
tracyzhou@google.com
tsuji@google.com
twickham@google.com
+vadimt@google.com
+vanjan@google.com
victortulias@google.com
winsonc@google.com
wleshner@google.com
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/ribbon/ui/composable/Ribbon.kt b/packages/SystemUI/compose/features/src/com/android/systemui/ribbon/ui/composable/Ribbon.kt
new file mode 100644
index 0000000..daa1592
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/ribbon/ui/composable/Ribbon.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.ribbon.ui.composable
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.layout.layout
+import com.android.compose.modifiers.thenIf
+import kotlin.math.PI
+import kotlin.math.cos
+import kotlin.math.roundToInt
+import kotlin.math.sin
+import kotlin.math.tan
+
+/**
+ * Renders a "ribbon" at the bottom right corner of its container.
+ *
+ * The [content] is rendered leaning at an angle of [degrees] degrees (between `1` and `89`,
+ * inclusive), with an alpha of [alpha] (between `0f` and `1f`, inclusive).
+ *
+ * The background color of the strip can be modified by passing a value to the [backgroundColor] or
+ * `null` to remove the strip background.
+ *
+ * Note: this function assumes that it's been placed at the bottom right of its parent by its
+ * caller. It's the caller's responsibility to meet that assumption by actually placing this
+ * composable element at the bottom right.
+ */
+@Composable
+fun BottomRightCornerRibbon(
+ content: @Composable () -> Unit,
+ modifier: Modifier = Modifier,
+ degrees: Int = 45,
+ alpha: Float = 0.6f,
+ backgroundColor: Color? = Color.Red,
+) {
+ check(degrees in 1..89)
+ check(alpha in 0f..1f)
+
+ val radians = degrees * (PI / 180)
+
+ Box(
+ content = { content() },
+ modifier =
+ modifier
+ .graphicsLayer {
+ this.alpha = alpha
+
+ val w = size.width
+ val h = size.height
+
+ val sine = sin(radians).toFloat()
+ val cosine = cos(radians).toFloat()
+
+ translationX = (w - w * cosine + h * sine) / 2f
+ translationY = (h - w * sine + h * cosine) / 2f
+ rotationZ = 360f - degrees
+ }
+ .thenIf(backgroundColor != null) { Modifier.background(backgroundColor!!) }
+ .layout { measurable, constraints ->
+ val placeable = measurable.measure(constraints)
+
+ val tangent = tan(radians)
+ val leftPadding = (placeable.measuredHeight / tangent).roundToInt()
+ val rightPadding = (placeable.measuredHeight * tangent).roundToInt()
+
+ layout(
+ width = placeable.measuredWidth + leftPadding + rightPadding,
+ height = placeable.measuredHeight,
+ ) {
+ placeable.place(leftPadding, 0)
+ }
+ }
+ )
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
index 019287d..6a5a368 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
@@ -18,14 +18,19 @@
package com.android.systemui.scene.ui.composable
+import android.os.SystemProperties
+import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.PointerEventPass
import androidx.compose.ui.input.pointer.motionEventSpy
import androidx.compose.ui.input.pointer.pointerInput
@@ -37,6 +42,7 @@
import com.android.compose.animation.scene.Swipe
import com.android.compose.animation.scene.UserAction as SceneTransitionUserAction
import com.android.compose.animation.scene.observableTransitionState
+import com.android.systemui.ribbon.ui.composable.BottomRightCornerRibbon
import com.android.systemui.scene.shared.model.Direction
import com.android.systemui.scene.shared.model.ObservableTransitionState
import com.android.systemui.scene.shared.model.SceneKey
@@ -75,53 +81,70 @@
val currentDestinations: Map<UserAction, SceneModel> by
currentScene.destinationScenes().collectAsState()
val state = remember { SceneTransitionLayoutState(currentSceneKey.toTransitionSceneKey()) }
+ val isRibbonEnabled = remember { SystemProperties.getBoolean("flexi.ribbon", false) }
DisposableEffect(viewModel, state) {
viewModel.setTransitionState(state.observableTransitionState().map { it.toModel() })
onDispose { viewModel.setTransitionState(null) }
}
- SceneTransitionLayout(
- currentScene = currentSceneKey.toTransitionSceneKey(),
- onChangeScene = viewModel::onSceneChanged,
- transitions = SceneContainerTransitions,
- state = state,
- modifier =
- modifier
- .fillMaxSize()
- .motionEventSpy { event -> viewModel.onMotionEvent(event) }
- .pointerInput(Unit) {
- awaitPointerEventScope {
- while (true) {
- awaitPointerEvent(PointerEventPass.Final)
- viewModel.onMotionEventComplete()
+ Box(
+ modifier = Modifier.fillMaxSize(),
+ ) {
+ SceneTransitionLayout(
+ currentScene = currentSceneKey.toTransitionSceneKey(),
+ onChangeScene = viewModel::onSceneChanged,
+ transitions = SceneContainerTransitions,
+ state = state,
+ modifier =
+ modifier
+ .fillMaxSize()
+ .motionEventSpy { event -> viewModel.onMotionEvent(event) }
+ .pointerInput(Unit) {
+ awaitPointerEventScope {
+ while (true) {
+ awaitPointerEvent(PointerEventPass.Final)
+ viewModel.onMotionEventComplete()
+ }
}
}
- }
- ) {
- sceneByKey.forEach { (sceneKey, composableScene) ->
- scene(
- key = sceneKey.toTransitionSceneKey(),
- userActions =
- if (sceneKey == currentSceneKey) {
- currentDestinations
- } else {
- composableScene.destinationScenes().value
- }
- .map { (userAction, destinationSceneModel) ->
- toTransitionModels(userAction, destinationSceneModel)
- }
- .toMap(),
- ) {
- with(composableScene) {
- this@scene.Content(
- modifier =
- Modifier.element(sceneKey.toTransitionSceneKey().rootElementKey)
- .fillMaxSize(),
- )
+ ) {
+ sceneByKey.forEach { (sceneKey, composableScene) ->
+ scene(
+ key = sceneKey.toTransitionSceneKey(),
+ userActions =
+ if (sceneKey == currentSceneKey) {
+ currentDestinations
+ } else {
+ composableScene.destinationScenes().value
+ }
+ .map { (userAction, destinationSceneModel) ->
+ toTransitionModels(userAction, destinationSceneModel)
+ }
+ .toMap(),
+ ) {
+ with(composableScene) {
+ this@scene.Content(
+ modifier =
+ Modifier.element(sceneKey.toTransitionSceneKey().rootElementKey)
+ .fillMaxSize(),
+ )
+ }
}
}
}
+
+ if (isRibbonEnabled) {
+ BottomRightCornerRibbon(
+ content = {
+ Text(
+ text = "flexi\uD83E\uDD43",
+ color = Color.White,
+ )
+ },
+ modifier = Modifier.align(Alignment.BottomEnd),
+ )
+ }
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/statusbar/phone/SystemUIDialogFactoryExt.kt b/packages/SystemUI/compose/features/src/com/android/systemui/statusbar/phone/SystemUIDialogFactoryExt.kt
index 5d6dd3b..23d3089 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/statusbar/phone/SystemUIDialogFactoryExt.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/statusbar/phone/SystemUIDialogFactoryExt.kt
@@ -48,10 +48,11 @@
*/
fun SystemUIDialogFactory.create(
context: Context = this.applicationContext,
+ theme: Int = SystemUIDialog.DEFAULT_THEME,
dismissOnDeviceLock: Boolean = SystemUIDialog.DEFAULT_DISMISS_ON_DEVICE_LOCK,
content: @Composable (SystemUIDialog) -> Unit,
): ComponentSystemUIDialog {
- val dialog = create(context, dismissOnDeviceLock)
+ val dialog = create(context, theme, dismissOnDeviceLock)
// Create the dialog so that it is properly constructed before we set the Compose content.
// Otherwise, the ComposeView won't render properly.
diff --git a/packages/SystemUI/res/layout/media_projection_app_selector.xml b/packages/SystemUI/res/layout/media_projection_app_selector.xml
index e474938..b77f78d 100644
--- a/packages/SystemUI/res/layout/media_projection_app_selector.xml
+++ b/packages/SystemUI/res/layout/media_projection_app_selector.xml
@@ -20,10 +20,11 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
- androidprv:maxCollapsedHeight="0dp"
+ androidprv:maxCollapsedHeight="10000dp"
androidprv:maxCollapsedHeightSmall="56dp"
androidprv:maxWidth="@*android:dimen/chooser_width"
android:id="@*android:id/contentPanel">
+ <!-- maxCollapsedHeight above is huge, to make sure the layout is always expanded. -->
<LinearLayout
android:id="@*android:id/chooser_header"
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt
index daff5fe..aa33100 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt
@@ -16,13 +16,19 @@
package com.android.systemui.biometrics.data.repository
+import android.Manifest.permission.USE_BIOMETRIC_INTERNAL
+import android.annotation.RequiresPermission
+import android.hardware.biometrics.ComponentInfoInternal
import android.hardware.biometrics.SensorLocationInternal
+import android.hardware.biometrics.SensorProperties
import android.hardware.fingerprint.FingerprintManager
+import android.hardware.fingerprint.FingerprintSensorProperties
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
import com.android.systemui.biometrics.shared.model.SensorStrength
import com.android.systemui.biometrics.shared.model.toSensorStrength
+import com.android.systemui.biometrics.shared.model.toSensorType
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
@@ -31,11 +37,10 @@
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.flow.shareIn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
/**
* A repository for the global state of FingerprintProperty.
@@ -44,22 +49,17 @@
*/
interface FingerprintPropertyRepository {
- /**
- * If the repository is initialized or not. Other properties are defaults until this is true.
- */
- val isInitialized: Flow<Boolean>
-
/** The id of fingerprint sensor. */
- val sensorId: StateFlow<Int>
+ val sensorId: Flow<Int>
/** The security strength of sensor (convenience, weak, strong). */
- val strength: StateFlow<SensorStrength>
+ val strength: Flow<SensorStrength>
/** The types of fingerprint sensor (rear, ultrasonic, optical, etc.). */
- val sensorType: StateFlow<FingerprintSensorType>
+ val sensorType: Flow<FingerprintSensorType>
/** The sensor location relative to each physical display. */
- val sensorLocations: StateFlow<Map<String, SensorLocationInternal>>
+ val sensorLocations: Flow<Map<String, SensorLocationInternal>>
}
@SysUISingleton
@@ -70,64 +70,64 @@
private val fingerprintManager: FingerprintManager?,
) : FingerprintPropertyRepository {
- override val isInitialized: Flow<Boolean> =
+ @RequiresPermission(USE_BIOMETRIC_INTERNAL)
+ private val props: StateFlow<FingerprintSensorPropertiesInternal> =
conflatedCallbackFlow {
val callback =
object : IFingerprintAuthenticatorsRegisteredCallback.Stub() {
override fun onAllAuthenticatorsRegistered(
sensors: List<FingerprintSensorPropertiesInternal>
) {
- if (sensors.isNotEmpty()) {
- setProperties(sensors[0])
- trySendWithFailureLogging(true, TAG, "initialize properties")
+ if (sensors.isEmpty()) {
+ trySendWithFailureLogging(
+ DEFAULT_PROPS,
+ TAG,
+ "no registered sensors, use default props"
+ )
+ } else {
+ trySendWithFailureLogging(
+ sensors[0],
+ TAG,
+ "update properties on authenticators registered"
+ )
}
}
}
fingerprintManager?.addAuthenticatorsRegisteredCallback(callback)
- trySendWithFailureLogging(false, TAG, "initial value defaulting to false")
awaitClose {}
}
- .shareIn(scope = applicationScope, started = SharingStarted.Eagerly, replay = 1)
+ .stateIn(
+ applicationScope,
+ started = SharingStarted.Eagerly,
+ initialValue = DEFAULT_PROPS,
+ )
- private val _sensorId: MutableStateFlow<Int> = MutableStateFlow(-1)
- override val sensorId: StateFlow<Int> = _sensorId.asStateFlow()
+ override val sensorId: Flow<Int> = props.map { it.sensorId }
- private val _strength: MutableStateFlow<SensorStrength> =
- MutableStateFlow(SensorStrength.CONVENIENCE)
- override val strength = _strength.asStateFlow()
+ override val strength: Flow<SensorStrength> = props.map { it.sensorStrength.toSensorStrength() }
- private val _sensorType: MutableStateFlow<FingerprintSensorType> =
- MutableStateFlow(FingerprintSensorType.UNKNOWN)
- override val sensorType = _sensorType.asStateFlow()
+ override val sensorType: Flow<FingerprintSensorType> =
+ props.map { it.sensorType.toSensorType() }
- private val _sensorLocations: MutableStateFlow<Map<String, SensorLocationInternal>> =
- MutableStateFlow(mapOf("" to SensorLocationInternal.DEFAULT))
- override val sensorLocations: StateFlow<Map<String, SensorLocationInternal>> =
- _sensorLocations.asStateFlow()
-
- private fun setProperties(prop: FingerprintSensorPropertiesInternal) {
- _sensorId.value = prop.sensorId
- _strength.value = prop.sensorStrength.toSensorStrength()
- _sensorType.value = sensorTypeIntToObject(prop.sensorType)
- _sensorLocations.value =
- prop.allLocations.associateBy { sensorLocationInternal ->
+ override val sensorLocations: Flow<Map<String, SensorLocationInternal>> =
+ props.map {
+ it.allLocations.associateBy { sensorLocationInternal ->
sensorLocationInternal.displayId
}
- }
+ }
companion object {
private const val TAG = "FingerprintPropertyRepositoryImpl"
- }
-}
-
-private fun sensorTypeIntToObject(value: Int): FingerprintSensorType {
- return when (value) {
- 0 -> FingerprintSensorType.UNKNOWN
- 1 -> FingerprintSensorType.REAR
- 2 -> FingerprintSensorType.UDFPS_ULTRASONIC
- 3 -> FingerprintSensorType.UDFPS_OPTICAL
- 4 -> FingerprintSensorType.POWER_BUTTON
- 5 -> FingerprintSensorType.HOME_BUTTON
- else -> throw IllegalArgumentException("Invalid SensorType value: $value")
+ private val DEFAULT_PROPS =
+ FingerprintSensorPropertiesInternal(
+ -1 /* sensorId */,
+ SensorProperties.STRENGTH_CONVENIENCE,
+ 0 /* maxEnrollmentsPerUser */,
+ listOf<ComponentInfoInternal>(),
+ FingerprintSensorProperties.TYPE_UNKNOWN,
+ false /* halControlsIllumination */,
+ true /* resetLockoutRequiresHardwareAuthToken */,
+ listOf<SensorLocationInternal>(SensorLocationInternal.DEFAULT)
+ )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt
index 5badcaf..a6ad24e 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt
@@ -32,7 +32,6 @@
import com.android.systemui.dagger.SysUISingleton
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
@@ -69,7 +68,7 @@
val isConfirmationRequired: Flow<Boolean>
/** Fingerprint sensor type */
- val sensorType: StateFlow<FingerprintSensorType>
+ val sensorType: Flow<FingerprintSensorType>
/** Use biometrics for authentication. */
fun useBiometricsForAuthentication(
@@ -95,7 +94,7 @@
class PromptSelectorInteractorImpl
@Inject
constructor(
- private val fingerprintPropertyRepository: FingerprintPropertyRepository,
+ fingerprintPropertyRepository: FingerprintPropertyRepository,
private val promptRepository: PromptRepository,
lockPatternUtils: LockPatternUtils,
) : PromptSelectorInteractor {
@@ -147,8 +146,7 @@
}
}
- override val sensorType: StateFlow<FingerprintSensorType> =
- fingerprintPropertyRepository.sensorType
+ override val sensorType: Flow<FingerprintSensorType> = fingerprintPropertyRepository.sensorType
override fun useBiometricsForAuthentication(
promptInfo: PromptInfo,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractor.kt
index aa85e5f3..75ae061 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractor.kt
@@ -17,32 +17,43 @@
package com.android.systemui.biometrics.domain.interactor
import android.hardware.biometrics.SensorLocationInternal
-import android.util.Log
import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository
import com.android.systemui.dagger.SysUISingleton
import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.combine
/** Business logic for SideFps overlay offsets. */
interface SideFpsOverlayInteractor {
- /** Get the corresponding offsets based on different displayId. */
- fun getOverlayOffsets(displayId: String): SensorLocationInternal
+ /** The displayId of the current display. */
+ val displayId: Flow<String>
+
+ /** Overlay offsets corresponding to given displayId. */
+ val overlayOffsets: Flow<SensorLocationInternal>
+
+ /** Called on display changes, used to keep the display state in sync */
+ fun onDisplayChanged(displayId: String)
}
@SysUISingleton
class SideFpsOverlayInteractorImpl
@Inject
-constructor(private val fingerprintPropertyRepository: FingerprintPropertyRepository) :
+constructor(fingerprintPropertyRepository: FingerprintPropertyRepository) :
SideFpsOverlayInteractor {
- override fun getOverlayOffsets(displayId: String): SensorLocationInternal {
- val offsets = fingerprintPropertyRepository.sensorLocations.value
- return if (offsets.containsKey(displayId)) {
- offsets[displayId]!!
- } else {
- Log.w(TAG, "No location specified for display: $displayId")
- offsets[""]!!
+ private val _displayId: MutableStateFlow<String> = MutableStateFlow("")
+ override val displayId: Flow<String> = _displayId.asStateFlow()
+
+ override val overlayOffsets: Flow<SensorLocationInternal> =
+ combine(displayId, fingerprintPropertyRepository.sensorLocations) { displayId, offsets ->
+ offsets[displayId] ?: SensorLocationInternal.DEFAULT
}
+
+ override fun onDisplayChanged(displayId: String) {
+ _displayId.value = displayId
}
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/FingerprintSensorType.kt b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/FingerprintSensorType.kt
index df5cefd..c6fdcb3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/FingerprintSensorType.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/FingerprintSensorType.kt
@@ -27,3 +27,15 @@
POWER_BUTTON,
HOME_BUTTON,
}
+
+/** Convert [this] to corresponding [FingerprintSensorType] */
+fun Int.toSensorType(): FingerprintSensorType =
+ when (this) {
+ FingerprintSensorProperties.TYPE_UNKNOWN -> FingerprintSensorType.UNKNOWN
+ FingerprintSensorProperties.TYPE_REAR -> FingerprintSensorType.REAR
+ FingerprintSensorProperties.TYPE_UDFPS_ULTRASONIC -> FingerprintSensorType.UDFPS_ULTRASONIC
+ FingerprintSensorProperties.TYPE_UDFPS_OPTICAL -> FingerprintSensorType.UDFPS_OPTICAL
+ FingerprintSensorProperties.TYPE_POWER_BUTTON -> FingerprintSensorType.POWER_BUTTON
+ FingerprintSensorProperties.TYPE_HOME_BUTTON -> FingerprintSensorType.HOME_BUTTON
+ else -> throw IllegalArgumentException("Invalid SensorType value: $this")
+ }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/SensorStrength.kt b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/SensorStrength.kt
index 30e865e..476daac 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/SensorStrength.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/SensorStrength.kt
@@ -28,8 +28,8 @@
/** Convert [this] to corresponding [SensorStrength] */
fun Int.toSensorStrength(): SensorStrength =
when (this) {
- 0 -> SensorStrength.CONVENIENCE
- 1 -> SensorStrength.WEAK
- 2 -> SensorStrength.STRONG
+ SensorProperties.STRENGTH_CONVENIENCE -> SensorStrength.CONVENIENCE
+ SensorProperties.STRENGTH_WEAK -> SensorStrength.WEAK
+ SensorProperties.STRENGTH_STRONG -> SensorStrength.STRONG
else -> throw IllegalArgumentException("Invalid SensorStrength value: $this")
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptFingerprintIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptFingerprintIconViewModel.kt
index 9b30acb..b406ea4 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptFingerprintIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptFingerprintIconViewModel.kt
@@ -33,7 +33,7 @@
@Inject
constructor(
private val displayStateInteractor: DisplayStateInteractor,
- private val promptSelectorInteractor: PromptSelectorInteractor,
+ promptSelectorInteractor: PromptSelectorInteractor,
) {
/** Current device rotation. */
private var rotation: Int = Surface.ROTATION_0
diff --git a/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt b/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt
index ecc9d0e..f730935 100644
--- a/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt
@@ -34,7 +34,8 @@
context: Context,
private val onStartMirroringClickListener: View.OnClickListener,
private val onCancelMirroring: View.OnClickListener,
-) : Dialog(context, R.style.Theme_SystemUI_Dialog) {
+ theme: Int = R.style.Theme_SystemUI_Dialog,
+) : Dialog(context, theme) {
private lateinit var mirrorButton: TextView
private lateinit var dismissButton: TextView
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 1e5fcbe..2cc07fd 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -802,4 +802,8 @@
/** Enable haptic slider component in the brightness slider */
@JvmField
val HAPTIC_BRIGHTNESS_SLIDER = unreleasedFlag("haptic_brightness_slider")
+
+ // TODO(b/287205379): Tracking bug
+ @JvmField
+ val QS_CONTAINER_GRAPH_OPTIMIZER = unreleasedFlag( "qs_container_graph_optimizer")
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialog.kt b/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialog.kt
index b5b56b2..6f25f7c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialog.kt
@@ -44,7 +44,8 @@
context: Context,
initialCurrentLevel: Int,
initialMaxLevel: Int,
-) : Dialog(context, R.style.Theme_SystemUI_Dialog) {
+ theme: Int = R.style.Theme_SystemUI_Dialog,
+) : Dialog(context, theme) {
private data class RootProperties(
val cornerRadius: Float,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
index 8954947..b82e01b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
@@ -50,7 +50,6 @@
import com.android.systemui.log.FaceAuthenticationLogger
import com.android.systemui.log.SessionTracker
import com.android.systemui.log.table.TableLogBuffer
-import com.android.systemui.log.table.logDiffsForTable
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.user.data.model.SelectionStatus
import com.android.systemui.user.data.repository.UserRepository
@@ -66,9 +65,10 @@
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flowOf
@@ -77,6 +77,7 @@
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@@ -153,7 +154,7 @@
private val faceAuthLogger: FaceAuthenticationLogger,
private val biometricSettingsRepository: BiometricSettingsRepository,
private val deviceEntryFingerprintAuthRepository: DeviceEntryFingerprintAuthRepository,
- private val trustRepository: TrustRepository,
+ trustRepository: TrustRepository,
private val keyguardRepository: KeyguardRepository,
private val keyguardInteractor: KeyguardInteractor,
private val alternateBouncerInteractor: AlternateBouncerInteractor,
@@ -202,11 +203,9 @@
private val keyguardSessionId: InstanceId?
get() = sessionTracker.getSessionId(StatusBarManager.SESSION_KEYGUARD)
- private val _canRunFaceAuth = MutableStateFlow(false)
override val canRunFaceAuth: StateFlow<Boolean>
- get() = _canRunFaceAuth
- private val canRunDetection = MutableStateFlow(false)
+ private val canRunDetection: StateFlow<Boolean>
private val _isAuthenticated = MutableStateFlow(false)
override val isAuthenticated: Flow<Boolean>
@@ -252,10 +251,58 @@
dumpManager.registerCriticalDumpable("DeviceEntryFaceAuthRepositoryImpl", this)
if (featureFlags.isEnabled(Flags.FACE_AUTH_REFACTOR)) {
+ canRunFaceAuth =
+ listOf(
+ *gatingConditionsForAuthAndDetect(),
+ Pair(isLockedOut.isFalse(), "isNotInLockOutState"),
+ Pair(
+ trustRepository.isCurrentUserTrusted.isFalse(),
+ "currentUserIsNotTrusted"
+ ),
+ Pair(
+ biometricSettingsRepository.isFaceAuthCurrentlyAllowed,
+ "isFaceAuthCurrentlyAllowed"
+ ),
+ Pair(isAuthenticated.isFalse(), "faceNotAuthenticated"),
+ )
+ .andAllFlows("canFaceAuthRun", faceAuthLog)
+ .flowOn(mainDispatcher)
+ .stateIn(applicationScope, SharingStarted.Eagerly, false)
+
+ // Face detection can run only when lockscreen bypass is enabled
+ // & detection is supported
+ // & biometric unlock is not allowed
+ // or user is trusted by trust manager & we want to run face detect to dismiss
+ // keyguard
+ canRunDetection =
+ listOf(
+ *gatingConditionsForAuthAndDetect(),
+ Pair(isBypassEnabled, "isBypassEnabled"),
+ Pair(
+ biometricSettingsRepository.isFaceAuthCurrentlyAllowed
+ .isFalse()
+ .or(trustRepository.isCurrentUserTrusted),
+ "faceAuthIsNotCurrentlyAllowedOrCurrentUserIsTrusted"
+ ),
+ // We don't want to run face detect if fingerprint can be used to unlock the
+ // device
+ // but it's not possible to authenticate with FP from the bouncer (UDFPS)
+ Pair(
+ and(isUdfps(), deviceEntryFingerprintAuthRepository.isRunning)
+ .isFalse(),
+ "udfpsAuthIsNotPossibleAnymore"
+ )
+ )
+ .andAllFlows("canFaceDetectRun", faceDetectLog)
+ .flowOn(mainDispatcher)
+ .stateIn(applicationScope, SharingStarted.Eagerly, false)
observeFaceAuthGatingChecks()
observeFaceDetectGatingChecks()
observeFaceAuthResettingConditions()
listenForSchedulingWatchdog()
+ } else {
+ canRunFaceAuth = MutableStateFlow(false).asStateFlow()
+ canRunDetection = MutableStateFlow(false).asStateFlow()
}
}
@@ -298,39 +345,13 @@
}
private fun observeFaceDetectGatingChecks() {
- // Face detection can run only when lockscreen bypass is enabled
- // & detection is supported
- // & biometric unlock is not allowed
- // or user is trusted by trust manager & we want to run face detect to dismiss keyguard
- listOf(
- canFaceAuthOrDetectRun(faceDetectLog),
- logAndObserve(isBypassEnabled, "isBypassEnabled", faceDetectLog),
- logAndObserve(
- biometricSettingsRepository.isFaceAuthCurrentlyAllowed
- .isFalse()
- .or(trustRepository.isCurrentUserTrusted),
- "faceAuthIsNotCurrentlyAllowedOrCurrentUserIsTrusted",
- faceDetectLog
- ),
- // We don't want to run face detect if fingerprint can be used to unlock the device
- // but it's not possible to authenticate with FP from the bouncer (UDFPS)
- logAndObserve(
- and(isUdfps(), deviceEntryFingerprintAuthRepository.isRunning).isFalse(),
- "udfpsAuthIsNotPossibleAnymore",
- faceDetectLog
- )
- )
- .reduce(::and)
- .distinctUntilChanged()
+ canRunDetection
.onEach {
- faceAuthLogger.canRunDetectionChanged(it)
- canRunDetection.value = it
if (!it) {
cancelDetection()
}
}
.flowOn(mainDispatcher)
- .logDiffsForTable(faceDetectLog, "", "canFaceDetectRun", false)
.launchIn(applicationScope)
}
@@ -339,76 +360,44 @@
it == BiometricType.UNDER_DISPLAY_FINGERPRINT
}
- private fun canFaceAuthOrDetectRun(tableLogBuffer: TableLogBuffer): Flow<Boolean> {
- return listOf(
- logAndObserve(
- biometricSettingsRepository.isFaceAuthEnrolledAndEnabled,
- "isFaceAuthEnrolledAndEnabled",
- tableLogBuffer
- ),
- logAndObserve(faceAuthPaused.isFalse(), "faceAuthIsNotPaused", tableLogBuffer),
- logAndObserve(
- keyguardRepository.isKeyguardGoingAway.isFalse(),
- "keyguardNotGoingAway",
- tableLogBuffer
- ),
- logAndObserve(
- keyguardRepository.wakefulness.map { it.isStartingToSleep() }.isFalse(),
- "deviceNotStartingToSleep",
- tableLogBuffer
- ),
- logAndObserve(
- keyguardInteractor.isSecureCameraActive
- .isFalse()
- .or(
- alternateBouncerInteractor.isVisible.or(
- keyguardInteractor.primaryBouncerShowing
- )
- ),
- "secureCameraNotActiveOrAnyBouncerIsShowing",
- tableLogBuffer
- ),
- logAndObserve(
- biometricSettingsRepository.isFaceAuthSupportedInCurrentPosture,
- "isFaceAuthSupportedInCurrentPosture",
- tableLogBuffer
- ),
- logAndObserve(
- biometricSettingsRepository.isCurrentUserInLockdown.isFalse(),
- "userHasNotLockedDownDevice",
- tableLogBuffer
- ),
- logAndObserve(
- keyguardRepository.isKeyguardShowing,
- "isKeyguardShowing",
- tableLogBuffer
- )
- )
- .reduce(::and)
+ private fun gatingConditionsForAuthAndDetect(): Array<Pair<Flow<Boolean>, String>> {
+ return arrayOf(
+ Pair(
+ biometricSettingsRepository.isFaceAuthEnrolledAndEnabled,
+ "isFaceAuthEnrolledAndEnabled"
+ ),
+ Pair(faceAuthPaused.isFalse(), "faceAuthIsNotPaused"),
+ Pair(keyguardRepository.isKeyguardGoingAway.isFalse(), "keyguardNotGoingAway"),
+ Pair(
+ keyguardRepository.wakefulness.map { it.isStartingToSleep() }.isFalse(),
+ "deviceNotStartingToSleep"
+ ),
+ Pair(
+ keyguardInteractor.isSecureCameraActive
+ .isFalse()
+ .or(
+ alternateBouncerInteractor.isVisible.or(
+ keyguardInteractor.primaryBouncerShowing
+ )
+ ),
+ "secureCameraNotActiveOrAnyBouncerIsShowing"
+ ),
+ Pair(
+ biometricSettingsRepository.isFaceAuthSupportedInCurrentPosture,
+ "isFaceAuthSupportedInCurrentPosture"
+ ),
+ Pair(
+ biometricSettingsRepository.isCurrentUserInLockdown.isFalse(),
+ "userHasNotLockedDownDevice"
+ ),
+ Pair(keyguardRepository.isKeyguardShowing, "isKeyguardShowing")
+ )
}
private fun observeFaceAuthGatingChecks() {
- // Face auth can run only if all of the gating conditions are true.
- listOf(
- canFaceAuthOrDetectRun(faceAuthLog),
- logAndObserve(isLockedOut.isFalse(), "isNotInLockOutState", faceAuthLog),
- logAndObserve(
- trustRepository.isCurrentUserTrusted.isFalse(),
- "currentUserIsNotTrusted",
- faceAuthLog
- ),
- logAndObserve(
- biometricSettingsRepository.isFaceAuthCurrentlyAllowed,
- "isFaceAuthCurrentlyAllowed",
- faceAuthLog
- ),
- logAndObserve(isAuthenticated.isFalse(), "faceNotAuthenticated", faceAuthLog),
- )
- .reduce(::and)
- .distinctUntilChanged()
+ canRunFaceAuth
.onEach {
faceAuthLogger.canFaceAuthRunChanged(it)
- _canRunFaceAuth.value = it
if (!it) {
// Cancel currently running auth if any of the gating checks are false.
faceAuthLogger.cancellingFaceAuth()
@@ -416,7 +405,6 @@
}
}
.flowOn(mainDispatcher)
- .logDiffsForTable(faceAuthLog, "", "canFaceAuthRun", false)
.launchIn(applicationScope)
}
@@ -618,22 +606,6 @@
_isAuthRunning.value = false
}
- private fun logAndObserve(
- cond: Flow<Boolean>,
- conditionName: String,
- logBuffer: TableLogBuffer
- ): Flow<Boolean> {
- return cond
- .distinctUntilChanged()
- .logDiffsForTable(
- logBuffer,
- columnName = conditionName,
- columnPrefix = "",
- initialValue = false
- )
- .onEach { faceAuthLogger.observedConditionChanged(it, conditionName) }
- }
-
companion object {
const val TAG = "DeviceEntryFaceAuthRepository"
@@ -688,3 +660,18 @@
private fun Flow<Boolean>.isFalse(): Flow<Boolean> {
return this.map { !it }
}
+
+private fun List<Pair<Flow<Boolean>, String>>.andAllFlows(
+ combinedLoggingInfo: String,
+ tableLogBuffer: TableLogBuffer
+): Flow<Boolean> {
+ return combine(this.map { it.first }) {
+ val combinedValue =
+ it.reduceIndexed { index, accumulator, current ->
+ tableLogBuffer.logChange(prefix = "", columnName = this[index].second, current)
+ return@reduceIndexed accumulator && current
+ }
+ tableLogBuffer.logChange(prefix = "", combinedLoggingInfo, combinedValue)
+ return@combine combinedValue
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepository.kt
index f91ae74..f5ef27d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepository.kt
@@ -24,6 +24,7 @@
import com.android.systemui.keyguard.ui.view.layout.blueprints.DefaultKeyguardBlueprint.Companion.DEFAULT
import com.android.systemui.keyguard.ui.view.layout.blueprints.KeyguardBlueprintModule
import java.io.PrintWriter
+import java.util.TreeMap
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
@@ -52,11 +53,12 @@
blueprints: Set<@JvmSuppressWildcards KeyguardBlueprint>,
@Application private val applicationScope: CoroutineScope,
) {
- private val blueprintIdMap: Map<String, KeyguardBlueprint> = blueprints.associateBy { it.id }
+ private val blueprintIdMap: TreeMap<String, KeyguardBlueprint> = TreeMap()
private val _blueprint: MutableSharedFlow<KeyguardBlueprint> = MutableSharedFlow(replay = 1)
val blueprint: Flow<KeyguardBlueprint> = _blueprint.asSharedFlow()
init {
+ blueprintIdMap.putAll(blueprints.associateBy { it.id })
applyBlueprint(blueprintIdMap[DEFAULT]!!)
applicationScope.launch {
configurationRepository.onAnyConfigurationChange.collect { refreshBlueprint() }
@@ -69,6 +71,20 @@
* @param blueprintId
* @return whether the transition has succeeded.
*/
+ fun applyBlueprint(index: Int): Boolean {
+ ArrayList(blueprintIdMap.values)[index]?.let {
+ applyBlueprint(it)
+ return true
+ }
+ return false
+ }
+
+ /**
+ * Emits the blueprint value to the collectors.
+ *
+ * @param blueprintId
+ * @return whether the transition has succeeded.
+ */
fun applyBlueprint(blueprintId: String?): Boolean {
val blueprint = blueprintIdMap[blueprintId] ?: return false
applyBlueprint(blueprint)
@@ -89,6 +105,6 @@
/** Prints all available blueprints to the PrintWriter. */
fun printBlueprints(pw: PrintWriter) {
- blueprintIdMap.forEach { entry -> pw.println("${entry.key}") }
+ blueprintIdMap.onEachIndexed { index, entry -> pw.println("$index: ${entry.key}") }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt
index 390ad7e..6ce9185 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt
@@ -37,6 +37,16 @@
return keyguardBlueprintRepository.applyBlueprint(blueprintId)
}
+ /**
+ * Transitions to a blueprint.
+ *
+ * @param blueprintId
+ * @return whether the transition has succeeded.
+ */
+ fun transitionToBlueprint(blueprintId: Int): Boolean {
+ return keyguardBlueprintRepository.applyBlueprint(blueprintId)
+ }
+
/** Re-emits the blueprint value to the collectors. */
fun refreshBlueprint() {
keyguardBlueprintRepository.refreshBlueprint()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/KeyguardBlueprintCommandListener.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/KeyguardBlueprintCommandListener.kt
index 36d21f1..ce7ec0e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/KeyguardBlueprintCommandListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/KeyguardBlueprintCommandListener.kt
@@ -16,6 +16,7 @@
package com.android.systemui.keyguard.ui.view.layout
+import androidx.core.text.isDigitsOnly
import com.android.systemui.keyguard.data.repository.KeyguardBlueprintRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
import com.android.systemui.statusbar.commandline.Command
@@ -45,7 +46,11 @@
return
}
- if (keyguardBlueprintInteractor.transitionToBlueprint(arg)) {
+ if (
+ arg.isDigitsOnly() && keyguardBlueprintInteractor.transitionToBlueprint(arg.toInt())
+ ) {
+ pw.println("Transition succeeded!")
+ } else if (keyguardBlueprintInteractor.transitionToBlueprint(arg)) {
pw.println("Transition succeeded!")
} else {
pw.println("Invalid argument! To see available blueprint ids, run:")
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt
index 79a97fb..6534dcf 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt
@@ -61,6 +61,6 @@
)
companion object {
- const val SHORTCUTS_BESIDE_UDFPS = "shortcutsBesideUdfps"
+ const val SHORTCUTS_BESIDE_UDFPS = "shortcuts-besides-udfps"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AlignShortcutsToUdfpsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AlignShortcutsToUdfpsSection.kt
index 79b7157..5aba229 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AlignShortcutsToUdfpsSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AlignShortcutsToUdfpsSection.kt
@@ -18,8 +18,6 @@
package com.android.systemui.keyguard.ui.view.layout.sections
import android.content.res.Resources
-import android.view.View
-import android.widget.ImageView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
@@ -27,13 +25,10 @@
import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
import androidx.constraintlayout.widget.ConstraintSet.RIGHT
import androidx.constraintlayout.widget.ConstraintSet.TOP
-import androidx.core.content.res.ResourcesCompat
import com.android.systemui.R
-import com.android.systemui.animation.view.LaunchableImageView
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.shared.model.KeyguardSection
import com.android.systemui.keyguard.ui.binder.KeyguardQuickAffordanceViewBinder
import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordancesCombinedViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
@@ -53,10 +48,7 @@
private val falsingManager: FalsingManager,
private val indicationController: KeyguardIndicationController,
private val vibratorHelper: VibratorHelper,
-) : KeyguardSection() {
- private var leftShortcutHandle: KeyguardQuickAffordanceViewBinder.Binding? = null
- private var rightShortcutHandle: KeyguardQuickAffordanceViewBinder.Binding? = null
-
+) : BaseShortcutSection() {
override fun addViews(constraintLayout: ConstraintLayout) {
if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
addLeftShortcut(constraintLayout)
@@ -109,67 +101,4 @@
connect(R.id.end_button, BOTTOM, R.id.lock_icon_view, BOTTOM)
}
}
-
- override fun removeViews(constraintLayout: ConstraintLayout) {
- leftShortcutHandle?.destroy()
- rightShortcutHandle?.destroy()
- constraintLayout.removeView(R.id.start_button)
- constraintLayout.removeView(R.id.end_button)
- }
-
- private fun addLeftShortcut(constraintLayout: ConstraintLayout) {
- val padding =
- constraintLayout.resources.getDimensionPixelSize(
- R.dimen.keyguard_affordance_fixed_padding
- )
- val view =
- LaunchableImageView(constraintLayout.context, null).apply {
- id = R.id.start_button
- scaleType = ImageView.ScaleType.FIT_CENTER
- background =
- ResourcesCompat.getDrawable(
- context.resources,
- R.drawable.keyguard_bottom_affordance_bg,
- context.theme
- )
- foreground =
- ResourcesCompat.getDrawable(
- context.resources,
- R.drawable.keyguard_bottom_affordance_selected_border,
- context.theme
- )
- visibility = View.INVISIBLE
- setPadding(padding, padding, padding, padding)
- }
- constraintLayout.addView(view)
- }
-
- private fun addRightShortcut(constraintLayout: ConstraintLayout) {
- if (constraintLayout.findViewById<View>(R.id.end_button) != null) return
-
- val padding =
- constraintLayout.resources.getDimensionPixelSize(
- R.dimen.keyguard_affordance_fixed_padding
- )
- val view =
- LaunchableImageView(constraintLayout.context, null).apply {
- id = R.id.end_button
- scaleType = ImageView.ScaleType.FIT_CENTER
- background =
- ResourcesCompat.getDrawable(
- context.resources,
- R.drawable.keyguard_bottom_affordance_bg,
- context.theme
- )
- foreground =
- ResourcesCompat.getDrawable(
- context.resources,
- R.drawable.keyguard_bottom_affordance_selected_border,
- context.theme
- )
- visibility = View.INVISIBLE
- setPadding(padding, padding, padding, padding)
- }
- constraintLayout.addView(view)
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/BaseShortcutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/BaseShortcutSection.kt
new file mode 100644
index 0000000..d046a19
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/BaseShortcutSection.kt
@@ -0,0 +1,99 @@
+package com.android.systemui.keyguard.ui.view.layout.sections
+
+import android.view.View
+import android.widget.ImageView
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.core.content.res.ResourcesCompat
+import com.android.systemui.R
+import com.android.systemui.animation.view.LaunchableImageView
+import com.android.systemui.keyguard.shared.model.KeyguardSection
+import com.android.systemui.keyguard.ui.binder.KeyguardQuickAffordanceViewBinder
+
+abstract class BaseShortcutSection : KeyguardSection() {
+ protected var leftShortcutHandle: KeyguardQuickAffordanceViewBinder.Binding? = null
+ protected var rightShortcutHandle: KeyguardQuickAffordanceViewBinder.Binding? = null
+
+ override fun removeViews(constraintLayout: ConstraintLayout) {
+ leftShortcutHandle?.destroy()
+ rightShortcutHandle?.destroy()
+ constraintLayout.removeView(R.id.start_button)
+ constraintLayout.removeView(R.id.end_button)
+ }
+
+ protected fun addLeftShortcut(constraintLayout: ConstraintLayout) {
+ val padding =
+ constraintLayout.resources.getDimensionPixelSize(
+ R.dimen.keyguard_affordance_fixed_padding
+ )
+ val view =
+ LaunchableImageView(constraintLayout.context, null).apply {
+ id = R.id.start_button
+ scaleType = ImageView.ScaleType.FIT_CENTER
+ background =
+ ResourcesCompat.getDrawable(
+ context.resources,
+ R.drawable.keyguard_bottom_affordance_bg,
+ context.theme
+ )
+ foreground =
+ ResourcesCompat.getDrawable(
+ context.resources,
+ R.drawable.keyguard_bottom_affordance_selected_border,
+ context.theme
+ )
+ visibility = View.INVISIBLE
+ setPadding(padding, padding, padding, padding)
+ }
+ constraintLayout.addView(view)
+ }
+
+ protected fun addRightShortcut(constraintLayout: ConstraintLayout) {
+ if (constraintLayout.findViewById<View>(R.id.end_button) != null) return
+
+ val padding =
+ constraintLayout.resources.getDimensionPixelSize(
+ R.dimen.keyguard_affordance_fixed_padding
+ )
+ val view =
+ LaunchableImageView(constraintLayout.context, null).apply {
+ id = R.id.end_button
+ scaleType = ImageView.ScaleType.FIT_CENTER
+ background =
+ ResourcesCompat.getDrawable(
+ context.resources,
+ R.drawable.keyguard_bottom_affordance_bg,
+ context.theme
+ )
+ foreground =
+ ResourcesCompat.getDrawable(
+ context.resources,
+ R.drawable.keyguard_bottom_affordance_selected_border,
+ context.theme
+ )
+ visibility = View.INVISIBLE
+ setPadding(padding, padding, padding, padding)
+ }
+ constraintLayout.addView(view)
+ }
+ /**
+ * Defines equality as same class.
+ *
+ * This is to enable set operations to be done as an optimization to blueprint transitions.
+ */
+ override fun equals(other: Any?): Boolean {
+ return other is BaseShortcutSection
+ }
+
+ /**
+ * Defines hashcode as class.
+ *
+ * This is to enable set operations to be done as an optimization to blueprint transitions.
+ */
+ override fun hashCode(): Int {
+ return KEY.hashCode()
+ }
+
+ companion object {
+ private const val KEY = "shortcuts"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt
index a2db1df..13ef985 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt
@@ -18,21 +18,16 @@
package com.android.systemui.keyguard.ui.view.layout.sections
import android.content.res.Resources
-import android.view.View
-import android.widget.ImageView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
import androidx.constraintlayout.widget.ConstraintSet.LEFT
import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
import androidx.constraintlayout.widget.ConstraintSet.RIGHT
-import androidx.core.content.res.ResourcesCompat
import com.android.systemui.R
-import com.android.systemui.animation.view.LaunchableImageView
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.shared.model.KeyguardSection
import com.android.systemui.keyguard.ui.binder.KeyguardQuickAffordanceViewBinder
import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordancesCombinedViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
@@ -52,10 +47,7 @@
private val falsingManager: FalsingManager,
private val indicationController: KeyguardIndicationController,
private val vibratorHelper: VibratorHelper,
-) : KeyguardSection() {
- private var leftShortcutHandle: KeyguardQuickAffordanceViewBinder.Binding? = null
- private var rightShortcutHandle: KeyguardQuickAffordanceViewBinder.Binding? = null
-
+) : BaseShortcutSection() {
override fun addViews(constraintLayout: ConstraintLayout) {
if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
addLeftShortcut(constraintLayout)
@@ -108,67 +100,4 @@
connect(R.id.end_button, BOTTOM, PARENT_ID, BOTTOM, verticalOffsetMargin)
}
}
-
- override fun removeViews(constraintLayout: ConstraintLayout) {
- leftShortcutHandle?.destroy()
- rightShortcutHandle?.destroy()
- constraintLayout.removeView(R.id.start_button)
- constraintLayout.removeView(R.id.end_button)
- }
-
- private fun addLeftShortcut(constraintLayout: ConstraintLayout) {
- val padding =
- constraintLayout.resources.getDimensionPixelSize(
- R.dimen.keyguard_affordance_fixed_padding
- )
- val view =
- LaunchableImageView(constraintLayout.context, null).apply {
- id = R.id.start_button
- scaleType = ImageView.ScaleType.FIT_CENTER
- background =
- ResourcesCompat.getDrawable(
- context.resources,
- R.drawable.keyguard_bottom_affordance_bg,
- context.theme
- )
- foreground =
- ResourcesCompat.getDrawable(
- context.resources,
- R.drawable.keyguard_bottom_affordance_selected_border,
- context.theme
- )
- visibility = View.INVISIBLE
- setPadding(padding, padding, padding, padding)
- }
- constraintLayout.addView(view)
- }
-
- private fun addRightShortcut(constraintLayout: ConstraintLayout) {
- if (constraintLayout.findViewById<View>(R.id.end_button) != null) return
-
- val padding =
- constraintLayout.resources.getDimensionPixelSize(
- R.dimen.keyguard_affordance_fixed_padding
- )
- val view =
- LaunchableImageView(constraintLayout.context, null).apply {
- id = R.id.end_button
- scaleType = ImageView.ScaleType.FIT_CENTER
- background =
- ResourcesCompat.getDrawable(
- context.resources,
- R.drawable.keyguard_bottom_affordance_bg,
- context.theme
- )
- foreground =
- ResourcesCompat.getDrawable(
- context.resources,
- R.drawable.keyguard_bottom_affordance_selected_border,
- context.theme
- )
- visibility = View.INVISIBLE
- setPadding(padding, padding, padding, padding)
- }
- constraintLayout.addView(view)
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index 4a76dd0..2dbcbc9 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -40,6 +40,7 @@
import android.view.IWindowSession;
import android.view.View;
import android.view.ViewGroup;
+import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
import android.view.WindowManagerGlobal;
@@ -409,9 +410,9 @@
private void applyForceShowNavigationFlag(NotificationShadeWindowState state) {
if (state.panelExpanded || state.bouncerShowing
|| ENABLE_REMOTE_INPUT && state.remoteInputActive) {
- mLpChanged.privateFlags |= LayoutParams.PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION;
+ mLpChanged.forciblyShownTypes |= WindowInsets.Type.navigationBars();
} else {
- mLpChanged.privateFlags &= ~LayoutParams.PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION;
+ mLpChanged.forciblyShownTypes &= ~WindowInsets.Type.navigationBars();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
index 3f7512a..f1e75b1 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
@@ -76,7 +76,7 @@
scenesProvider: Provider<Set<@JvmSuppressWildcards Scene>>,
layoutInsetController: NotificationInsetsController,
): WindowRootView {
- return if (sceneContainerFlags.isEnabled()) {
+ return if (Flags.SCENE_CONTAINER_ENABLED && sceneContainerFlags.isEnabled()) {
val sceneWindowRootView =
layoutInflater.inflate(R.layout.scene_window_root, null) as SceneWindowRootView
sceneWindowRootView.init(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index b797c63..b45a688 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -2680,6 +2680,7 @@
&& mStatusBarStateController.getDozeAmount() == 1f
&& mWakefulnessLifecycle.getLastWakeReason()
== PowerManager.WAKE_REASON_POWER_BUTTON
+ && mFingerprintManager.get() != null
&& mFingerprintManager.get().isPowerbuttonFps()
&& mKeyguardUpdateMonitor
.getCachedIsUnlockWithFingerprintPossible(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialogFactory.kt
index 3b15065..d91ca92 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialogFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialogFactory.kt
@@ -48,13 +48,14 @@
*/
fun create(
context: Context = this.applicationContext,
+ theme: Int = SystemUIDialog.DEFAULT_THEME,
dismissOnDeviceLock: Boolean = SystemUIDialog.DEFAULT_DISMISS_ON_DEVICE_LOCK,
): ComponentSystemUIDialog {
Assert.isMainThread()
return ComponentSystemUIDialog(
context,
- SystemUIDialog.DEFAULT_THEME,
+ theme,
dismissOnDeviceLock,
featureFlags,
dialogManager,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java
index 24987ab..f4cc0ed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java
@@ -21,7 +21,6 @@
import static android.view.WindowInsets.Type.tappableElement;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR;
import static com.android.systemui.util.leak.RotationUtils.ROTATION_LANDSCAPE;
import static com.android.systemui.util.leak.RotationUtils.ROTATION_NONE;
@@ -44,6 +43,7 @@
import android.view.Surface;
import android.view.View;
import android.view.ViewGroup;
+import android.view.WindowInsets;
import android.view.WindowManager;
import com.android.internal.policy.SystemBarUtils;
@@ -361,9 +361,9 @@
|| state.mIsLaunchAnimationRunning
// Don't force-show the status bar if the user has already dismissed it.
|| state.mOngoingProcessRequiresStatusBarVisible) {
- mLpChanged.privateFlags |= PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR;
+ mLpChanged.forciblyShownTypes |= WindowInsets.Type.statusBars();
} else {
- mLpChanged.privateFlags &= ~PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR;
+ mLpChanged.forciblyShownTypes &= ~WindowInsets.Type.statusBars();
}
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FingerprintRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FingerprintRepositoryImplTest.kt
index 239e317..ed9ae5e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FingerprintRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FingerprintRepositoryImplTest.kt
@@ -29,6 +29,7 @@
import com.android.systemui.biometrics.shared.model.SensorStrength
import com.android.systemui.coroutines.collectLastValue
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
@@ -44,6 +45,7 @@
import org.mockito.Mockito.verify
import org.mockito.junit.MockitoJUnit
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(JUnit4::class)
class FingerprintRepositoryImplTest : SysuiTestCase() {
@@ -73,10 +75,15 @@
@Test
fun initializeProperties() =
testScope.runTest {
- val isInitialized = collectLastValue(repository.isInitialized)
+ val sensorId by collectLastValue(repository.sensorId)
+ val strength by collectLastValue(repository.strength)
+ val sensorType by collectLastValue(repository.sensorType)
+ val sensorLocations by collectLastValue(repository.sensorLocations)
- assertDefaultProperties()
- assertThat(isInitialized()).isFalse()
+ // Assert default properties.
+ assertThat(sensorId).isEqualTo(-1)
+ assertThat(strength).isEqualTo(SensorStrength.CONVENIENCE)
+ assertThat(sensorType).isEqualTo(FingerprintSensorType.UNKNOWN)
val fingerprintProps =
listOf(
@@ -115,31 +122,24 @@
fingerprintAuthenticatorsCaptor.value.onAllAuthenticatorsRegistered(fingerprintProps)
- assertThat(repository.sensorId.value).isEqualTo(1)
- assertThat(repository.strength.value).isEqualTo(SensorStrength.STRONG)
- assertThat(repository.sensorType.value).isEqualTo(FingerprintSensorType.REAR)
+ assertThat(sensorId).isEqualTo(1)
+ assertThat(strength).isEqualTo(SensorStrength.STRONG)
+ assertThat(sensorType).isEqualTo(FingerprintSensorType.REAR)
- assertThat(repository.sensorLocations.value.size).isEqualTo(2)
- assertThat(repository.sensorLocations.value).containsKey("display_id_1")
- with(repository.sensorLocations.value["display_id_1"]!!) {
+ assertThat(sensorLocations?.size).isEqualTo(2)
+ assertThat(sensorLocations).containsKey("display_id_1")
+ with(sensorLocations?.get("display_id_1")!!) {
assertThat(displayId).isEqualTo("display_id_1")
assertThat(sensorLocationX).isEqualTo(100)
assertThat(sensorLocationY).isEqualTo(300)
assertThat(sensorRadius).isEqualTo(20)
}
- assertThat(repository.sensorLocations.value).containsKey("")
- with(repository.sensorLocations.value[""]!!) {
+ assertThat(sensorLocations).containsKey("")
+ with(sensorLocations?.get("")!!) {
assertThat(displayId).isEqualTo("")
assertThat(sensorLocationX).isEqualTo(540)
assertThat(sensorLocationY).isEqualTo(1636)
assertThat(sensorRadius).isEqualTo(130)
}
- assertThat(isInitialized()).isTrue()
}
-
- private fun assertDefaultProperties() {
- assertThat(repository.sensorId.value).isEqualTo(-1)
- assertThat(repository.strength.value).isEqualTo(SensorStrength.CONVENIENCE)
- assertThat(repository.sensorType.value).isEqualTo(FingerprintSensorType.UNKNOWN)
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractorTest.kt
index fd96cf4..712eef1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractorTest.kt
@@ -22,6 +22,7 @@
import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
import com.android.systemui.biometrics.shared.model.SensorStrength
+import com.android.systemui.coroutines.collectLastValue
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
@@ -51,7 +52,7 @@
}
@Test
- fun testGetOverlayOffsets() =
+ fun testOverlayOffsetUpdates() =
testScope.runTest {
fingerprintRepository.setProperties(
sensorId = 1,
@@ -76,16 +77,32 @@
)
)
- var offsets = interactor.getOverlayOffsets("display_id_1")
- assertThat(offsets.displayId).isEqualTo("display_id_1")
- assertThat(offsets.sensorLocationX).isEqualTo(100)
- assertThat(offsets.sensorLocationY).isEqualTo(300)
- assertThat(offsets.sensorRadius).isEqualTo(20)
+ val displayId by collectLastValue(interactor.displayId)
+ val offsets by collectLastValue(interactor.overlayOffsets)
- offsets = interactor.getOverlayOffsets("invalid_display_id")
- assertThat(offsets.displayId).isEqualTo("")
- assertThat(offsets.sensorLocationX).isEqualTo(540)
- assertThat(offsets.sensorLocationY).isEqualTo(1636)
- assertThat(offsets.sensorRadius).isEqualTo(130)
+ // Assert offsets of empty displayId.
+ assertThat(displayId).isEqualTo("")
+ assertThat(offsets?.displayId).isEqualTo("")
+ assertThat(offsets?.sensorLocationX).isEqualTo(540)
+ assertThat(offsets?.sensorLocationY).isEqualTo(1636)
+ assertThat(offsets?.sensorRadius).isEqualTo(130)
+
+ // Offsets should be updated correctly.
+ interactor.onDisplayChanged("display_id_1")
+ assertThat(displayId).isEqualTo("display_id_1")
+ assertThat(offsets?.displayId).isEqualTo("display_id_1")
+ assertThat(offsets?.sensorLocationX).isEqualTo(100)
+ assertThat(offsets?.sensorLocationY).isEqualTo(300)
+ assertThat(offsets?.sensorRadius).isEqualTo(20)
+
+ // Should return default offset when the displayId is invalid.
+ interactor.onDisplayChanged("invalid_display_id")
+ assertThat(displayId).isEqualTo("invalid_display_id")
+ assertThat(offsets?.displayId).isEqualTo(SensorLocationInternal.DEFAULT.displayId)
+ assertThat(offsets?.sensorLocationX)
+ .isEqualTo(SensorLocationInternal.DEFAULT.sensorLocationX)
+ assertThat(offsets?.sensorLocationY)
+ .isEqualTo(SensorLocationInternal.DEFAULT.sensorLocationY)
+ assertThat(offsets?.sensorRadius).isEqualTo(SensorLocationInternal.DEFAULT.sensorRadius)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/KeyguardBlueprintCommandListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/KeyguardBlueprintCommandListenerTest.kt
index bb73dc6..dbf6a29 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/KeyguardBlueprintCommandListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/KeyguardBlueprintCommandListenerTest.kt
@@ -81,4 +81,10 @@
command().execute(pw, listOf("fake"))
verify(keyguardBlueprintInteractor).transitionToBlueprint("fake")
}
+
+ @Test
+ fun testValidArg_Int() {
+ command().execute(pw, listOf("1"))
+ verify(keyguardBlueprintInteractor).transitionToBlueprint(1)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index 137566b..bd3fb9f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -399,7 +399,6 @@
when(mGradientColors.supportsDarkText()).thenReturn(true);
when(mColorExtractor.getNeutralColors()).thenReturn(mGradientColors);
- ConfigurationController configurationController = new ConfigurationControllerImpl(mContext);
when(mLockscreenWallpaperLazy.get()).thenReturn(mLockscreenWallpaper);
when(mBiometricUnlockControllerLazy.get()).thenReturn(mBiometricUnlockController);
@@ -438,6 +437,11 @@
when(mUserTracker.getUserHandle()).thenReturn(
UserHandle.of(ActivityManager.getCurrentUser()));
+ createCentralSurfaces();
+ }
+
+ private void createCentralSurfaces() {
+ ConfigurationController configurationController = new ConfigurationControllerImpl(mContext);
mCentralSurfaces = new CentralSurfacesImpl(
mContext,
mNotificationsController,
@@ -1083,6 +1087,27 @@
verify(mNotificationPanelViewController).setTouchAndAnimationDisabled(true);
}
+ /** Regression test for b/298355063 */
+ @Test
+ public void fingerprintManagerNull_noNPE() {
+ // GIVEN null fingerprint manager
+ mFingerprintManager = null;
+ createCentralSurfaces();
+
+ // GIVEN should animate doze wakeup
+ when(mDozeServiceHost.shouldAnimateWakeup()).thenReturn(true);
+ when(mBiometricUnlockController.getMode()).thenReturn(
+ BiometricUnlockController.MODE_ONLY_WAKE);
+ when(mDozeServiceHost.isPulsing()).thenReturn(false);
+ when(mStatusBarStateController.getDozeAmount()).thenReturn(1f);
+
+ // WHEN waking up from the power button
+ mWakefulnessLifecycle.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
+ mCentralSurfaces.mWakefulnessObserver.onStartedWakingUp();
+
+ // THEN no NPE when fingerprintManager is null
+ }
+
/**
* Configures the appropriate mocks and then calls {@link CentralSurfacesImpl#updateIsKeyguard}
* to reconfigure the keyguard to reflect the requested showing/occluded states.
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFingerprintPropertyRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFingerprintPropertyRepository.kt
index 2362a52..0c5e438 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFingerprintPropertyRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFingerprintPropertyRepository.kt
@@ -20,16 +20,12 @@
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
import com.android.systemui.biometrics.shared.model.SensorStrength
import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
class FakeFingerprintPropertyRepository : FingerprintPropertyRepository {
- private val _isInitialized: MutableStateFlow<Boolean> = MutableStateFlow(false)
- override val isInitialized = _isInitialized.asStateFlow()
-
private val _sensorId: MutableStateFlow<Int> = MutableStateFlow(-1)
- override val sensorId: StateFlow<Int> = _sensorId.asStateFlow()
+ override val sensorId = _sensorId.asStateFlow()
private val _strength: MutableStateFlow<SensorStrength> =
MutableStateFlow(SensorStrength.CONVENIENCE)
@@ -37,12 +33,11 @@
private val _sensorType: MutableStateFlow<FingerprintSensorType> =
MutableStateFlow(FingerprintSensorType.UNKNOWN)
- override val sensorType: StateFlow<FingerprintSensorType> = _sensorType.asStateFlow()
+ override val sensorType = _sensorType.asStateFlow()
private val _sensorLocations: MutableStateFlow<Map<String, SensorLocationInternal>> =
MutableStateFlow(mapOf("" to SensorLocationInternal.DEFAULT))
- override val sensorLocations: StateFlow<Map<String, SensorLocationInternal>> =
- _sensorLocations.asStateFlow()
+ override val sensorLocations = _sensorLocations.asStateFlow()
fun setProperties(
sensorId: Int,
@@ -54,6 +49,5 @@
_strength.value = strength
_sensorType.value = sensorType
_sensorLocations.value = sensorLocations
- _isInitialized.value = true
}
}
diff --git a/services/accessibility/Android.bp b/services/accessibility/Android.bp
index bf8a9af..e9bb763 100644
--- a/services/accessibility/Android.bp
+++ b/services/accessibility/Android.bp
@@ -27,4 +27,20 @@
"services.core",
"androidx.annotation_annotation",
],
+ static_libs: [
+ "com_android_server_accessibility_flags_lib",
+ ],
+}
+
+aconfig_declarations {
+ name: "com_android_server_accessibility_flags",
+ package: "com.android.server.accessibility",
+ srcs: [
+ "accessibility.aconfig",
+ ],
+}
+
+java_aconfig_library {
+ name: "com_android_server_accessibility_flags_lib",
+ aconfig_declarations: "com_android_server_accessibility_flags",
}
diff --git a/services/accessibility/accessibility.aconfig b/services/accessibility/accessibility.aconfig
new file mode 100644
index 0000000..b5fc2b6
--- /dev/null
+++ b/services/accessibility/accessibility.aconfig
@@ -0,0 +1,7 @@
+package: "com.android.server.accessibility"
+flag {
+ name: "proxy_use_apps_on_virtual_device_listener"
+ namespace: "accessibility"
+ description: "Fixes race condition described in b/286587811"
+ bug: "286587811"
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/ProxyManager.java b/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
index 119f575..ed77476 100644
--- a/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
@@ -33,6 +33,7 @@
import android.os.IBinder;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
+import android.util.ArraySet;
import android.util.IntArray;
import android.util.Slog;
import android.util.SparseArray;
@@ -42,6 +43,7 @@
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.IAccessibilityManagerClient;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IntPair;
import com.android.server.LocalServices;
import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
@@ -96,6 +98,9 @@
private final SystemSupport mSystemSupport;
+ private VirtualDeviceManagerInternal.AppsOnVirtualDeviceListener
+ mAppsOnVirtualDeviceListener;
+
/**
* Callbacks into AccessibilityManagerService.
*/
@@ -174,6 +179,16 @@
synchronized (mLock) {
mProxyA11yServiceConnections.put(displayId, connection);
+ if (Flags.proxyUseAppsOnVirtualDeviceListener()) {
+ if (mAppsOnVirtualDeviceListener == null) {
+ mAppsOnVirtualDeviceListener = allRunningUids ->
+ notifyProxyOfRunningAppsChange(allRunningUids);
+ final VirtualDeviceManagerInternal localVdm = getLocalVdm();
+ if (localVdm != null) {
+ localVdm.registerAppsOnVirtualDeviceListener(mAppsOnVirtualDeviceListener);
+ }
+ }
+ }
}
// If the client dies, make sure to remove the connection.
@@ -276,11 +291,21 @@
}
}
});
- // If there isn't an existing proxy for the device id, reset clients. Resetting
+ // If there isn't an existing proxy for the device id, reset app clients. Resetting
// will usually happen, since in most cases there will only be one proxy for a
// device.
if (!isProxyedDeviceId(deviceId)) {
synchronized (mLock) {
+ if (Flags.proxyUseAppsOnVirtualDeviceListener()) {
+ if (mProxyA11yServiceConnections.size() == 0) {
+ final VirtualDeviceManagerInternal localVdm = getLocalVdm();
+ if (localVdm != null && mAppsOnVirtualDeviceListener != null) {
+ localVdm.unregisterAppsOnVirtualDeviceListener(
+ mAppsOnVirtualDeviceListener);
+ mAppsOnVirtualDeviceListener = null;
+ }
+ }
+ }
mSystemSupport.removeDeviceIdLocked(deviceId);
mLastStates.delete(deviceId);
}
@@ -307,7 +332,7 @@
* Returns {@code true} if {@code deviceId} is being proxy-ed.
*/
public boolean isProxyedDeviceId(int deviceId) {
- if (deviceId == DEVICE_ID_DEFAULT && deviceId == DEVICE_ID_INVALID) {
+ if (deviceId == DEVICE_ID_DEFAULT || deviceId == DEVICE_ID_INVALID) {
return false;
}
boolean isTrackingDeviceId;
@@ -566,7 +591,7 @@
* This is similar to onUserStateChangeLocked and onClientChangeLocked, but does not require an
* A11yUserState and only checks proxy-relevant settings.
*/
- public void onProxyChanged(int deviceId) {
+ private void onProxyChanged(int deviceId, boolean forceUpdate) {
if (DEBUG) {
Slog.v(LOG_TAG, "onProxyChanged called for deviceId: " + deviceId);
}
@@ -584,7 +609,7 @@
// Calls A11yManager#setRelevantEventTypes (test these)
updateRelevantEventTypesLocked(deviceId);
// Calls A11yManager#setState
- scheduleUpdateProxyClientsIfNeededLocked(deviceId);
+ scheduleUpdateProxyClientsIfNeededLocked(deviceId, forceUpdate);
//Calls A11yManager#notifyServicesStateChanged(timeout)
scheduleNotifyProxyClientsOfServicesStateChangeLocked(deviceId);
// Calls A11yManager#setFocusAppearance
@@ -594,16 +619,25 @@
}
/**
+ * Handles proxy changes, but does not force an update of app clients.
+ */
+ public void onProxyChanged(int deviceId) {
+ onProxyChanged(deviceId, false);
+ }
+
+ /**
* Updates the states of the app AccessibilityManagers.
*/
- private void scheduleUpdateProxyClientsIfNeededLocked(int deviceId) {
+ private void scheduleUpdateProxyClientsIfNeededLocked(int deviceId, boolean forceUpdate) {
final int proxyState = getStateLocked(deviceId);
if (DEBUG) {
Slog.v(LOG_TAG, "State for device id " + deviceId + " is " + proxyState);
Slog.v(LOG_TAG, "Last state for device id " + deviceId + " is "
+ getLastSentStateLocked(deviceId));
+ Slog.v(LOG_TAG, "force update: " + forceUpdate);
}
- if ((getLastSentStateLocked(deviceId)) != proxyState) {
+ if ((getLastSentStateLocked(deviceId)) != proxyState
+ || (Flags.proxyUseAppsOnVirtualDeviceListener() && forceUpdate)) {
setLastStateLocked(deviceId, proxyState);
mMainHandler.post(() -> {
synchronized (mLock) {
@@ -792,7 +826,7 @@
}
/**
- * Updates the device ids of IAccessibilityManagerClients if needed.
+ * Updates the device ids of IAccessibilityManagerClients if needed after a proxy change.
*/
private void updateDeviceIdsIfNeededLocked(int deviceId,
@NonNull RemoteCallbackList<IAccessibilityManagerClient> clients) {
@@ -804,13 +838,66 @@
for (int i = 0; i < clients.getRegisteredCallbackCount(); i++) {
final AccessibilityManagerService.Client client =
((AccessibilityManagerService.Client) clients.getRegisteredCallbackCookie(i));
- if (deviceId != DEVICE_ID_DEFAULT && deviceId != DEVICE_ID_INVALID
- && localVdm.getDeviceIdsForUid(client.mUid).contains(deviceId)) {
- if (DEBUG) {
- Slog.v(LOG_TAG, "Packages moved to device id " + deviceId + " are "
- + Arrays.toString(client.mPackageNames));
+ if (Flags.proxyUseAppsOnVirtualDeviceListener()) {
+ if (deviceId == DEVICE_ID_DEFAULT || deviceId == DEVICE_ID_INVALID) {
+ continue;
}
- client.mDeviceId = deviceId;
+ boolean uidBelongsToDevice =
+ localVdm.getDeviceIdsForUid(client.mUid).contains(deviceId);
+ if (client.mDeviceId != deviceId && uidBelongsToDevice) {
+ if (DEBUG) {
+ Slog.v(LOG_TAG, "Packages moved to device id " + deviceId + " are "
+ + Arrays.toString(client.mPackageNames));
+ }
+ client.mDeviceId = deviceId;
+ } else if (client.mDeviceId == deviceId && !uidBelongsToDevice) {
+ client.mDeviceId = DEVICE_ID_DEFAULT;
+ if (DEBUG) {
+ Slog.v(LOG_TAG, "Packages moved to the default device from device id "
+ + deviceId + " are " + Arrays.toString(client.mPackageNames));
+ }
+ }
+ } else {
+ if (deviceId != DEVICE_ID_DEFAULT && deviceId != DEVICE_ID_INVALID
+ && localVdm.getDeviceIdsForUid(client.mUid).contains(deviceId)) {
+ if (DEBUG) {
+ Slog.v(LOG_TAG, "Packages moved to device id " + deviceId + " are "
+ + Arrays.toString(client.mPackageNames));
+ }
+ client.mDeviceId = deviceId;
+ }
+ }
+ }
+ }
+
+ @VisibleForTesting
+ void notifyProxyOfRunningAppsChange(Set<Integer> allRunningUids) {
+ if (DEBUG) {
+ Slog.v(LOG_TAG, "notifyProxyOfRunningAppsChange: " + allRunningUids);
+ }
+ synchronized (mLock) {
+ if (mProxyA11yServiceConnections.size() == 0) {
+ return;
+ }
+ final VirtualDeviceManagerInternal localVdm = getLocalVdm();
+ if (localVdm == null) {
+ return;
+ }
+ final ArraySet<Integer> deviceIdsToUpdate = new ArraySet<>();
+ for (int i = 0; i < mProxyA11yServiceConnections.size(); i++) {
+ final ProxyAccessibilityServiceConnection proxy =
+ mProxyA11yServiceConnections.valueAt(i);
+ if (proxy != null) {
+ final int proxyDeviceId = proxy.getDeviceId();
+ for (Integer uid : allRunningUids) {
+ if (localVdm.getDeviceIdsForUid(uid).contains(proxyDeviceId)) {
+ deviceIdsToUpdate.add(proxyDeviceId);
+ }
+ }
+ }
+ }
+ for (Integer proxyDeviceId : deviceIdsToUpdate) {
+ onProxyChanged(proxyDeviceId, true);
}
}
}
@@ -843,6 +930,11 @@
return mLocalVdm;
}
+ @VisibleForTesting
+ void setLocalVirtualDeviceManager(VirtualDeviceManagerInternal localVdm) {
+ mLocalVdm = localVdm;
+ }
+
/**
* Prints information belonging to each display that is controlled by an
* AccessibilityDisplayProxy.
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java
index 39756df..cae047f 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java
@@ -18,7 +18,6 @@
import static android.service.autofill.AutofillFieldClassificationService.EXTRA_SCORES;
import static android.service.autofill.AutofillService.EXTRA_RESULT;
-
import static com.android.server.autofill.AutofillManagerService.RECEIVER_BUNDLE_EXTRA_SESSIONS;
import android.os.Bundle;
@@ -155,20 +154,31 @@
pw.println("");
}
+ Method[] flagMethods = {};
+
try {
- Method[] flagMethods = Flags.class.getMethods();
- // For some reason, unreferenced flags do not show up here
- // Maybe compiler optomized them out of bytecode?
- for (Method method : flagMethods) {
- if (Modifier.isPublic(method.getModifiers())) {
- pw.println(method.getName() + ": " + method.invoke(null));
- }
- }
- } catch (Exception ex) {
- pw.println(ex);
+ flagMethods = Flags.class.getDeclaredMethods();
+ } catch (SecurityException ex) {
+ ex.printStackTrace(pw);
return -1;
}
+ // For some reason, unreferenced flags do not show up here
+ // Maybe compiler optomized them out of bytecode?
+ for (Method method : flagMethods) {
+ if (!Modifier.isPublic(method.getModifiers())) {
+ continue;
+ }
+ try {
+ pw.print(method.getName() + ": ");
+ pw.print(method.invoke(null));
+ } catch (Exception ex) {
+ ex.printStackTrace(pw);
+ } finally {
+ pw.println("");
+ }
+ }
+
return 0;
}
diff --git a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
index b07a0bb..102c262 100644
--- a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
+++ b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
@@ -47,7 +47,6 @@
import java.util.Set;
-
/**
* A controller to control the policies of the windows that can be displayed on the virtual display.
*/
@@ -106,12 +105,14 @@
public static final long ALLOW_SECURE_ACTIVITY_DISPLAY_ON_REMOTE_DEVICE = 201712607L;
@NonNull
private final ArraySet<UserHandle> mAllowedUsers;
- private final boolean mActivityLaunchAllowedByDefault;
+ @GuardedBy("mGenericWindowPolicyControllerLock")
+ private boolean mActivityLaunchAllowedByDefault;
@NonNull
- private final ArraySet<ComponentName> mActivityPolicyExceptions;
+ @GuardedBy("mGenericWindowPolicyControllerLock")
+ private final Set<ComponentName> mActivityPolicyExemptions;
private final boolean mCrossTaskNavigationAllowedByDefault;
@NonNull
- private final ArraySet<ComponentName> mCrossTaskNavigationExceptions;
+ private final ArraySet<ComponentName> mCrossTaskNavigationExemptions;
private final Object mGenericWindowPolicyControllerLock = new Object();
@Nullable private final ActivityBlockedCallback mActivityBlockedCallback;
private int mDisplayId = Display.INVALID_DISPLAY;
@@ -142,11 +143,11 @@
* @param allowedUsers The set of users that are allowed to stream in this display.
* @param activityLaunchAllowedByDefault Whether activities are default allowed to be launched
* or blocked.
- * @param activityPolicyExceptions The set of activities explicitly exempt from the default
+ * @param activityPolicyExemptions The set of activities explicitly exempt from the default
* activity policy.
* @param crossTaskNavigationAllowedByDefault Whether cross task navigations are allowed by
* default or not.
- * @param crossTaskNavigationExceptions The set of components explicitly exempt from the default
+ * @param crossTaskNavigationExemptions The set of components explicitly exempt from the default
* navigation policy.
* @param activityListener Activity listener to listen for activity changes.
* @param activityBlockedCallback Callback that is called when an activity is blocked from
@@ -157,12 +158,14 @@
* passed in filters.
* @param showTasksInHostDeviceRecents whether to show activities in recents on the host device.
*/
- public GenericWindowPolicyController(int windowFlags, int systemWindowFlags,
+ public GenericWindowPolicyController(
+ int windowFlags,
+ int systemWindowFlags,
@NonNull ArraySet<UserHandle> allowedUsers,
boolean activityLaunchAllowedByDefault,
- @NonNull Set<ComponentName> activityPolicyExceptions,
+ @NonNull Set<ComponentName> activityPolicyExemptions,
boolean crossTaskNavigationAllowedByDefault,
- @NonNull Set<ComponentName> crossTaskNavigationExceptions,
+ @NonNull Set<ComponentName> crossTaskNavigationExemptions,
@Nullable ActivityListener activityListener,
@Nullable PipBlockedCallback pipBlockedCallback,
@Nullable ActivityBlockedCallback activityBlockedCallback,
@@ -173,9 +176,9 @@
super();
mAllowedUsers = allowedUsers;
mActivityLaunchAllowedByDefault = activityLaunchAllowedByDefault;
- mActivityPolicyExceptions = new ArraySet<>(activityPolicyExceptions);
+ mActivityPolicyExemptions = activityPolicyExemptions;
mCrossTaskNavigationAllowedByDefault = crossTaskNavigationAllowedByDefault;
- mCrossTaskNavigationExceptions = new ArraySet<>(crossTaskNavigationExceptions);
+ mCrossTaskNavigationExemptions = new ArraySet<>(crossTaskNavigationExemptions);
mActivityBlockedCallback = activityBlockedCallback;
setInterestedWindowFlags(windowFlags, systemWindowFlags);
mActivityListener = activityListener;
@@ -202,6 +205,24 @@
}
}
+ void setActivityLaunchDefaultAllowed(boolean activityLaunchDefaultAllowed) {
+ synchronized (mGenericWindowPolicyControllerLock) {
+ mActivityLaunchAllowedByDefault = activityLaunchDefaultAllowed;
+ }
+ }
+
+ void addActivityPolicyExemption(@NonNull ComponentName componentName) {
+ synchronized (mGenericWindowPolicyControllerLock) {
+ mActivityPolicyExemptions.add(componentName);
+ }
+ }
+
+ void removeActivityPolicyExemption(@NonNull ComponentName componentName) {
+ synchronized (mGenericWindowPolicyControllerLock) {
+ mActivityPolicyExemptions.remove(componentName);
+ }
+ }
+
/** Register a listener for running applications changes. */
public void registerRunningAppsChangedListener(@NonNull RunningAppsChangedListener listener) {
synchronized (mGenericWindowPolicyControllerLock) {
@@ -265,14 +286,17 @@
+ mDisplayCategories);
return false;
}
- if (!isAllowedByPolicy(mActivityLaunchAllowedByDefault, mActivityPolicyExceptions,
- activityComponent)) {
- Slog.d(TAG, "Virtual device launch disallowed by policy: " + activityComponent);
- return false;
+ synchronized (mGenericWindowPolicyControllerLock) {
+ if (!isAllowedByPolicy(mActivityLaunchAllowedByDefault, mActivityPolicyExemptions,
+ activityComponent)) {
+ Slog.d(TAG, "Virtual device launch disallowed by policy: "
+ + activityComponent);
+ return false;
+ }
}
if (isNewTask && launchingFromDisplayId != DEFAULT_DISPLAY
&& !isAllowedByPolicy(mCrossTaskNavigationAllowedByDefault,
- mCrossTaskNavigationExceptions, activityComponent)) {
+ mCrossTaskNavigationExemptions, activityComponent)) {
Slog.d(TAG, "Virtual device cross task navigation disallowed by policy: "
+ activityComponent);
return false;
@@ -378,11 +402,11 @@
&& mDisplayCategories.contains(activityInfo.requiredDisplayCategory);
}
- private boolean isAllowedByPolicy(boolean allowedByDefault, ArraySet<ComponentName> exceptions,
- ComponentName component) {
- // Either allowed and the exceptions do not contain the component,
- // or disallowed and the exceptions contain the component.
- return allowedByDefault != exceptions.contains(component);
+ private static boolean isAllowedByPolicy(boolean allowedByDefault,
+ Set<ComponentName> exemptions, ComponentName component) {
+ // Either allowed and the exemptions do not contain the component,
+ // or disallowed and the exemptions contain the component.
+ return allowedByDefault != exemptions.contains(component);
}
@VisibleForTesting
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index 8f765e4..3b13410 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -22,6 +22,7 @@
import static android.companion.virtual.VirtualDeviceParams.ACTIVITY_POLICY_DEFAULT_ALLOWED;
import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
import static android.companion.virtual.VirtualDeviceParams.NAVIGATION_POLICY_DEFAULT_ALLOWED;
+import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_ACTIVITY;
import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_RECENTS;
import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
@@ -93,7 +94,6 @@
import android.view.WindowManager;
import android.widget.Toast;
-
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.BlockedAppStreamingActivity;
@@ -174,11 +174,15 @@
@NonNull
private final VirtualDevice mPublicVirtualDeviceObject;
+ @GuardedBy("mVirtualDeviceLock")
+ @NonNull
+ private final Set<ComponentName> mActivityPolicyExemptions;
+
private ActivityListener createListenerAdapter() {
return new ActivityListener() {
@Override
- public void onTopActivityChanged(int displayId, ComponentName topActivity) {
+ public void onTopActivityChanged(int displayId, @NonNull ComponentName topActivity) {
try {
mActivityListener.onTopActivityChanged(displayId, topActivity,
UserHandle.USER_NULL);
@@ -188,7 +192,7 @@
}
@Override
- public void onTopActivityChanged(int displayId, ComponentName topActivity,
+ public void onTopActivityChanged(int displayId, @NonNull ComponentName topActivity,
@UserIdInt int userId) {
try {
mActivityListener.onTopActivityChanged(displayId, topActivity, userId);
@@ -295,6 +299,18 @@
mPublicVirtualDeviceObject = new VirtualDevice(
this, getDeviceId(), getPersistentDeviceId(), mParams.getName());
+
+ if (Flags.dynamicPolicy()) {
+ mActivityPolicyExemptions = new ArraySet<>(
+ mParams.getDevicePolicy(POLICY_TYPE_ACTIVITY) == DEVICE_POLICY_DEFAULT
+ ? mParams.getBlockedActivities()
+ : mParams.getAllowedActivities());
+ } else {
+ mActivityPolicyExemptions =
+ mParams.getDefaultActivityPolicy() == ACTIVITY_POLICY_DEFAULT_ALLOWED
+ ? mParams.getBlockedActivities()
+ : mParams.getAllowedActivities();
+ }
}
@VisibleForTesting
@@ -414,6 +430,34 @@
}
}
+ @Override // Binder call
+ @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ public void addActivityPolicyExemption(@NonNull ComponentName componentName) {
+ super.addActivityPolicyExemption_enforcePermission();
+ synchronized (mVirtualDeviceLock) {
+ if (mActivityPolicyExemptions.add(componentName)) {
+ for (int i = 0; i < mVirtualDisplays.size(); i++) {
+ mVirtualDisplays.valueAt(i).getWindowPolicyController()
+ .addActivityPolicyExemption(componentName);
+ }
+ }
+ }
+ }
+
+ @Override // Binder call
+ @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ public void removeActivityPolicyExemption(@NonNull ComponentName componentName) {
+ super.removeActivityPolicyExemption_enforcePermission();
+ synchronized (mVirtualDeviceLock) {
+ if (mActivityPolicyExemptions.remove(componentName)) {
+ for (int i = 0; i < mVirtualDisplays.size(); i++) {
+ mVirtualDisplays.valueAt(i).getWindowPolicyController()
+ .removeActivityPolicyExemption(componentName);
+ }
+ }
+ }
+ }
+
private void sendPendingIntent(int displayId, PendingIntent pendingIntent)
throws PendingIntent.CanceledException {
final ActivityOptions options = ActivityOptions.makeBasic().setLaunchDisplayId(displayId);
@@ -543,6 +587,16 @@
}
}
break;
+ case POLICY_TYPE_ACTIVITY:
+ synchronized (mVirtualDeviceLock) {
+ mDevicePolicies.put(policyType, devicePolicy);
+ for (int i = 0; i < mVirtualDisplays.size(); i++) {
+ mVirtualDisplays.valueAt(i).getWindowPolicyController()
+ .setActivityLaunchDefaultAllowed(
+ devicePolicy == DEVICE_POLICY_DEFAULT);
+ }
+ }
+ break;
default:
throw new IllegalArgumentException("Device policy " + policyType
+ " cannot be changed at runtime. ");
@@ -840,24 +894,26 @@
mSensorController.dump(fout);
}
- private GenericWindowPolicyController createWindowPolicyController(
+ @GuardedBy("mVirtualDeviceLock")
+ private GenericWindowPolicyController createWindowPolicyControllerLocked(
@NonNull Set<String> displayCategories) {
final boolean activityLaunchAllowedByDefault =
- mParams.getDefaultActivityPolicy() == ACTIVITY_POLICY_DEFAULT_ALLOWED;
+ Flags.dynamicPolicy()
+ ? getDevicePolicy(POLICY_TYPE_ACTIVITY) == DEVICE_POLICY_DEFAULT
+ : mParams.getDefaultActivityPolicy() == ACTIVITY_POLICY_DEFAULT_ALLOWED;
final boolean crossTaskNavigationAllowedByDefault =
mParams.getDefaultNavigationPolicy() == NAVIGATION_POLICY_DEFAULT_ALLOWED;
final boolean showTasksInHostDeviceRecents =
- mParams.getDevicePolicy(POLICY_TYPE_RECENTS) == DEVICE_POLICY_DEFAULT;
+ getDevicePolicy(POLICY_TYPE_RECENTS) == DEVICE_POLICY_DEFAULT;
final GenericWindowPolicyController gwpc = new GenericWindowPolicyController(
FLAG_SECURE,
SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS,
getAllowedUserHandles(),
activityLaunchAllowedByDefault,
- /*activityPolicyExceptions=*/activityLaunchAllowedByDefault
- ? mParams.getBlockedActivities() : mParams.getAllowedActivities(),
+ mActivityPolicyExemptions,
crossTaskNavigationAllowedByDefault,
- /*crossTaskNavigationExceptions=*/crossTaskNavigationAllowedByDefault
+ /*crossTaskNavigationExemptions=*/crossTaskNavigationAllowedByDefault
? mParams.getBlockedCrossTaskNavigations()
: mParams.getAllowedCrossTaskNavigations(),
createListenerAdapter(),
@@ -873,8 +929,10 @@
int createVirtualDisplay(@NonNull VirtualDisplayConfig virtualDisplayConfig,
@NonNull IVirtualDisplayCallback callback, String packageName) {
- GenericWindowPolicyController gwpc = createWindowPolicyController(
- virtualDisplayConfig.getDisplayCategories());
+ GenericWindowPolicyController gwpc;
+ synchronized (mVirtualDeviceLock) {
+ gwpc = createWindowPolicyControllerLocked(virtualDisplayConfig.getDisplayCategories());
+ }
DisplayManagerInternal displayManager = LocalServices.getService(
DisplayManagerInternal.class);
int displayId;
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 7329f1a..b941aaf 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -180,7 +180,6 @@
"android.hardware.rebootescrow-V1-java",
"android.hardware.power.stats-V2-java",
"android.hidl.manager-V1.2-java",
- "com.android.server.security.flags-aconfig-java",
"cbor-java",
"display_flags_lib",
"icu4j_calendar_astronomer",
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 0d265a0..b5911f6 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -9543,6 +9543,14 @@
}
} else {
worker.start();
+ if (process != null && process.mPid == MY_PID && "crash".equals(eventType)) {
+ // We're actually crashing, let's wait for up to 2 seconds before killing ourselves,
+ // so the data could be persisted into the dropbox.
+ try {
+ worker.join(2000);
+ } catch (InterruptedException ignored) {
+ }
+ }
}
}
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index d3176ee4..87077a6 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -2823,9 +2823,7 @@
}
}
- if (schedGroup < SCHED_GROUP_TOP_APP
- && cr.hasFlag(Context.BIND_SCHEDULE_LIKE_TOP_APP)
- && clientIsSystem) {
+ if (cr.hasFlag(Context.BIND_SCHEDULE_LIKE_TOP_APP) && clientIsSystem) {
schedGroup = SCHED_GROUP_TOP_APP;
state.setScheduleLikeTopApp(true);
}
diff --git a/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java b/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java
index fdf607d..64691e0 100644
--- a/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java
+++ b/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java
@@ -53,6 +53,9 @@
static final int MAXIMUM_ENROLLMENT_NOTIFICATIONS = 1;
@NonNull private final Context mContext;
+ @NonNull private final PackageManager mPackageManager;
+ @NonNull private final FaceManager mFaceManager;
+ @NonNull private final FingerprintManager mFingerprintManager;
private final float mThreshold;
private final int mModality;
@@ -86,6 +89,10 @@
mModality = modality;
mBiometricNotification = biometricNotification;
+ mPackageManager = context.getPackageManager();
+ mFaceManager = mContext.getSystemService(FaceManager.class);
+ mFingerprintManager = mContext.getSystemService(FingerprintManager.class);
+
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_USER_REMOVED);
context.registerReceiver(mBroadcastReceiver, intentFilter);
@@ -108,6 +115,13 @@
/** Update total authentication and rejected attempts. */
public void authenticate(int userId, boolean authenticated) {
+
+ // Don't collect data for single-modality devices or user has both biometrics enrolled.
+ if (isSingleModalityDevice()
+ || (hasEnrolledFace(userId) && hasEnrolledFingerprint(userId))) {
+ return;
+ }
+
// SharedPreference is not ready when starting system server, initialize
// mUserAuthenticationStatsMap in authentication to ensure SharedPreference
// is ready for application use.
@@ -150,25 +164,9 @@
authenticationStats.resetData();
- final PackageManager packageManager = mContext.getPackageManager();
+ final boolean hasEnrolledFace = hasEnrolledFace(userId);
+ final boolean hasEnrolledFingerprint = hasEnrolledFingerprint(userId);
- // Don't send notification to single-modality devices.
- if (!packageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)
- || !packageManager.hasSystemFeature(PackageManager.FEATURE_FACE)) {
- return;
- }
-
- final FaceManager faceManager = mContext.getSystemService(FaceManager.class);
- final boolean hasEnrolledFace = faceManager.hasEnrolledTemplates(userId);
-
- final FingerprintManager fingerprintManager = mContext
- .getSystemService(FingerprintManager.class);
- final boolean hasEnrolledFingerprint = fingerprintManager.hasEnrolledTemplates(userId);
-
- // Don't send notification when both face and fingerprint are enrolled.
- if (hasEnrolledFace && hasEnrolledFingerprint) {
- return;
- }
if (hasEnrolledFace && !hasEnrolledFingerprint) {
mBiometricNotification.sendFpEnrollNotification(mContext);
authenticationStats.updateNotificationCounter();
@@ -199,6 +197,19 @@
}
}
+ private boolean isSingleModalityDevice() {
+ return !mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)
+ || !mPackageManager.hasSystemFeature(PackageManager.FEATURE_FACE);
+ }
+
+ private boolean hasEnrolledFace(int userId) {
+ return mFaceManager.hasEnrolledTemplates(userId);
+ }
+
+ private boolean hasEnrolledFingerprint(int userId) {
+ return mFingerprintManager.hasEnrolledTemplates(userId);
+ }
+
/**
* Only being used in tests. Callers should not make any changes to the returned
* authentication stats.
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricNotificationUtils.java b/services/core/java/com/android/server/biometrics/sensors/BiometricNotificationUtils.java
index f1c74f0..2aec9ae 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricNotificationUtils.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricNotificationUtils.java
@@ -176,6 +176,7 @@
.setSmallIcon(R.drawable.ic_lock)
.setContentTitle(title)
.setContentText(content)
+ .setStyle(new Notification.BigTextStyle().bigText(content))
.setSubText(name)
.setOnlyAlertOnce(true)
.setLocalOnly(true)
diff --git a/services/core/java/com/android/server/broadcastradio/aidl/ConversionUtils.java b/services/core/java/com/android/server/broadcastradio/aidl/ConversionUtils.java
index adea13f..d4232ab 100644
--- a/services/core/java/com/android/server/broadcastradio/aidl/ConversionUtils.java
+++ b/services/core/java/com/android/server/broadcastradio/aidl/ConversionUtils.java
@@ -29,7 +29,6 @@
import android.hardware.broadcastradio.ProgramFilter;
import android.hardware.broadcastradio.ProgramIdentifier;
import android.hardware.broadcastradio.ProgramInfo;
-import android.hardware.broadcastradio.ProgramListChunk;
import android.hardware.broadcastradio.Properties;
import android.hardware.broadcastradio.Result;
import android.hardware.broadcastradio.VendorKeyValue;
@@ -38,6 +37,7 @@
import android.hardware.radio.RadioManager;
import android.hardware.radio.RadioMetadata;
import android.hardware.radio.RadioTuner;
+import android.hardware.radio.UniqueProgramIdentifier;
import android.os.Build;
import android.os.ParcelableException;
import android.os.ServiceSpecificException;
@@ -553,31 +553,6 @@
return hwFilter;
}
- static ProgramList.Chunk chunkFromHalProgramListChunk(ProgramListChunk chunk) {
- Set<RadioManager.ProgramInfo> modified = new ArraySet<>(chunk.modified.length);
- for (int i = 0; i < chunk.modified.length; i++) {
- RadioManager.ProgramInfo modifiedInfo =
- programInfoFromHalProgramInfo(chunk.modified[i]);
- if (modifiedInfo == null) {
- Slogf.w(TAG, "Program info %s in program list chunk is not valid",
- chunk.modified[i]);
- continue;
- }
- modified.add(modifiedInfo);
- }
- Set<ProgramSelector.Identifier> removed = new ArraySet<>();
- if (chunk.removed != null) {
- for (int i = 0; i < chunk.removed.length; i++) {
- ProgramSelector.Identifier removedId =
- identifierFromHalProgramIdentifier(chunk.removed[i]);
- if (removedId != null) {
- removed.add(removedId);
- }
- }
- }
- return new ProgramList.Chunk(chunk.purge, chunk.complete, modified, removed);
- }
-
private static boolean isNewIdentifierInU(ProgramSelector.Identifier id) {
return id.getType() == ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT;
}
@@ -630,11 +605,11 @@
modified.add(info);
}
}
- Set<ProgramSelector.Identifier> removed = new ArraySet<>();
- Iterator<ProgramSelector.Identifier> removedIterator = chunk.getRemoved().iterator();
+ Set<UniqueProgramIdentifier> removed = new ArraySet<>();
+ Iterator<UniqueProgramIdentifier> removedIterator = chunk.getRemoved().iterator();
while (removedIterator.hasNext()) {
- ProgramSelector.Identifier id = removedIterator.next();
- if (!isNewIdentifierInU(id)) {
+ UniqueProgramIdentifier id = removedIterator.next();
+ if (!isNewIdentifierInU(id.getPrimaryId())) {
removed.add(id);
}
}
diff --git a/services/core/java/com/android/server/broadcastradio/aidl/ProgramInfoCache.java b/services/core/java/com/android/server/broadcastradio/aidl/ProgramInfoCache.java
index c9ae735..756dbbb 100644
--- a/services/core/java/com/android/server/broadcastradio/aidl/ProgramInfoCache.java
+++ b/services/core/java/com/android/server/broadcastradio/aidl/ProgramInfoCache.java
@@ -17,9 +17,11 @@
package com.android.server.broadcastradio.aidl;
import android.annotation.Nullable;
+import android.hardware.broadcastradio.ProgramListChunk;
import android.hardware.radio.ProgramList;
-import android.hardware.radio.ProgramSelector;
+import android.hardware.radio.ProgramSelector.Identifier;
import android.hardware.radio.RadioManager;
+import android.hardware.radio.UniqueProgramIdentifier;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -30,7 +32,6 @@
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
-import java.util.Map;
import java.util.Set;
/**
@@ -40,24 +41,25 @@
private static final String TAG = "BcRadioAidlSrv.cache";
/**
- * Maximum number of {@link RadioManager#ProgramInfo} elements that will be put into a
+ * Maximum number of {@link RadioManager.ProgramInfo} elements that will be put into a
* ProgramList.Chunk.mModified array. Used to try to ensure a single ProgramList.Chunk
* stays within the AIDL data size limit.
*/
private static final int MAX_NUM_MODIFIED_PER_CHUNK = 100;
/**
- * Maximum number of {@link ProgramSelector#Identifier} elements that will be put
- * into the removed array of {@link ProgramList#Chunk}. Used to try to ensure a single
- * {@link ProgramList#Chunk} stays within the AIDL data size limit.
+ * Maximum number of {@link Identifier} elements that will be put into the removed array
+ * of {@link ProgramList.Chunk}. Use to attempt and keep the single {@link ProgramList.Chunk}
+ * within the AIDL data size limit.
*/
private static final int MAX_NUM_REMOVED_PER_CHUNK = 500;
/**
- * Map from primary identifier to corresponding {@link RadioManager#ProgramInfo}.
+ * Map from primary identifier to {@link UniqueProgramIdentifier} and then to corresponding
+ * {@link RadioManager.ProgramInfo}.
*/
- private final Map<ProgramSelector.Identifier, RadioManager.ProgramInfo> mProgramInfoMap =
- new ArrayMap<>();
+ private final ArrayMap<Identifier, ArrayMap<UniqueProgramIdentifier, RadioManager.ProgramInfo>>
+ mProgramInfoMap = new ArrayMap<>();
/**
* Flag indicating whether mProgramInfoMap is considered complete based upon the received
@@ -81,13 +83,17 @@
mFilter = filter;
mComplete = complete;
for (int i = 0; i < programInfos.length; i++) {
- mProgramInfoMap.put(programInfos[i].getSelector().getPrimaryId(), programInfos[i]);
+ putInfo(programInfos[i]);
}
}
@VisibleForTesting
List<RadioManager.ProgramInfo> toProgramInfoList() {
- return new ArrayList<>(mProgramInfoMap.values());
+ List<RadioManager.ProgramInfo> programInfoList = new ArrayList<>();
+ for (int index = 0; index < mProgramInfoMap.size(); index++) {
+ programInfoList.addAll(mProgramInfoMap.valueAt(index).values());
+ }
+ return programInfoList;
}
@Override
@@ -97,10 +103,14 @@
sb.append(", mFilter = ");
sb.append(mFilter);
sb.append(", mProgramInfoMap = [");
- mProgramInfoMap.forEach((id, programInfo) -> {
- sb.append(", ");
- sb.append(programInfo);
- });
+ for (int index = 0; index < mProgramInfoMap.size(); index++) {
+ ArrayMap<UniqueProgramIdentifier, RadioManager.ProgramInfo> entries =
+ mProgramInfoMap.valueAt(index);
+ for (int entryIndex = 0; entryIndex < entries.size(); entryIndex++) {
+ sb.append(", ");
+ sb.append(entries.valueAt(entryIndex));
+ }
+ }
return sb.append("])").toString();
}
@@ -114,8 +124,7 @@
}
@VisibleForTesting
- void updateFromHalProgramListChunk(
- android.hardware.broadcastradio.ProgramListChunk chunk) {
+ void updateFromHalProgramListChunk(ProgramListChunk chunk) {
if (chunk.purge) {
mProgramInfoMap.clear();
}
@@ -125,8 +134,9 @@
if (programInfo == null) {
Slogf.e(TAG, "Program info in program info %s in chunk is not valid",
chunk.modified[i]);
+ continue;
}
- mProgramInfoMap.put(programInfo.getSelector().getPrimaryId(), programInfo);
+ putInfo(programInfo);
}
if (chunk.removed != null) {
for (int i = 0; i < chunk.removed.length; i++) {
@@ -155,25 +165,31 @@
purge = true;
}
- Set<RadioManager.ProgramInfo> modified = new ArraySet<>();
- Set<ProgramSelector.Identifier> removed = new ArraySet<>(mProgramInfoMap.keySet());
- for (Map.Entry<ProgramSelector.Identifier, RadioManager.ProgramInfo> entry
- : other.mProgramInfoMap.entrySet()) {
- ProgramSelector.Identifier id = entry.getKey();
+ ArraySet<RadioManager.ProgramInfo> modified = new ArraySet<>();
+ ArraySet<UniqueProgramIdentifier> removed = new ArraySet<>();
+ for (int index = 0; index < mProgramInfoMap.size(); index++) {
+ removed.addAll(mProgramInfoMap.valueAt(index).keySet());
+ }
+ for (int index = 0; index < other.mProgramInfoMap.size(); index++) {
+ Identifier id = other.mProgramInfoMap.keyAt(index);
if (!passesFilter(id)) {
continue;
}
- removed.remove(id);
+ ArrayMap<UniqueProgramIdentifier, RadioManager.ProgramInfo> entries =
+ other.mProgramInfoMap.valueAt(index);
+ for (int entryIndex = 0; entryIndex < entries.size(); entryIndex++) {
+ removed.remove(entries.keyAt(entryIndex));
- RadioManager.ProgramInfo newInfo = entry.getValue();
- if (!shouldIncludeInModified(newInfo)) {
- continue;
+ RadioManager.ProgramInfo newInfo = entries.valueAt(entryIndex);
+ if (!shouldIncludeInModified(newInfo)) {
+ continue;
+ }
+ putInfo(newInfo);
+ modified.add(newInfo);
}
- mProgramInfoMap.put(id, newInfo);
- modified.add(newInfo);
}
- for (ProgramSelector.Identifier rem : removed) {
- mProgramInfoMap.remove(rem);
+ for (int removedIndex = 0; removedIndex < removed.size(); removedIndex++) {
+ removeUniqueId(removed.valueAt(removedIndex));
}
mComplete = other.mComplete;
return buildChunks(purge, mComplete, modified, maxNumModifiedPerChunk, removed,
@@ -181,45 +197,61 @@
}
@Nullable
- List<ProgramList.Chunk> filterAndApplyChunk(ProgramList.Chunk chunk) {
+ List<ProgramList.Chunk> filterAndApplyChunk(ProgramListChunk chunk) {
return filterAndApplyChunkInternal(chunk, MAX_NUM_MODIFIED_PER_CHUNK,
MAX_NUM_REMOVED_PER_CHUNK);
}
@VisibleForTesting
@Nullable
- List<ProgramList.Chunk> filterAndApplyChunkInternal(ProgramList.Chunk chunk,
+ List<ProgramList.Chunk> filterAndApplyChunkInternal(ProgramListChunk chunk,
int maxNumModifiedPerChunk, int maxNumRemovedPerChunk) {
- if (chunk.isPurge()) {
+ if (chunk.purge) {
mProgramInfoMap.clear();
}
Set<RadioManager.ProgramInfo> modified = new ArraySet<>();
- Set<ProgramSelector.Identifier> removed = new ArraySet<>();
- for (RadioManager.ProgramInfo info : chunk.getModified()) {
- ProgramSelector.Identifier id = info.getSelector().getPrimaryId();
- if (!passesFilter(id) || !shouldIncludeInModified(info)) {
+ for (int i = 0; i < chunk.modified.length; i++) {
+ RadioManager.ProgramInfo info =
+ ConversionUtils.programInfoFromHalProgramInfo(chunk.modified[i]);
+ if (info == null) {
+ Slogf.w(TAG, "Program info %s in program list chunk is not valid",
+ chunk.modified[i]);
continue;
}
- mProgramInfoMap.put(id, info);
+ Identifier primaryId = info.getSelector().getPrimaryId();
+ if (!passesFilter(primaryId) || !shouldIncludeInModified(info)) {
+ continue;
+ }
+ putInfo(info);
modified.add(info);
}
- for (ProgramSelector.Identifier id : chunk.getRemoved()) {
- if (mProgramInfoMap.containsKey(id)) {
- mProgramInfoMap.remove(id);
- removed.add(id);
+ Set<UniqueProgramIdentifier> removed = new ArraySet<>();
+ if (chunk.removed != null) {
+ for (int i = 0; i < chunk.removed.length; i++) {
+ Identifier removedId = ConversionUtils.identifierFromHalProgramIdentifier(
+ chunk.removed[i]);
+ if (removedId == null) {
+ Slogf.w(TAG, "Removed identifier %s in program list chunk is not valid",
+ chunk.modified[i]);
+ continue;
+ }
+ if (mProgramInfoMap.containsKey(removedId)) {
+ removed.addAll(mProgramInfoMap.get(removedId).keySet());
+ mProgramInfoMap.remove(removedId);
+ }
}
}
- if (modified.isEmpty() && removed.isEmpty() && mComplete == chunk.isComplete()
- && !chunk.isPurge()) {
+ if (modified.isEmpty() && removed.isEmpty() && mComplete == chunk.complete
+ && !chunk.purge) {
return null;
}
- mComplete = chunk.isComplete();
- return buildChunks(chunk.isPurge(), mComplete, modified, maxNumModifiedPerChunk, removed,
+ mComplete = chunk.complete;
+ return buildChunks(chunk.purge, mComplete, modified, maxNumModifiedPerChunk, removed,
maxNumRemovedPerChunk);
}
- private boolean passesFilter(ProgramSelector.Identifier id) {
+ private boolean passesFilter(Identifier id) {
if (mFilter == null) {
return true;
}
@@ -233,9 +265,32 @@
return mFilter.areCategoriesIncluded() || !id.isCategoryType();
}
+ private void putInfo(RadioManager.ProgramInfo info) {
+ Identifier primaryId = info.getSelector().getPrimaryId();
+ if (!mProgramInfoMap.containsKey(primaryId)) {
+ mProgramInfoMap.put(primaryId, new ArrayMap<>());
+ }
+ mProgramInfoMap.get(primaryId).put(new UniqueProgramIdentifier(info.getSelector()), info);
+ }
+
+ private void removeUniqueId(UniqueProgramIdentifier uniqueId) {
+ Identifier primaryId = uniqueId.getPrimaryId();
+ if (!mProgramInfoMap.containsKey(primaryId)) {
+ return;
+ }
+ mProgramInfoMap.get(primaryId).remove(uniqueId);
+ if (mProgramInfoMap.get(primaryId).isEmpty()) {
+ mProgramInfoMap.remove(primaryId);
+ }
+ }
+
private boolean shouldIncludeInModified(RadioManager.ProgramInfo newInfo) {
- RadioManager.ProgramInfo oldInfo = mProgramInfoMap.get(
- newInfo.getSelector().getPrimaryId());
+ Identifier primaryId = newInfo.getSelector().getPrimaryId();
+ RadioManager.ProgramInfo oldInfo = null;
+ if (mProgramInfoMap.containsKey(primaryId)) {
+ UniqueProgramIdentifier uniqueId = new UniqueProgramIdentifier(newInfo.getSelector());
+ oldInfo = mProgramInfoMap.get(primaryId).get(uniqueId);
+ }
if (oldInfo == null) {
return true;
}
@@ -251,7 +306,7 @@
private static List<ProgramList.Chunk> buildChunks(boolean purge, boolean complete,
@Nullable Collection<RadioManager.ProgramInfo> modified, int maxNumModifiedPerChunk,
- @Nullable Collection<ProgramSelector.Identifier> removed, int maxNumRemovedPerChunk) {
+ @Nullable Collection<UniqueProgramIdentifier> removed, int maxNumRemovedPerChunk) {
// Communication protocol requires that if purge is set, removed is empty.
if (purge) {
removed = null;
@@ -275,7 +330,7 @@
int modifiedPerChunk = 0;
int removedPerChunk = 0;
Iterator<RadioManager.ProgramInfo> modifiedIter = null;
- Iterator<ProgramSelector.Identifier> removedIter = null;
+ Iterator<UniqueProgramIdentifier> removedIter = null;
if (modified != null) {
modifiedPerChunk = roundUpFraction(modified.size(), numChunks);
modifiedIter = modified.iterator();
@@ -287,7 +342,7 @@
List<ProgramList.Chunk> chunks = new ArrayList<>(numChunks);
for (int i = 0; i < numChunks; i++) {
ArraySet<RadioManager.ProgramInfo> modifiedChunk = new ArraySet<>();
- ArraySet<ProgramSelector.Identifier> removedChunk = new ArraySet<>();
+ ArraySet<UniqueProgramIdentifier> removedChunk = new ArraySet<>();
if (modifiedIter != null) {
for (int j = 0; j < modifiedPerChunk && modifiedIter.hasNext(); j++) {
modifiedChunk.add(modifiedIter.next());
diff --git a/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java b/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java
index 7c87c6c..2ae7f95 100644
--- a/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java
+++ b/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java
@@ -142,12 +142,11 @@
public void onProgramListUpdated(ProgramListChunk programListChunk) {
fireLater(() -> {
synchronized (mLock) {
- android.hardware.radio.ProgramList.Chunk chunk =
- ConversionUtils.chunkFromHalProgramListChunk(programListChunk);
- mProgramInfoCache.filterAndApplyChunk(chunk);
+ mProgramInfoCache.filterAndApplyChunk(programListChunk);
for (int i = 0; i < mAidlTunerSessions.size(); i++) {
- mAidlTunerSessions.valueAt(i).onMergedProgramListUpdateFromHal(chunk);
+ mAidlTunerSessions.valueAt(i).onMergedProgramListUpdateFromHal(
+ programListChunk);
}
}
});
diff --git a/services/core/java/com/android/server/broadcastradio/aidl/TunerSession.java b/services/core/java/com/android/server/broadcastradio/aidl/TunerSession.java
index beff7bd..4ed36ec 100644
--- a/services/core/java/com/android/server/broadcastradio/aidl/TunerSession.java
+++ b/services/core/java/com/android/server/broadcastradio/aidl/TunerSession.java
@@ -20,6 +20,7 @@
import android.graphics.Bitmap;
import android.hardware.broadcastradio.ConfigFlag;
import android.hardware.broadcastradio.IBroadcastRadio;
+import android.hardware.broadcastradio.ProgramListChunk;
import android.hardware.radio.ITuner;
import android.hardware.radio.ProgramList;
import android.hardware.radio.ProgramSelector;
@@ -297,7 +298,7 @@
}
}
- void onMergedProgramListUpdateFromHal(ProgramList.Chunk mergedChunk) {
+ void onMergedProgramListUpdateFromHal(ProgramListChunk mergedChunk) {
List<ProgramList.Chunk> clientUpdateChunks;
synchronized (mLock) {
if (mProgramInfoCache == null) {
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/Convert.java b/services/core/java/com/android/server/broadcastradio/hal2/Convert.java
index e6908b1..fb1138f 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/Convert.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/Convert.java
@@ -28,7 +28,6 @@
import android.hardware.broadcastradio.V2_0.ProgramFilter;
import android.hardware.broadcastradio.V2_0.ProgramIdentifier;
import android.hardware.broadcastradio.V2_0.ProgramInfo;
-import android.hardware.broadcastradio.V2_0.ProgramListChunk;
import android.hardware.broadcastradio.V2_0.Properties;
import android.hardware.broadcastradio.V2_0.Result;
import android.hardware.broadcastradio.V2_0.VendorKeyValue;
@@ -425,16 +424,6 @@
return hwFilter;
}
- static @NonNull ProgramList.Chunk programListChunkFromHal(@NonNull ProgramListChunk chunk) {
- Set<RadioManager.ProgramInfo> modified = chunk.modified.stream().
- map(info -> programInfoFromHal(info)).collect(Collectors.toSet());
- Set<ProgramSelector.Identifier> removed = chunk.removed.stream().
- map(id -> Objects.requireNonNull(programIdentifierFromHal(id))).
- collect(Collectors.toSet());
-
- return new ProgramList.Chunk(chunk.purge, chunk.complete, modified, removed);
- }
-
public static @NonNull android.hardware.radio.Announcement announcementFromHal(
@NonNull Announcement hwAnnouncement) {
return new android.hardware.radio.Announcement(
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/ProgramInfoCache.java b/services/core/java/com/android/server/broadcastradio/hal2/ProgramInfoCache.java
index 9831af6..111953d 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/ProgramInfoCache.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/ProgramInfoCache.java
@@ -16,21 +16,21 @@
package com.android.server.broadcastradio.hal2;
-import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.hardware.broadcastradio.V2_0.ProgramListChunk;
import android.hardware.radio.ProgramList;
-import android.hardware.radio.ProgramSelector;
+import android.hardware.radio.ProgramSelector.Identifier;
import android.hardware.radio.RadioManager;
+import android.hardware.radio.UniqueProgramIdentifier;
+import android.util.ArrayMap;
+import android.util.ArraySet;
import com.android.internal.annotations.VisibleForTesting;
import java.util.ArrayList;
import java.util.Collection;
-import java.util.HashMap;
-import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
-import java.util.Map;
import java.util.Set;
final class ProgramInfoCache {
@@ -40,13 +40,14 @@
private static final int MAX_NUM_MODIFIED_PER_CHUNK = 100;
// Maximum number of ProgramSelector.Identifier elements that will be put into a
- // ProgramList.Chunk.mRemoved array. Used to try to ensure a single ProgramList.Chunk stays
+ // ProgramList.Chunk.mRemoved array. Use to attempt and keep the single ProgramList.Chunk
// within the AIDL data size limit.
private static final int MAX_NUM_REMOVED_PER_CHUNK = 500;
- // Map from primary identifier to corresponding ProgramInfo.
- private final Map<ProgramSelector.Identifier, RadioManager.ProgramInfo> mProgramInfoMap =
- new HashMap<>();
+ // Map from primary identifier to a map of unique identifiers and program info, where the
+ // containing map has unique identifiers to program info.
+ private final ArrayMap<Identifier, ArrayMap<UniqueProgramIdentifier, RadioManager.ProgramInfo>>
+ mProgramInfoMap = new ArrayMap<>();
// Flag indicating whether mProgramInfoMap is considered complete based upon the received
// updates.
@@ -66,18 +67,18 @@
RadioManager.ProgramInfo... programInfos) {
mFilter = filter;
mComplete = complete;
- for (RadioManager.ProgramInfo programInfo : programInfos) {
- mProgramInfoMap.put(programInfo.getSelector().getPrimaryId(), programInfo);
+ for (int i = 0; i < programInfos.length; i++) {
+ putInfo(programInfos[i]);
}
}
@VisibleForTesting
- boolean programInfosAreExactly(RadioManager.ProgramInfo... programInfos) {
- Map<ProgramSelector.Identifier, RadioManager.ProgramInfo> expectedMap = new HashMap<>();
- for (RadioManager.ProgramInfo programInfo : programInfos) {
- expectedMap.put(programInfo.getSelector().getPrimaryId(), programInfo);
+ List<RadioManager.ProgramInfo> toProgramInfoList() {
+ List<RadioManager.ProgramInfo> programInfoList = new ArrayList<>();
+ for (int index = 0; index < mProgramInfoMap.size(); index++) {
+ programInfoList.addAll(mProgramInfoMap.valueAt(index).values());
}
- return expectedMap.equals(mProgramInfoMap);
+ return programInfoList;
}
@Override
@@ -87,10 +88,14 @@
sb.append(", mFilter = ");
sb.append(mFilter);
sb.append(", mProgramInfoMap = [");
- mProgramInfoMap.forEach((id, programInfo) -> {
- sb.append("\n");
- sb.append(programInfo.toString());
- });
+ for (int index = 0; index < mProgramInfoMap.size(); index++) {
+ ArrayMap<UniqueProgramIdentifier, RadioManager.ProgramInfo> entries =
+ mProgramInfoMap.valueAt(index);
+ for (int entryIndex = 0; entryIndex < entries.size(); entryIndex++) {
+ sb.append(", ");
+ sb.append(entries.valueAt(entryIndex));
+ }
+ }
sb.append("]");
return sb.toString();
}
@@ -103,14 +108,13 @@
return mFilter;
}
- void updateFromHalProgramListChunk(
- @NonNull android.hardware.broadcastradio.V2_0.ProgramListChunk chunk) {
+ void updateFromHalProgramListChunk(ProgramListChunk chunk) {
if (chunk.purge) {
mProgramInfoMap.clear();
}
for (android.hardware.broadcastradio.V2_0.ProgramInfo halProgramInfo : chunk.modified) {
RadioManager.ProgramInfo programInfo = Convert.programInfoFromHal(halProgramInfo);
- mProgramInfoMap.put(programInfo.getSelector().getPrimaryId(), programInfo);
+ putInfo(programInfo);
}
for (android.hardware.broadcastradio.V2_0.ProgramIdentifier halProgramId : chunk.removed) {
mProgramInfoMap.remove(Convert.programIdentifierFromHal(halProgramId));
@@ -118,14 +122,13 @@
mComplete = chunk.complete;
}
- @NonNull List<ProgramList.Chunk> filterAndUpdateFrom(@NonNull ProgramInfoCache other,
- boolean purge) {
+ List<ProgramList.Chunk> filterAndUpdateFrom(ProgramInfoCache other, boolean purge) {
return filterAndUpdateFromInternal(other, purge, MAX_NUM_MODIFIED_PER_CHUNK,
MAX_NUM_REMOVED_PER_CHUNK);
}
@VisibleForTesting
- @NonNull List<ProgramList.Chunk> filterAndUpdateFromInternal(@NonNull ProgramInfoCache other,
+ List<ProgramList.Chunk> filterAndUpdateFromInternal(ProgramInfoCache other,
boolean purge, int maxNumModifiedPerChunk, int maxNumRemovedPerChunk) {
if (purge) {
mProgramInfoMap.clear();
@@ -136,69 +139,82 @@
purge = true;
}
- Set<RadioManager.ProgramInfo> modified = new HashSet<>();
- Set<ProgramSelector.Identifier> removed = new HashSet<>(mProgramInfoMap.keySet());
- for (Map.Entry<ProgramSelector.Identifier, RadioManager.ProgramInfo> entry
- : other.mProgramInfoMap.entrySet()) {
- ProgramSelector.Identifier id = entry.getKey();
+ ArraySet<RadioManager.ProgramInfo> modified = new ArraySet<>();
+ ArraySet<UniqueProgramIdentifier> removed = new ArraySet<>();
+ for (int index = 0; index < mProgramInfoMap.size(); index++) {
+ removed.addAll(mProgramInfoMap.valueAt(index).keySet());
+ }
+ for (int index = 0; index < other.mProgramInfoMap.size(); index++) {
+ Identifier id = other.mProgramInfoMap.keyAt(index);
if (!passesFilter(id)) {
continue;
}
- removed.remove(id);
+ ArrayMap<UniqueProgramIdentifier, RadioManager.ProgramInfo> entries =
+ other.mProgramInfoMap.valueAt(index);
+ for (int entryIndex = 0; entryIndex < entries.size(); entryIndex++) {
+ removed.remove(entries.keyAt(entryIndex));
- RadioManager.ProgramInfo newInfo = entry.getValue();
- if (!shouldIncludeInModified(newInfo)) {
- continue;
+ RadioManager.ProgramInfo newInfo = entries.valueAt(entryIndex);
+ if (!shouldIncludeInModified(newInfo)) {
+ continue;
+ }
+ putInfo(newInfo);
+ modified.add(newInfo);
}
- mProgramInfoMap.put(id, newInfo);
- modified.add(newInfo);
}
- for (ProgramSelector.Identifier rem : removed) {
- mProgramInfoMap.remove(rem);
+ for (int removedIndex = 0; removedIndex < removed.size(); removedIndex++) {
+ removeUniqueId(removed.valueAt(removedIndex));
}
mComplete = other.mComplete;
return buildChunks(purge, mComplete, modified, maxNumModifiedPerChunk, removed,
maxNumRemovedPerChunk);
}
- @Nullable List<ProgramList.Chunk> filterAndApplyChunk(@NonNull ProgramList.Chunk chunk) {
+ @Nullable
+ List<ProgramList.Chunk> filterAndApplyChunk(ProgramListChunk chunk) {
return filterAndApplyChunkInternal(chunk, MAX_NUM_MODIFIED_PER_CHUNK,
MAX_NUM_REMOVED_PER_CHUNK);
}
@VisibleForTesting
- @Nullable List<ProgramList.Chunk> filterAndApplyChunkInternal(@NonNull ProgramList.Chunk chunk,
+ @Nullable
+ List<ProgramList.Chunk> filterAndApplyChunkInternal(ProgramListChunk chunk,
int maxNumModifiedPerChunk, int maxNumRemovedPerChunk) {
- if (chunk.isPurge()) {
+ if (chunk.purge) {
mProgramInfoMap.clear();
}
- Set<RadioManager.ProgramInfo> modified = new HashSet<>();
- Set<ProgramSelector.Identifier> removed = new HashSet<>();
- for (RadioManager.ProgramInfo info : chunk.getModified()) {
- ProgramSelector.Identifier id = info.getSelector().getPrimaryId();
- if (!passesFilter(id) || !shouldIncludeInModified(info)) {
+ Set<RadioManager.ProgramInfo> modified = new ArraySet<>();
+ for (android.hardware.broadcastradio.V2_0.ProgramInfo halProgramInfo : chunk.modified) {
+ RadioManager.ProgramInfo info = Convert.programInfoFromHal(halProgramInfo);
+ Identifier primaryId = info.getSelector().getPrimaryId();
+ if (!passesFilter(primaryId) || !shouldIncludeInModified(info)) {
continue;
}
- mProgramInfoMap.put(id, info);
+ putInfo(info);
modified.add(info);
}
- for (ProgramSelector.Identifier id : chunk.getRemoved()) {
- if (mProgramInfoMap.containsKey(id)) {
- mProgramInfoMap.remove(id);
- removed.add(id);
+ Set<UniqueProgramIdentifier> removed = new ArraySet<>();
+ for (android.hardware.broadcastradio.V2_0.ProgramIdentifier halProgramId : chunk.removed) {
+ Identifier removedId = Convert.programIdentifierFromHal(halProgramId);
+ if (removedId == null) {
+ continue;
+ }
+ if (mProgramInfoMap.containsKey(removedId)) {
+ removed.addAll(mProgramInfoMap.get(removedId).keySet());
+ mProgramInfoMap.remove(removedId);
}
}
- if (modified.isEmpty() && removed.isEmpty() && mComplete == chunk.isComplete()
- && !chunk.isPurge()) {
+ if (modified.isEmpty() && removed.isEmpty() && mComplete == chunk.complete
+ && !chunk.purge) {
return null;
}
- mComplete = chunk.isComplete();
- return buildChunks(chunk.isPurge(), mComplete, modified, maxNumModifiedPerChunk, removed,
+ mComplete = chunk.complete;
+ return buildChunks(chunk.purge, mComplete, modified, maxNumModifiedPerChunk, removed,
maxNumRemovedPerChunk);
}
- private boolean passesFilter(ProgramSelector.Identifier id) {
+ private boolean passesFilter(Identifier id) {
if (mFilter == null) {
return true;
}
@@ -215,9 +231,33 @@
return true;
}
+ private void putInfo(RadioManager.ProgramInfo info) {
+ Identifier primaryId = info.getSelector().getPrimaryId();
+ if (!mProgramInfoMap.containsKey(primaryId)) {
+ mProgramInfoMap.put(primaryId, new ArrayMap<>());
+ }
+ mProgramInfoMap.get(primaryId).put(new UniqueProgramIdentifier(
+ info.getSelector()), info);
+ }
+
+ private void removeUniqueId(UniqueProgramIdentifier uniqueId) {
+ Identifier primaryId = uniqueId.getPrimaryId();
+ if (!mProgramInfoMap.containsKey(primaryId)) {
+ return;
+ }
+ mProgramInfoMap.get(primaryId).remove(uniqueId);
+ if (mProgramInfoMap.get(primaryId).isEmpty()) {
+ mProgramInfoMap.remove(primaryId);
+ }
+ }
+
private boolean shouldIncludeInModified(RadioManager.ProgramInfo newInfo) {
- RadioManager.ProgramInfo oldInfo = mProgramInfoMap.get(
- newInfo.getSelector().getPrimaryId());
+ Identifier primaryId = newInfo.getSelector().getPrimaryId();
+ RadioManager.ProgramInfo oldInfo = null;
+ if (mProgramInfoMap.containsKey(primaryId)) {
+ UniqueProgramIdentifier uniqueId = new UniqueProgramIdentifier(newInfo.getSelector());
+ oldInfo = mProgramInfoMap.get(primaryId).get(uniqueId);
+ }
if (oldInfo == null) {
return true;
}
@@ -231,9 +271,9 @@
return (numerator / denominator) + (numerator % denominator > 0 ? 1 : 0);
}
- private static @NonNull List<ProgramList.Chunk> buildChunks(boolean purge, boolean complete,
+ private static List<ProgramList.Chunk> buildChunks(boolean purge, boolean complete,
@Nullable Collection<RadioManager.ProgramInfo> modified, int maxNumModifiedPerChunk,
- @Nullable Collection<ProgramSelector.Identifier> removed, int maxNumRemovedPerChunk) {
+ @Nullable Collection<UniqueProgramIdentifier> removed, int maxNumRemovedPerChunk) {
// Communication protocol requires that if purge is set, removed is empty.
if (purge) {
removed = null;
@@ -257,7 +297,7 @@
int modifiedPerChunk = 0;
int removedPerChunk = 0;
Iterator<RadioManager.ProgramInfo> modifiedIter = null;
- Iterator<ProgramSelector.Identifier> removedIter = null;
+ Iterator<UniqueProgramIdentifier> removedIter = null;
if (modified != null) {
modifiedPerChunk = roundUpFraction(modified.size(), numChunks);
modifiedIter = modified.iterator();
@@ -268,8 +308,8 @@
}
List<ProgramList.Chunk> chunks = new ArrayList<ProgramList.Chunk>(numChunks);
for (int i = 0; i < numChunks; i++) {
- HashSet<RadioManager.ProgramInfo> modifiedChunk = new HashSet<>();
- HashSet<ProgramSelector.Identifier> removedChunk = new HashSet<>();
+ ArraySet<RadioManager.ProgramInfo> modifiedChunk = new ArraySet<>();
+ ArraySet<UniqueProgramIdentifier> removedChunk = new ArraySet<>();
if (modifiedIter != null) {
for (int j = 0; j < modifiedPerChunk && modifiedIter.hasNext(); j++) {
modifiedChunk.add(modifiedIter.next());
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java b/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java
index 7b5cb898..a54af2e 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java
@@ -111,13 +111,11 @@
@Override
public void onProgramListUpdated(ProgramListChunk programListChunk) {
fireLater(() -> {
- android.hardware.radio.ProgramList.Chunk chunk =
- Convert.programListChunkFromHal(programListChunk);
synchronized (mLock) {
- mProgramInfoCache.filterAndApplyChunk(chunk);
+ mProgramInfoCache.filterAndApplyChunk(programListChunk);
for (TunerSession tunerSession : mAidlTunerSessions) {
- tunerSession.onMergedProgramListUpdateFromHal(chunk);
+ tunerSession.onMergedProgramListUpdateFromHal(programListChunk);
}
}
});
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java b/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java
index 1efc4a5..978dc01 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java
@@ -21,6 +21,7 @@
import android.graphics.Bitmap;
import android.hardware.broadcastradio.V2_0.ConfigFlag;
import android.hardware.broadcastradio.V2_0.ITunerSession;
+import android.hardware.broadcastradio.V2_0.ProgramListChunk;
import android.hardware.broadcastradio.V2_0.Result;
import android.hardware.radio.ITuner;
import android.hardware.radio.ProgramList;
@@ -267,7 +268,7 @@
}
}
- void onMergedProgramListUpdateFromHal(ProgramList.Chunk mergedChunk) {
+ void onMergedProgramListUpdateFromHal(ProgramListChunk mergedChunk) {
List<ProgramList.Chunk> clientUpdateChunks = null;
synchronized (mLock) {
if (mProgramInfoCache == null) {
diff --git a/services/core/java/com/android/server/content/SyncStorageEngine.java b/services/core/java/com/android/server/content/SyncStorageEngine.java
index b890bbd..9805fd3 100644
--- a/services/core/java/com/android/server/content/SyncStorageEngine.java
+++ b/services/core/java/com/android/server/content/SyncStorageEngine.java
@@ -1754,7 +1754,7 @@
eventType = parser.next();
} while (eventType != XmlPullParser.END_DOCUMENT);
}
- } catch (XmlPullParserException e) {
+ } catch (XmlPullParserException | ArrayIndexOutOfBoundsException e) {
Slog.w(TAG, "Error reading accounts", e);
return;
} catch (java.io.IOException e) {
diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java
index 4d3bf400..b5a373e 100644
--- a/services/core/java/com/android/server/pm/DeletePackageHelper.java
+++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java
@@ -464,10 +464,7 @@
if (DEBUG_REMOVE) Slog.d(TAG, "Still installed by other users");
clearPackageStateAndReturn = true;
} else {
- // We need to set it back to 'installed' so the uninstall
- // broadcasts will be sent correctly.
if (DEBUG_REMOVE) Slog.d(TAG, "Not installed by other users, full delete");
- ps.setInstalled(true, userId);
mPm.mSettings.writeKernelMappingLPr(ps);
clearPackageStateAndReturn = false;
}
diff --git a/services/core/java/com/android/server/pm/PackageArchiverService.java b/services/core/java/com/android/server/pm/PackageArchiverService.java
index c7f067b..e052407 100644
--- a/services/core/java/com/android/server/pm/PackageArchiverService.java
+++ b/services/core/java/com/android/server/pm/PackageArchiverService.java
@@ -16,6 +16,7 @@
package com.android.server.pm;
+import static android.app.ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED;
import static android.content.pm.PackageManager.DELETE_KEEP_DATA;
import static android.os.PowerExemptionManager.REASON_PACKAGE_UNARCHIVE;
import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
@@ -24,6 +25,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
+import android.annotation.UserIdInt;
import android.app.AppOpsManager;
import android.app.BroadcastOptions;
import android.content.Context;
@@ -33,24 +35,37 @@
import android.content.pm.LauncherActivityInfo;
import android.content.pm.LauncherApps;
import android.content.pm.PackageArchiver;
+import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.content.pm.VersionedPackage;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
import android.os.Binder;
import android.os.Bundle;
+import android.os.Environment;
import android.os.ParcelableException;
+import android.os.SELinux;
import android.os.UserHandle;
import android.text.TextUtils;
+import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.server.pm.pkg.ArchiveState;
import com.android.server.pm.pkg.ArchiveState.ArchiveActivityInfo;
import com.android.server.pm.pkg.PackageStateInternal;
import com.android.server.pm.pkg.PackageUserStateInternal;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
+import java.util.concurrent.CompletableFuture;
/**
* Responsible archiving apps and returning information about archived apps.
@@ -61,6 +76,8 @@
*/
public class PackageArchiverService extends IPackageArchiverService.Stub {
+ private static final String TAG = "PackageArchiverService";
+
/**
* The maximum time granted for an app store to start a foreground service when unarchival
* is requested.
@@ -68,6 +85,8 @@
// TODO(b/297358628) Make this configurable through a flag.
private static final int DEFAULT_UNARCHIVE_FOREGROUND_TIMEOUT_MS = 120 * 1000;
+ private static final String ARCHIVE_ICONS_DIR = "package_archiver";
+
private final Context mContext;
private final PackageManagerService mPm;
@@ -97,25 +116,44 @@
snapshot.enforceCrossUserPermission(binderUid, userId, true, true,
"archiveApp");
verifyCaller(providedUid, binderUid);
- ArchiveState archiveState;
+ CompletableFuture<ArchiveState> archiveStateFuture;
try {
- archiveState = createArchiveState(packageName, userId);
- // TODO(b/282952870) Should be reverted if uninstall fails/cancels
- storeArchiveState(packageName, archiveState, userId);
+ archiveStateFuture = createArchiveState(packageName, userId);
} catch (PackageManager.NameNotFoundException e) {
+ Slog.d(TAG, TextUtils.formatSimple("Failed to archive %s with message %s",
+ packageName, e.getMessage()));
throw new ParcelableException(e);
}
- // TODO(b/278553670) Add special strings for the delete dialog
- mPm.mInstallerService.uninstall(
- new VersionedPackage(packageName, PackageManager.VERSION_CODE_HIGHEST),
- callerPackageName, DELETE_KEEP_DATA, intentSender, userId);
+ archiveStateFuture
+ .thenAccept(
+ archiveState -> {
+ // TODO(b/282952870) Should be reverted if uninstall fails/cancels
+ try {
+ storeArchiveState(packageName, archiveState, userId);
+ } catch (PackageManager.NameNotFoundException e) {
+ sendFailureStatus(intentSender, packageName, e.getMessage());
+ return;
+ }
+
+ // TODO(b/278553670) Add special strings for the delete dialog
+ mPm.mInstallerService.uninstall(
+ new VersionedPackage(packageName,
+ PackageManager.VERSION_CODE_HIGHEST),
+ callerPackageName, DELETE_KEEP_DATA, intentSender, userId,
+ binderUid);
+ })
+ .exceptionally(
+ e -> {
+ sendFailureStatus(intentSender, packageName, e.getMessage());
+ return null;
+ });
}
/**
* Creates archived state for the package and user.
*/
- public ArchiveState createArchiveState(String packageName, int userId)
+ public CompletableFuture<ArchiveState> createArchiveState(String packageName, int userId)
throws PackageManager.NameNotFoundException {
PackageStateInternal ps = getPackageState(packageName, mPm.snapshotComputer(),
Binder.getCallingUid(), userId);
@@ -123,16 +161,56 @@
verifyInstaller(responsibleInstallerPackage);
List<LauncherActivityInfo> mainActivities = getLauncherActivityInfos(ps, userId);
+ final CompletableFuture<ArchiveState> archiveState = new CompletableFuture<>();
+ mPm.mHandler.post(() -> {
+ try {
+ archiveState.complete(
+ createArchiveStateInternal(packageName, userId, mainActivities,
+ responsibleInstallerPackage));
+ } catch (IOException e) {
+ archiveState.completeExceptionally(e);
+ }
+ });
+ return archiveState;
+ }
+
+ private ArchiveState createArchiveStateInternal(String packageName, int userId,
+ List<LauncherActivityInfo> mainActivities, String installerPackage)
+ throws IOException {
List<ArchiveActivityInfo> archiveActivityInfos = new ArrayList<>();
for (int i = 0; i < mainActivities.size(); i++) {
- // TODO(b/278553670) Extract and store launcher icons
+ LauncherActivityInfo mainActivity = mainActivities.get(i);
+ Path iconPath = storeIcon(packageName, mainActivity, userId);
ArchiveActivityInfo activityInfo = new ArchiveActivityInfo(
- mainActivities.get(i).getLabel().toString(),
- Path.of("/TODO"), null);
+ mainActivity.getLabel().toString(), iconPath, null);
archiveActivityInfos.add(activityInfo);
}
- return new ArchiveState(archiveActivityInfos, responsibleInstallerPackage);
+ return new ArchiveState(archiveActivityInfos, installerPackage);
+ }
+
+ // TODO(b/298452477) Handle monochrome icons.
+ @VisibleForTesting
+ Path storeIcon(String packageName, LauncherActivityInfo mainActivity,
+ @UserIdInt int userId)
+ throws IOException {
+ int iconResourceId = mainActivity.getActivityInfo().getIconResource();
+ if (iconResourceId == 0) {
+ // The app doesn't define an icon. No need to store anything.
+ return null;
+ }
+ File iconsDir = createIconsDir(userId);
+ File iconFile = new File(iconsDir, packageName + "-" + mainActivity.getName() + ".png");
+ Bitmap icon = drawableToBitmap(mainActivity.getIcon(/* density= */ 0));
+ try (FileOutputStream out = new FileOutputStream(iconFile)) {
+ // Note: Quality is ignored for PNGs.
+ if (!icon.compress(Bitmap.CompressFormat.PNG, /* quality= */ 100, out)) {
+ throw new IOException(TextUtils.formatSimple("Failure to store icon file %s",
+ iconFile.getName()));
+ }
+ out.flush();
+ }
+ return iconFile.toPath();
}
private void verifyInstaller(String installerPackage)
@@ -313,6 +391,29 @@
return ps;
}
+ private void sendFailureStatus(IntentSender statusReceiver, String packageName,
+ String message) {
+ Slog.d(TAG, TextUtils.formatSimple("Failed to archive %s with message %s", packageName,
+ message));
+ final Intent fillIn = new Intent();
+ fillIn.putExtra(PackageInstaller.EXTRA_PACKAGE_NAME, packageName);
+ fillIn.putExtra(PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_FAILURE);
+ fillIn.putExtra(PackageInstaller.EXTRA_STATUS_MESSAGE, message);
+ try {
+ final BroadcastOptions options = BroadcastOptions.makeBasic();
+ options.setPendingIntentBackgroundActivityStartMode(
+ MODE_BACKGROUND_ACTIVITY_START_DENIED);
+ statusReceiver.sendIntent(mContext, 0, fillIn, /* onFinished= */ null,
+ /* handler= */ null, /* requiredPermission= */ null, options.toBundle());
+ } catch (IntentSender.SendIntentException e) {
+ Slog.e(
+ TAG,
+ TextUtils.formatSimple("Failed to send failure status for %s with message %s",
+ packageName, message),
+ e);
+ }
+ }
+
private static void verifyCaller(int providedUid, int binderUid) {
if (providedUid != binderUid) {
throw new SecurityException(
@@ -323,4 +424,44 @@
binderUid));
}
}
+
+ private File createIconsDir(@UserIdInt int userId) throws IOException {
+ File iconsDir = getIconsDir(userId);
+ if (!iconsDir.isDirectory()) {
+ iconsDir.delete();
+ iconsDir.mkdirs();
+ if (!iconsDir.isDirectory()) {
+ throw new IOException("Unable to create directory " + iconsDir);
+ }
+ }
+ SELinux.restorecon(iconsDir);
+ return iconsDir;
+ }
+
+ private File getIconsDir(int userId) {
+ return new File(Environment.getDataSystemCeDirectory(userId), ARCHIVE_ICONS_DIR);
+ }
+
+ private static Bitmap drawableToBitmap(Drawable drawable) {
+ if (drawable instanceof BitmapDrawable) {
+ return ((BitmapDrawable) drawable).getBitmap();
+
+ }
+
+ Bitmap bitmap;
+ if (drawable.getIntrinsicWidth() <= 0 || drawable.getIntrinsicHeight() <= 0) {
+ // Needed for drawables that are just a single color.
+ bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
+ } else {
+ bitmap =
+ Bitmap.createBitmap(
+ drawable.getIntrinsicWidth(),
+ drawable.getIntrinsicHeight(),
+ Bitmap.Config.ARGB_8888);
+ }
+ Canvas canvas = new Canvas(bitmap);
+ drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
+ drawable.draw(canvas);
+ return bitmap;
+ }
}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index e360256..fabef76 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -1240,8 +1240,18 @@
@Override
public void uninstall(VersionedPackage versionedPackage, String callerPackageName, int flags,
IntentSender statusReceiver, int userId) {
+ uninstall(
+ versionedPackage,
+ callerPackageName,
+ flags,
+ statusReceiver,
+ userId,
+ Binder.getCallingUid());
+ }
+
+ void uninstall(VersionedPackage versionedPackage, String callerPackageName, int flags,
+ IntentSender statusReceiver, int userId, int callingUid) {
final Computer snapshot = mPm.snapshotComputer();
- final int callingUid = Binder.getCallingUid();
snapshot.enforceCrossUserPermission(callingUid, userId, true, true, "uninstall");
if (!PackageManagerServiceUtils.isRootOrShell(callingUid)) {
mAppOps.checkPackage(callingUid, callerPackageName);
@@ -1257,7 +1267,7 @@
final PackageDeleteObserverAdapter adapter = new PackageDeleteObserverAdapter(mContext,
statusReceiver, versionedPackage.getPackageName(),
canSilentlyInstallPackage, userId);
- if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DELETE_PACKAGES)
+ if (mContext.checkCallingOrSelfPermission(Manifest.permission.DELETE_PACKAGES)
== PackageManager.PERMISSION_GRANTED) {
// Sweet, call straight through!
mPm.deletePackageVersioned(versionedPackage, adapter.getBinder(), userId, flags);
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index e8e6470..0dd4111ad 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -3641,9 +3641,6 @@
@GuardedBy("mLock")
private void maybeStageFsveritySignatureLocked(File origFile, File targetFile,
boolean fsVerityRequired) throws PackageManagerException {
- if (com.android.server.security.Flags.deprecateFsvSig()) {
- return;
- }
final File originalSignature = new File(
VerityUtils.getFsveritySignatureFilePath(origFile.getPath()));
if (originalSignature.exists()) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index 0423249..2028231 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -543,9 +543,6 @@
/** Returns true if standard APK Verity is enabled. */
static boolean isApkVerityEnabled() {
- if (com.android.server.security.Flags.deprecateFsvSig()) {
- return false;
- }
return Build.VERSION.DEVICE_INITIAL_SDK_INT >= Build.VERSION_CODES.R
|| SystemProperties.getInt("ro.apk_verity.mode", FSVERITY_DISABLED)
== FSVERITY_ENABLED;
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index d9f1df5..a0c9cd1 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -2784,33 +2784,111 @@
private int runGrantRevokePermission(boolean grant) throws RemoteException {
int userId = UserHandle.USER_SYSTEM;
- String opt = null;
+ String opt;
+ boolean allPermissions = false;
while ((opt = getNextOption()) != null) {
if (opt.equals("--user")) {
userId = UserHandle.parseUserArg(getNextArgRequired());
}
+ if (opt.equals("--all-permissions")) {
+ allPermissions = true;
+ }
}
String pkg = getNextArg();
- if (pkg == null) {
+ if (!allPermissions && pkg == null) {
getErrPrintWriter().println("Error: no package specified");
return 1;
}
String perm = getNextArg();
- if (perm == null) {
+ if (!allPermissions && perm == null) {
getErrPrintWriter().println("Error: no permission specified");
return 1;
}
+ if (allPermissions && perm != null) {
+ getErrPrintWriter().println("Error: permission specified but not expected");
+ return 1;
+ }
final UserHandle translatedUser = UserHandle.of(translateUserId(userId,
UserHandle.USER_NULL, "runGrantRevokePermission"));
- if (grant) {
- mPermissionManager.grantRuntimePermission(pkg, perm, translatedUser);
+
+ List<PackageInfo> packageInfos;
+ if (pkg == null) {
+ packageInfos = mContext.getPackageManager().getInstalledPackages(
+ PackageManager.GET_PERMISSIONS);
} else {
- mPermissionManager.revokeRuntimePermission(pkg, perm, translatedUser, null);
+ try {
+ packageInfos = Collections.singletonList(
+ mContext.getPackageManager().getPackageInfo(pkg,
+ PackageManager.GET_PERMISSIONS));
+ } catch (NameNotFoundException e) {
+ getErrPrintWriter().println("Error: package not found");
+ return 1;
+ }
+ }
+
+ for (PackageInfo packageInfo : packageInfos) {
+ List<String> permissions = Collections.singletonList(perm);
+ if (allPermissions) {
+ permissions = getRequestedRuntimePermissions(packageInfo);
+ }
+ for (String permission : permissions) {
+ if (grant) {
+ try {
+ mPermissionManager.grantRuntimePermission(packageInfo.packageName,
+ permission,
+ translatedUser);
+ } catch (Exception e) {
+ if (!allPermissions) {
+ throw e;
+ } else {
+ Slog.w(TAG, "Could not grant permission " + permission, e);
+ }
+ }
+ } else {
+ try {
+ mPermissionManager.revokeRuntimePermission(packageInfo.packageName,
+ permission,
+ translatedUser, null);
+ } catch (Exception e) {
+ if (!allPermissions) {
+ throw e;
+ } else {
+ Slog.w(TAG, "Could not grant permission " + permission, e);
+ }
+ }
+ }
+ }
}
return 0;
}
+ private List<String> getRequestedRuntimePermissions(PackageInfo info) {
+ // No requested permissions
+ if (info.requestedPermissions == null) {
+ return new ArrayList<>();
+ }
+ List<String> result = new ArrayList<>();
+ PackageManager pm = mContext.getPackageManager();
+ // Iterate through requested permissions for denied ones
+ for (String permission : info.requestedPermissions) {
+ PermissionInfo pi = null;
+ try {
+ pi = pm.getPermissionInfo(permission, 0);
+ } catch (NameNotFoundException nnfe) {
+ // ignore
+ }
+ if (pi == null) {
+ continue;
+ }
+ if (pi.getProtection() != PermissionInfo.PROTECTION_DANGEROUS) {
+ continue;
+ }
+ result.add(permission);
+ }
+ return result;
+ }
+
private int runResetPermissions() throws RemoteException {
mLegacyPermissionManager.resetRuntimePermissions();
return 0;
@@ -4643,11 +4721,15 @@
pw.println(" get-distracting-restriction [--user USER_ID] PACKAGE [PACKAGE...]");
pw.println(" Gets the specified restriction flags of given package(s) (of the user).");
pw.println("");
- pw.println(" grant [--user USER_ID] PACKAGE PERMISSION");
- pw.println(" revoke [--user USER_ID] PACKAGE PERMISSION");
+ pw.println(" grant [--user USER_ID] [--all-permissions] PACKAGE PERMISSION");
+ pw.println(" revoke [--user USER_ID] [--all-permissions] PACKAGE PERMISSION");
pw.println(" These commands either grant or revoke permissions to apps. The permissions");
pw.println(" must be declared as used in the app's manifest, be runtime permissions");
pw.println(" (protection level dangerous), and the app targeting SDK greater than Lollipop MR1.");
+ pw.println(" Flags are:");
+ pw.println(" --user: Specifies the user for which the operation needs to be performed");
+ pw.println(" --all-permissions: If specified all the missing runtime permissions will");
+ pw.println(" be granted to the PACKAGE or to all the packages if none is specified.");
pw.println("");
pw.println(" set-permission-flags [--user USER_ID] PACKAGE PERMISSION [FLAGS..]");
pw.println(" clear-permission-flags [--user USER_ID] PACKAGE PERMISSION [FLAGS..]");
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 88184c0..114f80d 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -35,6 +35,7 @@
import android.os.UserHandle;
import android.os.incremental.IncrementalManager;
import android.service.pm.PackageProto;
+import android.service.pm.PackageProto.UserInfoProto.ArchiveState.ArchiveActivityInfo;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -1161,18 +1162,15 @@
for (ArchiveState.ArchiveActivityInfo activityInfo : archiveState.getActivityInfos()) {
long activityInfoToken = proto.start(
PackageProto.UserInfoProto.ArchiveState.ACTIVITY_INFOS);
- proto.write(PackageProto.UserInfoProto.ArchiveState.ArchiveActivityInfo.TITLE,
- activityInfo.getTitle());
- proto.write(
- PackageProto.UserInfoProto.ArchiveState.ArchiveActivityInfo.ICON_BITMAP_PATH,
- activityInfo.getIconBitmap().toAbsolutePath().toString());
- proto.write(
- PackageProto
- .UserInfoProto
- .ArchiveState
- .ArchiveActivityInfo
- .MONOCHROME_ICON_BITMAP_PATH,
- activityInfo.getMonochromeIconBitmap().toAbsolutePath().toString());
+ proto.write(ArchiveActivityInfo.TITLE, activityInfo.getTitle());
+ if (activityInfo.getIconBitmap() != null) {
+ proto.write(ArchiveActivityInfo.ICON_BITMAP_PATH,
+ activityInfo.getIconBitmap().toAbsolutePath().toString());
+ }
+ if (activityInfo.getMonochromeIconBitmap() != null) {
+ proto.write(ArchiveActivityInfo.MONOCHROME_ICON_BITMAP_PATH,
+ activityInfo.getMonochromeIconBitmap().toAbsolutePath().toString());
+ }
proto.end(activityInfoToken);
}
diff --git a/services/core/java/com/android/server/pm/RemovePackageHelper.java b/services/core/java/com/android/server/pm/RemovePackageHelper.java
index 2aedf0d..8dec425 100644
--- a/services/core/java/com/android/server/pm/RemovePackageHelper.java
+++ b/services/core/java/com/android/server/pm/RemovePackageHelper.java
@@ -22,7 +22,6 @@
import static android.os.storage.StorageManager.FLAG_STORAGE_CE;
import static android.os.storage.StorageManager.FLAG_STORAGE_DE;
import static android.os.storage.StorageManager.FLAG_STORAGE_EXTERNAL;
-
import static com.android.server.pm.InstructionSets.getDexCodeInstructionSets;
import static com.android.server.pm.PackageManagerService.DEBUG_INSTALL;
import static com.android.server.pm.PackageManagerService.DEBUG_REMOVE;
@@ -400,6 +399,21 @@
changedUsers);
mPm.postPreferredActivityChangedBroadcast(UserHandle.USER_ALL);
}
+ } else if (!deletedPs.isSystem() && outInfo != null && !outInfo.mIsUpdate
+ && outInfo.mRemovedUsers != null) {
+ // For non-system uninstalls with DELETE_KEEP_DATA, set the installed state to false
+ // for affected users. This does not apply to app updates where the old apk is replaced
+ // but the old data remains.
+ if (DEBUG_REMOVE) {
+ Slog.d(TAG, "Updating installed state to false because of DELETE_KEEP_DATA");
+ }
+ for (int userId : outInfo.mRemovedUsers) {
+ if (DEBUG_REMOVE) {
+ final boolean wasInstalled = deletedPs.getInstalled(userId);
+ Slog.d(TAG, " user " + userId + ": " + wasInstalled + " => " + false);
+ }
+ deletedPs.setInstalled(/* installed= */ false, userId);
+ }
}
// make sure to preserve per-user installed state if this removal was just
// a downgrade of a system app to the factory package
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 1137681..111a32d 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -2062,8 +2062,9 @@
if (tagName.equals(TAG_ARCHIVE_ACTIVITY_INFO)) {
String title = parser.getAttributeValue(null,
ATTR_ARCHIVE_ACTIVITY_TITLE);
- Path iconPath = Path.of(parser.getAttributeValue(null,
- ATTR_ARCHIVE_ICON_PATH));
+ String iconAttribute = parser.getAttributeValue(null,
+ ATTR_ARCHIVE_ICON_PATH);
+ Path iconPath = iconAttribute == null ? null : Path.of(iconAttribute);
String monochromeAttribute = parser.getAttributeValue(null,
ATTR_ARCHIVE_MONOCHROME_ICON_PATH);
Path monochromeIconPath = monochromeAttribute == null ? null : Path.of(
@@ -2447,8 +2448,10 @@
for (ArchiveState.ArchiveActivityInfo activityInfo : archiveState.getActivityInfos()) {
serializer.startTag(null, TAG_ARCHIVE_ACTIVITY_INFO);
serializer.attribute(null, ATTR_ARCHIVE_ACTIVITY_TITLE, activityInfo.getTitle());
- serializer.attribute(null, ATTR_ARCHIVE_ICON_PATH,
- activityInfo.getIconBitmap().toAbsolutePath().toString());
+ if (activityInfo.getIconBitmap() != null) {
+ serializer.attribute(null, ATTR_ARCHIVE_ICON_PATH,
+ activityInfo.getIconBitmap().toAbsolutePath().toString());
+ }
if (activityInfo.getMonochromeIconBitmap() != null) {
serializer.attribute(null, ATTR_ARCHIVE_MONOCHROME_ICON_PATH,
activityInfo.getMonochromeIconBitmap().toAbsolutePath().toString());
diff --git a/services/core/java/com/android/server/pm/pkg/ArchiveState.java b/services/core/java/com/android/server/pm/pkg/ArchiveState.java
index d44ae16..4916a4a 100644
--- a/services/core/java/com/android/server/pm/pkg/ArchiveState.java
+++ b/services/core/java/com/android/server/pm/pkg/ArchiveState.java
@@ -56,8 +56,11 @@
@NonNull
private final String mTitle;
- /** The path to the stored icon of the activity in the app's locale. */
- @NonNull
+ /**
+ * The path to the stored icon of the activity in the app's locale. Null if the app does
+ * not define any icon (default icon would be shown on the launcher).
+ */
+ @Nullable
private final Path mIconBitmap;
/** See {@link #mIconBitmap}. Only set if the app defined a monochrome icon. */
@@ -85,21 +88,20 @@
* @param title
* Corresponds to the activity's android:label in the app's locale.
* @param iconBitmap
- * The path to the stored icon of the activity in the app's locale.
+ * The path to the stored icon of the activity in the app's locale. Null if the app does
+ * not define any icon (default icon would be shown on the launcher).
* @param monochromeIconBitmap
* See {@link #mIconBitmap}. Only set if the app defined a monochrome icon.
*/
@DataClass.Generated.Member
public ArchiveActivityInfo(
@NonNull String title,
- @NonNull Path iconBitmap,
+ @Nullable Path iconBitmap,
@Nullable Path monochromeIconBitmap) {
this.mTitle = title;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mTitle);
this.mIconBitmap = iconBitmap;
- com.android.internal.util.AnnotationValidations.validate(
- NonNull.class, null, mIconBitmap);
this.mMonochromeIconBitmap = monochromeIconBitmap;
// onConstructed(); // You can define this method to get a callback
@@ -114,10 +116,11 @@
}
/**
- * The path to the stored icon of the activity in the app's locale.
+ * The path to the stored icon of the activity in the app's locale. Null if the app does
+ * not define any icon (default icon would be shown on the launcher).
*/
@DataClass.Generated.Member
- public @NonNull Path getIconBitmap() {
+ public @Nullable Path getIconBitmap() {
return mIconBitmap;
}
@@ -174,10 +177,10 @@
}
@DataClass.Generated(
- time = 1689169065133L,
+ time = 1693590309015L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/ArchiveState.java",
- inputSignatures = "private final @android.annotation.NonNull java.lang.String mTitle\nprivate final @android.annotation.NonNull java.nio.file.Path mIconBitmap\nprivate final @android.annotation.Nullable java.nio.file.Path mMonochromeIconBitmap\nclass ArchiveActivityInfo extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true)")
+ inputSignatures = "private final @android.annotation.NonNull java.lang.String mTitle\nprivate final @android.annotation.Nullable java.nio.file.Path mIconBitmap\nprivate final @android.annotation.Nullable java.nio.file.Path mMonochromeIconBitmap\nclass ArchiveActivityInfo extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true)")
@Deprecated
private void __metadata() {}
@@ -292,7 +295,7 @@
}
@DataClass.Generated(
- time = 1689169065144L,
+ time = 1693590309027L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/ArchiveState.java",
inputSignatures = "private final @android.annotation.NonNull java.util.List<com.android.server.pm.pkg.ArchiveActivityInfo> mActivityInfos\nprivate final @android.annotation.NonNull java.lang.String mInstallerTitle\nclass ArchiveState extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true)")
diff --git a/services/core/java/com/android/server/security/FileIntegrityService.java b/services/core/java/com/android/server/security/FileIntegrityService.java
index 0879d95..3aed6e3 100644
--- a/services/core/java/com/android/server/security/FileIntegrityService.java
+++ b/services/core/java/com/android/server/security/FileIntegrityService.java
@@ -90,13 +90,6 @@
@NonNull String packageName) {
checkCallerPermission(packageName);
- if (Flags.deprecateFsvSig()) {
- // When deprecated, stop telling the caller that any app source certificate is
- // trusted on the current device. This behavior is also consistent with devices
- // without this feature support.
- return false;
- }
-
try {
if (!VerityUtils.isFsVeritySupported()) {
return false;
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index 9905ddf..635e11b 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -159,10 +159,26 @@
private VirtualDeviceManagerInternal mVirtualDeviceManager;
private enum TrustState {
- UNTRUSTED, // the phone is not unlocked by any trustagents
- TRUSTABLE, // the phone is in a semi-locked state that can be unlocked if
- // FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE is passed and a trustagent is trusted
- TRUSTED // the phone is unlocked
+ // UNTRUSTED means that TrustManagerService is currently *not* giving permission for the
+ // user's Keyguard to be dismissed, and grants of trust by trust agents are remembered in
+ // the corresponding TrustAgentWrapper but are not recognized until the device is unlocked
+ // for the user. I.e., if the device is locked and the state is UNTRUSTED, it cannot be
+ // unlocked by a trust agent. Automotive devices are an exception; grants of trust are
+ // always recognized on them.
+ UNTRUSTED,
+
+ // TRUSTABLE is the same as UNTRUSTED except that new grants of trust using
+ // FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE are recognized for moving to TRUSTED. I.e., if
+ // the device is locked and the state is TRUSTABLE, it can be unlocked by a trust agent,
+ // provided that the trust agent chooses to use Active Unlock. The TRUSTABLE state is only
+ // possible as a result of a downgrade from TRUSTED, after a trust agent used
+ // FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE in its most recent grant.
+ TRUSTABLE,
+
+ // TRUSTED means that TrustManagerService is currently giving permission for the user's
+ // Keyguard to be dismissed. This implies that the device is unlocked for the user (where
+ // the case of Keyguard showing but dismissible just with swipe counts as "unlocked").
+ TRUSTED
};
@GuardedBy("mUserTrustState")
@@ -744,6 +760,12 @@
}
}
+ private TrustState getUserTrustStateInner(int userId) {
+ synchronized (mUserTrustState) {
+ return mUserTrustState.get(userId, TrustState.UNTRUSTED);
+ }
+ }
+
boolean isDeviceLockedInner(int userId) {
synchronized (mDeviceLockedForUser) {
return mDeviceLockedForUser.get(userId, true);
@@ -806,7 +828,12 @@
continue;
}
- boolean trusted = aggregateIsTrusted(id);
+ final boolean trusted;
+ if (android.security.Flags.fixUnlockedDeviceRequiredKeys()) {
+ trusted = getUserTrustStateInner(id) == TrustState.TRUSTED;
+ } else {
+ trusted = aggregateIsTrusted(id);
+ }
boolean showingKeyguard = true;
boolean biometricAuthenticated = false;
boolean currentUserIsUnlocked = false;
@@ -1627,7 +1654,7 @@
if (isCurrent) {
fout.print(" (current)");
}
- fout.print(": trusted=" + dumpBool(aggregateIsTrusted(user.id)));
+ fout.print(": trustState=" + getUserTrustStateInner(user.id));
fout.print(", trustManaged=" + dumpBool(aggregateIsTrustManaged(user.id)));
fout.print(", deviceLocked=" + dumpBool(isDeviceLockedInner(user.id)));
fout.print(", isActiveUnlockRunning=" + dumpBool(
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index d430dda..b823e73 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -1170,9 +1170,7 @@
fullscreenRequest, r);
reportMultiwindowFullscreenRequestValidatingResult(callback, validateResult);
if (validateResult != RESULT_APPROVED) {
- if (queued) {
- transition.abort();
- }
+ transition.abort();
return;
}
transition.collect(topFocusedRootTask);
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index fd42077..4aea70c 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -68,7 +68,6 @@
import static android.view.WindowManager.TRANSIT_PIP;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_TO_LAUNCHER_CLEAR_SNAPSHOT;
-
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_DREAM;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS;
@@ -3660,6 +3659,9 @@
synchronized (mGlobalLock) {
if (r.getParent() == null) {
Slog.e(TAG, "Skip enterPictureInPictureMode, destroyed " + r);
+ if (transition != null) {
+ transition.abort();
+ }
return;
}
EventLogTags.writeWmEnterPip(r.mUserId, System.identityHashCode(r),
@@ -5628,6 +5630,15 @@
}
}
+ void registerCompatScaleProvider(@CompatScaleProvider.CompatScaleModeOrderId int id,
+ @NonNull CompatScaleProvider provider) {
+ mCompatModePackages.registerCompatScaleProvider(id, provider);
+ }
+
+ void unregisterCompatScaleProvider(@CompatScaleProvider.CompatScaleModeOrderId int id) {
+ mCompatModePackages.unregisterCompatScaleProvider(id);
+ }
+
/**
* Returns {@code true} if the process represented by the pid passed as argument is
* instrumented and the instrumentation source was granted with the permission also
diff --git a/services/core/java/com/android/server/wm/CompatModePackages.java b/services/core/java/com/android/server/wm/CompatModePackages.java
index c6978fd..e906b18 100644
--- a/services/core/java/com/android/server/wm/CompatModePackages.java
+++ b/services/core/java/com/android/server/wm/CompatModePackages.java
@@ -20,7 +20,10 @@
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS;
+import static com.android.server.wm.CompatScaleProvider.COMPAT_SCALE_MODE_SYSTEM_FIRST;
+import static com.android.server.wm.CompatScaleProvider.COMPAT_SCALE_MODE_SYSTEM_LAST;
+import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.AppGlobals;
import android.app.GameManagerInternal;
@@ -32,6 +35,7 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.res.CompatibilityInfo;
+import android.content.res.CompatibilityInfo.CompatScale;
import android.content.res.Configuration;
import android.os.Build;
import android.os.Handler;
@@ -332,6 +336,8 @@
private final HashMap<String, Integer> mPackages = new HashMap<>();
private final CompatHandler mHandler;
+ private final SparseArray<CompatScaleProvider> mProviders = new SparseArray<>();
+
public CompatModePackages(ActivityTaskManagerService service, File systemDir, Handler handler) {
mService = service;
mFile = new AtomicFile(new File(systemDir, "packages-compat.xml"), "compat-mode");
@@ -441,13 +447,38 @@
public CompatibilityInfo compatibilityInfoForPackageLocked(ApplicationInfo ai) {
final boolean forceCompat = getPackageCompatModeEnabledLocked(ai);
- final float compatScale = getCompatScale(ai.packageName, ai.uid);
+ final CompatScale compatScale = getCompatScaleFromProvider(ai.packageName, ai.uid);
+ final float appScale = compatScale != null
+ ? compatScale.mScaleFactor
+ : getCompatScale(ai.packageName, ai.uid, /* checkProvider= */ false);
+ final float densityScale = compatScale != null ? compatScale.mDensityScaleFactor : 1f;
final Configuration config = mService.getGlobalConfiguration();
return new CompatibilityInfo(ai, config.screenLayout, config.smallestScreenWidthDp,
- forceCompat, compatScale);
+ forceCompat, appScale, densityScale);
}
float getCompatScale(String packageName, int uid) {
+ return getCompatScale(packageName, uid, /* checkProvider= */ true);
+ }
+
+ private CompatScale getCompatScaleFromProvider(String packageName, int uid) {
+ for (int i = 0; i < mProviders.size(); i++) {
+ final CompatScaleProvider provider = mProviders.valueAt(i);
+ final CompatScale compatScale = provider.getCompatScale(packageName, uid);
+ if (compatScale != null) {
+ return compatScale;
+ }
+ }
+ return null;
+ }
+
+ private float getCompatScale(String packageName, int uid, boolean checkProviders) {
+ if (checkProviders) {
+ final CompatScale compatScale = getCompatScaleFromProvider(packageName, uid);
+ if (compatScale != null) {
+ return compatScale.mScaleFactor;
+ }
+ }
final UserHandle userHandle = UserHandle.getUserHandleForUid(uid);
if (mGameManager == null) {
mGameManager = LocalServices.getService(GameManagerInternal.class);
@@ -487,6 +518,36 @@
return 1f;
}
+ void registerCompatScaleProvider(@CompatScaleProvider.CompatScaleModeOrderId int id,
+ @NonNull CompatScaleProvider provider) {
+ synchronized (mService.mGlobalLock) {
+ if (mProviders.contains(id)) {
+ throw new IllegalArgumentException("Duplicate id provided: " + id);
+ }
+ if (provider == null) {
+ throw new IllegalArgumentException("The passed CompatScaleProvider "
+ + "can not be null");
+ }
+ if (!CompatScaleProvider.isValidOrderId(id)) {
+ throw new IllegalArgumentException(
+ "Provided id " + id + " is not in range of valid ids for system "
+ + "services [" + COMPAT_SCALE_MODE_SYSTEM_FIRST + ","
+ + COMPAT_SCALE_MODE_SYSTEM_LAST + "]");
+ }
+ mProviders.put(id, provider);
+ }
+ }
+
+ void unregisterCompatScaleProvider(@CompatScaleProvider.CompatScaleModeOrderId int id) {
+ synchronized (mService.mGlobalLock) {
+ if (!mProviders.contains(id)) {
+ throw new IllegalArgumentException(
+ "CompatScaleProvider with id (" + id + ") is not registered");
+ }
+ mProviders.remove(id);
+ }
+ }
+
private static float getScalingFactor(String packageName, UserHandle userHandle) {
if (CompatChanges.isChangeEnabled(DOWNSCALE_90, packageName, userHandle)) {
return 0.9f;
diff --git a/services/core/java/com/android/server/wm/CompatScaleProvider.java b/services/core/java/com/android/server/wm/CompatScaleProvider.java
new file mode 100644
index 0000000..5474ece
--- /dev/null
+++ b/services/core/java/com/android/server/wm/CompatScaleProvider.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.wm;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.res.CompatibilityInfo.CompatScale;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * An interface for services that need to provide compatibility scale different than
+ * the default android compatibility.
+ */
+public interface CompatScaleProvider {
+
+ /**
+ * The unique id of each provider registered by a system service which determines the order
+ * it will execute in.
+ */
+ @IntDef(prefix = { "COMPAT_SCALE_MODE_" }, value = {
+ // Order Ids for system services
+ COMPAT_SCALE_MODE_SYSTEM_FIRST,
+ COMPAT_SCALE_MODE_GAME,
+ COMPAT_SCALE_MODE_PRODUCT,
+ COMPAT_SCALE_MODE_SYSTEM_LAST, // Update this when adding new ids
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface CompatScaleModeOrderId {}
+
+ /**
+ * The first id, used by the framework to determine the valid range of ids.
+ * @hide
+ */
+ int COMPAT_SCALE_MODE_SYSTEM_FIRST = 0;
+
+ /**
+ * TODO(b/295207384)
+ * The identifier for {@link android.app.GameManagerInternal} provider
+ * @hide
+ */
+ int COMPAT_SCALE_MODE_GAME = 1;
+
+ /**
+ * The identifier for a provider which is specific to the type of android product like
+ * Automotive, Wear, TV etc.
+ * @hide
+ */
+ int COMPAT_SCALE_MODE_PRODUCT = 2;
+
+ /**
+ * The final id, used by the framework to determine the valid range of ids. Update this when
+ * adding new ids.
+ * @hide
+ */
+ int COMPAT_SCALE_MODE_SYSTEM_LAST = COMPAT_SCALE_MODE_PRODUCT;
+
+ /**
+ * Returns {@code true} if the id is in the range of valid system services
+ * @hide
+ */
+ static boolean isValidOrderId(int id) {
+ return (id >= COMPAT_SCALE_MODE_SYSTEM_FIRST && id <= COMPAT_SCALE_MODE_SYSTEM_LAST);
+ }
+
+ /**
+ * @return an instance of {@link CompatScale} to apply for the given package
+ */
+ @Nullable
+ CompatScale getCompatScale(@NonNull String packageName, int uid);
+}
diff --git a/services/core/java/com/android/server/wm/ContentRecorder.java b/services/core/java/com/android/server/wm/ContentRecorder.java
index 5aa7c97..f0e4149 100644
--- a/services/core/java/com/android/server/wm/ContentRecorder.java
+++ b/services/core/java/com/android/server/wm/ContentRecorder.java
@@ -35,6 +35,7 @@
import android.os.ServiceManager;
import android.provider.DeviceConfig;
import android.view.ContentRecordingSession;
+import android.view.ContentRecordingSession.RecordContent;
import android.view.Display;
import android.view.SurfaceControl;
@@ -84,6 +85,7 @@
/**
* The last configuration orientation.
*/
+ @Configuration.Orientation
private int mLastOrientation = ORIENTATION_UNDEFINED;
ContentRecorder(@NonNull DisplayContent displayContent) {
@@ -156,7 +158,8 @@
// Retrieve the size of the region to record, and continue with the update
// if the bounds or orientation has changed.
final Rect recordedContentBounds = mRecordedWindowContainer.getBounds();
- int recordedContentOrientation = mRecordedWindowContainer.getOrientation();
+ @Configuration.Orientation int recordedContentOrientation =
+ mRecordedWindowContainer.getConfiguration().orientation;
if (!mLastRecordedBounds.equals(recordedContentBounds)
|| lastOrientation != recordedContentOrientation) {
Point surfaceSize = fetchSurfaceSizeIfPresent();
@@ -356,7 +359,7 @@
*/
@Nullable
private WindowContainer retrieveRecordedWindowContainer() {
- final int contentToRecord = mContentRecordingSession.getContentToRecord();
+ @RecordContent final int contentToRecord = mContentRecordingSession.getContentToRecord();
final IBinder tokenToRecord = mContentRecordingSession.getTokenToRecord();
switch (contentToRecord) {
case RECORD_CONTENT_DISPLAY:
@@ -472,6 +475,12 @@
shiftedY = (surfaceSize.y - scaledHeight) / 2;
}
+ ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
+ "Content Recording: Apply transformations of shift %d x %d, scale %f, crop %d x "
+ + "%d for display %d",
+ shiftedX, shiftedY, scale, recordedContentBounds.width(),
+ recordedContentBounds.height(), mDisplayContent.getDisplayId());
+
transaction
// Crop the area to capture to exclude the 'extra' wallpaper that is used
// for parallax (b/189930234).
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 707b779..395ab3a 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -269,6 +269,8 @@
private boolean mIsFreeformWindowOverlappingWithNavBar;
+ private @InsetsType int mForciblyShownTypes;
+
private boolean mIsImmersiveMode;
// The windows we were told about in focusChanged.
@@ -1402,6 +1404,7 @@
mAllowLockscreenWhenOn = false;
mShowingDream = false;
mIsFreeformWindowOverlappingWithNavBar = false;
+ mForciblyShownTypes = 0;
}
/**
@@ -1459,6 +1462,10 @@
}
}
+ if (win.mSession.mCanForceShowingInsets) {
+ mForciblyShownTypes |= win.mAttrs.forciblyShownTypes;
+ }
+
if (!affectsSystemUi) {
return;
}
@@ -1640,6 +1647,10 @@
mService.mPolicy.setAllowLockscreenWhenOn(getDisplayId(), mAllowLockscreenWhenOn);
}
+ boolean areTypesForciblyShownTransiently(@InsetsType int types) {
+ return (mForciblyShownTypes & types) == types;
+ }
+
/**
* Applies the keyguard policy to a specific window.
*
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index 835c92d..03f025b 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -23,8 +23,6 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.view.InsetsSource.ID_IME;
import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
import android.annotation.NonNull;
@@ -473,7 +471,7 @@
// we will dispatch the real visibility of status bar to the client.
return mPermanentControlTarget;
}
- if (forceShowsStatusBarTransiently() && !fake) {
+ if (mPolicy.areTypesForciblyShownTransiently(Type.statusBars()) && !fake) {
// Status bar is forcibly shown transiently, and its new visibility won't be
// dispatched to the client so that we can keep the layout stable. We will dispatch the
// fake control to the client, so that it can re-show the bar during this scenario.
@@ -505,7 +503,7 @@
if (imeWin != null && imeWin.isVisible() && !mHideNavBarForKeyboard) {
// Force showing navigation bar while IME is visible and if navigation bar is not
// configured to be hidden by the IME.
- return null;
+ return mPermanentControlTarget;
}
if (!fake && isTransient(Type.navigationBars())) {
return mTransientControlTarget;
@@ -533,7 +531,7 @@
// bar, and we will dispatch the real visibility of navigation bar to the client.
return mPermanentControlTarget;
}
- if (forceShowsNavigationBarTransiently() && !fake) {
+ if (mPolicy.areTypesForciblyShownTransiently(Type.navigationBars()) && !fake) {
// Navigation bar is forcibly shown transiently, and its new visibility won't be
// dispatched to the client so that we can keep the layout stable. We will dispatch the
// fake control to the client, so that it can re-show the bar during this scenario.
@@ -603,17 +601,6 @@
&& focusedWin.getAttrs().type <= WindowManager.LayoutParams.LAST_APPLICATION_WINDOW;
}
- private boolean forceShowsStatusBarTransiently() {
- final WindowState win = mPolicy.getStatusBar();
- return win != null && (win.mAttrs.privateFlags & PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR) != 0;
- }
-
- private boolean forceShowsNavigationBarTransiently() {
- final WindowState win = mPolicy.getNotificationShade();
- return win != null
- && (win.mAttrs.privateFlags & PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION) != 0;
- }
-
private void dispatchTransientSystemBarsVisibilityChanged(
@Nullable WindowState focusedWindow,
boolean areVisible,
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 1845ae8..0c45eea 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -23,6 +23,7 @@
import static android.Manifest.permission.SET_UNRESTRICTED_GESTURE_EXCLUSION;
import static android.Manifest.permission.SET_UNRESTRICTED_KEEP_CLEAR_AREAS;
import static android.Manifest.permission.START_TASKS_FROM_RECENTS;
+import static android.Manifest.permission.STATUS_BAR_SERVICE;
import static android.Manifest.permission.SYSTEM_APPLICATION_OVERLAY;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.content.ClipDescription.MIMETYPE_APPLICATION_ACTIVITY;
@@ -108,6 +109,7 @@
private final ArraySet<WindowSurfaceController> mAlertWindowSurfaces = new ArraySet<>();
private final DragDropController mDragDropController;
final boolean mCanAddInternalSystemWindow;
+ final boolean mCanForceShowingInsets;
private final boolean mCanStartTasksFromRecents;
final boolean mCanCreateSystemApplicationOverlay;
@@ -131,6 +133,9 @@
mLastReportedAnimatorScale = service.getCurrentAnimatorScale();
mCanAddInternalSystemWindow = service.mContext.checkCallingOrSelfPermission(
INTERNAL_SYSTEM_WINDOW) == PERMISSION_GRANTED;
+ mCanForceShowingInsets = service.mAtmService.isCallerRecents(mUid)
+ || service.mContext.checkCallingOrSelfPermission(STATUS_BAR_SERVICE)
+ == PERMISSION_GRANTED;
mCanHideNonSystemOverlayWindows = service.mContext.checkCallingOrSelfPermission(
HIDE_NON_SYSTEM_OVERLAY_WINDOWS) == PERMISSION_GRANTED
|| service.mContext.checkCallingOrSelfPermission(HIDE_OVERLAY_WINDOWS)
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index a6c6491..843e6d1 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -1487,6 +1487,11 @@
return;
}
+ if (mState != STATE_STARTED) {
+ Slog.e(TAG, "Playing a Transition which hasn't started! #" + mSyncId + " This will "
+ + "likely cause an exception in Shell");
+ }
+
mState = STATE_PLAYING;
mStartTransaction = transaction;
mFinishTransaction = mController.mAtm.mWindowManager.mTransactionFactory.get();
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 55deb22..176bc283 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -42,6 +42,7 @@
#include <android_view_VerifiedMotionEvent.h>
#include <batteryservice/include/batteryservice/BatteryServiceConstants.h>
#include <binder/IServiceManager.h>
+#include <com_android_input_flags.h>
#include <input/Input.h>
#include <input/PointerController.h>
#include <input/SpriteController.h>
@@ -81,6 +82,8 @@
static constexpr std::chrono::milliseconds MAX_VIBRATE_PATTERN_DELAY_MILLIS =
std::chrono::duration_cast<std::chrono::milliseconds>(MAX_VIBRATE_PATTERN_DELAY);
+namespace input_flags = com::android::input::flags;
+
namespace android {
// The exponent used to calculate the pointer speed scaling factor.
@@ -733,7 +736,7 @@
ensureSpriteControllerLocked();
static const bool ENABLE_POINTER_CHOREOGRAPHER =
- sysprop::InputProperties::enable_pointer_choreographer().value_or(false);
+ input_flags::enable_pointer_choreographer();
// Disable the functionality of the legacy PointerController if PointerChoreographer is
// enabled.
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index b4a66bd..76b41b7 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -1895,6 +1895,13 @@
assertProcStates(app2, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
assertBfsl(app2);
+
+ bindService(client2, app1, null, 0, mock(IBinder.class));
+ bindService(app1, client2, null, 0, mock(IBinder.class));
+ client2.mServices.setHasForegroundServices(false, 0, /* hasNoneType=*/false);
+ updateOomAdj(app1, client1, client2);
+ assertProcStates(app1, PROCESS_STATE_IMPORTANT_FOREGROUND, VISIBLE_APP_ADJ,
+ SCHED_GROUP_TOP_APP);
}
@SuppressWarnings("GuardedBy")
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverServiceTest.java
index 80576a6..60b28d3 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverServiceTest.java
@@ -25,6 +25,7 @@
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
@@ -34,11 +35,15 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentSender;
+import android.content.pm.ActivityInfo;
import android.content.pm.LauncherActivityInfo;
import android.content.pm.LauncherApps;
import android.content.pm.PackageArchiver;
+import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.content.pm.VersionedPackage;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
@@ -62,6 +67,7 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
@@ -74,9 +80,10 @@
private static final String PACKAGE = "com.example";
private static final String CALLER_PACKAGE = "com.caller";
private static final String INSTALLER_PACKAGE = "com.installer";
+ private static final Path ICON_PATH = Path.of("icon.png");
@Rule
- public final MockSystemRule mMockSystem = new MockSystemRule();
+ public final MockSystemRule rule = new MockSystemRule();
@Mock
private IntentSender mIntentSender;
@@ -87,9 +94,13 @@
@Mock
private LauncherApps mLauncherApps;
@Mock
+ private PackageManager mPackageManager;
+ @Mock
private PackageInstallerService mInstallerService;
@Mock
private PackageStateInternal mPackageState;
+ @Mock
+ private Bitmap mIcon;
private final InstallSource mInstallSource =
InstallSource.create(
@@ -102,7 +113,6 @@
/* packageSource= */ 0);
private final List<LauncherActivityInfo> mLauncherActivityInfos = createLauncherActivities();
-
private final int mUserId = UserHandle.CURRENT.getIdentifier();
private PackageUserStateImpl mUserState;
@@ -114,10 +124,10 @@
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- mMockSystem.system().stageNominalSystemState();
- when(mMockSystem.mocks().getInjector().getPackageInstallerService()).thenReturn(
+ rule.system().stageNominalSystemState();
+ when(rule.mocks().getInjector().getPackageInstallerService()).thenReturn(
mInstallerService);
- PackageManagerService pm = spy(new PackageManagerService(mMockSystem.mocks().getInjector(),
+ PackageManagerService pm = spy(new PackageManagerService(rule.mocks().getInjector(),
/* factoryTest= */false,
MockSystem.Companion.getDEFAULT_VERSION_INFO().fingerprint,
/* isEngBuild= */ false,
@@ -132,18 +142,27 @@
when(mPackageState.getPackageName()).thenReturn(PACKAGE);
when(mPackageState.getInstallSource()).thenReturn(mInstallSource);
mPackageSetting = createBasicPackageSetting();
- when(mMockSystem.mocks().getSettings().getPackageLPr(eq(PACKAGE))).thenReturn(
+ when(rule.mocks().getSettings().getPackageLPr(eq(PACKAGE))).thenReturn(
mPackageSetting);
mUserState = new PackageUserStateImpl().setInstalled(true);
mPackageSetting.setUserState(mUserId, mUserState);
when(mPackageState.getUserStateOrDefault(eq(mUserId))).thenReturn(mUserState);
+
when(mContext.getSystemService(LauncherApps.class)).thenReturn(mLauncherApps);
when(mLauncherApps.getActivityList(eq(PACKAGE), eq(UserHandle.CURRENT))).thenReturn(
mLauncherActivityInfos);
doReturn(mComputer).when(pm).snapshotComputer();
when(mComputer.getPackageUid(eq(CALLER_PACKAGE), eq(0L), eq(mUserId))).thenReturn(
Binder.getCallingUid());
- mArchiveService = new PackageArchiverService(mContext, pm);
+
+ when(mContext.getPackageManager()).thenReturn(mPackageManager);
+ when(mPackageManager.getResourcesForApplication(eq(PACKAGE))).thenReturn(
+ mock(Resources.class));
+ when(mIcon.compress(eq(Bitmap.CompressFormat.PNG), eq(100), any())).thenReturn(true);
+
+ mArchiveService = spy(new PackageArchiverService(mContext, pm));
+ doReturn(ICON_PATH).when(mArchiveService).storeIcon(eq(PACKAGE),
+ any(LauncherActivityInfo.class), eq(mUserId));
}
@Test
@@ -175,15 +194,20 @@
}
@Test
- public void archiveApp_packageNotInstalledForUser() {
+ public void archiveApp_packageNotInstalledForUser() throws IntentSender.SendIntentException {
mPackageSetting.modifyUserState(UserHandle.CURRENT.getIdentifier()).setInstalled(false);
- Exception e = assertThrows(
- ParcelableException.class,
- () -> mArchiveService.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender,
- UserHandle.CURRENT));
- assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
- assertThat(e.getCause()).hasMessageThat().isEqualTo(
+ mArchiveService.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, UserHandle.CURRENT);
+ rule.mocks().getHandler().flush();
+
+ ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+ verify(mIntentSender).sendIntent(any(), anyInt(), intentCaptor.capture(), any(), any(),
+ any(), any());
+ Intent value = intentCaptor.getValue();
+ assertThat(value.getStringExtra(PackageInstaller.EXTRA_PACKAGE_NAME)).isEqualTo(PACKAGE);
+ assertThat(value.getIntExtra(PackageInstaller.EXTRA_STATUS, 0)).isEqualTo(
+ PackageInstaller.STATUS_FAILURE);
+ assertThat(value.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE)).isEqualTo(
String.format("Package %s not found.", PACKAGE));
}
@@ -223,13 +247,34 @@
}
@Test
+ public void archiveApp_storeIconFails() throws IntentSender.SendIntentException, IOException {
+ IOException e = new IOException("IO");
+ doThrow(e).when(mArchiveService).storeIcon(eq(PACKAGE),
+ any(LauncherActivityInfo.class), eq(mUserId));
+
+ mArchiveService.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, UserHandle.CURRENT);
+ rule.mocks().getHandler().flush();
+
+ ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+ verify(mIntentSender).sendIntent(any(), anyInt(), intentCaptor.capture(), any(), any(),
+ any(), any());
+ Intent value = intentCaptor.getValue();
+ assertThat(value.getStringExtra(PackageInstaller.EXTRA_PACKAGE_NAME)).isEqualTo(PACKAGE);
+ assertThat(value.getIntExtra(PackageInstaller.EXTRA_STATUS, 0)).isEqualTo(
+ PackageInstaller.STATUS_FAILURE);
+ assertThat(value.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE)).isEqualTo(
+ e.toString());
+ }
+
+ @Test
public void archiveApp_success() {
mArchiveService.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, UserHandle.CURRENT);
+ rule.mocks().getHandler().flush();
verify(mInstallerService).uninstall(
eq(new VersionedPackage(PACKAGE, PackageManager.VERSION_CODE_HIGHEST)),
eq(CALLER_PACKAGE), eq(DELETE_KEEP_DATA), eq(mIntentSender),
- eq(UserHandle.CURRENT.getIdentifier()));
+ eq(UserHandle.CURRENT.getIdentifier()), anyInt());
assertThat(mPackageSetting.readUserState(
UserHandle.CURRENT.getIdentifier()).getArchiveState()).isEqualTo(
createArchiveState());
@@ -305,7 +350,7 @@
mUserState.setArchiveState(createArchiveState()).setInstalled(false);
mArchiveService.requestUnarchive(PACKAGE, CALLER_PACKAGE, UserHandle.CURRENT);
- mMockSystem.mocks().getHandler().flush();
+ rule.mocks().getHandler().flush();
ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
verify(mContext).sendOrderedBroadcastAsUser(
@@ -331,20 +376,22 @@
private static ArchiveState createArchiveState() {
List<ArchiveState.ArchiveActivityInfo> activityInfos = new ArrayList<>();
for (LauncherActivityInfo mainActivity : createLauncherActivities()) {
- // TODO(b/278553670) Extract and store launcher icons
ArchiveState.ArchiveActivityInfo activityInfo = new ArchiveState.ArchiveActivityInfo(
mainActivity.getLabel().toString(),
- Path.of("/TODO"), null);
+ ICON_PATH, null);
activityInfos.add(activityInfo);
}
return new ArchiveState(activityInfos, INSTALLER_PACKAGE);
}
private static List<LauncherActivityInfo> createLauncherActivities() {
+ ActivityInfo activityInfo = mock(ActivityInfo.class);
LauncherActivityInfo activity1 = mock(LauncherActivityInfo.class);
when(activity1.getLabel()).thenReturn("activity1");
+ when(activity1.getActivityInfo()).thenReturn(activityInfo);
LauncherActivityInfo activity2 = mock(LauncherActivityInfo.class);
when(activity2.getLabel()).thenReturn("activity2");
+ when(activity2.getActivityInfo()).thenReturn(activityInfo);
return List.of(activity1, activity2);
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/tare/AgentTest.java b/services/tests/mockingservicestests/src/com/android/server/tare/AgentTest.java
index 94fff22..a3917765 100644
--- a/services/tests/mockingservicestests/src/com/android/server/tare/AgentTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/tare/AgentTest.java
@@ -55,6 +55,7 @@
@Mock
private InternalResourceService mIrs;
+ private Agent mAgent;
private Scribe mScribe;
private static class MockScribe extends Scribe {
@@ -80,10 +81,13 @@
doReturn(mIrs).when(mIrs).getLock();
doReturn(mock(AlarmManager.class)).when(mContext).getSystemService(Context.ALARM_SERVICE);
mScribe = new MockScribe(mIrs, mAnalyst);
+ mAgent = new Agent(mIrs, mScribe, mAnalyst);
}
@After
public void tearDown() {
+ mAgent.tearDownLocked();
+
if (mMockingSession != null) {
mMockingSession.finishMocking();
}
@@ -99,7 +103,6 @@
final int userId = 0;
final String pkgName = "com.test";
- final Agent agent = new Agent(mIrs, mScribe, mAnalyst);
final Ledger ledger = mScribe.getLedgerLocked(userId, pkgName);
doReturn(consumptionLimit).when(mIrs).getConsumptionLimitLocked();
@@ -107,66 +110,64 @@
.getMaxSatiatedBalance(anyInt(), anyString());
Ledger.Transaction transaction = new Ledger.Transaction(0, 0, 0, null, 5, 10);
- agent.recordTransactionLocked(userId, pkgName, ledger, transaction, false);
+ mAgent.recordTransactionLocked(userId, pkgName, ledger, transaction, false);
assertEquals(5, ledger.getCurrentBalance());
assertEquals(remainingCakes - 10, mScribe.getRemainingConsumableCakesLocked());
- agent.onPackageRemovedLocked(userId, pkgName);
+ mAgent.onPackageRemovedLocked(userId, pkgName);
assertEquals(remainingCakes - 10, mScribe.getRemainingConsumableCakesLocked());
assertLedgersEqual(new Ledger(), mScribe.getLedgerLocked(userId, pkgName));
}
@Test
public void testRecordTransaction_UnderMax() {
- Agent agent = new Agent(mIrs, mScribe, mAnalyst);
Ledger ledger = new Ledger();
doReturn(1_000_000L).when(mIrs).getConsumptionLimitLocked();
doReturn(1_000_000L).when(mEconomicPolicy).getMaxSatiatedBalance(anyInt(), anyString());
Ledger.Transaction transaction = new Ledger.Transaction(0, 0, 0, null, 5, 0);
- agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
+ mAgent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
assertEquals(5, ledger.getCurrentBalance());
transaction = new Ledger.Transaction(0, 0, 0, null, 995, 0);
- agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
+ mAgent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
assertEquals(1000, ledger.getCurrentBalance());
transaction = new Ledger.Transaction(0, 0, 0, null, -500, 250);
- agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
+ mAgent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
assertEquals(500, ledger.getCurrentBalance());
transaction = new Ledger.Transaction(0, 0, 0, null, 999_500L, 500);
- agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
+ mAgent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
assertEquals(1_000_000L, ledger.getCurrentBalance());
transaction = new Ledger.Transaction(0, 0, 0, null, -1_000_001L, 1000);
- agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
+ mAgent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
assertEquals(-1, ledger.getCurrentBalance());
}
@Test
public void testRecordTransaction_MaxConsumptionLimit() {
- Agent agent = new Agent(mIrs, mScribe, mAnalyst);
Ledger ledger = new Ledger();
doReturn(1000L).when(mIrs).getConsumptionLimitLocked();
doReturn(1_000_000L).when(mEconomicPolicy).getMaxSatiatedBalance(anyInt(), anyString());
Ledger.Transaction transaction = new Ledger.Transaction(0, 0, 0, null, 5, 0);
- agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
+ mAgent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
assertEquals(5, ledger.getCurrentBalance());
transaction = new Ledger.Transaction(0, 0, 0, null, 995, 0);
- agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
+ mAgent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
assertEquals(1000, ledger.getCurrentBalance());
transaction = new Ledger.Transaction(0, 0, 0, null, -500, 250);
- agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
+ mAgent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
assertEquals(500, ledger.getCurrentBalance());
transaction = new Ledger.Transaction(0, 0, 0, null, 2000, 0);
- agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
+ mAgent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
assertEquals(2500, ledger.getCurrentBalance());
// ConsumptionLimit can change as the battery level changes. Ledger balances shouldn't be
@@ -174,57 +175,56 @@
doReturn(900L).when(mIrs).getConsumptionLimitLocked();
transaction = new Ledger.Transaction(0, 0, 0, null, 100, 0);
- agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
+ mAgent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
assertEquals(2600, ledger.getCurrentBalance());
transaction = new Ledger.Transaction(0, 0, 0, null, -50, 50);
- agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
+ mAgent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
assertEquals(2550, ledger.getCurrentBalance());
transaction = new Ledger.Transaction(0, 0, 0, null, -200, 100);
- agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
+ mAgent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
assertEquals(2350, ledger.getCurrentBalance());
doReturn(800L).when(mIrs).getConsumptionLimitLocked();
transaction = new Ledger.Transaction(0, 0, 0, null, 100, 0);
- agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
+ mAgent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
assertEquals(2450, ledger.getCurrentBalance());
}
@Test
public void testRecordTransaction_MaxSatiatedBalance() {
- Agent agent = new Agent(mIrs, mScribe, mAnalyst);
Ledger ledger = new Ledger();
doReturn(1_000_000L).when(mIrs).getConsumptionLimitLocked();
doReturn(1000L).when(mEconomicPolicy).getMaxSatiatedBalance(anyInt(), anyString());
Ledger.Transaction transaction = new Ledger.Transaction(0, 0, 0, null, 5, 0);
- agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
+ mAgent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
assertEquals(5, ledger.getCurrentBalance());
transaction = new Ledger.Transaction(0, 0, 0, null, 995, 0);
- agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
+ mAgent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
assertEquals(1000, ledger.getCurrentBalance());
transaction = new Ledger.Transaction(0, 0, 0, null, -500, 250);
- agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
+ mAgent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
assertEquals(500, ledger.getCurrentBalance());
transaction = new Ledger.Transaction(0, 0, 0, null, 999_500L, 1000);
- agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
+ mAgent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
assertEquals(1_000, ledger.getCurrentBalance());
// Shouldn't change in normal operation, but adding test case in case it does.
doReturn(900L).when(mEconomicPolicy).getMaxSatiatedBalance(anyInt(), anyString());
transaction = new Ledger.Transaction(0, 0, 0, null, 500, 0);
- agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
+ mAgent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
assertEquals(1_000, ledger.getCurrentBalance());
transaction = new Ledger.Transaction(0, 0, 0, null, -1001, 500);
- agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
+ mAgent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
assertEquals(-1, ledger.getCurrentBalance());
}
}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/ProxyManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/ProxyManagerTest.java
index 6c7b995..035bef6 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/ProxyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/ProxyManagerTest.java
@@ -23,7 +23,14 @@
import static com.android.server.accessibility.ProxyManager.PROXY_COMPONENT_CLASS_NAME;
import static com.android.server.accessibility.ProxyManager.PROXY_COMPONENT_PACKAGE_NAME;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.accessibilityservice.AccessibilityGestureEvent;
import android.accessibilityservice.AccessibilityServiceInfo;
@@ -40,6 +47,7 @@
import android.os.IBinder;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.util.ArraySet;
import android.view.KeyEvent;
import android.view.MotionEvent;
@@ -48,6 +56,8 @@
import android.view.accessibility.IAccessibilityManagerClient;
import android.view.inputmethod.EditorInfo;
+import androidx.test.InstrumentationRegistry;
+
import com.android.internal.R;
import com.android.internal.inputmethod.IAccessibilityInputMethodSession;
import com.android.internal.inputmethod.IAccessibilityInputMethodSessionCallback;
@@ -58,20 +68,12 @@
import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
import com.android.server.wm.WindowManagerInternal;
-import static com.google.common.truth.Truth.assertThat;
-
-import androidx.test.InstrumentationRegistry;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.mockito.Mock;
+import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
@@ -87,6 +89,10 @@
private static final int DEVICE_ID = 10;
private static final int STREAMED_CALLING_UID = 9876;
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+
@Mock private Context mMockContext;
@Mock private AccessibilitySecurityPolicy mMockSecurityPolicy;
@Mock private AccessibilityWindowManager mMockA11yWindowManager;
@@ -110,6 +116,8 @@
MockitoAnnotations.initMocks(this);
final Resources resources = InstrumentationRegistry.getContext().getResources();
+ mSetFlagsRule.enableFlags(Flags.FLAG_PROXY_USE_APPS_ON_VIRTUAL_DEVICE_LISTENER);
+
mFocusStrokeWidthDefaultValue =
resources.getDimensionPixelSize(R.dimen.accessibility_focus_highlight_stroke_width);
mFocusColorDefaultValue = resources.getColor(R.color.accessibility_focus_highlight_color);
@@ -218,6 +226,39 @@
}
/**
+ * Tests that the manager's AppsOnVirtualDeviceListener implementation propagates the running
+ * app changes to the proxy device.
+ */
+ @Test
+ public void testUpdateProxyOfRunningAppsChange_changedUidIsStreamedApp_propagatesChange() {
+ final VirtualDeviceManagerInternal localVdm =
+ Mockito.mock(VirtualDeviceManagerInternal.class);
+ when(localVdm.getDeviceIdsForUid(anyInt())).thenReturn(new ArraySet(Set.of(DEVICE_ID)));
+
+ mProxyManager.setLocalVirtualDeviceManager(localVdm);
+ registerProxy(DISPLAY_ID);
+ verify(localVdm).registerAppsOnVirtualDeviceListener(any());
+
+ final ArraySet<Integer> runningUids = new ArraySet(Set.of(STREAMED_CALLING_UID));
+
+ // Flush any existing messages. The messages after this come from onProxyChanged.
+ mMessageCapturingHandler.sendAllMessages();
+
+ // The virtual device has been updated with the streamed app's UID, so the proxy is
+ // updated.
+ mProxyManager.notifyProxyOfRunningAppsChange(runningUids);
+
+ verify(localVdm).getDeviceIdsForUid(STREAMED_CALLING_UID);
+ verify(mMockProxySystemSupport).getCurrentUserClientsLocked();
+ verify(mMockProxySystemSupport).getGlobalClientsLocked();
+ // Messages to notify IAccessibilityManagerClients should be posted.
+ assertThat(mMessageCapturingHandler.hasMessages()).isTrue();
+
+ mProxyManager.unregisterProxy(DISPLAY_ID);
+ verify(localVdm).unregisterAppsOnVirtualDeviceListener(any());
+ }
+
+ /**
* Tests that getting the first device id for an app uid, such as when an app queries for
* device-specific state, returns the right device id.
*/
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsCollectorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsCollectorTest.java
index 0b730f1..fa6e7f6 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsCollectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsCollectorTest.java
@@ -115,6 +115,11 @@
// Assert that the user doesn't exist in the map initially.
assertThat(mAuthenticationStatsCollector.getAuthenticationStatsForUser(USER_ID_1)).isNull();
+ when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT))
+ .thenReturn(true);
+ when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FACE)).thenReturn(true);
+ when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
+
mAuthenticationStatsCollector.authenticate(USER_ID_1, true /* authenticated */);
AuthenticationStats authenticationStats =
@@ -130,6 +135,11 @@
// Assert that the user doesn't exist in the map initially.
assertThat(mAuthenticationStatsCollector.getAuthenticationStatsForUser(USER_ID_1)).isNull();
+ when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT))
+ .thenReturn(true);
+ when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FACE)).thenReturn(true);
+ when(mFingerprintManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
+
mAuthenticationStatsCollector.authenticate(USER_ID_1, false /* authenticated */);
AuthenticationStats authenticationStats =
@@ -176,6 +186,11 @@
40 /* rejectedAttempts */, 0 /* enrollmentNotifications */,
0 /* modality */));
+ when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT))
+ .thenReturn(true);
+ when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FACE)).thenReturn(true);
+ when(mFingerprintManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
+
mAuthenticationStatsCollector.authenticate(USER_ID_1, false /* authenticated */);
// Assert that no notification should be sent.
@@ -233,13 +248,13 @@
// Assert that no notification should be sent.
verify(mBiometricNotification, never()).sendFaceEnrollNotification(any());
verify(mBiometricNotification, never()).sendFpEnrollNotification(any());
- // Assert that data has been reset.
+ // Assert that data hasn't been reset.
AuthenticationStats authenticationStats = mAuthenticationStatsCollector
.getAuthenticationStatsForUser(USER_ID_1);
- assertThat(authenticationStats.getTotalAttempts()).isEqualTo(0);
- assertThat(authenticationStats.getRejectedAttempts()).isEqualTo(0);
+ assertThat(authenticationStats.getTotalAttempts()).isEqualTo(500);
+ assertThat(authenticationStats.getRejectedAttempts()).isEqualTo(400);
assertThat(authenticationStats.getEnrollmentNotifications()).isEqualTo(0);
- assertThat(authenticationStats.getFrr()).isWithin(0f).of(-1.0f);
+ assertThat(authenticationStats.getFrr()).isWithin(0f).of(0.8f);
}
@Test
@@ -260,13 +275,13 @@
// Assert that no notification should be sent.
verify(mBiometricNotification, never()).sendFaceEnrollNotification(any());
verify(mBiometricNotification, never()).sendFpEnrollNotification(any());
- // Assert that data has been reset.
+ // Assert that data hasn't been reset.
AuthenticationStats authenticationStats = mAuthenticationStatsCollector
.getAuthenticationStatsForUser(USER_ID_1);
- assertThat(authenticationStats.getTotalAttempts()).isEqualTo(0);
- assertThat(authenticationStats.getRejectedAttempts()).isEqualTo(0);
+ assertThat(authenticationStats.getTotalAttempts()).isEqualTo(500);
+ assertThat(authenticationStats.getRejectedAttempts()).isEqualTo(400);
assertThat(authenticationStats.getEnrollmentNotifications()).isEqualTo(0);
- assertThat(authenticationStats.getFrr()).isWithin(0f).of(-1.0f);
+ assertThat(authenticationStats.getFrr()).isWithin(0f).of(0.8f);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java
index 78655a5..c40ad28 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java
@@ -79,9 +79,9 @@
SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS,
/* allowedUsers= */ new ArraySet<>(),
/* activityLaunchAllowedByDefault= */ true,
- /* activityPolicyExceptions= */ new ArraySet<>(),
+ /* activityPolicyExemptions= */ new ArraySet<>(),
/* crossTaskNavigationAllowedByDefault= */ true,
- /* crossTaskNavigationExceptions= */ new ArraySet<>(),
+ /* crossTaskNavigationExemptions= */ new ArraySet<>(),
/* activityListener= */ null,
/* pipBlockedCallback= */ null,
/* activityBlockedCallback= */ null,
diff --git a/services/tests/wmtests/src/com/android/server/wm/CompatScaleProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/CompatScaleProviderTest.java
new file mode 100644
index 0000000..96e3cb1
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/CompatScaleProviderTest.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
+import static org.junit.Assert.assertThrows;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.InOrder;
+
+/**
+ * Tests for the {@link CompatScaleProvider} interface.
+ * See {@link CompatModePackages} class for implementation.
+ *
+ * Build/Install/Run:
+ * atest WmTests:CompatScaleProviderTest
+ */
+@SmallTest
+@Presubmit
+public class CompatScaleProviderTest extends SystemServiceTestsBase {
+ private static final String TEST_PACKAGE = "compat.mode.packages";
+ static final int TEST_USER_ID = 1;
+
+ private ActivityTaskManagerService mAtm;
+
+ /**
+ * setup method before every test.
+ */
+ @Before
+ public void setUp() {
+ mAtm = mSystemServicesTestRule.getActivityTaskManagerService();
+ }
+
+ /**
+ * Registering a {@link CompatScaleProvider} with an invalid id should throw an exception.
+ */
+ @Test
+ public void registerCompatScaleProviderWithInvalidId() {
+ CompatScaleProvider compatScaleProvider = mock(CompatScaleProvider.class);
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> mAtm.registerCompatScaleProvider(-1, compatScaleProvider)
+ );
+ }
+
+ /**
+ * Registering a {@code null} {@link CompatScaleProvider} should throw an exception.
+ */
+ @Test
+ public void registerCompatScaleProviderFailIfCallbackIsNull() {
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> mAtm.registerCompatScaleProvider(
+ CompatScaleProvider.COMPAT_SCALE_MODE_PRODUCT, null)
+ );
+ }
+
+ /**
+ * Registering a {@link CompatScaleProvider} with a already registered id should throw an
+ * exception.
+ */
+ @Test
+ public void registerCompatScaleProviderFailIfIdIsAlreadyRegistered() {
+ CompatScaleProvider compatScaleProvider = mock(CompatScaleProvider.class);
+ mAtm.registerCompatScaleProvider(CompatScaleProvider.COMPAT_SCALE_MODE_PRODUCT,
+ compatScaleProvider);
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> mAtm.registerCompatScaleProvider(
+ CompatScaleProvider.COMPAT_SCALE_MODE_PRODUCT, compatScaleProvider)
+ );
+ mAtm.unregisterCompatScaleProvider(CompatScaleProvider.COMPAT_SCALE_MODE_PRODUCT);
+ }
+
+ /**
+ * Successfully registering a {@link CompatScaleProvider} with should result in callbacks
+ * getting called.
+ */
+ @Test
+ public void registerCompatScaleProviderSuccessfully() {
+ CompatScaleProvider compatScaleProvider = mock(CompatScaleProvider.class);
+ mAtm.registerCompatScaleProvider(CompatScaleProvider.COMPAT_SCALE_MODE_PRODUCT,
+ compatScaleProvider);
+ mAtm.mCompatModePackages.getCompatScale(TEST_PACKAGE, TEST_USER_ID);
+ verify(compatScaleProvider, times(1)).getCompatScale(TEST_PACKAGE, TEST_USER_ID);
+ mAtm.unregisterCompatScaleProvider(CompatScaleProvider.COMPAT_SCALE_MODE_PRODUCT);
+ }
+
+ /**
+ * Unregistering a {@link CompatScaleProvider} with a unregistered id should throw an exception.
+ */
+ @Test
+ public void unregisterCompatScaleProviderFailIfIdNotRegistered() {
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> mAtm.unregisterCompatScaleProvider(
+ CompatScaleProvider.COMPAT_SCALE_MODE_PRODUCT)
+ );
+ }
+
+ /**
+ * Unregistering a {@link CompatScaleProvider} with an invalid id should throw an exception.
+ */
+ @Test
+ public void unregisterCompatScaleProviderFailIfIdNotInRange() {
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> mAtm.unregisterCompatScaleProvider(-1)
+ );
+ }
+
+ /**
+ * Successfully unregistering a {@link CompatScaleProvider} should stop the callbacks from
+ * getting called.
+ */
+ @Test
+ public void unregisterCompatScaleProviderSuccessfully() {
+ CompatScaleProvider compatScaleProvider = mock(CompatScaleProvider.class);
+ mAtm.registerCompatScaleProvider(CompatScaleProvider.COMPAT_SCALE_MODE_PRODUCT,
+ compatScaleProvider);
+ mAtm.unregisterCompatScaleProvider(CompatScaleProvider.COMPAT_SCALE_MODE_PRODUCT);
+ mAtm.mCompatModePackages.getCompatScale(TEST_PACKAGE, TEST_USER_ID);
+ verify(compatScaleProvider, never()).getCompatScale(TEST_PACKAGE, TEST_USER_ID);
+ }
+
+ /**
+ * Order of calling {@link CompatScaleProvider} is same as the id that was used for
+ * registering it.
+ */
+ @Test
+ public void registerCompatScaleProviderRespectsOrderId() {
+ CompatScaleProvider gameModeCompatScaleProvider = mock(CompatScaleProvider.class);
+ CompatScaleProvider productCompatScaleProvider = mock(CompatScaleProvider.class);
+ mAtm.registerCompatScaleProvider(CompatScaleProvider.COMPAT_SCALE_MODE_GAME,
+ gameModeCompatScaleProvider);
+ mAtm.registerCompatScaleProvider(CompatScaleProvider.COMPAT_SCALE_MODE_PRODUCT,
+ productCompatScaleProvider);
+ mAtm.mCompatModePackages.getCompatScale(TEST_PACKAGE, TEST_USER_ID);
+ InOrder inOrder = inOrder(gameModeCompatScaleProvider, productCompatScaleProvider);
+ inOrder.verify(gameModeCompatScaleProvider).getCompatScale(TEST_PACKAGE, TEST_USER_ID);
+ inOrder.verify(productCompatScaleProvider).getCompatScale(TEST_PACKAGE, TEST_USER_ID);
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java b/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
index c84eab3..622e81e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
@@ -16,6 +16,8 @@
package com.android.server.wm;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR;
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
@@ -77,6 +79,7 @@
@RunWith(WindowTestRunner.class)
public class ContentRecorderTests extends WindowTestsBase {
private static IBinder sTaskWindowContainerToken;
+ private DisplayContent mVirtualDisplayContent;
private Task mTask;
private final ContentRecordingSession mDisplaySession =
ContentRecordingSession.createDisplaySession(DEFAULT_DISPLAY);
@@ -107,11 +110,11 @@
displayInfo.logicalWidth = sSurfaceSize.x;
displayInfo.logicalHeight = sSurfaceSize.y;
displayInfo.state = STATE_ON;
- final DisplayContent virtualDisplayContent = createNewDisplay(displayInfo);
- final int displayId = virtualDisplayContent.getDisplayId();
- mContentRecorder = new ContentRecorder(virtualDisplayContent,
+ mVirtualDisplayContent = createNewDisplay(displayInfo);
+ final int displayId = mVirtualDisplayContent.getDisplayId();
+ mContentRecorder = new ContentRecorder(mVirtualDisplayContent,
mMediaProjectionManagerWrapper);
- spyOn(virtualDisplayContent);
+ spyOn(mVirtualDisplayContent);
// GIVEN MediaProjection has already initialized the WindowToken of the DisplayArea to
// record.
@@ -119,7 +122,7 @@
mDisplaySession.setDisplayToRecord(mDefaultDisplay.mDisplayId);
// GIVEN there is a window token associated with a task to record.
- sTaskWindowContainerToken = setUpTaskWindowContainerToken(virtualDisplayContent);
+ sTaskWindowContainerToken = setUpTaskWindowContainerToken(mVirtualDisplayContent);
mTaskSession = ContentRecordingSession.createTaskSession(sTaskWindowContainerToken);
mTaskSession.setVirtualDisplayId(displayId);
@@ -252,7 +255,11 @@
public void testOnConfigurationChanged_resizesSurface() {
mContentRecorder.setContentRecordingSession(mDisplaySession);
mContentRecorder.updateRecording();
- mContentRecorder.onConfigurationChanged(ORIENTATION_PORTRAIT);
+ // Ensure a different orientation when we check if something has changed.
+ @Configuration.Orientation final int lastOrientation =
+ mDisplayContent.getConfiguration().orientation == ORIENTATION_PORTRAIT
+ ? ORIENTATION_LANDSCAPE : ORIENTATION_PORTRAIT;
+ mContentRecorder.onConfigurationChanged(lastOrientation);
verify(mTransaction, atLeast(2)).setPosition(eq(mRecordedSurface), anyFloat(),
anyFloat());
@@ -261,12 +268,53 @@
}
@Test
+ public void testOnConfigurationChanged_resizesVirtualDisplay() {
+ final int newWidth = 55;
+ mContentRecorder.setContentRecordingSession(mDisplaySession);
+ mContentRecorder.updateRecording();
+
+ // The user rotates the device, so the host app resizes the virtual display for the capture.
+ resizeDisplay(mDisplayContent, newWidth, sSurfaceSize.y);
+ resizeDisplay(mVirtualDisplayContent, newWidth, sSurfaceSize.y);
+ mContentRecorder.onConfigurationChanged(mDisplayContent.getConfiguration().orientation);
+
+ verify(mTransaction, atLeast(2)).setPosition(eq(mRecordedSurface), anyFloat(),
+ anyFloat());
+ verify(mTransaction, atLeast(2)).setMatrix(eq(mRecordedSurface), anyFloat(), anyFloat(),
+ anyFloat(), anyFloat());
+ }
+
+ @Test
+ public void testOnConfigurationChanged_rotateVirtualDisplay() {
+ mContentRecorder.setContentRecordingSession(mDisplaySession);
+ mContentRecorder.updateRecording();
+
+ // Change a value that we shouldn't rely upon; it has the wrong type.
+ mVirtualDisplayContent.setOverrideOrientation(SCREEN_ORIENTATION_FULL_SENSOR);
+ mContentRecorder.onConfigurationChanged(
+ mVirtualDisplayContent.getConfiguration().orientation);
+
+ // No resize is issued, only the initial transformations when we started recording.
+ verify(mTransaction).setPosition(eq(mRecordedSurface), anyFloat(),
+ anyFloat());
+ verify(mTransaction).setMatrix(eq(mRecordedSurface), anyFloat(), anyFloat(),
+ anyFloat(), anyFloat());
+ }
+
+ @Test
public void testOnTaskOrientationConfigurationChanged_resizesSurface() {
mContentRecorder.setContentRecordingSession(mTaskSession);
mContentRecorder.updateRecording();
Configuration config = mTask.getConfiguration();
- config.orientation = ORIENTATION_PORTRAIT;
+ // Ensure a different orientation when we compare.
+ @Configuration.Orientation final int orientation =
+ config.orientation == ORIENTATION_PORTRAIT ? ORIENTATION_LANDSCAPE
+ : ORIENTATION_PORTRAIT;
+ final Rect lastBounds = config.windowConfiguration.getBounds();
+ config.orientation = orientation;
+ config.windowConfiguration.setBounds(
+ new Rect(0, 0, lastBounds.height(), lastBounds.width()));
mTask.onConfigurationChanged(config);
verify(mTransaction, atLeast(2)).setPosition(eq(mRecordedSurface), anyFloat(),
@@ -279,13 +327,15 @@
public void testOnTaskBoundsConfigurationChanged_notifiesCallback() {
mTask.getRootTask().setWindowingMode(WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW);
+ final int minWidth = 222;
+ final int minHeight = 777;
final int recordedWidth = 333;
final int recordedHeight = 999;
final ActivityInfo info = new ActivityInfo();
info.windowLayout = new ActivityInfo.WindowLayout(-1 /* width */,
-1 /* widthFraction */, -1 /* height */, -1 /* heightFraction */,
- Gravity.NO_GRAVITY, recordedWidth, recordedHeight);
+ Gravity.NO_GRAVITY, minWidth, minHeight);
mTask.setMinDimensions(info);
// WHEN a recording is ongoing.
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
index 994dcf1..411712e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
@@ -23,8 +23,6 @@
import static android.view.WindowInsets.Type.navigationBars;
import static android.view.WindowInsets.Type.statusBars;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE;
@@ -108,7 +106,7 @@
@Test
public void testControlsForDispatch_forceStatusBarVisible() {
- addStatusBar().mAttrs.privateFlags |= PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR;
+ addStatusBar().mAttrs.forciblyShownTypes |= statusBars();
addNavigationBar();
final InsetsSourceControl[] controls = addAppWindowAndGetControlsForDispatch();
@@ -120,8 +118,8 @@
@Test
public void testControlsForDispatch_statusBarForceShowNavigation() {
- addWindow(TYPE_NOTIFICATION_SHADE, "notificationShade").mAttrs.privateFlags |=
- PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION;
+ addWindow(TYPE_NOTIFICATION_SHADE, "notificationShade").mAttrs.forciblyShownTypes |=
+ navigationBars();
addStatusBar();
addNavigationBar();
@@ -135,7 +133,7 @@
@Test
public void testControlsForDispatch_statusBarForceShowNavigation_butFocusedAnyways() {
WindowState notifShade = addWindow(TYPE_NOTIFICATION_SHADE, "notificationShade");
- notifShade.mAttrs.privateFlags |= PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION;
+ notifShade.mAttrs.forciblyShownTypes |= navigationBars();
addNavigationBar();
mDisplayContent.getInsetsPolicy().updateBarControlTarget(notifShade);
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 0cdd9b8..8f68c0f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -112,6 +112,7 @@
import android.view.WindowInsets;
import android.view.WindowManager;
+import androidx.test.filters.FlakyTest;
import androidx.test.filters.MediumTest;
import com.android.internal.policy.SystemBarUtils;
@@ -2361,6 +2362,7 @@
}
@Test
+ @FlakyTest(bugId = 299220009)
public void testUserOverrideAspectRatioNotEnabled() {
setUpDisplaySizeWithApp(/* dw */ 1600, /* dh */ 1400);
@@ -2409,8 +2411,9 @@
.setUid(android.os.Process.myUid())
.build();
activity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
- activity.mWmService.mLetterboxConfiguration
- .setUserAppAspectRatioSettingsOverrideEnabled(enabled);
+ spyOn(activity.mWmService.mLetterboxConfiguration);
+ doReturn(enabled).when(activity.mWmService.mLetterboxConfiguration)
+ .isUserAppAspectRatioSettingsEnabled();
// Set user aspect ratio override
final IPackageManager pm = mAtm.getPackageManager();
try {
@@ -4249,6 +4252,7 @@
// Set up a display in landscape with a fixed-orientation PORTRAIT app
setUpDisplaySizeWithApp(2800, 1400);
mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+ mWm.mLetterboxConfiguration.setIsAutomaticReachabilityInBookModeEnabled(false);
mWm.mLetterboxConfiguration.setLetterboxHorizontalPositionMultiplier(0.5f);
prepareUnresizable(mActivity, 1.75f, SCREEN_ORIENTATION_PORTRAIT);
diff --git a/services/usage/java/com/android/server/usage/UsageStatsDatabase.java b/services/usage/java/com/android/server/usage/UsageStatsDatabase.java
index f1c5865..b028b47 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsDatabase.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsDatabase.java
@@ -22,6 +22,7 @@
import android.app.usage.UsageStats;
import android.app.usage.UsageStatsManager;
import android.os.Build;
+import android.os.SystemClock;
import android.os.SystemProperties;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -1567,6 +1568,13 @@
}
}
+ void deleteDataFor(String pkg) {
+ // reuse the existing prune method to delete data for the specified package.
+ // we'll use the current timestamp so that all events before now get pruned.
+ prunePackagesDataOnUpgrade(
+ new HashMap<>(Collections.singletonMap(pkg, SystemClock.elapsedRealtime())));
+ }
+
IntervalStats readIntervalStatsForFile(int interval, long fileName) {
synchronized (mLock) {
final IntervalStats stats = new IntervalStats();
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 90b798c..7db32a9 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -2043,6 +2043,12 @@
mAppStandby.clearLastUsedTimestampsForTest(packageName, userId);
}
+ void deletePackageData(@NonNull String packageName, @UserIdInt int userId) {
+ synchronized (mLock) {
+ mUserState.get(userId).deleteDataFor(packageName);
+ }
+ }
+
private final class BinderService extends IUsageStatsManager.Stub {
private boolean hasPermission(String callingPackage) {
diff --git a/services/usage/java/com/android/server/usage/UsageStatsShellCommand.java b/services/usage/java/com/android/server/usage/UsageStatsShellCommand.java
index 772b22a..4cb31f9 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsShellCommand.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsShellCommand.java
@@ -38,6 +38,8 @@
switch (cmd) {
case "clear-last-used-timestamps":
return runClearLastUsedTimestamps();
+ case "delete-package-data":
+ return deletePackageData();
default:
return handleDefaultCommands(cmd);
}
@@ -51,14 +53,38 @@
pw.println(" Print this help text.");
pw.println();
pw.println("clear-last-used-timestamps PACKAGE_NAME [-u | --user USER_ID]");
- pw.println(" Clears any existing usage data for the given package.");
+ pw.println(" Clears the last used timestamps for the given package.");
+ pw.println();
+ pw.println("delete-package-data PACKAGE_NAME [-u | --user USER_ID]");
+ pw.println(" Deletes all the usage stats for the given package.");
pw.println();
}
@SuppressLint("AndroidFrameworkRequiresPermission")
private int runClearLastUsedTimestamps() {
final String packageName = getNextArgRequired();
+ final int userId = getUserId();
+ if (userId == -1) {
+ return -1;
+ }
+ mService.clearLastUsedTimestamps(packageName, userId);
+ return 0;
+ }
+
+ @SuppressLint("AndroidFrameworkRequiresPermission")
+ private int deletePackageData() {
+ final String packageName = getNextArgRequired();
+ final int userId = getUserId();
+ if (userId == -1) {
+ return -1;
+ }
+
+ mService.deletePackageData(packageName, userId);
+ return 0;
+ }
+
+ private int getUserId() {
int userId = UserHandle.USER_CURRENT;
String opt;
while ((opt = getNextOption()) != null) {
@@ -72,8 +98,6 @@
if (userId == UserHandle.USER_CURRENT) {
userId = ActivityManager.getCurrentUser();
}
-
- mService.clearLastUsedTimestamps(packageName, userId);
- return 0;
+ return userId;
}
}
diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
index fd56b6e..7d2e1a4 100644
--- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
@@ -974,6 +974,10 @@
mDatabase.dumpMappings(ipw);
}
+ void deleteDataFor(String pkg) {
+ mDatabase.deleteDataFor(pkg);
+ }
+
void dumpFile(IndentingPrintWriter ipw, String[] args) {
if (args == null || args.length == 0) {
// dump all files for every interval for specified user
diff --git a/tests/TrustTests/Android.bp b/tests/TrustTests/Android.bp
index a1b888a..c216bce 100644
--- a/tests/TrustTests/Android.bp
+++ b/tests/TrustTests/Android.bp
@@ -25,6 +25,7 @@
"androidx.test.rules",
"androidx.test.ext.junit",
"androidx.test.uiautomator_uiautomator",
+ "flag-junit",
"mockito-target-minus-junit4",
"servicestests-utils",
"truth-prebuilt",
diff --git a/tests/TrustTests/src/android/trust/test/GrantAndRevokeTrustTest.kt b/tests/TrustTests/src/android/trust/test/GrantAndRevokeTrustTest.kt
index f864fed..1dfd5c0 100644
--- a/tests/TrustTests/src/android/trust/test/GrantAndRevokeTrustTest.kt
+++ b/tests/TrustTests/src/android/trust/test/GrantAndRevokeTrustTest.kt
@@ -16,6 +16,10 @@
package android.trust.test
+import android.content.pm.PackageManager
+import android.platform.test.annotations.RequiresFlagsDisabled
+import android.platform.test.annotations.RequiresFlagsEnabled
+import android.platform.test.flag.junit.DeviceFlagsValueProvider
import android.service.trust.GrantTrustResult
import android.trust.BaseTrustAgentService
import android.trust.TrustTestActivity
@@ -27,6 +31,7 @@
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
import androidx.test.uiautomator.UiDevice
import com.android.server.testutils.mock
+import org.junit.Assume.assumeFalse
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -45,6 +50,7 @@
private val activityScenarioRule = ActivityScenarioRule(TrustTestActivity::class.java)
private val lockStateTrackingRule = LockStateTrackingRule()
private val trustAgentRule = TrustAgentRule<GrantAndRevokeTrustAgent>()
+ private val packageManager = getInstrumentation().getTargetContext().getPackageManager()
@get:Rule
val rule: RuleChain = RuleChain
@@ -52,6 +58,7 @@
.around(ScreenLockRule())
.around(lockStateTrackingRule)
.around(trustAgentRule)
+ .around(DeviceFlagsValueProvider.createCheckFlagsRule())
@Before
fun manageTrust() {
@@ -72,7 +79,7 @@
trustAgentRule.agent.grantTrust(GRANT_MESSAGE, 10000, 0) {}
uiDevice.sleep()
- lockStateTrackingRule.assertUnlocked()
+ lockStateTrackingRule.assertUnlockedAndTrusted()
}
@Test
@@ -86,6 +93,51 @@
}
@Test
+ @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS)
+ fun grantCannotActivelyUnlockDevice() {
+ // On automotive, trust agents can actively unlock the device.
+ assumeFalse(packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE))
+
+ // Lock the device.
+ uiDevice.sleep()
+ lockStateTrackingRule.assertLocked()
+
+ // Grant trust.
+ trustAgentRule.agent.grantTrust(GRANT_MESSAGE, 10000, 0) {}
+
+ // The grant should not have unlocked the device. Wait a bit so that
+ // TrustManagerService probably will have finished processing the grant.
+ await()
+ lockStateTrackingRule.assertLocked()
+
+ // Turn the screen on and off to cause TrustManagerService to refresh
+ // its deviceLocked state. Then verify the state is still locked. This
+ // part failed before the fix for b/296464083.
+ uiDevice.wakeUp()
+ uiDevice.sleep()
+ await()
+ lockStateTrackingRule.assertLocked()
+ }
+
+ @Test
+ @RequiresFlagsDisabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS)
+ fun grantCouldCauseWrongDeviceLockedStateDueToBug() {
+ // On automotive, trust agents can actively unlock the device.
+ assumeFalse(packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE))
+
+ // Verify that b/296464083 exists. That is, when the device is locked
+ // and a trust agent grants trust, the deviceLocked state incorrectly
+ // becomes false even though the device correctly remains locked.
+ uiDevice.sleep()
+ lockStateTrackingRule.assertLocked()
+ trustAgentRule.agent.grantTrust(GRANT_MESSAGE, 10000, 0) {}
+ uiDevice.wakeUp()
+ uiDevice.sleep()
+ await()
+ lockStateTrackingRule.assertUnlockedButNotReally()
+ }
+
+ @Test
fun grantDoesNotCallBack() {
val callback = mock<(GrantTrustResult) -> Unit>()
trustAgentRule.agent.grantTrust(GRANT_MESSAGE, 0, 0, callback)
diff --git a/tests/TrustTests/src/android/trust/test/TemporaryAndRenewableTrustTest.kt b/tests/TrustTests/src/android/trust/test/TemporaryAndRenewableTrustTest.kt
index ae72247..96362b8 100644
--- a/tests/TrustTests/src/android/trust/test/TemporaryAndRenewableTrustTest.kt
+++ b/tests/TrustTests/src/android/trust/test/TemporaryAndRenewableTrustTest.kt
@@ -102,7 +102,7 @@
trustAgentRule.agent.grantTrust(
GRANT_MESSAGE, 0, FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE) {}
- lockStateTrackingRule.assertUnlocked()
+ lockStateTrackingRule.assertUnlockedAndTrusted()
}
@Test
@@ -125,7 +125,7 @@
Log.i(TAG, "Callback received; status=${it.status}")
result = it
}
- lockStateTrackingRule.assertUnlocked()
+ lockStateTrackingRule.assertUnlockedAndTrusted()
wait("callback triggered") { result?.status == STATUS_UNLOCKED_BY_GRANT }
}
diff --git a/tests/TrustTests/src/android/trust/test/lib/LockStateTrackingRule.kt b/tests/TrustTests/src/android/trust/test/lib/LockStateTrackingRule.kt
index c1a7bd9..5a8f828 100644
--- a/tests/TrustTests/src/android/trust/test/lib/LockStateTrackingRule.kt
+++ b/tests/TrustTests/src/android/trust/test/lib/LockStateTrackingRule.kt
@@ -16,6 +16,7 @@
package android.trust.test.lib
+import android.app.KeyguardManager
import android.app.trust.TrustManager
import android.content.Context
import android.util.Log
@@ -26,18 +27,23 @@
import org.junit.runners.model.Statement
/**
- * Rule for tracking the lock state of the device based on events emitted to [TrustListener].
+ * Rule for tracking the trusted state of the device based on events emitted to
+ * [TrustListener]. Provides helper methods for verifying that the trusted
+ * state has a particular value and is consistent with (a) the keyguard "locked"
+ * (i.e. showing) value when applicable, and (b) the device locked value that is
+ * tracked by TrustManagerService and is queryable via KeyguardManager.
*/
class LockStateTrackingRule : TestRule {
private val context: Context = getApplicationContext()
private val windowManager = checkNotNull(WindowManagerGlobal.getWindowManagerService())
+ private val keyguardManager = context.getSystemService(KeyguardManager::class.java) as KeyguardManager
- @Volatile lateinit var lockState: LockState
+ @Volatile lateinit var trustState: TrustState
private set
override fun apply(base: Statement, description: Description) = object : Statement() {
override fun evaluate() {
- lockState = LockState(locked = windowManager.isKeyguardLocked)
+ trustState = TrustState()
val trustManager = context.getSystemService(TrustManager::class.java) as TrustManager
val listener = Listener()
@@ -51,12 +57,25 @@
}
fun assertLocked() {
- wait("un-locked per TrustListener") { lockState.locked == true }
- wait("keyguard lock") { windowManager.isKeyguardLocked }
+ wait("device locked") { keyguardManager.isDeviceLocked }
+ // isDeviceLocked implies isKeyguardLocked && !trusted.
+ wait("keyguard locked") { windowManager.isKeyguardLocked }
+ wait("not trusted") { trustState.trusted == false }
}
- fun assertUnlocked() {
- wait("locked per TrustListener") { lockState.locked == false }
+ // TODO(b/299298338) remove this when removing FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS
+ fun assertUnlockedButNotReally() {
+ wait("device unlocked") { !keyguardManager.isDeviceLocked }
+ wait("not trusted") { trustState.trusted == false }
+ wait("keyguard locked") { windowManager.isKeyguardLocked }
+ }
+
+ fun assertUnlockedAndTrusted() {
+ wait("device unlocked") { !keyguardManager.isDeviceLocked }
+ wait("trusted") { trustState.trusted == true }
+ // Can't check for !isKeyguardLocked here, since isKeyguardLocked
+ // returns true in the case where the keyguard is dismissible with
+ // swipe, which is considered "device unlocked"!
}
inner class Listener : TestTrustListener() {
@@ -68,12 +87,12 @@
trustGrantedMessages: MutableList<String>
) {
Log.d(TAG, "Device became trusted=$enabled")
- lockState = lockState.copy(locked = !enabled)
+ trustState = trustState.copy(trusted=enabled)
}
}
- data class LockState(
- val locked: Boolean? = null
+ data class TrustState(
+ val trusted: Boolean? = null
)
companion object {
diff --git a/tools/aapt/ZipEntry.cpp b/tools/aapt/ZipEntry.cpp
index 5339285..6886993 100644
--- a/tools/aapt/ZipEntry.cpp
+++ b/tools/aapt/ZipEntry.cpp
@@ -18,6 +18,8 @@
// Access to entries in a Zip archive.
//
+#define _POSIX_THREAD_SAFE_FUNCTIONS // For mingw localtime_r().
+
#define LOG_TAG "zip"
#include "ZipEntry.h"
@@ -337,39 +339,26 @@
/*
* Set the CDE/LFH timestamp from UNIX time.
*/
-void ZipEntry::setModWhen(time_t when)
-{
-#if !defined(_WIN32)
- struct tm tmResult;
-#endif
- time_t even;
- unsigned short zdate, ztime;
-
- struct tm* ptm;
-
+void ZipEntry::setModWhen(time_t when) {
/* round up to an even number of seconds */
- even = (time_t)(((unsigned long)(when) + 1) & (~1));
+ time_t even = (time_t)(((unsigned long)(when) + 1) & (~1));
/* expand */
-#if !defined(_WIN32)
- ptm = localtime_r(&even, &tmResult);
-#else
- ptm = localtime(&even);
-#endif
+ struct tm tmResult;
+ struct tm* ptm = localtime_r(&even, &tmResult);
int year;
year = ptm->tm_year;
if (year < 80)
year = 80;
- zdate = (year - 80) << 9 | (ptm->tm_mon+1) << 5 | ptm->tm_mday;
- ztime = ptm->tm_hour << 11 | ptm->tm_min << 5 | ptm->tm_sec >> 1;
+ unsigned short zdate = (year - 80) << 9 | (ptm->tm_mon + 1) << 5 | ptm->tm_mday;
+ unsigned short ztime = ptm->tm_hour << 11 | ptm->tm_min << 5 | ptm->tm_sec >> 1;
mCDE.mLastModFileTime = mLFH.mLastModFileTime = ztime;
mCDE.mLastModFileDate = mLFH.mLastModFileDate = zdate;
}
-
/*
* ===========================================================================
* ZipEntry::LocalFileHeader
diff --git a/tools/aapt2/compile/InlineXmlFormatParser.h b/tools/aapt2/compile/InlineXmlFormatParser.h
index 4300023..3a5161b 100644
--- a/tools/aapt2/compile/InlineXmlFormatParser.h
+++ b/tools/aapt2/compile/InlineXmlFormatParser.h
@@ -21,8 +21,8 @@
#include <vector>
#include "android-base/macros.h"
-
#include "process/IResourceTableConsumer.h"
+#include "xml/XmlDom.h"
namespace aapt {
diff --git a/tools/aapt2/format/Archive_test.cpp b/tools/aapt2/format/Archive_test.cpp
index 3c44da7..fd50af9 100644
--- a/tools/aapt2/format/Archive_test.cpp
+++ b/tools/aapt2/format/Archive_test.cpp
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+#include <stdlib.h>
+
#include "test/Test.h"
namespace aapt {
@@ -34,6 +36,29 @@
std::string error_;
};
+class TzSetter {
+ public:
+ explicit TzSetter(const std::string& new_tz) {
+ old_tz_ = getenv("TZ");
+ new_tz_ = "TZ=" + new_tz;
+ putenv(const_cast<char*>(new_tz_.c_str()));
+ tzset();
+ }
+
+ ~TzSetter() {
+ if (old_tz_) {
+ putenv(old_tz_);
+ } else {
+ putenv(const_cast<char*>("TZ"));
+ }
+ tzset();
+ }
+
+ private:
+ char* old_tz_;
+ std::string new_tz_;
+};
+
std::unique_ptr<uint8_t[]> MakeTestArray() {
auto array = std::make_unique<uint8_t[]>(kTestDataLength);
for (int index = 0; index < kTestDataLength; ++index) {
@@ -86,6 +111,22 @@
}
}
+void VerifyZipFileTimestamps(const std::string& output_path) {
+ std::unique_ptr<io::ZipFileCollection> zip = io::ZipFileCollection::Create(output_path, nullptr);
+ auto it = zip->Iterator();
+ while (it->HasNext()) {
+ auto file = it->Next();
+ struct tm modification_time;
+ ASSERT_TRUE(file->GetModificationTime(&modification_time));
+ EXPECT_EQ(modification_time.tm_year, 80);
+ EXPECT_EQ(modification_time.tm_mon, 0);
+ EXPECT_EQ(modification_time.tm_mday, 1);
+ EXPECT_EQ(modification_time.tm_hour, 0);
+ EXPECT_EQ(modification_time.tm_min, 0);
+ EXPECT_EQ(modification_time.tm_sec, 0);
+ }
+}
+
TEST_F(ArchiveTest, DirectoryWriteEntrySuccess) {
std::string output_path = GetTestPath("output");
std::unique_ptr<IArchiveWriter> writer = MakeDirectoryWriter(output_path);
@@ -206,4 +247,73 @@
ASSERT_EQ("ZipFileWriteFileError", writer->GetError());
}
+TEST_F(ArchiveTest, ZipFileTimeZoneUTC) {
+ TzSetter tz("UTC0");
+ std::string output_path = GetTestPath("output.apk");
+ std::unique_ptr<IArchiveWriter> writer = MakeZipFileWriter(output_path);
+ std::unique_ptr<uint8_t[]> data1 = MakeTestArray();
+ std::unique_ptr<uint8_t[]> data2 = MakeTestArray();
+
+ ASSERT_TRUE(writer->StartEntry("test1", 0));
+ ASSERT_TRUE(writer->Write(static_cast<const void*>(data1.get()), kTestDataLength));
+ ASSERT_TRUE(writer->FinishEntry());
+ ASSERT_FALSE(writer->HadError());
+
+ ASSERT_TRUE(writer->StartEntry("test2", 0));
+ ASSERT_TRUE(writer->Write(static_cast<const void*>(data2.get()), kTestDataLength));
+ ASSERT_TRUE(writer->FinishEntry());
+ ASSERT_FALSE(writer->HadError());
+
+ writer.reset();
+
+ // All zip file entries must have the same timestamp, regardless of time zone. See: b/277978832
+ VerifyZipFileTimestamps(output_path);
+}
+
+TEST_F(ArchiveTest, ZipFileTimeZoneWestOfUTC) {
+ TzSetter tz("PST8");
+ std::string output_path = GetTestPath("output.apk");
+ std::unique_ptr<IArchiveWriter> writer = MakeZipFileWriter(output_path);
+ std::unique_ptr<uint8_t[]> data1 = MakeTestArray();
+ std::unique_ptr<uint8_t[]> data2 = MakeTestArray();
+
+ ASSERT_TRUE(writer->StartEntry("test1", 0));
+ ASSERT_TRUE(writer->Write(static_cast<const void*>(data1.get()), kTestDataLength));
+ ASSERT_TRUE(writer->FinishEntry());
+ ASSERT_FALSE(writer->HadError());
+
+ ASSERT_TRUE(writer->StartEntry("test2", 0));
+ ASSERT_TRUE(writer->Write(static_cast<const void*>(data2.get()), kTestDataLength));
+ ASSERT_TRUE(writer->FinishEntry());
+ ASSERT_FALSE(writer->HadError());
+
+ writer.reset();
+
+ // All zip file entries must have the same timestamp, regardless of time zone. See: b/277978832
+ VerifyZipFileTimestamps(output_path);
+}
+
+TEST_F(ArchiveTest, ZipFileTimeZoneEastOfUTC) {
+ TzSetter tz("EET-2");
+ std::string output_path = GetTestPath("output.apk");
+ std::unique_ptr<IArchiveWriter> writer = MakeZipFileWriter(output_path);
+ std::unique_ptr<uint8_t[]> data1 = MakeTestArray();
+ std::unique_ptr<uint8_t[]> data2 = MakeTestArray();
+
+ ASSERT_TRUE(writer->StartEntry("test1", 0));
+ ASSERT_TRUE(writer->Write(static_cast<const void*>(data1.get()), kTestDataLength));
+ ASSERT_TRUE(writer->FinishEntry());
+ ASSERT_FALSE(writer->HadError());
+
+ ASSERT_TRUE(writer->StartEntry("test2", 0));
+ ASSERT_TRUE(writer->Write(static_cast<const void*>(data2.get()), kTestDataLength));
+ ASSERT_TRUE(writer->FinishEntry());
+ ASSERT_FALSE(writer->HadError());
+
+ writer.reset();
+
+ // All zip file entries must have the same timestamp, regardless of time zone. See: b/277978832
+ VerifyZipFileTimestamps(output_path);
+}
+
} // namespace aapt
diff --git a/tools/aapt2/io/File.h b/tools/aapt2/io/File.h
index 08d497d..673d1b7 100644
--- a/tools/aapt2/io/File.h
+++ b/tools/aapt2/io/File.h
@@ -57,6 +57,11 @@
return false;
}
+ // Fills in buf with the last modification time of the file. Returns true if successful,
+ // otherwise false (i.e., the operation is not supported or the file system is unable to provide
+ // a last modification time).
+ virtual bool GetModificationTime(struct tm* buf) const = 0;
+
private:
// Any segments created from this IFile need to be owned by this IFile, so
// keep them
@@ -79,6 +84,10 @@
return file_->GetSource();
}
+ bool GetModificationTime(struct tm* buf) const override {
+ return file_->GetModificationTime(buf);
+ };
+
private:
DISALLOW_COPY_AND_ASSIGN(FileSegment);
diff --git a/tools/aapt2/io/FileSystem.cpp b/tools/aapt2/io/FileSystem.cpp
index a64982a..6a692e4 100644
--- a/tools/aapt2/io/FileSystem.cpp
+++ b/tools/aapt2/io/FileSystem.cpp
@@ -14,9 +14,12 @@
* limitations under the License.
*/
+#define _POSIX_THREAD_SAFE_FUNCTIONS // For mingw localtime_r().
+
#include "io/FileSystem.h"
#include <dirent.h>
+#include <sys/stat.h>
#include "android-base/errors.h"
#include "androidfw/Source.h"
@@ -54,6 +57,23 @@
return source_;
}
+bool RegularFile::GetModificationTime(struct tm* buf) const {
+ if (buf == nullptr) {
+ return false;
+ }
+ struct stat stat_buf;
+ if (stat(source_.path.c_str(), &stat_buf) != 0) {
+ return false;
+ }
+
+ struct tm* ptm;
+ struct tm tm_result;
+ ptm = localtime_r(&stat_buf.st_mtime, &tm_result);
+
+ *buf = *ptm;
+ return true;
+}
+
FileCollectionIterator::FileCollectionIterator(FileCollection* collection)
: current_(collection->files_.begin()), end_(collection->files_.end()) {}
diff --git a/tools/aapt2/io/FileSystem.h b/tools/aapt2/io/FileSystem.h
index 0e798fc..f975196 100644
--- a/tools/aapt2/io/FileSystem.h
+++ b/tools/aapt2/io/FileSystem.h
@@ -32,6 +32,7 @@
std::unique_ptr<IData> OpenAsData() override;
std::unique_ptr<io::InputStream> OpenInputStream() override;
const android::Source& GetSource() const override;
+ bool GetModificationTime(struct tm* buf) const override;
private:
DISALLOW_COPY_AND_ASSIGN(RegularFile);
diff --git a/tools/aapt2/io/ZipArchive.cpp b/tools/aapt2/io/ZipArchive.cpp
index 4a5385d..cb5bbe9 100644
--- a/tools/aapt2/io/ZipArchive.cpp
+++ b/tools/aapt2/io/ZipArchive.cpp
@@ -75,6 +75,14 @@
return zip_entry_.method != kCompressStored;
}
+bool ZipFile::GetModificationTime(struct tm* buf) const {
+ if (buf == nullptr) {
+ return false;
+ }
+ *buf = zip_entry_.GetModificationTime();
+ return true;
+}
+
ZipFileCollectionIterator::ZipFileCollectionIterator(
ZipFileCollection* collection)
: current_(collection->files_.begin()), end_(collection->files_.end()) {}
diff --git a/tools/aapt2/io/ZipArchive.h b/tools/aapt2/io/ZipArchive.h
index c263aa4..ac125d0 100644
--- a/tools/aapt2/io/ZipArchive.h
+++ b/tools/aapt2/io/ZipArchive.h
@@ -38,6 +38,7 @@
std::unique_ptr<io::InputStream> OpenInputStream() override;
const android::Source& GetSource() const override;
bool WasCompressed() override;
+ bool GetModificationTime(struct tm* buf) const override;
private:
::ZipArchiveHandle zip_handle_;
diff --git a/tools/aapt2/test/Common.h b/tools/aapt2/test/Common.h
index 83a0f3f..e48668c 100644
--- a/tools/aapt2/test/Common.h
+++ b/tools/aapt2/test/Common.h
@@ -98,6 +98,10 @@
return source_;
}
+ bool GetModificationTime(struct tm* buf) const override {
+ return false;
+ };
+
private:
DISALLOW_COPY_AND_ASSIGN(TestFile);
diff --git a/tools/lint/common/Android.bp b/tools/lint/common/Android.bp
index 898f88b..8bfbfe5 100644
--- a/tools/lint/common/Android.bp
+++ b/tools/lint/common/Android.bp
@@ -27,3 +27,30 @@
libs: ["lint_api"],
kotlincflags: ["-Xjvm-default=all"],
}
+
+java_defaults {
+ name: "AndroidLintCheckerTestDefaults",
+ srcs: ["checks/src/test/java/**/*.kt"],
+ static_libs: [
+ "junit",
+ "lint",
+ "lint_tests",
+ ],
+ test_options: {
+ unit_test: true,
+ tradefed_options: [
+ {
+ // lint bundles in some classes that were built with older versions
+ // of libraries, and no longer load. Since tradefed tries to load
+ // all classes in the jar to look for tests, it crashes loading them.
+ // Exclude these classes from tradefed's search.
+ name: "exclude-paths",
+ value: "org/apache",
+ },
+ {
+ name: "exclude-paths",
+ value: "META-INF",
+ },
+ ],
+ },
+}
diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/AidlImplementationDetector.kt b/tools/lint/common/src/main/java/com/google/android/lint/aidl/AidlImplementationDetector.kt
similarity index 100%
rename from tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/AidlImplementationDetector.kt
rename to tools/lint/common/src/main/java/com/google/android/lint/aidl/AidlImplementationDetector.kt
diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/Constants.kt b/tools/lint/common/src/main/java/com/google/android/lint/aidl/Constants.kt
similarity index 99%
rename from tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/Constants.kt
rename to tools/lint/common/src/main/java/com/google/android/lint/aidl/Constants.kt
index f1727b7..a18ed15 100644
--- a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/Constants.kt
+++ b/tools/lint/common/src/main/java/com/google/android/lint/aidl/Constants.kt
@@ -29,9 +29,10 @@
const val BINDER_CLASS = "android.os.Binder"
const val IINTERFACE_INTERFACE = "android.os.IInterface"
-const val AIDL_PERMISSION_HELPER_SUFFIX = "_enforcePermission"
const val PERMISSION_PREFIX_LITERAL = "android.permission."
+const val AIDL_PERMISSION_HELPER_SUFFIX = "_enforcePermission"
+
/**
* If a non java (e.g. c++) backend is enabled, the @EnforcePermission
* annotation cannot be used. At time of writing, the mechanism
diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionUtils.kt b/tools/lint/common/src/main/java/com/google/android/lint/aidl/EnforcePermissionUtils.kt
similarity index 100%
rename from tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionUtils.kt
rename to tools/lint/common/src/main/java/com/google/android/lint/aidl/EnforcePermissionUtils.kt
diff --git a/tools/lint/fix/soong_lint_fix.py b/tools/lint/fix/soong_lint_fix.py
index cd4d778d..acc0ad0 100644
--- a/tools/lint/fix/soong_lint_fix.py
+++ b/tools/lint/fix/soong_lint_fix.py
@@ -29,6 +29,39 @@
PATH_SUFFIX = "android_common/lint"
FIX_ZIP = "suggested-fixes.zip"
+
+class SoongModule:
+ """A Soong module to lint.
+
+ The constructor takes the name of the module (for example,
+ "framework-minus-apex"). find() must be called to extract the intermediate
+ module path from Soong's module-info.json
+ """
+ def __init__(self, name):
+ self._name = name
+
+ def find(self, module_info):
+ """Finds the module in the loaded module_info.json."""
+ if self._name not in module_info:
+ raise Exception(f"Module {self._name} not found!")
+
+ partial_path = module_info[self._name]["path"][0]
+ print(f"Found module {partial_path}/{self._name}.")
+ self._path = f"{PATH_PREFIX}/{partial_path}/{self._name}/{PATH_SUFFIX}"
+
+ @property
+ def name(self):
+ return self._name
+
+ @property
+ def lint_report(self):
+ return f"{self._path}/lint-report.txt"
+
+ @property
+ def suggested_fixes(self):
+ return f"{self._path}/{FIX_ZIP}"
+
+
class SoongLintFix:
"""
This class creates a command line tool that will
@@ -53,16 +86,14 @@
self._parser = _setup_parser()
self._args = None
self._kwargs = None
- self._path = None
- self._target = None
+ self._modules = []
-
- def run(self, additional_setup=None, custom_fix=None):
+ def run(self):
"""
Run the script
"""
self._setup()
- self._find_module()
+ self._find_modules()
self._lint()
if not self._args.no_fix:
@@ -87,8 +118,6 @@
os.chdir(ANDROID_BUILD_TOP)
-
- def _find_module(self):
print("Refreshing soong modules...")
try:
os.mkdir(ANDROID_PRODUCT_OUT)
@@ -97,48 +126,47 @@
subprocess.call(f"{SOONG_UI} --make-mode {PRODUCT_OUT}/module-info.json", **self._kwargs)
print("done.")
+
+ def _find_modules(self):
with open(f"{ANDROID_PRODUCT_OUT}/module-info.json") as f:
module_info = json.load(f)
- if self._args.module not in module_info:
- sys.exit(f"Module {self._args.module} not found!")
-
- module_path = module_info[self._args.module]["path"][0]
- print(f"Found module {module_path}/{self._args.module}.")
-
- self._path = f"{PATH_PREFIX}/{module_path}/{self._args.module}/{PATH_SUFFIX}"
- self._target = f"{self._path}/lint-report.txt"
-
+ for module_name in self._args.modules:
+ module = SoongModule(module_name)
+ module.find(module_info)
+ self._modules.append(module)
def _lint(self):
print("Cleaning up any old lint results...")
- try:
- os.remove(f"{self._target}")
- os.remove(f"{self._path}/{FIX_ZIP}")
- except FileNotFoundError:
- pass
+ for module in self._modules:
+ try:
+ os.remove(f"{module.lint_report}")
+ os.remove(f"{module.suggested_fixes}")
+ except FileNotFoundError:
+ pass
print("done.")
- print(f"Generating {self._target}")
- subprocess.call(f"{SOONG_UI} --make-mode {self._target}", **self._kwargs)
+ target = " ".join([ module.lint_report for module in self._modules ])
+ print(f"Generating {target}")
+ subprocess.call(f"{SOONG_UI} --make-mode {target}", **self._kwargs)
print("done.")
-
def _fix(self):
- print("Copying suggested fixes to the tree...")
- with zipfile.ZipFile(f"{self._path}/{FIX_ZIP}") as zip:
- for name in zip.namelist():
- if name.startswith("out") or not name.endswith(".java"):
- continue
- with zip.open(name) as src, open(f"{ANDROID_BUILD_TOP}/{name}", "wb") as dst:
- shutil.copyfileobj(src, dst)
+ for module in self._modules:
+ print(f"Copying suggested fixes for {module.name} to the tree...")
+ with zipfile.ZipFile(f"{module.suggested_fixes}") as zip:
+ for name in zip.namelist():
+ if name.startswith("out") or not name.endswith(".java"):
+ continue
+ with zip.open(name) as src, open(f"{ANDROID_BUILD_TOP}/{name}", "wb") as dst:
+ shutil.copyfileobj(src, dst)
print("done.")
-
def _print(self):
- print("### lint-report.txt ###", end="\n\n")
- with open(self._target, "r") as f:
- print(f.read())
+ for module in self._modules:
+ print(f"### lint-report.txt {module.name} ###", end="\n\n")
+ with open(module.lint_report, "r") as f:
+ print(f.read())
def _setup_parser():
@@ -151,7 +179,8 @@
**Gotcha**: You must have run `source build/envsetup.sh` and `lunch` first.
""", formatter_class=argparse.RawTextHelpFormatter)
- parser.add_argument('module',
+ parser.add_argument('modules',
+ nargs='+',
help='The soong build module to run '
'(e.g. framework-minus-apex or services.core.unboosted)')
@@ -170,4 +199,4 @@
return parser
if __name__ == "__main__":
- SoongLintFix().run()
\ No newline at end of file
+ SoongLintFix().run()
diff --git a/tools/lint/framework/Android.bp b/tools/lint/framework/Android.bp
index 30a6daa..5acdf43 100644
--- a/tools/lint/framework/Android.bp
+++ b/tools/lint/framework/Android.bp
@@ -37,28 +37,9 @@
java_test_host {
name: "AndroidFrameworkLintCheckerTest",
+ defaults: ["AndroidLintCheckerTestDefaults"],
srcs: ["checks/src/test/java/**/*.kt"],
static_libs: [
"AndroidFrameworkLintChecker",
- "junit",
- "lint",
- "lint_tests",
],
- test_options: {
- unit_test: true,
- tradefed_options: [
- {
- // lint bundles in some classes that were built with older versions
- // of libraries, and no longer load. Since tradefed tries to load
- // all classes in the jar to look for tests, it crashes loading them.
- // Exclude these classes from tradefed's search.
- name: "exclude-paths",
- value: "org/apache",
- },
- {
- name: "exclude-paths",
- value: "META-INF",
- },
- ],
- },
}
diff --git a/tools/lint/global/Android.bp b/tools/lint/global/Android.bp
index bedb7bd..3e74171 100644
--- a/tools/lint/global/Android.bp
+++ b/tools/lint/global/Android.bp
@@ -38,28 +38,9 @@
java_test_host {
name: "AndroidGlobalLintCheckerTest",
+ defaults: ["AndroidLintCheckerTestDefaults"],
srcs: ["checks/src/test/java/**/*.kt"],
static_libs: [
"AndroidGlobalLintChecker",
- "junit",
- "lint",
- "lint_tests",
],
- test_options: {
- unit_test: true,
- tradefed_options: [
- {
- // lint bundles in some classes that were built with older versions
- // of libraries, and no longer load. Since tradefed tries to load
- // all classes in the jar to look for tests, it crashes loading them.
- // Exclude these classes from tradefed's search.
- name: "exclude-paths",
- value: "org/apache",
- },
- {
- name: "exclude-paths",
- value: "META-INF",
- },
- ],
- },
}
diff --git a/tools/lint/utils/Android.bp b/tools/lint/utils/Android.bp
new file mode 100644
index 0000000..75e8d68
--- /dev/null
+++ b/tools/lint/utils/Android.bp
@@ -0,0 +1,45 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+java_library_host {
+ name: "AndroidUtilsLintChecker",
+ srcs: ["checks/src/main/java/**/*.kt"],
+ plugins: ["auto_service_plugin"],
+ libs: [
+ "auto_service_annotations",
+ "lint_api",
+ ],
+ static_libs: [
+ "AndroidCommonLint",
+ ],
+ kotlincflags: ["-Xjvm-default=all"],
+}
+
+java_test_host {
+ name: "AndroidUtilsLintCheckerTest",
+ defaults: ["AndroidLintCheckerTestDefaults"],
+ srcs: ["checks/src/test/java/**/*.kt"],
+ static_libs: [
+ "AndroidUtilsLintChecker",
+ ],
+}
diff --git a/tools/lint/utils/checks/src/main/java/com/google/android/lint/AndroidUtilsIssueRegistry.kt b/tools/lint/utils/checks/src/main/java/com/google/android/lint/AndroidUtilsIssueRegistry.kt
new file mode 100644
index 0000000..fa61c42
--- /dev/null
+++ b/tools/lint/utils/checks/src/main/java/com/google/android/lint/AndroidUtilsIssueRegistry.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.lint
+
+import com.android.tools.lint.client.api.IssueRegistry
+import com.android.tools.lint.client.api.Vendor
+import com.android.tools.lint.detector.api.CURRENT_API
+import com.google.android.lint.aidl.AnnotatedAidlCounter
+import com.google.auto.service.AutoService
+
+@AutoService(IssueRegistry::class)
+@Suppress("UnstableApiUsage")
+class AndroidUtilsIssueRegistry : IssueRegistry() {
+ override val issues = listOf(
+ AnnotatedAidlCounter.ISSUE_ANNOTATED_AIDL_COUNTER,
+ )
+
+ override val api: Int
+ get() = CURRENT_API
+
+ override val minApi: Int
+ get() = 8
+
+ override val vendor: Vendor = Vendor(
+ vendorName = "Android",
+ feedbackUrl = "http://b/issues/new?component=315013",
+ contact = "tweek@google.com"
+ )
+}
diff --git a/tools/lint/utils/checks/src/main/java/com/google/android/lint/aidl/AnnotatedAidlCounter.kt b/tools/lint/utils/checks/src/main/java/com/google/android/lint/aidl/AnnotatedAidlCounter.kt
new file mode 100644
index 0000000..f0ec3f4
--- /dev/null
+++ b/tools/lint/utils/checks/src/main/java/com/google/android/lint/aidl/AnnotatedAidlCounter.kt
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.lint.aidl
+
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Context
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.Location
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import org.jetbrains.uast.UBlockExpression
+import org.jetbrains.uast.UMethod
+
+import java.util.TreeMap
+
+/**
+ * Count the number of AIDL interfaces. Reports the number of annotated and
+ * non-annotated methods.
+ */
+@Suppress("UnstableApiUsage")
+class AnnotatedAidlCounter : AidlImplementationDetector() {
+
+ private data class Stat(
+ var unannotated: Int = 0,
+ var enforced: Int = 0,
+ var notRequired: Int = 0,
+ )
+
+ private var packagesStats: TreeMap<String, Stat> = TreeMap<String, Stat>()
+
+ override fun visitAidlMethod(
+ context: JavaContext,
+ node: UMethod,
+ interfaceName: String,
+ body: UBlockExpression
+ ) {
+ val packageName = context.uastFile?.packageName ?: "<unknown>"
+ var packageStat = packagesStats.getOrDefault(packageName, Stat())
+ when {
+ node.hasAnnotation(ANNOTATION_ENFORCE_PERMISSION) -> packageStat.enforced += 1
+ node.hasAnnotation(ANNOTATION_REQUIRES_NO_PERMISSION) -> packageStat.notRequired += 1
+ else -> packageStat.unannotated += 1
+ }
+ packagesStats.put(packageName, packageStat)
+ // context.driver.client.log(null, "%s.%s#%s".format(packageName, interfaceName, node.name))
+ }
+
+ override fun afterCheckRootProject(context: Context) {
+ var total = Stat()
+ for ((packageName, stat) in packagesStats) {
+ context.client.log(null, "package $packageName => $stat")
+ total.unannotated += stat.unannotated
+ total.enforced += stat.enforced
+ total.notRequired += stat.notRequired
+ }
+ val location = Location.create(context.project.dir)
+ context.report(
+ ISSUE_ANNOTATED_AIDL_COUNTER,
+ location,
+ "module ${context.project.name} => $total"
+ )
+ }
+
+ companion object {
+
+ @JvmField
+ val ISSUE_ANNOTATED_AIDL_COUNTER = Issue.create(
+ id = "AnnotatedAidlCounter",
+ briefDescription = "Statistics on the number of annotated AIDL methods.",
+ explanation = "",
+ category = Category.SECURITY,
+ priority = 5,
+ severity = Severity.INFORMATIONAL,
+ implementation = Implementation(
+ AnnotatedAidlCounter::class.java,
+ Scope.JAVA_FILE_SCOPE
+ ),
+ )
+ }
+}
diff --git a/tools/lint/utils/checks/src/test/java/com/google/android/lint/aidl/AnnotatedAidlCounterTest.kt b/tools/lint/utils/checks/src/test/java/com/google/android/lint/aidl/AnnotatedAidlCounterTest.kt
new file mode 100644
index 0000000..692b7da
--- /dev/null
+++ b/tools/lint/utils/checks/src/test/java/com/google/android/lint/aidl/AnnotatedAidlCounterTest.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.lint.aidl
+
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest
+import com.android.tools.lint.checks.infrastructure.TestFile
+import com.android.tools.lint.checks.infrastructure.TestLintTask
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+
+@Suppress("UnstableApiUsage")
+class AnnotatedAidlCounterTest : LintDetectorTest() {
+ override fun getDetector(): Detector = AnnotatedAidlCounter()
+
+ override fun getIssues(): List<Issue> = listOf(
+ AnnotatedAidlCounter.ISSUE_ANNOTATED_AIDL_COUNTER,
+ )
+
+ override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)
+
+ /** No issue scenario */
+
+ fun testDoesNotDetectIssuesCorrectAnnotationOnMethod() {
+ lint().files(java(
+ """
+ package test.pkg;
+ import android.annotation.EnforcePermission;
+ public class TestClass2 extends IFooMethod.Stub {
+ @Override
+ @EnforcePermission(android.Manifest.permission.READ_PHONE_STATE)
+ public void testMethod() {}
+ }
+ """).indented(),
+ *stubs
+ )
+ .run()
+ .expect("""
+ app: Information: module app => Stat(unannotated=0, enforced=1, notRequired=0) [AnnotatedAidlCounter]
+ 0 errors, 0 warnings
+ """)
+ }
+
+ // A service with permission annotation on the method.
+ private val interfaceIFooMethodStub: TestFile = java(
+ """
+ public interface IFooMethod extends android.os.IInterface {
+ public static abstract class Stub extends android.os.Binder implements IFooMethod {}
+ @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE)
+ public void testMethod();
+ }
+ """
+ ).indented()
+
+ // A service without any permission annotation.
+ private val interfaceIBarStub: TestFile = java(
+ """
+ public interface IBar extends android.os.IInterface {
+ public static abstract class Stub extends android.os.Binder implements IBar {
+ @Override
+ public void testMethod() {}
+ }
+ public void testMethod();
+ }
+ """
+ ).indented()
+
+ private val manifestPermissionStub: TestFile = java(
+ """
+ package android.Manifest;
+ class permission {
+ public static final String READ_PHONE_STATE = "android.permission.READ_PHONE_STATE";
+ }
+ """
+ ).indented()
+
+ private val enforcePermissionAnnotationStub: TestFile = java(
+ """
+ package android.annotation;
+ public @interface EnforcePermission {}
+ """
+ ).indented()
+
+ private val stubs = arrayOf(interfaceIFooMethodStub, interfaceIBarStub,
+ manifestPermissionStub, enforcePermissionAnnotationStub)
+}