Merge "Add request to authentication entry pending intent"
diff --git a/Android.bp b/Android.bp
index 2a6afe5..cfab18e 100644
--- a/Android.bp
+++ b/Android.bp
@@ -694,3 +694,12 @@
"ProtoLibraries.bp",
"TestProtoLibraries.bp",
]
+
+java_api_contribution {
+ name: "api-stubs-docs-non-updatable-public-stubs",
+ api_surface: "public",
+ api_file: "core/api/current.txt",
+ visibility: [
+ "//build/orchestrator/apis",
+ ],
+}
diff --git a/StubLibraries.bp b/StubLibraries.bp
index 48c44c9..fc046fb 100644
--- a/StubLibraries.bp
+++ b/StubLibraries.bp
@@ -598,3 +598,12 @@
],
visibility: ["//visibility:public"],
}
+
+java_api_contribution {
+ name: "frameworks-base-core-api-module-lib-stubs",
+ api_surface: "module-lib",
+ api_file: "core/api/module-lib-current.txt",
+ visibility: [
+ "//build/orchestrator/apis",
+ ],
+}
diff --git a/apct-tests/perftests/packagemanager/AndroidManifest.xml b/apct-tests/perftests/packagemanager/AndroidManifest.xml
index 3b9431f..b4a34b8 100644
--- a/apct-tests/perftests/packagemanager/AndroidManifest.xml
+++ b/apct-tests/perftests/packagemanager/AndroidManifest.xml
@@ -26,6 +26,7 @@
<permission android:name="com.android.perftests.packagemanager.TestPermission" />
<uses-permission android:name="com.android.perftests.packagemanager.TestPermission" />
+ <uses-permission android:name="android.permission.GET_APP_METADATA" />
<queries>
<package android:name="com.android.perftests.appenumeration0" />
diff --git a/apct-tests/perftests/packagemanager/src/android/os/PackageManagerPerfTest.java b/apct-tests/perftests/packagemanager/src/android/os/PackageManagerPerfTest.java
index 673c6a3..4bcc8c4 100644
--- a/apct-tests/perftests/packagemanager/src/android/os/PackageManagerPerfTest.java
+++ b/apct-tests/perftests/packagemanager/src/android/os/PackageManagerPerfTest.java
@@ -19,10 +19,15 @@
import static libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
import static libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
+import static org.junit.Assert.assertEquals;
+
+import android.Manifest;
import android.compat.testing.PlatformCompatChangeRule;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentSender;
+import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
@@ -31,11 +36,19 @@
import androidx.test.filters.LargeTest;
import androidx.test.platform.app.InstrumentationRegistry;
+import com.android.compatibility.common.util.AdoptShellPermissionsRule;
+import com.android.cts.install.lib.Install;
+import com.android.cts.install.lib.InstallUtils;
+import com.android.cts.install.lib.LocalIntentSender;
+import com.android.cts.install.lib.TestApp;
+
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.io.IOException;
+
@RunWith(AndroidJUnit4.class)
@LargeTest
public class PackageManagerPerfTest {
@@ -46,6 +59,8 @@
private static final ComponentName TEST_ACTIVITY =
new ComponentName("com.android.perftests.packagemanager",
"android.perftests.utils.PerfTestActivity");
+ private static final String TEST_FIELD = "test";
+ private static final String TEST_VALUE = "value";
@Rule
public final PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
@@ -53,9 +68,39 @@
@Rule
public final PlatformCompatChangeRule mPlatformCompatChangeRule =
new PlatformCompatChangeRule();
+ @Rule
+ public AdoptShellPermissionsRule mAdoptShellPermissionsRule = new AdoptShellPermissionsRule(
+ InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+ Manifest.permission.INSTALL_PACKAGES,
+ Manifest.permission.DELETE_PACKAGES);
+
+ final Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
+ private PackageInstaller mPackageInstaller;
public PackageManagerPerfTest() throws PackageManager.NameNotFoundException {
- final Context context = InstrumentationRegistry.getInstrumentation().getContext();
+ mPackageInstaller = mContext.getPackageManager().getPackageInstaller();
+ }
+
+ private void installTestApp(TestApp testApp) throws IOException, InterruptedException {
+ Install install = Install.single(testApp);
+ final int expectedSessionId = install.createSession();
+ PackageInstaller.Session session =
+ InstallUtils.openPackageInstallerSession(expectedSessionId);
+ PersistableBundle bundle = new PersistableBundle();
+ bundle.putString(TEST_FIELD, TEST_VALUE);
+ session.setAppMetadata(bundle);
+ LocalIntentSender localIntentSender = new LocalIntentSender();
+ session.commit(localIntentSender.getIntentSender());
+ Intent intent = localIntentSender.getResult();
+ InstallUtils.assertStatusSuccess(intent);
+ }
+
+ private void uninstallTestApp(String packageName) throws InterruptedException {
+ LocalIntentSender localIntentSender = new LocalIntentSender();
+ IntentSender intentSender = localIntentSender.getIntentSender();
+ mPackageInstaller.uninstall(packageName, intentSender);
+ Intent intent = localIntentSender.getResult();
+ InstallUtils.assertStatusSuccess(intent);
}
@Before
@@ -65,6 +110,24 @@
}
@Test
+ public void testGetAppMetadata() throws Exception {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ installTestApp(TestApp.A1);
+ final PackageManager pm =
+ InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageManager();
+ final String packageName = TestApp.A1.getPackageName();
+
+ while (state.keepRunning()) {
+ PersistableBundle bundle = pm.getAppMetadata(packageName);
+ state.pauseTiming();
+ assertEquals(bundle.size(), 1);
+ assertEquals(bundle.getString(TEST_FIELD), TEST_VALUE);
+ state.resumeTiming();
+ }
+ uninstallTestApp(packageName);
+ }
+
+ @Test
@DisableCompatChanges(PackageManager.FILTER_APPLICATION_QUERY)
public void testCheckPermissionExists() {
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
diff --git a/core/api/current.txt b/core/api/current.txt
index 4f6ccff4..3cc5631 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -5905,7 +5905,7 @@
ctor public LocaleConfig(@NonNull android.content.Context);
ctor public LocaleConfig(@NonNull android.os.LocaleList);
method public int describeContents();
- method @NonNull public static android.app.LocaleConfig fromResources(@NonNull android.content.Context);
+ method @NonNull public static android.app.LocaleConfig fromContextIgnoringOverride(@NonNull android.content.Context);
method public int getStatus();
method @Nullable public android.os.LocaleList getSupportedLocales();
method public void writeToParcel(@NonNull android.os.Parcel, int);
@@ -9231,15 +9231,18 @@
}
public final class CompanionDeviceManager {
+ method @RequiresPermission("android.permission.MANAGE_COMPANION_DEVICES") public void addOnAssociationsChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.companion.CompanionDeviceManager.OnAssociationsChangedListener);
method @RequiresPermission(anyOf={android.Manifest.permission.REQUEST_COMPANION_PROFILE_WATCH, android.Manifest.permission.REQUEST_COMPANION_PROFILE_COMPUTER, android.Manifest.permission.REQUEST_COMPANION_PROFILE_APP_STREAMING, android.Manifest.permission.REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION}, conditional=true) public void associate(@NonNull android.companion.AssociationRequest, @NonNull android.companion.CompanionDeviceManager.Callback, @Nullable android.os.Handler);
method @RequiresPermission(anyOf={android.Manifest.permission.REQUEST_COMPANION_PROFILE_WATCH, android.Manifest.permission.REQUEST_COMPANION_PROFILE_COMPUTER, android.Manifest.permission.REQUEST_COMPANION_PROFILE_APP_STREAMING, android.Manifest.permission.REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION}, conditional=true) public void associate(@NonNull android.companion.AssociationRequest, @NonNull java.util.concurrent.Executor, @NonNull android.companion.CompanionDeviceManager.Callback);
method @Nullable public android.content.IntentSender buildAssociationCancellationIntent();
method @Nullable public android.content.IntentSender buildPermissionTransferUserConsentIntent(int) throws android.companion.DeviceNotAssociatedException;
method @Deprecated public void disassociate(@NonNull String);
method public void disassociate(int);
+ method @NonNull @RequiresPermission("android.permission.MANAGE_COMPANION_DEVICES") public java.util.List<android.companion.AssociationInfo> getAllAssociations();
method @Deprecated @NonNull public java.util.List<java.lang.String> getAssociations();
method @NonNull public java.util.List<android.companion.AssociationInfo> getMyAssociations();
method @Deprecated public boolean hasNotificationAccess(android.content.ComponentName);
+ method @RequiresPermission("android.permission.MANAGE_COMPANION_DEVICES") public void removeOnAssociationsChangedListener(@NonNull android.companion.CompanionDeviceManager.OnAssociationsChangedListener);
method public void requestNotificationAccess(android.content.ComponentName);
method @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE) public void startObservingDevicePresence(@NonNull String) throws android.companion.DeviceNotAssociatedException;
method @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE) public void stopObservingDevicePresence(@NonNull String) throws android.companion.DeviceNotAssociatedException;
@@ -9260,6 +9263,10 @@
method public abstract void onFailure(@Nullable CharSequence);
}
+ public static interface CompanionDeviceManager.OnAssociationsChangedListener {
+ method public void onAssociationsChanged(@NonNull java.util.List<android.companion.AssociationInfo>);
+ }
+
public abstract class CompanionDeviceService extends android.app.Service {
ctor public CompanionDeviceService();
method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent);
@@ -11876,6 +11883,7 @@
public class PackageInstaller {
method public void abandonSession(int);
method public void checkInstallConstraints(@NonNull java.util.List<java.lang.String>, @NonNull android.content.pm.PackageInstaller.InstallConstraints, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.content.pm.PackageInstaller.InstallConstraintsResult>);
+ method public void commitSessionAfterInstallConstraintsAreMet(int, @NonNull android.content.IntentSender, @NonNull android.content.pm.PackageInstaller.InstallConstraints, long);
method public int createSession(@NonNull android.content.pm.PackageInstaller.SessionParams) throws java.io.IOException;
method @Deprecated @Nullable public android.content.pm.PackageInstaller.SessionInfo getActiveStagedSession();
method @NonNull public java.util.List<android.content.pm.PackageInstaller.SessionInfo> getActiveStagedSessions();
@@ -11920,6 +11928,7 @@
field public static final int STATUS_FAILURE_INCOMPATIBLE = 7; // 0x7
field public static final int STATUS_FAILURE_INVALID = 4; // 0x4
field public static final int STATUS_FAILURE_STORAGE = 6; // 0x6
+ field public static final int STATUS_FAILURE_TIMEOUT = 8; // 0x8
field public static final int STATUS_PENDING_USER_ACTION = -1; // 0xffffffff
field public static final int STATUS_SUCCESS = 0; // 0x0
}
@@ -33212,6 +33221,7 @@
field public static final String DISALLOW_CONFIG_CELL_BROADCASTS = "no_config_cell_broadcasts";
field public static final String DISALLOW_CONFIG_CREDENTIALS = "no_config_credentials";
field public static final String DISALLOW_CONFIG_DATE_TIME = "no_config_date_time";
+ field public static final String DISALLOW_CONFIG_DEFAULT_APPS = "disallow_config_default_apps";
field public static final String DISALLOW_CONFIG_LOCALE = "no_config_locale";
field public static final String DISALLOW_CONFIG_LOCATION = "no_config_location";
field public static final String DISALLOW_CONFIG_MOBILE_NETWORKS = "no_config_mobile_networks";
@@ -46635,8 +46645,8 @@
field public static final int DONE = -1; // 0xffffffff
}
- public static class SegmentFinder.DefaultSegmentFinder extends android.text.SegmentFinder {
- ctor public SegmentFinder.DefaultSegmentFinder(@NonNull int[]);
+ public static class SegmentFinder.PrescribedSegmentFinder extends android.text.SegmentFinder {
+ ctor public SegmentFinder.PrescribedSegmentFinder(@NonNull int[]);
method public int nextEndBoundary(@IntRange(from=0) int);
method public int nextStartBoundary(@IntRange(from=0) int);
method public int previousEndBoundary(@IntRange(from=0) int);
@@ -54598,14 +54608,6 @@
public abstract class HandwritingGesture {
method @Nullable public final String getFallbackText();
- field public static final int GESTURE_TYPE_DELETE = 4; // 0x4
- field public static final int GESTURE_TYPE_DELETE_RANGE = 64; // 0x40
- field public static final int GESTURE_TYPE_INSERT = 2; // 0x2
- field public static final int GESTURE_TYPE_JOIN_OR_SPLIT = 16; // 0x10
- field public static final int GESTURE_TYPE_NONE = 0; // 0x0
- field public static final int GESTURE_TYPE_REMOVE_SPACE = 8; // 0x8
- field public static final int GESTURE_TYPE_SELECT = 1; // 0x1
- field public static final int GESTURE_TYPE_SELECT_RANGE = 32; // 0x20
field public static final int GRANULARITY_CHARACTER = 2; // 0x2
field public static final int GRANULARITY_WORD = 1; // 0x1
}
@@ -55105,15 +55107,15 @@
public final class TextBoundsInfo implements android.os.Parcelable {
method public int describeContents();
method @IntRange(from=0, to=125) public int getCharacterBidiLevel(int);
- method @NonNull public android.graphics.RectF getCharacterBounds(int);
+ method @NonNull public void getCharacterBounds(int, @NonNull android.graphics.RectF);
method public int getCharacterFlags(int);
- method public int getEnd();
+ method public int getEndIndex();
method @NonNull public android.text.SegmentFinder getGraphemeSegmentFinder();
method @NonNull public android.text.SegmentFinder getLineSegmentFinder();
- method @NonNull public android.graphics.Matrix getMatrix();
+ method @NonNull public void getMatrix(@NonNull android.graphics.Matrix);
method public int getOffsetForPosition(float, float);
method @Nullable public int[] getRangeForRect(@NonNull android.graphics.RectF, @NonNull android.text.SegmentFinder, @NonNull android.text.Layout.TextInclusionStrategy);
- method public int getStart();
+ method public int getStartIndex();
method @NonNull public android.text.SegmentFinder getWordSegmentFinder();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.view.inputmethod.TextBoundsInfo> CREATOR;
@@ -55124,7 +55126,7 @@
}
public static final class TextBoundsInfo.Builder {
- ctor public TextBoundsInfo.Builder();
+ ctor public TextBoundsInfo.Builder(int, int);
method @NonNull public android.view.inputmethod.TextBoundsInfo build();
method @NonNull public android.view.inputmethod.TextBoundsInfo.Builder clear();
method @NonNull public android.view.inputmethod.TextBoundsInfo.Builder setCharacterBidiLevel(@NonNull int[]);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 3ac8286..85f8813 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -806,10 +806,12 @@
method @Nullable public android.content.IntentFilter getDeliveryGroupMatchingFilter();
method @Nullable public String getDeliveryGroupMatchingKey();
method public int getDeliveryGroupPolicy();
+ method public boolean isDeferUntilActive();
method public boolean isPendingIntentBackgroundActivityLaunchAllowed();
method public static android.app.BroadcastOptions makeBasic();
method @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RESPONSE_STATS) public void recordResponseEventWhileInBackground(@IntRange(from=0) long);
method @RequiresPermission(android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND) public void setBackgroundActivityStartsAllowed(boolean);
+ method @NonNull public android.app.BroadcastOptions setDeferUntilActive(boolean);
method @NonNull public android.app.BroadcastOptions setDeliveryGroupMatchingFilter(@NonNull android.content.IntentFilter);
method @NonNull public android.app.BroadcastOptions setDeliveryGroupMatchingKey(@NonNull String, @NonNull String);
method @NonNull public android.app.BroadcastOptions setDeliveryGroupPolicy(int);
@@ -2973,18 +2975,11 @@
}
public final class CompanionDeviceManager {
- method @RequiresPermission("android.permission.MANAGE_COMPANION_DEVICES") public void addOnAssociationsChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.companion.CompanionDeviceManager.OnAssociationsChangedListener);
method @RequiresPermission(android.Manifest.permission.ASSOCIATE_COMPANION_DEVICES) public void associate(@NonNull String, @NonNull android.net.MacAddress, @NonNull byte[]);
method @RequiresPermission("android.permission.MANAGE_COMPANION_DEVICES") public boolean canPairWithoutPrompt(@NonNull String, @NonNull String, @NonNull android.os.UserHandle);
- method @NonNull @RequiresPermission("android.permission.MANAGE_COMPANION_DEVICES") public java.util.List<android.companion.AssociationInfo> getAllAssociations();
method @RequiresPermission("android.permission.MANAGE_COMPANION_DEVICES") public boolean isDeviceAssociatedForWifiConnection(@NonNull String, @NonNull android.net.MacAddress, @NonNull android.os.UserHandle);
method @RequiresPermission(android.Manifest.permission.REQUEST_COMPANION_SELF_MANAGED) public void notifyDeviceAppeared(int);
method @RequiresPermission(android.Manifest.permission.REQUEST_COMPANION_SELF_MANAGED) public void notifyDeviceDisappeared(int);
- method @RequiresPermission("android.permission.MANAGE_COMPANION_DEVICES") public void removeOnAssociationsChangedListener(@NonNull android.companion.CompanionDeviceManager.OnAssociationsChangedListener);
- }
-
- public static interface CompanionDeviceManager.OnAssociationsChangedListener {
- method public void onAssociationsChanged(@NonNull java.util.List<android.companion.AssociationInfo>);
}
}
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 3bc11fa..d25c3f5 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1246,7 +1246,7 @@
public final class CameraManager {
method public String[] getCameraIdListNoLazy() throws android.hardware.camera2.CameraAccessException;
method @RequiresPermission(allOf={android.Manifest.permission.SYSTEM_CAMERA, android.Manifest.permission.CAMERA}) public void openCamera(@NonNull String, int, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.CameraDevice.StateCallback) throws android.hardware.camera2.CameraAccessException;
- field public static final long OVERRIDE_FRONT_CAMERA_APP_COMPAT = 250678880L; // 0xef10e60L
+ field public static final long OVERRIDE_CAMERA_LANDSCAPE_TO_PORTRAIT = 250678880L; // 0xef10e60L
}
public abstract static class CameraManager.AvailabilityCallback {
@@ -3322,6 +3322,14 @@
public abstract class HandwritingGesture {
method public final int getGestureType();
+ field public static final int GESTURE_TYPE_DELETE = 4; // 0x4
+ field public static final int GESTURE_TYPE_DELETE_RANGE = 64; // 0x40
+ field public static final int GESTURE_TYPE_INSERT = 2; // 0x2
+ field public static final int GESTURE_TYPE_JOIN_OR_SPLIT = 16; // 0x10
+ field public static final int GESTURE_TYPE_NONE = 0; // 0x0
+ field public static final int GESTURE_TYPE_REMOVE_SPACE = 8; // 0x8
+ field public static final int GESTURE_TYPE_SELECT = 1; // 0x1
+ field public static final int GESTURE_TYPE_SELECT_RANGE = 32; // 0x20
}
public final class InlineSuggestion implements android.os.Parcelable {
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 6826b67..3e21124 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -20,6 +20,8 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.PermissionMethod;
+import android.annotation.PermissionName;
import android.annotation.UserIdInt;
import android.app.ActivityManager.ProcessCapability;
import android.app.ActivityManager.RestrictionLevel;
@@ -31,8 +33,6 @@
import android.content.pm.ActivityInfo;
import android.content.pm.ActivityPresentationInfo;
import android.content.pm.ApplicationInfo;
-import android.content.pm.PermissionMethod;
-import android.content.pm.PermissionName;
import android.content.pm.UserInfo;
import android.net.Uri;
import android.os.Bundle;
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 86482c0..3f27964 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -4558,7 +4558,11 @@
service.attach(context, this, data.info.name, data.token, app,
ActivityManager.getService());
if (!service.isUiContext()) { // WindowProviderService is a UI Context.
- service.updateDeviceId(mLastReportedDeviceId);
+ VirtualDeviceManager vdm = context.getSystemService(VirtualDeviceManager.class);
+ if (mLastReportedDeviceId == VirtualDeviceManager.DEVICE_ID_DEFAULT
+ || vdm.isValidVirtualDeviceId(mLastReportedDeviceId)) {
+ service.updateDeviceId(mLastReportedDeviceId);
+ }
}
service.onCreate();
mServicesData.put(data.token, data);
diff --git a/core/java/android/app/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java
index e202760..5cf10d0 100644
--- a/core/java/android/app/BroadcastOptions.java
+++ b/core/java/android/app/BroadcastOptions.java
@@ -63,6 +63,7 @@
private long mRequireCompatChangeId = CHANGE_INVALID;
private boolean mRequireCompatChangeEnabled = true;
private boolean mIsAlarmBroadcast = false;
+ private boolean mIsDeferUntilActive = false;
private long mIdForResponseEvent;
private @Nullable IntentFilter mRemoveMatchingFilter;
private @DeliveryGroupPolicy int mDeliveryGroupPolicy;
@@ -201,6 +202,12 @@
"android:broadcast.removeMatchingFilter";
/**
+ * Corresponds to {@link #setDeferUntilActive(boolean)}.
+ */
+ private static final String KEY_DEFER_UNTIL_ACTIVE =
+ "android:broadcast.deferuntilactive";
+
+ /**
* Corresponds to {@link #setDeliveryGroupPolicy(int)}.
*/
private static final String KEY_DELIVERY_GROUP_POLICY =
@@ -320,6 +327,7 @@
BundleMerger.class);
mDeliveryGroupMatchingFilter = opts.getParcelable(KEY_DELIVERY_GROUP_MATCHING_FILTER,
IntentFilter.class);
+ mIsDeferUntilActive = opts.getBoolean(KEY_DEFER_UNTIL_ACTIVE, false);
}
/**
@@ -700,6 +708,41 @@
}
/**
+ * Sets whether the broadcast should not run until the process is in an active process state
+ * (ie, a process exists for the app and the app is not in a cached process state).
+ *
+ * Whether an app's process state is considered active is independent of its standby bucket.
+ *
+ * A broadcast that is deferred until the process is active will not execute until the process
+ * is brought to an active state by some other action, like a job, alarm, or service binding. As
+ * a result, the broadcast may be delayed indefinitely. This deferral only applies to runtime
+ * registered receivers of a broadcast. Any manifest receivers will run immediately, similar to
+ * how a manifest receiver would start a new process in order to run a broadcast receiver.
+ *
+ * Ordered broadcasts, alarm broadcasts, interactive broadcasts, and manifest broadcasts are
+ * never deferred.
+ *
+ * Unordered broadcasts and unordered broadcasts with completion callbacks may be
+ * deferred. Completion callbacks for broadcasts deferred until active are
+ * best-effort. Completion callbacks will run when all eligible processes have finished
+ * executing the broadcast. Processes in inactive process states that defer the broadcast are
+ * not considered eligible and may not execute the broadcast prior to the completion callback.
+ *
+ * @hide
+ */
+ @SystemApi
+ public @NonNull BroadcastOptions setDeferUntilActive(boolean shouldDefer) {
+ mIsDeferUntilActive = shouldDefer;
+ return this;
+ }
+
+ /** @hide */
+ @SystemApi
+ public boolean isDeferUntilActive() {
+ return mIsDeferUntilActive;
+ }
+
+ /**
* When enqueuing this broadcast, remove all pending broadcasts previously
* sent by this app which match the given filter.
* <p>
@@ -963,6 +1006,9 @@
if (mDeliveryGroupMatchingFilter != null) {
b.putParcelable(KEY_DELIVERY_GROUP_MATCHING_FILTER, mDeliveryGroupMatchingFilter);
}
+ if (mIsDeferUntilActive) {
+ b.putBoolean(KEY_DEFER_UNTIL_ACTIVE, mIsDeferUntilActive);
+ }
return b.isEmpty() ? null : b;
}
}
diff --git a/core/java/android/app/LocaleConfig.java b/core/java/android/app/LocaleConfig.java
index 50ba7db..69693fc 100644
--- a/core/java/android/app/LocaleConfig.java
+++ b/core/java/android/app/LocaleConfig.java
@@ -110,7 +110,7 @@
* @see Context#createPackageContext(String, int).
*/
@NonNull
- public static LocaleConfig fromResources(@NonNull Context context) {
+ public static LocaleConfig fromContextIgnoringOverride(@NonNull Context context) {
return new LocaleConfig(context, false);
}
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index 63f4bcf..d31124d 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -662,9 +662,7 @@
* @return the associations list
* @see #addOnAssociationsChangedListener(Executor, OnAssociationsChangedListener)
* @see #removeOnAssociationsChangedListener(OnAssociationsChangedListener)
- * @hide
*/
- @SystemApi
@UserHandleAware
@RequiresPermission(android.Manifest.permission.MANAGE_COMPANION_DEVICES)
public @NonNull List<AssociationInfo> getAllAssociations() {
@@ -678,10 +676,7 @@
/**
* Listener for any changes to {@link AssociationInfo}.
- *
- * @hide
*/
- @SystemApi
public interface OnAssociationsChangedListener {
/**
* Invoked when a change occurs to any of the associations for the user (including adding
@@ -696,9 +691,7 @@
* Register listener for any changes to {@link AssociationInfo}.
*
* @see #getAllAssociations()
- * @hide
*/
- @SystemApi
@UserHandleAware
@RequiresPermission(android.Manifest.permission.MANAGE_COMPANION_DEVICES)
public void addOnAssociationsChangedListener(
@@ -720,9 +713,7 @@
* Unregister listener for any changes to {@link AssociationInfo}.
*
* @see #getAllAssociations()
- * @hide
*/
- @SystemApi
@UserHandleAware
@RequiresPermission(android.Manifest.permission.MANAGE_COMPANION_DEVICES)
public void removeOnAssociationsChangedListener(
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 879c8bd..74c3f45 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -26,6 +26,8 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.PermissionMethod;
+import android.annotation.PermissionName;
import android.annotation.RequiresPermission;
import android.annotation.StringDef;
import android.annotation.StringRes;
@@ -53,8 +55,6 @@
import android.compat.annotation.UnsupportedAppUsage;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
-import android.content.pm.PermissionMethod;
-import android.content.pm.PermissionName;
import android.content.res.AssetManager;
import android.content.res.ColorStateList;
import android.content.res.Configuration;
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 7ee8f60..4318c39 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -11601,7 +11601,7 @@
private void toUriInner(StringBuilder uri, String scheme, String defAction,
String defPackage, int flags) {
if (scheme != null) {
- uri.append("scheme=").append(scheme).append(';');
+ uri.append("scheme=").append(Uri.encode(scheme)).append(';');
}
if (mAction != null && !mAction.equals(defAction)) {
uri.append("action=").append(Uri.encode(mAction)).append(';');
diff --git a/core/java/android/content/pm/IPackageInstallerSession.aidl b/core/java/android/content/pm/IPackageInstallerSession.aidl
index c9a5632..9dd9c0f 100644
--- a/core/java/android/content/pm/IPackageInstallerSession.aidl
+++ b/core/java/android/content/pm/IPackageInstallerSession.aidl
@@ -46,6 +46,8 @@
void commit(in IntentSender statusReceiver, boolean forTransferred);
void transfer(in String packageName);
void abandon();
+ void seal();
+ List<String> fetchPackageNames();
DataLoaderParamsParcel getDataLoaderParams();
void addFile(int location, String name, long lengthBytes, in byte[] metadata, in byte[] signature);
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 812b5b3..df1340d 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -44,8 +44,11 @@
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.app.ActivityManager;
+import android.app.ActivityThread;
import android.app.AppGlobals;
import android.compat.annotation.UnsupportedAppUsage;
+import android.content.IIntentReceiver;
+import android.content.IIntentSender;
import android.content.Intent;
import android.content.IntentSender;
import android.content.pm.PackageManager.DeleteFlags;
@@ -59,9 +62,11 @@
import android.icu.util.ULocale;
import android.net.Uri;
import android.os.Build;
+import android.os.Bundle;
import android.os.FileBridge;
import android.os.Handler;
import android.os.HandlerExecutor;
+import android.os.IBinder;
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import android.os.Parcelable;
@@ -226,8 +231,8 @@
* {@link #STATUS_PENDING_USER_ACTION}, {@link #STATUS_SUCCESS},
* {@link #STATUS_FAILURE}, {@link #STATUS_FAILURE_ABORTED},
* {@link #STATUS_FAILURE_BLOCKED}, {@link #STATUS_FAILURE_CONFLICT},
- * {@link #STATUS_FAILURE_INCOMPATIBLE}, {@link #STATUS_FAILURE_INVALID}, or
- * {@link #STATUS_FAILURE_STORAGE}.
+ * {@link #STATUS_FAILURE_INCOMPATIBLE}, {@link #STATUS_FAILURE_INVALID},
+ * {@link #STATUS_FAILURE_STORAGE}, or {@link #STATUS_FAILURE_TIMEOUT}.
* <p>
* More information about a status may be available through additional
* extras; see the individual status documentation for details.
@@ -441,6 +446,13 @@
public static final int STATUS_FAILURE_INCOMPATIBLE = 7;
/**
+ * The operation failed because it didn't complete within the specified timeout.
+ *
+ * @see #EXTRA_STATUS_MESSAGE
+ */
+ public static final int STATUS_FAILURE_TIMEOUT = 8;
+
+ /**
* Default value, non-streaming installation session.
*
* @see #EXTRA_DATA_LOADER_TYPE
@@ -1016,6 +1028,61 @@
}
/**
+ * Commit the session when all constraints are satisfied. This is a convenient method to
+ * combine {@link #waitForInstallConstraints(List, InstallConstraints, IntentSender, long)}
+ * and {@link Session#commit(IntentSender)}.
+ * <p>
+ * Once this method is called, the session is sealed and no additional mutations
+ * may be performed on the session. In the case of timeout, you may commit the
+ * session again using this method or {@link Session#commit(IntentSender)} for retries.
+ *
+ * @param statusReceiver Called when the state of the session changes. Intents
+ * sent to this receiver contain {@link #EXTRA_STATUS}.
+ * Refer to the individual status codes on how to handle them.
+ * @param constraints The requirements to satisfy before committing the session.
+ * @param timeoutMillis The maximum time to wait, in milliseconds until the
+ * constraints are satisfied. The caller will be notified via
+ * {@code statusReceiver} if timeout happens before commit.
+ */
+ public void commitSessionAfterInstallConstraintsAreMet(int sessionId,
+ @NonNull IntentSender statusReceiver, @NonNull InstallConstraints constraints,
+ @DurationMillisLong long timeoutMillis) {
+ try {
+ var session = mInstaller.openSession(sessionId);
+ session.seal();
+ var packageNames = session.fetchPackageNames();
+ var intentSender = new IntentSender((IIntentSender) new IIntentSender.Stub() {
+ @Override
+ public void send(int code, Intent intent, String resolvedType,
+ IBinder allowlistToken, IIntentReceiver finishedReceiver,
+ String requiredPermission, Bundle options) {
+ var result = intent.getParcelableExtra(
+ PackageInstaller.EXTRA_INSTALL_CONSTRAINTS_RESULT,
+ InstallConstraintsResult.class);
+ try {
+ if (result.isAllConstraintsSatisfied()) {
+ session.commit(statusReceiver, false);
+ } else {
+ // timeout
+ final Intent fillIn = new Intent();
+ fillIn.putExtra(PackageInstaller.EXTRA_SESSION_ID, sessionId);
+ fillIn.putExtra(PackageInstaller.EXTRA_STATUS, STATUS_FAILURE_TIMEOUT);
+ fillIn.putExtra(PackageInstaller.EXTRA_STATUS_MESSAGE,
+ "Install constraints not satisfied within timeout");
+ statusReceiver.sendIntent(
+ ActivityThread.currentApplication(), 0, fillIn, null, null);
+ }
+ } catch (Exception ignore) {
+ }
+ }
+ });
+ waitForInstallConstraints(packageNames, constraints, intentSender, timeoutMillis);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Events for observing session lifecycle.
* <p>
* A typical session lifecycle looks like this:
diff --git a/core/java/android/content/pm/PermissionMethod.java b/core/java/android/content/pm/PermissionMethod.java
deleted file mode 100644
index 647c696..0000000
--- a/core/java/android/content/pm/PermissionMethod.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.content.pm;
-
-import static java.lang.annotation.ElementType.METHOD;
-import static java.lang.annotation.RetentionPolicy.CLASS;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.Target;
-
-/**
- * Documents that the subject method's job is to look
- * up whether the provided or calling uid/pid has the requested permission.
- *
- * <p>Methods should either return `void`, but potentially throw {@link SecurityException},
- * or return {@link android.content.pm.PackageManager.PermissionResult} `int`.
- *
- * @hide
- */
-@Retention(CLASS)
-@Target({METHOD})
-public @interface PermissionMethod {
- /**
- * Hard-coded list of permissions checked by this method
- */
- @PermissionName String[] value() default {};
- /**
- * If true, the check passes if the caller
- * has any ONE of the supplied permissions
- */
- boolean anyOf() default false;
- /**
- * Signifies that the permission check passes if
- * the calling process OR the current process has
- * the permission
- */
- boolean orSelf() default false;
-}
diff --git a/core/java/android/content/pm/PermissionName.java b/core/java/android/content/pm/PermissionName.java
deleted file mode 100644
index 719e13b..0000000
--- a/core/java/android/content/pm/PermissionName.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.content.pm;
-
-import static java.lang.annotation.ElementType.FIELD;
-import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
-import static java.lang.annotation.ElementType.METHOD;
-import static java.lang.annotation.ElementType.PARAMETER;
-import static java.lang.annotation.RetentionPolicy.CLASS;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.Target;
-
-/**
- * Denotes that the annotated {@link String} represents a permission name.
- *
- * @hide
- */
-@Retention(CLASS)
-@Target({PARAMETER, METHOD, LOCAL_VARIABLE, FIELD})
-public @interface PermissionName {}
diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java
index c716f319..81d6ba9 100644
--- a/core/java/android/hardware/Camera.java
+++ b/core/java/android/hardware/Camera.java
@@ -24,7 +24,6 @@
import android.annotation.SdkConstant.SdkConstantType;
import android.app.ActivityThread;
import android.app.AppOpsManager;
-import android.app.compat.CompatChanges;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.graphics.ImageFormat;
@@ -42,7 +41,6 @@
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
-import android.os.SystemProperties;
import android.renderscript.Allocation;
import android.renderscript.Element;
import android.renderscript.RSIllegalArgumentException;
@@ -279,14 +277,6 @@
*/
public native static int getNumberOfCameras();
- private static final boolean sLandscapeToPortrait =
- SystemProperties.getBoolean(CameraManager.LANDSCAPE_TO_PORTRAIT_PROP, false);
-
- private static boolean shouldOverrideToPortrait() {
- return CompatChanges.isChangeEnabled(CameraManager.OVERRIDE_FRONT_CAMERA_APP_COMPAT)
- && sLandscapeToPortrait;
- }
-
/**
* Returns the information about a particular camera.
* If {@link #getNumberOfCameras()} returns N, the valid id is 0 to N-1.
@@ -296,7 +286,8 @@
* low-level failure).
*/
public static void getCameraInfo(int cameraId, CameraInfo cameraInfo) {
- boolean overrideToPortrait = shouldOverrideToPortrait();
+ boolean overrideToPortrait = CameraManager.shouldOverrideToPortrait(
+ ActivityThread.currentApplication().getApplicationContext());
_getCameraInfo(cameraId, overrideToPortrait, cameraInfo);
IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
@@ -493,7 +484,8 @@
mEventHandler = null;
}
- boolean overrideToPortrait = shouldOverrideToPortrait();
+ boolean overrideToPortrait = CameraManager.shouldOverrideToPortrait(
+ ActivityThread.currentApplication().getApplicationContext());
return native_setup(new WeakReference<Camera>(this), cameraId,
ActivityThread.currentOpPackageName(), overrideToPortrait);
}
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index 5b6e288..b5a58b2 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -115,7 +115,14 @@
@Overridable
@EnabledSince(targetSdkVersion = android.os.Build.VERSION_CODES.BASE)
@TestApi
- public static final long OVERRIDE_FRONT_CAMERA_APP_COMPAT = 250678880L;
+ public static final long OVERRIDE_CAMERA_LANDSCAPE_TO_PORTRAIT = 250678880L;
+
+ /**
+ * Package-level opt in/out for the above.
+ * @hide
+ */
+ public static final String PROPERTY_COMPAT_OVERRIDE_LANDSCAPE_TO_PORTRAIT =
+ "android.camera.PROPERTY_COMPAT_OVERRIDE_LANDSCAPE_TO_PORTRAIT";
/**
* System property for allowing the above
@@ -607,7 +614,7 @@
try {
Size displaySize = getDisplaySize();
- boolean overrideToPortrait = shouldOverrideToPortrait();
+ boolean overrideToPortrait = shouldOverrideToPortrait(mContext);
CameraMetadataNative info = cameraService.getCameraCharacteristics(cameraId,
mContext.getApplicationInfo().targetSdkVersion, overrideToPortrait);
try {
@@ -727,7 +734,7 @@
"Camera service is currently unavailable");
}
- boolean overrideToPortrait = shouldOverrideToPortrait();
+ boolean overrideToPortrait = shouldOverrideToPortrait(mContext);
cameraUser = cameraService.connectDevice(callbacks, cameraId,
mContext.getOpPackageName(), mContext.getAttributionTag(), uid,
oomScoreOffset, mContext.getApplicationInfo().targetSdkVersion,
@@ -1159,9 +1166,26 @@
return CameraManagerGlobal.get().getTorchStrengthLevel(cameraId);
}
- private static boolean shouldOverrideToPortrait() {
- return CompatChanges.isChangeEnabled(OVERRIDE_FRONT_CAMERA_APP_COMPAT)
- && CameraManagerGlobal.sLandscapeToPortrait;
+ /**
+ * @hide
+ */
+ public static boolean shouldOverrideToPortrait(@Nullable Context context) {
+ if (!CameraManagerGlobal.sLandscapeToPortrait) {
+ return false;
+ }
+
+ if (context != null) {
+ PackageManager packageManager = context.getPackageManager();
+
+ try {
+ return packageManager.getProperty(context.getOpPackageName(),
+ PROPERTY_COMPAT_OVERRIDE_LANDSCAPE_TO_PORTRAIT).getBoolean();
+ } catch (PackageManager.NameNotFoundException e) {
+ // No such property
+ }
+ }
+
+ return CompatChanges.isChangeEnabled(OVERRIDE_CAMERA_LANDSCAPE_TO_PORTRAIT);
}
/**
diff --git a/core/java/android/hardware/devicestate/DeviceStateManager.java b/core/java/android/hardware/devicestate/DeviceStateManager.java
index dba1a5e..6a667fe 100644
--- a/core/java/android/hardware/devicestate/DeviceStateManager.java
+++ b/core/java/android/hardware/devicestate/DeviceStateManager.java
@@ -251,6 +251,10 @@
@Nullable
private Boolean lastResult;
+ public FoldStateListener(Context context) {
+ this(context, folded -> {});
+ }
+
public FoldStateListener(Context context, Consumer<Boolean> listener) {
mFoldedDeviceStates = context.getResources().getIntArray(
com.android.internal.R.array.config_foldedDeviceStates);
@@ -266,5 +270,10 @@
mDelegate.accept(folded);
}
}
+
+ @Nullable
+ public Boolean getFolded() {
+ return lastResult;
+ }
}
}
diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl
index 2bf187a..5fcc31e 100644
--- a/core/java/android/hardware/face/IFaceService.aidl
+++ b/core/java/android/hardware/face/IFaceService.aidl
@@ -43,7 +43,7 @@
byte[] dumpSensorServiceStateProto(int sensorId, boolean clearSchedulerBuffer);
// Retrieve static sensor properties for all face sensors
- @EnforcePermission("MANAGE_BIOMETRIC")
+ @EnforcePermission("USE_BIOMETRIC_INTERNAL")
List<FaceSensorPropertiesInternal> getSensorPropertiesInternal(String opPackageName);
// Retrieve static sensor properties for the specified sensor
diff --git a/core/java/android/inputmethodservice/IRemoteInputConnectionInvoker.java b/core/java/android/inputmethodservice/IRemoteInputConnectionInvoker.java
index 8759a6a..8e6f6dc 100644
--- a/core/java/android/inputmethodservice/IRemoteInputConnectionInvoker.java
+++ b/core/java/android/inputmethodservice/IRemoteInputConnectionInvoker.java
@@ -767,20 +767,20 @@
/**
* Invokes {@link IRemoteInputConnection#requestTextBoundsInfo(InputConnectionCommandHeader,
* RectF, ResultReceiver)}
- * @param rectF {@code rectF} parameter to be passed.
+ * @param bounds {@code rectF} parameter to be passed.
* @param executor {@code Executor} parameter to be passed.
* @param consumer {@code Consumer} parameter to be passed.
*/
@AnyThread
public void requestTextBoundsInfo(
- @NonNull RectF rectF, @NonNull @CallbackExecutor Executor executor,
+ @NonNull RectF bounds, @NonNull @CallbackExecutor Executor executor,
@NonNull Consumer<TextBoundsInfoResult> consumer) {
Objects.requireNonNull(executor);
Objects.requireNonNull(consumer);
final ResultReceiver resultReceiver = new TextBoundsInfoResultReceiver(executor, consumer);
try {
- mConnection.requestTextBoundsInfo(createHeader(), rectF, resultReceiver);
+ mConnection.requestTextBoundsInfo(createHeader(), bounds, resultReceiver);
} catch (RemoteException e) {
executor.execute(() -> consumer.accept(new TextBoundsInfoResult(CODE_CANCELLED)));
}
diff --git a/core/java/android/inputmethodservice/RemoteInputConnection.java b/core/java/android/inputmethodservice/RemoteInputConnection.java
index f93f9ab..ec26ace 100644
--- a/core/java/android/inputmethodservice/RemoteInputConnection.java
+++ b/core/java/android/inputmethodservice/RemoteInputConnection.java
@@ -476,9 +476,9 @@
@AnyThread
public void requestTextBoundsInfo(
- @NonNull RectF rectF, @NonNull @CallbackExecutor Executor executor,
+ @NonNull RectF bounds, @NonNull @CallbackExecutor Executor executor,
@NonNull Consumer<TextBoundsInfoResult> consumer) {
- mInvoker.requestTextBoundsInfo(rectF, executor, consumer);
+ mInvoker.requestTextBoundsInfo(bounds, executor, consumer);
}
@AnyThread
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index def0cbd..00676f3 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -358,16 +358,16 @@
* Return the Linux UID assigned to the process that sent the transaction
* currently being processed.
*
- * Logs WTF if the current thread is not currently
+ * Slog.wtf if the current thread is not currently
* executing an incoming transaction and the calling identity has not been
* explicitly set with {@link #clearCallingIdentity()}
*
* @hide
*/
- public static final int getCallingUidOrWtf() {
+ public static final int getCallingUidOrWtf(String message) {
if (!isDirectlyHandlingTransaction() && !hasExplicitIdentity()) {
- Log.wtfStack(TAG,
- "Thread is not in a binder transaction, "
+ Slog.wtf(TAG,
+ message + ": Thread is not in a binder transaction, "
+ "and the calling identity has not been "
+ "explicitly set with clearCallingIdentity");
}
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 0e4c2b2..b016c781 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -1472,6 +1472,21 @@
public static final String DISALLOW_BIOMETRIC = "disallow_biometric";
/**
+ * Specifies whether the user is allowed to modify default apps in settings.
+ *
+ * <p>This restriction can be set by device or profile owner.
+ *
+ * <p>The default value is <code>false</code>.
+ *
+ * <p>Key for user restrictions.
+ * <p>Type: Boolean
+ * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+ * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+ * @see #getUserRestrictions()
+ */
+ public static final String DISALLOW_CONFIG_DEFAULT_APPS = "disallow_config_default_apps";
+
+ /**
* Application restriction key that is used to indicate the pending arrival
* of real restrictions for the app.
*
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 190b738..ec3ef9d 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -22,6 +22,8 @@
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.PermissionMethod;
+import android.annotation.PermissionName;
import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
@@ -48,7 +50,6 @@
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
-import android.content.pm.PermissionName;
import android.content.pm.ResolveInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
@@ -9986,11 +9987,10 @@
"fingerprint_side_fps_auth_downtime";
/**
- * Whether or not a SFPS device is required to be interactive for auth to unlock the device.
+ * Whether or not a SFPS device is enabling the performant auth setting.
* @hide
*/
- public static final String SFPS_REQUIRE_SCREEN_ON_TO_AUTH_ENABLED =
- "sfps_require_screen_on_to_auth_enabled";
+ public static final String SFPS_PERFORMANT_AUTH_ENABLED = "sfps_performant_auth_enabled";
/**
* Whether or not debugging is enabled.
@@ -18503,6 +18503,8 @@
* @see #checkCallingPermission
* @hide
*/
+ @PermissionMethod(orSelf = true)
+ @PackageManager.PermissionResult
public static int checkCallingOrSelfPermission(@NonNull @PermissionName String permission) {
return ActivityThread.currentApplication()
.getApplicationContext().checkCallingOrSelfPermission(permission);
diff --git a/core/java/android/security/rkp/IRegistration.aidl b/core/java/android/security/rkp/IRegistration.aidl
index 6522a45..8ec13b9 100644
--- a/core/java/android/security/rkp/IRegistration.aidl
+++ b/core/java/android/security/rkp/IRegistration.aidl
@@ -17,6 +17,7 @@
package android.security.rkp;
import android.security.rkp.IGetKeyCallback;
+import android.security.rkp.IStoreUpgradedKeyCallback;
/**
* This interface is associated with the registration of an
@@ -70,16 +71,18 @@
* mechanism, see the documentation for IKeyMintDevice.upgradeKey.
*
* Once a key has been upgraded, the IRegistration where the key is stored
- * needs to be told about the new blob. After calling storeUpgradedKey,
+ * needs to be told about the new blob. After calling storeUpgradedKeyAsync,
* getKey will return the new key blob instead of the old one.
*
* Note that this function does NOT extend the lifetime of key blobs. The
* certificate for the key is unchanged, and the key will still expire at
- * the same time it would have if storeUpgradedKey had never been called.
+ * the same time it would have if storeUpgradedKeyAsync had never been called.
*
* @param oldKeyBlob The old key blob to be replaced by {@code newKeyBlob}.
- *
* @param newKeyblob The new blob to replace {@code oldKeyBlob}.
+ * @param callback Receives the result of the call. A callback must only
+ * be used with one {@code storeUpgradedKeyAsync} call at a time.
*/
- void storeUpgradedKey(in byte[] oldKeyBlob, in byte[] newKeyBlob);
+ void storeUpgradedKeyAsync(
+ in byte[] oldKeyBlob, in byte[] newKeyBlob, IStoreUpgradedKeyCallback callback);
}
diff --git a/core/java/android/security/rkp/IStoreUpgradedKeyCallback.aidl b/core/java/android/security/rkp/IStoreUpgradedKeyCallback.aidl
new file mode 100644
index 0000000..7f72fa0
--- /dev/null
+++ b/core/java/android/security/rkp/IStoreUpgradedKeyCallback.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.rkp;
+
+/**
+ * Callback interface for storing an upgraded remotely provisioned key blob.
+ * {@link IRegistration}.
+ *
+ * @hide
+ */
+oneway interface IStoreUpgradedKeyCallback {
+ /**
+ * Called in response to {@link IRegistration.storeUpgradedKeyAsync}, indicating
+ * a remotely-provisioned key is available.
+ */
+ void onSuccess();
+
+ /**
+ * Called when an error has occurred while trying to store an upgraded
+ * remotely provisioned key.
+ *
+ * @param error A description of what failed, suitable for logging.
+ */
+ void onError(String error);
+}
diff --git a/core/java/android/text/SegmentFinder.java b/core/java/android/text/SegmentFinder.java
index be0094b..047d07a 100644
--- a/core/java/android/text/SegmentFinder.java
+++ b/core/java/android/text/SegmentFinder.java
@@ -74,7 +74,7 @@
/**
* The default {@link SegmentFinder} implementation based on given segment ranges.
*/
- public static class DefaultSegmentFinder extends SegmentFinder {
+ public static class PrescribedSegmentFinder extends SegmentFinder {
private final int[] mSegments;
/**
@@ -87,7 +87,7 @@
* @throws IllegalArgumentException if the given segments array's length is not even; the
* given segments are not sorted or there are segments overlap with others.
*/
- public DefaultSegmentFinder(@NonNull int[] segments) {
+ public PrescribedSegmentFinder(@NonNull int[] segments) {
checkSegmentsValid(segments);
mSegments = segments;
}
diff --git a/core/java/android/view/inputmethod/HandwritingGesture.java b/core/java/android/view/inputmethod/HandwritingGesture.java
index 2516269..e0fc426 100644
--- a/core/java/android/view/inputmethod/HandwritingGesture.java
+++ b/core/java/android/view/inputmethod/HandwritingGesture.java
@@ -82,38 +82,60 @@
@IntDef({GRANULARITY_CHARACTER, GRANULARITY_WORD})
@interface Granularity {}
- /** Undefined gesture type. */
+ /**
+ * Undefined gesture type.
+ * @hide
+ */
+ @TestApi
public static final int GESTURE_TYPE_NONE = 0x0000;
/**
* Gesture of type {@link SelectGesture} to select an area of text.
+ * @hide
*/
+ @TestApi
public static final int GESTURE_TYPE_SELECT = 0x0001;
/**
* Gesture of type {@link InsertGesture} to insert text at a designated point.
+ * @hide
*/
+ @TestApi
public static final int GESTURE_TYPE_INSERT = 1 << 1;
/**
* Gesture of type {@link DeleteGesture} to delete an area of text.
+ * @hide
*/
+ @TestApi
public static final int GESTURE_TYPE_DELETE = 1 << 2;
- /** Gesture of type {@link RemoveSpaceGesture} to remove whitespace from text. */
+ /**
+ * Gesture of type {@link RemoveSpaceGesture} to remove whitespace from text.
+ * @hide
+ */
+ @TestApi
public static final int GESTURE_TYPE_REMOVE_SPACE = 1 << 3;
- /** Gesture of type {@link JoinOrSplitGesture} to join or split text. */
+ /**
+ * Gesture of type {@link JoinOrSplitGesture} to join or split text.
+ * @hide
+ */
+ @TestApi
public static final int GESTURE_TYPE_JOIN_OR_SPLIT = 1 << 4;
/**
* Gesture of type {@link SelectRangeGesture} to select range of text.
+ * @hide
*/
+ @TestApi
public static final int GESTURE_TYPE_SELECT_RANGE = 1 << 5;
/**
* Gesture of type {@link DeleteRangeGesture} to delete range of text.
+ * @hide
*/
+ @TestApi
public static final int GESTURE_TYPE_DELETE_RANGE = 1 << 6;
/**
diff --git a/core/java/android/view/inputmethod/InputConnection.java b/core/java/android/view/inputmethod/InputConnection.java
index 9b519c3..6872536 100644
--- a/core/java/android/view/inputmethod/InputConnection.java
+++ b/core/java/android/view/inputmethod/InputConnection.java
@@ -1262,13 +1262,13 @@
/**
* Called by input method to request the {@link TextBoundsInfo} for a range of text which is
- * covered by or in vicinity of the given {@code RectF}. It can be used as a supplementary
+ * covered by or in vicinity of the given {@code bounds}. It can be used as a supplementary
* method to implement the handwriting gesture API -
* {@link #performHandwritingGesture(HandwritingGesture, Executor, IntConsumer)}.
*
* <p><strong>Editor authors</strong>: It's preferred that the editor returns a
* {@link TextBoundsInfo} of all the text lines whose bounds intersect with the given
- * {@code rectF}.
+ * {@code bounds}.
* </p>
*
* <p><strong>IME authors</strong>: This method is expensive when the text is long. Please
@@ -1276,7 +1276,7 @@
* consuming. It's preferable to only request text bounds in smaller areas.
* </p>
*
- * @param rectF the interested area where the text bounds are requested, in the screen
+ * @param bounds the interested area where the text bounds are requested, in the screen
* coordinates.
* @param executor the executor to run the callback.
* @param consumer the callback invoked by editor to return the result. It must return a
@@ -1286,7 +1286,7 @@
* @see android.view.inputmethod.TextBoundsInfoResult
*/
default void requestTextBoundsInfo(
- @NonNull RectF rectF, @NonNull @CallbackExecutor Executor executor,
+ @NonNull RectF bounds, @NonNull @CallbackExecutor Executor executor,
@NonNull Consumer<TextBoundsInfoResult> consumer) {
Objects.requireNonNull(executor);
Objects.requireNonNull(consumer);
diff --git a/core/java/android/view/inputmethod/InputConnectionWrapper.java b/core/java/android/view/inputmethod/InputConnectionWrapper.java
index 4befd6f..5e323fa 100644
--- a/core/java/android/view/inputmethod/InputConnectionWrapper.java
+++ b/core/java/android/view/inputmethod/InputConnectionWrapper.java
@@ -362,9 +362,9 @@
*/
@Override
public void requestTextBoundsInfo(
- @NonNull RectF rectF, @NonNull @CallbackExecutor Executor executor,
+ @NonNull RectF bounds, @NonNull @CallbackExecutor Executor executor,
@NonNull Consumer<TextBoundsInfoResult> consumer) {
- mTarget.requestTextBoundsInfo(rectF, executor, consumer);
+ mTarget.requestTextBoundsInfo(bounds, executor, consumer);
}
/**
diff --git a/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java b/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java
index 7525d72..6f8b422 100644
--- a/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java
+++ b/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java
@@ -1106,7 +1106,7 @@
@Dispatching(cancellable = true)
@Override
public void requestTextBoundsInfo(
- InputConnectionCommandHeader header, RectF rectF,
+ InputConnectionCommandHeader header, RectF bounds,
@NonNull ResultReceiver resultReceiver) {
dispatchWithTracing("requestTextBoundsInfo", () -> {
if (header.mSessionId != mCurrentSessionId.get()) {
@@ -1121,7 +1121,7 @@
}
ic.requestTextBoundsInfo(
- rectF,
+ bounds,
Runnable::run,
(textBoundsInfoResult) -> {
final int resultCode = textBoundsInfoResult.getResultCode();
diff --git a/core/java/android/view/inputmethod/TextBoundsInfo.java b/core/java/android/view/inputmethod/TextBoundsInfo.java
index dd05543..d42d94e 100644
--- a/core/java/android/view/inputmethod/TextBoundsInfo.java
+++ b/core/java/android/view/inputmethod/TextBoundsInfo.java
@@ -43,8 +43,8 @@
* The text bounds information of a slice of text in the editor.
*
* <p> This class provides IME the layout information of the text within the range from
- * {@link #getStart()} to {@link #getEnd()}. It's intended to be used by IME as a supplementary API
- * to support handwriting gestures.
+ * {@link #getStartIndex()} to {@link #getEndIndex()}. It's intended to be used by IME as a
+ * supplementary API to support handwriting gestures.
* </p>
*/
public final class TextBoundsInfo implements Parcelable {
@@ -168,16 +168,13 @@
private final SegmentFinder mGraphemeSegmentFinder;
/**
- * Returns a new instance of {@link android.graphics.Matrix} that indicates the transformation
+ * Set the given {@link android.graphics.Matrix} to be the transformation
* matrix that is to be applied other positional data in this class.
- *
- * @return a new instance (copy) of the transformation matrix.
*/
@NonNull
- public Matrix getMatrix() {
- final Matrix matrix = new Matrix();
+ public void getMatrix(@NonNull Matrix matrix) {
+ Objects.requireNonNull(matrix);
matrix.setValues(mMatrixValues);
- return matrix;
}
/**
@@ -186,7 +183,7 @@
*
* @see Builder#setStartAndEnd(int, int)
*/
- public int getStart() {
+ public int getStartIndex() {
return mStart;
}
@@ -196,28 +193,28 @@
*
* @see Builder#setStartAndEnd(int, int)
*/
- public int getEnd() {
+ public int getEndIndex() {
return mEnd;
}
/**
- * Return the bounds of the character at the given {@code index}, in the coordinates of the
- * editor.
+ * Set the bounds of the character at the given {@code index} to the given {@link RectF}, in
+ * the coordinates of the editor.
*
* @param index the index of the queried character.
- * @return the bounding box of the queried character.
+ * @param bounds the {@link RectF} used to receive the result.
*
* @throws IndexOutOfBoundsException if the given {@code index} is out of the range from
* the {@code start} to the {@code end}.
*/
@NonNull
- public RectF getCharacterBounds(int index) {
+ public void getCharacterBounds(int index, @NonNull RectF bounds) {
if (index < mStart || index >= mEnd) {
throw new IndexOutOfBoundsException("Index is out of the bounds of "
+ "[" + mStart + ", " + mEnd + ").");
}
final int offset = 4 * (index - mStart);
- return new RectF(mCharacterBounds[offset], mCharacterBounds[offset + 1],
+ bounds.set(mCharacterBounds[offset], mCharacterBounds[offset + 1],
mCharacterBounds[offset + 2], mCharacterBounds[offset + 3]);
}
@@ -333,6 +330,16 @@
* won't check the text in the ranges of [5, 7) and [12, 15).
* </p>
*
+ * <p> Under the following conditions, this method will return -1 indicating that no valid
+ * character is found:
+ * <ul>
+ * <li> The given {@code y} coordinate is above the first line or below the last line (the
+ * first line or the last line is identified by the {@link SegmentFinder} returned from
+ * {@link #getLineSegmentFinder()}). </li>
+ * <li> There is no character in this {@link TextBoundsInfo}. </li>
+ * </ul>
+ * </p>
+ *
* @param x the x coordinates of the interested location, in the editor's coordinates.
* @param y the y coordinates of the interested location, in the editor's coordinates.
* @return the index of the character whose position is closest to the given location. It will
@@ -990,8 +997,8 @@
public static final class Builder {
private final float[] mMatrixValues = new float[9];
private boolean mMatrixInitialized;
- private int mStart;
- private int mEnd;
+ private int mStart = -1;
+ private int mEnd = -1;
private float[] mCharacterBounds;
private int[] mCharacterFlags;
private int[] mCharacterBidiLevels;
@@ -999,6 +1006,17 @@
private SegmentFinder mWordSegmentFinder;
private SegmentFinder mGraphemeSegmentFinder;
+ /**
+ * Create a builder for {@link TextBoundsInfo}.
+ * @param start the start index of the {@link TextBoundsInfo}, inclusive.
+ * @param end the end index of the {@link TextBoundsInfo}, exclusive.
+ * @throws IllegalArgumentException if the given {@code start} or {@code end} is negative,
+ * or {@code end} is smaller than the {@code start}.
+ */
+ public Builder(int start, int end) {
+ setStartAndEnd(start, end);
+ }
+
/** Clear all the parameters set on this {@link Builder} to reuse it. */
@NonNull
public Builder clear() {
@@ -1152,7 +1170,7 @@
*
* @see #getGraphemeSegmentFinder()
* @see SegmentFinder
- * @see SegmentFinder.DefaultSegmentFinder
+ * @see SegmentFinder.PrescribedSegmentFinder
*/
@NonNull
public Builder setGraphemeSegmentFinder(@NonNull SegmentFinder graphemeSegmentFinder) {
@@ -1171,7 +1189,7 @@
*
* @see #getWordSegmentFinder()
* @see SegmentFinder
- * @see SegmentFinder.DefaultSegmentFinder
+ * @see SegmentFinder.PrescribedSegmentFinder
*/
@NonNull
public Builder setWordSegmentFinder(@NonNull SegmentFinder wordSegmentFinder) {
@@ -1193,7 +1211,7 @@
*
* @see #getLineSegmentFinder()
* @see SegmentFinder
- * @see SegmentFinder.DefaultSegmentFinder
+ * @see SegmentFinder.PrescribedSegmentFinder
*/
@NonNull
public Builder setLineSegmentFinder(@NonNull SegmentFinder lineSegmentFinder) {
@@ -1360,7 +1378,7 @@
breaks = GrowingArrayUtils.append(breaks, count++, start + offset);
}
}
- return new SegmentFinder.DefaultSegmentFinder(Arrays.copyOf(breaks, count));
+ return new SegmentFinder.PrescribedSegmentFinder(Arrays.copyOf(breaks, count));
}
/**
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index b9b928e..4005bc8 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -13245,7 +13245,7 @@
* Creates the {@link TextBoundsInfo} for the text lines that intersects with the {@code rectF}.
* @hide
*/
- public TextBoundsInfo getTextBoundsInfo(@NonNull RectF rectF) {
+ public TextBoundsInfo getTextBoundsInfo(@NonNull RectF bounds) {
final Layout layout = getLayout();
if (layout == null) {
// No valid text layout, return null.
@@ -13268,19 +13268,18 @@
final float layoutLeft = viewportToContentHorizontalOffset();
final float layoutTop = viewportToContentVerticalOffset();
- final RectF localRectF = new RectF(rectF);
- globalToLocalMatrix.mapRect(localRectF);
- localRectF.offset(-layoutLeft, -layoutTop);
+ final RectF localBounds = new RectF(bounds);
+ globalToLocalMatrix.mapRect(localBounds);
+ localBounds.offset(-layoutLeft, -layoutTop);
// Text length is 0. There is no character bounds, return empty TextBoundsInfo.
// rectF doesn't intersect with the layout, return empty TextBoundsInfo.
- if (!localRectF.intersects(0f, 0f, layout.getWidth(), layout.getHeight())
+ if (!localBounds.intersects(0f, 0f, layout.getWidth(), layout.getHeight())
|| text.length() == 0) {
- final TextBoundsInfo.Builder builder = new TextBoundsInfo.Builder();
+ final TextBoundsInfo.Builder builder = new TextBoundsInfo.Builder(0, 0);
final SegmentFinder emptySegmentFinder =
- new SegmentFinder.DefaultSegmentFinder(new int[0]);
- builder.setStartAndEnd(0, 0)
- .setMatrix(localToGlobalMatrix)
+ new SegmentFinder.PrescribedSegmentFinder(new int[0]);
+ builder.setMatrix(localToGlobalMatrix)
.setCharacterBounds(new float[0])
.setCharacterBidiLevel(new int[0])
.setCharacterFlags(new int[0])
@@ -13290,8 +13289,8 @@
return builder.build();
}
- final int startLine = layout.getLineForVertical((int) Math.floor(localRectF.top));
- final int endLine = layout.getLineForVertical((int) Math.floor(localRectF.bottom));
+ final int startLine = layout.getLineForVertical((int) Math.floor(localBounds.top));
+ final int endLine = layout.getLineForVertical((int) Math.floor(localBounds.bottom));
final int start = layout.getLineStart(startLine);
final int end = layout.getLineEnd(endLine);
@@ -13349,18 +13348,18 @@
lineRanges[2 * offset] = layout.getLineStart(line);
lineRanges[2 * offset + 1] = layout.getLineEnd(line);
}
- final SegmentFinder lineSegmentFinder = new SegmentFinder.DefaultSegmentFinder(lineRanges);
+ final SegmentFinder lineSegmentFinder =
+ new SegmentFinder.PrescribedSegmentFinder(lineRanges);
- final TextBoundsInfo.Builder builder = new TextBoundsInfo.Builder();
- builder.setStartAndEnd(start, end)
+ return new TextBoundsInfo.Builder(start, end)
.setMatrix(localToGlobalMatrix)
.setCharacterBounds(characterBounds)
.setCharacterBidiLevel(characterBidiLevels)
.setCharacterFlags(characterFlags)
.setGraphemeSegmentFinder(graphemeSegmentFinder)
.setLineSegmentFinder(lineSegmentFinder)
- .setWordSegmentFinder(wordSegmentFinder);
- return builder.build();
+ .setWordSegmentFinder(wordSegmentFinder)
+ .build();
}
/**
diff --git a/core/java/com/android/internal/inputmethod/EditableInputConnection.java b/core/java/com/android/internal/inputmethod/EditableInputConnection.java
index 52e7471..8690e8d 100644
--- a/core/java/com/android/internal/inputmethod/EditableInputConnection.java
+++ b/core/java/com/android/internal/inputmethod/EditableInputConnection.java
@@ -271,9 +271,9 @@
@Override
public void requestTextBoundsInfo(
- @NonNull RectF rectF, @Nullable @CallbackExecutor Executor executor,
+ @NonNull RectF bounds, @Nullable @CallbackExecutor Executor executor,
@NonNull Consumer<TextBoundsInfoResult> consumer) {
- final TextBoundsInfo textBoundsInfo = mTextView.getTextBoundsInfo(rectF);
+ final TextBoundsInfo textBoundsInfo = mTextView.getTextBoundsInfo(bounds);
final int resultCode;
if (textBoundsInfo != null) {
resultCode = TextBoundsInfoResult.CODE_SUCCESS;
diff --git a/core/java/com/android/internal/inputmethod/IRemoteInputConnection.aidl b/core/java/com/android/internal/inputmethod/IRemoteInputConnection.aidl
index 65016c2..b375936 100644
--- a/core/java/com/android/internal/inputmethod/IRemoteInputConnection.aidl
+++ b/core/java/com/android/internal/inputmethod/IRemoteInputConnection.aidl
@@ -111,7 +111,7 @@
int cursorUpdateMode, int cursorUpdateFilter, int imeDisplayId,
in AndroidFuture future /* T=Boolean */);
- void requestTextBoundsInfo(in InputConnectionCommandHeader header, in RectF rect,
+ void requestTextBoundsInfo(in InputConnectionCommandHeader header, in RectF bounds,
in ResultReceiver resultReceiver /* T=TextBoundsInfoResult */);
void commitContent(in InputConnectionCommandHeader header, in InputContentInfo inputContentInfo,
diff --git a/core/java/com/android/internal/os/BatteryStatsHistory.java b/core/java/com/android/internal/os/BatteryStatsHistory.java
index 3d8982b..04b7239cb 100644
--- a/core/java/com/android/internal/os/BatteryStatsHistory.java
+++ b/core/java/com/android/internal/os/BatteryStatsHistory.java
@@ -187,8 +187,6 @@
private int mNextHistoryTagIdx = 0;
private int mNumHistoryTagChars = 0;
private int mHistoryBufferLastPos = -1;
- private int mActiveHistoryStates = 0xffffffff;
- private int mActiveHistoryStates2 = 0xffffffff;
private long mLastHistoryElapsedRealtimeMs = 0;
private long mTrackRunningHistoryElapsedRealtimeMs = 0;
private long mTrackRunningHistoryUptimeMs = 0;
@@ -362,8 +360,6 @@
mNextHistoryTagIdx = 0;
mNumHistoryTagChars = 0;
mHistoryBufferLastPos = -1;
- mActiveHistoryStates = 0xffffffff;
- mActiveHistoryStates2 = 0xffffffff;
if (mStepDetailsCalculator != null) {
mStepDetailsCalculator.clear();
}
@@ -1098,8 +1094,9 @@
*/
public void recordScreenBrightnessEvent(long elapsedRealtimeMs, long uptimeMs,
int brightnessBin) {
- mHistoryCur.states = (mHistoryCur.states & ~HistoryItem.STATE_BRIGHTNESS_MASK)
- | (brightnessBin << HistoryItem.STATE_BRIGHTNESS_SHIFT);
+ mHistoryCur.states = setBitField(mHistoryCur.states, brightnessBin,
+ HistoryItem.STATE_BRIGHTNESS_SHIFT,
+ HistoryItem.STATE_BRIGHTNESS_MASK);
writeHistoryItem(elapsedRealtimeMs, uptimeMs);
}
@@ -1108,8 +1105,9 @@
*/
public void recordGpsSignalQualityEvent(long elapsedRealtimeMs, long uptimeMs,
int signalLevel) {
- mHistoryCur.states2 = (mHistoryCur.states2 & ~HistoryItem.STATE2_GPS_SIGNAL_QUALITY_MASK)
- | (signalLevel << HistoryItem.STATE2_GPS_SIGNAL_QUALITY_SHIFT);
+ mHistoryCur.states2 = setBitField(mHistoryCur.states2, signalLevel,
+ HistoryItem.STATE2_GPS_SIGNAL_QUALITY_SHIFT,
+ HistoryItem.STATE2_GPS_SIGNAL_QUALITY_MASK);
writeHistoryItem(elapsedRealtimeMs, uptimeMs);
}
@@ -1117,8 +1115,9 @@
* Records a device idle mode change event.
*/
public void recordDeviceIdleEvent(long elapsedRealtimeMs, long uptimeMs, int mode) {
- mHistoryCur.states2 = (mHistoryCur.states2 & ~HistoryItem.STATE2_DEVICE_IDLE_MASK)
- | (mode << HistoryItem.STATE2_DEVICE_IDLE_SHIFT);
+ mHistoryCur.states2 = setBitField(mHistoryCur.states2, mode,
+ HistoryItem.STATE2_DEVICE_IDLE_SHIFT,
+ HistoryItem.STATE2_DEVICE_IDLE_MASK);
writeHistoryItem(elapsedRealtimeMs, uptimeMs);
}
@@ -1130,13 +1129,15 @@
mHistoryCur.states = (mHistoryCur.states | addStateFlag) & ~removeStateFlag;
if (state != -1) {
mHistoryCur.states =
- (mHistoryCur.states & ~HistoryItem.STATE_PHONE_STATE_MASK)
- | (state << HistoryItem.STATE_PHONE_STATE_SHIFT);
+ setBitField(mHistoryCur.states, state,
+ HistoryItem.STATE_PHONE_STATE_SHIFT,
+ HistoryItem.STATE_PHONE_STATE_MASK);
}
if (signalStrength != -1) {
mHistoryCur.states =
- (mHistoryCur.states & ~HistoryItem.STATE_PHONE_SIGNAL_STRENGTH_MASK)
- | (signalStrength << HistoryItem.STATE_PHONE_SIGNAL_STRENGTH_SHIFT);
+ setBitField(mHistoryCur.states, signalStrength,
+ HistoryItem.STATE_PHONE_SIGNAL_STRENGTH_SHIFT,
+ HistoryItem.STATE_PHONE_SIGNAL_STRENGTH_MASK);
}
writeHistoryItem(elapsedRealtimeMs, uptimeMs);
}
@@ -1146,8 +1147,9 @@
*/
public void recordDataConnectionTypeChangeEvent(long elapsedRealtimeMs, long uptimeMs,
int dataConnectionType) {
- mHistoryCur.states = (mHistoryCur.states & ~HistoryItem.STATE_DATA_CONNECTION_MASK)
- | (dataConnectionType << HistoryItem.STATE_DATA_CONNECTION_SHIFT);
+ mHistoryCur.states = setBitField(mHistoryCur.states, dataConnectionType,
+ HistoryItem.STATE_DATA_CONNECTION_SHIFT,
+ HistoryItem.STATE_DATA_CONNECTION_MASK);
writeHistoryItem(elapsedRealtimeMs, uptimeMs);
}
@@ -1157,8 +1159,9 @@
public void recordWifiSupplicantStateChangeEvent(long elapsedRealtimeMs, long uptimeMs,
int supplState) {
mHistoryCur.states2 =
- (mHistoryCur.states2 & ~HistoryItem.STATE2_WIFI_SUPPL_STATE_MASK)
- | (supplState << HistoryItem.STATE2_WIFI_SUPPL_STATE_SHIFT);
+ setBitField(mHistoryCur.states2, supplState,
+ HistoryItem.STATE2_WIFI_SUPPL_STATE_SHIFT,
+ HistoryItem.STATE2_WIFI_SUPPL_STATE_MASK);
writeHistoryItem(elapsedRealtimeMs, uptimeMs);
}
@@ -1168,8 +1171,9 @@
public void recordWifiSignalStrengthChangeEvent(long elapsedRealtimeMs, long uptimeMs,
int strengthBin) {
mHistoryCur.states2 =
- (mHistoryCur.states2 & ~HistoryItem.STATE2_WIFI_SIGNAL_STRENGTH_MASK)
- | (strengthBin << HistoryItem.STATE2_WIFI_SIGNAL_STRENGTH_SHIFT);
+ setBitField(mHistoryCur.states2, strengthBin,
+ HistoryItem.STATE2_WIFI_SIGNAL_STRENGTH_SHIFT,
+ HistoryItem.STATE2_WIFI_SIGNAL_STRENGTH_MASK);
writeHistoryItem(elapsedRealtimeMs, uptimeMs);
}
@@ -1227,6 +1231,16 @@
}
}
+ private int setBitField(int bits, int value, int shift, int mask) {
+ int shiftedValue = value << shift;
+ if ((shiftedValue & ~mask) != 0) {
+ Slog.wtfStack(TAG, "Value " + Integer.toHexString(value)
+ + " does not fit in the bit field: " + Integer.toHexString(mask));
+ shiftedValue &= mask;
+ }
+ return (bits & ~mask) | shiftedValue;
+ }
+
/**
* Writes the current history item to history.
*/
@@ -1260,8 +1274,8 @@
}
final long timeDiffMs = (mHistoryBaseTimeMs + elapsedRealtimeMs) - mHistoryLastWritten.time;
- final int diffStates = mHistoryLastWritten.states ^ (cur.states & mActiveHistoryStates);
- final int diffStates2 = mHistoryLastWritten.states2 ^ (cur.states2 & mActiveHistoryStates2);
+ final int diffStates = mHistoryLastWritten.states ^ cur.states;
+ final int diffStates2 = mHistoryLastWritten.states2 ^ cur.states2;
final int lastDiffStates = mHistoryLastWritten.states ^ mHistoryLastLastWritten.states;
final int lastDiffStates2 = mHistoryLastWritten.states2 ^ mHistoryLastLastWritten.states2;
if (DEBUG) {
@@ -1274,9 +1288,9 @@
recordTraceEvents(cur.eventCode, cur.eventTag);
recordTraceCounters(mHistoryLastWritten.states,
- cur.states & mActiveHistoryStates, BatteryStats.HISTORY_STATE_DESCRIPTIONS);
+ cur.states, BatteryStats.HISTORY_STATE_DESCRIPTIONS);
recordTraceCounters(mHistoryLastWritten.states2,
- cur.states2 & mActiveHistoryStates2, BatteryStats.HISTORY_STATE2_DESCRIPTIONS);
+ cur.states2, BatteryStats.HISTORY_STATE2_DESCRIPTIONS);
if (mHistoryBufferLastPos >= 0 && mHistoryLastWritten.cmd == HistoryItem.CMD_UPDATE
&& timeDiffMs < 1000 && (diffStates & lastDiffStates) == 0
@@ -1387,8 +1401,6 @@
final boolean hasTags = mHistoryLastWritten.tagsFirstOccurrence || cur.tagsFirstOccurrence;
mHistoryLastWritten.setTo(mHistoryBaseTimeMs + elapsedRealtimeMs, cmd, cur);
mHistoryLastWritten.tagsFirstOccurrence = hasTags;
- mHistoryLastWritten.states &= mActiveHistoryStates;
- mHistoryLastWritten.states2 &= mActiveHistoryStates2;
writeHistoryDelta(mHistoryBuffer, mHistoryLastWritten, mHistoryLastLastWritten);
mLastHistoryElapsedRealtimeMs = elapsedRealtimeMs;
cur.wakelockTag = null;
@@ -1627,7 +1639,7 @@
}
if (cur.eventCode != HistoryItem.EVENT_NONE) {
final int index = writeHistoryTag(cur.eventTag);
- final int codeAndIndex = (cur.eventCode & 0xffff) | (index << 16);
+ final int codeAndIndex = setBitField(cur.eventCode & 0xffff, index, 16, 0xFFFF0000);
dest.writeInt(codeAndIndex);
if ((index & BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG) != 0) {
cur.eventTag.writeToParcel(dest, 0);
@@ -1689,9 +1701,11 @@
}
private int buildBatteryLevelInt(HistoryItem h) {
- return ((((int) h.batteryLevel) << 25) & 0xfe000000)
- | ((((int) h.batteryTemperature) << 15) & 0x01ff8000)
- | ((((int) h.batteryVoltage) << 1) & 0x00007ffe);
+ int bits = 0;
+ bits = setBitField(bits, h.batteryLevel, 25, 0xfe000000 /* 7F << 25 */);
+ bits = setBitField(bits, h.batteryTemperature, 15, 0x01ff8000 /* 3FF << 15 */);
+ bits = setBitField(bits, h.batteryVoltage, 1, 0x00007ffe /* 3FFF << 1 */);
+ return bits;
}
private int buildStateInt(HistoryItem h) {
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 82db716..cce1b2b 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
@@ -21,10 +21,15 @@
import android.hardware.broadcastradio.ProgramInfo;
import android.hardware.broadcastradio.ProgramListChunk;
import android.hardware.broadcastradio.VendorKeyValue;
+import android.hardware.radio.ProgramList;
import android.hardware.radio.ProgramSelector;
import android.hardware.radio.RadioManager;
import android.hardware.radio.RadioMetadata;
+import android.os.RemoteException;
import android.util.ArrayMap;
+import android.util.ArraySet;
+
+import java.util.List;
final class AidlTestUtils {
@@ -94,6 +99,14 @@
return makeHalProgramInfo(hwSel, hwSel.primaryId, hwSel.primaryId, hwSignalQuality);
}
+ static ProgramInfo programInfoToHalProgramInfo(RadioManager.ProgramInfo info) {
+ return makeHalProgramInfo(
+ ConversionUtils.programSelectorToHalProgramSelector(info.getSelector()),
+ ConversionUtils.identifierToHalProgramIdentifier(info.getLogicallyTunedTo()),
+ ConversionUtils.identifierToHalProgramIdentifier(info.getPhysicallyTunedTo()),
+ info.getSignalStrength());
+ }
+
static ProgramInfo makeHalProgramInfo(
android.hardware.broadcastradio.ProgramSelector hwSel,
ProgramIdentifier logicallyTunedTo, ProgramIdentifier physicallyTunedTo,
@@ -108,7 +121,23 @@
return hwInfo;
}
- static ProgramListChunk makeProgramListChunk(boolean purge, boolean complete,
+ static ProgramListChunk makeHalChunk(boolean purge, boolean complete,
+ List<RadioManager.ProgramInfo> modified, List<ProgramSelector.Identifier> removed) {
+ ProgramInfo[] halModified =
+ new android.hardware.broadcastradio.ProgramInfo[modified.size()];
+ for (int i = 0; i < modified.size(); i++) {
+ halModified[i] = programInfoToHalProgramInfo(modified.get(i));
+ }
+
+ ProgramIdentifier[] halRemoved =
+ new android.hardware.broadcastradio.ProgramIdentifier[removed.size()];
+ for (int i = 0; i < removed.size(); i++) {
+ halRemoved[i] = ConversionUtils.identifierToHalProgramIdentifier(removed.get(i));
+ }
+ return makeHalChunk(purge, complete, halModified, halRemoved);
+ }
+
+ static ProgramListChunk makeHalChunk(boolean purge, boolean complete,
ProgramInfo[] modified, ProgramIdentifier[] removed) {
ProgramListChunk halChunk = new ProgramListChunk();
halChunk.purge = purge;
@@ -118,6 +147,21 @@
return halChunk;
}
+ static ProgramList.Chunk makeChunk(boolean purge, boolean complete,
+ List<RadioManager.ProgramInfo> modified,
+ List<ProgramSelector.Identifier> removed) throws RemoteException {
+ ArraySet<RadioManager.ProgramInfo> modifiedSet = new ArraySet<>();
+ if (modified != null) {
+ modifiedSet.addAll(modified);
+ }
+ ArraySet<ProgramSelector.Identifier> removedSet = new ArraySet<>();
+ if (removed != null) {
+ removedSet.addAll(removed);
+ }
+ ProgramList.Chunk chunk = new ProgramList.Chunk(purge, complete, modifiedSet, removedSet);
+ return chunk;
+ }
+
static VendorKeyValue makeVendorKeyValue(String vendorKey, String vendorValue) {
VendorKeyValue vendorKeyValue = new VendorKeyValue();
vendorKeyValue.key = vendorKey;
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 710c150..5d0e076 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
@@ -328,7 +328,7 @@
TEST_HAL_DAB_SID_EXT_ID, TEST_HAL_DAB_FREQUENCY_ID, TEST_SIGNAL_QUALITY);
RadioManager.ProgramInfo dabInfo =
ConversionUtils.programInfoFromHalProgramInfo(halDabInfo);
- ProgramListChunk halChunk = AidlTestUtils.makeProgramListChunk(purge, complete,
+ ProgramListChunk halChunk = AidlTestUtils.makeHalChunk(purge, complete,
new ProgramInfo[]{halDabInfo},
new ProgramIdentifier[]{TEST_HAL_VENDOR_ID, TEST_HAL_FM_FREQUENCY_ID});
@@ -353,7 +353,7 @@
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.makeProgramListChunk(purge, complete,
+ ProgramListChunk halChunk = AidlTestUtils.makeHalChunk(purge, complete,
new ProgramInfo[]{halDabInfo}, new ProgramIdentifier[]{TEST_HAL_FM_FREQUENCY_ID});
ProgramList.Chunk chunk = ConversionUtils.chunkFromHalProgramListChunk(halChunk);
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
new file mode 100644
index 0000000..d54397e
--- /dev/null
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ProgramInfoCacheTest.java
@@ -0,0 +1,409 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.broadcastradio.aidl;
+
+import android.hardware.broadcastradio.ProgramIdentifier;
+import android.hardware.broadcastradio.ProgramInfo;
+import android.hardware.broadcastradio.ProgramListChunk;
+import android.hardware.radio.ProgramList;
+import android.hardware.radio.ProgramSelector;
+import android.hardware.radio.RadioManager;
+import android.os.RemoteException;
+import android.util.ArraySet;
+
+import com.google.common.truth.Expect;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Unit tests for AIDL ProgramInfoCache
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class ProgramInfoCacheTest {
+
+ private static final int TEST_SIGNAL_QUALITY = 90;
+
+ private static final ProgramSelector.Identifier TEST_FM_FREQUENCY_ID =
+ new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY,
+ /* value= */ 88_500);
+ 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,
+ TEST_SIGNAL_QUALITY);
+ private static final RadioManager.ProgramInfo TEST_FM_INFO_MODIFIED =
+ AidlTestUtils.makeProgramInfo(AidlTestUtils.makeProgramSelector(
+ ProgramSelector.PROGRAM_TYPE_FM, TEST_FM_FREQUENCY_ID), TEST_FM_FREQUENCY_ID,
+ TEST_FM_FREQUENCY_ID, /* signalQuality= */ 99);
+
+ private static final ProgramSelector.Identifier TEST_AM_FREQUENCY_ID =
+ new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY,
+ /* value= */ 1_700);
+ 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,
+ TEST_SIGNAL_QUALITY);
+
+ 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_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),
+ TEST_SIGNAL_QUALITY);
+
+ private static final ProgramSelector.Identifier TEST_DAB_DMB_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.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_VENDOR_ID =
+ new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_VENDOR_START,
+ /* value= */ 9_001);
+ private static final RadioManager.ProgramInfo TEST_VENDOR_INFO = AidlTestUtils.makeProgramInfo(
+ AidlTestUtils.makeProgramSelector(ProgramSelector.PROGRAM_TYPE_VENDOR_START,
+ 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);
+
+ @Rule
+ public final Expect expect = Expect.create();
+
+ @Test
+ public void isComplete_forCompleteProgramInfoCache_returnsTrue() {
+ expect.withMessage("Complete program info cache")
+ .that(FULL_PROGRAM_INFO_CACHE.isComplete()).isTrue();
+ }
+
+ @Test
+ public void isComplete_forIncompleteProgramInfoCache_returnsFalse() {
+ ProgramInfoCache programInfoCache = new ProgramInfoCache(/* filter= */ null,
+ /* complete= */ false);
+ expect.withMessage("Incomplete program info cache")
+ .that(programInfoCache.isComplete()).isFalse();
+ }
+
+ @Test
+ public void getFilter_forProgramInfoCache() {
+ ProgramList.Filter fmFilter = new ProgramList.Filter(
+ Set.of(ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY), new ArraySet<>(),
+ /* includeCategories= */ true, /* excludeModifications= */ false);
+ ProgramInfoCache fmProgramInfoCache = new ProgramInfoCache(fmFilter);
+
+ expect.withMessage("Program info cache filter")
+ .that(fmProgramInfoCache.getFilter()).isEqualTo(fmFilter);
+ }
+
+ @Test
+ public void updateFromHalProgramListChunk_withPurgingCompleteChunk() {
+ ProgramInfoCache cache = new ProgramInfoCache(/* filter= */ null,
+ /* complete= */ false, TEST_FM_INFO);
+ ProgramListChunk chunk = AidlTestUtils.makeHalChunk(/* purge= */ true, /* complete= */ true,
+ new ProgramInfo[]{AidlTestUtils.programInfoToHalProgramInfo(TEST_RDS_INFO),
+ AidlTestUtils.programInfoToHalProgramInfo(TEST_VENDOR_INFO)},
+ new ProgramIdentifier[]{});
+
+ cache.updateFromHalProgramListChunk(chunk);
+
+ expect.withMessage("Program cache updated with purge-enabled and complete chunk")
+ .that(cache.toProgramInfoList())
+ .containsExactly(TEST_RDS_INFO, TEST_VENDOR_INFO);
+ expect.withMessage("Complete program cache").that(cache.isComplete()).isTrue();
+ }
+
+ @Test
+ public void updateFromHalProgramListChunk_withNonPurgingIncompleteChunk() {
+ ProgramInfoCache cache = new ProgramInfoCache(/* filter= */ null,
+ /* complete= */ false, TEST_FM_INFO, TEST_RDS_INFO, TEST_AM_INFO);
+ ProgramListChunk chunk = AidlTestUtils.makeHalChunk(/* purge= */ false,
+ /* complete= */ false,
+ new ProgramInfo[]{AidlTestUtils.programInfoToHalProgramInfo(TEST_FM_INFO_MODIFIED),
+ AidlTestUtils.programInfoToHalProgramInfo(TEST_VENDOR_INFO)},
+ new ProgramIdentifier[]{ConversionUtils.identifierToHalProgramIdentifier(
+ TEST_RDS_PI_ID)});
+
+ cache.updateFromHalProgramListChunk(chunk);
+
+ expect.withMessage("Program cache updated with non-purging and incomplete chunk")
+ .that(cache.toProgramInfoList())
+ .containsExactly(TEST_FM_INFO_MODIFIED, TEST_VENDOR_INFO, TEST_AM_INFO);
+ expect.withMessage("Incomplete program cache").that(cache.isComplete()).isFalse();
+ }
+
+ @Test
+ public void filterAndUpdateFromInternal_withNullFilter() {
+ ProgramInfoCache cache = new ProgramInfoCache(/* filter= */ null,
+ /* complete= */ true);
+
+ cache.filterAndUpdateFromInternal(FULL_PROGRAM_INFO_CACHE, /* purge= */ false);
+
+ 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
+ public void filterAndUpdateFromInternal_withEmptyFilter() {
+ ProgramInfoCache cache = new ProgramInfoCache(new ProgramList.Filter(new ArraySet<>(),
+ new ArraySet<>(),
+ /* includeCategories= */ true, /* excludeModifications= */ false));
+
+ cache.filterAndUpdateFromInternal(FULL_PROGRAM_INFO_CACHE, /* purge= */ false);
+
+ 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
+ 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<>(),
+ /* 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);
+ }
+
+ @Test
+ public void filterAndUpdateFromInternal_withFilterByIdentifier() {
+ 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);
+
+ expect.withMessage("Program cache filtered by identifier")
+ .that(cache.toProgramInfoList()).containsExactly(TEST_FM_INFO, TEST_DAB_INFO);
+ verifyChunkListPurge(programListChunks, /* purge= */ true);
+ verifyChunkListComplete(programListChunks, FULL_PROGRAM_INFO_CACHE.isComplete());
+ verifyChunkListModified(programListChunks, maxNumModifiedPerChunk, TEST_FM_INFO,
+ TEST_DAB_INFO);
+ verifyChunkListRemoved(programListChunks, maxNumRemovedPerChunk);
+ }
+
+ @Test
+ public void filterAndUpdateFromInternal_withFilterExcludingCategories() {
+ ProgramInfoCache cache = new ProgramInfoCache(new ProgramList.Filter(new ArraySet<>(),
+ 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);
+
+ expect.withMessage("Program cache filtered by excluding categories")
+ .that(cache.toProgramInfoList())
+ .containsExactly(TEST_FM_INFO, TEST_AM_INFO, TEST_RDS_INFO, TEST_DAB_INFO);
+ verifyChunkListPurge(programListChunks, /* purge= */ true);
+ verifyChunkListComplete(programListChunks, FULL_PROGRAM_INFO_CACHE.isComplete());
+ verifyChunkListModified(programListChunks, maxNumModifiedPerChunk, TEST_FM_INFO,
+ TEST_AM_INFO, TEST_RDS_INFO, TEST_DAB_INFO);
+ verifyChunkListRemoved(programListChunks, maxNumRemovedPerChunk);
+ }
+
+ @Test
+ public void filterAndUpdateFromInternal_withFilterExcludingModifications() {
+ ProgramList.Filter filterExcludingModifications = new ProgramList.Filter(new ArraySet<>(),
+ new ArraySet<>(), /* includeCategories= */ true,
+ /* excludeModifications= */ true);
+ 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;
+
+ List<ProgramList.Chunk> programListChunks = cache.filterAndUpdateFromInternal(halCache,
+ /* purge= */ false, maxNumModifiedPerChunk, maxNumRemovedPerChunk);
+
+ expect.withMessage("Program cache filtered by excluding modifications")
+ .that(cache.toProgramInfoList())
+ .containsExactly(TEST_FM_INFO, 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);
+ }
+
+ @Test
+ public void filterAndUpdateFromInternal_withPurge() {
+ ProgramInfoCache cache = new ProgramInfoCache(new ProgramList.Filter(new ArraySet<>(),
+ new ArraySet<>(), /* includeCategories= */ true,
+ /* excludeModifications= */ false),
+ /* complete= */ true, TEST_FM_INFO, TEST_RDS_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;
+
+ List<ProgramList.Chunk> programListChunks = cache.filterAndUpdateFromInternal(halCache,
+ /* purge= */ true, maxNumModifiedPerChunk, maxNumRemovedPerChunk);
+
+ expect.withMessage("Purged program cache").that(cache.toProgramInfoList())
+ .containsExactly(TEST_FM_INFO_MODIFIED, TEST_DAB_INFO, 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);
+ }
+
+ @Test
+ public void filterAndApplyChunkInternal_withPurgingIncompleteChunk() 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;
+
+ List<ProgramList.Chunk> programListChunks = cache.filterAndApplyChunkInternal(chunk,
+ maxNumModifiedPerChunk, maxNumRemovedPerChunk);
+
+ 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);
+ verifyChunkListPurge(programListChunks, /* purge= */ true);
+ verifyChunkListComplete(programListChunks, /* complete= */ false);
+ verifyChunkListModified(programListChunks, maxNumModifiedPerChunk, TEST_FM_INFO_MODIFIED,
+ TEST_RDS_INFO, TEST_VENDOR_INFO);
+ verifyChunkListRemoved(programListChunks, maxNumRemovedPerChunk);
+ }
+
+ @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),
+ 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);
+
+ expect.withMessage("Program cache applied with purge-enabled 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);
+ }
+
+ private void verifyChunkListPurge(List<ProgramList.Chunk> chunks, boolean purge) {
+ if (chunks.isEmpty()) {
+ return;
+ }
+ for (int i = 0; i < chunks.size(); i++) {
+ ProgramList.Chunk chunk = chunks.get(i);
+ boolean expectedPurge = (i == 0 && purge);
+
+ expect.withMessage("Purge for chunk %s", i)
+ .that(chunk.isPurge()).isEqualTo(expectedPurge);
+ }
+ }
+
+ private void verifyChunkListComplete(List<ProgramList.Chunk> chunks, boolean complete) {
+ if (chunks.isEmpty()) {
+ return;
+ }
+ for (int i = 0; i < chunks.size(); i++) {
+ ProgramList.Chunk chunk = chunks.get(i);
+ boolean expectedComplete = (i == chunks.size() - 1 && complete);
+
+ expect.withMessage("Purge for chunk %s", i)
+ .that(chunk.isComplete()).isEqualTo(expectedComplete);
+ }
+ }
+
+ private void verifyChunkListModified(List<ProgramList.Chunk> chunks,
+ int maxModifiedPerChunk, RadioManager.ProgramInfo... expectedProgramInfos) {
+ if (chunks.isEmpty()) {
+ expect.withMessage("Empty program info list")
+ .that(expectedProgramInfos.length).isEqualTo(0);
+ return;
+ }
+
+ ArraySet<RadioManager.ProgramInfo> actualSet = new ArraySet<>();
+ for (int i = 0; i < chunks.size(); i++) {
+ Set<RadioManager.ProgramInfo> chunkModified = chunks.get(i).getModified();
+ actualSet.addAll(chunkModified);
+
+ expect.withMessage("Chunk %s modified program info array size", i)
+ .that(chunkModified.size()).isAtMost(maxModifiedPerChunk);
+ }
+ expect.withMessage("Program info items")
+ .that(actualSet).containsExactlyElementsIn(expectedProgramInfos);
+ }
+
+ private void verifyChunkListRemoved(List<ProgramList.Chunk> chunks,
+ int maxRemovedPerChunk, ProgramSelector.Identifier... expectedIdentifiers) {
+ if (chunks.isEmpty()) {
+ expect.withMessage("Empty program info list")
+ .that(expectedIdentifiers.length).isEqualTo(0);
+ return;
+ }
+
+ ArraySet<ProgramSelector.Identifier> actualSet = new ArraySet<>();
+ for (int i = 0; i < chunks.size(); i++) {
+ Set<ProgramSelector.Identifier> chunkRemoved = chunks.get(i).getRemoved();
+ actualSet.addAll(chunkRemoved);
+
+ expect.withMessage("Chunk %s removed identifier array size ", i)
+ .that(chunkRemoved.size()).isAtMost(maxRemovedPerChunk);
+ }
+ expect.withMessage("Removed identifier items")
+ .that(actualSet).containsExactlyElementsIn(expectedIdentifiers);
+ }
+}
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 d7723ac..464ecb2 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
@@ -37,7 +37,9 @@
import android.hardware.broadcastradio.IBroadcastRadio;
import android.hardware.broadcastradio.ITunerCallback;
import android.hardware.broadcastradio.IdentifierType;
+import android.hardware.broadcastradio.ProgramFilter;
import android.hardware.broadcastradio.ProgramInfo;
+import android.hardware.broadcastradio.ProgramListChunk;
import android.hardware.broadcastradio.Result;
import android.hardware.broadcastradio.VendorKeyValue;
import android.hardware.radio.ProgramList;
@@ -61,8 +63,10 @@
import org.mockito.Mock;
import org.mockito.verification.VerificationWithTimeout;
+import java.util.ArrayList;
import java.util.List;
import java.util.Map;
+import java.util.Set;
/**
* Tests for AIDL HAL TunerSession.
@@ -72,7 +76,7 @@
private static final int TARGET_SDK_VERSION = Build.VERSION_CODES.CUR_DEVELOPMENT;
private static final VerificationWithTimeout CALLBACK_TIMEOUT =
timeout(/* millis= */ 200);
- private static final int SIGNAL_QUALITY = 1;
+ private static final int SIGNAL_QUALITY = 90;
private static final long AM_FM_FREQUENCY_SPACING = 500;
private static final long[] AM_FM_FREQUENCY_LIST = {97_500, 98_100, 99_100};
private static final RadioManager.FmBandDescriptor FM_BAND_DESCRIPTOR =
@@ -84,6 +88,27 @@
new RadioManager.FmBandConfig(FM_BAND_DESCRIPTOR);
private static final int UNSUPPORTED_CONFIG_FLAG = 0;
+ 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,
+ SIGNAL_QUALITY);
+ private static final RadioManager.ProgramInfo TEST_FM_INFO_MODIFIED =
+ 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);
+
// Mocks
@Mock private IBroadcastRadio mBroadcastRadioMock;
private android.hardware.radio.ITunerCallback[] mAidlTunerCallbackMocks;
@@ -393,7 +418,7 @@
}
@Test
- public void tune_withHalHasUnknownError_fails() throws Exception {
+ public void tune_withUnknownErrorFromHal_fails() throws Exception {
openAidlClients(/* numClients= */ 1);
ProgramSelector sel = AidlTestUtils.makeFmSelector(AM_FM_FREQUENCY_LIST[1]);
doThrow(new ServiceSpecificException(Result.UNKNOWN_ERROR))
@@ -403,7 +428,7 @@
mTunerSessions[0].tune(sel);
});
- assertWithMessage("Exception for tuning when HAL has unknown error")
+ assertWithMessage("Unknown error HAL exception when tuning")
.that(thrown).hasMessageThat().contains("UNKNOWN_ERROR");
}
@@ -536,7 +561,7 @@
}
@Test
- public void seek_withHalHasInternalError_fails() throws Exception {
+ public void seek_withInternalErrorFromHal_fails() throws Exception {
openAidlClients(/* numClients= */ 1);
doThrow(new ServiceSpecificException(Result.INTERNAL_ERROR))
.when(mBroadcastRadioMock).seek(anyBoolean(), anyBoolean());
@@ -545,7 +570,7 @@
mTunerSessions[0].seek(/* directionDown= */ true, /* skipSubChannel= */ false);
});
- assertWithMessage("Exception for seeking when HAL has internal error")
+ assertWithMessage("Internal error HAL exception when seeking")
.that(thrown).hasMessageThat().contains("INTERNAL_ERROR");
}
@@ -644,11 +669,276 @@
}
@Test
+ public void startProgramListUpdates_withEmptyFilter() throws Exception {
+ 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<>();
+ ProgramListChunk halProgramList = AidlTestUtils.makeHalChunk(/* purge= */ true,
+ /* complete= */ true, modified, removed);
+ ProgramList.Chunk expectedProgramList =
+ AidlTestUtils.makeChunk(/* purge= */ true, /* complete= */ true, modified, removed);
+
+ mTunerSessions[0].startProgramListUpdates(filter);
+ mHalTunerCallback.onProgramListUpdated(halProgramList);
+
+ verify(mBroadcastRadioMock).startProgramListUpdates(halFilter);
+ verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT)
+ .onProgramListUpdated(expectedProgramList);
+ }
+
+ @Test
+ public void startProgramListUpdates_withCallbackCalledForMultipleTimes() throws Exception {
+ openAidlClients(/* numClients= */ 1);
+ ProgramList.Filter filter = new ProgramList.Filter(new ArraySet<>(), new ArraySet<>(),
+ /* includeCategories= */ true, /* excludeModifications= */ false);
+ mTunerSessions[0].startProgramListUpdates(filter);
+ mHalTunerCallback.onProgramListUpdated(AidlTestUtils.makeHalChunk(/* purge= */ true,
+ /* complete= */ true, List.of(TEST_FM_INFO, TEST_RDS_INFO), new ArrayList<>()));
+ verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onProgramListUpdated(
+ AidlTestUtils.makeChunk(/* purge= */ true, /* complete= */ true,
+ List.of(TEST_FM_INFO, TEST_RDS_INFO), new ArrayList<>()));
+
+ mHalTunerCallback.onProgramListUpdated(AidlTestUtils.makeHalChunk(/* purge= */ false,
+ /* complete= */ true, List.of(TEST_FM_INFO_MODIFIED), List.of(TEST_RDS_PI_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)));
+ }
+
+ @Test
+ public void startProgramListUpdates_withTheSameFilterForMultipleTimes() throws Exception {
+ openAidlClients(/* numClients= */ 1);
+ ProgramList.Filter filter = new ProgramList.Filter(new ArraySet<>(), new ArraySet<>(),
+ /* includeCategories= */ true, /* excludeModifications= */ false);
+ mTunerSessions[0].startProgramListUpdates(filter);
+ mHalTunerCallback.onProgramListUpdated(AidlTestUtils.makeHalChunk(/* purge= */ true,
+ /* complete= */ true, List.of(TEST_FM_INFO, TEST_RDS_INFO), new ArrayList<>()));
+ verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onProgramListUpdated(
+ AidlTestUtils.makeChunk(/* purge= */ true, /* complete= */ true,
+ List.of(TEST_FM_INFO, TEST_RDS_INFO), new ArrayList<>()));
+ mHalTunerCallback.onProgramListUpdated(AidlTestUtils.makeHalChunk(/* purge= */ false,
+ /* complete= */ true, List.of(TEST_FM_INFO_MODIFIED), List.of(TEST_RDS_PI_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)));
+
+ mTunerSessions[0].startProgramListUpdates(filter);
+
+ verify(mBroadcastRadioMock).startProgramListUpdates(any());
+ verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onProgramListUpdated(
+ AidlTestUtils.makeChunk(/* purge= */ true, /* complete= */ true,
+ List.of(TEST_FM_INFO_MODIFIED), new ArrayList<>()));
+ }
+
+ @Test
+ public void startProgramListUpdates_withNullFilter() throws Exception {
+ openAidlClients(/* numClients= */ 1);
+
+ mTunerSessions[0].startProgramListUpdates(/* filter= */ null);
+ mHalTunerCallback.onProgramListUpdated(AidlTestUtils.makeHalChunk(/* purge= */ true,
+ /* complete= */ true, List.of(TEST_FM_INFO, TEST_RDS_INFO), 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<>()));
+
+ mHalTunerCallback.onProgramListUpdated(AidlTestUtils.makeHalChunk(/* purge= */ false,
+ /* complete= */ true, List.of(TEST_FM_INFO_MODIFIED), List.of(TEST_RDS_PI_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)));
+ }
+
+ @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,
+ /* 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<>()));
+
+ verify(mBroadcastRadioMock).startProgramListUpdates(halFilter);
+ verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onProgramListUpdated(
+ AidlTestUtils.makeChunk(/* purge= */ false, /* complete= */ true,
+ List.of(TEST_RDS_INFO), new ArrayList<>()));
+
+ mHalTunerCallback.onProgramListUpdated(AidlTestUtils.makeHalChunk(/* purge= */ false,
+ /* complete= */ true, List.of(TEST_FM_INFO), new ArrayList<>()));
+
+ verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onProgramListUpdated(any());
+ }
+
+ @Test
+ public void startProgramListUpdates_withFilterExcludingModifications() throws Exception {
+ openAidlClients(/* numClients= */ 1);
+ ProgramList.Filter filterExcludingModifications = new ProgramList.Filter(
+ Set.of(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<>()));
+
+ verify(mBroadcastRadioMock).startProgramListUpdates(halFilter);
+ verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onProgramListUpdated(
+ AidlTestUtils.makeChunk(/* purge= */ false, /* complete= */ true,
+ List.of(TEST_FM_INFO), new ArrayList<>()));
+
+ mHalTunerCallback.onProgramListUpdated(AidlTestUtils.makeHalChunk(/* purge= */ false,
+ /* complete= */ true, List.of(TEST_FM_INFO_MODIFIED), new ArrayList<>()));
+
+ verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onProgramListUpdated(any());
+ }
+
+ @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<>(),
+ /* 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<>()));
+
+ verify(mBroadcastRadioMock).startProgramListUpdates(halFilter);
+ verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onProgramListUpdated(
+ AidlTestUtils.makeChunk(/* purge= */ false, /* complete= */ true,
+ List.of(TEST_FM_INFO), new ArrayList<>()));
+
+ mHalTunerCallback.onProgramListUpdated(AidlTestUtils.makeHalChunk(/* purge= */ false,
+ /* complete= */ true, List.of(TEST_FM_INFO_MODIFIED), new ArrayList<>()));
+
+ verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onProgramListUpdated(
+ AidlTestUtils.makeChunk(/* purge= */ false, /* complete= */ true,
+ List.of(TEST_FM_INFO_MODIFIED), new ArrayList<>()));
+ }
+
+ @Test
+ public void onProgramListUpdated_afterSessionClosed_doesNotUpdates() throws Exception {
+ openAidlClients(/* numClients= */ 1);
+ ProgramList.Filter filter = new ProgramList.Filter(new ArraySet<>(), new ArraySet<>(),
+ /* includeCategories= */ true, /* excludeModifications= */ false);
+ mTunerSessions[0].startProgramListUpdates(filter);
+
+ mTunerSessions[0].close();
+
+ verify(mBroadcastRadioMock).stopProgramListUpdates();
+
+ mHalTunerCallback.onProgramListUpdated(AidlTestUtils.makeHalChunk(/* purge= */ false,
+ /* complete= */ true, List.of(TEST_FM_INFO), new ArrayList<>()));
+
+ verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT.times(0)).onProgramListUpdated(any());
+ }
+
+ @Test
+ public void startProgramListUpdates_forMultipleSessions() throws Exception {
+ int numSessions = 3;
+ openAidlClients(numSessions);
+ ProgramList.Filter fmIdFilter = new ProgramList.Filter(new ArraySet<>(),
+ Set.of(TEST_FM_FREQUENCY_ID), /* includeCategories= */ false,
+ /* excludeModifications= */ true);
+ ProgramList.Filter filterExcludingCategories = new ProgramList.Filter(new ArraySet<>(),
+ new ArraySet<>(), /* includeCategories= */ true,
+ /* excludeModifications= */ true);
+ ProgramList.Filter rdsTypeFilter = new ProgramList.Filter(
+ Set.of(ProgramSelector.IDENTIFIER_TYPE_RDS_PI), new ArraySet<>(),
+ /* includeCategories= */ true, /* excludeModifications= */ false);
+
+ mTunerSessions[0].startProgramListUpdates(fmIdFilter);
+
+ ProgramFilter halFilter = ConversionUtils.filterToHalProgramFilter(fmIdFilter);
+ verify(mBroadcastRadioMock).startProgramListUpdates(halFilter);
+
+ mTunerSessions[1].startProgramListUpdates(filterExcludingCategories);
+
+ halFilter.identifiers = new android.hardware.broadcastradio.ProgramIdentifier[]{};
+ halFilter.includeCategories = true;
+ verify(mBroadcastRadioMock).startProgramListUpdates(halFilter);
+
+ mTunerSessions[2].startProgramListUpdates(rdsTypeFilter);
+
+ halFilter.excludeModifications = false;
+ verify(mBroadcastRadioMock).startProgramListUpdates(halFilter);
+ }
+
+ @Test
+ public void onProgramListUpdated_forMultipleSessions() throws Exception {
+ int numSessions = 3;
+ openAidlClients(numSessions);
+ List<ProgramList.Filter> filters = List.of(new ProgramList.Filter(
+ Set.of(ProgramSelector.IDENTIFIER_TYPE_RDS_PI), new ArraySet<>(),
+ /* includeCategories= */ true, /* excludeModifications= */ false),
+ new ProgramList.Filter(new ArraySet<>(), Set.of(TEST_FM_FREQUENCY_ID),
+ /* includeCategories= */ false, /* excludeModifications= */ true),
+ new ProgramList.Filter(new ArraySet<>(), new ArraySet<>(),
+ /* includeCategories= */ true, /* excludeModifications= */ true));
+
+ for (int index = 0; index < numSessions; index++) {
+ mTunerSessions[index].startProgramListUpdates(filters.get(index));
+ }
+
+ mHalTunerCallback.onProgramListUpdated(AidlTestUtils.makeHalChunk(/* purge= */ false,
+ /* complete= */ true, List.of(TEST_FM_INFO, TEST_RDS_INFO), new ArrayList<>()));
+
+ verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT)
+ .onProgramListUpdated(AidlTestUtils.makeChunk(/* purge= */ false,
+ /* complete= */ true, List.of(TEST_RDS_INFO), 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<>()));
+ }
+
+ @Test
+ public void startProgramListUpdates_forNonCurrentUser_doesNotStartUpdates() throws Exception {
+ openAidlClients(/* numClients= */ 1);
+ ProgramList.Filter filter = new ProgramList.Filter(new ArraySet<>(), new ArraySet<>(),
+ /* includeCategories= */ true, /* excludeModifications= */ false);
+ doReturn(false).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
+
+ mTunerSessions[0].startProgramListUpdates(filter);
+
+ verify(mBroadcastRadioMock, never()).startProgramListUpdates(any());
+ }
+
+ @Test
+ public void startProgramListUpdates_withUnknownErrorFromHal_fails() throws Exception {
+ openAidlClients(/* numClients= */ 1);
+ doThrow(new ServiceSpecificException(Result.UNKNOWN_ERROR))
+ .when(mBroadcastRadioMock).startProgramListUpdates(any());
+
+ ParcelableException thrown = assertThrows(ParcelableException.class, () -> {
+ mTunerSessions[0].startProgramListUpdates(/* filter= */ null);
+ });
+
+ assertWithMessage("Unknown error HAL exception when updating program list")
+ .that(thrown).hasMessageThat().contains("UNKNOWN_ERROR");
+ }
+
+ @Test
public void stopProgramListUpdates() throws Exception {
openAidlClients(/* numClients= */ 1);
- ProgramList.Filter aidlFilter = new ProgramList.Filter(new ArraySet<>(), new ArraySet<>(),
+ ProgramList.Filter filter = new ProgramList.Filter(new ArraySet<>(), new ArraySet<>(),
/* includeCategories= */ true, /* excludeModifications= */ false);
- mTunerSessions[0].startProgramListUpdates(aidlFilter);
+ mTunerSessions[0].startProgramListUpdates(filter);
mTunerSessions[0].stopProgramListUpdates();
@@ -658,9 +948,9 @@
@Test
public void stopProgramListUpdates_forNonCurrentUser_doesNotStopUpdates() throws Exception {
openAidlClients(/* numClients= */ 1);
- ProgramList.Filter aidlFilter = new ProgramList.Filter(new ArraySet<>(), new ArraySet<>(),
+ ProgramList.Filter filter = new ProgramList.Filter(new ArraySet<>(), new ArraySet<>(),
/* includeCategories= */ true, /* excludeModifications= */ false);
- mTunerSessions[0].startProgramListUpdates(aidlFilter);
+ mTunerSessions[0].startProgramListUpdates(filter);
doReturn(false).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
mTunerSessions[0].stopProgramListUpdates();
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TunerSessionHidlTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TunerSessionHidlTest.java
index ea9a846..3815008 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TunerSessionHidlTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TunerSessionHidlTest.java
@@ -373,7 +373,7 @@
}
@Test
- public void tune_withHalHasUnknownError_fails() throws Exception {
+ public void tune_withUnknownErrorFromHal_fails() throws Exception {
openAidlClients(/* numClients= */ 1);
ProgramSelector sel = TestUtils.makeFmSelector(AM_FM_FREQUENCY_LIST[1]);
doAnswer(invocation -> Result.UNKNOWN_ERROR).when(mHalTunerSessionMock).tune(any());
@@ -382,7 +382,7 @@
mTunerSessions[0].tune(sel);
});
- assertWithMessage("Exception for tuning when HAL has unknown error")
+ assertWithMessage("Unknown error HAL exception when tuning")
.that(thrown).hasMessageThat().contains(Result.toString(Result.UNKNOWN_ERROR));
}
@@ -513,7 +513,7 @@
}
@Test
- public void seek_withHalHasInternalError_fails() throws Exception {
+ public void seek_withInternalErrorFromHal_fails() throws Exception {
openAidlClients(/* numClients= */ 1);
doAnswer(invocation -> Result.INTERNAL_ERROR).when(mHalTunerSessionMock)
.scan(anyBoolean(), anyBoolean());
@@ -522,7 +522,7 @@
mTunerSessions[0].seek(/* directionDown= */ true, /* skipSubChannel= */ false);
});
- assertWithMessage("Exception for seeking when HAL has internal error")
+ assertWithMessage("Internal error HAL exception when seeking")
.that(thrown).hasMessageThat().contains(Result.toString(Result.INTERNAL_ERROR));
}
@@ -633,6 +633,32 @@
}
@Test
+ public void startProgramListUpdates_forNonCurrentUser_doesNotStartUpdates() throws Exception {
+ openAidlClients(/* numClients= */ 1);
+ ProgramList.Filter filter = new ProgramList.Filter(new ArraySet<>(), new ArraySet<>(),
+ /* includeCategories= */ true, /* excludeModifications= */ false);
+ doReturn(false).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
+
+ mTunerSessions[0].startProgramListUpdates(filter);
+
+ verify(mHalTunerSessionMock, never()).startProgramListUpdates(any());
+ }
+
+ @Test
+ public void startProgramListUpdates_withUnknownErrorFromHal_fails() throws Exception {
+ openAidlClients(/* numClients= */ 1);
+ doAnswer(invocation -> Result.UNKNOWN_ERROR).when(mHalTunerSessionMock)
+ .startProgramListUpdates(any());
+
+ ParcelableException thrown = assertThrows(ParcelableException.class, () -> {
+ mTunerSessions[0].startProgramListUpdates(/* filter= */ null);
+ });
+
+ assertWithMessage("Unknown error HAL exception when updating program list")
+ .that(thrown).hasMessageThat().contains(Result.toString(Result.UNKNOWN_ERROR));
+ }
+
+ @Test
public void stopProgramListUpdates() throws Exception {
openAidlClients(/* numClients= */ 1);
ProgramList.Filter aidlFilter = new ProgramList.Filter(new ArraySet<>(), new ArraySet<>(),
diff --git a/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt b/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt
index 625c318..249e246 100644
--- a/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt
+++ b/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt
@@ -16,6 +16,8 @@
package android.content.res
+
+import android.platform.test.annotations.Presubmit
import androidx.core.util.forEach
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
@@ -27,6 +29,7 @@
import org.junit.Test
import org.junit.runner.RunWith
+@Presubmit
@RunWith(AndroidJUnit4::class)
class FontScaleConverterFactoryTest {
@@ -79,10 +82,10 @@
@LargeTest
@Test
fun allFeasibleScalesAndConversionsDoNotCrash() {
- generateSequenceOfFractions(-10000f..10000f, step = 0.01f)
+ generateSequenceOfFractions(-10f..10f, step = 0.01f)
.mapNotNull{ FontScaleConverterFactory.forScale(it) }
.flatMap{ table ->
- generateSequenceOfFractions(-10000f..10000f, step = 0.01f)
+ generateSequenceOfFractions(-2000f..2000f, step = 0.01f)
.map{ Pair(table, it) }
}
.forEach { (table, sp) ->
diff --git a/core/tests/coretests/src/android/content/res/FontScaleConverterTest.kt b/core/tests/coretests/src/android/content/res/FontScaleConverterTest.kt
index e9f850e..bfa8c9a 100644
--- a/core/tests/coretests/src/android/content/res/FontScaleConverterTest.kt
+++ b/core/tests/coretests/src/android/content/res/FontScaleConverterTest.kt
@@ -16,11 +16,13 @@
package android.content.res
+import android.platform.test.annotations.Presubmit
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.google.common.truth.Truth.assertWithMessage
import org.junit.Test
import org.junit.runner.RunWith
+@Presubmit
@RunWith(AndroidJUnit4::class)
class FontScaleConverterTest {
diff --git a/core/tests/coretests/src/android/util/TypedValueTest.kt b/core/tests/coretests/src/android/util/TypedValueTest.kt
index b020c38..af01447 100644
--- a/core/tests/coretests/src/android/util/TypedValueTest.kt
+++ b/core/tests/coretests/src/android/util/TypedValueTest.kt
@@ -17,6 +17,7 @@
package android.util
import android.content.res.FontScaleConverterFactory
+import android.platform.test.annotations.Presubmit
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import androidx.test.filters.SmallTest
@@ -30,6 +31,7 @@
import kotlin.math.min
import kotlin.math.roundToInt
+@Presubmit
@RunWith(AndroidJUnit4::class)
class TypedValueTest {
@LargeTest
@@ -223,7 +225,6 @@
metrics.scaledDensity = 0f
listOf(
- TypedValue.COMPLEX_UNIT_PX,
TypedValue.COMPLEX_UNIT_DIP,
TypedValue.COMPLEX_UNIT_SP,
TypedValue.COMPLEX_UNIT_PT,
@@ -257,8 +258,7 @@
TypedValue.COMPLEX_UNIT_MM
)
.forEach { dimenType ->
- // Test for every integer value in the range...
- for (i: Int in -(1 shl 23) until (1 shl 23)) {
+ for (i: Int in -10000 until 10000) {
assertRoundTripIsEqual(i.toFloat(), dimenType, metrics)
assertRoundTripIsEqual(i - .1f, dimenType, metrics)
assertRoundTripIsEqual(i + .5f, dimenType, metrics)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
index af31391..6230d22 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
@@ -17,6 +17,7 @@
import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
+import static com.android.wm.shell.bubbles.Bubble.KEY_APP_BUBBLE;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_DATA;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
@@ -684,7 +685,8 @@
if (bubble.getPendingIntentCanceled()
|| !(reason == Bubbles.DISMISS_AGED
|| reason == Bubbles.DISMISS_USER_GESTURE
- || reason == Bubbles.DISMISS_RELOAD_FROM_DISK)) {
+ || reason == Bubbles.DISMISS_RELOAD_FROM_DISK)
+ || KEY_APP_BUBBLE.equals(bubble.getKey())) {
return;
}
if (DEBUG_BUBBLE_DATA) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 8ddc3c04..1488469 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -605,9 +605,19 @@
float splitRatio, RemoteAnimationAdapter adapter, InstanceId instanceId) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
if (options1 == null) options1 = new Bundle();
+ if (taskId2 == INVALID_TASK_ID) {
+ // Launching a solo task.
+ ActivityOptions activityOptions = ActivityOptions.fromBundle(options1);
+ activityOptions.update(ActivityOptions.makeRemoteAnimation(adapter));
+ options1 = activityOptions.toBundle();
+ addActivityOptions(options1, null /* launchTarget */);
+ wct.startTask(taskId1, options1);
+ mSyncQueue.queue(wct);
+ return;
+ }
+
addActivityOptions(options1, mSideStage);
wct.startTask(taskId1, options1);
-
startWithLegacyTransition(wct, taskId2, options2, splitPosition, splitRatio, adapter,
instanceId);
}
@@ -632,9 +642,19 @@
InstanceId instanceId) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
if (options1 == null) options1 = new Bundle();
+ if (taskId == INVALID_TASK_ID) {
+ // Launching a solo task.
+ ActivityOptions activityOptions = ActivityOptions.fromBundle(options1);
+ activityOptions.update(ActivityOptions.makeRemoteAnimation(adapter));
+ options1 = activityOptions.toBundle();
+ addActivityOptions(options1, null /* launchTarget */);
+ wct.sendPendingIntent(pendingIntent, fillInIntent, options1);
+ mSyncQueue.queue(wct);
+ return;
+ }
+
addActivityOptions(options1, mSideStage);
wct.sendPendingIntent(pendingIntent, fillInIntent, options1);
-
startWithLegacyTransition(wct, taskId, options2, splitPosition, splitRatio, adapter,
instanceId);
}
@@ -696,6 +716,34 @@
mShouldUpdateRecents = false;
mIsSplitEntering = true;
+ setSideStagePosition(sidePosition, wct);
+ if (!mMainStage.isActive()) {
+ mMainStage.activate(wct, false /* reparent */);
+ }
+
+ if (mainOptions == null) mainOptions = new Bundle();
+ addActivityOptions(mainOptions, mMainStage);
+ mainOptions = wrapAsSplitRemoteAnimation(adapter, mainOptions);
+
+ updateWindowBounds(mSplitLayout, wct);
+ if (mainTaskId == INVALID_TASK_ID) {
+ wct.sendPendingIntent(mainPendingIntent, mainFillInIntent, mainOptions);
+ } else {
+ wct.startTask(mainTaskId, mainOptions);
+ }
+
+ wct.reorder(mRootTaskInfo.token, true);
+ wct.setForceTranslucent(mRootTaskInfo.token, false);
+
+ mSyncQueue.queue(wct);
+ mSyncQueue.runInSync(t -> {
+ setDividerVisibility(true, t);
+ });
+
+ setEnterInstanceId(instanceId);
+ }
+
+ private Bundle wrapAsSplitRemoteAnimation(RemoteAnimationAdapter adapter, Bundle options) {
final WindowContainerTransaction evictWct = new WindowContainerTransaction();
if (isSplitScreenVisible()) {
mMainStage.evictAllChildren(evictWct);
@@ -739,37 +787,9 @@
};
RemoteAnimationAdapter wrappedAdapter = new RemoteAnimationAdapter(
wrapper, adapter.getDuration(), adapter.getStatusBarTransitionDelay());
-
- if (mainOptions == null) {
- mainOptions = ActivityOptions.makeRemoteAnimation(wrappedAdapter).toBundle();
- } else {
- ActivityOptions mainActivityOptions = ActivityOptions.fromBundle(mainOptions);
- mainActivityOptions.update(ActivityOptions.makeRemoteAnimation(wrappedAdapter));
- mainOptions = mainActivityOptions.toBundle();
- }
-
- setSideStagePosition(sidePosition, wct);
- if (!mMainStage.isActive()) {
- mMainStage.activate(wct, false /* reparent */);
- }
-
- if (mainOptions == null) mainOptions = new Bundle();
- addActivityOptions(mainOptions, mMainStage);
- updateWindowBounds(mSplitLayout, wct);
- if (mainTaskId == INVALID_TASK_ID) {
- wct.sendPendingIntent(mainPendingIntent, mainFillInIntent, mainOptions);
- } else {
- wct.startTask(mainTaskId, mainOptions);
- }
- wct.reorder(mRootTaskInfo.token, true);
- wct.setForceTranslucent(mRootTaskInfo.token, false);
-
- mSyncQueue.queue(wct);
- mSyncQueue.runInSync(t -> {
- setDividerVisibility(true, t);
- });
-
- setEnterInstanceId(instanceId);
+ ActivityOptions activityOptions = ActivityOptions.fromBundle(options);
+ activityOptions.update(ActivityOptions.makeRemoteAnimation(wrappedAdapter));
+ return activityOptions.toBundle();
}
private void setEnterInstanceId(InstanceId instanceId) {
@@ -1228,8 +1248,10 @@
return SPLIT_POSITION_UNDEFINED;
}
- private void addActivityOptions(Bundle opts, StageTaskListener stage) {
- opts.putParcelable(KEY_LAUNCH_ROOT_TASK_TOKEN, stage.mRootTaskInfo.token);
+ private void addActivityOptions(Bundle opts, @Nullable StageTaskListener launchTarget) {
+ if (launchTarget != null) {
+ opts.putParcelable(KEY_LAUNCH_ROOT_TASK_TOKEN, launchTarget.mRootTaskInfo.token);
+ }
// Put BAL flags to avoid activity start aborted. Otherwise, flows like shortcut to split
// will be canceled.
opts.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED, true);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
index e6711ac..8b025cd 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
@@ -16,6 +16,8 @@
package com.android.wm.shell.bubbles;
+import static com.android.wm.shell.bubbles.Bubble.KEY_APP_BUBBLE;
+
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
@@ -32,6 +34,7 @@
import android.app.Notification;
import android.app.PendingIntent;
+import android.content.Intent;
import android.content.LocusId;
import android.graphics.drawable.Icon;
import android.os.Bundle;
@@ -94,6 +97,7 @@
private Bubble mBubbleInterruptive;
private Bubble mBubbleDismissed;
private Bubble mBubbleLocusId;
+ private Bubble mAppBubble;
private BubbleData mBubbleData;
private TestableBubblePositioner mPositioner;
@@ -178,6 +182,11 @@
mBubbleMetadataFlagListener,
mPendingIntentCanceledListener,
mMainExecutor);
+
+ Intent appBubbleIntent = new Intent(mContext, BubblesTestActivity.class);
+ appBubbleIntent.setPackage(mContext.getPackageName());
+ mAppBubble = new Bubble(appBubbleIntent, new UserHandle(1), mMainExecutor);
+
mPositioner = new TestableBubblePositioner(mContext,
mock(WindowManager.class));
mBubbleData = new BubbleData(getContext(), mBubbleLogger, mPositioner,
@@ -1089,6 +1098,18 @@
assertOverflowChangedTo(ImmutableList.of());
}
+ @Test
+ public void test_removeAppBubble_skipsOverflow() {
+ mBubbleData.notificationEntryUpdated(mAppBubble, true /* suppressFlyout*/,
+ false /* showInShade */);
+ assertThat(mBubbleData.getBubbleInStackWithKey(KEY_APP_BUBBLE)).isEqualTo(mAppBubble);
+
+ mBubbleData.dismissBubbleWithKey(KEY_APP_BUBBLE, Bubbles.DISMISS_USER_GESTURE);
+
+ assertThat(mBubbleData.getOverflowBubbleWithKey(KEY_APP_BUBBLE)).isNull();
+ assertThat(mBubbleData.getBubbleInStackWithKey(KEY_APP_BUBBLE)).isNull();
+ }
+
private void verifyUpdateReceived() {
verify(mListener).applyUpdate(mUpdateCaptor.capture());
reset(mListener);
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 23cf913..aeead5e 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -215,6 +215,15 @@
path: "apex/java",
}
+java_api_contribution {
+ name: "framework-graphics-public-stubs",
+ api_surface: "public",
+ api_file: "api/current.txt",
+ visibility: [
+ "//build/orchestrator/apis",
+ ],
+}
+
// ------------------------
// APEX
// ------------------------
diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h
index 2539694..b7d4dc9 100644
--- a/libs/hwui/RecordingCanvas.h
+++ b/libs/hwui/RecordingCanvas.h
@@ -16,11 +16,13 @@
#pragma once
-#include "CanvasTransform.h"
-#include "hwui/Bitmap.h"
-#include "utils/Macros.h"
-#include "utils/TypeLogic.h"
+#include <SkRuntimeEffect.h>
+#include <log/log.h>
+#include <cstdlib>
+#include <vector>
+
+#include "CanvasTransform.h"
#include "SkCanvas.h"
#include "SkCanvasVirtualEnforcer.h"
#include "SkDrawable.h"
@@ -28,11 +30,11 @@
#include "SkPaint.h"
#include "SkPath.h"
#include "SkRect.h"
-
+#include "hwui/Bitmap.h"
#include "pipeline/skia/AnimatedDrawables.h"
-
-#include <SkRuntimeEffect.h>
-#include <vector>
+#include "utils/AutoMalloc.h"
+#include "utils/Macros.h"
+#include "utils/TypeLogic.h"
enum class SkBlendMode;
class SkRRect;
@@ -145,7 +147,7 @@
template <typename Fn, typename... Args>
void map(const Fn[], Args...) const;
- SkAutoTMalloc<uint8_t> fBytes;
+ AutoTMalloc<uint8_t> fBytes;
size_t fUsed = 0;
size_t fReserved = 0;
diff --git a/libs/hwui/utils/AutoMalloc.h b/libs/hwui/utils/AutoMalloc.h
new file mode 100644
index 0000000..05f5e9f
--- /dev/null
+++ b/libs/hwui/utils/AutoMalloc.h
@@ -0,0 +1,94 @@
+/**
+ * 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.
+ */
+
+#pragma once
+
+#include <cstdlib>
+#include <memory>
+#include <type_traits>
+
+namespace android {
+namespace uirenderer {
+
+/** Manages an array of T elements, freeing the array in the destructor.
+ * Does NOT call any constructors/destructors on T (T must be POD).
+ */
+template <typename T,
+ typename = std::enable_if_t<std::is_trivially_default_constructible<T>::value &&
+ std::is_trivially_destructible<T>::value>>
+class AutoTMalloc {
+public:
+ /** Takes ownership of the ptr. The ptr must be a value which can be passed to std::free. */
+ explicit AutoTMalloc(T* ptr = nullptr) : fPtr(ptr) {}
+
+ /** Allocates space for 'count' Ts. */
+ explicit AutoTMalloc(size_t count) : fPtr(mallocIfCountThrowOnFail(count)) {}
+
+ AutoTMalloc(AutoTMalloc&&) = default;
+ AutoTMalloc& operator=(AutoTMalloc&&) = default;
+
+ /** Resize the memory area pointed to by the current ptr preserving contents. */
+ void realloc(size_t count) { fPtr.reset(reallocIfCountThrowOnFail(count)); }
+
+ /** Resize the memory area pointed to by the current ptr without preserving contents. */
+ T* reset(size_t count = 0) {
+ fPtr.reset(mallocIfCountThrowOnFail(count));
+ return this->get();
+ }
+
+ T* get() const { return fPtr.get(); }
+
+ operator T*() { return fPtr.get(); }
+
+ operator const T*() const { return fPtr.get(); }
+
+ T& operator[](int index) { return fPtr.get()[index]; }
+
+ const T& operator[](int index) const { return fPtr.get()[index]; }
+
+ /**
+ * Transfer ownership of the ptr to the caller, setting the internal
+ * pointer to NULL. Note that this differs from get(), which also returns
+ * the pointer, but it does not transfer ownership.
+ */
+ T* release() { return fPtr.release(); }
+
+private:
+ struct FreeDeleter {
+ void operator()(uint8_t* p) { std::free(p); }
+ };
+ std::unique_ptr<T, FreeDeleter> fPtr;
+
+ T* mallocIfCountThrowOnFail(size_t count) {
+ T* newPtr = nullptr;
+ if (count) {
+ newPtr = (T*)std::malloc(count * sizeof(T));
+ LOG_ALWAYS_FATAL_IF(!newPtr, "failed to malloc %zu bytes", count * sizeof(T));
+ }
+ return newPtr;
+ }
+ T* reallocIfCountThrowOnFail(size_t count) {
+ T* newPtr = nullptr;
+ if (count) {
+ newPtr = (T*)std::realloc(fPtr.release(), count * sizeof(T));
+ LOG_ALWAYS_FATAL_IF(!newPtr, "failed to realloc %zu bytes", count * sizeof(T));
+ }
+ return newPtr;
+ }
+};
+
+} // namespace uirenderer
+} // namespace android
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 4475aed..24c5b41 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -7022,6 +7022,10 @@
* Returns an array of {@link AudioDeviceInfo} objects corresponding to the audio devices
* currently connected to the system and meeting the criteria specified in the
* <code>flags</code> parameter.
+ * Notes that Android audio framework only support one device per device type. In that case,
+ * if there are multiple audio device with the same device type connected to the Android device,
+ * only the last reported device will be known by Android audio framework and returned by this
+ * API.
* @param flags A set of bitflags specifying the criteria to test.
* @see #GET_DEVICES_OUTPUTS
* @see #GET_DEVICES_INPUTS
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 90874bb..06c3476 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -123,7 +123,7 @@
Settings.Secure.FINGERPRINT_SIDE_FPS_BP_POWER_WINDOW,
Settings.Secure.FINGERPRINT_SIDE_FPS_ENROLL_TAP_WINDOW,
Settings.Secure.FINGERPRINT_SIDE_FPS_AUTH_DOWNTIME,
- Settings.Secure.SFPS_REQUIRE_SCREEN_ON_TO_AUTH_ENABLED,
+ Settings.Secure.SFPS_PERFORMANT_AUTH_ENABLED,
Settings.Secure.ACTIVE_UNLOCK_ON_WAKE,
Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT,
Settings.Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index 62f4c41..d72d4d5 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -178,7 +178,7 @@
VALIDATORS.put(Secure.FINGERPRINT_SIDE_FPS_ENROLL_TAP_WINDOW,
NON_NEGATIVE_INTEGER_VALIDATOR);
VALIDATORS.put(Secure.FINGERPRINT_SIDE_FPS_AUTH_DOWNTIME, NON_NEGATIVE_INTEGER_VALIDATOR);
- VALIDATORS.put(Secure.SFPS_REQUIRE_SCREEN_ON_TO_AUTH_ENABLED, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(Secure.SFPS_PERFORMANT_AUTH_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.SHOW_MEDIA_WHEN_BYPASSING, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.FACE_UNLOCK_APP_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION, BOOLEAN_VALIDATOR);
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 4be1d30..ecb88f6 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -415,7 +415,6 @@
<service android:name=".screenshot.ScreenshotCrossProfileService"
android:permission="com.android.systemui.permission.SELF"
- android:process=":screenshot_cross_profile"
android:exported="false" />
<service android:name=".screenrecord.RecordingService"
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt
index 2903288..3d341af 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt
@@ -33,7 +33,9 @@
private const val FONT_ITALIC_ANIMATION_STEP = 0.1f
private const val FONT_ITALIC_DEFAULT_VALUE = 0f
-/** Provide interpolation of two fonts by adjusting font variation settings. */
+/**
+ * Provide interpolation of two fonts by adjusting font variation settings.
+ */
class FontInterpolator {
/**
@@ -59,14 +61,11 @@
var index: Int,
val sortedAxes: MutableList<FontVariationAxis>
) {
- constructor(
- font: Font,
- axes: List<FontVariationAxis>
- ) : this(
- font.sourceIdentifier,
- font.ttcIndex,
- axes.toMutableList().apply { sortBy { it.tag } }
- )
+ constructor(font: Font, axes: List<FontVariationAxis>) :
+ this(font.sourceIdentifier,
+ font.ttcIndex,
+ axes.toMutableList().apply { sortBy { it.tag } }
+ )
fun set(font: Font, axes: List<FontVariationAxis>) {
sourceId = font.sourceIdentifier
@@ -87,7 +86,9 @@
private val tmpInterpKey = InterpKey(null, null, 0f)
private val tmpVarFontKey = VarFontKey(0, 0, mutableListOf())
- /** Linear interpolate the font variation settings. */
+ /**
+ * Linear interpolate the font variation settings.
+ */
fun lerp(start: Font, end: Font, progress: Float): Font {
if (progress == 0f) {
return start
@@ -114,34 +115,27 @@
// this doesn't take much time since the variation axes is usually up to 5. If we need to
// support more number of axes, we may want to preprocess the font and store the sorted axes
// and also pre-fill the missing axes value with default value from 'fvar' table.
- val newAxes =
- lerp(startAxes, endAxes) { tag, startValue, endValue ->
- when (tag) {
- // TODO: Good to parse 'fvar' table for retrieving default value.
- TAG_WGHT ->
- adjustWeight(
- MathUtils.lerp(
+ val newAxes = lerp(startAxes, endAxes) { tag, startValue, endValue ->
+ when (tag) {
+ // TODO: Good to parse 'fvar' table for retrieving default value.
+ TAG_WGHT -> adjustWeight(
+ MathUtils.lerp(
startValue ?: FONT_WEIGHT_DEFAULT_VALUE,
endValue ?: FONT_WEIGHT_DEFAULT_VALUE,
- progress
- )
- )
- TAG_ITAL ->
- adjustItalic(
- MathUtils.lerp(
+ progress))
+ TAG_ITAL -> adjustItalic(
+ MathUtils.lerp(
startValue ?: FONT_ITALIC_DEFAULT_VALUE,
endValue ?: FONT_ITALIC_DEFAULT_VALUE,
- progress
- )
- )
- else -> {
- require(startValue != null && endValue != null) {
- "Unable to interpolate due to unknown default axes value : $tag"
- }
- MathUtils.lerp(startValue, endValue, progress)
+ progress))
+ else -> {
+ require(startValue != null && endValue != null) {
+ "Unable to interpolate due to unknown default axes value : $tag"
}
+ MathUtils.lerp(startValue, endValue, progress)
}
}
+ }
// Check if we already make font for this axes. This is typically happens if the animation
// happens backward.
@@ -155,7 +149,9 @@
// This is the first time to make the font for the axes. Build and store it to the cache.
// Font.Builder#build won't throw IOException since creating fonts from existing fonts will
// not do any IO work.
- val newFont = Font.Builder(start).setFontVariationSettings(newAxes.toTypedArray()).build()
+ val newFont = Font.Builder(start)
+ .setFontVariationSettings(newAxes.toTypedArray())
+ .build()
interpCache[InterpKey(start, end, progress)] = newFont
verFontCache[VarFontKey(start, newAxes)] = newFont
return newFont
@@ -177,28 +173,26 @@
val tagA = if (i < start.size) start[i].tag else null
val tagB = if (j < end.size) end[j].tag else null
- val comp =
- when {
- tagA == null -> 1
- tagB == null -> -1
- else -> tagA.compareTo(tagB)
- }
+ val comp = when {
+ tagA == null -> 1
+ tagB == null -> -1
+ else -> tagA.compareTo(tagB)
+ }
- val axis =
- when {
- comp == 0 -> {
- val v = filter(tagA!!, start[i++].styleValue, end[j++].styleValue)
- FontVariationAxis(tagA, v)
- }
- comp < 0 -> {
- val v = filter(tagA!!, start[i++].styleValue, null)
- FontVariationAxis(tagA, v)
- }
- else -> { // comp > 0
- val v = filter(tagB!!, null, end[j++].styleValue)
- FontVariationAxis(tagB, v)
- }
+ val axis = when {
+ comp == 0 -> {
+ val v = filter(tagA!!, start[i++].styleValue, end[j++].styleValue)
+ FontVariationAxis(tagA, v)
}
+ comp < 0 -> {
+ val v = filter(tagA!!, start[i++].styleValue, null)
+ FontVariationAxis(tagA, v)
+ }
+ else -> { // comp > 0
+ val v = filter(tagB!!, null, end[j++].styleValue)
+ FontVariationAxis(tagB, v)
+ }
+ }
result.add(axis)
}
@@ -208,21 +202,21 @@
// For the performance reasons, we animate weight with FONT_WEIGHT_ANIMATION_STEP. This helps
// Cache hit ratio in the Skia glyph cache.
private fun adjustWeight(value: Float) =
- coerceInWithStep(value, FONT_WEIGHT_MIN, FONT_WEIGHT_MAX, FONT_WEIGHT_ANIMATION_STEP)
+ coerceInWithStep(value, FONT_WEIGHT_MIN, FONT_WEIGHT_MAX, FONT_WEIGHT_ANIMATION_STEP)
// For the performance reasons, we animate italic with FONT_ITALIC_ANIMATION_STEP. This helps
// Cache hit ratio in the Skia glyph cache.
private fun adjustItalic(value: Float) =
- coerceInWithStep(value, FONT_ITALIC_MIN, FONT_ITALIC_MAX, FONT_ITALIC_ANIMATION_STEP)
+ coerceInWithStep(value, FONT_ITALIC_MIN, FONT_ITALIC_MAX, FONT_ITALIC_ANIMATION_STEP)
private fun coerceInWithStep(v: Float, min: Float, max: Float, step: Float) =
- (v.coerceIn(min, max) / step).toInt() * step
+ (v.coerceIn(min, max) / step).toInt() * step
companion object {
private val EMPTY_AXES = arrayOf<FontVariationAxis>()
// Returns true if given two font instance can be interpolated.
fun canInterpolate(start: Font, end: Font) =
- start.ttcIndex == end.ttcIndex && start.sourceIdentifier == end.sourceIdentifier
+ start.ttcIndex == end.ttcIndex && start.sourceIdentifier == end.sourceIdentifier
}
}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
index 65d6c83..5f1bb83 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
@@ -36,7 +36,8 @@
* Currently this class can provide text style animation for text weight and text size. For example
* the simple view that draws text with animating text size is like as follows:
*
- * ```
+ * <pre>
+ * <code>
* class SimpleTextAnimation : View {
* @JvmOverloads constructor(...)
*
@@ -52,34 +53,39 @@
* animator.setTextStyle(-1 /* unchanged weight */, sizePx, animate)
* }
* }
- * ```
+ * </code>
+ * </pre>
*/
-class TextAnimator(layout: Layout, private val invalidateCallback: () -> Unit) {
+class TextAnimator(
+ layout: Layout,
+ private val invalidateCallback: () -> Unit
+) {
// Following two members are for mutable for testing purposes.
public var textInterpolator: TextInterpolator = TextInterpolator(layout)
- public var animator: ValueAnimator =
- ValueAnimator.ofFloat(1f).apply {
- duration = DEFAULT_ANIMATION_DURATION
- addUpdateListener {
- textInterpolator.progress = it.animatedValue as Float
- invalidateCallback()
- }
- addListener(
- object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator?) {
- textInterpolator.rebase()
- }
- override fun onAnimationCancel(animation: Animator?) = textInterpolator.rebase()
- }
- )
+ public var animator: ValueAnimator = ValueAnimator.ofFloat(1f).apply {
+ duration = DEFAULT_ANIMATION_DURATION
+ addUpdateListener {
+ textInterpolator.progress = it.animatedValue as Float
+ invalidateCallback()
}
+ addListener(object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator?) {
+ textInterpolator.rebase()
+ }
+ override fun onAnimationCancel(animation: Animator?) = textInterpolator.rebase()
+ })
+ }
sealed class PositionedGlyph {
- /** Mutable X coordinate of the glyph position relative from drawing offset. */
+ /**
+ * Mutable X coordinate of the glyph position relative from drawing offset.
+ */
var x: Float = 0f
- /** Mutable Y coordinate of the glyph position relative from the baseline. */
+ /**
+ * Mutable Y coordinate of the glyph position relative from the baseline.
+ */
var y: Float = 0f
/**
@@ -90,29 +96,40 @@
/**
* Mutable text size of the glyph in pixels.
*/
- /** Mutable text size of the glyph in pixels. */
var textSize: Float = 0f
- /** Mutable color of the glyph. */
+ /**
+ * Mutable color of the glyph.
+ */
var color: Int = 0
- /** Immutable character offset in the text that the current font run start. */
+ /**
+ * Immutable character offset in the text that the current font run start.
+ */
abstract var runStart: Int
protected set
- /** Immutable run length of the font run. */
+ /**
+ * Immutable run length of the font run.
+ */
abstract var runLength: Int
protected set
- /** Immutable glyph index of the font run. */
+ /**
+ * Immutable glyph index of the font run.
+ */
abstract var glyphIndex: Int
protected set
- /** Immutable font instance for this font run. */
+ /**
+ * Immutable font instance for this font run.
+ */
abstract var font: Font
protected set
- /** Immutable glyph ID for this glyph. */
+ /**
+ * Immutable glyph ID for this glyph.
+ */
abstract var glyphId: Int
protected set
}
@@ -130,30 +147,30 @@
/**
* GlyphFilter applied just before drawing to canvas for tweaking positions and text size.
*
- * This callback is called for each glyphs just before drawing the glyphs. This function will be
- * called with the intrinsic position, size, color, glyph ID and font instance. You can mutate
- * the position, size and color for tweaking animations. Do not keep the reference of passed
- * glyph object. The interpolator reuses that object for avoiding object allocations.
+ * This callback is called for each glyphs just before drawing the glyphs. This function will
+ * be called with the intrinsic position, size, color, glyph ID and font instance. You can
+ * mutate the position, size and color for tweaking animations.
+ * Do not keep the reference of passed glyph object. The interpolator reuses that object for
+ * avoiding object allocations.
*
- * Details: The text is drawn with font run units. The font run is a text segment that draws
- * with the same font. The {@code runStart} and {@code runLimit} is a range of the font run in
- * the text that current glyph is in. Once the font run is determined, the system will convert
- * characters into glyph IDs. The {@code glyphId} is the glyph identifier in the font and {@code
- * glyphIndex} is the offset of the converted glyph array. Please note that the {@code
- * glyphIndex} is not a character index, because the character will not be converted to glyph
- * one-by-one. If there are ligatures including emoji sequence, etc, the glyph ID may be
+ * Details:
+ * The text is drawn with font run units. The font run is a text segment that draws with the
+ * same font. The {@code runStart} and {@code runLimit} is a range of the font run in the text
+ * that current glyph is in. Once the font run is determined, the system will convert characters
+ * into glyph IDs. The {@code glyphId} is the glyph identifier in the font and
+ * {@code glyphIndex} is the offset of the converted glyph array. Please note that the
+ * {@code glyphIndex} is not a character index, because the character will not be converted to
+ * glyph one-by-one. If there are ligatures including emoji sequence, etc, the glyph ID may be
* composed from multiple characters.
*
* Here is an example of font runs: "fin. 終わり"
*
- * ```
* Characters : f i n . _ 終 わ り
* Code Points: \u0066 \u0069 \u006E \u002E \u0020 \u7D42 \u308F \u308A
* Font Runs : <-- Roboto-Regular.ttf --><-- NotoSans-CJK.otf -->
* runStart = 0, runLength = 5 runStart = 5, runLength = 3
* Glyph IDs : 194 48 7 8 4367 1039 1002
* Glyph Index: 0 1 2 3 0 1 2
- * ```
*
* In this example, the "fi" is converted into ligature form, thus the single glyph ID is
* assigned for two characters, f and i.
@@ -176,29 +193,28 @@
*/
var glyphFilter: GlyphCallback?
get() = textInterpolator.glyphFilter
- set(value) {
- textInterpolator.glyphFilter = value
- }
+ set(value) { textInterpolator.glyphFilter = value }
fun draw(c: Canvas) = textInterpolator.draw(c)
/**
* Set text style with animation.
*
- * By passing -1 to weight, the view preserve the current weight. By passing -1 to textSize, the
- * view preserve the current text size. Bu passing -1 to duration, the default text animation,
- * 1000ms, is used. By passing false to animate, the text will be updated without animation.
+ * By passing -1 to weight, the view preserve the current weight.
+ * By passing -1 to textSize, the view preserve the current text size.
+ * Bu passing -1 to duration, the default text animation, 1000ms, is used.
+ * By passing false to animate, the text will be updated without animation.
*
* @param weight an optional text weight.
* @param textSize an optional font size.
- * @param colors an optional colors array that must be the same size as numLines passed to the
- * TextInterpolator
+ * @param colors an optional colors array that must be the same size as numLines passed to
+ * the TextInterpolator
* @param animate an optional boolean indicating true for showing style transition as animation,
- * false for immediate style transition. True by default.
+ * false for immediate style transition. True by default.
* @param duration an optional animation duration in milliseconds. This is ignored if animate is
- * false.
+ * false.
* @param interpolator an optional time interpolator. If null is passed, last set interpolator
- * will be used. This is ignored if animate is false.
+ * will be used. This is ignored if animate is false.
*/
fun setTextStyle(
weight: Int = -1,
@@ -221,11 +237,10 @@
if (weight >= 0) {
// Paint#setFontVariationSettings creates Typeface instance from scratch. To reduce the
// memory impact, cache the typeface result.
- textInterpolator.targetPaint.typeface =
- typefaceCache.getOrElse(weight) {
- textInterpolator.targetPaint.fontVariationSettings = "'$TAG_WGHT' $weight"
- textInterpolator.targetPaint.typeface
- }
+ textInterpolator.targetPaint.typeface = typefaceCache.getOrElse(weight) {
+ textInterpolator.targetPaint.fontVariationSettings = "'$TAG_WGHT' $weight"
+ textInterpolator.targetPaint.typeface
+ }
}
if (color != null) {
textInterpolator.targetPaint.color = color
@@ -234,24 +249,22 @@
if (animate) {
animator.startDelay = delay
- animator.duration =
- if (duration == -1L) {
- DEFAULT_ANIMATION_DURATION
- } else {
- duration
- }
+ animator.duration = if (duration == -1L) {
+ DEFAULT_ANIMATION_DURATION
+ } else {
+ duration
+ }
interpolator?.let { animator.interpolator = it }
if (onAnimationEnd != null) {
- val listener =
- object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator?) {
- onAnimationEnd.run()
- animator.removeListener(this)
- }
- override fun onAnimationCancel(animation: Animator?) {
- animator.removeListener(this)
- }
+ val listener = object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator?) {
+ onAnimationEnd.run()
+ animator.removeListener(this)
}
+ override fun onAnimationCancel(animation: Animator?) {
+ animator.removeListener(this)
+ }
+ }
animator.addListener(listener)
}
animator.start()
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt
index f9fb42c..0448c81 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt
@@ -26,8 +26,12 @@
import com.android.internal.graphics.ColorUtils
import java.lang.Math.max
-/** Provide text style linear interpolation for plain text. */
-class TextInterpolator(layout: Layout) {
+/**
+ * Provide text style linear interpolation for plain text.
+ */
+class TextInterpolator(
+ layout: Layout
+) {
/**
* Returns base paint used for interpolation.
@@ -60,11 +64,12 @@
var baseFont: Font,
var targetFont: Font
) {
- val length: Int
- get() = end - start
+ val length: Int get() = end - start
}
- /** A class represents text layout of a single run. */
+ /**
+ * A class represents text layout of a single run.
+ */
private class Run(
val glyphIds: IntArray,
val baseX: FloatArray, // same length as glyphIds
@@ -74,8 +79,12 @@
val fontRuns: List<FontRun>
)
- /** A class represents text layout of a single line. */
- private class Line(val runs: List<Run>)
+ /**
+ * A class represents text layout of a single line.
+ */
+ private class Line(
+ val runs: List<Run>
+ )
private var lines = listOf<Line>()
private val fontInterpolator = FontInterpolator()
@@ -97,8 +106,8 @@
/**
* The layout used for drawing text.
*
- * Only non-styled text is supported. Even if the given layout is created from Spanned, the span
- * information is not used.
+ * Only non-styled text is supported. Even if the given layout is created from Spanned, the
+ * span information is not used.
*
* The paint objects used for interpolation are not changed by this method call.
*
@@ -121,8 +130,8 @@
/**
* Recalculate internal text layout for interpolation.
*
- * Whenever the target paint is modified, call this method to recalculate internal text layout
- * used for interpolation.
+ * Whenever the target paint is modified, call this method to recalculate internal
+ * text layout used for interpolation.
*/
fun onTargetPaintModified() {
updatePositionsAndFonts(shapeText(layout, targetPaint), updateBase = false)
@@ -131,8 +140,8 @@
/**
* Recalculate internal text layout for interpolation.
*
- * Whenever the base paint is modified, call this method to recalculate internal text layout
- * used for interpolation.
+ * Whenever the base paint is modified, call this method to recalculate internal
+ * text layout used for interpolation.
*/
fun onBasePaintModified() {
updatePositionsAndFonts(shapeText(layout, basePaint), updateBase = true)
@@ -143,11 +152,11 @@
*
* The text interpolator does not calculate all the text position by text shaper due to
* performance reasons. Instead, the text interpolator shape the start and end state and
- * calculate text position of the middle state by linear interpolation. Due to this trick, the
- * text positions of the middle state is likely different from the text shaper result. So, if
- * you want to start animation from the middle state, you will see the glyph jumps due to this
- * trick, i.e. the progress 0.5 of interpolation between weight 400 and 700 is different from
- * text shape result of weight 550.
+ * calculate text position of the middle state by linear interpolation. Due to this trick,
+ * the text positions of the middle state is likely different from the text shaper result.
+ * So, if you want to start animation from the middle state, you will see the glyph jumps due to
+ * this trick, i.e. the progress 0.5 of interpolation between weight 400 and 700 is different
+ * from text shape result of weight 550.
*
* After calling this method, do not call onBasePaintModified() since it reshape the text and
* update the base state. As in above notice, the text shaping result at current progress is
@@ -159,7 +168,8 @@
* animate weight from 200 to 400, then if you want to move back to 200 at the half of the
* animation, it will look like
*
- * ```
+ * <pre>
+ * <code>
* val interp = TextInterpolator(layout)
*
* // Interpolate between weight 200 to 400.
@@ -189,7 +199,9 @@
* // progress is 0.5
* animator.start()
* }
- * ```
+ * </code>
+ * </pre>
+ *
*/
fun rebase() {
if (progress == 0f) {
@@ -251,75 +263,69 @@
}
var maxRunLength = 0
- lines =
- baseLayout.zip(targetLayout) { baseLine, targetLine ->
- val runs =
- baseLine.zip(targetLine) { base, target ->
- require(base.glyphCount() == target.glyphCount()) {
- "Inconsistent glyph count at line ${lines.size}"
+ lines = baseLayout.zip(targetLayout) { baseLine, targetLine ->
+ val runs = baseLine.zip(targetLine) { base, target ->
+
+ require(base.glyphCount() == target.glyphCount()) {
+ "Inconsistent glyph count at line ${lines.size}"
+ }
+
+ val glyphCount = base.glyphCount()
+
+ // Good to recycle the array if the existing array can hold the new layout result.
+ val glyphIds = IntArray(glyphCount) {
+ base.getGlyphId(it).also { baseGlyphId ->
+ require(baseGlyphId == target.getGlyphId(it)) {
+ "Inconsistent glyph ID at $it in line ${lines.size}"
}
+ }
+ }
- val glyphCount = base.glyphCount()
+ val baseX = FloatArray(glyphCount) { base.getGlyphX(it) }
+ val baseY = FloatArray(glyphCount) { base.getGlyphY(it) }
+ val targetX = FloatArray(glyphCount) { target.getGlyphX(it) }
+ val targetY = FloatArray(glyphCount) { target.getGlyphY(it) }
- // Good to recycle the array if the existing array can hold the new layout
- // result.
- val glyphIds =
- IntArray(glyphCount) {
- base.getGlyphId(it).also { baseGlyphId ->
- require(baseGlyphId == target.getGlyphId(it)) {
- "Inconsistent glyph ID at $it in line ${lines.size}"
- }
- }
+ // Calculate font runs
+ val fontRun = mutableListOf<FontRun>()
+ if (glyphCount != 0) {
+ var start = 0
+ var baseFont = base.getFont(start)
+ var targetFont = target.getFont(start)
+ require(FontInterpolator.canInterpolate(baseFont, targetFont)) {
+ "Cannot interpolate font at $start ($baseFont vs $targetFont)"
+ }
+
+ for (i in 1 until glyphCount) {
+ val nextBaseFont = base.getFont(i)
+ val nextTargetFont = target.getFont(i)
+
+ if (baseFont !== nextBaseFont) {
+ require(targetFont !== nextTargetFont) {
+ "Base font has changed at $i but target font has not changed."
}
-
- val baseX = FloatArray(glyphCount) { base.getGlyphX(it) }
- val baseY = FloatArray(glyphCount) { base.getGlyphY(it) }
- val targetX = FloatArray(glyphCount) { target.getGlyphX(it) }
- val targetY = FloatArray(glyphCount) { target.getGlyphY(it) }
-
- // Calculate font runs
- val fontRun = mutableListOf<FontRun>()
- if (glyphCount != 0) {
- var start = 0
- var baseFont = base.getFont(start)
- var targetFont = target.getFont(start)
+ // Font transition point. push run and reset context.
+ fontRun.add(FontRun(start, i, baseFont, targetFont))
+ maxRunLength = max(maxRunLength, i - start)
+ baseFont = nextBaseFont
+ targetFont = nextTargetFont
+ start = i
require(FontInterpolator.canInterpolate(baseFont, targetFont)) {
"Cannot interpolate font at $start ($baseFont vs $targetFont)"
}
-
- for (i in 1 until glyphCount) {
- val nextBaseFont = base.getFont(i)
- val nextTargetFont = target.getFont(i)
-
- if (baseFont !== nextBaseFont) {
- require(targetFont !== nextTargetFont) {
- "Base font has changed at $i but target font has not " +
- "changed."
- }
- // Font transition point. push run and reset context.
- fontRun.add(FontRun(start, i, baseFont, targetFont))
- maxRunLength = max(maxRunLength, i - start)
- baseFont = nextBaseFont
- targetFont = nextTargetFont
- start = i
- require(FontInterpolator.canInterpolate(baseFont, targetFont)) {
- "Cannot interpolate font at $start ($baseFont vs " +
- "$targetFont)"
- }
- } else { // baseFont === nextBaseFont
- require(targetFont === nextTargetFont) {
- "Base font has not changed at $i but target font has " +
- "changed."
- }
- }
+ } else { // baseFont === nextBaseFont
+ require(targetFont === nextTargetFont) {
+ "Base font has not changed at $i but target font has changed."
}
- fontRun.add(FontRun(start, glyphCount, baseFont, targetFont))
- maxRunLength = max(maxRunLength, glyphCount - start)
}
- Run(glyphIds, baseX, baseY, targetX, targetY, fontRun)
}
- Line(runs)
+ fontRun.add(FontRun(start, glyphCount, baseFont, targetFont))
+ maxRunLength = max(maxRunLength, glyphCount - start)
+ }
+ Run(glyphIds, baseX, baseY, targetX, targetY, fontRun)
}
+ Line(runs)
+ }
// Update float array used for drawing.
if (tmpPositionArray.size < maxRunLength * 2) {
@@ -351,9 +357,9 @@
if (glyphFilter == null) {
for (i in run.start until run.end) {
tmpPositionArray[arrayIndex++] =
- MathUtils.lerp(line.baseX[i], line.targetX[i], progress)
+ MathUtils.lerp(line.baseX[i], line.targetX[i], progress)
tmpPositionArray[arrayIndex++] =
- MathUtils.lerp(line.baseY[i], line.targetY[i], progress)
+ MathUtils.lerp(line.baseY[i], line.targetY[i], progress)
}
c.drawGlyphs(line.glyphIds, run.start, tmpPositionArray, 0, run.length, font, paint)
return
@@ -382,14 +388,13 @@
tmpPaintForGlyph.color = tmpGlyph.color
c.drawGlyphs(
- line.glyphIds,
- prevStart,
- tmpPositionArray,
- 0,
- i - prevStart,
- font,
- tmpPaintForGlyph
- )
+ line.glyphIds,
+ prevStart,
+ tmpPositionArray,
+ 0,
+ i - prevStart,
+ font,
+ tmpPaintForGlyph)
prevStart = i
arrayIndex = 0
}
@@ -399,14 +404,13 @@
}
c.drawGlyphs(
- line.glyphIds,
- prevStart,
- tmpPositionArray,
- 0,
- run.end - prevStart,
- font,
- tmpPaintForGlyph
- )
+ line.glyphIds,
+ prevStart,
+ tmpPositionArray,
+ 0,
+ run.end - prevStart,
+ font,
+ tmpPaintForGlyph)
}
private fun updatePositionsAndFonts(
@@ -414,7 +418,9 @@
updateBase: Boolean
) {
// Update target positions with newly calculated text layout.
- check(layoutResult.size == lines.size) { "The new layout result has different line count." }
+ check(layoutResult.size == lines.size) {
+ "The new layout result has different line count."
+ }
lines.zip(layoutResult) { line, runs ->
line.runs.zip(runs) { lineRun, newGlyphs ->
@@ -430,7 +436,7 @@
}
require(newFont === newGlyphs.getFont(i)) {
"The new layout has different font run." +
- " $newFont vs ${newGlyphs.getFont(i)} at $i"
+ " $newFont vs ${newGlyphs.getFont(i)} at $i"
}
}
@@ -438,7 +444,7 @@
// check new font can be interpolatable with base font.
require(FontInterpolator.canInterpolate(newFont, run.baseFont)) {
"New font cannot be interpolated with existing font. $newFont," +
- " ${run.baseFont}"
+ " ${run.baseFont}"
}
if (updateBase) {
@@ -474,7 +480,10 @@
}
// Shape the text and stores the result to out argument.
- private fun shapeText(layout: Layout, paint: TextPaint): List<List<PositionedGlyphs>> {
+ private fun shapeText(
+ layout: Layout,
+ paint: TextPaint
+ ): List<List<PositionedGlyphs>> {
val out = mutableListOf<List<PositionedGlyphs>>()
for (lineNo in 0 until layout.lineCount) { // Shape all lines.
val lineStart = layout.getLineStart(lineNo)
@@ -486,13 +495,10 @@
}
val runs = mutableListOf<PositionedGlyphs>()
- TextShaper.shapeText(
- layout.text,
- lineStart,
- count,
- layout.textDirectionHeuristic,
- paint
- ) { _, _, glyphs, _ -> runs.add(glyphs) }
+ TextShaper.shapeText(layout.text, lineStart, count, layout.textDirectionHeuristic,
+ paint) { _, _, glyphs, _ ->
+ runs.add(glyphs)
+ }
out.add(runs)
}
return out
@@ -500,8 +506,8 @@
}
private fun Layout.getDrawOrigin(lineNo: Int) =
- if (getParagraphDirection(lineNo) == Layout.DIR_LEFT_TO_RIGHT) {
- getLineLeft(lineNo)
- } else {
- getLineRight(lineNo)
- }
+ if (getParagraphDirection(lineNo) == Layout.DIR_LEFT_TO_RIGHT) {
+ getLineLeft(lineNo)
+ } else {
+ getLineRight(lineNo)
+ }
diff --git a/packages/SystemUI/checks/Android.bp b/packages/SystemUI/checks/Android.bp
index 40580d2..d3f66e3 100644
--- a/packages/SystemUI/checks/Android.bp
+++ b/packages/SystemUI/checks/Android.bp
@@ -37,12 +37,6 @@
java_test_host {
name: "SystemUILintCheckerTest",
- // TODO(b/239881504): Since this test was written, Android
- // Lint was updated, and now includes classes that were
- // compiled for java 15. The soong build doesn't support
- // java 15 yet, so we can't compile against "lint". Disable
- // the test until java 15 is supported.
- enabled: false,
srcs: [
"tests/**/*.kt",
"tests/**/*.java",
@@ -59,5 +53,19 @@
],
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/packages/SystemUI/res/drawable/overlay_badge_background.xml b/packages/SystemUI/res/drawable/overlay_badge_background.xml
index 857632e..53122c1 100644
--- a/packages/SystemUI/res/drawable/overlay_badge_background.xml
+++ b/packages/SystemUI/res/drawable/overlay_badge_background.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright (C) 2020 The Android Open Source Project
+ ~ Copyright (C) 2022 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
@@ -14,8 +14,11 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
- android:shape="oval">
- <solid android:color="?androidprv:attr/colorSurface"/>
-</shape>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="48dp"
+ android:height="48dp"
+ android:viewportWidth="48.0"
+ android:viewportHeight="48.0">
+ <path
+ android:pathData="M0,0M48,48"/>
+</vector>
diff --git a/packages/SystemUI/res/layout/combined_qs_header.xml b/packages/SystemUI/res/layout/combined_qs_header.xml
index a565988..d689828 100644
--- a/packages/SystemUI/res/layout/combined_qs_header.xml
+++ b/packages/SystemUI/res/layout/combined_qs_header.xml
@@ -148,9 +148,4 @@
<include layout="@layout/ongoing_privacy_chip"/>
</FrameLayout>
- <Space
- android:layout_width="0dp"
- android:layout_height="0dp"
- android:id="@+id/space"
- />
</com.android.systemui.util.NoRemeasureMotionLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/screenshot_static.xml b/packages/SystemUI/res/layout/screenshot_static.xml
index 9fb84c8..496eb6e 100644
--- a/packages/SystemUI/res/layout/screenshot_static.xml
+++ b/packages/SystemUI/res/layout/screenshot_static.xml
@@ -93,13 +93,10 @@
app:layout_constraintBottom_toBottomOf="@id/screenshot_preview_border"/>
<ImageView
android:id="@+id/screenshot_badge"
- android:layout_width="24dp"
- android:layout_height="24dp"
- android:padding="4dp"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
android:visibility="gone"
- android:background="@drawable/overlay_badge_background"
android:elevation="8dp"
- android:src="@drawable/overlay_cancel"
app:layout_constraintBottom_toBottomOf="@id/screenshot_preview_border"
app:layout_constraintEnd_toEndOf="@id/screenshot_preview_border"/>
<FrameLayout
diff --git a/packages/SystemUI/res/xml/qs_header.xml b/packages/SystemUI/res/xml/qs_header.xml
index eca2b2a..d97031f 100644
--- a/packages/SystemUI/res/xml/qs_header.xml
+++ b/packages/SystemUI/res/xml/qs_header.xml
@@ -56,13 +56,9 @@
<Layout
android:layout_width="wrap_content"
android:layout_height="@dimen/new_qs_header_non_clickable_element_height"
- app:layout_constrainedWidth="true"
app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toStartOf="@id/space"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/carrier_group"
- app:layout_constraintHorizontal_bias="0"
- app:layout_constraintHorizontal_chainStyle="spread_inside"
/>
</Constraint>
@@ -87,39 +83,27 @@
<Constraint
android:id="@+id/statusIcons">
<Layout
- android:layout_width="wrap_content"
+ android:layout_width="0dp"
android:layout_height="@dimen/new_qs_header_non_clickable_element_height"
- app:layout_constraintStart_toEndOf="@id/space"
+ app:layout_constraintWidth_default="wrap"
+ app:layout_constraintStart_toEndOf="@id/date"
app:layout_constraintEnd_toStartOf="@id/batteryRemainingIcon"
app:layout_constraintTop_toTopOf="@id/date"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintHorizontal_bias="1"
+ app:layout_constraintBottom_toBottomOf="@id/date"
/>
</Constraint>
<Constraint
android:id="@+id/batteryRemainingIcon">
<Layout
- android:layout_width="wrap_content"
+ android:layout_width="0dp"
android:layout_height="@dimen/new_qs_header_non_clickable_element_height"
+ app:layout_constraintWidth_default="wrap"
app:layout_constraintHeight_min="@dimen/new_qs_header_non_clickable_element_height"
- app:layout_constraintStart_toEndOf="@id/statusIcons"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/date"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintHorizontal_bias="1"
- app:layout_constraintHorizontal_chainStyle="spread_inside"
+ app:layout_constraintBottom_toBottomOf="@id/date"
/>
</Constraint>
-
- <Constraint
- android:id="@id/space">
- <Layout
- android:layout_width="0dp"
- android:layout_height="0dp"
- app:layout_constraintStart_toEndOf="@id/date"
- app:layout_constraintEnd_toStartOf="@id/statusIcons"
- />
- </Constraint>
</ConstraintSet>
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java b/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
index 0fc9ef9..632fcdc 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
@@ -22,8 +22,6 @@
import android.os.HandlerThread;
import android.util.Log;
-import androidx.annotation.Nullable;
-
import com.android.systemui.dagger.GlobalRootComponent;
import com.android.systemui.dagger.SysUIComponent;
import com.android.systemui.dagger.WMComponent;
@@ -55,7 +53,6 @@
mContext = context;
}
- @Nullable
protected abstract GlobalRootComponent.Builder getGlobalRootComponentBuilder();
/**
@@ -72,11 +69,6 @@
* Starts the initialization process. This stands up the Dagger graph.
*/
public void init(boolean fromTest) throws ExecutionException, InterruptedException {
- GlobalRootComponent.Builder globalBuilder = getGlobalRootComponentBuilder();
- if (globalBuilder == null) {
- return;
- }
-
mRootComponent = getGlobalRootComponentBuilder()
.context(mContext)
.instrumentationTest(fromTest)
@@ -127,7 +119,6 @@
.setBackAnimation(Optional.ofNullable(null))
.setDesktopMode(Optional.ofNullable(null));
}
-
mSysUIComponent = builder.build();
if (initializeComponents) {
mSysUIComponent.init();
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIInitializerImpl.kt b/packages/SystemUI/src/com/android/systemui/SystemUIInitializerImpl.kt
index 55c095b..8aa3040 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIInitializerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIInitializerImpl.kt
@@ -16,7 +16,6 @@
package com.android.systemui
-import android.app.Application
import android.content.Context
import com.android.systemui.dagger.DaggerReferenceGlobalRootComponent
import com.android.systemui.dagger.GlobalRootComponent
@@ -25,17 +24,7 @@
* {@link SystemUIInitializer} that stands up AOSP SystemUI.
*/
class SystemUIInitializerImpl(context: Context) : SystemUIInitializer(context) {
-
- override fun getGlobalRootComponentBuilder(): GlobalRootComponent.Builder? {
- return when (Application.getProcessName()) {
- SCREENSHOT_CROSS_PROFILE_PROCESS -> null
- else -> DaggerReferenceGlobalRootComponent.builder()
- }
- }
-
- companion object {
- private const val SYSTEMUI_PROCESS = "com.android.systemui"
- private const val SCREENSHOT_CROSS_PROFILE_PROCESS =
- "$SYSTEMUI_PROCESS:screenshot_cross_profile"
+ override fun getGlobalRootComponentBuilder(): GlobalRootComponent.Builder {
+ return DaggerReferenceGlobalRootComponent.builder()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
index e8e1f2e..e9ac840 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
@@ -176,7 +176,8 @@
private @Classifier.InteractionType int mPriorInteractionType = Classifier.GENERIC;
@Inject
- public BrightLineFalsingManager(FalsingDataProvider falsingDataProvider,
+ public BrightLineFalsingManager(
+ FalsingDataProvider falsingDataProvider,
MetricsLogger metricsLogger,
@Named(BRIGHT_LINE_GESTURE_CLASSIFERS) Set<FalsingClassifier> classifiers,
SingleTapClassifier singleTapClassifier, LongTapClassifier longTapClassifier,
@@ -399,7 +400,9 @@
|| mDataProvider.isJustUnlockedWithFace()
|| mDataProvider.isDocked()
|| mAccessibilityManager.isTouchExplorationEnabled()
- || mDataProvider.isA11yAction();
+ || mDataProvider.isA11yAction()
+ || (mFeatureFlags.isEnabled(Flags.FALSING_OFF_FOR_UNFOLDED)
+ && !mDataProvider.isFolded());
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java
index 09ebeea..5f347c1 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java
@@ -16,6 +16,7 @@
package com.android.systemui.classifier;
+import android.hardware.devicestate.DeviceStateManager.FoldStateListener;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
import android.view.MotionEvent.PointerCoords;
@@ -42,6 +43,7 @@
private final int mWidthPixels;
private final int mHeightPixels;
private BatteryController mBatteryController;
+ private final FoldStateListener mFoldStateListener;
private final DockManager mDockManager;
private final float mXdpi;
private final float mYdpi;
@@ -65,12 +67,14 @@
public FalsingDataProvider(
DisplayMetrics displayMetrics,
BatteryController batteryController,
+ FoldStateListener foldStateListener,
DockManager dockManager) {
mXdpi = displayMetrics.xdpi;
mYdpi = displayMetrics.ydpi;
mWidthPixels = displayMetrics.widthPixels;
mHeightPixels = displayMetrics.heightPixels;
mBatteryController = batteryController;
+ mFoldStateListener = foldStateListener;
mDockManager = dockManager;
FalsingClassifier.logInfo("xdpi, ydpi: " + getXdpi() + ", " + getYdpi());
@@ -376,6 +380,10 @@
return mBatteryController.isWirelessCharging() || mDockManager.isDocked();
}
+ public boolean isFolded() {
+ return Boolean.TRUE.equals(mFoldStateListener.getFolded());
+ }
+
/** Implement to be alerted abotu the beginning and ending of falsing tracking. */
public interface SessionListener {
/** Called when the lock screen is shown and falsing-tracking begins. */
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 52da3f8..d040f8f 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -207,6 +207,9 @@
val AUTO_PIN_CONFIRMATION =
unreleasedFlag(224, "auto_pin_confirmation", "auto_pin_confirmation")
+ // TODO(b/262859270): Tracking Bug
+ @JvmField val FALSING_OFF_FOR_UNFOLDED = releasedFlag(225, "falsing_off_for_unfolded")
+
// 300 - power menu
// TODO(b/254512600): Tracking Bug
@JvmField val POWER_MENU_LITE = releasedFlag(300, "power_menu_lite")
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 6b121b8..18854e5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -41,6 +41,7 @@
import android.animation.ValueAnimator;
import android.app.ActivityTaskManager;
import android.app.AlarmManager;
+import android.app.BroadcastOptions;
import android.app.PendingIntent;
import android.app.StatusBarManager;
import android.app.WindowConfiguration;
@@ -391,6 +392,12 @@
| Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
| Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
+ private static final Bundle USER_PRESENT_INTENT_OPTIONS =
+ BroadcastOptions.makeBasic()
+ .setDeferUntilActive(true)
+ .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT)
+ .toBundle();
+
/**
* {@link #setKeyguardEnabled} waits on this condition when it re-enables
* the keyguard.
@@ -1921,13 +1928,23 @@
return;
}
- // if the keyguard is already showing, don't bother. check flags in both files
- // to account for the hiding animation which results in a delay and discrepancy
- // between flags
+ // If the keyguard is already showing, see if we don't need to bother re-showing it. Check
+ // flags in both files to account for the hiding animation which results in a delay and
+ // discrepancy between flags.
if (mShowing && mKeyguardStateController.isShowing()) {
- if (DEBUG) Log.d(TAG, "doKeyguard: not showing because it is already showing");
- resetStateLocked();
- return;
+ if (mPM.isInteractive()) {
+ // It's already showing, and we're not trying to show it while the screen is off.
+ // We can simply reset all of the views.
+ if (DEBUG) Log.d(TAG, "doKeyguard: not showing because it is already showing");
+ resetStateLocked();
+ return;
+ } else {
+ // We are trying to show the keyguard while the screen is off - this results from
+ // race conditions involving locking while unlocking. Don't short-circuit here and
+ // ensure the keyguard is fully re-shown.
+ Log.e(TAG,
+ "doKeyguard: already showing, but re-showing since we're not interactive");
+ }
}
// In split system user mode, we never unlock system user.
@@ -2319,7 +2336,10 @@
Context.USER_SERVICE);
mUiBgExecutor.execute(() -> {
for (int profileId : um.getProfileIdsWithDisabled(currentUser.getIdentifier())) {
- mContext.sendBroadcastAsUser(USER_PRESENT_INTENT, UserHandle.of(profileId));
+ mContext.sendBroadcastAsUser(USER_PRESENT_INTENT,
+ UserHandle.of(profileId),
+ null,
+ USER_PRESENT_INTENT_OPTIONS);
}
mLockPatternUtils.userPresent(currentUserId);
});
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
index 7cf63f6..1da30ad 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
@@ -36,7 +36,6 @@
void removeCallback(Callback callback);
void removeTile(String tileSpec);
void removeTiles(Collection<String> specs);
- void unmarkTileAsAutoAdded(String tileSpec);
int indexOf(String tileSpec);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
index cad296b..100853c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
@@ -427,11 +427,6 @@
mMainExecutor.execute(() -> changeTileSpecs(tileSpecs -> tileSpecs.removeAll(specs)));
}
- @Override
- public void unmarkTileAsAutoAdded(String spec) {
- if (mAutoTiles != null) mAutoTiles.unmarkTileAsAutoAdded(spec);
- }
-
/**
* Add a tile to the end
*
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
index a6c7781..72c6bfe 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
@@ -101,7 +101,6 @@
@MainThread
public void onManagedProfileRemoved() {
mHost.removeTile(getTileSpec());
- mHost.unmarkTileAsAutoAdded(getTileSpec());
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 91ebf79..b21a485 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -687,8 +687,8 @@
}
});
if (mFlags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY)) {
- mScreenshotView.badgeScreenshot(
- mContext.getPackageManager().getUserBadgeForDensity(owner, 0));
+ mScreenshotView.badgeScreenshot(mContext.getPackageManager().getUserBadgedIcon(
+ mContext.getDrawable(R.drawable.overlay_badge_background), owner));
}
mScreenshotView.setScreenshot(mScreenBitmap, screenInsets);
if (DEBUG_WINDOW) {
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
index 200288b..4dbe099 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
@@ -111,7 +111,7 @@
// These get called when a managed profile goes in or out of quiet mode.
addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE)
addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE)
-
+ addAction(Intent.ACTION_MANAGED_PROFILE_ADDED)
addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED)
addAction(Intent.ACTION_MANAGED_PROFILE_UNLOCKED)
}
@@ -128,6 +128,7 @@
Intent.ACTION_USER_INFO_CHANGED,
Intent.ACTION_MANAGED_PROFILE_AVAILABLE,
Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE,
+ Intent.ACTION_MANAGED_PROFILE_ADDED,
Intent.ACTION_MANAGED_PROFILE_REMOVED,
Intent.ACTION_MANAGED_PROFILE_UNLOCKED -> {
handleProfilesChanged()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
index 9070ead..149ec54 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
@@ -154,9 +154,7 @@
if (!mAutoTracker.isAdded(SAVER)) {
mDataSaverController.addCallback(mDataSaverListener);
}
- if (!mAutoTracker.isAdded(WORK)) {
- mManagedProfileController.addCallback(mProfileCallback);
- }
+ mManagedProfileController.addCallback(mProfileCallback);
if (!mAutoTracker.isAdded(NIGHT)
&& ColorDisplayManager.isNightDisplayAvailable(mContext)) {
mNightDisplayListener.setCallback(mNightDisplayCallback);
@@ -275,18 +273,18 @@
return mCurrentUser.getIdentifier();
}
- public void unmarkTileAsAutoAdded(String tabSpec) {
- mAutoTracker.setTileRemoved(tabSpec);
- }
-
private final ManagedProfileController.Callback mProfileCallback =
new ManagedProfileController.Callback() {
@Override
public void onManagedProfileChanged() {
- if (mAutoTracker.isAdded(WORK)) return;
if (mManagedProfileController.hasActiveProfile()) {
+ if (mAutoTracker.isAdded(WORK)) return;
mHost.addTile(WORK);
mAutoTracker.setTileAdded(WORK);
+ } else {
+ if (!mAutoTracker.isAdded(WORK)) return;
+ mHost.removeTile(WORK);
+ mAutoTracker.setTileRemoved(WORK);
}
}
@@ -429,7 +427,7 @@
initSafetyTile();
} else if (!isSafetyCenterEnabled && mAutoTracker.isAdded(mSafetySpec)) {
mHost.removeTile(mSafetySpec);
- mHost.unmarkTileAsAutoAdded(mSafetySpec);
+ mAutoTracker.setTileRemoved(mSafetySpec);
}
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt
index 59ad24a..2709da3 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt
@@ -17,6 +17,9 @@
package com.android.systemui.unfold
import android.content.Context
+import android.hardware.devicestate.DeviceStateManager
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.LifecycleScreenStatusProvider
import com.android.systemui.unfold.config.UnfoldTransitionConfig
import com.android.systemui.unfold.system.SystemUnfoldSharedModule
@@ -32,6 +35,7 @@
import dagger.Module
import dagger.Provides
import java.util.Optional
+import java.util.concurrent.Executor
import javax.inject.Named
import javax.inject.Singleton
@@ -40,6 +44,20 @@
@Provides @UnfoldTransitionATracePrefix fun tracingTagPrefix() = "systemui"
+ /** A globally available FoldStateListener that allows one to query the fold state. */
+ @Provides
+ @Singleton
+ fun providesFoldStateListener(
+ deviceStateManager: DeviceStateManager,
+ @Application context: Context,
+ @Main executor: Executor
+ ): DeviceStateManager.FoldStateListener {
+ val listener = DeviceStateManager.FoldStateListener(context)
+ deviceStateManager.registerCallback(executor, listener)
+
+ return listener
+ }
+
@Provides
@Singleton
fun providesFoldStateLoggingProvider(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java
index 0fadc13..e4df754 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java
@@ -106,6 +106,7 @@
mClassifiers.add(mClassifierB);
when(mFalsingDataProvider.getRecentMotionEvents()).thenReturn(mMotionEventList);
when(mKeyguardStateController.isShowing()).thenReturn(true);
+ when(mFalsingDataProvider.isFolded()).thenReturn(true);
mBrightLineFalsingManager = new BrightLineFalsingManager(mFalsingDataProvider,
mMetricsLogger, mClassifiers, mSingleTapClassfier, mLongTapClassifier,
mDoubleTapClassifier, mHistoryTracker, mKeyguardStateController,
@@ -121,6 +122,7 @@
mGestureFinalizedListener = gestureCompleteListenerCaptor.getValue();
mFakeFeatureFlags.set(Flags.FALSING_FOR_LONG_TAPS, true);
mFakeFeatureFlags.set(Flags.MEDIA_FALSING_PENALTY, true);
+ mFakeFeatureFlags.set(Flags.FALSING_OFF_FOR_UNFOLDED, true);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java
index 4281ee0..ae38eb6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java
@@ -89,25 +89,27 @@
mClassifiers.add(mClassifierA);
when(mFalsingDataProvider.getRecentMotionEvents()).thenReturn(mMotionEventList);
when(mKeyguardStateController.isShowing()).thenReturn(true);
+ when(mFalsingDataProvider.isFolded()).thenReturn(true);
mBrightLineFalsingManager = new BrightLineFalsingManager(mFalsingDataProvider,
mMetricsLogger, mClassifiers, mSingleTapClassifier, mLongTapClassifier,
mDoubleTapClassifier, mHistoryTracker, mKeyguardStateController,
mAccessibilityManager, false, mFakeFeatureFlags);
mFakeFeatureFlags.set(Flags.FALSING_FOR_LONG_TAPS, true);
+ mFakeFeatureFlags.set(Flags.FALSING_OFF_FOR_UNFOLDED, true);
}
@Test
public void testA11yDisablesGesture() {
- assertThat(mBrightLineFalsingManager.isFalseTap(1)).isTrue();
+ assertThat(mBrightLineFalsingManager.isFalseTouch(Classifier.GENERIC)).isTrue();
when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(true);
- assertThat(mBrightLineFalsingManager.isFalseTap(1)).isFalse();
+ assertThat(mBrightLineFalsingManager.isFalseTouch(Classifier.GENERIC)).isFalse();
}
@Test
public void testA11yDisablesTap() {
- assertThat(mBrightLineFalsingManager.isFalseTouch(Classifier.GENERIC)).isTrue();
+ assertThat(mBrightLineFalsingManager.isFalseTap(1)).isTrue();
when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(true);
- assertThat(mBrightLineFalsingManager.isFalseTouch(Classifier.GENERIC)).isFalse();
+ assertThat(mBrightLineFalsingManager.isFalseTap(1)).isFalse();
}
@@ -179,4 +181,11 @@
when(mFalsingDataProvider.isA11yAction()).thenReturn(true);
assertThat(mBrightLineFalsingManager.isFalseTap(1)).isFalse();
}
+
+ @Test
+ public void testSkipUnfolded() {
+ assertThat(mBrightLineFalsingManager.isFalseTouch(Classifier.GENERIC)).isTrue();
+ when(mFalsingDataProvider.isFolded()).thenReturn(false);
+ assertThat(mBrightLineFalsingManager.isFalseTouch(Classifier.GENERIC)).isFalse();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/ClassifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/ClassifierTest.java
index 5fa7214..94cf384 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/ClassifierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/ClassifierTest.java
@@ -16,6 +16,7 @@
package com.android.systemui.classifier;
+import android.hardware.devicestate.DeviceStateManager.FoldStateListener;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
@@ -38,6 +39,7 @@
private float mOffsetY = 0;
@Mock
private BatteryController mBatteryController;
+ private FoldStateListener mFoldStateListener = new FoldStateListener(mContext);
private final DockManagerFake mDockManager = new DockManagerFake();
public void setup() {
@@ -47,7 +49,8 @@
displayMetrics.ydpi = 100;
displayMetrics.widthPixels = 1000;
displayMetrics.heightPixels = 1000;
- mDataProvider = new FalsingDataProvider(displayMetrics, mBatteryController, mDockManager);
+ mDataProvider = new FalsingDataProvider(
+ displayMetrics, mBatteryController, mFoldStateListener, mDockManager);
}
@After
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java
index d315c2d..c451a1e7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java
@@ -24,6 +24,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.hardware.devicestate.DeviceStateManager.FoldStateListener;
import android.testing.AndroidTestingRunner;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
@@ -50,6 +51,8 @@
private FalsingDataProvider mDataProvider;
@Mock
private BatteryController mBatteryController;
+ @Mock
+ private FoldStateListener mFoldStateListener;
private final DockManagerFake mDockManager = new DockManagerFake();
@Before
@@ -61,7 +64,8 @@
displayMetrics.ydpi = 100;
displayMetrics.widthPixels = 1000;
displayMetrics.heightPixels = 1000;
- mDataProvider = new FalsingDataProvider(displayMetrics, mBatteryController, mDockManager);
+ mDataProvider = new FalsingDataProvider(
+ displayMetrics, mBatteryController, mFoldStateListener, mDockManager);
}
@After
@@ -316,4 +320,16 @@
mDataProvider.onA11yAction();
assertThat(mDataProvider.isA11yAction()).isTrue();
}
+
+ @Test
+ public void test_FoldedState_Folded() {
+ when(mFoldStateListener.getFolded()).thenReturn(true);
+ assertThat(mDataProvider.isFolded()).isTrue();
+ }
+
+ @Test
+ public void test_FoldedState_Unfolded() {
+ when(mFoldStateListener.getFolded()).thenReturn(false);
+ assertThat(mDataProvider.isFolded()).isFalse();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 59e4655..7c20e3c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -29,6 +29,7 @@
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -485,6 +486,38 @@
assertTrue(mViewMediator.isShowingAndNotOccluded());
}
+ @Test
+ @TestableLooper.RunWithLooper(setAsMainLooper = true)
+ public void testDoKeyguardWhileInteractive_resets() {
+ mViewMediator.setShowingLocked(true);
+ when(mKeyguardStateController.isShowing()).thenReturn(true);
+ TestableLooper.get(this).processAllMessages();
+
+ when(mPowerManager.isInteractive()).thenReturn(true);
+
+ mViewMediator.onSystemReady();
+ TestableLooper.get(this).processAllMessages();
+
+ assertTrue(mViewMediator.isShowingAndNotOccluded());
+ verify(mStatusBarKeyguardViewManager).reset(anyBoolean());
+ }
+
+ @Test
+ @TestableLooper.RunWithLooper(setAsMainLooper = true)
+ public void testDoKeyguardWhileNotInteractive_showsInsteadOfResetting() {
+ mViewMediator.setShowingLocked(true);
+ when(mKeyguardStateController.isShowing()).thenReturn(true);
+ TestableLooper.get(this).processAllMessages();
+
+ when(mPowerManager.isInteractive()).thenReturn(false);
+
+ mViewMediator.onSystemReady();
+ TestableLooper.get(this).processAllMessages();
+
+ assertTrue(mViewMediator.isShowingAndNotOccluded());
+ verify(mStatusBarKeyguardViewManager, never()).reset(anyBoolean());
+ }
+
private void createAndStartViewMediator() {
mViewMediator = new KeyguardViewMediator(
mContext,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplReceiveTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplReceiveTest.kt
new file mode 100644
index 0000000..3710281
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplReceiveTest.kt
@@ -0,0 +1,100 @@
+package com.android.systemui.settings
+
+import android.content.Context
+import android.content.Intent
+import android.content.pm.UserInfo
+import android.os.Handler
+import android.os.UserHandle
+import android.os.UserManager
+import androidx.concurrent.futures.DirectExecutor
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.capture
+import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.Executor
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(Parameterized::class)
+class UserTrackerImplReceiveTest : SysuiTestCase() {
+
+ companion object {
+
+ @JvmStatic
+ @Parameterized.Parameters
+ fun data(): Iterable<String> =
+ listOf(
+ Intent.ACTION_USER_INFO_CHANGED,
+ Intent.ACTION_MANAGED_PROFILE_AVAILABLE,
+ Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE,
+ Intent.ACTION_MANAGED_PROFILE_ADDED,
+ Intent.ACTION_MANAGED_PROFILE_REMOVED,
+ Intent.ACTION_MANAGED_PROFILE_UNLOCKED
+ )
+ }
+
+ private val executor: Executor = DirectExecutor.INSTANCE
+
+ @Mock private lateinit var context: Context
+ @Mock private lateinit var userManager: UserManager
+ @Mock(stubOnly = true) private lateinit var dumpManager: DumpManager
+ @Mock(stubOnly = true) private lateinit var handler: Handler
+
+ @Parameterized.Parameter lateinit var intentAction: String
+ @Mock private lateinit var callback: UserTracker.Callback
+ @Captor private lateinit var captor: ArgumentCaptor<List<UserInfo>>
+
+ private lateinit var tracker: UserTrackerImpl
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ `when`(context.user).thenReturn(UserHandle.SYSTEM)
+ `when`(context.createContextAsUser(ArgumentMatchers.any(), anyInt())).thenReturn(context)
+
+ tracker = UserTrackerImpl(context, userManager, dumpManager, handler)
+ }
+
+ @Test
+ fun `calls callback and updates profiles when an intent received`() {
+ tracker.initialize(0)
+ tracker.addCallback(callback, executor)
+ val profileID = tracker.userId + 10
+
+ `when`(userManager.getProfiles(anyInt())).thenAnswer { invocation ->
+ val id = invocation.getArgument<Int>(0)
+ val info = UserInfo(id, "", UserInfo.FLAG_FULL)
+ val infoProfile =
+ UserInfo(
+ id + 10,
+ "",
+ "",
+ UserInfo.FLAG_MANAGED_PROFILE,
+ UserManager.USER_TYPE_PROFILE_MANAGED
+ )
+ infoProfile.profileGroupId = id
+ listOf(info, infoProfile)
+ }
+
+ tracker.onReceive(context, Intent(intentAction))
+
+ verify(callback, times(0)).onUserChanged(anyInt(), any())
+ verify(callback, times(1)).onProfilesChanged(capture(captor))
+ assertThat(captor.value.map { it.id }).containsExactly(0, profileID)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt
index 52462c7..e65bbb1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt
@@ -124,6 +124,16 @@
verify(context).registerReceiverForAllUsers(
eq(tracker), capture(captor), isNull(), eq(handler))
+ with(captor.value) {
+ assertThat(countActions()).isEqualTo(7)
+ assertThat(hasAction(Intent.ACTION_USER_SWITCHED)).isTrue()
+ assertThat(hasAction(Intent.ACTION_USER_INFO_CHANGED)).isTrue()
+ assertThat(hasAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE)).isTrue()
+ assertThat(hasAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE)).isTrue()
+ assertThat(hasAction(Intent.ACTION_MANAGED_PROFILE_ADDED)).isTrue()
+ assertThat(hasAction(Intent.ACTION_MANAGED_PROFILE_REMOVED)).isTrue()
+ assertThat(hasAction(Intent.ACTION_MANAGED_PROFILE_UNLOCKED)).isTrue()
+ }
}
@Test
@@ -280,37 +290,6 @@
}
@Test
- fun testCallbackCalledOnProfileChanged() {
- tracker.initialize(0)
- val callback = TestCallback()
- tracker.addCallback(callback, executor)
- val profileID = tracker.userId + 10
-
- `when`(userManager.getProfiles(anyInt())).thenAnswer { invocation ->
- val id = invocation.getArgument<Int>(0)
- val info = UserInfo(id, "", UserInfo.FLAG_FULL)
- val infoProfile = UserInfo(
- id + 10,
- "",
- "",
- UserInfo.FLAG_MANAGED_PROFILE,
- UserManager.USER_TYPE_PROFILE_MANAGED
- )
- infoProfile.profileGroupId = id
- listOf(info, infoProfile)
- }
-
- val intent = Intent(Intent.ACTION_MANAGED_PROFILE_AVAILABLE)
- .putExtra(Intent.EXTRA_USER, UserHandle.of(profileID))
-
- tracker.onReceive(context, intent)
-
- assertThat(callback.calledOnUserChanged).isEqualTo(0)
- assertThat(callback.calledOnProfilesChanged).isEqualTo(1)
- assertThat(callback.lastUserProfiles.map { it.id }).containsExactly(0, profileID)
- }
-
- @Test
fun testCallbackCalledOnUserInfoChanged() {
tracker.initialize(0)
val callback = TestCallback()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt
index 88651c1..f802a5e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.shade
import android.testing.AndroidTestingRunner
+import android.view.ViewGroup
import androidx.constraintlayout.widget.ConstraintSet
import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
import androidx.constraintlayout.widget.ConstraintSet.START
@@ -92,12 +93,12 @@
assertThat(getConstraint(R.id.clock).layout.horizontalBias).isEqualTo(0f)
assertThat(getConstraint(R.id.date).layout.startToStart).isEqualTo(PARENT_ID)
- assertThat(getConstraint(R.id.date).layout.horizontalBias).isEqualTo(0f)
+ assertThat(getConstraint(R.id.date).layout.horizontalBias).isEqualTo(0.5f)
assertThat(getConstraint(R.id.batteryRemainingIcon).layout.endToEnd)
.isEqualTo(PARENT_ID)
assertThat(getConstraint(R.id.batteryRemainingIcon).layout.horizontalBias)
- .isEqualTo(1f)
+ .isEqualTo(0.5f)
assertThat(getConstraint(R.id.privacy_container).layout.endToEnd)
.isEqualTo(R.id.end_guide)
@@ -331,10 +332,8 @@
val views = mapOf(
R.id.clock to "clock",
R.id.date to "date",
- R.id.statusIcons to "icons",
R.id.privacy_container to "privacy",
R.id.carrier_group to "carriers",
- R.id.batteryRemainingIcon to "battery",
)
views.forEach { (id, name) ->
assertWithMessage("$name has 0 height in qqs")
@@ -352,11 +351,8 @@
fun testCheckViewsDontChangeSizeBetweenAnimationConstraints() {
val views = mapOf(
R.id.clock to "clock",
- R.id.date to "date",
- R.id.statusIcons to "icons",
R.id.privacy_container to "privacy",
R.id.carrier_group to "carriers",
- R.id.batteryRemainingIcon to "battery",
)
views.forEach { (id, name) ->
expect.withMessage("$name changes height")
@@ -369,8 +365,8 @@
}
private fun Int.fromConstraint() = when (this) {
- -1 -> "MATCH_PARENT"
- -2 -> "WRAP_CONTENT"
+ ViewGroup.LayoutParams.MATCH_PARENT -> "MATCH_PARENT"
+ ViewGroup.LayoutParams.WRAP_CONTENT -> "WRAP_CONTENT"
else -> toString()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
index 4ccbc6d..091bb54 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
@@ -24,6 +24,7 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNotNull;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.doReturn;
@@ -74,6 +75,7 @@
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;
+import org.mockito.stubbing.Answer;
import java.util.Collections;
import java.util.List;
@@ -115,8 +117,10 @@
@Spy private PackageManager mPackageManager;
private final boolean mIsReduceBrightColorsAvailable = true;
- private AutoTileManager mAutoTileManager;
+ private AutoTileManager mAutoTileManager; // under test
+
private SecureSettings mSecureSettings;
+ private ManagedProfileController.Callback mManagedProfileCallback;
@Before
public void setUp() throws Exception {
@@ -303,7 +307,7 @@
InOrder inOrderManagedProfile = inOrder(mManagedProfileController);
inOrderManagedProfile.verify(mManagedProfileController).removeCallback(any());
- inOrderManagedProfile.verify(mManagedProfileController, never()).addCallback(any());
+ inOrderManagedProfile.verify(mManagedProfileController).addCallback(any());
if (ColorDisplayManager.isNightDisplayAvailable(mContext)) {
InOrder inOrderNightDisplay = inOrder(mNightDisplayListener);
@@ -504,6 +508,40 @@
}
@Test
+ public void managedProfileAdded_tileAdded() {
+ when(mAutoAddTracker.isAdded(eq("work"))).thenReturn(false);
+ mAutoTileManager = createAutoTileManager(mContext);
+ Mockito.doAnswer((Answer<Object>) invocation -> {
+ mManagedProfileCallback = invocation.getArgument(0);
+ return null;
+ }).when(mManagedProfileController).addCallback(any());
+ mAutoTileManager.init();
+ when(mManagedProfileController.hasActiveProfile()).thenReturn(true);
+
+ mManagedProfileCallback.onManagedProfileChanged();
+
+ verify(mQsTileHost, times(1)).addTile(eq("work"));
+ verify(mAutoAddTracker, times(1)).setTileAdded(eq("work"));
+ }
+
+ @Test
+ public void managedProfileRemoved_tileRemoved() {
+ when(mAutoAddTracker.isAdded(eq("work"))).thenReturn(true);
+ mAutoTileManager = createAutoTileManager(mContext);
+ Mockito.doAnswer((Answer<Object>) invocation -> {
+ mManagedProfileCallback = invocation.getArgument(0);
+ return null;
+ }).when(mManagedProfileController).addCallback(any());
+ mAutoTileManager.init();
+ when(mManagedProfileController.hasActiveProfile()).thenReturn(false);
+
+ mManagedProfileCallback.onManagedProfileChanged();
+
+ verify(mQsTileHost, times(1)).removeTile(eq("work"));
+ verify(mAutoAddTracker, times(1)).setTileRemoved(eq("work"));
+ }
+
+ @Test
public void testEmptyArray_doesNotCrash() {
mContext.getOrCreateTestableResources().addOverride(
R.array.config_quickSettingsAutoAdd, new String[0]);
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index 71a4c73..b41664f 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -189,15 +189,20 @@
private long mLastBatteryLevelChangedSentMs;
private Bundle mBatteryChangedOptions = BroadcastOptions.makeRemovingMatchingFilter(
- new IntentFilter(Intent.ACTION_BATTERY_CHANGED)).toBundle();
+ new IntentFilter(Intent.ACTION_BATTERY_CHANGED)).setDeferUntilActive(true)
+ .toBundle();
private Bundle mPowerConnectedOptions = BroadcastOptions.makeRemovingMatchingFilter(
- new IntentFilter(Intent.ACTION_POWER_DISCONNECTED)).toBundle();
+ new IntentFilter(Intent.ACTION_POWER_DISCONNECTED)).setDeferUntilActive(true)
+ .toBundle();
private Bundle mPowerDisconnectedOptions = BroadcastOptions.makeRemovingMatchingFilter(
- new IntentFilter(Intent.ACTION_POWER_CONNECTED)).toBundle();
+ new IntentFilter(Intent.ACTION_POWER_CONNECTED)).setDeferUntilActive(true)
+ .toBundle();
private Bundle mBatteryLowOptions = BroadcastOptions.makeRemovingMatchingFilter(
- new IntentFilter(Intent.ACTION_BATTERY_OKAY)).toBundle();
+ new IntentFilter(Intent.ACTION_BATTERY_OKAY)).setDeferUntilActive(true)
+ .toBundle();
private Bundle mBatteryOkayOptions = BroadcastOptions.makeRemovingMatchingFilter(
- new IntentFilter(Intent.ACTION_BATTERY_LOW)).toBundle();
+ new IntentFilter(Intent.ACTION_BATTERY_LOW)).setDeferUntilActive(true)
+ .toBundle();
private MetricsLogger mMetricsLogger;
diff --git a/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java
index 819c948..68722d2 100644
--- a/services/core/java/com/android/server/BinaryTransparencyService.java
+++ b/services/core/java/com/android/server/BinaryTransparencyService.java
@@ -531,27 +531,30 @@
pw.println("|--> Pre-installed package install location: "
+ origPackageFilepath);
- if (useSha256) {
- String sha256Digest = PackageUtils.computeSha256DigestForLargeFile(
- origPackageFilepath, PackageUtils.createLargeFileBuffer());
- pw.println("|--> Pre-installed package SHA-256 digest: "
- + sha256Digest);
- }
+ if (!origPackageFilepath.equals(APEX_PRELOAD_LOCATION_ERROR)) {
+ if (useSha256) {
+ String sha256Digest = PackageUtils.computeSha256DigestForLargeFile(
+ origPackageFilepath, PackageUtils.createLargeFileBuffer());
+ pw.println("|--> Pre-installed package SHA-256 digest: "
+ + sha256Digest);
+ }
-
- Map<Integer, byte[]> contentDigests = computeApkContentDigest(
- origPackageFilepath);
- if (contentDigests == null) {
- pw.println("ERROR: Failed to compute package content digest for "
- + origPackageFilepath);
- } else {
- for (Map.Entry<Integer, byte[]> entry : contentDigests.entrySet()) {
- Integer algorithmId = entry.getKey();
- byte[] contentDigest = entry.getValue();
- pw.println("|--> Pre-installed package content digest: "
- + HexEncoding.encodeToString(contentDigest, false));
- pw.println("|--> Pre-installed package content digest algorithm: "
- + translateContentDigestAlgorithmIdToString(algorithmId));
+ Map<Integer, byte[]> contentDigests = computeApkContentDigest(
+ origPackageFilepath);
+ if (contentDigests == null) {
+ pw.println("|--> ERROR: Failed to compute package content digest "
+ + "for " + origPackageFilepath);
+ } else {
+ for (Map.Entry<Integer, byte[]> entry : contentDigests.entrySet()) {
+ Integer algorithmId = entry.getKey();
+ byte[] contentDigest = entry.getValue();
+ pw.println("|--> Pre-installed package content digest: "
+ + HexEncoding.encodeToString(contentDigest, false));
+ pw.println("|--> Pre-installed package content digest "
+ + "algorithm: "
+ + translateContentDigestAlgorithmIdToString(
+ algorithmId));
+ }
}
}
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index d478dca..1bc312e 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -159,6 +159,8 @@
import android.Manifest.permission;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.PermissionMethod;
+import android.annotation.PermissionName;
import android.annotation.UserIdInt;
import android.app.Activity;
import android.app.ActivityClient;
@@ -251,8 +253,6 @@
import android.content.pm.PackageManagerInternal;
import android.content.pm.ParceledListSlice;
import android.content.pm.PermissionInfo;
-import android.content.pm.PermissionMethod;
-import android.content.pm.PermissionName;
import android.content.pm.ProcessInfo;
import android.content.pm.ProviderInfo;
import android.content.pm.ProviderInfoList;
@@ -14108,8 +14108,16 @@
if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST,
(sticky ? "Broadcast sticky: ": "Broadcast: ") + intent
+ " ordered=" + ordered + " userid=" + userId);
- if ((resultTo != null) && !ordered && !mEnableModernQueue) {
- Slog.w(TAG, "Broadcast " + intent + " not ordered but result callback requested!");
+ if ((resultTo != null) && !ordered) {
+ if (!mEnableModernQueue) {
+ Slog.w(TAG, "Broadcast " + intent + " not ordered but result callback requested!");
+ }
+ if (!UserHandle.isCore(callingUid)) {
+ String msg = "Unauthorized unordered resultTo broadcast "
+ + intent + " sent from uid " + callingUid;
+ Slog.w(TAG, msg);
+ throw new SecurityException(msg);
+ }
}
userId = mUserController.handleIncomingUser(callingPid, callingUid, userId, true,
@@ -14190,6 +14198,18 @@
}
}
+ // resultTo broadcasts are always infinitely deferrable.
+ if ((resultTo != null) && !ordered && mEnableModernQueue) {
+ if (brOptions == null) {
+ brOptions = BroadcastOptions.makeBasic();
+ }
+ brOptions.setDeferUntilActive(true);
+ }
+
+ if (ordered && brOptions != null && brOptions.isDeferUntilActive()) {
+ throw new IllegalArgumentException("Ordered broadcasts can't be deferred until active");
+ }
+
// Verify that protected broadcasts are only being sent by system code,
// and that system code is only sending protected broadcasts.
final boolean isProtectedBroadcast;
diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
index fa7748b..41d9a11 100644
--- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
@@ -62,6 +62,7 @@
*/
// @NotThreadSafe
class BroadcastProcessQueue {
+ static final boolean VERBOSE = false;
final @NonNull BroadcastConstants constants;
final @NonNull String processName;
final int uid;
@@ -168,10 +169,14 @@
/**
* Count of pending broadcasts of these various flavors.
*/
+ private int mCountEnqueued;
+ private int mCountDeferred;
private int mCountForeground;
+ private int mCountForegroundDeferred;
private int mCountOrdered;
private int mCountAlarm;
private int mCountPrioritized;
+ private int mCountPrioritizedDeferred;
private int mCountInteractive;
private int mCountResultTo;
private int mCountInstrumented;
@@ -226,10 +231,10 @@
* used for ordered broadcasts and priority traunches.
*/
public void enqueueOrReplaceBroadcast(@NonNull BroadcastRecord record, int recordIndex,
- @NonNull BroadcastConsumer replacedBroadcastConsumer) {
+ @NonNull BroadcastConsumer replacedBroadcastConsumer, boolean wouldBeSkipped) {
if (record.isReplacePending()) {
final boolean didReplace = replaceBroadcast(record, recordIndex,
- replacedBroadcastConsumer);
+ replacedBroadcastConsumer, wouldBeSkipped);
if (didReplace) {
return;
}
@@ -240,13 +245,14 @@
SomeArgs newBroadcastArgs = SomeArgs.obtain();
newBroadcastArgs.arg1 = record;
newBroadcastArgs.argi1 = recordIndex;
+ newBroadcastArgs.argi2 = (wouldBeSkipped ? 1 : 0);
// Cross-broadcast prioritization policy: some broadcasts might warrant being
// issued ahead of others that are already pending, for example if this new
// broadcast is in a different delivery class or is tied to a direct user interaction
// with implicit responsiveness expectations.
getQueueForBroadcast(record).addLast(newBroadcastArgs);
- onBroadcastEnqueued(record, recordIndex);
+ onBroadcastEnqueued(record, recordIndex, wouldBeSkipped);
}
/**
@@ -258,11 +264,12 @@
* {@code false} otherwise.
*/
private boolean replaceBroadcast(@NonNull BroadcastRecord record, int recordIndex,
- @NonNull BroadcastConsumer replacedBroadcastConsumer) {
+ @NonNull BroadcastConsumer replacedBroadcastConsumer, boolean wouldBeSkipped) {
final int count = mPendingQueues.size();
for (int i = 0; i < count; ++i) {
final ArrayDeque<SomeArgs> queue = mPendingQueues.get(i);
- if (replaceBroadcastInQueue(queue, record, recordIndex, replacedBroadcastConsumer)) {
+ if (replaceBroadcastInQueue(queue, record, recordIndex,
+ replacedBroadcastConsumer, wouldBeSkipped)) {
return true;
}
}
@@ -279,13 +286,15 @@
*/
private boolean replaceBroadcastInQueue(@NonNull ArrayDeque<SomeArgs> queue,
@NonNull BroadcastRecord record, int recordIndex,
- @NonNull BroadcastConsumer replacedBroadcastConsumer) {
+ @NonNull BroadcastConsumer replacedBroadcastConsumer,
+ boolean wouldBeSkipped) {
final Iterator<SomeArgs> it = queue.descendingIterator();
final Object receiver = record.receivers.get(recordIndex);
while (it.hasNext()) {
final SomeArgs args = it.next();
final BroadcastRecord testRecord = (BroadcastRecord) args.arg1;
final int testRecordIndex = args.argi1;
+ final boolean testWouldBeSkipped = (args.argi2 == 1);
final Object testReceiver = testRecord.receivers.get(testRecordIndex);
if ((record.callingUid == testRecord.callingUid)
&& (record.userId == testRecord.userId)
@@ -295,9 +304,10 @@
// Exact match found; perform in-place swap
args.arg1 = record;
args.argi1 = recordIndex;
+ args.argi2 = (wouldBeSkipped ? 1 : 0);
record.copyEnqueueTimeFrom(testRecord);
- onBroadcastDequeued(testRecord, testRecordIndex);
- onBroadcastEnqueued(record, recordIndex);
+ onBroadcastDequeued(testRecord, testRecordIndex, testWouldBeSkipped);
+ onBroadcastEnqueued(record, recordIndex, wouldBeSkipped);
replacedBroadcastConsumer.accept(testRecord, testRecordIndex);
return true;
}
@@ -352,12 +362,13 @@
final SomeArgs args = it.next();
final BroadcastRecord record = (BroadcastRecord) args.arg1;
final int recordIndex = args.argi1;
+ final boolean recordWouldBeSkipped = (args.argi2 == 1);
if (predicate.test(record, recordIndex)) {
consumer.accept(record, recordIndex);
if (andRemove) {
args.recycle();
it.remove();
- onBroadcastDequeued(record, recordIndex);
+ onBroadcastDequeued(record, recordIndex, recordWouldBeSkipped);
}
didSomething = true;
}
@@ -423,7 +434,7 @@
}
public int getPreferredSchedulingGroupLocked() {
- if (mCountForeground > 0) {
+ if (mCountForeground > mCountForegroundDeferred) {
// We have a foreground broadcast somewhere down the queue, so
// boost priority until we drain them all
return ProcessList.SCHED_GROUP_DEFAULT;
@@ -469,10 +480,11 @@
final SomeArgs next = removeNextBroadcast();
mActive = (BroadcastRecord) next.arg1;
mActiveIndex = next.argi1;
+ final boolean wouldBeSkipped = (next.argi2 == 1);
mActiveCountSinceIdle++;
mActiveViaColdStart = false;
next.recycle();
- onBroadcastDequeued(mActive, mActiveIndex);
+ onBroadcastDequeued(mActive, mActiveIndex, wouldBeSkipped);
}
/**
@@ -489,8 +501,16 @@
/**
* Update summary statistics when the given record has been enqueued.
*/
- private void onBroadcastEnqueued(@NonNull BroadcastRecord record, int recordIndex) {
+ private void onBroadcastEnqueued(@NonNull BroadcastRecord record, int recordIndex,
+ boolean wouldBeSkipped) {
+ mCountEnqueued++;
+ if (record.deferUntilActive) {
+ mCountDeferred++;
+ }
if (record.isForeground()) {
+ if (record.deferUntilActive) {
+ mCountForegroundDeferred++;
+ }
mCountForeground++;
}
if (record.ordered) {
@@ -500,6 +520,9 @@
mCountAlarm++;
}
if (record.prioritized) {
+ if (record.deferUntilActive) {
+ mCountPrioritizedDeferred++;
+ }
mCountPrioritized++;
}
if (record.interactive) {
@@ -511,7 +534,8 @@
if (record.callerInstrumented) {
mCountInstrumented++;
}
- if (record.receivers.get(recordIndex) instanceof ResolveInfo) {
+ if (!wouldBeSkipped
+ && (record.receivers.get(recordIndex) instanceof ResolveInfo)) {
mCountManifest++;
}
invalidateRunnableAt();
@@ -520,8 +544,16 @@
/**
* Update summary statistics when the given record has been dequeued.
*/
- private void onBroadcastDequeued(@NonNull BroadcastRecord record, int recordIndex) {
+ private void onBroadcastDequeued(@NonNull BroadcastRecord record, int recordIndex,
+ boolean wouldBeSkipped) {
+ mCountEnqueued--;
+ if (record.deferUntilActive) {
+ mCountDeferred--;
+ }
if (record.isForeground()) {
+ if (record.deferUntilActive) {
+ mCountForegroundDeferred--;
+ }
mCountForeground--;
}
if (record.ordered) {
@@ -531,6 +563,9 @@
mCountAlarm--;
}
if (record.prioritized) {
+ if (record.deferUntilActive) {
+ mCountPrioritizedDeferred--;
+ }
mCountPrioritized--;
}
if (record.interactive) {
@@ -542,7 +577,8 @@
if (record.callerInstrumented) {
mCountInstrumented--;
}
- if (record.receivers.get(recordIndex) instanceof ResolveInfo) {
+ if (!wouldBeSkipped
+ && (record.receivers.get(recordIndex) instanceof ResolveInfo)) {
mCountManifest--;
}
invalidateRunnableAt();
@@ -741,6 +777,15 @@
return mRunnableAt != Long.MAX_VALUE;
}
+ public boolean isDeferredUntilActive() {
+ if (mRunnableAtInvalidated) updateRunnableAt();
+ return mRunnableAtReason == BroadcastProcessQueue.REASON_CACHED_INFINITE_DEFER;
+ }
+
+ public boolean hasDeferredBroadcasts() {
+ return (mCountDeferred > 0);
+ }
+
/**
* Return time at which this process is considered runnable. This is
* typically the time at which the next pending broadcast was first
@@ -776,6 +821,7 @@
static final int REASON_INSTRUMENTED = 5;
static final int REASON_PERSISTENT = 6;
static final int REASON_FORCE_DELAYED = 7;
+ static final int REASON_CACHED_INFINITE_DEFER = 8;
static final int REASON_CONTAINS_FOREGROUND = 10;
static final int REASON_CONTAINS_ORDERED = 11;
static final int REASON_CONTAINS_ALARM = 12;
@@ -794,6 +840,7 @@
REASON_INSTRUMENTED,
REASON_PERSISTENT,
REASON_FORCE_DELAYED,
+ REASON_CACHED_INFINITE_DEFER,
REASON_CONTAINS_FOREGROUND,
REASON_CONTAINS_ORDERED,
REASON_CONTAINS_ALARM,
@@ -816,6 +863,7 @@
case REASON_INSTRUMENTED: return "INSTRUMENTED";
case REASON_PERSISTENT: return "PERSISTENT";
case REASON_FORCE_DELAYED: return "FORCE_DELAYED";
+ case REASON_CACHED_INFINITE_DEFER: return "INFINITE_DEFER";
case REASON_CONTAINS_FOREGROUND: return "CONTAINS_FOREGROUND";
case REASON_CONTAINS_ORDERED: return "CONTAINS_ORDERED";
case REASON_CONTAINS_ALARM: return "CONTAINS_ALARM";
@@ -831,9 +879,16 @@
private boolean blockedOnOrderedDispatch(BroadcastRecord r, int index) {
final int blockedUntilTerminalCount = r.blockedUntilTerminalCount[index];
+ int existingDeferredCount = 0;
+ if (r.deferUntilActive) {
+ for (int i = 0; i < index; i++) {
+ if (r.deferredUntilActive[i]) existingDeferredCount++;
+ }
+ }
+
// We might be blocked waiting for other receivers to finish,
// typically for an ordered broadcast or priority traunches
- if (r.terminalCount < blockedUntilTerminalCount
+ if ((r.terminalCount + existingDeferredCount) < blockedUntilTerminalCount
&& !isDeliveryStateTerminal(r.getDeliveryState(index))) {
return true;
}
@@ -862,7 +917,7 @@
if (mForcedDelayedDurationMs > 0) {
mRunnableAt = runnableAt + mForcedDelayedDurationMs;
mRunnableAtReason = REASON_FORCE_DELAYED;
- } else if (mCountForeground > 0) {
+ } else if (mCountForeground > mCountForegroundDeferred) {
mRunnableAt = runnableAt + constants.DELAY_URGENT_MILLIS;
mRunnableAtReason = REASON_CONTAINS_FOREGROUND;
} else if (mCountInteractive > 0) {
@@ -880,12 +935,9 @@
} else if (mCountAlarm > 0) {
mRunnableAt = runnableAt;
mRunnableAtReason = REASON_CONTAINS_ALARM;
- } else if (mCountPrioritized > 0) {
+ } else if (mCountPrioritized > mCountPrioritizedDeferred) {
mRunnableAt = runnableAt;
mRunnableAtReason = REASON_CONTAINS_PRIORITIZED;
- } else if (mCountResultTo > 0) {
- mRunnableAt = runnableAt;
- mRunnableAtReason = REASON_CONTAINS_RESULT_TO;
} else if (mCountManifest > 0) {
mRunnableAt = runnableAt;
mRunnableAtReason = REASON_CONTAINS_MANIFEST;
@@ -893,8 +945,39 @@
mRunnableAt = runnableAt;
mRunnableAtReason = REASON_PERSISTENT;
} else if (mProcessCached) {
- mRunnableAt = runnableAt + constants.DELAY_CACHED_MILLIS;
- mRunnableAtReason = REASON_CACHED;
+ if (r.deferUntilActive) {
+ // All enqueued broadcasts are deferrable, defer
+ if (mCountDeferred == mCountEnqueued) {
+ mRunnableAt = Long.MAX_VALUE;
+ mRunnableAtReason = REASON_CACHED_INFINITE_DEFER;
+ } else {
+ // At least one enqueued broadcast isn't deferrable, repick time and reason
+ // for this record. If a later record is not deferrable and is one of these
+ // special cases, one of the cases above would have already caught that.
+ if (r.isForeground()) {
+ mRunnableAt = runnableAt + constants.DELAY_URGENT_MILLIS;
+ mRunnableAtReason = REASON_CONTAINS_FOREGROUND;
+ } else if (r.prioritized) {
+ mRunnableAt = runnableAt;
+ mRunnableAtReason = REASON_CONTAINS_PRIORITIZED;
+ } else if (r.resultTo != null) {
+ mRunnableAt = runnableAt;
+ mRunnableAtReason = REASON_CONTAINS_RESULT_TO;
+ } else {
+ mRunnableAt = runnableAt + constants.DELAY_CACHED_MILLIS;
+ mRunnableAtReason = REASON_CACHED;
+ }
+ }
+ } else {
+ // This record isn't deferrable
+ mRunnableAt = runnableAt + constants.DELAY_CACHED_MILLIS;
+ mRunnableAtReason = REASON_CACHED;
+ }
+ } else if (mCountResultTo > 0) {
+ // All resultTo broadcasts are infinitely deferrable, so if the app
+ // is already cached, they'll be deferred on the line above
+ mRunnableAt = runnableAt;
+ mRunnableAtReason = REASON_CONTAINS_RESULT_TO;
} else {
mRunnableAt = runnableAt + constants.DELAY_NORMAL_MILLIS;
mRunnableAtReason = REASON_NORMAL;
@@ -907,6 +990,15 @@
mRunnableAt = Math.min(mRunnableAt, runnableAt);
mRunnableAtReason = REASON_MAX_PENDING;
}
+
+ if (VERBOSE) {
+ Trace.instantForTrack(Trace.TRACE_TAG_ACTIVITY_MANAGER, "BroadcastQueue",
+ ((app != null) ? app.processName : "(null)")
+ + ":" + r.intent.toString() + ":"
+ + r.deferUntilActive
+ + ":" + mRunnableAt + " " + reasonToString(mRunnableAtReason)
+ + ":" + ((app != null) ? app.isCached() : "false"));
+ }
} else {
mRunnableAt = Long.MAX_VALUE;
mRunnableAtReason = REASON_EMPTY;
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index a994b1d..d819bfc 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -385,6 +385,8 @@
// If app isn't running, and there's nothing in the queue, clean up
if (queue.isEmpty() && !queue.isActive() && !queue.isProcessWarm()) {
removeProcessQueue(queue.processName, queue.uid);
+ } else {
+ updateQueueDeferred(queue);
}
}
@@ -637,11 +639,34 @@
final ArraySet<BroadcastRecord> replacedBroadcasts = new ArraySet<>();
final BroadcastConsumer replacedBroadcastConsumer =
(record, i) -> replacedBroadcasts.add(record);
+ boolean enqueuedBroadcast = false;
+
for (int i = 0; i < r.receivers.size(); i++) {
final Object receiver = r.receivers.get(i);
final BroadcastProcessQueue queue = getOrCreateProcessQueue(
getReceiverProcessName(receiver), getReceiverUid(receiver));
- queue.enqueueOrReplaceBroadcast(r, i, replacedBroadcastConsumer);
+
+ boolean wouldBeSkipped = false;
+ if (receiver instanceof ResolveInfo) {
+ // If the app is running but would not have been started if the process weren't
+ // running, we're going to deliver the broadcast but mark that it's not a manifest
+ // broadcast that would have started the app. This allows BroadcastProcessQueue to
+ // defer the broadcast as though it were a normal runtime receiver.
+ wouldBeSkipped = (mSkipPolicy.shouldSkipMessage(r, receiver) != null);
+ if (wouldBeSkipped && queue.app == null && !queue.getActiveViaColdStart()) {
+ // Skip receiver if there's no running app, the app is not being started, and
+ // the app wouldn't be launched for this broadcast
+ setDeliveryState(null, null, r, i, receiver, BroadcastRecord.DELIVERY_SKIPPED,
+ "skipped by policy to avoid cold start");
+ continue;
+ }
+ }
+ enqueuedBroadcast = true;
+ queue.enqueueOrReplaceBroadcast(r, i, replacedBroadcastConsumer, wouldBeSkipped);
+ if (r.isDeferUntilActive() && queue.isDeferredUntilActive()) {
+ setDeliveryState(queue, null, r, i, receiver, BroadcastRecord.DELIVERY_DEFERRED,
+ "deferred at enqueue time");
+ }
updateRunnableList(queue);
enqueueUpdateRunningList();
}
@@ -651,7 +676,7 @@
skipAndCancelReplacedBroadcasts(replacedBroadcasts);
// If nothing to dispatch, send any pending result immediately
- if (r.receivers.isEmpty()) {
+ if (r.receivers.isEmpty() || !enqueuedBroadcast) {
scheduleResultTo(r);
notifyFinishBroadcast(r);
}
@@ -1195,11 +1220,19 @@
@NonNull Object receiver, @DeliveryState int newDeliveryState, String reason) {
final int cookie = traceBegin("setDeliveryState");
final int oldDeliveryState = getDeliveryState(r, index);
+ boolean checkFinished = false;
// Only apply state when we haven't already reached a terminal state;
// this is how we ignore racing timeout messages
if (!isDeliveryStateTerminal(oldDeliveryState)) {
r.setDeliveryState(index, newDeliveryState);
+ if (oldDeliveryState == BroadcastRecord.DELIVERY_DEFERRED) {
+ r.deferredCount--;
+ } else if (newDeliveryState == BroadcastRecord.DELIVERY_DEFERRED) {
+ // If we're deferring a broadcast, maybe that's enough to unblock the final callback
+ r.deferredCount++;
+ checkFinished = true;
+ }
}
// Emit any relevant tracing results when we're changing the delivery
@@ -1217,7 +1250,8 @@
// bookkeeping to update for ordered broadcasts
if (!isDeliveryStateTerminal(oldDeliveryState)
&& isDeliveryStateTerminal(newDeliveryState)) {
- if (newDeliveryState != BroadcastRecord.DELIVERY_DELIVERED) {
+ if (DEBUG_BROADCAST
+ && newDeliveryState != BroadcastRecord.DELIVERY_DELIVERED) {
logw("Delivery state of " + r + " to " + receiver
+ " via " + app + " changed from "
+ deliveryStateToString(oldDeliveryState) + " to "
@@ -1226,9 +1260,12 @@
r.terminalCount++;
notifyFinishReceiver(queue, r, index, receiver);
-
- // When entire ordered broadcast finished, deliver final result
- final boolean recordFinished = (r.terminalCount == r.receivers.size());
+ checkFinished = true;
+ }
+ // When entire ordered broadcast finished, deliver final result
+ if (checkFinished) {
+ final boolean recordFinished =
+ ((r.terminalCount + r.deferredCount) == r.receivers.size());
if (recordFinished) {
scheduleResultTo(r);
}
@@ -1329,6 +1366,16 @@
r.resultExtras = null;
};
+ private final BroadcastConsumer mBroadcastConsumerDefer = (r, i) -> {
+ setDeliveryState(null, null, r, i, r.receivers.get(i), BroadcastRecord.DELIVERY_DEFERRED,
+ "mBroadcastConsumerDefer");
+ };
+
+ private final BroadcastConsumer mBroadcastConsumerUndoDefer = (r, i) -> {
+ setDeliveryState(null, null, r, i, r.receivers.get(i), BroadcastRecord.DELIVERY_PENDING,
+ "mBroadcastConsumerUndoDefer");
+ };
+
/**
* Verify that all known {@link #mProcessQueues} are in the state tested by
* the given {@link Predicate}.
@@ -1392,6 +1439,21 @@
}
}
+ private void updateQueueDeferred(
+ @NonNull BroadcastProcessQueue leaf) {
+ if (leaf.isDeferredUntilActive()) {
+ leaf.forEachMatchingBroadcast((r, i) -> {
+ return r.deferUntilActive && (r.getDeliveryState(i)
+ == BroadcastRecord.DELIVERY_PENDING);
+ }, mBroadcastConsumerDefer, false);
+ } else if (leaf.hasDeferredBroadcasts()) {
+ leaf.forEachMatchingBroadcast((r, i) -> {
+ return r.deferUntilActive && (r.getDeliveryState(i)
+ == BroadcastRecord.DELIVERY_DEFERRED);
+ }, mBroadcastConsumerUndoDefer, false);
+ }
+ }
+
@Override
public void start(@NonNull ContentResolver resolver) {
mFgConstants.startObserving(mHandler, resolver);
@@ -1404,6 +1466,7 @@
BroadcastProcessQueue leaf = mProcessQueues.get(uid);
while (leaf != null) {
leaf.setProcessCached(cached);
+ updateQueueDeferred(leaf);
updateRunnableList(leaf);
leaf = leaf.processNameNext;
}
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index 1d4d425..9679fb8 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -88,6 +88,7 @@
final boolean interactive; // originated from user interaction?
final boolean initialSticky; // initial broadcast from register to sticky?
final boolean prioritized; // contains more than one priority tranche
+ final boolean deferUntilActive; // infinitely deferrable broadcast
final int userId; // user id this broadcast was for
final @Nullable String resolvedType; // the resolved data type
final @Nullable String[] requiredPermissions; // permissions the caller has required
@@ -97,6 +98,7 @@
final @Nullable BroadcastOptions options; // BroadcastOptions supplied by caller
final @NonNull List<Object> receivers; // contains BroadcastFilter and ResolveInfo
final @DeliveryState int[] delivery; // delivery state of each receiver
+ final boolean[] deferredUntilActive; // whether each receiver is infinitely deferred
final int[] blockedUntilTerminalCount; // blocked until count of each receiver
@Nullable ProcessRecord resultToApp; // who receives final result if non-null
@Nullable IIntentReceiver resultTo; // who receives final result if non-null
@@ -130,6 +132,7 @@
int manifestCount; // number of manifest receivers dispatched.
int manifestSkipCount; // number of manifest receivers skipped.
int terminalCount; // number of receivers in terminal state.
+ int deferredCount; // number of receivers in deferred state.
@Nullable BroadcastQueue queue; // the outbound queue handling this broadcast
// if set to true, app's process will be temporarily allowed to start activities from background
@@ -168,6 +171,8 @@
static final int DELIVERY_SCHEDULED = 4;
/** Terminal state: failure to dispatch */
static final int DELIVERY_FAILURE = 5;
+ /** Intermediate state: currently deferred while app is cached */
+ static final int DELIVERY_DEFERRED = 6;
@IntDef(flag = false, prefix = { "DELIVERY_" }, value = {
DELIVERY_PENDING,
@@ -176,6 +181,7 @@
DELIVERY_TIMEOUT,
DELIVERY_SCHEDULED,
DELIVERY_FAILURE,
+ DELIVERY_DEFERRED,
})
@Retention(RetentionPolicy.SOURCE)
public @interface DeliveryState {}
@@ -188,6 +194,7 @@
case DELIVERY_TIMEOUT: return "TIMEOUT";
case DELIVERY_SCHEDULED: return "SCHEDULED";
case DELIVERY_FAILURE: return "FAILURE";
+ case DELIVERY_DEFERRED: return "DEFERRED";
default: return Integer.toString(deliveryState);
}
}
@@ -388,6 +395,8 @@
options = _options;
receivers = (_receivers != null) ? _receivers : EMPTY_RECEIVERS;
delivery = new int[_receivers != null ? _receivers.size() : 0];
+ deferUntilActive = options != null ? options.isDeferUntilActive() : false;
+ deferredUntilActive = new boolean[deferUntilActive ? delivery.length : 0];
blockedUntilTerminalCount = calculateBlockedUntilTerminalCount(receivers, _serialized);
scheduledTime = new long[delivery.length];
terminalTime = new long[delivery.length];
@@ -443,6 +452,8 @@
options = from.options;
receivers = from.receivers;
delivery = from.delivery;
+ deferUntilActive = from.deferUntilActive;
+ deferredUntilActive = from.deferredUntilActive;
blockedUntilTerminalCount = from.blockedUntilTerminalCount;
scheduledTime = from.scheduledTime;
terminalTime = from.terminalTime;
@@ -606,7 +617,7 @@
*/
void setDeliveryState(int index, @DeliveryState int deliveryState) {
delivery[index] = deliveryState;
-
+ if (deferUntilActive) deferredUntilActive[index] = false;
switch (deliveryState) {
case DELIVERY_DELIVERED:
case DELIVERY_SKIPPED:
@@ -617,6 +628,9 @@
case DELIVERY_SCHEDULED:
scheduledTime[index] = SystemClock.uptimeMillis();
break;
+ case DELIVERY_DEFERRED:
+ if (deferUntilActive) deferredUntilActive[index] = true;
+ break;
}
}
@@ -647,6 +661,10 @@
return (intent.getFlags() & Intent.FLAG_RECEIVER_OFFLOAD) != 0;
}
+ boolean isDeferUntilActive() {
+ return deferUntilActive;
+ }
+
/**
* Core policy determination about this broadcast's delivery prioritization
*/
diff --git a/services/core/java/com/android/server/am/BroadcastSkipPolicy.java b/services/core/java/com/android/server/am/BroadcastSkipPolicy.java
index 481ab17..6718319 100644
--- a/services/core/java/com/android/server/am/BroadcastSkipPolicy.java
+++ b/services/core/java/com/android/server/am/BroadcastSkipPolicy.java
@@ -229,12 +229,7 @@
return "Background execution disabled: receiving "
+ r.intent + " to "
+ component.flattenToShortString();
- } else if (((r.intent.getFlags()&Intent.FLAG_RECEIVER_EXCLUDE_BACKGROUND) != 0)
- || (r.intent.getComponent() == null
- && r.intent.getPackage() == null
- && ((r.intent.getFlags()
- & Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND) == 0)
- && !isSignaturePerm(r.requiredPermissions))) {
+ } else if (disallowBackgroundStart(r)) {
mService.addBackgroundCheckViolationLocked(r.intent.getAction(),
component.getPackageName());
return "Background execution not allowed: receiving "
@@ -341,6 +336,18 @@
}
/**
+ * Determine if the given {@link BroadcastRecord} is eligible to launch processes.
+ */
+ public boolean disallowBackgroundStart(@NonNull BroadcastRecord r) {
+ return ((r.intent.getFlags() & Intent.FLAG_RECEIVER_EXCLUDE_BACKGROUND) != 0)
+ || (r.intent.getComponent() == null
+ && r.intent.getPackage() == null
+ && ((r.intent.getFlags()
+ & Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND) == 0)
+ && !isSignaturePerm(r.requiredPermissions));
+ }
+
+ /**
* Determine if the given {@link BroadcastRecord} is eligible to be sent to
* the given {@link BroadcastFilter}.
*
@@ -624,7 +631,7 @@
/**
* Return true if all given permissions are signature-only perms.
*/
- private boolean isSignaturePerm(String[] perms) {
+ private static boolean isSignaturePerm(String[] perms) {
if (perms == null) {
return false;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
index cb409fe..0248010 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
@@ -125,7 +125,7 @@
return proto.getBytes();
}
- @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_BIOMETRIC)
+ @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override // Binder call
public List<FaceSensorPropertiesInternal> getSensorPropertiesInternal(
String opPackageName) {
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 39b1354..c9ae735 100644
--- a/services/core/java/com/android/server/broadcastradio/aidl/ProgramInfoCache.java
+++ b/services/core/java/com/android/server/broadcastradio/aidl/ProgramInfoCache.java
@@ -86,12 +86,8 @@
}
@VisibleForTesting
- boolean programInfosAreExactly(RadioManager.ProgramInfo... programInfos) {
- Map<ProgramSelector.Identifier, RadioManager.ProgramInfo> expectedMap = new ArrayMap<>();
- for (int i = 0; i < programInfos.length; i++) {
- expectedMap.put(programInfos[i].getSelector().getPrimaryId(), programInfos[i]);
- }
- return expectedMap.equals(mProgramInfoMap);
+ List<RadioManager.ProgramInfo> toProgramInfoList() {
+ return new ArrayList<>(mProgramInfoMap.values());
}
@Override
diff --git a/services/core/java/com/android/server/pm/AppsFilterImpl.java b/services/core/java/com/android/server/pm/AppsFilterImpl.java
index 5ff1909b..ac8ff21 100644
--- a/services/core/java/com/android/server/pm/AppsFilterImpl.java
+++ b/services/core/java/com/android/server/pm/AppsFilterImpl.java
@@ -32,6 +32,7 @@
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_MANAGER_APPS_FILTER_CACHE_UPDATE_REPORTED__EVENT_TYPE__PACKAGE_DELETED;
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_MANAGER_APPS_FILTER_CACHE_UPDATE_REPORTED__EVENT_TYPE__PACKAGE_REPLACED;
import static com.android.server.pm.AppsFilterUtils.canQueryAsInstaller;
+import static com.android.server.pm.AppsFilterUtils.canQueryAsUpdateOwner;
import static com.android.server.pm.AppsFilterUtils.canQueryViaComponents;
import static com.android.server.pm.AppsFilterUtils.canQueryViaPackage;
import static com.android.server.pm.AppsFilterUtils.canQueryViaUsesLibrary;
@@ -670,7 +671,8 @@
}
}
if (canQueryViaPackage(existingPkg, newPkg)
- || canQueryAsInstaller(existingSetting, newPkg)) {
+ || canQueryAsInstaller(existingSetting, newPkg)
+ || canQueryAsUpdateOwner(existingSetting, newPkg)) {
synchronized (mQueriesViaPackageLock) {
mQueriesViaPackage.add(existingSetting.getAppId(),
newPkgSetting.getAppId());
@@ -697,7 +699,8 @@
}
}
if (canQueryViaPackage(newPkg, existingPkg)
- || canQueryAsInstaller(newPkgSetting, existingPkg)) {
+ || canQueryAsInstaller(newPkgSetting, existingPkg)
+ || canQueryAsUpdateOwner(newPkgSetting, existingPkg)) {
synchronized (mQueriesViaPackageLock) {
mQueriesViaPackage.add(newPkgSetting.getAppId(),
existingSetting.getAppId());
diff --git a/services/core/java/com/android/server/pm/AppsFilterUtils.java b/services/core/java/com/android/server/pm/AppsFilterUtils.java
index 8da1d21..d38b83f 100644
--- a/services/core/java/com/android/server/pm/AppsFilterUtils.java
+++ b/services/core/java/com/android/server/pm/AppsFilterUtils.java
@@ -91,6 +91,15 @@
return false;
}
+ public static boolean canQueryAsUpdateOwner(PackageStateInternal querying,
+ AndroidPackage potentialTarget) {
+ final InstallSource installSource = querying.getInstallSource();
+ if (potentialTarget.getPackageName().equals(installSource.mUpdateOwnerPackageName)) {
+ return true;
+ }
+ return false;
+ }
+
public static boolean canQueryViaUsesLibrary(AndroidPackage querying,
AndroidPackage potentialTarget) {
if (potentialTarget.getLibraryNames().isEmpty()) {
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 8ea7788..350f5ef 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -1842,6 +1842,60 @@
dispatchSessionSealed();
}
+ @Override
+ public void seal() {
+ assertNotChild("seal");
+ assertCallerIsOwnerOrRoot();
+ try {
+ sealInternal();
+ for (var child : getChildSessions()) {
+ child.sealInternal();
+ }
+ } catch (PackageManagerException e) {
+ throw new IllegalStateException("Package is not valid", e);
+ }
+ }
+
+ private void sealInternal() throws PackageManagerException {
+ synchronized (mLock) {
+ sealLocked();
+ }
+ }
+
+ @Override
+ public List<String> fetchPackageNames() {
+ assertNotChild("fetchPackageNames");
+ assertCallerIsOwnerOrRoot();
+ var sessions = getSelfOrChildSessions();
+ var result = new ArrayList<String>(sessions.size());
+ for (var s : sessions) {
+ result.add(s.fetchPackageName());
+ }
+ return result;
+ }
+
+ private String fetchPackageName() {
+ assertSealed("fetchPackageName");
+ synchronized (mLock) {
+ final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
+ final List<File> addedFiles = getAddedApksLocked();
+ for (File addedFile : addedFiles) {
+ final ParseResult<ApkLite> result =
+ ApkLiteParseUtils.parseApkLite(input.reset(), addedFile, 0);
+ if (result.isError()) {
+ throw new IllegalStateException(
+ "Can't parse package for session=" + sessionId, result.getException());
+ }
+ final ApkLite apk = result.getResult();
+ var packageName = apk.getPackageName();
+ if (packageName != null) {
+ return packageName;
+ }
+ }
+ throw new IllegalStateException("Can't fetch package name for session=" + sessionId);
+ }
+ }
+
/**
* Kicks off the install flow. The first step is to persist 'sealed' flags
* to prevent mutations of hard links created later.
@@ -2095,6 +2149,11 @@
}
}
+ @NonNull
+ private List<PackageInstallerSession> getSelfOrChildSessions() {
+ return isMultiPackage() ? getChildSessions() : Collections.singletonList(this);
+ }
+
/**
* Seal the session to prevent further modification.
*
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index a72ae56..0de1a4e 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -33,6 +33,7 @@
import static com.android.server.pm.PackageManagerService.STUB_SUFFIX;
import static com.android.server.pm.PackageManagerService.TAG;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
@@ -113,6 +114,8 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.nio.file.Path;
import java.security.SecureRandom;
import java.security.cert.CertificateEncodingException;
@@ -148,6 +151,29 @@
ThreadLocal.withInitial(() -> false);
/**
+ * Type used with {@link #canJoinSharedUserId(String, SigningDetails, SharedUserSetting, int)}
+ * when the package attempting to join the sharedUserId is a new install.
+ */
+ public static final int SHARED_USER_ID_JOIN_TYPE_INSTALL = 0;
+ /**
+ * Type used with {@link #canJoinSharedUserId(String, SigningDetails, SharedUserSetting, int)}
+ * when the package attempting to join the sharedUserId is an update.
+ */
+ public static final int SHARED_USER_ID_JOIN_TYPE_UPDATE = 1;
+ /**
+ * Type used with {@link #canJoinSharedUserId(String, SigningDetails, SharedUserSetting, int)}
+ * when the package attempting to join the sharedUserId is a part of the system image.
+ */
+ public static final int SHARED_USER_ID_JOIN_TYPE_SYSTEM = 2;
+ @IntDef(prefix = { "TYPE_" }, value = {
+ SHARED_USER_ID_JOIN_TYPE_INSTALL,
+ SHARED_USER_ID_JOIN_TYPE_UPDATE,
+ SHARED_USER_ID_JOIN_TYPE_SYSTEM,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface SharedUserIdJoinType {}
+
+ /**
* Components of apps targeting Android T and above will stop receiving intents from
* external callers that do not match its declared intent filters.
*
@@ -575,17 +601,9 @@
// the older ones. We check to see if either the new package is signed by an older cert
// with which the current sharedUser is ok, or if it is signed by a newer one, and is ok
// with being sharedUser with the existing signing cert.
- boolean match = canJoinSharedUserId(parsedSignatures,
- sharedUserSetting.getSigningDetails());
- // Special case: if the sharedUserId capability check failed it could be due to this
- // being the only package in the sharedUserId so far and the lineage being updated to
- // deny the sharedUserId capability of the previous key in the lineage.
- final ArraySet<PackageStateInternal> susPackageStates =
- (ArraySet<PackageStateInternal>) sharedUserSetting.getPackageStates();
- if (!match && susPackageStates.size() == 1
- && susPackageStates.valueAt(0).getPackageName().equals(packageName)) {
- match = true;
- }
+ boolean match = canJoinSharedUserId(packageName, parsedSignatures, sharedUserSetting,
+ pkgSetting.getSigningDetails().getSignatures() != null
+ ? SHARED_USER_ID_JOIN_TYPE_UPDATE : SHARED_USER_ID_JOIN_TYPE_INSTALL);
if (!match && compareCompat) {
match = matchSignaturesCompat(
packageName, sharedUserSetting.signatures, parsedSignatures);
@@ -608,36 +626,6 @@
+ " has no signatures that match those in shared user "
+ sharedUserSetting.name + "; ignoring!");
}
- // It is possible that this package contains a lineage that blocks sharedUserId access
- // to an already installed package in the sharedUserId signed with a previous key.
- // Iterate over all of the packages in the sharedUserId and ensure any that are signed
- // with a key in this package's lineage have the SHARED_USER_ID capability granted.
- if (parsedSignatures.hasPastSigningCertificates()) {
- for (int i = 0; i < susPackageStates.size(); i++) {
- PackageStateInternal shUidPkgSetting = susPackageStates.valueAt(i);
- // if the current package in the sharedUserId is the package being updated then
- // skip this check as the update may revoke the sharedUserId capability from
- // the key with which this app was previously signed.
- if (packageName.equals(shUidPkgSetting.getPackageName())) {
- continue;
- }
- SigningDetails shUidSigningDetails =
- shUidPkgSetting.getSigningDetails();
- // The capability check only needs to be performed against the package if it is
- // signed with a key that is in the lineage of the package being installed.
- if (parsedSignatures.hasAncestor(shUidSigningDetails)) {
- if (!parsedSignatures.checkCapability(shUidSigningDetails,
- SigningDetails.CertCapabilities.SHARED_USER_ID)) {
- throw new PackageManagerException(
- INSTALL_FAILED_SHARED_USER_INCOMPATIBLE,
- "Package " + packageName
- + " revoked the sharedUserId capability from the"
- + " signing key used to sign "
- + shUidPkgSetting.getPackageName());
- }
- }
- }
- }
// If the lineage of this package diverges from the lineage of the sharedUserId then
// do not allow the installation to proceed.
if (!parsedSignatures.hasCommonAncestor(
@@ -651,25 +639,97 @@
}
/**
- * Returns whether the package with {@code packageSigningDetails} can join the sharedUserId
- * with {@code sharedUserSigningDetails}.
+ * Returns whether the package {@code packageName} can join the sharedUserId based on the
+ * settings in {@code sharedUserSetting}.
* <p>
* A sharedUserId maintains a shared {@link SigningDetails} containing the full lineage and
* capabilities for each package in the sharedUserId. A package can join the sharedUserId if
* its current signer is the same as the shared signer, or if the current signer of either
* is in the signing lineage of the other with the {@link
* SigningDetails.CertCapabilities#SHARED_USER_ID} capability granted to that previous signer
- * in the lineage.
+ * in the lineage. In the case of a key compromise, an app signed with a lineage revoking
+ * this capability from a previous signing key can still join the sharedUserId with another
+ * app signed with this previous key if the joining app is being updated; however, a new
+ * install will not be allowed until all apps have rotated off the key with the capability
+ * revoked.
*
+ * @param packageName the name of the package seeking to join the sharedUserId
* @param packageSigningDetails the {@code SigningDetails} of the package seeking to join the
- * sharedUserId
- * @param sharedUserSigningDetails the {@code SigningDetails} of the sharedUserId
+ * sharedUserId
+ * @param sharedUserSetting the {@code SharedUserSetting} for the sharedUserId {@code
+ * packageName} is seeking to join
+ * @param joinType the type of join (install, update, system, etc)
* @return true if the package seeking to join the sharedUserId meets the requirements
*/
- public static boolean canJoinSharedUserId(@NonNull SigningDetails packageSigningDetails,
- @NonNull SigningDetails sharedUserSigningDetails) {
- return packageSigningDetails.checkCapability(sharedUserSigningDetails, SHARED_USER_ID)
- || sharedUserSigningDetails.checkCapability(packageSigningDetails, SHARED_USER_ID);
+ public static boolean canJoinSharedUserId(@NonNull String packageName,
+ @NonNull SigningDetails packageSigningDetails,
+ @NonNull SharedUserSetting sharedUserSetting, @SharedUserIdJoinType int joinType) {
+ SigningDetails sharedUserSigningDetails = sharedUserSetting.getSigningDetails();
+ boolean capabilityGranted =
+ packageSigningDetails.checkCapability(sharedUserSigningDetails, SHARED_USER_ID)
+ || sharedUserSigningDetails.checkCapability(packageSigningDetails,
+ SHARED_USER_ID);
+
+ // If the current signer for either the package or the sharedUserId is the current signer
+ // of the other or in the lineage of the other with the SHARED_USER_ID capability granted,
+ // then a system and update join type can proceed; an install join type is not allowed here
+ // since the sharedUserId may contain packages that are signed with a key untrusted by
+ // the new package.
+ if (capabilityGranted && joinType != SHARED_USER_ID_JOIN_TYPE_INSTALL) {
+ return true;
+ }
+
+ // If the package is signed with a key that is no longer trusted by the sharedUserId, then
+ // the join should not be allowed unless this is a system join type; system packages can
+ // join the sharedUserId as long as they share a common lineage.
+ if (!capabilityGranted && sharedUserSigningDetails.hasAncestor(packageSigningDetails)) {
+ if (joinType == SHARED_USER_ID_JOIN_TYPE_SYSTEM) {
+ return true;
+ }
+ return false;
+ }
+
+ // If the package is signed with a rotated key that no longer trusts the sharedUserId key,
+ // then allow system and update join types to rotate away from an untrusted key; install
+ // join types are not allowed since a new package that doesn't trust a previous key
+ // shouldn't be allowed to join until all packages in the sharedUserId have rotated off the
+ // untrusted key.
+ if (!capabilityGranted && packageSigningDetails.hasAncestor(sharedUserSigningDetails)) {
+ if (joinType != SHARED_USER_ID_JOIN_TYPE_INSTALL) {
+ return true;
+ }
+ return false;
+ }
+
+ // If the capability is not granted and the package signatures are not an ancestor
+ // or descendant of the sharedUserId signatures, then do not allow any join type to join
+ // the sharedUserId since there are no common signatures.
+ if (!capabilityGranted) {
+ return false;
+ }
+
+ // At this point this is a new install with the capability granted; ensure the current
+ // packages in the sharedUserId are all signed by a key trusted by the new package.
+ final ArraySet<PackageStateInternal> susPackageStates =
+ (ArraySet<PackageStateInternal>) sharedUserSetting.getPackageStates();
+ if (packageSigningDetails.hasPastSigningCertificates()) {
+ for (PackageStateInternal shUidPkgSetting : susPackageStates) {
+ SigningDetails shUidSigningDetails = shUidPkgSetting.getSigningDetails();
+ // The capability check only needs to be performed against the package if it is
+ // signed with a key that is in the lineage of the package being installed.
+ if (packageSigningDetails.hasAncestor(shUidSigningDetails)) {
+ if (!packageSigningDetails.checkCapability(shUidSigningDetails,
+ SigningDetails.CertCapabilities.SHARED_USER_ID)) {
+ Slog.d(TAG, "Package " + packageName
+ + " revoked the sharedUserId capability from the"
+ + " signing key used to sign "
+ + shUidPkgSetting.getPackageName());
+ return false;
+ }
+ }
+ }
+ }
+ return true;
}
/**
diff --git a/services/core/java/com/android/server/pm/ReconcilePackageUtils.java b/services/core/java/com/android/server/pm/ReconcilePackageUtils.java
index 99bcbc9..58dcb02 100644
--- a/services/core/java/com/android/server/pm/ReconcilePackageUtils.java
+++ b/services/core/java/com/android/server/pm/ReconcilePackageUtils.java
@@ -213,8 +213,9 @@
if (sharedUserSetting != null) {
if (sharedUserSetting.signaturesChanged != null
&& !PackageManagerServiceUtils.canJoinSharedUserId(
- parsedPackage.getSigningDetails(),
- sharedUserSetting.getSigningDetails())) {
+ parsedPackage.getPackageName(), parsedPackage.getSigningDetails(),
+ sharedUserSetting,
+ PackageManagerServiceUtils.SHARED_USER_ID_JOIN_TYPE_SYSTEM)) {
if (SystemProperties.getInt("ro.product.first_api_level", 0) <= 29) {
// Mismatched signatures is an error and silently skipping system
// packages will likely break the device in unforeseen ways.
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index a966d98..a8cf8cb 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1829,23 +1829,6 @@
}
/**
- * Gets the current user id, or the target user id in case there is a started user switch.
- *
- * @return id of current or target foreground user, or {@link UserHandle#USER_NULL} if
- * {@link ActivityManagerInternal} is not available yet.
- */
- @VisibleForTesting
- int getCurrentOrTargetUserId() {
- ActivityManagerInternal activityManagerInternal = getActivityManagerInternal();
- if (activityManagerInternal == null) {
- Slog.w(LOG_TAG, "getCurrentOrTargetUserId() called too early, ActivityManagerInternal"
- + " is not set yet");
- return UserHandle.USER_NULL;
- }
- return activityManagerInternal.getCurrentUser().id;
- }
-
- /**
* Gets whether the user is the current foreground user or a started profile of that user.
*
* <p>Doesn't perform any permission check.
@@ -5419,7 +5402,8 @@
final long ident = Binder.clearCallingIdentity();
try {
final UserData userData;
- if (userId == getCurrentOrTargetUserId()) {
+ int currentUser = getCurrentUserId();
+ if (currentUser == userId) {
Slog.w(LOG_TAG, "Current user cannot be removed.");
return false;
}
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index 27d74d5..214fd61 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -149,7 +149,8 @@
UserManager.DISALLOW_WIFI_DIRECT,
UserManager.DISALLOW_ADD_WIFI_CONFIG,
UserManager.DISALLOW_CELLULAR_2G,
- UserManager.DISALLOW_ULTRA_WIDEBAND_RADIO
+ UserManager.DISALLOW_ULTRA_WIDEBAND_RADIO,
+ UserManager.DISALLOW_CONFIG_DEFAULT_APPS
});
public static final Set<String> DEPRECATED_USER_RESTRICTIONS = Sets.newArraySet(
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
index e7159db..31f291f 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
@@ -248,6 +248,9 @@
private static final String MAX_NUM_COMPONENTS_ERR_MSG =
"Total number of components has exceeded the maximum number: " + MAX_NUM_COMPONENTS;
+ /** The maximum permission name length. */
+ private static final int MAX_PERMISSION_NAME_LENGTH = 512;
+
@IntDef(flag = true, prefix = { "PARSE_" }, value = {
PARSE_CHATTY,
PARSE_COLLECT_CERTIFICATES,
@@ -1263,6 +1266,11 @@
// that may change.
String name = sa.getNonResourceString(
R.styleable.AndroidManifestUsesPermission_name);
+ if (TextUtils.length(name) > MAX_PERMISSION_NAME_LENGTH) {
+ return input.error(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
+ "The name in the <uses-permission> is greater than "
+ + MAX_PERMISSION_NAME_LENGTH);
+ }
int minSdkVersion = parseMinOrMaxSdkVersion(sa,
R.styleable.AndroidManifestUsesPermission_minSdkVersion,
diff --git a/services/core/java/com/android/server/security/rkp/RemoteProvisioningRegistration.java b/services/core/java/com/android/server/security/rkp/RemoteProvisioningRegistration.java
index 868f34b..0e92709 100644
--- a/services/core/java/com/android/server/security/rkp/RemoteProvisioningRegistration.java
+++ b/services/core/java/com/android/server/security/rkp/RemoteProvisioningRegistration.java
@@ -21,10 +21,12 @@
import android.os.OutcomeReceiver;
import android.security.rkp.IGetKeyCallback;
import android.security.rkp.IRegistration;
+import android.security.rkp.IStoreUpgradedKeyCallback;
import android.security.rkp.service.RegistrationProxy;
import android.security.rkp.service.RemotelyProvisionedKey;
import android.util.Log;
+import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
@@ -36,8 +38,10 @@
*/
final class RemoteProvisioningRegistration extends IRegistration.Stub {
static final String TAG = RemoteProvisioningService.TAG;
- private final ConcurrentHashMap<IGetKeyCallback, CancellationSignal> mOperations =
+ private final ConcurrentHashMap<IGetKeyCallback, CancellationSignal> mGetKeyOperations =
new ConcurrentHashMap<>();
+ private final Set<IStoreUpgradedKeyCallback> mStoreUpgradedKeyOperations =
+ ConcurrentHashMap.newKeySet();
private final RegistrationProxy mRegistration;
private final Executor mExecutor;
@@ -49,7 +53,7 @@
@Override
public void onResult(RemotelyProvisionedKey result) {
- mOperations.remove(mCallback);
+ mGetKeyOperations.remove(mCallback);
Log.i(TAG, "Successfully fetched key for client " + mCallback.hashCode());
android.security.rkp.RemotelyProvisionedKey parcelable =
new android.security.rkp.RemotelyProvisionedKey();
@@ -60,7 +64,7 @@
@Override
public void onError(Exception e) {
- mOperations.remove(mCallback);
+ mGetKeyOperations.remove(mCallback);
if (e instanceof OperationCanceledException) {
Log.i(TAG, "Operation cancelled for client " + mCallback.hashCode());
wrapCallback(mCallback::onCancel);
@@ -79,7 +83,7 @@
@Override
public void getKey(int keyId, IGetKeyCallback callback) {
CancellationSignal cancellationSignal = new CancellationSignal();
- if (mOperations.putIfAbsent(callback, cancellationSignal) != null) {
+ if (mGetKeyOperations.putIfAbsent(callback, cancellationSignal) != null) {
Log.e(TAG, "Client can only request one call at a time " + callback.hashCode());
throw new IllegalArgumentException(
"Callback is already associated with an existing operation: "
@@ -92,14 +96,14 @@
new GetKeyReceiver(callback));
} catch (Exception e) {
Log.e(TAG, "getKeyAsync threw an exception for client " + callback.hashCode(), e);
- mOperations.remove(callback);
+ mGetKeyOperations.remove(callback);
wrapCallback(() -> callback.onError(e.getMessage()));
}
}
@Override
public void cancelGetKey(IGetKeyCallback callback) {
- CancellationSignal cancellationSignal = mOperations.remove(callback);
+ CancellationSignal cancellationSignal = mGetKeyOperations.remove(callback);
if (cancellationSignal == null) {
throw new IllegalArgumentException(
"Invalid client in cancelGetKey: " + callback.hashCode());
@@ -110,9 +114,35 @@
}
@Override
- public void storeUpgradedKey(byte[] oldKeyBlob, byte[] newKeyBlob) {
- // TODO(b/262748535)
- Log.e(TAG, "RegistrationBinder.storeUpgradedKey NOT YET IMPLEMENTED");
+ public void storeUpgradedKeyAsync(byte[] oldKeyBlob, byte[] newKeyBlob,
+ IStoreUpgradedKeyCallback callback) {
+ if (!mStoreUpgradedKeyOperations.add(callback)) {
+ throw new IllegalArgumentException(
+ "Callback is already associated with an existing operation: "
+ + callback.hashCode());
+ }
+
+ try {
+ mRegistration.storeUpgradedKeyAsync(oldKeyBlob, newKeyBlob, mExecutor,
+ new OutcomeReceiver<>() {
+ @Override
+ public void onResult(Void result) {
+ mStoreUpgradedKeyOperations.remove(callback);
+ wrapCallback(callback::onSuccess);
+ }
+
+ @Override
+ public void onError(Exception e) {
+ mStoreUpgradedKeyOperations.remove(callback);
+ wrapCallback(() -> callback.onError(e.getMessage()));
+ }
+ });
+ } catch (Exception e) {
+ Log.e(TAG, "storeUpgradedKeyAsync threw an exception for client "
+ + callback.hashCode(), e);
+ mStoreUpgradedKeyOperations.remove(callback);
+ wrapCallback(() -> callback.onError(e.getMessage()));
+ }
}
interface CallbackRunner {
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/AppsFilterImplTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/AppsFilterImplTest.java
index 56cd7a9..7909ba4 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/AppsFilterImplTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/AppsFilterImplTest.java
@@ -283,7 +283,7 @@
assertFalse(
appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
SYSTEM_USER));
- watcher.verifyNoChangeReported("shouldFilterAplication");
+ watcher.verifyNoChangeReported("shouldFilterApplication");
}
@Test
@@ -1081,7 +1081,7 @@
assertTrue(
appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
SYSTEM_USER));
- watcher.verifyNoChangeReported("shouldFilterAplication");
+ watcher.verifyNoChangeReported("shouldFilterApplication");
}
@Test
@@ -1110,7 +1110,36 @@
assertFalse(
appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
SYSTEM_USER));
- watcher.verifyNoChangeReported("shouldFilterAplication");
+ watcher.verifyNoChangeReported("shouldFilterApplication");
+ }
+
+ @Test
+ public void testUpdateOwner_DoesntFilter() throws Exception {
+ final AppsFilterImpl appsFilter =
+ new AppsFilterImpl(mFeatureConfigMock, new String[]{}, /* systemAppsQueryable */
+ false, /* overlayProvider */ null, mMockHandler);
+ final WatchableTester watcher = new WatchableTester(appsFilter, "onChange");
+ watcher.register();
+ simulateAddBasicAndroid(appsFilter);
+ watcher.verifyChangeReported("addBasicAndroid");
+ appsFilter.onSystemReady(mPmInternal);
+ watcher.verifyChangeReported("systemReady");
+
+ PackageSetting target = simulateAddPackage(appsFilter, pkg("com.some.package"),
+ DUMMY_TARGET_APPID);
+ watcher.verifyChangeReported("add package");
+ PackageSetting calling = simulateAddPackage(appsFilter, pkg("com.some.other.package"),
+ DUMMY_CALLING_APPID, withInstallSource(null /* initiatingPackageName */,
+ null /* originatingPackageName */, null /* installerPackageName */,
+ INVALID_UID, target.getPackageName(),
+ null /* installerAttributionTag */,
+ false /* isInitiatingPackageUninstalled */));
+ watcher.verifyChangeReported("add package");
+
+ assertFalse(
+ appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
+ SYSTEM_USER));
+ watcher.verifyNoChangeReported("shouldFilterApplication");
}
@Test
@@ -1139,7 +1168,7 @@
assertFalse(
appsFilter.shouldFilterApplication(mSnapshot, DUMMY_TARGET_APPID, target,
instrumentation, SYSTEM_USER));
- watcher.verifyNoChangeReported("shouldFilterAplication");
+ watcher.verifyNoChangeReported("shouldFilterApplication");
}
@Test
diff --git a/services/tests/RemoteProvisioningServiceTests/src/com/android/server/security/rkp/RemoteProvisioningRegistrationTest.java b/services/tests/RemoteProvisioningServiceTests/src/com/android/server/security/rkp/RemoteProvisioningRegistrationTest.java
index 470f2be..7b361d3 100644
--- a/services/tests/RemoteProvisioningServiceTests/src/com/android/server/security/rkp/RemoteProvisioningRegistrationTest.java
+++ b/services/tests/RemoteProvisioningServiceTests/src/com/android/server/security/rkp/RemoteProvisioningRegistrationTest.java
@@ -34,7 +34,9 @@
import android.os.CancellationSignal;
import android.os.OperationCanceledException;
import android.os.OutcomeReceiver;
+import android.os.RemoteException;
import android.security.rkp.IGetKeyCallback;
+import android.security.rkp.IStoreUpgradedKeyCallback;
import android.security.rkp.service.RegistrationProxy;
import android.security.rkp.service.RemotelyProvisionedKey;
@@ -72,6 +74,12 @@
return answerVoid(answer);
}
+ // answerVoid wrapper for mocking storeUpgradeKeyAsync.
+ static Answer<Void> answerStoreUpgradedKeyAsync(
+ VoidAnswer4<byte[], byte[], Executor, OutcomeReceiver<Void, Exception>> answer) {
+ return answerVoid(answer);
+ }
+
// matcher helper, making it easier to match the different key types
private android.security.rkp.RemotelyProvisionedKey matches(
RemotelyProvisionedKey expectedKey) {
@@ -178,16 +186,63 @@
@Test
public void storeUpgradedKeySuccess() throws Exception {
- // TODO(b/262748535)
+ doAnswer(
+ answerStoreUpgradedKeyAsync((oldBlob, newBlob, executor, receiver) ->
+ executor.execute(() -> receiver.onResult(null))))
+ .when(mRegistrationProxy)
+ .storeUpgradedKeyAsync(any(), any(), any(), any());
+
+ IStoreUpgradedKeyCallback callback = mock(IStoreUpgradedKeyCallback.class);
+ mRegistration.storeUpgradedKeyAsync(new byte[0], new byte[0], callback);
+ verify(callback).onSuccess();
+ verifyNoMoreInteractions(callback);
}
@Test
public void storeUpgradedKeyFails() throws Exception {
- // TODO(b/262748535)
+ final String errorString = "this is a failure";
+ doAnswer(
+ answerStoreUpgradedKeyAsync((oldBlob, newBlob, executor, receiver) ->
+ executor.execute(() -> receiver.onError(new RemoteException(errorString)))))
+ .when(mRegistrationProxy)
+ .storeUpgradedKeyAsync(any(), any(), any(), any());
+
+ IStoreUpgradedKeyCallback callback = mock(IStoreUpgradedKeyCallback.class);
+ mRegistration.storeUpgradedKeyAsync(new byte[0], new byte[0], callback);
+ verify(callback).onError(errorString);
+ verifyNoMoreInteractions(callback);
}
@Test
- public void storeUpgradedCatchesExceptionFromProxy() throws Exception {
- // TODO(b/262748535)
+ public void storeUpgradedKeyHandlesException() throws Exception {
+ final String errorString = "all aboard the failboat, toot toot";
+ doThrow(new IllegalArgumentException(errorString))
+ .when(mRegistrationProxy)
+ .storeUpgradedKeyAsync(any(), any(), any(), any());
+
+ IStoreUpgradedKeyCallback callback = mock(IStoreUpgradedKeyCallback.class);
+ mRegistration.storeUpgradedKeyAsync(new byte[0], new byte[0], callback);
+ verify(callback).onError(errorString);
+ verifyNoMoreInteractions(callback);
}
+
+ @Test
+ public void storeUpgradedKeyDuplicateCallback() throws Exception {
+ IStoreUpgradedKeyCallback callback = mock(IStoreUpgradedKeyCallback.class);
+
+ doAnswer(
+ answerStoreUpgradedKeyAsync((oldBlob, newBlob, executor, receiver) -> {
+ assertThrows(IllegalArgumentException.class,
+ () -> mRegistration.storeUpgradedKeyAsync(new byte[0], new byte[0],
+ callback));
+ executor.execute(() -> receiver.onResult(null));
+ }))
+ .when(mRegistrationProxy)
+ .storeUpgradedKeyAsync(any(), any(), any(), any());
+
+ mRegistration.storeUpgradedKeyAsync(new byte[0], new byte[0], callback);
+ verify(callback).onSuccess();
+ verifyNoMoreInteractions(callback);
+ }
+
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
index 77127c5..92570aa 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -22,7 +22,6 @@
import static com.android.server.am.BroadcastProcessQueue.REASON_CONTAINS_MANIFEST;
import static com.android.server.am.BroadcastProcessQueue.REASON_CONTAINS_ORDERED;
import static com.android.server.am.BroadcastProcessQueue.REASON_CONTAINS_PRIORITIZED;
-import static com.android.server.am.BroadcastProcessQueue.REASON_CONTAINS_RESULT_TO;
import static com.android.server.am.BroadcastProcessQueue.insertIntoRunnableList;
import static com.android.server.am.BroadcastProcessQueue.removeFromRunnableList;
import static com.android.server.am.BroadcastQueueTest.CLASS_BLUE;
@@ -115,8 +114,29 @@
mConstants.DELAY_NORMAL_MILLIS = 10_000;
mConstants.DELAY_CACHED_MILLIS = 120_000;
+ final BroadcastSkipPolicy emptySkipPolicy = new BroadcastSkipPolicy(mAms) {
+ public boolean shouldSkip(BroadcastRecord r, Object o) {
+ // Ignored
+ return false;
+ }
+ public String shouldSkipMessage(BroadcastRecord r, Object o) {
+ // Ignored
+ return null;
+ }
+ public boolean disallowBackgroundStart(BroadcastRecord r) {
+ // Ignored
+ return false;
+ }
+ };
+ final BroadcastHistory emptyHistory = new BroadcastHistory(mConstants) {
+ public void addBroadcastToHistoryLocked(BroadcastRecord original) {
+ // Ignored
+ }
+ };
+
+
mImpl = new BroadcastQueueModernImpl(mAms, mHandlerThread.getThreadHandler(),
- mConstants, mConstants);
+ mConstants, mConstants, emptySkipPolicy, emptyHistory);
doReturn(1L).when(mQueue1).getRunnableAt();
doReturn(2L).when(mQueue2).getRunnableAt();
@@ -200,7 +220,8 @@
private void enqueueOrReplaceBroadcast(BroadcastProcessQueue queue,
BroadcastRecord record, int recordIndex, long enqueueTime) {
- queue.enqueueOrReplaceBroadcast(record, recordIndex, null /* replacedBroadcastConsumer */);
+ queue.enqueueOrReplaceBroadcast(record, recordIndex,
+ null /* replacedBroadcastConsumer */, false);
record.enqueueTime = enqueueTime;
}
@@ -330,7 +351,8 @@
final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane,
List.of(makeMockRegisteredReceiver()));
- queue.enqueueOrReplaceBroadcast(airplaneRecord, 0, null /* replacedBroadcastConsumer */);
+ queue.enqueueOrReplaceBroadcast(airplaneRecord, 0,
+ null /* replacedBroadcastConsumer */, false);
queue.setProcessCached(false);
final long notCachedRunnableAt = queue.getRunnableAt();
@@ -352,12 +374,14 @@
// enqueue a bg-priority broadcast then a fg-priority one
final Intent timezone = new Intent(Intent.ACTION_TIMEZONE_CHANGED);
final BroadcastRecord timezoneRecord = makeBroadcastRecord(timezone);
- queue.enqueueOrReplaceBroadcast(timezoneRecord, 0, null /* replacedBroadcastConsumer */);
+ queue.enqueueOrReplaceBroadcast(timezoneRecord, 0,
+ null /* replacedBroadcastConsumer */, false);
final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
airplane.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane);
- queue.enqueueOrReplaceBroadcast(airplaneRecord, 0, null /* replacedBroadcastConsumer */);
+ queue.enqueueOrReplaceBroadcast(airplaneRecord, 0,
+ null /* replacedBroadcastConsumer */, false);
// verify that:
// (a) the queue is immediately runnable by existence of a fg-priority broadcast
@@ -388,7 +412,8 @@
final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane, null,
List.of(withPriority(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN), 10),
withPriority(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN), 0)), true);
- queue.enqueueOrReplaceBroadcast(airplaneRecord, 1, null /* replacedBroadcastConsumer */);
+ queue.enqueueOrReplaceBroadcast(airplaneRecord, 1,
+ null /* replacedBroadcastConsumer */, false);
assertFalse(queue.isRunnable());
assertEquals(BroadcastProcessQueue.REASON_BLOCKED, queue.getRunnableAtReason());
@@ -411,7 +436,8 @@
final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane,
List.of(makeMockRegisteredReceiver()));
- queue.enqueueOrReplaceBroadcast(airplaneRecord, 0, null /* replacedBroadcastConsumer */);
+ queue.enqueueOrReplaceBroadcast(airplaneRecord, 0,
+ null /* replacedBroadcastConsumer */, false);
mConstants.MAX_PENDING_BROADCASTS = 128;
queue.invalidateRunnableAt();
@@ -437,11 +463,13 @@
new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED),
List.of(makeMockRegisteredReceiver()));
- queue.enqueueOrReplaceBroadcast(lazyRecord, 0, null /* replacedBroadcastConsumer */);
+ queue.enqueueOrReplaceBroadcast(lazyRecord, 0,
+ null /* replacedBroadcastConsumer */, false);
assertThat(queue.getRunnableAt()).isGreaterThan(lazyRecord.enqueueTime);
assertThat(queue.getRunnableAtReason()).isNotEqualTo(testRunnableAtReason);
- queue.enqueueOrReplaceBroadcast(testRecord, 0, null /* replacedBroadcastConsumer */);
+ queue.enqueueOrReplaceBroadcast(testRecord, 0,
+ null /* replacedBroadcastConsumer */, false);
assertThat(queue.getRunnableAt()).isAtMost(testRecord.enqueueTime);
assertThat(queue.getRunnableAtReason()).isEqualTo(testRunnableAtReason);
}
@@ -459,13 +487,6 @@
}
@Test
- public void testRunnableAt_Cached_ResultTo() {
- final IIntentReceiver resultTo = mock(IIntentReceiver.class);
- doRunnableAt_Cached(makeBroadcastRecord(makeMockIntent(), null,
- List.of(makeMockRegisteredReceiver()), resultTo, false), REASON_CONTAINS_RESULT_TO);
- }
-
- @Test
public void testRunnableAt_Cached_Foreground() {
final Intent foregroundIntent = new Intent();
foregroundIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
@@ -511,25 +532,25 @@
queue.enqueueOrReplaceBroadcast(
makeBroadcastRecord(new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED)
.addFlags(Intent.FLAG_RECEIVER_OFFLOAD)), 0,
- null /* replacedBroadcastConsumer */);
+ null /* replacedBroadcastConsumer */, false);
queue.enqueueOrReplaceBroadcast(
makeBroadcastRecord(new Intent(Intent.ACTION_TIMEZONE_CHANGED)), 0,
- null /* replacedBroadcastConsumer */);
+ null /* replacedBroadcastConsumer */, false);
queue.enqueueOrReplaceBroadcast(
makeBroadcastRecord(new Intent(Intent.ACTION_LOCKED_BOOT_COMPLETED)
.addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0,
- null /* replacedBroadcastConsumer */);
+ null /* replacedBroadcastConsumer */, false);
queue.enqueueOrReplaceBroadcast(
makeBroadcastRecord(new Intent(Intent.ACTION_ALARM_CHANGED)
.addFlags(Intent.FLAG_RECEIVER_OFFLOAD)), 0,
- null /* replacedBroadcastConsumer */);
+ null /* replacedBroadcastConsumer */, false);
queue.enqueueOrReplaceBroadcast(
makeBroadcastRecord(new Intent(Intent.ACTION_TIME_TICK)), 0,
- null /* replacedBroadcastConsumer */);
+ null /* replacedBroadcastConsumer */, false);
queue.enqueueOrReplaceBroadcast(
makeBroadcastRecord(new Intent(Intent.ACTION_LOCALE_CHANGED)
.addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0,
- null /* replacedBroadcastConsumer */);
+ null /* replacedBroadcastConsumer */, false);
queue.makeActiveNextPending();
assertEquals(Intent.ACTION_LOCKED_BOOT_COMPLETED, queue.getActive().intent.getAction());
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index 8d9dda08..64be0f7 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -357,6 +357,11 @@
}
receiversToSkip.add(o);
}
+ public boolean disallowBackgroundStart(BroadcastRecord r) {
+ // Ignored
+ return false;
+ }
+
}
private class TestInjector extends Injector {
diff --git a/services/tests/servicestests/src/com/android/server/am/CoreSettingsObserverTest.java b/services/tests/servicestests/src/com/android/server/am/CoreSettingsObserverTest.java
index 574aaf0..5f55f09 100644
--- a/services/tests/servicestests/src/com/android/server/am/CoreSettingsObserverTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/CoreSettingsObserverTest.java
@@ -97,6 +97,8 @@
mContentResolver = new MockContentResolver(mContext);
mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
when(mContext.getContentResolver()).thenReturn(mContentResolver);
+ when(mContext.getCacheDir()).thenReturn(originalContext.getCacheDir());
+ when(mContext.getAttributionSource()).thenReturn(originalContext.getAttributionSource());
when(mContext.getResources()).thenReturn(mResources);
// To prevent NullPointerException at the constructor of ActivityManagerConstants.
when(mResources.getStringArray(anyInt())).thenReturn(new String[0]);
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserLifecycleStressTest.java b/services/tests/servicestests/src/com/android/server/pm/UserLifecycleStressTest.java
index b00fb92..bbe8907 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserLifecycleStressTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserLifecycleStressTest.java
@@ -29,7 +29,6 @@
import android.os.RemoteException;
import android.os.UserManager;
import android.platform.test.annotations.Postsubmit;
-import android.provider.Settings;
import android.util.Log;
import androidx.test.InstrumentationRegistry;
@@ -38,7 +37,6 @@
import com.android.internal.util.FunctionalUtils;
-import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -64,22 +62,12 @@
private Context mContext;
private UserManager mUserManager;
private ActivityManager mActivityManager;
- private String mRemoveGuestOnExitOriginalValue;
@Before
public void setup() {
mContext = InstrumentationRegistry.getInstrumentation().getContext();
mUserManager = mContext.getSystemService(UserManager.class);
mActivityManager = mContext.getSystemService(ActivityManager.class);
- mRemoveGuestOnExitOriginalValue = Settings.Global.getString(mContext.getContentResolver(),
- Settings.Global.REMOVE_GUEST_ON_EXIT);
-
- }
-
- @After
- public void tearDown() {
- Settings.Global.putString(mContext.getContentResolver(),
- Settings.Global.REMOVE_GUEST_ON_EXIT, mRemoveGuestOnExitOriginalValue);
}
/**
@@ -117,9 +105,6 @@
**/
@Test
public void switchToExistingGuestAndStartOverStressTest() throws Exception {
- Settings.Global.putString(mContext.getContentResolver(),
- Settings.Global.REMOVE_GUEST_ON_EXIT, "0");
-
if (ActivityManager.getCurrentUser() != USER_SYSTEM) {
switchUser(USER_SYSTEM);
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeAndDialogThemeAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeAndDialogThemeAppTest.kt
index da3c62d..cb530da 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeAndDialogThemeAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeAndDialogThemeAppTest.kt
@@ -16,7 +16,6 @@
package com.android.server.wm.flicker.ime
-import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import android.view.WindowInsets.Type.ime
import android.view.WindowInsets.Type.navigationBars
@@ -64,16 +63,6 @@
transitions { testApp.dismissDialog(wmHelper) }
}
- /** {@inheritDoc} */
- @Postsubmit
- @Test
- override fun taskBarWindowIsAlwaysVisible() = super.taskBarWindowIsAlwaysVisible()
-
- /** {@inheritDoc} */
- @Postsubmit
- @Test
- override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
-
/** Checks that [ComponentNameMatcher.IME] layer becomes visible during the transition */
@Presubmit @Test fun imeWindowIsAlwaysVisible() = flicker.imeWindowIsAlwaysVisible()
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowFromFixedOrientationAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowFromFixedOrientationAppTest.kt
index c2526d3..0e732b5 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowFromFixedOrientationAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowFromFixedOrientationAppTest.kt
@@ -16,7 +16,7 @@
package com.android.server.wm.flicker.ime
-import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.BaseTest
@@ -68,25 +68,11 @@
teardown { imeTestApp.exit(wmHelper) }
}
- /** {@inheritDoc} */
- @Postsubmit
- @Test
- override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
-
- @Postsubmit
- @Test
- override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
-
- /** {@inheritDoc} */
- @Postsubmit
- @Test
- override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
-
@Presubmit @Test fun imeWindowBecomesVisible() = flicker.imeWindowBecomesVisible()
@Presubmit @Test fun imeLayerBecomesVisible() = flicker.imeLayerBecomesVisible()
- @Postsubmit
+ @FlakyTest(bugId = 240918620)
@Test
fun snapshotStartingWindowLayerCoversExactlyOnApp() {
Assume.assumeFalse(isShellTransitionsEnabled)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt
index a6bbf54..477ddb3 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt
@@ -17,7 +17,6 @@
package com.android.server.wm.flicker.ime
import android.platform.test.annotations.FlakyTest
-import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.BaseTest
@@ -92,52 +91,10 @@
wmHelper.StateSyncBuilder().withFullScreenApp(imeTestApp).waitForAndVerify()
}
}
-
/** {@inheritDoc} */
- @Postsubmit
+ @FlakyTest(bugId = 265016201)
@Test
- override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible()
-
- /** {@inheritDoc} */
- @Postsubmit @Test override fun entireScreenCovered() = super.entireScreenCovered()
-
- /** {@inheritDoc} */
- @Postsubmit
- @Test
- override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
-
- /** {@inheritDoc} */
- @Postsubmit
- @Test
- override fun statusBarLayerIsVisibleAtStartAndEnd() =
- super.statusBarLayerIsVisibleAtStartAndEnd()
-
- /** {@inheritDoc} */
- @Postsubmit
- @Test
- override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
-
- /** {@inheritDoc} */
- @Postsubmit
- @Test
- override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
-
- /** {@inheritDoc} */
- @Postsubmit
- @Test
- override fun statusBarLayerPositionAtStartAndEnd() = super.statusBarLayerPositionAtStartAndEnd()
-
- /** {@inheritDoc} */
- @Postsubmit
- @Test
- override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
- super.visibleLayersShownMoreThanOneConsecutiveEntry()
-
- /** {@inheritDoc} */
- @Postsubmit
- @Test
- override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
- super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+ override fun entireScreenCovered() = super.entireScreenCovered()
@Presubmit
@Test
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
index 1b23952..8b250c3 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
@@ -16,7 +16,6 @@
package com.android.server.wm.flicker.rotation
-import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.IwTest
import android.platform.test.annotations.Presubmit
import androidx.test.filters.RequiresDevice
@@ -121,11 +120,6 @@
rotationLayerAppearsAndVanishesAssertion()
}
- /** {@inheritDoc} */
- @FlakyTest(bugId = 206753786)
- @Test
- override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
-
@Test
@IwTest(focusArea = "framework")
override fun cujCompleted() {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
index c26485b..d76c94d 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
@@ -16,7 +16,6 @@
package com.android.server.wm.flicker.rotation
-import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.IwTest
import android.platform.test.annotations.Presubmit
import android.view.WindowManager
@@ -201,11 +200,6 @@
flicker.assertEventLog { this.focusDoesNotChange() }
}
- /** {@inheritDoc} */
- @FlakyTest
- @Test
- override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
-
@Test
@IwTest(focusArea = "framework")
override fun cujCompleted() {
@@ -224,6 +218,7 @@
runAndIgnoreAssumptionViolation { appLayerAlwaysVisible() }
runAndIgnoreAssumptionViolation { navBarLayerIsVisibleAtStartAndEnd() }
runAndIgnoreAssumptionViolation { navBarWindowIsAlwaysVisible() }
+ runAndIgnoreAssumptionViolation { navBarLayerPositionAtStartAndEnd() }
runAndIgnoreAssumptionViolation { taskBarLayerIsVisibleAtStartAndEnd() }
runAndIgnoreAssumptionViolation { taskBarWindowIsAlwaysVisible() }
}
diff --git a/tools/lint/common/src/main/java/com/google/android/lint/Constants.kt b/tools/lint/common/src/main/java/com/google/android/lint/Constants.kt
index 3d5d01c..0ef165f 100644
--- a/tools/lint/common/src/main/java/com/google/android/lint/Constants.kt
+++ b/tools/lint/common/src/main/java/com/google/android/lint/Constants.kt
@@ -35,6 +35,6 @@
Method(CLASS_ACTIVITY_MANAGER_INTERNAL, "enforceCallingPermission")
)
-const val ANNOTATION_PERMISSION_METHOD = "android.content.pm.PermissionMethod"
-const val ANNOTATION_PERMISSION_NAME = "android.content.pm.PermissionName"
+const val ANNOTATION_PERMISSION_METHOD = "android.annotation.PermissionMethod"
+const val ANNOTATION_PERMISSION_NAME = "android.annotation.PermissionName"
const val ANNOTATION_PERMISSION_RESULT = "android.content.pm.PackageManager.PermissionResult"