diff options
210 files changed, 6612 insertions, 1381 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp index beb11fc3ee35..98b62b3e2046 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -26,6 +26,7 @@ aconfig_srcjars = [ ":android.content.flags-aconfig-java{.generated_srcjars}", ":android.content.pm.flags-aconfig-java{.generated_srcjars}", ":android.content.res.flags-aconfig-java{.generated_srcjars}", + ":android.crashrecovery.flags-aconfig-java{.generated_srcjars}", ":android.credentials.flags-aconfig-java{.generated_srcjars}", ":android.database.sqlite-aconfig-java{.generated_srcjars}", ":android.hardware.biometrics.flags-aconfig-java{.generated_srcjars}", @@ -94,6 +95,7 @@ stubs_defaults { "android.companion.virtual.flags-aconfig", "android.content.pm.flags-aconfig", "android.content.res.flags-aconfig", + "android.crashrecovery.flags-aconfig", "android.credentials.flags-aconfig", "android.database.sqlite-aconfig", "android.hardware.biometrics.flags-aconfig", @@ -1078,3 +1080,16 @@ java_aconfig_library { aconfig_declarations: "android.adaptiveauth.flags-aconfig", defaults: ["framework-minus-apex-aconfig-java-defaults"], } + +// CrashRecovery Module +aconfig_declarations { + name: "android.crashrecovery.flags-aconfig", + package: "android.crashrecovery.flags", + srcs: ["packages/CrashRecovery/aconfig/flags.aconfig"], +} + +java_aconfig_library { + name: "android.crashrecovery.flags-aconfig-java", + aconfig_declarations: "android.crashrecovery.flags-aconfig", + defaults: ["framework-minus-apex-aconfig-java-defaults"], +}
\ No newline at end of file diff --git a/Android.bp b/Android.bp index 9c56733650cb..e12f74fcd7ca 100644 --- a/Android.bp +++ b/Android.bp @@ -95,6 +95,7 @@ filegroup { ":platform-compat-native-aidl", // AIDL sources from external directories + ":android.frameworks.location.altitude-V2-java-source", ":android.hardware.biometrics.common-V4-java-source", ":android.hardware.biometrics.fingerprint-V3-java-source", ":android.hardware.biometrics.face-V4-java-source", diff --git a/apct-tests/perftests/contentcapture/src/android/view/contentcapture/AbstractContentCapturePerfTestCase.java b/apct-tests/perftests/contentcapture/src/android/view/contentcapture/AbstractContentCapturePerfTestCase.java index 0ea2dafbb047..bc8470888a5a 100644 --- a/apct-tests/perftests/contentcapture/src/android/view/contentcapture/AbstractContentCapturePerfTestCase.java +++ b/apct-tests/perftests/contentcapture/src/android/view/contentcapture/AbstractContentCapturePerfTestCase.java @@ -248,6 +248,11 @@ public abstract class AbstractContentCapturePerfTestCase { return mServiceWatcher.waitOnCreate(); } + /** Wait for session paused. */ + public void waitForSessionPaused() throws InterruptedException { + mServiceWatcher.waitSessionPaused(); + } + @NonNull protected ActivityWatcher startWatcher() { return mActivitiesWatcher.watch(CustomTestActivity.class); diff --git a/apct-tests/perftests/contentcapture/src/android/view/contentcapture/LoginTest.java b/apct-tests/perftests/contentcapture/src/android/view/contentcapture/LoginTest.java index aa95dfdfdf16..44e8a67e72ef 100644 --- a/apct-tests/perftests/contentcapture/src/android/view/contentcapture/LoginTest.java +++ b/apct-tests/perftests/contentcapture/src/android/view/contentcapture/LoginTest.java @@ -80,6 +80,34 @@ public class LoginTest extends AbstractContentCapturePerfTestCase { testActivityLaunchTime(R.layout.test_container_activity, 500); } + @Test + public void testSendEventsLatency() throws Throwable { + enableService(); + + testSendEventLatency(R.layout.test_container_activity, 0); + } + + @Test + public void testSendEventsLatency_contains100Views() throws Throwable { + enableService(); + + testSendEventLatency(R.layout.test_container_activity, 100); + } + + @Test + public void testSendEventsLatency_contains300Views() throws Throwable { + enableService(); + + testSendEventLatency(R.layout.test_container_activity, 300); + } + + @Test + public void testSendEventsLatency_contains500Views() throws Throwable { + enableService(); + + testSendEventLatency(R.layout.test_container_activity, 500); + } + private void testActivityLaunchTime(int layoutId, int numViews) throws Throwable { final Object drawNotifier = new Object(); final Intent intent = getLaunchIntent(layoutId, numViews); @@ -111,6 +139,38 @@ public class LoginTest extends AbstractContentCapturePerfTestCase { } } + private void testSendEventLatency(int layoutId, int numViews) throws Throwable { + final Object drawNotifier = new Object(); + final Intent intent = getLaunchIntent(layoutId, numViews); + intent.putExtra(CustomTestActivity.INTENT_EXTRA_FINISH_ON_IDLE, true); + intent.putExtra(CustomTestActivity.INTENT_EXTRA_DRAW_CALLBACK, + new RemoteCallback(result -> { + synchronized (drawNotifier) { + drawNotifier.notifyAll(); + } + })); + final ActivityWatcher watcher = startWatcher(); + + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + while (state.keepRunning()) { + mEntryActivity.startActivity(intent); + synchronized (drawNotifier) { + try { + drawNotifier.wait(GENERIC_TIMEOUT_MS); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + waitForSessionPaused(); + + // Ignore the time to finish the activity + state.pauseTiming(); + watcher.waitFor(DESTROYED); + sInstrumentation.waitForIdleSync(); + state.resumeTiming(); + } + } + @Test public void testOnVisibilityAggregated_visibleChanged() throws Throwable { enableService(); diff --git a/apct-tests/perftests/contentcapture/src/android/view/contentcapture/MyContentCaptureService.java b/apct-tests/perftests/contentcapture/src/android/view/contentcapture/MyContentCaptureService.java index ecc5112ab6dd..0b5345f5e2e1 100644 --- a/apct-tests/perftests/contentcapture/src/android/view/contentcapture/MyContentCaptureService.java +++ b/apct-tests/perftests/contentcapture/src/android/view/contentcapture/MyContentCaptureService.java @@ -114,6 +114,10 @@ public class MyContentCaptureService extends ContentCaptureService { public void onContentCaptureEvent(ContentCaptureSessionId sessionId, ContentCaptureEvent event) { Log.i(TAG, "onContentCaptureEventsRequest(session=" + sessionId + "): " + event); + if (sServiceWatcher != null + && event.getType() == ContentCaptureEvent.TYPE_SESSION_PAUSED) { + sServiceWatcher.mSessionPaused.countDown(); + } } @Override @@ -126,6 +130,7 @@ public class MyContentCaptureService extends ContentCaptureService { private static final long GENERIC_TIMEOUT_MS = 10_000; private final CountDownLatch mCreated = new CountDownLatch(1); private final CountDownLatch mDestroyed = new CountDownLatch(1); + private final CountDownLatch mSessionPaused = new CountDownLatch(1); private boolean mReadyToClear = true; private Pair<Set<String>, Set<ComponentName>> mAllowList; @@ -151,6 +156,11 @@ public class MyContentCaptureService extends ContentCaptureService { await(mDestroyed, "not destroyed"); } + /** Wait for session paused. */ + public void waitSessionPaused() throws InterruptedException { + await(mSessionPaused, "no Paused"); + } + /** * Allow just this package. */ diff --git a/apex/jobscheduler/framework/aconfig/job.aconfig b/apex/jobscheduler/framework/aconfig/job.aconfig index e73b434042af..788e82407926 100644 --- a/apex/jobscheduler/framework/aconfig/job.aconfig +++ b/apex/jobscheduler/framework/aconfig/job.aconfig @@ -13,3 +13,10 @@ flag { description: "Add APIs to let apps attach debug information to jobs" bug: "293491637" } + +flag { + name: "backup_jobs_exemption" + namespace: "backstage_power" + description: "Introduce a new RUN_BACKUP_JOBS permission and exemption logic allowing for longer running jobs for apps whose primary purpose is to backup or sync content." + bug: "318731461" +} diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java index 7a92cca74795..fc193d8147b5 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java @@ -1840,7 +1840,9 @@ public class JobSchedulerService extends com.android.server.SystemService /* isFlexConstraintSatisfied */ false, jobStatus.canApplyTransportAffinities(), jobStatus.getNumAppliedFlexibleConstraints(), - jobStatus.getNumDroppedFlexibleConstraints()); + jobStatus.getNumDroppedFlexibleConstraints(), + jobStatus.getFilteredTraceTag(), + jobStatus.getFilteredDebugTags()); // If the job is immediately ready to run, then we can just immediately // put it in the pending list and try to schedule it. This is especially @@ -2288,7 +2290,9 @@ public class JobSchedulerService extends com.android.server.SystemService cancelled.isConstraintSatisfied(JobStatus.CONSTRAINT_FLEXIBLE), cancelled.canApplyTransportAffinities(), cancelled.getNumAppliedFlexibleConstraints(), - cancelled.getNumDroppedFlexibleConstraints()); + cancelled.getNumDroppedFlexibleConstraints(), + cancelled.getFilteredTraceTag(), + cancelled.getFilteredDebugTags()); } // If this is a replacement, bring in the new version of the job if (incomingJob != null) { @@ -5448,6 +5452,9 @@ public class JobSchedulerService extends com.android.server.SystemService pw.print(Flags.FLAG_THROW_ON_UNSUPPORTED_BIAS_USAGE, Flags.throwOnUnsupportedBiasUsage()); pw.println(); + pw.print(android.app.job.Flags.FLAG_BACKUP_JOBS_EXEMPTION, + android.app.job.Flags.backupJobsExemption()); + pw.println(); pw.decreaseIndent(); pw.println(); diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java index 6f2393adfc7b..0cf6a7a8a4f6 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java @@ -356,6 +356,9 @@ public final class JobSchedulerShellCommand extends BasicShellCommandHandler { case com.android.server.job.Flags.FLAG_THROW_ON_UNSUPPORTED_BIAS_USAGE: pw.println(com.android.server.job.Flags.throwOnUnsupportedBiasUsage()); break; + case android.app.job.Flags.FLAG_BACKUP_JOBS_EXEMPTION: + pw.println(android.app.job.Flags.backupJobsExemption()); + break; default: pw.println("Unknown flag: " + flagName); break; diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java index fe55e2702916..8ab7d2fae49f 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java @@ -541,7 +541,9 @@ public final class JobServiceContext implements ServiceConnection { job.isConstraintSatisfied(JobStatus.CONSTRAINT_FLEXIBLE), job.canApplyTransportAffinities(), job.getNumAppliedFlexibleConstraints(), - job.getNumDroppedFlexibleConstraints()); + job.getNumDroppedFlexibleConstraints(), + job.getFilteredTraceTag(), + job.getFilteredDebugTags()); sEnqueuedJwiAtJobStart.logSampleWithUid(job.getUid(), job.getWorkCount()); final String sourcePackage = job.getSourcePackageName(); if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) { @@ -1630,7 +1632,9 @@ public final class JobServiceContext implements ServiceConnection { completedJob.isConstraintSatisfied(JobStatus.CONSTRAINT_FLEXIBLE), completedJob.canApplyTransportAffinities(), completedJob.getNumAppliedFlexibleConstraints(), - completedJob.getNumDroppedFlexibleConstraints()); + completedJob.getNumDroppedFlexibleConstraints(), + completedJob.getFilteredTraceTag(), + completedJob.getFilteredDebugTags()); if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) { Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_SYSTEM_SERVER, "JobScheduler", getId()); diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java index d39863c85f33..a4df5d829281 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java @@ -48,6 +48,7 @@ import android.util.ArrayMap; import android.util.ArraySet; import android.util.IndentingPrintWriter; import android.util.Pair; +import android.util.Patterns; import android.util.Range; import android.util.Slog; import android.util.TimeUtils; @@ -76,6 +77,7 @@ import java.util.Collections; import java.util.Objects; import java.util.Random; import java.util.function.Predicate; +import java.util.regex.Pattern; /** * Uniquely identifies a job internally. @@ -203,6 +205,17 @@ public final class JobStatus { // TODO(b/129954980): ensure this doesn't spam statsd, especially at boot private static final boolean STATS_LOG_ENABLED = false; + /** + * Simple patterns to match some common forms of PII. This is not intended all-encompassing and + * any clients should aim to do additional filtering. + */ + private static final ArrayMap<Pattern, String> BASIC_PII_FILTERS = new ArrayMap<>(); + + static { + BASIC_PII_FILTERS.put(Patterns.EMAIL_ADDRESS, "[EMAIL]"); + BASIC_PII_FILTERS.put(Patterns.PHONE, "[PHONE]"); + } + // No override. public static final int OVERRIDE_NONE = 0; // Override to improve sorting order. Does not affect constraint evaluation. @@ -250,6 +263,18 @@ public final class JobStatus { private final long mLoggingJobId; /** + * List of tags from {@link JobInfo#getDebugTags()}, filtered using {@link #BASIC_PII_FILTERS}. + * Lazily loaded in {@link #getFilteredDebugTags()}. + */ + @Nullable + private String[] mFilteredDebugTags; + /** + * Trace tag from {@link JobInfo#getTraceTag()}, filtered using {@link #BASIC_PII_FILTERS}. + * Lazily loaded in {@link #getFilteredTraceTag()}. + */ + @Nullable + private String mFilteredTraceTag; + /** * Tag to identify the wakelock held for this job. Lazily loaded in * {@link #getWakelockTag()} since it's not typically needed until the job is about to run. */ @@ -1325,6 +1350,47 @@ public final class JobStatus { return batteryName; } + @VisibleForTesting + @NonNull + static String applyBasicPiiFilters(@NonNull String val) { + for (int i = BASIC_PII_FILTERS.size() - 1; i >= 0; --i) { + val = BASIC_PII_FILTERS.keyAt(i).matcher(val).replaceAll(BASIC_PII_FILTERS.valueAt(i)); + } + return val; + } + + /** + * List of tags from {@link JobInfo#getDebugTags()}, filtered using a basic set of PII filters. + */ + @NonNull + public String[] getFilteredDebugTags() { + if (mFilteredDebugTags != null) { + return mFilteredDebugTags; + } + final ArraySet<String> debugTags = job.getDebugTagsArraySet(); + mFilteredDebugTags = new String[debugTags.size()]; + for (int i = 0; i < mFilteredDebugTags.length; ++i) { + mFilteredDebugTags[i] = applyBasicPiiFilters(debugTags.valueAt(i)); + } + return mFilteredDebugTags; + } + + /** + * Trace tag from {@link JobInfo#getTraceTag()}, filtered using a basic set of PII filters. + */ + @Nullable + public String getFilteredTraceTag() { + if (mFilteredTraceTag != null) { + return mFilteredTraceTag; + } + final String rawTag = job.getTraceTag(); + if (rawTag == null) { + return null; + } + mFilteredTraceTag = applyBasicPiiFilters(rawTag); + return mFilteredTraceTag; + } + /** Return the String to be used as the tag for the wakelock held for this job. */ @NonNull public String getWakelockTag() { diff --git a/core/api/current.txt b/core/api/current.txt index 3f0d10688466..66feebc6cc92 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -277,6 +277,7 @@ package android { field @FlaggedApi("android.companion.flags.device_presence") public static final String REQUEST_OBSERVE_DEVICE_UUID_PRESENCE = "android.permission.REQUEST_OBSERVE_DEVICE_UUID_PRESENCE"; field public static final String REQUEST_PASSWORD_COMPLEXITY = "android.permission.REQUEST_PASSWORD_COMPLEXITY"; field @Deprecated public static final String RESTART_PACKAGES = "android.permission.RESTART_PACKAGES"; + field @FlaggedApi("android.app.job.backup_jobs_exemption") public static final String RUN_BACKUP_JOBS = "android.permission.RUN_BACKUP_JOBS"; field public static final String RUN_USER_INITIATED_JOBS = "android.permission.RUN_USER_INITIATED_JOBS"; field public static final String SCHEDULE_EXACT_ALARM = "android.permission.SCHEDULE_EXACT_ALARM"; field public static final String SEND_RESPOND_VIA_MESSAGE = "android.permission.SEND_RESPOND_VIA_MESSAGE"; @@ -284,6 +285,7 @@ package android { field public static final String SET_ALARM = "com.android.alarm.permission.SET_ALARM"; field public static final String SET_ALWAYS_FINISH = "android.permission.SET_ALWAYS_FINISH"; field public static final String SET_ANIMATION_SCALE = "android.permission.SET_ANIMATION_SCALE"; + field @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") public static final String SET_BIOMETRIC_DIALOG_LOGO = "android.permission.SET_BIOMETRIC_DIALOG_LOGO"; field public static final String SET_DEBUG_APP = "android.permission.SET_DEBUG_APP"; field @Deprecated public static final String SET_PREFERRED_APPLICATIONS = "android.permission.SET_PREFERRED_APPLICATIONS"; field public static final String SET_PROCESS_LIMIT = "android.permission.SET_PROCESS_LIMIT"; @@ -442,6 +444,7 @@ package android { field public static final int alertDialogTheme = 16843529; // 0x1010309 field public static final int alignmentMode = 16843642; // 0x101037a field public static final int allContactsName = 16843468; // 0x10102cc + field @FlaggedApi("android.content.pm.relative_reference_intent_filters") public static final int allow; field public static final int allowAudioPlaybackCapture = 16844289; // 0x1010601 field public static final int allowBackup = 16843392; // 0x1010280 field public static final int allowClearUserData = 16842757; // 0x1010005 @@ -845,6 +848,7 @@ package android { field public static final int format24Hour = 16843723; // 0x10103cb field public static final int fraction = 16843992; // 0x10104d8 field public static final int fragment = 16843491; // 0x10102e3 + field @FlaggedApi("android.content.pm.relative_reference_intent_filters") public static final int fragmentAdvancedPattern; field public static final int fragmentAllowEnterTransitionOverlap = 16843976; // 0x10104c8 field public static final int fragmentAllowReturnTransitionOverlap = 16843977; // 0x10104c9 field public static final int fragmentCloseEnterAnimation = 16843495; // 0x10102e7 @@ -855,10 +859,13 @@ package android { field public static final int fragmentFadeExitAnimation = 16843498; // 0x10102ea field public static final int fragmentOpenEnterAnimation = 16843493; // 0x10102e5 field public static final int fragmentOpenExitAnimation = 16843494; // 0x10102e6 + field @FlaggedApi("android.content.pm.relative_reference_intent_filters") public static final int fragmentPattern; + field @FlaggedApi("android.content.pm.relative_reference_intent_filters") public static final int fragmentPrefix; field public static final int fragmentReenterTransition = 16843975; // 0x10104c7 field public static final int fragmentReturnTransition = 16843973; // 0x10104c5 field public static final int fragmentSharedElementEnterTransition = 16843972; // 0x10104c4 field public static final int fragmentSharedElementReturnTransition = 16843974; // 0x10104c6 + field @FlaggedApi("android.content.pm.relative_reference_intent_filters") public static final int fragmentSuffix; field public static final int freezesText = 16843116; // 0x101016c field public static final int fromAlpha = 16843210; // 0x10101ca field public static final int fromDegrees = 16843187; // 0x10101b3 @@ -1327,10 +1334,15 @@ package android { field public static final int propertyYName = 16843893; // 0x1010475 field public static final int protectionLevel = 16842761; // 0x1010009 field public static final int publicKey = 16843686; // 0x10103a6 + field @FlaggedApi("android.content.pm.relative_reference_intent_filters") public static final int query; field public static final int queryActionMsg = 16843227; // 0x10101db + field @FlaggedApi("android.content.pm.relative_reference_intent_filters") public static final int queryAdvancedPattern; field public static final int queryAfterZeroResults = 16843394; // 0x1010282 field public static final int queryBackground = 16843911; // 0x1010487 field public static final int queryHint = 16843608; // 0x1010358 + field @FlaggedApi("android.content.pm.relative_reference_intent_filters") public static final int queryPattern; + field @FlaggedApi("android.content.pm.relative_reference_intent_filters") public static final int queryPrefix; + field @FlaggedApi("android.content.pm.relative_reference_intent_filters") public static final int querySuffix; field public static final int quickContactBadgeStyleSmallWindowLarge = 16843443; // 0x10102b3 field public static final int quickContactBadgeStyleSmallWindowMedium = 16843442; // 0x10102b2 field public static final int quickContactBadgeStyleSmallWindowSmall = 16843441; // 0x10102b1 @@ -11385,10 +11397,12 @@ package android.content { method public final void addDataScheme(String); method public final void addDataSchemeSpecificPart(String, int); method public final void addDataType(String) throws android.content.IntentFilter.MalformedMimeTypeException; + method @FlaggedApi("android.content.pm.relative_reference_intent_filters") public final void addUriRelativeFilterGroup(@NonNull android.content.UriRelativeFilterGroup); method @NonNull public java.util.function.Predicate<android.content.Intent> asPredicate(); method @NonNull public java.util.function.Predicate<android.content.Intent> asPredicateWithTypeResolution(@NonNull android.content.ContentResolver); method public final java.util.Iterator<android.content.IntentFilter.AuthorityEntry> authoritiesIterator(); method public final java.util.Iterator<java.lang.String> categoriesIterator(); + method @FlaggedApi("android.content.pm.relative_reference_intent_filters") public final void clearUriRelativeFilterGroups(); method public final int countActions(); method public final int countCategories(); method public final int countDataAuthorities(); @@ -11396,6 +11410,7 @@ package android.content { method public final int countDataSchemeSpecificParts(); method public final int countDataSchemes(); method public final int countDataTypes(); + method @FlaggedApi("android.content.pm.relative_reference_intent_filters") public final int countUriRelativeFilterGroups(); method public static android.content.IntentFilter create(String, String); method public final int describeContents(); method public void dump(android.util.Printer, String); @@ -11407,6 +11422,7 @@ package android.content { method public final android.os.PatternMatcher getDataSchemeSpecificPart(int); method public final String getDataType(int); method public final int getPriority(); + method @FlaggedApi("android.content.pm.relative_reference_intent_filters") @NonNull public final android.content.UriRelativeFilterGroup getUriRelativeFilterGroup(int); method public final boolean hasAction(String); method public final boolean hasCategory(String); method public final boolean hasDataAuthority(android.net.Uri); @@ -11828,6 +11844,27 @@ package android.content { field public static final long INVALID_TIME = -9223372036854775808L; // 0x8000000000000000L } + @FlaggedApi("android.content.pm.relative_reference_intent_filters") public final class UriRelativeFilter { + ctor public UriRelativeFilter(int, int, @NonNull String); + method @NonNull public String getFilter(); + method public int getPatternType(); + method public int getUriPart(); + method public boolean matchData(@NonNull android.net.Uri); + field public static final int FRAGMENT = 2; // 0x2 + field public static final int PATH = 0; // 0x0 + field public static final int QUERY = 1; // 0x1 + } + + @FlaggedApi("android.content.pm.relative_reference_intent_filters") public final class UriRelativeFilterGroup { + ctor public UriRelativeFilterGroup(int); + method public void addUriRelativeFilter(@NonNull android.content.UriRelativeFilter); + method public int getAction(); + method @NonNull public java.util.Collection<android.content.UriRelativeFilter> getUriRelativeFilters(); + method public boolean matchData(@NonNull android.net.Uri); + field public static final int ACTION_ALLOW = 0; // 0x0 + field public static final int ACTION_BLOCK = 1; // 0x1 + } + } package android.content.om { @@ -18722,8 +18759,8 @@ package android.hardware.biometrics { method @Nullable public int getAllowedAuthenticators(); method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @Nullable public android.hardware.biometrics.PromptContentView getContentView(); method @Nullable public CharSequence getDescription(); - method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @Nullable @RequiresPermission("android.permission.MANAGE_BIOMETRIC_DIALOG") public android.graphics.Bitmap getLogoBitmap(); - method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @DrawableRes @RequiresPermission("android.permission.MANAGE_BIOMETRIC_DIALOG") public int getLogoRes(); + method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @Nullable @RequiresPermission(android.Manifest.permission.SET_BIOMETRIC_DIALOG_LOGO) public android.graphics.Bitmap getLogoBitmap(); + method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @DrawableRes @RequiresPermission(android.Manifest.permission.SET_BIOMETRIC_DIALOG_LOGO) public int getLogoRes(); method @Nullable public CharSequence getNegativeButtonText(); method @Nullable public CharSequence getSubtitle(); method @NonNull public CharSequence getTitle(); @@ -18773,8 +18810,8 @@ package android.hardware.biometrics { method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setContentView(@NonNull android.hardware.biometrics.PromptContentView); method @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setDescription(@NonNull CharSequence); method @Deprecated @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setDeviceCredentialAllowed(boolean); - method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @NonNull @RequiresPermission("android.permission.MANAGE_BIOMETRIC_DIALOG") public android.hardware.biometrics.BiometricPrompt.Builder setLogo(@DrawableRes int); - method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @NonNull @RequiresPermission("android.permission.MANAGE_BIOMETRIC_DIALOG") public android.hardware.biometrics.BiometricPrompt.Builder setLogo(@NonNull android.graphics.Bitmap); + method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @NonNull @RequiresPermission(android.Manifest.permission.SET_BIOMETRIC_DIALOG_LOGO) public android.hardware.biometrics.BiometricPrompt.Builder setLogoBitmap(@NonNull android.graphics.Bitmap); + method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @NonNull @RequiresPermission(android.Manifest.permission.SET_BIOMETRIC_DIALOG_LOGO) public android.hardware.biometrics.BiometricPrompt.Builder setLogoRes(@DrawableRes int); method @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setNegativeButton(@NonNull CharSequence, @NonNull java.util.concurrent.Executor, @NonNull android.content.DialogInterface.OnClickListener); method @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setSubtitle(@NonNull CharSequence); method @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setTitle(@NonNull CharSequence); @@ -33468,6 +33505,7 @@ package android.os { field public static final String DISALLOW_SHARING_ADMIN_CONFIGURED_WIFI = "no_sharing_admin_configured_wifi"; field public static final String DISALLOW_SMS = "no_sms"; field public static final String DISALLOW_SYSTEM_ERROR_DIALOGS = "no_system_error_dialogs"; + field @FlaggedApi("com.android.net.thread.flags.thread_user_restriction_enabled") public static final String DISALLOW_THREAD_NETWORK = "no_thread_network"; field public static final String DISALLOW_ULTRA_WIDEBAND_RADIO = "no_ultra_wideband_radio"; field public static final String DISALLOW_UNIFIED_PASSWORD = "no_unified_password"; field public static final String DISALLOW_UNINSTALL_APPS = "no_uninstall_apps"; @@ -53664,13 +53702,13 @@ package android.view { method @Deprecated public android.view.Display getDefaultDisplay(); method @NonNull public default android.view.WindowMetrics getMaximumWindowMetrics(); method public default boolean isCrossWindowBlurEnabled(); - method @FlaggedApi("com.android.window.flags.surface_control_input_receiver") @NonNull public default android.os.IBinder registerBatchedSurfaceControlInputReceiver(int, @NonNull android.os.IBinder, @NonNull android.view.SurfaceControl, @NonNull android.view.Choreographer, @NonNull android.view.SurfaceControlInputReceiver); + method @FlaggedApi("com.android.window.flags.surface_control_input_receiver") public default void registerBatchedSurfaceControlInputReceiver(int, @NonNull android.os.IBinder, @NonNull android.view.SurfaceControl, @NonNull android.view.Choreographer, @NonNull android.view.SurfaceControlInputReceiver); method @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") public default void registerTrustedPresentationListener(@NonNull android.os.IBinder, @NonNull android.window.TrustedPresentationThresholds, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>); - method @FlaggedApi("com.android.window.flags.surface_control_input_receiver") @NonNull public default android.os.IBinder registerUnbatchedSurfaceControlInputReceiver(int, @NonNull android.os.IBinder, @NonNull android.view.SurfaceControl, @NonNull android.os.Looper, @NonNull android.view.SurfaceControlInputReceiver); + method @FlaggedApi("com.android.window.flags.surface_control_input_receiver") public default void registerUnbatchedSurfaceControlInputReceiver(int, @NonNull android.os.IBinder, @NonNull android.view.SurfaceControl, @NonNull android.os.Looper, @NonNull android.view.SurfaceControlInputReceiver); method public default void removeCrossWindowBlurEnabledListener(@NonNull java.util.function.Consumer<java.lang.Boolean>); method public default void removeProposedRotationListener(@NonNull java.util.function.IntConsumer); method public void removeViewImmediate(android.view.View); - method @FlaggedApi("com.android.window.flags.surface_control_input_receiver") public default void unregisterSurfaceControlInputReceiver(@NonNull android.os.IBinder); + method @FlaggedApi("com.android.window.flags.surface_control_input_receiver") public default void unregisterSurfaceControlInputReceiver(@NonNull android.view.SurfaceControl); method @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") public default void unregisterTrustedPresentationListener(@NonNull java.util.function.Consumer<java.lang.Boolean>); field public static final String PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE = "android.window.PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE"; field public static final String PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED = "android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED"; diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 39eb6a32f176..017de17b5416 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -129,6 +129,7 @@ package android { field public static final String DISABLE_SYSTEM_SOUND_EFFECTS = "android.permission.DISABLE_SYSTEM_SOUND_EFFECTS"; field public static final String DISPATCH_PROVISIONING_MESSAGE = "android.permission.DISPATCH_PROVISIONING_MESSAGE"; field public static final String DOMAIN_VERIFICATION_AGENT = "android.permission.DOMAIN_VERIFICATION_AGENT"; + field @FlaggedApi("android.content.pm.emergency_install_permission") public static final String EMERGENCY_INSTALL_PACKAGES = "android.permission.EMERGENCY_INSTALL_PACKAGES"; field public static final String ENTER_CAR_MODE_PRIORITIZED = "android.permission.ENTER_CAR_MODE_PRIORITIZED"; field public static final String EXEMPT_FROM_AUDIO_RECORD_RESTRICTIONS = "android.permission.EXEMPT_FROM_AUDIO_RECORD_RESTRICTIONS"; field public static final String FORCE_BACK = "android.permission.FORCE_BACK"; @@ -871,6 +872,10 @@ package android.app { field @NonNull public static final android.os.Parcelable.Creator<android.app.AppOpsManager.PackageOps> CREATOR; } + @FlaggedApi("android.app.bic_client") public final class BackgroundInstallControlManager { + method @FlaggedApi("android.app.bic_client") @NonNull @RequiresPermission(android.Manifest.permission.GET_BACKGROUND_INSTALLED_PACKAGES) public java.util.List<android.content.pm.PackageInfo> getBackgroundInstalledPackages(long); + } + public class BroadcastOptions { method public void clearRequireCompatChange(); method public int getPendingIntentBackgroundActivityStartMode(); @@ -4227,6 +4232,7 @@ package android.content.pm { public final class UserProperties implements android.os.Parcelable { method public int describeContents(); method public int getCrossProfileContentSharingStrategy(); + method @FlaggedApi("android.multiuser.support_hiding_profiles") @NonNull public int getProfileApiVisibility(); method public int getShowInQuietMode(); method public int getShowInSharingSurfaces(); method public boolean isCredentialShareableWithParent(); @@ -4236,6 +4242,9 @@ package android.content.pm { field public static final int CROSS_PROFILE_CONTENT_SHARING_DELEGATE_FROM_PARENT = 1; // 0x1 field public static final int CROSS_PROFILE_CONTENT_SHARING_NO_DELEGATION = 0; // 0x0 field public static final int CROSS_PROFILE_CONTENT_SHARING_UNKNOWN = -1; // 0xffffffff + field @FlaggedApi("android.multiuser.support_hiding_profiles") public static final int PROFILE_API_VISIBILITY_HIDDEN = 1; // 0x1 + field @FlaggedApi("android.multiuser.support_hiding_profiles") public static final int PROFILE_API_VISIBILITY_UNKNOWN = -1; // 0xffffffff + field @FlaggedApi("android.multiuser.support_hiding_profiles") public static final int PROFILE_API_VISIBILITY_VISIBLE = 0; // 0x0 field public static final int SHOW_IN_QUIET_MODE_DEFAULT = 2; // 0x2 field public static final int SHOW_IN_QUIET_MODE_HIDDEN = 1; // 0x1 field public static final int SHOW_IN_QUIET_MODE_PAUSED = 0; // 0x0 @@ -14772,6 +14781,7 @@ package android.telephony { method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean matchesCurrentSimOperator(@NonNull String, int, @Nullable String); method public boolean needsOtaServiceProvisioning(); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void notifyOtaEmergencyNumberDbInstalled(); + method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") @RequiresPermission(android.Manifest.permission.DUMP) public void persistEmergencyCallDiagnosticData(@NonNull String, @NonNull android.telephony.TelephonyManager.EmergencyCallDiagnosticParams); method @RequiresPermission(android.Manifest.permission.REBOOT) public int prepareForUnattendedReboot(); method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean rebootRadio(); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerCarrierPrivilegesCallback(int, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyManager.CarrierPrivilegesCallback); @@ -14950,6 +14960,21 @@ package android.telephony { method public default void onCarrierServiceChanged(@Nullable String, int); } + @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public static final class TelephonyManager.EmergencyCallDiagnosticParams { + method public long getLogcatCollectionStartTimeMillis(); + method public boolean isLogcatCollectionEnabled(); + method public boolean isTelecomDumpSysCollectionEnabled(); + method public boolean isTelephonyDumpSysCollectionEnabled(); + } + + public static final class TelephonyManager.EmergencyCallDiagnosticParams.Builder { + ctor public TelephonyManager.EmergencyCallDiagnosticParams.Builder(); + method @NonNull public android.telephony.TelephonyManager.EmergencyCallDiagnosticParams build(); + method @NonNull public android.telephony.TelephonyManager.EmergencyCallDiagnosticParams.Builder setLogcatCollectionStartTimeMillis(long); + method @NonNull public android.telephony.TelephonyManager.EmergencyCallDiagnosticParams.Builder setTelecomDumpSysCollectionEnabled(boolean); + method @NonNull public android.telephony.TelephonyManager.EmergencyCallDiagnosticParams.Builder setTelephonyDumpSysCollectionEnabled(boolean); + } + public static class TelephonyManager.ModemActivityInfoException extends java.lang.Exception { ctor public TelephonyManager.ModemActivityInfoException(int); method public int getErrorCode(); diff --git a/core/api/test-current.txt b/core/api/test-current.txt index b8b98a3cb3ba..7a3d320dddab 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -3646,6 +3646,7 @@ package android.view { public interface WindowManager extends android.view.ViewManager { method public default int getDisplayImePolicy(int); + method @FlaggedApi("com.android.window.flags.surface_control_input_receiver") @Nullable public default android.os.IBinder getSurfaceControlInputClientToken(@NonNull android.view.SurfaceControl); method public static boolean hasWindowExtensionsEnabled(); method public default void holdLock(android.os.IBinder, int); method public default boolean isGlobalKey(int); diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java index 57fca74e7e59..4a566db3afb3 100644 --- a/core/java/android/app/ActivityOptions.java +++ b/core/java/android/app/ActivityOptions.java @@ -184,6 +184,12 @@ public class ActivityOptions extends ComponentOptions { public static final String KEY_ANIM_START_LISTENER = "android:activity.animStartListener"; /** + * Callback for when animation is aborted. + * @hide + */ + private static final String KEY_ANIM_ABORT_LISTENER = "android:activity.animAbortListener"; + + /** * Specific a theme for a splash screen window. * @hide */ @@ -459,6 +465,7 @@ public class ActivityOptions extends ComponentOptions { private int mHeight; private IRemoteCallback mAnimationStartedListener; private IRemoteCallback mAnimationFinishedListener; + private IRemoteCallback mAnimationAbortListener; private SceneTransitionInfo mSceneTransitionInfo; private PendingIntent mUsageTimeReport; private int mLaunchDisplayId = INVALID_DISPLAY; @@ -716,6 +723,14 @@ public class ActivityOptions extends ComponentOptions { } /** + * Callback for finding out when the given animation is finished + * @hide + */ + public void setOnAnimationFinishedListener(IRemoteCallback listener) { + mAnimationFinishedListener = listener; + } + + /** * Callback for finding out when the given animation has drawn its last frame. * @hide */ @@ -728,6 +743,14 @@ public class ActivityOptions extends ComponentOptions { } /** + * Callback for finding out when the given animation is aborted + * @hide + */ + public void setOnAnimationAbortListener(IRemoteCallback listener) { + mAnimationAbortListener = listener; + } + + /** * Create an ActivityOptions specifying an animation where the new * activity is scaled from a small originating area of the screen to * its final full representation. @@ -1327,6 +1350,8 @@ public class ActivityOptions extends ComponentOptions { KEY_PENDING_INTENT_CREATOR_BACKGROUND_ACTIVITY_START_MODE, MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED); mDisableStartingWindow = opts.getBoolean(KEY_DISABLE_STARTING_WINDOW); + mAnimationAbortListener = IRemoteCallback.Stub.asInterface( + opts.getBinder(KEY_ANIM_ABORT_LISTENER)); } /** @@ -1427,11 +1452,15 @@ public class ActivityOptions extends ComponentOptions { /** @hide */ public void abort() { - if (mAnimationStartedListener != null) { + sendResultIgnoreErrors(mAnimationStartedListener, null); + sendResultIgnoreErrors(mAnimationAbortListener, null); + } + + private void sendResultIgnoreErrors(IRemoteCallback callback, Bundle data) { + if (callback != null) { try { - mAnimationStartedListener.sendResult(null); - } catch (RemoteException e) { - } + callback.sendResult(data); + } catch (RemoteException e) { } } } @@ -2110,12 +2139,7 @@ public class ActivityOptions extends ComponentOptions { mCustomExitResId = otherOptions.mCustomExitResId; mCustomBackgroundColor = otherOptions.mCustomBackgroundColor; mThumbnail = null; - if (mAnimationStartedListener != null) { - try { - mAnimationStartedListener.sendResult(null); - } catch (RemoteException e) { - } - } + sendResultIgnoreErrors(mAnimationStartedListener, null); mAnimationStartedListener = otherOptions.mAnimationStartedListener; break; case ANIM_CUSTOM_IN_PLACE: @@ -2126,12 +2150,7 @@ public class ActivityOptions extends ComponentOptions { mStartY = otherOptions.mStartY; mWidth = otherOptions.mWidth; mHeight = otherOptions.mHeight; - if (mAnimationStartedListener != null) { - try { - mAnimationStartedListener.sendResult(null); - } catch (RemoteException e) { - } - } + sendResultIgnoreErrors(mAnimationStartedListener, null); mAnimationStartedListener = null; break; case ANIM_THUMBNAIL_SCALE_UP: @@ -2143,12 +2162,7 @@ public class ActivityOptions extends ComponentOptions { mStartY = otherOptions.mStartY; mWidth = otherOptions.mWidth; mHeight = otherOptions.mHeight; - if (mAnimationStartedListener != null) { - try { - mAnimationStartedListener.sendResult(null); - } catch (RemoteException e) { - } - } + sendResultIgnoreErrors(mAnimationStartedListener, null); mAnimationStartedListener = otherOptions.mAnimationStartedListener; break; case ANIM_SCENE_TRANSITION: @@ -2165,6 +2179,9 @@ public class ActivityOptions extends ComponentOptions { mRemoteAnimationAdapter = otherOptions.mRemoteAnimationAdapter; mLaunchIntoPipParams = otherOptions.mLaunchIntoPipParams; mIsEligibleForLegacyPermissionPrompt = otherOptions.mIsEligibleForLegacyPermissionPrompt; + + sendResultIgnoreErrors(mAnimationAbortListener, null); + mAnimationAbortListener = otherOptions.mAnimationAbortListener; } /** @@ -2363,6 +2380,8 @@ public class ActivityOptions extends ComponentOptions { if (mDisableStartingWindow) { b.putBoolean(KEY_DISABLE_STARTING_WINDOW, mDisableStartingWindow); } + b.putBinder(KEY_ANIM_ABORT_LISTENER, + mAnimationAbortListener != null ? mAnimationAbortListener.asBinder() : null); return b; } diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 1bdbd4c50634..5d2a26e13ee7 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -319,6 +319,10 @@ public final class ActivityThread extends ClientTransactionHandler public static final int SERVICE_DONE_EXECUTING_START = 1; /** Type for IActivityManager.serviceDoneExecuting: done stopping (destroying) service */ public static final int SERVICE_DONE_EXECUTING_STOP = 2; + /** Type for IActivityManager.serviceDoneExecuting: done with an onRebind call */ + public static final int SERVICE_DONE_EXECUTING_REBIND = 3; + /** Type for IActivityManager.serviceDoneExecuting: done with an onUnbind call */ + public static final int SERVICE_DONE_EXECUTING_UNBIND = 4; /** Use foreground GC policy (less pause time) and higher JIT weight. */ private static final int VM_PROCESS_STATE_JANK_PERCEPTIBLE = 0; @@ -4882,7 +4886,7 @@ public final class ActivityThread extends ClientTransactionHandler mServices.put(data.token, service); try { ActivityManager.getService().serviceDoneExecuting( - data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0); + data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0, null); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -4913,7 +4917,7 @@ public final class ActivityThread extends ClientTransactionHandler } else { s.onRebind(data.intent); ActivityManager.getService().serviceDoneExecuting( - data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0); + data.token, SERVICE_DONE_EXECUTING_REBIND, 0, 0, data.intent); } } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); @@ -4943,7 +4947,7 @@ public final class ActivityThread extends ClientTransactionHandler data.token, data.intent, doRebind); } else { ActivityManager.getService().serviceDoneExecuting( - data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0); + data.token, SERVICE_DONE_EXECUTING_UNBIND, 0, 0, data.intent); } } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); @@ -5057,7 +5061,7 @@ public final class ActivityThread extends ClientTransactionHandler try { ActivityManager.getService().serviceDoneExecuting( - data.token, SERVICE_DONE_EXECUTING_START, data.startId, res); + data.token, SERVICE_DONE_EXECUTING_START, data.startId, res, null); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -5089,7 +5093,7 @@ public final class ActivityThread extends ClientTransactionHandler try { ActivityManager.getService().serviceDoneExecuting( - token, SERVICE_DONE_EXECUTING_STOP, 0, 0); + token, SERVICE_DONE_EXECUTING_STOP, 0, 0, null); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index ccd8456129eb..00c4b0f6515f 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -1548,9 +1548,16 @@ public class AppOpsManager { public static final int OP_READ_SYSTEM_GRAMMATICAL_GENDER = AppProtoEnums.APP_OP_READ_SYSTEM_GRAMMATICAL_GENDER; + /** + * Allows an app whose primary use case is to backup or sync content to run longer jobs. + * + * @hide + */ + public static final int OP_RUN_BACKUP_JOBS = AppProtoEnums.APP_OP_RUN_BACKUP_JOBS; + /** @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public static final int _NUM_OP = 144; + public static final int _NUM_OP = 145; /** * All app ops represented as strings. @@ -1700,6 +1707,7 @@ public class AppOpsManager { OPSTR_ENABLE_MOBILE_DATA_BY_USER, OPSTR_RESERVED_FOR_TESTING, OPSTR_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER, + OPSTR_RUN_BACKUP_JOBS, }) public @interface AppOpString {} @@ -2392,6 +2400,13 @@ public class AppOpsManager { public static final String OPSTR_READ_SYSTEM_GRAMMATICAL_GENDER = "android:read_system_grammatical_gender"; + /** + * Allows an app whose primary use case is to backup or sync content to run longer jobs. + * + * @hide + */ + public static final String OPSTR_RUN_BACKUP_JOBS = "android:run_backup_jobs"; + /** {@link #sAppOpsToNote} not initialized yet for this op */ private static final byte SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED = 0; /** Should not collect noting of this app-op in {@link #sAppOpsToNote} */ @@ -2504,6 +2519,7 @@ public class AppOpsManager { OP_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA, OP_MEDIA_ROUTING_CONTROL, OP_READ_SYSTEM_GRAMMATICAL_GENDER, + OP_RUN_BACKUP_JOBS, }; static final AppOpInfo[] sAppOpInfos = new AppOpInfo[]{ @@ -2961,6 +2977,8 @@ public class AppOpsManager { // will make it an app-op permission in the future. // .setPermission(Manifest.permission.READ_SYSTEM_GRAMMATICAL_GENDER) .build(), + new AppOpInfo.Builder(OP_RUN_BACKUP_JOBS, OPSTR_RUN_BACKUP_JOBS, "RUN_BACKUP_JOBS") + .setPermission(Manifest.permission.RUN_BACKUP_JOBS).build(), }; // The number of longs needed to form a full bitmask of app ops diff --git a/core/java/android/app/BackgroundInstallControlManager.java b/core/java/android/app/BackgroundInstallControlManager.java new file mode 100644 index 000000000000..664fcebcfc05 --- /dev/null +++ b/core/java/android/app/BackgroundInstallControlManager.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app; + +import static android.Manifest.permission.GET_BACKGROUND_INSTALLED_PACKAGES; +import static android.annotation.SystemApi.Client.PRIVILEGED_APPS; + +import android.annotation.FlaggedApi; +import android.annotation.NonNull; +import android.annotation.RequiresPermission; +import android.annotation.SystemApi; +import android.annotation.SystemService; +import android.content.Context; +import android.content.pm.IBackgroundInstallControlService; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.os.RemoteException; +import android.os.ServiceManager; + +import java.util.List; + +/** + * BackgroundInstallControlManager client allows apps to query apps installed in background. + * + * <p>Any applications that was installed without an accompanying installer UI activity paired + * with recorded user interaction event is considered background installed. This is determined by + * analysis of user-activity logs. + * + * <p>Warning: BackgroundInstallControl should not be considered a definitive + * authority of identifying background installed applications. Consumers can use this as a + * supplementary signal, but must perform additional due diligence to confirm the install nature + * of the package. + * + * @hide + */ +@FlaggedApi(Flags.FLAG_BIC_CLIENT) +@SystemApi(client = PRIVILEGED_APPS) +@SystemService(Context.BACKGROUND_INSTALL_CONTROL_SERVICE) +public final class BackgroundInstallControlManager { + + private static final String TAG = "BackgroundInstallControlManager"; + private static IBackgroundInstallControlService sService; + private final Context mContext; + + BackgroundInstallControlManager(Context context) { + mContext = context; + } + + private static IBackgroundInstallControlService getService() { + if (sService == null) { + sService = + IBackgroundInstallControlService.Stub.asInterface( + ServiceManager.getService(Context.BACKGROUND_INSTALL_CONTROL_SERVICE)); + } + return sService; + } + + /** + * Returns a full list of {@link PackageInfo} of apps currently installed for the current user + * that are considered installed in the background. + * + * <p>Refer to top level doc {@link BackgroundInstallControlManager} for more details on + * background-installed applications. + * <p> + * + * @param flags - Flags will be used to call + * {@link PackageManager#getInstalledPackages(PackageInfoFlags)} to retrieve installed packages. + * @return A list of packages retrieved from {@link PackageManager} with non-background + * installed app filter applied. + * + * @hide + */ + @FlaggedApi(Flags.FLAG_BIC_CLIENT) + @SystemApi + @RequiresPermission(GET_BACKGROUND_INSTALLED_PACKAGES) + public @NonNull List<PackageInfo> getBackgroundInstalledPackages( + @PackageManager.PackageInfoFlagsBits long flags) { + List<PackageInfo> backgroundInstalledPackages; + try { + return getService() + .getBackgroundInstalledPackages(flags, mContext.getUserId()) + .getList(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + +} diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index 47403d26c907..b063d04655f5 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -294,7 +294,8 @@ interface IActivityManager { @UnsupportedAppUsage ParceledListSlice getRecentTasks(int maxNum, int flags, int userId); @UnsupportedAppUsage - oneway void serviceDoneExecuting(in IBinder token, int type, int startId, int res); + oneway void serviceDoneExecuting(in IBinder token, int type, int startId, int res, + in Intent intent); /** @deprecated Use {@link #getIntentSenderWithFeature} instead */ @UnsupportedAppUsage(maxTargetSdk=29, publicAlternatives="Use {@link PendingIntent#getIntentSender()} instead") IIntentSender getIntentSender(int type, in String packageName, in IBinder token, diff --git a/core/java/android/app/OWNERS b/core/java/android/app/OWNERS index 0760d4db9169..3b5bba20a10b 100644 --- a/core/java/android/app/OWNERS +++ b/core/java/android/app/OWNERS @@ -90,8 +90,8 @@ per-file InstantAppResolveInfo.aidl = file:/services/core/java/com/android/serve per-file pinner-client.aconfig = file:/core/java/android/app/pinner/OWNERS # BackgroundInstallControlManager -per-file BackgroundInstallControlManager.java = file:/services/core/java/com/android/server/pm/OWNERS -per-file background_install_control_manager.aconfig = file:/services/core/java/com/android/server/pm/OWNERS +per-file BackgroundInstallControlManager.java = file:/services/core/java/com/android/server/pm/BACKGROUND_INSTALL_OWNERS +per-file background_install_control_manager.aconfig = file:/services/core/java/com/android/server/pm/BACKGROUND_INSTALL_OWNERS # ResourcesManager per-file ResourcesManager.java = file:RESOURCES_OWNERS diff --git a/core/java/android/content/IntentFilter.java b/core/java/android/content/IntentFilter.java index ad3acd713c6b..79af65a3a3e5 100644 --- a/core/java/android/content/IntentFilter.java +++ b/core/java/android/content/IntentFilter.java @@ -16,11 +16,13 @@ package android.content; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.compat.annotation.UnsupportedAppUsage; +import android.content.pm.Flags; import android.net.Uri; import android.os.Build; import android.os.Bundle; @@ -175,6 +177,7 @@ public class IntentFilter implements Parcelable { private static final String ACTION_STR = "action"; private static final String AUTO_VERIFY_STR = "autoVerify"; private static final String EXTRAS_STR = "extras"; + private static final String URI_RELATIVE_FILTER_GROUP_STR = "uriRelativeFilterGroup"; private static final int[] EMPTY_INT_ARRAY = new int[0]; private static final long[] EMPTY_LONG_ARRAY = new long[0]; @@ -324,6 +327,7 @@ public class IntentFilter implements Parcelable { private ArrayList<PatternMatcher> mDataSchemeSpecificParts = null; private ArrayList<AuthorityEntry> mDataAuthorities = null; private ArrayList<PatternMatcher> mDataPaths = null; + private ArrayList<UriRelativeFilterGroup> mUriRelativeFilterGroups = null; private ArrayList<String> mStaticDataTypes = null; private ArrayList<String> mDataTypes = null; private ArrayList<String> mMimeGroups = null; @@ -520,6 +524,10 @@ public class IntentFilter implements Parcelable { if (o.mDataPaths != null) { mDataPaths = new ArrayList<PatternMatcher>(o.mDataPaths); } + if (o.mUriRelativeFilterGroups != null) { + mUriRelativeFilterGroups = + new ArrayList<UriRelativeFilterGroup>(o.mUriRelativeFilterGroups); + } if (o.mMimeGroups != null) { mMimeGroups = new ArrayList<String>(o.mMimeGroups); } @@ -1563,6 +1571,63 @@ public class IntentFilter implements Parcelable { } /** + * Add a new URI relative filter group to match against the Intent data. The + * intent filter must include one or more schemes (via {@link #addDataScheme}) + * <em>and</em> one or more authorities (via {@link #addDataAuthority}) for + * the group to be considered. + * + * <p>Groups will be matched in the order they were added and matching will only + * be done if no data paths match or if none are included. If both data paths and + * groups are not included, then only the scheme/authority must match.</p> + * + * @param group A {@link UriRelativeFilterGroup} to match the URI. + * + * @see UriRelativeFilterGroup + * @see #matchData + * @see #addDataScheme + * @see #addDataAuthority + */ + @FlaggedApi(Flags.FLAG_RELATIVE_REFERENCE_INTENT_FILTERS) + public final void addUriRelativeFilterGroup(@NonNull UriRelativeFilterGroup group) { + Objects.requireNonNull(group); + if (mUriRelativeFilterGroups == null) { + mUriRelativeFilterGroups = new ArrayList<>(); + } + mUriRelativeFilterGroups.add(group); + } + + /** + * Return the number of URI relative filter groups in the intent filter. + */ + @FlaggedApi(Flags.FLAG_RELATIVE_REFERENCE_INTENT_FILTERS) + public final int countUriRelativeFilterGroups() { + return mUriRelativeFilterGroups == null ? 0 : mUriRelativeFilterGroups.size(); + } + + /** + * Return a URI relative filter group in the intent filter. + * + * <p>Note: use of this method will result in a NullPointerException + * if no groups exists for this intent filter.</p> + * + * @param index index of the element to return + * @throws IndexOutOfBoundsException if index is out of range + */ + @FlaggedApi(Flags.FLAG_RELATIVE_REFERENCE_INTENT_FILTERS) + @NonNull + public final UriRelativeFilterGroup getUriRelativeFilterGroup(int index) { + return mUriRelativeFilterGroups.get(index); + } + + /** + * Removes all existing URI relative filter groups in the intent filter. + */ + @FlaggedApi(Flags.FLAG_RELATIVE_REFERENCE_INTENT_FILTERS) + public final void clearUriRelativeFilterGroups() { + mUriRelativeFilterGroups = null; + } + + /** * Match this intent filter against the given Intent data. This ignores * the data scheme -- unlike {@link #matchData}, the authority will match * regardless of whether there is a matching scheme. @@ -1677,12 +1742,24 @@ public class IntentFilter implements Parcelable { int authMatch = matchDataAuthority(data, wildcardSupported); if (authMatch >= 0) { final ArrayList<PatternMatcher> paths = mDataPaths; - if (paths == null) { - match = authMatch; - } else if (hasDataPath(data.getPath(), wildcardSupported)) { - match = MATCH_CATEGORY_PATH; + final ArrayList<UriRelativeFilterGroup> groups = mUriRelativeFilterGroups; + if (Flags.relativeReferenceIntentFilters()) { + if (paths == null && groups == null) { + match = authMatch; + } else if (hasDataPath(data.getPath(), wildcardSupported) + || matchRelRefGroups(data)) { + match = MATCH_CATEGORY_PATH; + } else { + return NO_MATCH_DATA; + } } else { - return NO_MATCH_DATA; + if (paths == null) { + match = authMatch; + } else if (hasDataPath(data.getPath(), wildcardSupported)) { + match = MATCH_CATEGORY_PATH; + } else { + return NO_MATCH_DATA; + } } } else { return NO_MATCH_DATA; @@ -1726,6 +1803,19 @@ public class IntentFilter implements Parcelable { return match + MATCH_ADJUSTMENT_NORMAL; } + private boolean matchRelRefGroups(Uri data) { + if (mUriRelativeFilterGroups == null) { + return false; + } + for (int i = 0; i < mUriRelativeFilterGroups.size(); i++) { + UriRelativeFilterGroup group = mUriRelativeFilterGroups.get(i); + if (group.matchData(data)) { + return group.getAction() == UriRelativeFilterGroup.ACTION_ALLOW; + } + } + return false; + } + /** * Add a new Intent category to match against. The semantics of * categories is the opposite of actions -- an Intent includes the @@ -2486,6 +2576,12 @@ public class IntentFilter implements Parcelable { } serializer.endTag(null, EXTRAS_STR); } + if (Flags.relativeReferenceIntentFilters()) { + N = countUriRelativeFilterGroups(); + for (int i = 0; i < N; i++) { + mUriRelativeFilterGroups.get(i).writeToXml(serializer); + } + } } /** @@ -2614,6 +2710,9 @@ public class IntentFilter implements Parcelable { } } else if (tagName.equals(EXTRAS_STR)) { mExtras = PersistableBundle.restoreFromXml(parser); + } else if (Flags.relativeReferenceIntentFilters() + && URI_RELATIVE_FILTER_GROUP_STR.equals(tagName)) { + addUriRelativeFilterGroup(new UriRelativeFilterGroup(parser)); } else { Log.w("IntentFilter", "Unknown tag parsing IntentFilter: " + tagName); } @@ -2680,6 +2779,12 @@ public class IntentFilter implements Parcelable { if (mExtras != null) { mExtras.dumpDebug(proto, IntentFilterProto.EXTRAS); } + if (Flags.relativeReferenceIntentFilters() && mUriRelativeFilterGroups != null) { + Iterator<UriRelativeFilterGroup> it = mUriRelativeFilterGroups.iterator(); + while (it.hasNext()) { + it.next().dumpDebug(proto, IntentFilterProto.URI_RELATIVE_FILTER_GROUPS); + } + } proto.end(token); } @@ -2744,6 +2849,15 @@ public class IntentFilter implements Parcelable { du.println(sb.toString()); } } + if (mUriRelativeFilterGroups != null) { + Iterator<UriRelativeFilterGroup> it = mUriRelativeFilterGroups.iterator(); + while (it.hasNext()) { + sb.setLength(0); + sb.append(prefix); sb.append("UriRelativeFilterGroup: \""); + sb.append(it.next()); sb.append("\""); + du.println(sb.toString()); + } + } if (mStaticDataTypes != null) { Iterator<String> it = mStaticDataTypes.iterator(); while (it.hasNext()) { @@ -2883,6 +2997,15 @@ public class IntentFilter implements Parcelable { } else { dest.writeInt(0); } + if (Flags.relativeReferenceIntentFilters() && mUriRelativeFilterGroups != null) { + final int N = mUriRelativeFilterGroups.size(); + dest.writeInt(N); + for (int i = 0; i < N; i++) { + mUriRelativeFilterGroups.get(i).writeToParcel(dest, flags); + } + } else { + dest.writeInt(0); + } } /** @@ -2989,6 +3112,13 @@ public class IntentFilter implements Parcelable { if (source.readInt() != 0) { mExtras = PersistableBundle.CREATOR.createFromParcel(source); } + N = source.readInt(); + if (Flags.relativeReferenceIntentFilters() && N > 0) { + mUriRelativeFilterGroups = new ArrayList<UriRelativeFilterGroup>(N); + for (int i = 0; i < N; i++) { + mUriRelativeFilterGroups.add(new UriRelativeFilterGroup(source)); + } + } } private boolean hasPartialTypes() { diff --git a/core/java/android/content/UriRelativeFilter.java b/core/java/android/content/UriRelativeFilter.java new file mode 100644 index 000000000000..9866cd0e992a --- /dev/null +++ b/core/java/android/content/UriRelativeFilter.java @@ -0,0 +1,260 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content; + +import android.annotation.FlaggedApi; +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.pm.Flags; +import android.net.Uri; +import android.os.Parcel; +import android.os.PatternMatcher; +import android.util.proto.ProtoOutputStream; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * A filter for matching Intent URI Data as part of a + * {@link UriRelativeFilterGroup}. A single filter can only be + * matched against either a URI path, query or fragment + */ +@FlaggedApi(Flags.FLAG_RELATIVE_REFERENCE_INTENT_FILTERS) +public final class UriRelativeFilter { + private static final String FILTER_STR = "filter"; + private static final String PART_STR = "part"; + private static final String PATTERN_STR = "pattern"; + static final String URI_RELATIVE_FILTER_STR = "uriRelativeFilter"; + + /** + * Value to indicate that the filter is to be applied to a URI path. + */ + public static final int PATH = 0; + /** + * Value to indicate that the filter is to be applied to a URI query. + */ + public static final int QUERY = 1; + /** + * Value to indicate that the filter is to be applied to a URI fragment. + */ + public static final int FRAGMENT = 2; + + /** @hide */ + @IntDef(value = { + PATH, + QUERY, + FRAGMENT + }) + @Retention(RetentionPolicy.SOURCE) + public @interface UriPart {} + + private final @UriPart int mUriPart; + private final @PatternMatcher.PatternType int mPatternType; + private final String mFilter; + + /** + * Creates a new UriRelativeFilter. + * + * @param uriPart The URI part this filter operates on. Can be either a + * {@link UriRelativeFilter#PATH}, {@link UriRelativeFilter#QUERY}, + * or {@link UriRelativeFilter#FRAGMENT}. + * @param patternType The pattern type of the filter. Can be either a + * {@link PatternMatcher#PATTERN_LITERAL}, + * {@link PatternMatcher#PATTERN_PREFIX}, +* {@link PatternMatcher#PATTERN_SUFFIX}, + * {@link PatternMatcher#PATTERN_SIMPLE_GLOB}, + * or {@link PatternMatcher#PATTERN_ADVANCED_GLOB}. + * @param filter A literal or pattern string depedning on patterType + * used to match a uriPart . + */ + public UriRelativeFilter( + @UriPart int uriPart, + @PatternMatcher.PatternType int patternType, + @NonNull String filter) { + mUriPart = uriPart; + com.android.internal.util.AnnotationValidations.validate( + UriPart.class, null, mUriPart); + mPatternType = patternType; + com.android.internal.util.AnnotationValidations.validate( + PatternMatcher.PatternType.class, null, mPatternType); + mFilter = filter; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mFilter); + } + + /** + * The URI part this filter operates on. + */ + public @UriPart int getUriPart() { + return mUriPart; + } + + /** + * The pattern type of the filter. + */ + public @PatternMatcher.PatternType int getPatternType() { + return mPatternType; + } + + /** + * The string used to filter the URI. + */ + public @NonNull String getFilter() { + return mFilter; + } + + /** + * Match this URI filter against an Intent's data. QUERY filters can + * match against any key value pair in the query string. PATH and + * FRAGMENT filters must match the entire string. + * + * @param data The full data string to match against, as supplied in + * Intent.data. + * + * @return true if there is a match. + */ + public boolean matchData(@NonNull Uri data) { + PatternMatcher pe = new PatternMatcher(mFilter, mPatternType); + switch (getUriPart()) { + case PATH: + return pe.match(data.getPath()); + case QUERY: + return matchQuery(pe, data.getQuery()); + case FRAGMENT: + return pe.match(data.getFragment()); + default: + return false; + } + } + + private boolean matchQuery(PatternMatcher pe, String query) { + if (query != null) { + String[] params = query.split("&"); + if (params.length == 1) { + params = query.split(";"); + } + for (int i = 0; i < params.length; i++) { + if (pe.match(params[i])) return true; + } + } + return false; + } + + /** @hide */ + public void dumpDebug(ProtoOutputStream proto, long fieldId) { + long token = proto.start(fieldId); + proto.write(UriRelativeFilterProto.URI_PART, mUriPart); + proto.write(UriRelativeFilterProto.PATTERN_TYPE, mPatternType); + proto.write(UriRelativeFilterProto.FILTER, mFilter); + proto.end(token); + } + + /** @hide */ + public void writeToXml(XmlSerializer serializer) throws IOException { + serializer.startTag(null, URI_RELATIVE_FILTER_STR); + serializer.attribute(null, PATTERN_STR, Integer.toString(mPatternType)); + serializer.attribute(null, PART_STR, Integer.toString(mUriPart)); + serializer.attribute(null, FILTER_STR, mFilter); + serializer.endTag(null, URI_RELATIVE_FILTER_STR); + } + + private String uriPartToString() { + switch (mUriPart) { + case PATH: + return "PATH"; + case QUERY: + return "QUERY"; + case FRAGMENT: + return "FRAGMENT"; + default: + return "UNKNOWN"; + } + } + + private String patternTypeToString() { + switch (mPatternType) { + case PatternMatcher.PATTERN_LITERAL: + return "LITERAL"; + case PatternMatcher.PATTERN_PREFIX: + return "PREFIX"; + case PatternMatcher.PATTERN_SIMPLE_GLOB: + return "GLOB"; + case PatternMatcher.PATTERN_ADVANCED_GLOB: + return "ADVANCED_GLOB"; + case PatternMatcher.PATTERN_SUFFIX: + return "SUFFIX"; + default: + return "UNKNOWN"; + } + } + + @Override + public String toString() { + return "UriRelativeFilter { " + + "uriPart = " + uriPartToString() + ", " + + "patternType = " + patternTypeToString() + ", " + + "filter = " + mFilter + + " }"; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + @SuppressWarnings("unchecked") + UriRelativeFilter that = (UriRelativeFilter) o; + return mUriPart == that.mUriPart + && mPatternType == that.mPatternType + && java.util.Objects.equals(mFilter, that.mFilter); + } + + @Override + public int hashCode() { + int _hash = 1; + _hash = 31 * _hash + mUriPart; + _hash = 31 * _hash + mPatternType; + _hash = 31 * _hash + java.util.Objects.hashCode(mFilter); + return _hash; + } + + /** @hide */ + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mUriPart); + dest.writeInt(mPatternType); + dest.writeString(mFilter); + } + + /** @hide */ + UriRelativeFilter(@NonNull android.os.Parcel in) { + mUriPart = in.readInt(); + mPatternType = in.readInt(); + mFilter = in.readString(); + } + + /** @hide */ + public UriRelativeFilter(XmlPullParser parser) throws XmlPullParserException, IOException { + mUriPart = Integer.parseInt(parser.getAttributeValue(null, PART_STR)); + mPatternType = Integer.parseInt(parser.getAttributeValue(null, PATTERN_STR)); + mFilter = parser.getAttributeValue(null, FILTER_STR); + } +} diff --git a/core/java/android/content/UriRelativeFilterGroup.java b/core/java/android/content/UriRelativeFilterGroup.java new file mode 100644 index 000000000000..72c396a73ec8 --- /dev/null +++ b/core/java/android/content/UriRelativeFilterGroup.java @@ -0,0 +1,216 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content; + +import android.annotation.FlaggedApi; +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.content.pm.Flags; +import android.net.Uri; +import android.os.Parcel; +import android.util.ArraySet; +import android.util.Log; +import android.util.proto.ProtoOutputStream; + +import com.android.internal.util.CollectionUtils; +import com.android.internal.util.XmlUtils; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.Objects; + +/** + * An intent data matching group based on a URI's relative reference which + * includes the path, query and fragment. The group is only considered as + * matching if <em>all</em> UriRelativeFilters in the group match. Each + * UriRelativeFilter defines a matching rule for a URI path, query or fragment. + * A group must contain one or more UriRelativeFilters to match but does not need to + * contain UriRelativeFilters for all existing parts of a URI to match. + * + * <p>For example, given a URI that contains path, query and fragment parts, + * a group containing only a path filter will match the URI if the path + * filter matches the URI path. If the group contains a path and query + * filter, then the group will only match if both path and query filters + * match. If a URI contains only a path with no query or fragment then a + * group can only match if it contains only a matching path filter. If the + * group also contained additional query or fragment filters then it will + * not match.</p> + */ +@FlaggedApi(Flags.FLAG_RELATIVE_REFERENCE_INTENT_FILTERS) +public final class UriRelativeFilterGroup { + private static final String ALLOW_STR = "allow"; + private static final String URI_RELATIVE_FILTER_GROUP_STR = "uriRelativeFilterGroup"; + + /** + * Value to indicate that the group match is allowed. + */ + public static final int ACTION_ALLOW = 0; + /** + * Value to indicate that the group match is blocked. + */ + public static final int ACTION_BLOCK = 1; + + /** @hide */ + @IntDef(value = { + ACTION_ALLOW, + ACTION_BLOCK + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Action {} + + private final @Action int mAction; + private final ArraySet<UriRelativeFilter> mUriRelativeFilters = new ArraySet<>(); + + /** + * New UriRelativeFilterGroup that matches a Intent data. + * + * @param action Whether this matching group should be allowed or disallowed. + */ + public UriRelativeFilterGroup(@Action int action) { + mAction = action; + } + + /** @hide */ + public UriRelativeFilterGroup(XmlPullParser parser) throws XmlPullParserException, IOException { + mAction = Integer.parseInt(parser.getAttributeValue(null, ALLOW_STR)); + + int outerDepth = parser.getDepth(); + int type; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG + || parser.getDepth() > outerDepth)) { + if (type == XmlPullParser.END_TAG + || type == XmlPullParser.TEXT) { + continue; + } + + String tagName = parser.getName(); + if (tagName.equals(UriRelativeFilter.URI_RELATIVE_FILTER_STR)) { + addUriRelativeFilter(new UriRelativeFilter(parser)); + } else { + Log.w("IntentFilter", "Unknown tag parsing IntentFilter: " + tagName); + } + XmlUtils.skipCurrentTag(parser); + } + } + + /** + * Return {@link UriRelativeFilterGroup#ACTION_ALLOW} if a URI is allowed when matched + * and {@link UriRelativeFilterGroup#ACTION_BLOCK} if a URI is blacked when matched. + */ + public @Action int getAction() { + return mAction; + } + + /** + * Add a filter to the group. + */ + public void addUriRelativeFilter(@NonNull UriRelativeFilter uriRelativeFilter) { + Objects.requireNonNull(uriRelativeFilter); + if (!CollectionUtils.contains(mUriRelativeFilters, uriRelativeFilter)) { + mUriRelativeFilters.add(uriRelativeFilter); + } + } + + /** + * Returns a unmodifiable view of the UriRelativeFilters list in this group. + */ + @NonNull + public Collection<UriRelativeFilter> getUriRelativeFilters() { + return Collections.unmodifiableCollection(mUriRelativeFilters); + } + + /** + * Match all URI filter in this group against {@link Intent#getData()}. + * + * @param data The full data string to match against, as supplied in + * Intent.data. + * @return true if all filters match. + */ + public boolean matchData(@NonNull Uri data) { + if (mUriRelativeFilters.size() == 0) { + return false; + } + for (UriRelativeFilter filter : mUriRelativeFilters) { + if (!filter.matchData(data)) { + return false; + } + } + return true; + } + + /** @hide */ + public void dumpDebug(ProtoOutputStream proto, long fieldId) { + long token = proto.start(fieldId); + proto.write(UriRelativeFilterGroupProto.ACTION, mAction); + Iterator<UriRelativeFilter> it = mUriRelativeFilters.iterator(); + while (it.hasNext()) { + it.next().dumpDebug(proto, UriRelativeFilterGroupProto.URI_RELATIVE_FILTERS); + } + proto.end(token); + } + + /** @hide */ + public void writeToXml(XmlSerializer serializer) throws IOException { + serializer.startTag(null, URI_RELATIVE_FILTER_GROUP_STR); + serializer.attribute(null, ALLOW_STR, Integer.toString(mAction)); + Iterator<UriRelativeFilter> it = mUriRelativeFilters.iterator(); + while (it.hasNext()) { + UriRelativeFilter filter = it.next(); + filter.writeToXml(serializer); + } + serializer.endTag(null, URI_RELATIVE_FILTER_GROUP_STR); + } + + @Override + public String toString() { + return "UriRelativeFilterGroup { allow = " + mAction + + ", uri_filters = " + mUriRelativeFilters + ", }"; + } + + /** @hide */ + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mAction); + final int n = mUriRelativeFilters.size(); + if (n > 0) { + dest.writeInt(n); + Iterator<UriRelativeFilter> it = mUriRelativeFilters.iterator(); + while (it.hasNext()) { + it.next().writeToParcel(dest, flags); + } + } else { + dest.writeInt(0); + } + } + + /** @hide */ + UriRelativeFilterGroup(@NonNull Parcel src) { + mAction = src.readInt(); + final int n = src.readInt(); + for (int i = 0; i < n; i++) { + mUriRelativeFilters.add(new UriRelativeFilter(src)); + } + } +} diff --git a/core/java/android/content/pm/UserProperties.java b/core/java/android/content/pm/UserProperties.java index 269c6c27821c..1d0e2db78bcb 100644 --- a/core/java/android/content/pm/UserProperties.java +++ b/core/java/android/content/pm/UserProperties.java @@ -16,6 +16,9 @@ package android.content.pm; +import static android.multiuser.Flags.FLAG_SUPPORT_HIDING_PROFILES; + +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -73,7 +76,7 @@ public final class UserProperties implements Parcelable { private static final String ATTR_CROSS_PROFILE_CONTENT_SHARING_STRATEGY = "crossProfileContentSharingStrategy"; - + private static final String ATTR_PROFILE_API_VISIBILITY = "profileApiVisibility"; /** Index values of each property (to indicate whether they are present in this object). */ @IntDef(prefix = "INDEX_", value = { INDEX_SHOW_IN_LAUNCHER, @@ -93,6 +96,7 @@ public final class UserProperties implements Parcelable { INDEX_AUTH_ALWAYS_REQUIRED_TO_DISABLE_QUIET_MODE, INDEX_CROSS_PROFILE_CONTENT_SHARING_STRATEGY, INDEX_ALLOW_STOPPING_USER_WITH_DELAYED_LOCKING, + INDEX_PROFILE_API_VISIBILITY }) @Retention(RetentionPolicy.SOURCE) private @interface PropertyIndex { @@ -114,6 +118,7 @@ public final class UserProperties implements Parcelable { private static final int INDEX_SHOW_IN_SHARING_SURFACES = 14; private static final int INDEX_CROSS_PROFILE_CONTENT_SHARING_STRATEGY = 15; private static final int INDEX_ALLOW_STOPPING_USER_WITH_DELAYED_LOCKING = 16; + private static final int INDEX_PROFILE_API_VISIBILITY = 17; /** A bit set, mapping each PropertyIndex to whether it is present (1) or absent (0). */ private long mPropertiesPresent = 0; @@ -450,6 +455,41 @@ public final class UserProperties implements Parcelable { @SuppressLint("UnflaggedApi") // b/306636213 public static final int CROSS_PROFILE_CONTENT_SHARING_DELEGATE_FROM_PARENT = 1; + /** + * Possible values for the profile visibility in public API surfaces. This indicates whether or + * not the information linked to the profile (userId, package names) should not be returned in + * API surfaces if a user is marked as hidden. + * + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = "PROFILE_API_VISIBILITY_", + value = { + PROFILE_API_VISIBILITY_UNKNOWN, + PROFILE_API_VISIBILITY_VISIBLE, + PROFILE_API_VISIBILITY_HIDDEN, + } + ) + public @interface ProfileApiVisibility { + } + /* + * The api visibility value for this profile user is undefined or unknown. + */ + @FlaggedApi(FLAG_SUPPORT_HIDING_PROFILES) + public static final int PROFILE_API_VISIBILITY_UNKNOWN = -1; + + /** + * Indicates that information about this profile user should be shown in API surfaces. + */ + @FlaggedApi(FLAG_SUPPORT_HIDING_PROFILES) + public static final int PROFILE_API_VISIBILITY_VISIBLE = 0; + + /** + * Indicates that information about this profile should be not be visible in API surfaces. + */ + @FlaggedApi(FLAG_SUPPORT_HIDING_PROFILES) + public static final int PROFILE_API_VISIBILITY_HIDDEN = 1; + /** * Creates a UserProperties (intended for the SystemServer) that stores a reference to the given @@ -510,6 +550,9 @@ public final class UserProperties implements Parcelable { setShowInQuietMode(orig.getShowInQuietMode()); setShowInSharingSurfaces(orig.getShowInSharingSurfaces()); setCrossProfileContentSharingStrategy(orig.getCrossProfileContentSharingStrategy()); + if (android.multiuser.Flags.supportHidingProfiles()) { + setProfileApiVisibility(orig.getProfileApiVisibility()); + } } /** @@ -951,9 +994,31 @@ public final class UserProperties implements Parcelable { } private @CrossProfileContentSharingStrategy int mCrossProfileContentSharingStrategy; + /** + * Returns the visibility of the profile user in API surfaces. Any information linked to the + * profile (userId, package names) should be hidden API surfaces if a user is marked as hidden. + */ + @NonNull + @FlaggedApi(FLAG_SUPPORT_HIDING_PROFILES) + public @ProfileApiVisibility int getProfileApiVisibility() { + if (isPresent(INDEX_PROFILE_API_VISIBILITY)) return mProfileApiVisibility; + if (mDefaultProperties != null) return mDefaultProperties.mProfileApiVisibility; + throw new SecurityException("You don't have permission to query profileApiVisibility"); + } + /** @hide */ + @NonNull + @FlaggedApi(FLAG_SUPPORT_HIDING_PROFILES) + public void setProfileApiVisibility(@ProfileApiVisibility int profileApiVisibility) { + this.mProfileApiVisibility = profileApiVisibility; + setPresent(INDEX_PROFILE_API_VISIBILITY); + } + private @ProfileApiVisibility int mProfileApiVisibility; @Override public String toString() { + String profileApiVisibility = + android.multiuser.Flags.supportHidingProfiles() ? ", mProfileApiVisibility=" + + getProfileApiVisibility() : ""; // Please print in increasing order of PropertyIndex. return "UserProperties{" + "mPropertiesPresent=" + Long.toBinaryString(mPropertiesPresent) @@ -977,6 +1042,7 @@ public final class UserProperties implements Parcelable { + ", mDeleteAppWithParent=" + getDeleteAppWithParent() + ", mAlwaysVisible=" + getAlwaysVisible() + ", mCrossProfileContentSharingStrategy=" + getCrossProfileContentSharingStrategy() + + profileApiVisibility + "}"; } @@ -1010,6 +1076,9 @@ public final class UserProperties implements Parcelable { pw.println(prefix + " mAlwaysVisible=" + getAlwaysVisible()); pw.println(prefix + " mCrossProfileContentSharingStrategy=" + getCrossProfileContentSharingStrategy()); + if (android.multiuser.Flags.supportHidingProfiles()) { + pw.println(prefix + " mProfileApiVisibility=" + getProfileApiVisibility()); + } } /** @@ -1093,6 +1162,12 @@ public final class UserProperties implements Parcelable { break; case ATTR_CROSS_PROFILE_CONTENT_SHARING_STRATEGY: setCrossProfileContentSharingStrategy(parser.getAttributeInt(i)); + break; + case ATTR_PROFILE_API_VISIBILITY: + if (android.multiuser.Flags.supportHidingProfiles()) { + setProfileApiVisibility(parser.getAttributeInt(i)); + } + break; default: Slog.w(LOG_TAG, "Skipping unknown property " + attributeName); } @@ -1175,6 +1250,12 @@ public final class UserProperties implements Parcelable { serializer.attributeInt(null, ATTR_CROSS_PROFILE_CONTENT_SHARING_STRATEGY, mCrossProfileContentSharingStrategy); } + if (isPresent(INDEX_PROFILE_API_VISIBILITY)) { + if (android.multiuser.Flags.supportHidingProfiles()) { + serializer.attributeInt(null, ATTR_PROFILE_API_VISIBILITY, + mProfileApiVisibility); + } + } } // For use only with an object that has already had any permission-lacking fields stripped out. @@ -1198,6 +1279,7 @@ public final class UserProperties implements Parcelable { dest.writeBoolean(mDeleteAppWithParent); dest.writeBoolean(mAlwaysVisible); dest.writeInt(mCrossProfileContentSharingStrategy); + dest.writeInt(mProfileApiVisibility); } /** @@ -1225,6 +1307,7 @@ public final class UserProperties implements Parcelable { mDeleteAppWithParent = source.readBoolean(); mAlwaysVisible = source.readBoolean(); mCrossProfileContentSharingStrategy = source.readInt(); + mProfileApiVisibility = source.readInt(); } @Override @@ -1274,6 +1357,7 @@ public final class UserProperties implements Parcelable { private boolean mAlwaysVisible = false; private @CrossProfileContentSharingStrategy int mCrossProfileContentSharingStrategy = CROSS_PROFILE_CONTENT_SHARING_NO_DELEGATION; + private @ProfileApiVisibility int mProfileApiVisibility = 0; /** * @hide @@ -1428,6 +1512,17 @@ public final class UserProperties implements Parcelable { return this; } + /** + * Sets the value for {@link #mProfileApiVisibility} + * @hide + */ + @NonNull + @FlaggedApi(FLAG_SUPPORT_HIDING_PROFILES) + public Builder setProfileApiVisibility(@ProfileApiVisibility int profileApiVisibility){ + mProfileApiVisibility = profileApiVisibility; + return this; + } + /** Builds a UserProperties object with *all* values populated. * @hide */ @@ -1452,7 +1547,8 @@ public final class UserProperties implements Parcelable { mAllowStoppingUserWithDelayedLocking, mDeleteAppWithParent, mAlwaysVisible, - mCrossProfileContentSharingStrategy); + mCrossProfileContentSharingStrategy, + mProfileApiVisibility); } } // end Builder @@ -1473,7 +1569,8 @@ public final class UserProperties implements Parcelable { boolean allowStoppingUserWithDelayedLocking, boolean deleteAppWithParent, boolean alwaysVisible, - @CrossProfileContentSharingStrategy int crossProfileContentSharingStrategy) { + @CrossProfileContentSharingStrategy int crossProfileContentSharingStrategy, + @ProfileApiVisibility int profileApiVisibility) { mDefaultProperties = null; setShowInLauncher(showInLauncher); setStartWithParent(startWithParent); @@ -1493,5 +1590,8 @@ public final class UserProperties implements Parcelable { setDeleteAppWithParent(deleteAppWithParent); setAlwaysVisible(alwaysVisible); setCrossProfileContentSharingStrategy(crossProfileContentSharingStrategy); + if (android.multiuser.Flags.supportHidingProfiles()) { + setProfileApiVisibility(profileApiVisibility); + } } } diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig index fd872906f53b..caff4576c9c3 100644 --- a/core/java/android/content/pm/flags.aconfig +++ b/core/java/android/content/pm/flags.aconfig @@ -110,6 +110,14 @@ flag { } flag { + name: "relative_reference_intent_filters" + namespace: "package_manager_service" + description: "Feature flag to enable relative reference intent filters" + bug: "307556883" + is_fixed_read_only: true +} + +flag { name: "fix_duplicated_flags" namespace: "package_manager_service" description: "Feature flag to fix duplicated PackageManager flag values" @@ -168,3 +176,10 @@ flag { description: "Feature flag to allow the sandbox SDK to query intent activities of the client app." bug: "295842134" } + +flag { + name: "emergency_install_permission" + namespace: "permissions" + description: "Feature flag to enable permission EMERGENCY_INSTALL_PACKAGES" + bug: "321080601" +} diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig index c08343713abb..efb8607f75f7 100644 --- a/core/java/android/content/pm/multiuser.aconfig +++ b/core/java/android/content/pm/multiuser.aconfig @@ -100,3 +100,11 @@ flag { description: "Enable only the API changes to support private space" bug: "299069460" } + +flag { + name: "support_hiding_profiles" + namespace: "profile_experiences" + description: "Allow the use of a hide_profile property to hide some profiles behind a permission" + bug: "316362775" + is_fixed_read_only: true +} diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java index c0424dbeb813..bdaf9d789960 100644 --- a/core/java/android/hardware/biometrics/BiometricPrompt.java +++ b/core/java/android/hardware/biometrics/BiometricPrompt.java @@ -16,7 +16,7 @@ package android.hardware.biometrics; -import static android.Manifest.permission.MANAGE_BIOMETRIC_DIALOG; +import static android.Manifest.permission.SET_BIOMETRIC_DIALOG_LOGO; import static android.Manifest.permission.TEST_BIOMETRIC; import static android.Manifest.permission.USE_BIOMETRIC; import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL; @@ -174,9 +174,9 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan * @return This builder. */ @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT) - @RequiresPermission(MANAGE_BIOMETRIC_DIALOG) + @RequiresPermission(SET_BIOMETRIC_DIALOG_LOGO) @NonNull - public BiometricPrompt.Builder setLogo(@DrawableRes int logoRes) { + public BiometricPrompt.Builder setLogoRes(@DrawableRes int logoRes) { mPromptInfo.setLogoRes(logoRes); return this; } @@ -193,9 +193,9 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan * @return This builder. */ @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT) - @RequiresPermission(MANAGE_BIOMETRIC_DIALOG) + @RequiresPermission(SET_BIOMETRIC_DIALOG_LOGO) @NonNull - public BiometricPrompt.Builder setLogo(@NonNull Bitmap logoBitmap) { + public BiometricPrompt.Builder setLogoBitmap(@NonNull Bitmap logoBitmap) { mPromptInfo.setLogoBitmap(logoBitmap); return this; } @@ -719,25 +719,25 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan /** * Gets the drawable resource of the logo for the prompt, as set by - * {@link Builder#setLogo(int)}. Currently for system applications use only. + * {@link Builder#setLogoRes(int)}. Currently for system applications use only. * * @return The drawable resource of the logo, or -1 if the prompt has no logo resource set. */ @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT) - @RequiresPermission(MANAGE_BIOMETRIC_DIALOG) + @RequiresPermission(SET_BIOMETRIC_DIALOG_LOGO) @DrawableRes public int getLogoRes() { return mPromptInfo.getLogoRes(); } /** - * Gets the logo bitmap for the prompt, as set by {@link Builder#setLogo(Bitmap)}. Currently for - * system applications use only. + * Gets the logo bitmap for the prompt, as set by {@link Builder#setLogoBitmap(Bitmap)}. + * Currently for system applications use only. * * @return The logo bitmap of the prompt, or null if the prompt has no logo bitmap set. */ @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT) - @RequiresPermission(MANAGE_BIOMETRIC_DIALOG) + @RequiresPermission(SET_BIOMETRIC_DIALOG_LOGO) @Nullable public Bitmap getLogoBitmap() { return mPromptInfo.getLogoBitmap(); diff --git a/core/java/android/hardware/biometrics/PromptInfo.java b/core/java/android/hardware/biometrics/PromptInfo.java index d788b37c781d..0f9cadc52608 100644 --- a/core/java/android/hardware/biometrics/PromptInfo.java +++ b/core/java/android/hardware/biometrics/PromptInfo.java @@ -166,9 +166,9 @@ public class PromptInfo implements Parcelable { } /** - * Returns whether MANAGE_BIOMETRIC_DIALOG is contained. + * Returns whether SET_BIOMETRIC_DIALOG_LOGO is contained. */ - public boolean containsManageBioApiConfigurations() { + public boolean containsSetLogoApiConfigurations() { if (mLogoRes != -1) { return true; } else if (mLogoBitmap != null) { diff --git a/core/java/android/os/PatternMatcher.java b/core/java/android/os/PatternMatcher.java index b5425b43fe7e..79a2c59d6a03 100644 --- a/core/java/android/os/PatternMatcher.java +++ b/core/java/android/os/PatternMatcher.java @@ -16,9 +16,12 @@ package android.os; +import android.annotation.IntDef; import android.util.Log; import android.util.proto.ProtoOutputStream; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Arrays; /** @@ -68,6 +71,17 @@ public class PatternMatcher implements Parcelable { */ public static final int PATTERN_SUFFIX = 4; + /** @hide */ + @IntDef(value = { + PATTERN_LITERAL, + PATTERN_PREFIX, + PATTERN_SIMPLE_GLOB, + PATTERN_ADVANCED_GLOB, + PATTERN_SUFFIX, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface PatternType {} + // token types for advanced matching private static final int TOKEN_TYPE_LITERAL = 0; private static final int TOKEN_TYPE_ANY = 1; diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index 533946d89706..d6df8d940904 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -1898,6 +1898,30 @@ public class UserManager { "no_near_field_communication_radio"; /** + * This user restriction specifies if Thread network is disallowed on the device. If Thread + * network is disallowed it cannot be turned on via Settings. + * + * <p>This restriction can only be set by a device owner or a profile owner of an + * organization-owned managed profile on the parent profile. + * In both cases, the restriction applies globally on the device and will turn off the + * Thread network radio if it's currently on and prevent the radio from being turned + * on in the future. + * + * <p> <a href="https://www.threadgroup.org">Thread</a> is a low-power and low-latency wireless + * mesh networking protocol built on IPv6. + * + * <p>Default is <code>false</code>. + * + * <p>Key for user restrictions. + * <p>Type: Boolean + * @see DevicePolicyManager#addUserRestriction(ComponentName, String) + * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) + * @see #getUserRestrictions() + */ + @FlaggedApi("com.android.net.thread.flags.thread_user_restriction_enabled") + public static final String DISALLOW_THREAD_NETWORK = "no_thread_network"; + + /** * List of key values that can be passed into the various user restriction related methods * in {@link UserManager} & {@link DevicePolicyManager}. * Note: This is slightly different from the real set of user restrictions listed in {@link @@ -1983,6 +2007,7 @@ public class UserManager { DISALLOW_ULTRA_WIDEBAND_RADIO, DISALLOW_GRANT_ADMIN, DISALLOW_NEAR_FIELD_COMMUNICATION_RADIO, + DISALLOW_THREAD_NETWORK, }) @Retention(RetentionPolicy.SOURCE) public @interface UserRestrictionKey {} diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index e3a41ba05ec6..11edcafecdee 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -444,6 +444,18 @@ public final class Settings { "android.settings.ACCESSIBILITY_DETAILS_SETTINGS"; /** + * Activity Action: Show settings to allow configuration of an accessibility + * shortcut belonging to an accessibility feature or features. + * <p> + * Input: ":settings:show_fragment_args" must contain "targets" denoting the services to edit. + * <p> + * Output: Nothing. + * @hide + **/ + public static final String ACTION_ACCESSIBILITY_SHORTCUT_SETTINGS = + "android.settings.ACCESSIBILITY_SHORTCUT_SETTINGS"; + + /** * Activity Action: Show settings to allow configuration of accessibility color and motion. * <p> * In some cases, a matching Activity may not exist, so ensure you diff --git a/core/java/android/service/autofill/AutofillService.java b/core/java/android/service/autofill/AutofillService.java index 5ad2502d1546..298bdb881e9f 100644 --- a/core/java/android/service/autofill/AutofillService.java +++ b/core/java/android/service/autofill/AutofillService.java @@ -622,6 +622,15 @@ public abstract class AutofillService extends Service { new FillCallback(callback, request.getId()))); } + @Override + public void onConvertCredentialRequest( + @NonNull ConvertCredentialRequest convertCredentialRequest, + @NonNull IConvertCredentialCallback convertCredentialCallback) { + mHandler.sendMessage(obtainMessage( + AutofillService::onConvertCredentialRequest, + AutofillService.this, convertCredentialRequest, + new ConvertCredentialCallback(convertCredentialCallback))); + } @Override public void onFillCredentialRequest(FillRequest request, IFillCallback callback, @@ -707,7 +716,19 @@ public abstract class AutofillService extends Service { */ public void onFillCredentialRequest(@NonNull FillRequest request, @NonNull CancellationSignal cancellationSignal, @NonNull FillCallback callback, - IAutoFillManagerClient autofillClientCallback) {} + @NonNull IAutoFillManagerClient autofillClientCallback) {} + + /** + * Called by the Android system to convert a credential manager response to a dataset + * + * @param convertCredentialRequest the request that has the original credential manager response + * @param convertCredentialCallback callback used to notify the result of the request. + * + * @hide + */ + public void onConvertCredentialRequest( + @NonNull ConvertCredentialRequest convertCredentialRequest, + @NonNull ConvertCredentialCallback convertCredentialCallback){} /** * Called when the user requests the service to save the contents of a screen. diff --git a/core/java/android/service/autofill/ConvertCredentialCallback.java b/core/java/android/service/autofill/ConvertCredentialCallback.java new file mode 100644 index 000000000000..a39f01115338 --- /dev/null +++ b/core/java/android/service/autofill/ConvertCredentialCallback.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.autofill; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.RemoteException; + +/** + * <p><code>ConvertCredentialCallback</code> handles convertCredentialResponse from Autofill + * Service. + * + * @hide + */ +public final class ConvertCredentialCallback { + + private static final String TAG = "ConvertCredentialCallback"; + + private final IConvertCredentialCallback mCallback; + + /** @hide */ + public ConvertCredentialCallback(IConvertCredentialCallback callback) { + mCallback = callback; + } + + /** + * Notifies the Android System that a convertCredentialRequest was fulfilled by the service. + * + * @param convertCredentialResponse the result + */ + public void onSuccess(@NonNull ConvertCredentialResponse convertCredentialResponse) { + try { + mCallback.onSuccess(convertCredentialResponse); + } catch (RemoteException e) { + e.rethrowAsRuntimeException(); + } + } + + /** + * Notifies the Android System that a convert credential request has failed + * + * @param message the error message + */ + public void onFailure(@Nullable CharSequence message) { + try { + mCallback.onFailure(message); + } catch (RemoteException e) { + e.rethrowAsRuntimeException(); + } + } +} diff --git a/core/java/android/service/autofill/ConvertCredentialRequest.aidl b/core/java/android/service/autofill/ConvertCredentialRequest.aidl new file mode 100644 index 000000000000..79681e2dbe84 --- /dev/null +++ b/core/java/android/service/autofill/ConvertCredentialRequest.aidl @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2024, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.autofill; + +parcelable ConvertCredentialRequest;
\ No newline at end of file diff --git a/core/java/android/service/autofill/ConvertCredentialRequest.java b/core/java/android/service/autofill/ConvertCredentialRequest.java new file mode 100644 index 000000000000..d2d7556f40b7 --- /dev/null +++ b/core/java/android/service/autofill/ConvertCredentialRequest.java @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.autofill; + +import android.annotation.NonNull; +import android.credentials.GetCredentialResponse; +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.util.DataClass; + + +/** + * This class represents a request to an autofill service to convert the credential manager response + * to a dataset. + * + * @hide + */ +@DataClass( + genToString = true, + genHiddenConstructor = true, + genHiddenConstDefs = true) +public final class ConvertCredentialRequest implements Parcelable { + private final @NonNull GetCredentialResponse mGetCredentialResponse; + private final @NonNull Bundle mClientState; + + + + // Code below generated by codegen v1.0.23. + // + // DO NOT MODIFY! + // CHECKSTYLE:OFF Generated code + // + // To regenerate run: + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/service/autofill/ConvertCredentialRequest.java + // + // To exclude the generated code from IntelliJ auto-formatting enable (one-time): + // Settings > Editor > Code Style > Formatter Control + //@formatter:off + + + /** + * Creates a new ConvertCredentialRequest. + * + * @hide + */ + @DataClass.Generated.Member + public ConvertCredentialRequest( + @NonNull GetCredentialResponse getCredentialResponse, + @NonNull Bundle clientState) { + this.mGetCredentialResponse = getCredentialResponse; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mGetCredentialResponse); + this.mClientState = clientState; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mClientState); + + // onConstructed(); // You can define this method to get a callback + } + + @DataClass.Generated.Member + public @NonNull GetCredentialResponse getGetCredentialResponse() { + return mGetCredentialResponse; + } + + @DataClass.Generated.Member + public @NonNull Bundle getClientState() { + return mClientState; + } + + @Override + @DataClass.Generated.Member + public String toString() { + // You can override field toString logic by defining methods like: + // String fieldNameToString() { ... } + + return "ConvertCredentialRequest { " + + "getCredentialResponse = " + mGetCredentialResponse + ", " + + "clientState = " + mClientState + + " }"; + } + + @Override + @DataClass.Generated.Member + public void writeToParcel(@NonNull Parcel dest, int flags) { + // You can override field parcelling by defining methods like: + // void parcelFieldName(Parcel dest, int flags) { ... } + + dest.writeTypedObject(mGetCredentialResponse, flags); + dest.writeBundle(mClientState); + } + + @Override + @DataClass.Generated.Member + public int describeContents() { return 0; } + + /** @hide */ + @SuppressWarnings({"unchecked", "RedundantCast"}) + @DataClass.Generated.Member + /* package-private */ ConvertCredentialRequest(@NonNull Parcel in) { + // You can override field unparcelling by defining methods like: + // static FieldType unparcelFieldName(Parcel in) { ... } + + GetCredentialResponse getCredentialResponse = (GetCredentialResponse) in.readTypedObject(GetCredentialResponse.CREATOR); + Bundle clientState = in.readBundle(); + + this.mGetCredentialResponse = getCredentialResponse; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mGetCredentialResponse); + this.mClientState = clientState; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mClientState); + + // onConstructed(); // You can define this method to get a callback + } + + @DataClass.Generated.Member + public static final @NonNull Parcelable.Creator<ConvertCredentialRequest> CREATOR + = new Parcelable.Creator<ConvertCredentialRequest>() { + @Override + public ConvertCredentialRequest[] newArray(int size) { + return new ConvertCredentialRequest[size]; + } + + @Override + public ConvertCredentialRequest createFromParcel(@NonNull Parcel in) { + return new ConvertCredentialRequest(in); + } + }; + + @DataClass.Generated( + time = 1706132305002L, + codegenVersion = "1.0.23", + sourceFile = "frameworks/base/core/java/android/service/autofill/ConvertCredentialRequest.java", + inputSignatures = "private final @android.annotation.NonNull android.credentials.GetCredentialResponse mGetCredentialResponse\nprivate final @android.annotation.NonNull android.os.Bundle mClientState\nclass ConvertCredentialRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstructor=true, genHiddenConstDefs=true)") + @Deprecated + private void __metadata() {} + + + //@formatter:on + // End of generated code + +} diff --git a/core/java/android/service/autofill/ConvertCredentialResponse.aidl b/core/java/android/service/autofill/ConvertCredentialResponse.aidl new file mode 100644 index 000000000000..98ac6f67c521 --- /dev/null +++ b/core/java/android/service/autofill/ConvertCredentialResponse.aidl @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2024, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.autofill; + +parcelable ConvertCredentialResponse;
\ No newline at end of file diff --git a/core/java/android/service/autofill/ConvertCredentialResponse.java b/core/java/android/service/autofill/ConvertCredentialResponse.java new file mode 100644 index 000000000000..5da4f63f31f9 --- /dev/null +++ b/core/java/android/service/autofill/ConvertCredentialResponse.java @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.autofill; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.util.DataClass; + +/** + * Response for a {@Link ConvertCredentialRequest} + * + * @hide + */ +@DataClass( + genToString = true, + genHiddenConstructor = true, + genHiddenConstDefs = true) +public final class ConvertCredentialResponse implements Parcelable { + private final @NonNull Dataset mDataset; + private final @Nullable Bundle mClientState; + + + + + // Code below generated by codegen v1.0.23. + // + // DO NOT MODIFY! + // CHECKSTYLE:OFF Generated code + // + // To regenerate run: + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/service/autofill/ConvertCredentialResponse.java + // + // To exclude the generated code from IntelliJ auto-formatting enable (one-time): + // Settings > Editor > Code Style > Formatter Control + //@formatter:off + + + /** + * Creates a new ConvertCredentialResponse. + * + * @hide + */ + @DataClass.Generated.Member + public ConvertCredentialResponse( + @NonNull Dataset dataset, + @Nullable Bundle clientState) { + this.mDataset = dataset; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mDataset); + this.mClientState = clientState; + + // onConstructed(); // You can define this method to get a callback + } + + @DataClass.Generated.Member + public @NonNull Dataset getDataset() { + return mDataset; + } + + @DataClass.Generated.Member + public @Nullable Bundle getClientState() { + return mClientState; + } + + @Override + @DataClass.Generated.Member + public String toString() { + // You can override field toString logic by defining methods like: + // String fieldNameToString() { ... } + + return "ConvertCredentialResponse { " + + "dataset = " + mDataset + ", " + + "clientState = " + mClientState + + " }"; + } + + @Override + @DataClass.Generated.Member + public void writeToParcel(@NonNull Parcel dest, int flags) { + // You can override field parcelling by defining methods like: + // void parcelFieldName(Parcel dest, int flags) { ... } + + byte flg = 0; + if (mClientState != null) flg |= 0x2; + dest.writeByte(flg); + dest.writeTypedObject(mDataset, flags); + if (mClientState != null) dest.writeBundle(mClientState); + } + + @Override + @DataClass.Generated.Member + public int describeContents() { return 0; } + + /** @hide */ + @SuppressWarnings({"unchecked", "RedundantCast"}) + @DataClass.Generated.Member + /* package-private */ ConvertCredentialResponse(@NonNull Parcel in) { + // You can override field unparcelling by defining methods like: + // static FieldType unparcelFieldName(Parcel in) { ... } + + byte flg = in.readByte(); + Dataset dataset = (Dataset) in.readTypedObject(Dataset.CREATOR); + Bundle clientState = (flg & 0x2) == 0 ? null : in.readBundle(); + + this.mDataset = dataset; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mDataset); + this.mClientState = clientState; + + // onConstructed(); // You can define this method to get a callback + } + + @DataClass.Generated.Member + public static final @NonNull Parcelable.Creator<ConvertCredentialResponse> CREATOR + = new Parcelable.Creator<ConvertCredentialResponse>() { + @Override + public ConvertCredentialResponse[] newArray(int size) { + return new ConvertCredentialResponse[size]; + } + + @Override + public ConvertCredentialResponse createFromParcel(@NonNull Parcel in) { + return new ConvertCredentialResponse(in); + } + }; + + @DataClass.Generated( + time = 1706132669373L, + codegenVersion = "1.0.23", + sourceFile = "frameworks/base/core/java/android/service/autofill/ConvertCredentialResponse.java", + inputSignatures = "private final @android.annotation.NonNull android.service.autofill.Dataset mDataset\nprivate final @android.annotation.Nullable android.os.Bundle mClientState\nclass ConvertCredentialResponse extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstructor=true, genHiddenConstDefs=true)") + @Deprecated + private void __metadata() {} + + + //@formatter:on + // End of generated code + +} diff --git a/core/java/android/service/autofill/FillEventHistory.java b/core/java/android/service/autofill/FillEventHistory.java index 5d58120ef5bb..98dda1031eff 100644 --- a/core/java/android/service/autofill/FillEventHistory.java +++ b/core/java/android/service/autofill/FillEventHistory.java @@ -309,12 +309,19 @@ public final class FillEventHistory implements Parcelable { /** The autofill suggestion is shown as a dialog presentation. */ public static final int UI_TYPE_DIALOG = 3; + /** + * The autofill suggestion is shown os a credman bottom sheet + * @hide + */ + public static final int UI_TYPE_CREDMAN_BOTTOM_SHEET = 4; + /** @hide */ @IntDef(prefix = { "UI_TYPE_" }, value = { UI_TYPE_UNKNOWN, UI_TYPE_MENU, UI_TYPE_INLINE, - UI_TYPE_DIALOG + UI_TYPE_DIALOG, + UI_TYPE_CREDMAN_BOTTOM_SHEET }) @Retention(RetentionPolicy.SOURCE) public @interface UiType {} @@ -755,6 +762,8 @@ public final class FillEventHistory implements Parcelable { return "UI_TYPE_INLINE"; case UI_TYPE_DIALOG: return "UI_TYPE_FILL_DIALOG"; + case UI_TYPE_CREDMAN_BOTTOM_SHEET: + return "UI_TYPE_CREDMAN_BOTTOM_SHEET"; default: return "UI_TYPE_UNKNOWN"; } diff --git a/core/java/android/service/autofill/IAutoFillService.aidl b/core/java/android/service/autofill/IAutoFillService.aidl index 03ead3266521..2c2feae7aeea 100644 --- a/core/java/android/service/autofill/IAutoFillService.aidl +++ b/core/java/android/service/autofill/IAutoFillService.aidl @@ -16,6 +16,8 @@ package android.service.autofill; +import android.service.autofill.ConvertCredentialRequest; +import android.service.autofill.IConvertCredentialCallback; import android.service.autofill.FillRequest; import android.service.autofill.IFillCallback; import android.service.autofill.ISaveCallback; @@ -32,7 +34,8 @@ oneway interface IAutoFillService { void onConnectedStateChanged(boolean connected); void onFillRequest(in FillRequest request, in IFillCallback callback); void onFillCredentialRequest(in FillRequest request, in IFillCallback callback, - in IAutoFillManagerClient client); + in IAutoFillManagerClient client); void onSaveRequest(in SaveRequest request, in ISaveCallback callback); void onSavedPasswordCountRequest(in IResultReceiver receiver); + void onConvertCredentialRequest(in ConvertCredentialRequest convertCredentialRequest, in IConvertCredentialCallback convertCredentialCallback); } diff --git a/core/java/android/service/autofill/IConvertCredentialCallback.aidl b/core/java/android/service/autofill/IConvertCredentialCallback.aidl new file mode 100644 index 000000000000..9dfc29429876 --- /dev/null +++ b/core/java/android/service/autofill/IConvertCredentialCallback.aidl @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.autofill; + +import android.os.ICancellationSignal; + +import android.service.autofill.ConvertCredentialResponse; + +/** + * Interface to receive the result of a convert credential request + * + * @hide + */ +oneway interface IConvertCredentialCallback { + void onSuccess(in ConvertCredentialResponse convertCredentialResponse); + void onFailure(CharSequence message); +} diff --git a/core/java/android/view/AttachedSurfaceControl.java b/core/java/android/view/AttachedSurfaceControl.java index f28574ecb3b2..27c509a8605f 100644 --- a/core/java/android/view/AttachedSurfaceControl.java +++ b/core/java/android/view/AttachedSurfaceControl.java @@ -197,9 +197,28 @@ public interface AttachedSurfaceControl { * Transfer the currently in progress touch gesture from the host to the requested * {@link SurfaceControlViewHost.SurfacePackage}. This requires that the * SurfaceControlViewHost was created with the current host's inputToken. + * <p> + * When the touch is transferred, the window currently receiving touch gets an ACTION_CANCEL + * and does not receive any further input events for this gesture. + * <p> + * The transferred-to window receives an ACTION_DOWN event and then the remainder of the + * input events for this gesture. It does not receive any of the previous events of this gesture + * that the originating window received. + * <p> + * The "transferTouch" API only works for the current gesture. When a new gesture arrives, + * input dispatcher will do a new round of hit testing. So, if the "host" window is still the + * first thing that's being touched, then it will receive the new gesture again. It will + * again be up to the host to transfer this new gesture to the embedded. + * <p> + * Once the transferred-to window receives the gesture, it can choose to give up this gesture + * and send it to another window that it's linked to (it can't be an arbitrary window for + * security reasons) using the same transferTouch API. Only the window currently receiving + * touch is allowed to transfer the gesture. * * @param surfacePackage The SurfacePackage to transfer the gesture to. * @return Whether the touch stream was transferred. + * @see SurfaceControlViewHost#transferTouchGestureToHost() for the reverse to transfer touch + * gesture from the embedded to the host. */ @FlaggedApi(Flags.FLAG_TRANSFER_GESTURE_TO_EMBEDDED) default boolean transferHostTouchGestureToEmbedded( diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java index ba7874eb2d21..6c6e8b247886 100644 --- a/core/java/android/view/Surface.java +++ b/core/java/android/view/Surface.java @@ -1073,8 +1073,7 @@ public class Surface implements Parcelable { if (error == -EINVAL) { throw new IllegalArgumentException("Invalid argument to Surface.setFrameRate()"); } else if (error != 0) { - throw new RuntimeException("Failed to set frame rate on Surface. Native error: " - + error); + Log.e(TAG, "Failed to set frame rate on Surface. Native error: " + error); } } } @@ -1256,13 +1255,13 @@ public class Surface implements Parcelable { } private static void registerNativeMemoryUsage() { - if (Flags.enableSurfaceNativeAllocRegistration()) { + if (Flags.enableSurfaceNativeAllocRegistrationRo()) { VMRuntime.getRuntime().registerNativeAllocation(SURFACE_NATIVE_ALLOCATION_SIZE_BYTES); } } private static void freeNativeMemoryUsage() { - if (Flags.enableSurfaceNativeAllocRegistration()) { + if (Flags.enableSurfaceNativeAllocRegistrationRo()) { VMRuntime.getRuntime().registerNativeFree(SURFACE_NATIVE_ALLOCATION_SIZE_BYTES); } } diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index 42355bb17c2d..427d053f754e 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -6022,8 +6022,8 @@ public interface WindowManager extends ViewManager { * This is different from * {@link #registerUnbatchedSurfaceControlInputReceiver(int, IBinder, SurfaceControl, Looper, * SurfaceControlInputReceiver)} in that the input events are received batched. The caller must - * invoke {@link #unregisterSurfaceControlInputReceiver(IBinder)} to clean up the resources when - * no longer needing to use the {@link SurfaceControlInputReceiver} + * invoke {@link #unregisterSurfaceControlInputReceiver(SurfaceControl)} to clean up the + * resources when no longer needing to use the {@link SurfaceControlInputReceiver} * * @param displayId The display that the SurfaceControl will be placed on. Input will * only work @@ -6035,14 +6035,9 @@ public interface WindowManager extends ViewManager { * @param choreographer The Choreographer used for batching. This should match the rendering * Choreographer. * @param receiver The SurfaceControlInputReceiver that will receive the input events - * @return an {@link IBinder} token that is used to unregister the input receiver via - * {@link #unregisterSurfaceControlInputReceiver(IBinder)}. - * @see #registerUnbatchedSurfaceControlInputReceiver(int, IBinder, SurfaceControl, Looper, - * SurfaceControlInputReceiver) */ @FlaggedApi(Flags.FLAG_SURFACE_CONTROL_INPUT_RECEIVER) - @NonNull - default IBinder registerBatchedSurfaceControlInputReceiver(int displayId, + default void registerBatchedSurfaceControlInputReceiver(int displayId, @NonNull IBinder hostToken, @NonNull SurfaceControl surfaceControl, @NonNull Choreographer choreographer, @NonNull SurfaceControlInputReceiver receiver) { throw new UnsupportedOperationException( @@ -6054,8 +6049,8 @@ public interface WindowManager extends ViewManager { * receive every input event. This is different than calling @link * #registerBatchedSurfaceControlInputReceiver(int, IBinder, SurfaceControl, Choreographer, * SurfaceControlInputReceiver)} in that the input events are received unbatched. The caller - * must invoke {@link #unregisterSurfaceControlInputReceiver(IBinder)} to clean up the resources - * when no longer needing to use the {@link SurfaceControlInputReceiver} + * must invoke {@link #unregisterSurfaceControlInputReceiver(SurfaceControl)} to clean up the + * resources when no longer needing to use the {@link SurfaceControlInputReceiver} * * @param displayId The display that the SurfaceControl will be placed on. Input will only * work if SurfaceControl is on that display and that display was @@ -6066,14 +6061,9 @@ public interface WindowManager extends ViewManager { * @param surfaceControl The SurfaceControl to register the InputChannel for * @param looper The looper to use when invoking callbacks. * @param receiver The SurfaceControlInputReceiver that will receive the input events - * @return an {@link IBinder} token that is used to unregister the input receiver via - * {@link #unregisterSurfaceControlInputReceiver(IBinder)}. - * @see #registerBatchedSurfaceControlInputReceiver(int, IBinder, SurfaceControl, Choreographer, - * SurfaceControlInputReceiver) **/ @FlaggedApi(Flags.FLAG_SURFACE_CONTROL_INPUT_RECEIVER) - @NonNull - default IBinder registerUnbatchedSurfaceControlInputReceiver(int displayId, + default void registerUnbatchedSurfaceControlInputReceiver(int displayId, @NonNull IBinder hostToken, @NonNull SurfaceControl surfaceControl, @NonNull Looper looper, @NonNull SurfaceControlInputReceiver receiver) { throw new UnsupportedOperationException( @@ -6091,17 +6081,32 @@ public interface WindowManager extends ViewManager { * {@link #registerUnbatchedSurfaceControlInputReceiver(int, IBinder, SurfaceControl, Looper, * SurfaceControlInputReceiver)} * - * @param token The token that was returned via - * {@link #registerBatchedSurfaceControlInputReceiver(int, IBinder, - * SurfaceControl, - * Choreographer, SurfaceControlInputReceiver)} or - * {@link #registerUnbatchedSurfaceControlInputReceiver(int, IBinder, - * SurfaceControl, - * Looper, SurfaceControlInputReceiver)} + * @param surfaceControl The SurfaceControl to remove and unregister the input channel for. */ @FlaggedApi(Flags.FLAG_SURFACE_CONTROL_INPUT_RECEIVER) - default void unregisterSurfaceControlInputReceiver(@NonNull IBinder token) { + default void unregisterSurfaceControlInputReceiver(@NonNull SurfaceControl surfaceControl) { throw new UnsupportedOperationException( "unregisterSurfaceControlInputReceiver is not implemented"); } + + /** + * Returns the input client token for the {@link SurfaceControl}. This will only return non null + * if the SurfaceControl was registered for input via + * { #registerBatchedSurfaceControlInputReceiver(int, IBinder, SurfaceControl, Choreographer, + * SurfaceControlInputReceiver)} or + * {@link #registerUnbatchedSurfaceControlInputReceiver(int, IBinder, SurfaceControl, Looper, + * SurfaceControlInputReceiver)}. + * <p> + * This is helpful for testing to ensure the test waits for the layer to be registered with + * SurfaceFlinger and Input before proceeding with the test. + * + * @hide + */ + @FlaggedApi(Flags.FLAG_SURFACE_CONTROL_INPUT_RECEIVER) + @TestApi + @Nullable + default IBinder getSurfaceControlInputClientToken(@NonNull SurfaceControl surfaceControl) { + throw new UnsupportedOperationException( + "getSurfaceControlInputClientToken is not implemented"); + } } diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java index 8d40f9a4f7b1..c49fce5b558f 100644 --- a/core/java/android/view/WindowManagerGlobal.java +++ b/core/java/android/view/WindowManagerGlobal.java @@ -38,10 +38,12 @@ import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; import android.util.Pair; +import android.util.SparseArray; import android.view.inputmethod.InputMethodManager; import android.window.ITrustedPresentationListener; import android.window.TrustedPresentationThresholds; +import com.android.internal.annotations.GuardedBy; import com.android.internal.util.FastPrintWriter; import java.io.FileDescriptor; @@ -50,7 +52,6 @@ import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.WeakHashMap; -import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executor; import java.util.function.Consumer; import java.util.function.IntConsumer; @@ -156,8 +157,9 @@ public final class WindowManagerGlobal { private final TrustedPresentationListener mTrustedPresentationListener = new TrustedPresentationListener(); - private final ConcurrentHashMap<IBinder, InputEventReceiver> mSurfaceControlInputReceivers = - new ConcurrentHashMap<>(); + @GuardedBy("mSurfaceControlInputReceivers") + private final SparseArray<SurfaceControlInputReceiverInfo> + mSurfaceControlInputReceivers = new SparseArray<>(); private WindowManagerGlobal() { } @@ -816,7 +818,7 @@ public final class WindowManagerGlobal { mTrustedPresentationListener.removeListener(listener); } - IBinder registerBatchedSurfaceControlInputReceiver(int displayId, + void registerBatchedSurfaceControlInputReceiver(int displayId, @NonNull IBinder hostToken, @NonNull SurfaceControl surfaceControl, @NonNull Choreographer choreographer, @NonNull SurfaceControlInputReceiver receiver) { IBinder clientToken = new Binder(); @@ -830,19 +832,21 @@ public final class WindowManagerGlobal { e.rethrowAsRuntimeException(); } - mSurfaceControlInputReceivers.put(clientToken, - new BatchedInputEventReceiver(inputChannel, choreographer.getLooper(), - choreographer) { - @Override - public void onInputEvent(InputEvent event) { - boolean handled = receiver.onInputEvent(event); - finishInputEvent(event, handled); - } - }); - return clientToken; + synchronized (mSurfaceControlInputReceivers) { + mSurfaceControlInputReceivers.put(surfaceControl.getLayerId(), + new SurfaceControlInputReceiverInfo(clientToken, + new BatchedInputEventReceiver(inputChannel, choreographer.getLooper(), + choreographer) { + @Override + public void onInputEvent(InputEvent event) { + boolean handled = receiver.onInputEvent(event); + finishInputEvent(event, handled); + } + })); + } } - IBinder registerUnbatchedSurfaceControlInputReceiver( + void registerUnbatchedSurfaceControlInputReceiver( int displayId, @NonNull IBinder hostToken, @NonNull SurfaceControl surfaceControl, @NonNull Looper looper, @NonNull SurfaceControlInputReceiver receiver) { IBinder clientToken = new Binder(); @@ -856,32 +860,53 @@ public final class WindowManagerGlobal { e.rethrowAsRuntimeException(); } - mSurfaceControlInputReceivers.put(clientToken, - new InputEventReceiver(inputChannel, looper) { - @Override - public void onInputEvent(InputEvent event) { - boolean handled = receiver.onInputEvent(event); - finishInputEvent(event, handled); - } - }); - - return clientToken; + synchronized (mSurfaceControlInputReceivers) { + mSurfaceControlInputReceivers.put(surfaceControl.getLayerId(), + new SurfaceControlInputReceiverInfo(clientToken, + new InputEventReceiver(inputChannel, looper) { + @Override + public void onInputEvent(InputEvent event) { + boolean handled = receiver.onInputEvent(event); + finishInputEvent(event, handled); + } + })); + } } - void unregisterSurfaceControlInputReceiver(IBinder token) { - InputEventReceiver inputEventReceiver = mSurfaceControlInputReceivers.get(token); - if (inputEventReceiver == null) { - Log.w(TAG, "No registered input event receiver with token: " + token); + void unregisterSurfaceControlInputReceiver(SurfaceControl surfaceControl) { + SurfaceControlInputReceiverInfo surfaceControlInputReceiverInfo; + synchronized (mSurfaceControlInputReceivers) { + surfaceControlInputReceiverInfo = mSurfaceControlInputReceivers.removeReturnOld( + surfaceControl.getLayerId()); + } + + if (surfaceControlInputReceiverInfo == null) { + Log.w(TAG, "No registered input event receiver with sc: " + surfaceControl); return; } try { - WindowManagerGlobal.getWindowSession().remove(token); + WindowManagerGlobal.getWindowSession().remove( + surfaceControlInputReceiverInfo.mClientToken); } catch (RemoteException e) { Log.e(TAG, "Failed to remove input channel", e); e.rethrowAsRuntimeException(); } - inputEventReceiver.dispose(); + surfaceControlInputReceiverInfo.mInputEventReceiver.dispose(); + } + + IBinder getSurfaceControlInputClientToken(SurfaceControl surfaceControl) { + SurfaceControlInputReceiverInfo surfaceControlInputReceiverInfo; + synchronized (mSurfaceControlInputReceivers) { + surfaceControlInputReceiverInfo = mSurfaceControlInputReceivers.get( + surfaceControl.getLayerId()); + } + + if (surfaceControlInputReceiverInfo == null) { + Log.w(TAG, "No registered input event receiver with sc: " + surfaceControl); + return null; + } + return surfaceControlInputReceiverInfo.mClientToken; } private final class TrustedPresentationListener extends @@ -976,6 +1001,17 @@ public final class WindowManagerGlobal { throw e.rethrowFromSystemServer(); } } + + private static class SurfaceControlInputReceiverInfo { + final IBinder mClientToken; + final InputEventReceiver mInputEventReceiver; + + private SurfaceControlInputReceiverInfo(IBinder clientToken, + InputEventReceiver inputEventReceiver) { + mClientToken = clientToken; + mInputEventReceiver = inputEventReceiver; + } + } } final class WindowLeaked extends AndroidRuntimeException { diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java index aaf5fcc6f095..41d181c1b10c 100644 --- a/core/java/android/view/WindowManagerImpl.java +++ b/core/java/android/view/WindowManagerImpl.java @@ -523,26 +523,30 @@ public final class WindowManagerImpl implements WindowManager { mGlobal.unregisterTrustedPresentationListener(listener); } - @NonNull @Override - public IBinder registerBatchedSurfaceControlInputReceiver(int displayId, + public void registerBatchedSurfaceControlInputReceiver(int displayId, @NonNull IBinder hostToken, @NonNull SurfaceControl surfaceControl, @NonNull Choreographer choreographer, @NonNull SurfaceControlInputReceiver receiver) { - return mGlobal.registerBatchedSurfaceControlInputReceiver(displayId, hostToken, + mGlobal.registerBatchedSurfaceControlInputReceiver(displayId, hostToken, surfaceControl, choreographer, receiver); } - @NonNull @Override - public IBinder registerUnbatchedSurfaceControlInputReceiver( + public void registerUnbatchedSurfaceControlInputReceiver( int displayId, @NonNull IBinder hostToken, @NonNull SurfaceControl surfaceControl, @NonNull Looper looper, @NonNull SurfaceControlInputReceiver receiver) { - return mGlobal.registerUnbatchedSurfaceControlInputReceiver(displayId, hostToken, + mGlobal.registerUnbatchedSurfaceControlInputReceiver(displayId, hostToken, surfaceControl, looper, receiver); } @Override - public void unregisterSurfaceControlInputReceiver(@NonNull IBinder token) { - mGlobal.unregisterSurfaceControlInputReceiver(token); + public void unregisterSurfaceControlInputReceiver(@NonNull SurfaceControl surfaceControl) { + mGlobal.unregisterSurfaceControlInputReceiver(surfaceControl); + } + + @Override + @Nullable + public IBinder getSurfaceControlInputClientToken(@NonNull SurfaceControl surfaceControl) { + return mGlobal.getSurfaceControlInputClientToken(surfaceControl); } } diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java index dbeffc89fa09..559ccfea72c2 100644 --- a/core/java/android/view/autofill/AutofillManager.java +++ b/core/java/android/view/autofill/AutofillManager.java @@ -298,6 +298,15 @@ public final class AutofillManager { "android.view.autofill.extra.AUGMENTED_AUTOFILL_CLIENT"; /** + * Internal extra used to pass the fill request id in client state of + * {@link ConvertCredentialResponse} + * + * @hide + */ + public static final String EXTRA_AUTOFILL_REQUEST_ID = + "android.view.autofill.extra.AUTOFILL_REQUEST_ID"; + + /** * Autofill Hint to indicate that it can match any field. * * @hide diff --git a/core/java/android/window/ScreenCapture.java b/core/java/android/window/ScreenCapture.java index befb0023ebe6..544642811a39 100644 --- a/core/java/android/window/ScreenCapture.java +++ b/core/java/android/window/ScreenCapture.java @@ -30,6 +30,8 @@ import android.os.Parcelable; import android.util.Log; import android.view.SurfaceControl; +import com.android.window.flags.Flags; + import libcore.util.NativeAllocationRegistry; import java.util.concurrent.CountDownLatch; @@ -48,7 +50,7 @@ public class ScreenCapture { private static native int nativeCaptureDisplay(DisplayCaptureArgs captureArgs, long captureListener); private static native int nativeCaptureLayers(LayerCaptureArgs captureArgs, - long captureListener); + long captureListener, boolean sync); private static native long nativeCreateScreenCaptureListener( ObjIntConsumer<ScreenshotHardwareBuffer> consumer); private static native void nativeWriteListenerToParcel(long nativeObject, Parcel out); @@ -134,7 +136,8 @@ public class ScreenCapture { */ public static ScreenshotHardwareBuffer captureLayers(LayerCaptureArgs captureArgs) { SynchronousScreenCaptureListener syncScreenCapture = createSyncCaptureListener(); - int status = captureLayers(captureArgs, syncScreenCapture); + int status = nativeCaptureLayers(captureArgs, syncScreenCapture.mNativeObject, + Flags.syncScreenCapture()); if (status != 0) { return null; } @@ -171,7 +174,7 @@ public class ScreenCapture { */ public static int captureLayers(@NonNull LayerCaptureArgs captureArgs, @NonNull ScreenCaptureListener captureListener) { - return nativeCaptureLayers(captureArgs, captureListener.mNativeObject); + return nativeCaptureLayers(captureArgs, captureListener.mNativeObject, false /* sync */); } /** @@ -674,7 +677,7 @@ public class ScreenCapture { * This listener can only be used for a single call to capture content call. */ public static class ScreenCaptureListener implements Parcelable { - private final long mNativeObject; + final long mNativeObject; private static final NativeAllocationRegistry sRegistry = NativeAllocationRegistry.createMalloced( ScreenCaptureListener.class.getClassLoader(), getNativeListenerFinalizer()); diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java index 86804c6117c7..65075aea7b27 100644 --- a/core/java/android/window/WindowOnBackInvokedDispatcher.java +++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java @@ -226,9 +226,6 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { setTopOnBackInvokedCallback(null); } - // We should also stop running animations since all callbacks have been removed. - // note: mSpring.skipToEnd(), in ProgressAnimator.reset(), requires the main handler. - Handler.getMain().post(mProgressAnimator::reset); mAllCallbacks.clear(); mOnBackInvokedCallbacks.clear(); } @@ -442,8 +439,7 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { return WindowOnBackInvokedDispatcher .isOnBackInvokedCallbackEnabled(activityInfo, applicationInfo, - () -> originalContext.obtainStyledAttributes( - new int[] {android.R.attr.windowSwipeToDismiss}), true); + () -> originalContext); } @Override @@ -501,7 +497,7 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { */ public static boolean isOnBackInvokedCallbackEnabled(@Nullable ActivityInfo activityInfo, @NonNull ApplicationInfo applicationInfo, - @NonNull Supplier<TypedArray> windowAttrSupplier, boolean recycleTypedArray) { + @NonNull Supplier<Context> contextSupplier) { // new back is enabled if the feature flag is enabled AND the app does not explicitly // request legacy back. if (!ENABLE_PREDICTIVE_BACK) { @@ -547,15 +543,15 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { // setTrigger(true) // Use the original context to resolve the styled attribute so that they stay // true to the window. - TypedArray windowAttr = windowAttrSupplier.get(); + final Context context = contextSupplier.get(); boolean windowSwipeToDismiss = true; - if (windowAttr != null) { - if (windowAttr.getIndexCount() > 0) { - windowSwipeToDismiss = windowAttr.getBoolean(0, true); - } - if (recycleTypedArray) { - windowAttr.recycle(); + if (context != null) { + final TypedArray array = context.obtainStyledAttributes( + new int[]{android.R.attr.windowSwipeToDismiss}); + if (array.getIndexCount() > 0) { + windowSwipeToDismiss = array.getBoolean(0, true); } + array.recycle(); } if (DEBUG) { diff --git a/core/java/android/window/flags/window_surfaces.aconfig b/core/java/android/window/flags/window_surfaces.aconfig index 751c1a8dd0ff..069affb4c06c 100644 --- a/core/java/android/window/flags/window_surfaces.aconfig +++ b/core/java/android/window/flags/window_surfaces.aconfig @@ -88,3 +88,11 @@ flag { is_fixed_read_only: true bug: "304574518" } + +flag { + namespace: "window_surfaces" + name: "sync_screen_capture" + description: "Create a screen capture API that blocks in SurfaceFlinger" + is_fixed_read_only: true + bug: "321263247" +} diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedIntentInfoUtils.java b/core/java/com/android/internal/pm/pkg/component/ParsedIntentInfoUtils.java index c6683cfc8331..05728eee174f 100644 --- a/core/java/com/android/internal/pm/pkg/component/ParsedIntentInfoUtils.java +++ b/core/java/com/android/internal/pm/pkg/component/ParsedIntentInfoUtils.java @@ -18,9 +18,13 @@ package com.android.internal.pm.pkg.component; import static com.android.internal.pm.pkg.parsing.ParsingUtils.ANDROID_RES_NAMESPACE; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.content.Intent; import android.content.IntentFilter; +import android.content.UriRelativeFilter; +import android.content.UriRelativeFilterGroup; +import android.content.pm.Flags; import android.content.pm.parsing.result.ParseInput; import android.content.pm.parsing.result.ParseResult; import android.content.res.Resources; @@ -132,6 +136,11 @@ public class ParsedIntentInfoUtils { case "data": result = parseData(intentInfo, res, parser, allowGlobs, input); break; + case "uri-relative-filter-group": + if (Flags.relativeReferenceIntentFilters()) { + result = parseRelRefGroup(intentInfo, pkg, res, parser, allowGlobs, input); + break; + } default: result = ParsingUtils.unknownTag("<intent-filter>", pkg, parser, input); break; @@ -163,6 +172,197 @@ public class ParsedIntentInfoUtils { } @NonNull + @FlaggedApi(Flags.FLAG_RELATIVE_REFERENCE_INTENT_FILTERS) + private static ParseResult<ParsedIntentInfo> parseRelRefGroup(ParsedIntentInfo intentInfo, + ParsingPackage pkg, Resources res, XmlResourceParser parser, boolean allowGlobs, + ParseInput input) throws XmlPullParserException, IOException { + IntentFilter intentFilter = intentInfo.getIntentFilter(); + TypedArray sa = res.obtainAttributes(parser, + R.styleable.AndroidManifestUriRelativeFilterGroup); + UriRelativeFilterGroup group; + try { + int action = UriRelativeFilterGroup.ACTION_ALLOW; + if (!sa.getBoolean(R.styleable.AndroidManifestUriRelativeFilterGroup_allow, true)) { + action = UriRelativeFilterGroup.ACTION_BLOCK; + } + group = new UriRelativeFilterGroup(action); + } finally { + sa.recycle(); + } + final int depth = parser.getDepth(); + int type; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG + || parser.getDepth() > depth)) { + if (type != XmlPullParser.START_TAG) { + continue; + } + + final ParseResult result; + String nodeName = parser.getName(); + switch (nodeName) { + case "data": + result = parseRelRefGroupData(group, res, parser, allowGlobs, input); + break; + default: + result = ParsingUtils.unknownTag("<uri-relative-filter-group>", + pkg, parser, input); + break; + } + + if (result.isError()) { + return input.error(result); + } + } + + if (group.getUriRelativeFilters().size() > 0) { + intentFilter.addUriRelativeFilterGroup(group); + } + return input.success(null); + } + + @NonNull + @FlaggedApi(Flags.FLAG_RELATIVE_REFERENCE_INTENT_FILTERS) + private static ParseResult<ParsedIntentInfo> parseRelRefGroupData(UriRelativeFilterGroup group, + Resources res, XmlResourceParser parser, boolean allowGlobs, ParseInput input) { + TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestData); + try { + String str = sa.getNonConfigurationString( + R.styleable.AndroidManifestData_path, 0); + if (str != null) { + group.addUriRelativeFilter(new UriRelativeFilter(UriRelativeFilter.PATH, + PatternMatcher.PATTERN_LITERAL, str)); + } + + str = sa.getNonConfigurationString( + R.styleable.AndroidManifestData_pathPrefix, 0); + if (str != null) { + group.addUriRelativeFilter(new UriRelativeFilter(UriRelativeFilter.PATH, + PatternMatcher.PATTERN_PREFIX, str)); + } + + str = sa.getNonConfigurationString( + R.styleable.AndroidManifestData_pathPattern, 0); + if (str != null) { + if (!allowGlobs) { + return input.error( + "pathPattern not allowed here; path must be literal"); + } + group.addUriRelativeFilter(new UriRelativeFilter(UriRelativeFilter.PATH, + PatternMatcher.PATTERN_SIMPLE_GLOB, str)); + } + + str = sa.getNonConfigurationString( + R.styleable.AndroidManifestData_pathAdvancedPattern, 0); + if (str != null) { + if (!allowGlobs) { + return input.error( + "pathAdvancedPattern not allowed here; path must be literal"); + } + group.addUriRelativeFilter(new UriRelativeFilter(UriRelativeFilter.PATH, + PatternMatcher.PATTERN_ADVANCED_GLOB, str)); + } + + str = sa.getNonConfigurationString( + R.styleable.AndroidManifestData_pathSuffix, 0); + if (str != null) { + group.addUriRelativeFilter(new UriRelativeFilter(UriRelativeFilter.PATH, + PatternMatcher.PATTERN_SUFFIX, str)); + } + + str = sa.getNonConfigurationString( + R.styleable.AndroidManifestData_fragment, 0); + if (str != null) { + group.addUriRelativeFilter(new UriRelativeFilter(UriRelativeFilter.FRAGMENT, + PatternMatcher.PATTERN_LITERAL, str)); + } + + str = sa.getNonConfigurationString( + R.styleable.AndroidManifestData_fragmentPrefix, 0); + if (str != null) { + group.addUriRelativeFilter(new UriRelativeFilter(UriRelativeFilter.FRAGMENT, + PatternMatcher.PATTERN_PREFIX, str)); + } + + str = sa.getNonConfigurationString( + R.styleable.AndroidManifestData_fragmentPattern, 0); + if (str != null) { + if (!allowGlobs) { + return input.error( + "fragmentPattern not allowed here; fragment must be literal"); + } + group.addUriRelativeFilter(new UriRelativeFilter(UriRelativeFilter.FRAGMENT, + PatternMatcher.PATTERN_SIMPLE_GLOB, str)); + } + + str = sa.getNonConfigurationString( + R.styleable.AndroidManifestData_fragmentAdvancedPattern, 0); + if (str != null) { + if (!allowGlobs) { + return input.error( + "fragmentAdvancedPattern not allowed here; fragment must be literal"); + } + group.addUriRelativeFilter(new UriRelativeFilter(UriRelativeFilter.FRAGMENT, + PatternMatcher.PATTERN_ADVANCED_GLOB, str)); + } + + str = sa.getNonConfigurationString( + R.styleable.AndroidManifestData_fragmentSuffix, 0); + if (str != null) { + group.addUriRelativeFilter(new UriRelativeFilter(UriRelativeFilter.FRAGMENT, + PatternMatcher.PATTERN_SUFFIX, str)); + } + + str = sa.getNonConfigurationString( + R.styleable.AndroidManifestData_query, 0); + if (str != null) { + group.addUriRelativeFilter(new UriRelativeFilter(UriRelativeFilter.QUERY, + PatternMatcher.PATTERN_LITERAL, str)); + } + + str = sa.getNonConfigurationString( + R.styleable.AndroidManifestData_queryPrefix, 0); + if (str != null) { + group.addUriRelativeFilter(new UriRelativeFilter(UriRelativeFilter.QUERY, + PatternMatcher.PATTERN_PREFIX, str)); + } + + str = sa.getNonConfigurationString( + R.styleable.AndroidManifestData_queryPattern, 0); + if (str != null) { + if (!allowGlobs) { + return input.error( + "queryPattern not allowed here; query must be literal"); + } + group.addUriRelativeFilter(new UriRelativeFilter(UriRelativeFilter.QUERY, + PatternMatcher.PATTERN_SIMPLE_GLOB, str)); + } + + str = sa.getNonConfigurationString( + R.styleable.AndroidManifestData_queryAdvancedPattern, 0); + if (str != null) { + if (!allowGlobs) { + return input.error( + "queryAdvancedPattern not allowed here; query must be literal"); + } + group.addUriRelativeFilter(new UriRelativeFilter(UriRelativeFilter.QUERY, + PatternMatcher.PATTERN_ADVANCED_GLOB, str)); + } + + str = sa.getNonConfigurationString( + R.styleable.AndroidManifestData_querySuffix, 0); + if (str != null) { + group.addUriRelativeFilter(new UriRelativeFilter(UriRelativeFilter.QUERY, + PatternMatcher.PATTERN_SUFFIX, str)); + } + + return input.success(null); + } finally { + sa.recycle(); + } + } + + @NonNull private static ParseResult<ParsedIntentInfo> parseData(ParsedIntentInfo intentInfo, Resources resources, XmlResourceParser parser, boolean allowGlobs, ParseInput input) { IntentFilter intentFilter = intentInfo.getIntentFilter(); diff --git a/core/jni/android_window_ScreenCapture.cpp b/core/jni/android_window_ScreenCapture.cpp index 6e903b3ab56d..1031542eb2e6 100644 --- a/core/jni/android_window_ScreenCapture.cpp +++ b/core/jni/android_window_ScreenCapture.cpp @@ -211,7 +211,7 @@ static jint nativeCaptureDisplay(JNIEnv* env, jclass clazz, jobject displayCaptu } static jint nativeCaptureLayers(JNIEnv* env, jclass clazz, jobject layerCaptureArgsObject, - jlong screenCaptureListenerObject) { + jlong screenCaptureListenerObject, jboolean sync) { LayerCaptureArgs captureArgs; getCaptureArgs(env, layerCaptureArgsObject, captureArgs); @@ -227,7 +227,7 @@ static jint nativeCaptureLayers(JNIEnv* env, jclass clazz, jobject layerCaptureA sp<gui::IScreenCaptureListener> captureListener = reinterpret_cast<gui::IScreenCaptureListener*>(screenCaptureListenerObject); - return ScreenshotClient::captureLayers(captureArgs, captureListener); + return ScreenshotClient::captureLayers(captureArgs, captureListener, sync); } static jlong nativeCreateScreenCaptureListener(JNIEnv* env, jclass clazz, jobject consumerObj) { @@ -281,7 +281,7 @@ static const JNINativeMethod sScreenCaptureMethods[] = { // clang-format off {"nativeCaptureDisplay", "(Landroid/window/ScreenCapture$DisplayCaptureArgs;J)I", (void*)nativeCaptureDisplay }, - {"nativeCaptureLayers", "(Landroid/window/ScreenCapture$LayerCaptureArgs;J)I", + {"nativeCaptureLayers", "(Landroid/window/ScreenCapture$LayerCaptureArgs;JZ)I", (void*)nativeCaptureLayers }, {"nativeCreateScreenCaptureListener", "(Ljava/util/function/ObjIntConsumer;)J", (void*)nativeCreateScreenCaptureListener }, diff --git a/core/proto/android/content/intent.proto b/core/proto/android/content/intent.proto index 75e29082508b..1d1f88b01838 100644 --- a/core/proto/android/content/intent.proto +++ b/core/proto/android/content/intent.proto @@ -66,7 +66,7 @@ message IntentProto { optional string identifier = 13 [ (.android.privacy).dest = DEST_EXPLICIT ]; } -// Next Tag: 12 +// Next Tag: 14 message IntentFilterProto { option (.android.msg_privacy).dest = DEST_AUTOMATIC; @@ -89,6 +89,7 @@ message IntentFilterProto { optional bool get_auto_verify = 10; repeated string mime_groups = 11; optional android.os.PersistableBundleProto extras = 12; + repeated UriRelativeFilterGroupProto uri_relative_filter_groups = 13; } message AuthorityEntryProto { @@ -98,3 +99,23 @@ message AuthorityEntryProto { optional bool wild = 2; optional int32 port = 3; } + +message UriRelativeFilterGroupProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + + enum Action { + ACTION_ALLOW = 0; + ACTION_BLOCK = 1; + } + + optional Action action = 1; + repeated UriRelativeFilterProto uri_relative_filters = 2; +} + +message UriRelativeFilterProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + + required int32 uri_part = 1; + required int32 pattern_type = 2; + required string filter = 3; +} diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 723eb70a00d9..d972556df015 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -6654,7 +6654,14 @@ <!-- Allows the system to control the BiometricDialog (SystemUI). Reserved for the system. @hide --> <permission android:name="android.permission.MANAGE_BIOMETRIC_DIALOG" - android:protectionLevel="signature" /> + android:protectionLevel="signature" /> + + <!-- Allows an application to set the BiometricDialog (SystemUI) logo . + <p>Not for use by third-party applications. + @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") + --> + <permission android:name="android.permission.SET_BIOMETRIC_DIALOG_LOGO" + android:protectionLevel="signature" /> <!-- Allows an application to control keyguard. Only allowed for system processes. @hide --> @@ -7801,6 +7808,16 @@ <permission android:name="android.permission.RUN_USER_INITIATED_JOBS" android:protectionLevel="normal"/> + <!-- @FlaggedApi("android.app.job.backup_jobs_exemption") + Gives applications whose <b>primary use case</b> is to backup or sync content increased + job execution allowance in order to complete the related work. The jobs must have a valid + content URI trigger and network constraint set. + <p>This is a special access permission that can be revoked by the system or the user. + <p>Protection level: signature|privileged|appop + --> + <permission android:name="android.permission.RUN_BACKUP_JOBS" + android:protectionLevel="signature|privileged|appop"/> + <!-- Allows an app access to the installer provided app metadata. @SystemApi @hide @@ -7968,6 +7985,16 @@ <permission android:name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER" android:protectionLevel="signature|privileged"/> + <!-- @SystemApi + @FlaggedApi("android.content.pm.emergency_install_permission") + Allows each app store in the system image to designate another app in the system image to + update the app store + <p>Protection level: signature|privileged + @hide + --> + <permission android:name="android.permission.EMERGENCY_INSTALL_PACKAGES" + android:protectionLevel="signature|privileged"/> + <!-- Attribution for Geofencing service. --> <attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/> <!-- Attribution for Country Detector. --> @@ -8374,6 +8401,16 @@ </intent-filter> </receiver> + <!-- Broadcast Receiver listens to sufficient verifier broadcast from Package Manager + when installing new SDK. Verification of SDK code during installation time is run + to determine compatibility with privacy sandbox restrictions. --> + <receiver android:name="com.android.server.sdksandbox.SdkSandboxVerifierReceiver" + android:exported="false"> + <intent-filter> + <action android:name="android.intent.action.PACKAGE_NEEDS_VERIFICATION"/> + </intent-filter> + </receiver> + <service android:name="android.hardware.location.GeofenceHardwareService" android:permission="android.permission.LOCATION_HARDWARE" android:exported="false" /> diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index 35276bf8ead2..6884fc0057d9 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -3438,6 +3438,20 @@ <!-- Attributes that can be supplied in an AndroidManifest.xml <code>data</code> tag, a child of the {@link #AndroidManifestIntentFilter intent-filter} tag, describing + a group matching rule consisting of one or more + {@link #AndroidManifestData data} tags that must all match. This + tag can be specified multiple times to create multiple groups that + will be matched in the order they are defined. --> + <declare-styleable name="AndroidManifestUriRelativeFilterGroup" + parent="AndroidManifestIntentFilter"> + <!-- Specify if this group is allow rule or disallow rule. If this + attribute is not specified then it is assumed to be true --> + <attr name="allow" format="boolean"/> + </declare-styleable> + + <!-- Attributes that can be supplied in an AndroidManifest.xml + <code>data</code> tag, a child of the + {@link #AndroidManifestIntentFilter intent-filter} tag, describing the types of data that match. This tag can be specified multiple times to supply multiple data options, as described in the {@link android.content.IntentFilter} class. Note that all such @@ -3445,7 +3459,8 @@ <code><data android:scheme="myscheme" android:host="me.com" /></code> is equivalent to <code><data android:scheme="myscheme" /> <data android:host="me.com" /></code>. --> - <declare-styleable name="AndroidManifestData" parent="AndroidManifestIntentFilter"> + <declare-styleable name="AndroidManifestData" + parent="AndroidManifestIntentFilter AndroidManifestUriRelativeFilterGroup"> <!-- Specify a MIME type that is handled, as per {@link android.content.IntentFilter#addDataType IntentFilter.addDataType()}. @@ -3549,6 +3564,70 @@ IntentFilter.addDataPath()} with {@link android.os.PatternMatcher#PATTERN_SUFFIX}. --> <attr name="pathSuffix" /> + <!-- Specify a URI query that must exactly match, as a + {@link android.content.UriRelativeFilter UriRelativeFilter} with + {@link android.os.PatternMatcher#PATTERN_LITERAL}. --> + <attr name="query" format="string" /> + <!-- Specify a URI query that must be a prefix to match, as a + {@link android.content.UriRelativeFilter UriRelativeFilter} with + {@link android.os.PatternMatcher#PATTERN_PREFIX}. --> + <attr name="queryPrefix" format="string" /> + <!-- Specify a URI query that matches a simple pattern, as a + {@link android.content.UriRelativeFilter UriRelativeFilter} with + {@link android.os.PatternMatcher#PATTERN_SIMPLE_GLOB}. + Note that because '\' is used as an escape character when + reading the string from XML (before it is parsed as a pattern), + you will need to double-escape: for example a literal "*" would + be written as "\\*" and a literal "\" would be written as + "\\\\". This is basically the same as what you would need to + write if constructing the string in Java code. --> + <attr name="queryPattern" format="string" /> + <!-- Specify a URI query that matches an advanced pattern, as a + {@link android.content.UriRelativeFilter UriRelativeFilter} with + {@link android.os.PatternMatcher#PATTERN_ADVANCED_GLOB}. + Note that because '\' is used as an escape character when + reading the string from XML (before it is parsed as a pattern), + you will need to double-escape: for example a literal "*" would + be written as "\\*" and a literal "\" would be written as + "\\\\". This is basically the same as what you would need to + write if constructing the string in Java code. --> + <attr name="queryAdvancedPattern" format="string" /> + <!-- Specify a URI query that must be a suffix to match, as a + {@link android.content.UriRelativeFilter UriRelativeFilter} with + {@link android.os.PatternMatcher#PATTERN_SUFFIX}. --> + <attr name="querySuffix" format="string" /> + <!-- Specify a URI fragment that must exactly match, as a + {@link android.content.UriRelativeFilter UriRelativeFilter} with + {@link android.os.PatternMatcher#PATTERN_LITERAL}. --> + <attr name="fragment" format="string" /> + <!-- Specify a URI fragment that must be a prefix to match, as a + {@link android.content.UriRelativeFilter UriRelativeFilter} with + {@link android.os.PatternMatcher#PATTERN_PREFIX}. --> + <attr name="fragmentPrefix" format="string" /> + <!-- Specify a URI fragment that matches a simple pattern, as a + {@link android.content.UriRelativeFilter UriRelativeFilter} with + {@link android.os.PatternMatcher#PATTERN_SIMPLE_GLOB}. + Note that because '\' is used as an escape character when + reading the string from XML (before it is parsed as a pattern), + you will need to double-escape: for example a literal "*" would + be written as "\\*" and a literal "\" would be written as + "\\\\". This is basically the same as what you would need to + write if constructing the string in Java code. --> + <attr name="fragmentPattern" format="string" /> + <!-- Specify a URI fragment that matches an advanced pattern, as a + {@link android.content.UriRelativeFilter UriRelativeFilter} with + {@link android.os.PatternMatcher#PATTERN_ADVANCED_GLOB}. + Note that because '\' is used as an escape character when + reading the string from XML (before it is parsed as a pattern), + you will need to double-escape: for example a literal "*" would + be written as "\\*" and a literal "\" would be written as + "\\\\". This is basically the same as what you would need to + write if constructing the string in Java code. --> + <attr name="fragmentAdvancedPattern" format="string" /> + <!-- Specify a URI fragment that must be a suffix to match, as a + {@link android.content.UriRelativeFilter UriRelativeFilter} with + {@link android.os.PatternMatcher#PATTERN_SUFFIX}. --> + <attr name="fragmentSuffix" format="string" /> </declare-styleable> <!-- Attributes that can be supplied in an AndroidManifest.xml diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml index b8fc052a2fa9..830e99ca907b 100644 --- a/core/res/res/values/public-staging.xml +++ b/core/res/res/values/public-staging.xml @@ -123,6 +123,26 @@ <public name="featureFlag"/> <!-- @FlaggedApi("android.multiuser.enable_system_user_only_for_services_and_providers") --> <public name="systemUserOnly"/> + <!-- @FlaggedApi("android.content.pm.relative_reference_intent_filters") --> + <public name="allow"/> + <!-- @FlaggedApi("android.content.pm.relative_reference_intent_filters") --> + <public name="query"/> + <!-- @FlaggedApi("android.content.pm.relative_reference_intent_filters") --> + <public name="queryPrefix"/> + <!-- @FlaggedApi("android.content.pm.relative_reference_intent_filters") --> + <public name="queryPattern"/> + <!-- @FlaggedApi("android.content.pm.relative_reference_intent_filters") --> + <public name="queryAdvancedPattern"/> + <!-- @FlaggedApi("android.content.pm.relative_reference_intent_filters") --> + <public name="querySuffix"/> + <!-- @FlaggedApi("android.content.pm.relative_reference_intent_filters") --> + <public name="fragmentPrefix"/> + <!-- @FlaggedApi("android.content.pm.relative_reference_intent_filters") --> + <public name="fragmentPattern"/> + <!-- @FlaggedApi("android.content.pm.relative_reference_intent_filters") --> + <public name="fragmentAdvancedPattern"/> + <!-- @FlaggedApi("android.content.pm.relative_reference_intent_filters") --> + <public name="fragmentSuffix"/> </staging-public-group> <staging-public-group type="id" first-id="0x01bc0000"> diff --git a/core/res/res/xml/sms_short_codes.xml b/core/res/res/xml/sms_short_codes.xml index 3a2e50aa06e8..9bb249999d99 100644 --- a/core/res/res/xml/sms_short_codes.xml +++ b/core/res/res/xml/sms_short_codes.xml @@ -34,7 +34,7 @@ http://smscoin.net/software/engine/WordPress/Paid+SMS-registration/ --> <!-- Arab Emirates --> - <shortcode country="ae" pattern="\\d{1,5}" free="1017|1355|3214" /> + <shortcode country="ae" pattern="\\d{1,5}" free="1017|1355|3214|6253" /> <!-- Albania: 5 digits, known short codes listed --> <shortcode country="al" pattern="\\d{5}" premium="15191|55[56]00" /> @@ -86,7 +86,7 @@ <shortcode country="cn" premium="1066.*" free="1065.*" /> <!-- Colombia: 1-6 digits (not confirmed) --> - <shortcode country="co" pattern="\\d{1,6}" free="890350|908160|892255|898002|898880|899960|899948|87739|85517" /> + <shortcode country="co" pattern="\\d{1,6}" free="890350|908160|892255|898002|898880|899960|899948|87739|85517|491289" /> <!-- Cyprus: 4-6 digits (not confirmed), known premium codes listed, plus EU --> <shortcode country="cy" pattern="\\d{4,6}" premium="7510" free="116\\d{3}" /> @@ -104,6 +104,12 @@ <!-- Denmark: see http://iprs.webspacecommerce.com/Denmark-Premium-Rate-Numbers --> <shortcode country="dk" pattern="\\d{4,5}" premium="1\\d{3}" free="116\\d{3}|4665" /> + <!-- Dominican Republic: 1-6 digits (standard system default, not country specific) --> + <shortcode country="do" pattern="\\d{1,6}" free="912892" /> + + <!-- Ecuador: 1-6 digits (standard system default, not country specific) --> + <shortcode country="ec" pattern="\\d{1,6}" free="466453" /> + <!-- Estonia: short codes 3-5 digits starting with 1, plus premium 7 digit numbers starting with 90, plus EU. http://www.tja.ee/public/documents/Elektrooniline_side/Oigusaktid/ENG/Estonian_Numbering_Plan_annex_06_09_2010.mht --> <shortcode country="ee" pattern="1\\d{2,4}" premium="90\\d{5}|15330|1701[0-3]" free="116\\d{3}|95034" /> @@ -154,8 +160,8 @@ http://www.comreg.ie/_fileupload/publications/ComReg1117.pdf --> <shortcode country="ie" pattern="\\d{5}" premium="5[3-9]\\d{3}" free="50\\d{3}|116\\d{3}" standard="5[12]\\d{3}" /> - <!-- Israel: 4 digits, known premium codes listed --> - <shortcode country="il" pattern="\\d{4}" premium="4422|4545" /> + <!-- Israel: 1-5 digits, known premium codes listed --> + <shortcode country="il" pattern="\\d{1,5}" premium="4422|4545" free="37477|6681" /> <!-- Italy: 5 digits (premium=41xxx,42xxx), plus EU: https://www.itu.int/dms_pub/itu-t/oth/02/02/T020200006B0001PDFE.pdf --> @@ -193,11 +199,14 @@ <shortcode country="mk" pattern="\\d{1,6}" free="129005|122" /> <!-- Mexico: 4-5 digits (not confirmed), known premium codes listed --> - <shortcode country="mx" pattern="\\d{4,5}" premium="53035|7766" free="26259|46645|50025|50052|5050|76551|88778|9963|91101|45453" /> + <shortcode country="mx" pattern="\\d{4,6}" premium="53035|7766" free="26259|46645|50025|50052|5050|76551|88778|9963|91101|45453|550346" /> <!-- Malaysia: 5 digits: http://www.skmm.gov.my/attachment/Consumer_Regulation/Mobile_Content_Services_FAQs.pdf --> <shortcode country="my" pattern="\\d{5}" premium="32298|33776" free="22099|28288|66668" /> + <!-- Namibia: 1-5 digits (standard system default, not country specific) --> + <shortcode country="na" pattern="\\d{1,5}" free="40005" /> + <!-- The Netherlands, 4 digits, known premium codes listed, plus EU --> <shortcode country="nl" pattern="\\d{4}" premium="4466|5040" free="116\\d{3}|2223|6225|2223|1662" /> diff --git a/core/tests/InputMethodCoreTests/Android.bp b/core/tests/InputMethodCoreTests/Android.bp new file mode 100644 index 000000000000..ac6462589e16 --- /dev/null +++ b/core/tests/InputMethodCoreTests/Android.bp @@ -0,0 +1,66 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +android_test { + name: "InputMethodCoreTests", + + srcs: [ + "src/**/*.java", + "src/**/*.kt", + "src/**/I*.aidl", + ], + + dxflags: ["--core-library"], + + static_libs: [ + "collector-device-lib-platform", + "android-common", + "frameworks-core-util-lib", + "androidx.core_core", + "androidx.core_core-ktx", + "androidx.test.ext.junit", + "androidx.test.runner", + "androidx.test.rules", + "flag-junit", + "junit-params", + "kotlin-test", + "mockito-target-minus-junit4", + "platform-test-annotations", + "platform-compat-test-rules", + "truth", + "print-test-util-lib", + "testng", + "device-time-shell-utils", + "testables", + "flag-junit", + ], + + libs: [ + "android.test.runner", + "android.test.base", + "android.test.mock", + "framework", + "ext", + "framework-res", + ], + + sdk_version: "core_platform", + test_suites: [ + "device-tests", + "automotive-tests", + ], + + certificate: "platform", + + resource_dirs: ["res"], + + data: [ + ":com.android.cts.helpers.aosp", + ], +} diff --git a/core/tests/InputMethodCoreTests/AndroidManifest.xml b/core/tests/InputMethodCoreTests/AndroidManifest.xml new file mode 100644 index 000000000000..8d00d0f755bf --- /dev/null +++ b/core/tests/InputMethodCoreTests/AndroidManifest.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2024 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + android:installLocation="internalOnly" + package="com.android.frameworks.inputmethodcoretests" + android:sharedUserId="com.android.uid.test"> + + <application + android:supportsRtl="true" + android:enableOnBackInvokedCallback="true"> + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.frameworks.inputmethodcoretests" + android:label="InputMethod Core Tests" /> +</manifest> diff --git a/core/tests/InputMethodCoreTests/AndroidTest.xml b/core/tests/InputMethodCoreTests/AndroidTest.xml new file mode 100644 index 000000000000..fa585d8d1075 --- /dev/null +++ b/core/tests/InputMethodCoreTests/AndroidTest.xml @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2024 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<configuration description="Runs InputMethod Core Tests."> + <option name="test-suite-tag" value="apct" /> + <option name="test-suite-tag" value="apct-instrumentation" /> + + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> + <option name="cleanup-apks" value="true" /> + <option name="test-file-name" value="InputMethodCoreTests.apk" /> + </target_preparer> + + <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> + <!-- TODO(b/254155965): Design a mechanism to finally remove this command. --> + <option name="run-command" value="settings put global device_config_sync_disabled 0" /> + </target_preparer> + + <target_preparer class="com.android.compatibility.common.tradefed.targetprep.DeviceInteractionHelperInstaller" /> + + <option name="test-tag" value="InputMethodCoreTests" /> + <test class="com.android.tradefed.testtype.AndroidJUnitTest" > + <option name="package" value="com.android.frameworks.inputmethodcoretests" /> + <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" /> + <option name="hidden-api-checks" value="false"/> + </test> +</configuration> diff --git a/core/tests/InputMethodCoreTests/OWNERS b/core/tests/InputMethodCoreTests/OWNERS new file mode 100644 index 000000000000..5deb2ce8f24b --- /dev/null +++ b/core/tests/InputMethodCoreTests/OWNERS @@ -0,0 +1 @@ +include /core/java/android/view/inputmethod/OWNERS diff --git a/core/tests/coretests/res/xml/ime_meta.xml b/core/tests/InputMethodCoreTests/res/xml/ime_meta.xml index a975718c50e9..a975718c50e9 100644 --- a/core/tests/coretests/res/xml/ime_meta.xml +++ b/core/tests/InputMethodCoreTests/res/xml/ime_meta.xml diff --git a/core/tests/coretests/res/xml/ime_meta_inline_suggestions.xml b/core/tests/InputMethodCoreTests/res/xml/ime_meta_inline_suggestions.xml index e67bf6331bfe..e67bf6331bfe 100644 --- a/core/tests/coretests/res/xml/ime_meta_inline_suggestions.xml +++ b/core/tests/InputMethodCoreTests/res/xml/ime_meta_inline_suggestions.xml diff --git a/core/tests/coretests/res/xml/ime_meta_inline_suggestions_with_touch_exploration.xml b/core/tests/InputMethodCoreTests/res/xml/ime_meta_inline_suggestions_with_touch_exploration.xml index 34402089b47d..34402089b47d 100644 --- a/core/tests/coretests/res/xml/ime_meta_inline_suggestions_with_touch_exploration.xml +++ b/core/tests/InputMethodCoreTests/res/xml/ime_meta_inline_suggestions_with_touch_exploration.xml diff --git a/core/tests/coretests/res/xml/ime_meta_sw_next.xml b/core/tests/InputMethodCoreTests/res/xml/ime_meta_sw_next.xml index 2e2ee33e9933..2e2ee33e9933 100644 --- a/core/tests/coretests/res/xml/ime_meta_sw_next.xml +++ b/core/tests/InputMethodCoreTests/res/xml/ime_meta_sw_next.xml diff --git a/core/tests/coretests/res/xml/ime_meta_virtual_device_only.xml b/core/tests/InputMethodCoreTests/res/xml/ime_meta_virtual_device_only.xml index 1905365808bc..1905365808bc 100644 --- a/core/tests/coretests/res/xml/ime_meta_virtual_device_only.xml +++ b/core/tests/InputMethodCoreTests/res/xml/ime_meta_virtual_device_only.xml diff --git a/core/tests/coretests/res/xml/ime_meta_vr_only.xml b/core/tests/InputMethodCoreTests/res/xml/ime_meta_vr_only.xml index 653a8ffcb944..653a8ffcb944 100644 --- a/core/tests/coretests/res/xml/ime_meta_vr_only.xml +++ b/core/tests/InputMethodCoreTests/res/xml/ime_meta_vr_only.xml diff --git a/core/tests/coretests/src/android/view/inputmethod/BaseInputConnectionTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/BaseInputConnectionTest.java index f04f603564f6..f04f603564f6 100644 --- a/core/tests/coretests/src/android/view/inputmethod/BaseInputConnectionTest.java +++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/BaseInputConnectionTest.java diff --git a/core/tests/coretests/src/android/view/inputmethod/CursorAnchorInfoTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/CursorAnchorInfoTest.java index 9d7d71d8d539..9d7d71d8d539 100644 --- a/core/tests/coretests/src/android/view/inputmethod/CursorAnchorInfoTest.java +++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/CursorAnchorInfoTest.java diff --git a/core/tests/coretests/src/android/view/inputmethod/DeleteRangeGestureTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/DeleteRangeGestureTest.java index d7b911dda672..d7b911dda672 100644 --- a/core/tests/coretests/src/android/view/inputmethod/DeleteRangeGestureTest.java +++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/DeleteRangeGestureTest.java diff --git a/core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/EditorInfoTest.java index 4839dd27b283..4839dd27b283 100644 --- a/core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java +++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/EditorInfoTest.java diff --git a/core/tests/coretests/src/android/view/inputmethod/InputMethodInfoTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodInfoTest.java index 909af7b4c5fb..a3f537ef5f1c 100644 --- a/core/tests/coretests/src/android/view/inputmethod/InputMethodInfoTest.java +++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodInfoTest.java @@ -32,7 +32,7 @@ import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; -import com.android.frameworks.coretests.R; +import com.android.frameworks.inputmethodcoretests.R; import org.junit.Rule; import org.junit.Test; diff --git a/core/tests/coretests/src/android/view/inputmethod/InputMethodManagerTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodManagerTest.java index d70572444128..d70572444128 100644 --- a/core/tests/coretests/src/android/view/inputmethod/InputMethodManagerTest.java +++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodManagerTest.java diff --git a/core/tests/coretests/src/android/view/inputmethod/InputMethodSubtypeArrayTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodSubtypeArrayTest.java index e7b1110f898a..e7b1110f898a 100644 --- a/core/tests/coretests/src/android/view/inputmethod/InputMethodSubtypeArrayTest.java +++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodSubtypeArrayTest.java diff --git a/core/tests/coretests/src/android/view/inputmethod/InputMethodSubtypeTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodSubtypeTest.java index 5095cad1b607..5095cad1b607 100644 --- a/core/tests/coretests/src/android/view/inputmethod/InputMethodSubtypeTest.java +++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodSubtypeTest.java diff --git a/core/tests/coretests/src/android/view/inputmethod/InsertGestureTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InsertGestureTest.java index 47a724d36038..47a724d36038 100644 --- a/core/tests/coretests/src/android/view/inputmethod/InsertGestureTest.java +++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InsertGestureTest.java diff --git a/core/tests/coretests/src/android/view/inputmethod/InsertModeGestureTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InsertModeGestureTest.java index a94f8772fcd0..a94f8772fcd0 100644 --- a/core/tests/coretests/src/android/view/inputmethod/InsertModeGestureTest.java +++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InsertModeGestureTest.java diff --git a/core/tests/coretests/src/android/view/inputmethod/ParcelableHandwritingGestureTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/ParcelableHandwritingGestureTest.java index 90f7d06857c7..90f7d06857c7 100644 --- a/core/tests/coretests/src/android/view/inputmethod/ParcelableHandwritingGestureTest.java +++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/ParcelableHandwritingGestureTest.java diff --git a/core/tests/coretests/src/android/view/inputmethod/SelectGestureTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/SelectGestureTest.java index b2eb07c0a9e7..b2eb07c0a9e7 100644 --- a/core/tests/coretests/src/android/view/inputmethod/SelectGestureTest.java +++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/SelectGestureTest.java diff --git a/core/tests/coretests/src/android/view/inputmethod/SelectRangeGestureTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/SelectRangeGestureTest.java index df63a4aaaefe..df63a4aaaefe 100644 --- a/core/tests/coretests/src/android/view/inputmethod/SelectRangeGestureTest.java +++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/SelectRangeGestureTest.java diff --git a/core/tests/coretests/src/android/view/inputmethod/SparseRectFArrayTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/SparseRectFArrayTest.java index f264cc630dc5..f264cc630dc5 100644 --- a/core/tests/coretests/src/android/view/inputmethod/SparseRectFArrayTest.java +++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/SparseRectFArrayTest.java diff --git a/core/tests/coretests/src/android/view/inputmethod/SurroundingTextTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/SurroundingTextTest.java index 047f33074460..047f33074460 100644 --- a/core/tests/coretests/src/android/view/inputmethod/SurroundingTextTest.java +++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/SurroundingTextTest.java diff --git a/core/tests/coretests/src/android/view/inputmethod/TextAppearanceInfoTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/TextAppearanceInfoTest.java index 0750cf1a64ab..0750cf1a64ab 100644 --- a/core/tests/coretests/src/android/view/inputmethod/TextAppearanceInfoTest.java +++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/TextAppearanceInfoTest.java diff --git a/core/tests/coretests/src/com/android/internal/inputmethod/CompletableFutureUtilTest.kt b/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/CompletableFutureUtilTest.kt index 07471f08e0f5..07471f08e0f5 100644 --- a/core/tests/coretests/src/com/android/internal/inputmethod/CompletableFutureUtilTest.kt +++ b/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/CompletableFutureUtilTest.kt diff --git a/core/tests/coretests/src/com/android/internal/inputmethod/InputConnectionWrapperTest.java b/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/InputConnectionWrapperTest.java index a6262944e8b0..a6262944e8b0 100644 --- a/core/tests/coretests/src/com/android/internal/inputmethod/InputConnectionWrapperTest.java +++ b/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/InputConnectionWrapperTest.java diff --git a/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodDebugTest.java b/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/InputMethodDebugTest.java index 32bfdcb7b217..32bfdcb7b217 100644 --- a/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodDebugTest.java +++ b/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/InputMethodDebugTest.java diff --git a/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodSubtypeHandleTest.java b/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/InputMethodSubtypeHandleTest.java index f111bf6fcd64..f111bf6fcd64 100644 --- a/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodSubtypeHandleTest.java +++ b/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/InputMethodSubtypeHandleTest.java diff --git a/core/tests/coretests/src/com/android/internal/inputmethod/SubtypeLocaleUtilsTest.java b/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/SubtypeLocaleUtilsTest.java index ba6390808151..ba6390808151 100644 --- a/core/tests/coretests/src/com/android/internal/inputmethod/SubtypeLocaleUtilsTest.java +++ b/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/SubtypeLocaleUtilsTest.java diff --git a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java index a709d7be898b..52ff0d4037e8 100644 --- a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java +++ b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java @@ -20,6 +20,7 @@ import static android.window.OnBackInvokedDispatcher.PRIORITY_DEFAULT; import static android.window.OnBackInvokedDispatcher.PRIORITY_OVERLAY; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.atLeast; @@ -358,7 +359,7 @@ public class WindowOnBackInvokedDispatcherTest { } @Test - public void onDetachFromWindow_cancelCallbackAndIgnoreOnBackInvoked() throws RemoteException { + public void onDetachFromWindow_cancelsBackAnimation() throws RemoteException { mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback1); OnBackInvokedCallbackInfo callbackInfo = assertSetCallbackInfo(); @@ -368,13 +369,12 @@ public class WindowOnBackInvokedDispatcherTest { waitForIdle(); verify(mCallback1).onBackStarted(any(BackEvent.class)); - // This should trigger mCallback1.onBackCancelled() + // This should trigger mCallback1.onBackCancelled() and unset the callback in WM mDispatcher.detachFromWindow(); - // This should be ignored by mCallback1 - callbackInfo.getCallback().onBackInvoked(); + OnBackInvokedCallbackInfo callbackInfo1 = assertSetCallbackInfo(); + assertNull(callbackInfo1); waitForIdle(); - verify(mCallback1, never()).onBackInvoked(); verify(mCallback1).onBackCancelled(); } } diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index 2de305f8e925..28734283e9b7 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -450,7 +450,7 @@ applications that come with the platform <!-- Permissions required for CTS test - android.server.biometrics --> <permission name="android.permission.USE_BIOMETRIC" /> <permission name="android.permission.TEST_BIOMETRIC" /> - <permission name="android.permission.MANAGE_BIOMETRIC_DIALOG" /> + <permission name="android.permission.SET_BIOMETRIC_DIALOG_LOGO" /> <permission name="android.permission.USE_BACKGROUND_FACE_AUTHENTICATION" /> <!-- Permissions required for CTS test - CtsContactsProviderTestCases --> <permission name="android.contacts.permission.MANAGE_SIM_ACCOUNTS" /> @@ -642,5 +642,6 @@ applications that come with the platform <privapp-permissions package="com.android.devicediagnostics"> <permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/> + <permission name="android.permission.BATTERY_STATS"/> </privapp-permissions> </permissions> diff --git a/libs/WindowManager/Shell/multivalentTests/OWNERS b/libs/WindowManager/Shell/multivalentTests/OWNERS new file mode 100644 index 000000000000..24c1a3a6d400 --- /dev/null +++ b/libs/WindowManager/Shell/multivalentTests/OWNERS @@ -0,0 +1,4 @@ +atsjenk@google.com +liranb@google.com +madym@google.com + diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java index 554b1fb99550..4ba05ce8aef1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java @@ -32,6 +32,7 @@ import static com.android.wm.shell.windowdecor.MoveToDesktopAnimator.DRAG_FREEFO import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; +import android.annotation.NonNull; import android.app.ActivityManager; import android.app.ActivityManager.RunningTaskInfo; import android.app.ActivityTaskManager; @@ -60,7 +61,6 @@ import android.view.ViewConfiguration; import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; -import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; @@ -544,12 +544,22 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { return true; } + /** + * Perform a task size toggle on release of the double-tap, assuming no drag event + * was handled during the double-tap. + * @param e The motion event that occurred during the double-tap gesture. + * @return true if the event should be consumed, false if not + */ @Override - public boolean onDoubleTap(@NonNull MotionEvent e) { + public boolean onDoubleTapEvent(@NonNull MotionEvent e) { + final int action = e.getActionMasked(); + if (mIsDragging || (action != MotionEvent.ACTION_UP + && action != MotionEvent.ACTION_CANCEL)) { + return false; + } final RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId); - mDesktopTasksController.ifPresent(c -> { - c.toggleDesktopTaskSize(taskInfo, mWindowDecorByTaskId.get(taskInfo.taskId)); - }); + mDesktopTasksController.ifPresent(c -> c.toggleDesktopTaskSize(taskInfo, + mWindowDecorByTaskId.get(taskInfo.taskId))); return true; } } diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp index 0abb6f5ed011..4e330da417be 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -629,14 +629,6 @@ cc_defaults { // Allow implicit fallthroughs in HardwareBitmapUploader.cpp until they are fixed. cflags: ["-Wno-implicit-fallthrough"], }, - host: { - srcs: [ - "utils/HostColorSpace.cpp", - ], - export_static_lib_headers: [ - "libarect", - ], - }, }, } diff --git a/libs/hwui/utils/HostColorSpace.cpp b/libs/hwui/utils/HostColorSpace.cpp deleted file mode 100644 index 77a6820c6999..000000000000 --- a/libs/hwui/utils/HostColorSpace.cpp +++ /dev/null @@ -1,417 +0,0 @@ -/* - * Copyright (C) 2019 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. - */ -// This is copied from framework/native/libs/ui in order not to include libui in host build - -#include <ui/ColorSpace.h> - -using namespace std::placeholders; - -namespace android { - -static constexpr float linearResponse(float v) { - return v; -} - -static constexpr float rcpResponse(float x, const ColorSpace::TransferParameters& p) { - return x >= p.d * p.c ? (std::pow(x, 1.0f / p.g) - p.b) / p.a : x / p.c; -} - -static constexpr float response(float x, const ColorSpace::TransferParameters& p) { - return x >= p.d ? std::pow(p.a * x + p.b, p.g) : p.c * x; -} - -static constexpr float rcpFullResponse(float x, const ColorSpace::TransferParameters& p) { - return x >= p.d * p.c ? (std::pow(x - p.e, 1.0f / p.g) - p.b) / p.a : (x - p.f) / p.c; -} - -static constexpr float fullResponse(float x, const ColorSpace::TransferParameters& p) { - return x >= p.d ? std::pow(p.a * x + p.b, p.g) + p.e : p.c * x + p.f; -} - -static float absRcpResponse(float x, float g,float a, float b, float c, float d) { - float xx = std::abs(x); - return std::copysign(xx >= d * c ? (std::pow(xx, 1.0f / g) - b) / a : xx / c, x); -} - -static float absResponse(float x, float g, float a, float b, float c, float d) { - float xx = std::abs(x); - return std::copysign(xx >= d ? std::pow(a * xx + b, g) : c * xx, x); -} - -static float safePow(float x, float e) { - return powf(x < 0.0f ? 0.0f : x, e); -} - -static ColorSpace::transfer_function toOETF(const ColorSpace::TransferParameters& parameters) { - if (parameters.e == 0.0f && parameters.f == 0.0f) { - return std::bind(rcpResponse, _1, parameters); - } - return std::bind(rcpFullResponse, _1, parameters); -} - -static ColorSpace::transfer_function toEOTF( const ColorSpace::TransferParameters& parameters) { - if (parameters.e == 0.0f && parameters.f == 0.0f) { - return std::bind(response, _1, parameters); - } - return std::bind(fullResponse, _1, parameters); -} - -static ColorSpace::transfer_function toOETF(float gamma) { - if (gamma == 1.0f) { - return linearResponse; - } - return std::bind(safePow, _1, 1.0f / gamma); -} - -static ColorSpace::transfer_function toEOTF(float gamma) { - if (gamma == 1.0f) { - return linearResponse; - } - return std::bind(safePow, _1, gamma); -} - -static constexpr std::array<float2, 3> computePrimaries(const mat3& rgbToXYZ) { - float3 r(rgbToXYZ * float3{1, 0, 0}); - float3 g(rgbToXYZ * float3{0, 1, 0}); - float3 b(rgbToXYZ * float3{0, 0, 1}); - - return {{r.xy / dot(r, float3{1}), - g.xy / dot(g, float3{1}), - b.xy / dot(b, float3{1})}}; -} - -static constexpr float2 computeWhitePoint(const mat3& rgbToXYZ) { - float3 w(rgbToXYZ * float3{1}); - return w.xy / dot(w, float3{1}); -} - -ColorSpace::ColorSpace( - const std::string& name, - const mat3& rgbToXYZ, - transfer_function OETF, - transfer_function EOTF, - clamping_function clamper) noexcept - : mName(name) - , mRGBtoXYZ(rgbToXYZ) - , mXYZtoRGB(inverse(rgbToXYZ)) - , mOETF(std::move(OETF)) - , mEOTF(std::move(EOTF)) - , mClamper(std::move(clamper)) - , mPrimaries(computePrimaries(rgbToXYZ)) - , mWhitePoint(computeWhitePoint(rgbToXYZ)) { -} - -ColorSpace::ColorSpace( - const std::string& name, - const mat3& rgbToXYZ, - const TransferParameters parameters, - clamping_function clamper) noexcept - : mName(name) - , mRGBtoXYZ(rgbToXYZ) - , mXYZtoRGB(inverse(rgbToXYZ)) - , mParameters(parameters) - , mOETF(toOETF(mParameters)) - , mEOTF(toEOTF(mParameters)) - , mClamper(std::move(clamper)) - , mPrimaries(computePrimaries(rgbToXYZ)) - , mWhitePoint(computeWhitePoint(rgbToXYZ)) { -} - -ColorSpace::ColorSpace( - const std::string& name, - const mat3& rgbToXYZ, - float gamma, - clamping_function clamper) noexcept - : mName(name) - , mRGBtoXYZ(rgbToXYZ) - , mXYZtoRGB(inverse(rgbToXYZ)) - , mParameters({gamma, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}) - , mOETF(toOETF(gamma)) - , mEOTF(toEOTF(gamma)) - , mClamper(std::move(clamper)) - , mPrimaries(computePrimaries(rgbToXYZ)) - , mWhitePoint(computeWhitePoint(rgbToXYZ)) { -} - -ColorSpace::ColorSpace( - const std::string& name, - const std::array<float2, 3>& primaries, - const float2& whitePoint, - transfer_function OETF, - transfer_function EOTF, - clamping_function clamper) noexcept - : mName(name) - , mRGBtoXYZ(computeXYZMatrix(primaries, whitePoint)) - , mXYZtoRGB(inverse(mRGBtoXYZ)) - , mOETF(std::move(OETF)) - , mEOTF(std::move(EOTF)) - , mClamper(std::move(clamper)) - , mPrimaries(primaries) - , mWhitePoint(whitePoint) { -} - -ColorSpace::ColorSpace( - const std::string& name, - const std::array<float2, 3>& primaries, - const float2& whitePoint, - const TransferParameters parameters, - clamping_function clamper) noexcept - : mName(name) - , mRGBtoXYZ(computeXYZMatrix(primaries, whitePoint)) - , mXYZtoRGB(inverse(mRGBtoXYZ)) - , mParameters(parameters) - , mOETF(toOETF(mParameters)) - , mEOTF(toEOTF(mParameters)) - , mClamper(std::move(clamper)) - , mPrimaries(primaries) - , mWhitePoint(whitePoint) { -} - -ColorSpace::ColorSpace( - const std::string& name, - const std::array<float2, 3>& primaries, - const float2& whitePoint, - float gamma, - clamping_function clamper) noexcept - : mName(name) - , mRGBtoXYZ(computeXYZMatrix(primaries, whitePoint)) - , mXYZtoRGB(inverse(mRGBtoXYZ)) - , mParameters({gamma, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}) - , mOETF(toOETF(gamma)) - , mEOTF(toEOTF(gamma)) - , mClamper(std::move(clamper)) - , mPrimaries(primaries) - , mWhitePoint(whitePoint) { -} - -constexpr mat3 ColorSpace::computeXYZMatrix( - const std::array<float2, 3>& primaries, const float2& whitePoint) { - const float2& R = primaries[0]; - const float2& G = primaries[1]; - const float2& B = primaries[2]; - const float2& W = whitePoint; - - float oneRxRy = (1 - R.x) / R.y; - float oneGxGy = (1 - G.x) / G.y; - float oneBxBy = (1 - B.x) / B.y; - float oneWxWy = (1 - W.x) / W.y; - - float RxRy = R.x / R.y; - float GxGy = G.x / G.y; - float BxBy = B.x / B.y; - float WxWy = W.x / W.y; - - float BY = - ((oneWxWy - oneRxRy) * (GxGy - RxRy) - (WxWy - RxRy) * (oneGxGy - oneRxRy)) / - ((oneBxBy - oneRxRy) * (GxGy - RxRy) - (BxBy - RxRy) * (oneGxGy - oneRxRy)); - float GY = (WxWy - RxRy - BY * (BxBy - RxRy)) / (GxGy - RxRy); - float RY = 1 - GY - BY; - - float RYRy = RY / R.y; - float GYGy = GY / G.y; - float BYBy = BY / B.y; - - return { - float3{RYRy * R.x, RY, RYRy * (1 - R.x - R.y)}, - float3{GYGy * G.x, GY, GYGy * (1 - G.x - G.y)}, - float3{BYBy * B.x, BY, BYBy * (1 - B.x - B.y)} - }; -} - -const ColorSpace ColorSpace::sRGB() { - return { - "sRGB IEC61966-2.1", - {{float2{0.640f, 0.330f}, {0.300f, 0.600f}, {0.150f, 0.060f}}}, - {0.3127f, 0.3290f}, - {2.4f, 1 / 1.055f, 0.055f / 1.055f, 1 / 12.92f, 0.04045f, 0.0f, 0.0f} - }; -} - -const ColorSpace ColorSpace::linearSRGB() { - return { - "sRGB IEC61966-2.1 (Linear)", - {{float2{0.640f, 0.330f}, {0.300f, 0.600f}, {0.150f, 0.060f}}}, - {0.3127f, 0.3290f} - }; -} - -const ColorSpace ColorSpace::extendedSRGB() { - return { - "scRGB-nl IEC 61966-2-2:2003", - {{float2{0.640f, 0.330f}, {0.300f, 0.600f}, {0.150f, 0.060f}}}, - {0.3127f, 0.3290f}, - std::bind(absRcpResponse, _1, 2.4f, 1 / 1.055f, 0.055f / 1.055f, 1 / 12.92f, 0.04045f), - std::bind(absResponse, _1, 2.4f, 1 / 1.055f, 0.055f / 1.055f, 1 / 12.92f, 0.04045f), - std::bind(clamp<float>, _1, -0.799f, 2.399f) - }; -} - -const ColorSpace ColorSpace::linearExtendedSRGB() { - return { - "scRGB IEC 61966-2-2:2003", - {{float2{0.640f, 0.330f}, {0.300f, 0.600f}, {0.150f, 0.060f}}}, - {0.3127f, 0.3290f}, - 1.0f, - std::bind(clamp<float>, _1, -0.5f, 7.499f) - }; -} - -const ColorSpace ColorSpace::NTSC() { - return { - "NTSC (1953)", - {{float2{0.67f, 0.33f}, {0.21f, 0.71f}, {0.14f, 0.08f}}}, - {0.310f, 0.316f}, - {1 / 0.45f, 1 / 1.099f, 0.099f / 1.099f, 1 / 4.5f, 0.081f, 0.0f, 0.0f} - }; -} - -const ColorSpace ColorSpace::BT709() { - return { - "Rec. ITU-R BT.709-5", - {{float2{0.640f, 0.330f}, {0.300f, 0.600f}, {0.150f, 0.060f}}}, - {0.3127f, 0.3290f}, - {1 / 0.45f, 1 / 1.099f, 0.099f / 1.099f, 1 / 4.5f, 0.081f, 0.0f, 0.0f} - }; -} - -const ColorSpace ColorSpace::BT2020() { - return { - "Rec. ITU-R BT.2020-1", - {{float2{0.708f, 0.292f}, {0.170f, 0.797f}, {0.131f, 0.046f}}}, - {0.3127f, 0.3290f}, - {1 / 0.45f, 1 / 1.099f, 0.099f / 1.099f, 1 / 4.5f, 0.081f, 0.0f, 0.0f} - }; -} - -const ColorSpace ColorSpace::AdobeRGB() { - return { - "Adobe RGB (1998)", - {{float2{0.64f, 0.33f}, {0.21f, 0.71f}, {0.15f, 0.06f}}}, - {0.3127f, 0.3290f}, - 2.2f - }; -} - -const ColorSpace ColorSpace::ProPhotoRGB() { - return { - "ROMM RGB ISO 22028-2:2013", - {{float2{0.7347f, 0.2653f}, {0.1596f, 0.8404f}, {0.0366f, 0.0001f}}}, - {0.34567f, 0.35850f}, - {1.8f, 1.0f, 0.0f, 1 / 16.0f, 0.031248f, 0.0f, 0.0f} - }; -} - -const ColorSpace ColorSpace::DisplayP3() { - return { - "Display P3", - {{float2{0.680f, 0.320f}, {0.265f, 0.690f}, {0.150f, 0.060f}}}, - {0.3127f, 0.3290f}, - {2.4f, 1 / 1.055f, 0.055f / 1.055f, 1 / 12.92f, 0.039f, 0.0f, 0.0f} - }; -} - -const ColorSpace ColorSpace::DCIP3() { - return { - "SMPTE RP 431-2-2007 DCI (P3)", - {{float2{0.680f, 0.320f}, {0.265f, 0.690f}, {0.150f, 0.060f}}}, - {0.314f, 0.351f}, - 2.6f - }; -} - -const ColorSpace ColorSpace::ACES() { - return { - "SMPTE ST 2065-1:2012 ACES", - {{float2{0.73470f, 0.26530f}, {0.0f, 1.0f}, {0.00010f, -0.0770f}}}, - {0.32168f, 0.33767f}, - 1.0f, - std::bind(clamp<float>, _1, -65504.0f, 65504.0f) - }; -} - -const ColorSpace ColorSpace::ACEScg() { - return { - "Academy S-2014-004 ACEScg", - {{float2{0.713f, 0.293f}, {0.165f, 0.830f}, {0.128f, 0.044f}}}, - {0.32168f, 0.33767f}, - 1.0f, - std::bind(clamp<float>, _1, -65504.0f, 65504.0f) - }; -} - -std::unique_ptr<float3[]> ColorSpace::createLUT(uint32_t size, const ColorSpace& src, - const ColorSpace& dst) { - size = clamp(size, 2u, 256u); - float m = 1.0f / float(size - 1); - - std::unique_ptr<float3[]> lut(new float3[size * size * size]); - float3* data = lut.get(); - - ColorSpaceConnector connector(src, dst); - - for (uint32_t z = 0; z < size; z++) { - for (int32_t y = int32_t(size - 1); y >= 0; y--) { - for (uint32_t x = 0; x < size; x++) { - *data++ = connector.transform({x * m, y * m, z * m}); - } - } - } - - return lut; -} - -static const float2 ILLUMINANT_D50_XY = {0.34567f, 0.35850f}; -static const float3 ILLUMINANT_D50_XYZ = {0.964212f, 1.0f, 0.825188f}; -static const mat3 BRADFORD = mat3{ - float3{ 0.8951f, -0.7502f, 0.0389f}, - float3{ 0.2664f, 1.7135f, -0.0685f}, - float3{-0.1614f, 0.0367f, 1.0296f} -}; - -static mat3 adaptation(const mat3& matrix, const float3& srcWhitePoint, const float3& dstWhitePoint) { - float3 srcLMS = matrix * srcWhitePoint; - float3 dstLMS = matrix * dstWhitePoint; - return inverse(matrix) * mat3{dstLMS / srcLMS} * matrix; -} - -ColorSpaceConnector::ColorSpaceConnector( - const ColorSpace& src, - const ColorSpace& dst) noexcept - : mSource(src) - , mDestination(dst) { - - if (all(lessThan(abs(src.getWhitePoint() - dst.getWhitePoint()), float2{1e-3f}))) { - mTransform = dst.getXYZtoRGB() * src.getRGBtoXYZ(); - } else { - mat3 rgbToXYZ(src.getRGBtoXYZ()); - mat3 xyzToRGB(dst.getXYZtoRGB()); - - float3 srcXYZ = ColorSpace::XYZ(float3{src.getWhitePoint(), 1}); - float3 dstXYZ = ColorSpace::XYZ(float3{dst.getWhitePoint(), 1}); - - if (any(greaterThan(abs(src.getWhitePoint() - ILLUMINANT_D50_XY), float2{1e-3f}))) { - rgbToXYZ = adaptation(BRADFORD, srcXYZ, ILLUMINANT_D50_XYZ) * src.getRGBtoXYZ(); - } - - if (any(greaterThan(abs(dst.getWhitePoint() - ILLUMINANT_D50_XY), float2{1e-3f}))) { - xyzToRGB = inverse(adaptation(BRADFORD, dstXYZ, ILLUMINANT_D50_XYZ) * dst.getRGBtoXYZ()); - } - - mTransform = xyzToRGB * rgbToXYZ; - } -} - -}; // namespace android diff --git a/location/java/android/location/altitude/AltitudeConverter.java b/location/java/android/location/altitude/AltitudeConverter.java index 3dc024efef56..6f8891216bed 100644 --- a/location/java/android/location/altitude/AltitudeConverter.java +++ b/location/java/android/location/altitude/AltitudeConverter.java @@ -19,9 +19,11 @@ package android.location.altitude; import android.annotation.NonNull; import android.annotation.WorkerThread; import android.content.Context; +import android.frameworks.location.altitude.GetGeoidHeightRequest; +import android.frameworks.location.altitude.GetGeoidHeightResponse; import android.location.Location; -import com.android.internal.location.altitude.GeoidHeightMap; +import com.android.internal.location.altitude.GeoidMap; import com.android.internal.location.altitude.S2CellIdUtils; import com.android.internal.location.altitude.nano.MapParamsProto; import com.android.internal.util.Preconditions; @@ -37,7 +39,7 @@ import java.io.IOException; * <pre> * Brian Julian and Michael Angermann. * "Resource efficient and accurate altitude conversion to Mean Sea Level." - * To appear in 2023 IEEE/ION Position, Location and Navigation Symposium (PLANS). + * 2023 IEEE/ION Position, Location and Navigation Symposium (PLANS). * </pre> */ public final class AltitudeConverter { @@ -45,8 +47,8 @@ public final class AltitudeConverter { private static final double MAX_ABS_VALID_LATITUDE = 90; private static final double MAX_ABS_VALID_LONGITUDE = 180; - /** Manages a mapping of geoid heights associated with S2 cells. */ - private final GeoidHeightMap mGeoidHeightMap = new GeoidHeightMap(); + /** Manages a mapping of geoid heights and expiration distances associated with S2 cells. */ + private final GeoidMap mGeoidMap = new GeoidMap(); /** * Creates an instance that manages an independent cache to optimized conversions of locations @@ -78,75 +80,87 @@ public final class AltitudeConverter { /** * Returns the four S2 cell IDs for the map square associated with the {@code location}. * - * <p>The first map cell contains the location, while the others are located horizontally, - * vertically, and diagonally, in that order, with respect to the S2 (i,j) coordinate system. If - * the diagonal map cell does not exist (i.e., the location is near an S2 cube vertex), its - * corresponding ID is set to zero. + * <p>The first map cell, denoted z11 in the appendix of the referenced paper above, contains + * the location. The others are the map cells denoted z21, z12, and z22, in that order. */ - @NonNull - private static long[] findMapSquare(@NonNull MapParamsProto params, + private static long[] findMapSquare(@NonNull MapParamsProto geoidHeightParams, @NonNull Location location) { long s2CellId = S2CellIdUtils.fromLatLngDegrees(location.getLatitude(), location.getLongitude()); // Cell-space properties and coordinates. - int sizeIj = 1 << (S2CellIdUtils.MAX_LEVEL - params.mapS2Level); + int sizeIj = 1 << (S2CellIdUtils.MAX_LEVEL - geoidHeightParams.mapS2Level); int maxIj = 1 << S2CellIdUtils.MAX_LEVEL; - long s0 = S2CellIdUtils.getParent(s2CellId, params.mapS2Level); - int f0 = S2CellIdUtils.getFace(s2CellId); - int i0 = S2CellIdUtils.getI(s2CellId); - int j0 = S2CellIdUtils.getJ(s2CellId); - int i1 = i0 + sizeIj; - int j1 = j0 + sizeIj; + long z11 = S2CellIdUtils.getParent(s2CellId, geoidHeightParams.mapS2Level); + int f11 = S2CellIdUtils.getFace(s2CellId); + int i1 = S2CellIdUtils.getI(s2CellId); + int j1 = S2CellIdUtils.getJ(s2CellId); + int i2 = i1 + sizeIj; + int j2 = j1 + sizeIj; // Non-boundary region calculation - simplest and most common case. - if (i1 < maxIj && j1 < maxIj) { - return new long[]{ - s0, - S2CellIdUtils.getParent(S2CellIdUtils.fromFij(f0, i1, j0), params.mapS2Level), - S2CellIdUtils.getParent(S2CellIdUtils.fromFij(f0, i0, j1), params.mapS2Level), - S2CellIdUtils.getParent(S2CellIdUtils.fromFij(f0, i1, j1), params.mapS2Level) - }; + if (i2 < maxIj && j2 < maxIj) { + return new long[]{z11, S2CellIdUtils.getParent(S2CellIdUtils.fromFij(f11, i2, j1), + geoidHeightParams.mapS2Level), S2CellIdUtils.getParent( + S2CellIdUtils.fromFij(f11, i1, j2), geoidHeightParams.mapS2Level), + S2CellIdUtils.getParent(S2CellIdUtils.fromFij(f11, i2, j2), + geoidHeightParams.mapS2Level)}; } - // Boundary region calculation. + // Boundary region calculation long[] edgeNeighbors = new long[4]; - S2CellIdUtils.getEdgeNeighbors(s0, edgeNeighbors); - long s1 = edgeNeighbors[1]; - long s2 = edgeNeighbors[2]; - long s3; - if (f0 % 2 == 1) { - S2CellIdUtils.getEdgeNeighbors(s1, edgeNeighbors); - if (i1 < maxIj) { - s3 = edgeNeighbors[2]; - } else { - s3 = s1; - s1 = edgeNeighbors[1]; - } - } else { - S2CellIdUtils.getEdgeNeighbors(s2, edgeNeighbors); - if (j1 < maxIj) { - s3 = edgeNeighbors[1]; - } else { - s3 = s2; - s2 = edgeNeighbors[3]; - } - } + S2CellIdUtils.getEdgeNeighbors(z11, edgeNeighbors); + long z11W = edgeNeighbors[0]; + long z11S = edgeNeighbors[1]; + long z11E = edgeNeighbors[2]; + long z11N = edgeNeighbors[3]; + + long[] otherEdgeNeighbors = new long[4]; + S2CellIdUtils.getEdgeNeighbors(z11W, otherEdgeNeighbors); + S2CellIdUtils.getEdgeNeighbors(z11S, edgeNeighbors); + long z11Sw = findCommonNeighbor(edgeNeighbors, otherEdgeNeighbors, z11); + S2CellIdUtils.getEdgeNeighbors(z11E, otherEdgeNeighbors); + long z11Se = findCommonNeighbor(edgeNeighbors, otherEdgeNeighbors, z11); + S2CellIdUtils.getEdgeNeighbors(z11N, edgeNeighbors); + long z11Ne = findCommonNeighbor(edgeNeighbors, otherEdgeNeighbors, z11); + + long z21 = (f11 % 2 == 1 && i2 >= maxIj) ? z11Sw : z11S; + long z12 = (f11 % 2 == 0 && j2 >= maxIj) ? z11Ne : z11E; + long z22 = (z21 == z11Sw) ? z11S : (z12 == z11Ne) ? z11E : z11Se; // Reuse edge neighbors' array to avoid an extra allocation. - edgeNeighbors[0] = s0; - edgeNeighbors[1] = s1; - edgeNeighbors[2] = s2; - edgeNeighbors[3] = s3; + edgeNeighbors[0] = z11; + edgeNeighbors[1] = z21; + edgeNeighbors[2] = z12; + edgeNeighbors[3] = z22; return edgeNeighbors; } /** + * Returns the first common non-z11 neighbor found between the two arrays of edge neighbors. If + * such a common neighbor does not exist, returns z11. + */ + private static long findCommonNeighbor(long[] edgeNeighbors, long[] otherEdgeNeighbors, + long z11) { + for (long edgeNeighbor : edgeNeighbors) { + if (edgeNeighbor == z11) { + continue; + } + for (long otherEdgeNeighbor : otherEdgeNeighbors) { + if (edgeNeighbor == otherEdgeNeighbor) { + return edgeNeighbor; + } + } + } + return z11; + } + + /** * Adds to {@code location} the bilinearly interpolated Mean Sea Level altitude. In addition, a * Mean Sea Level altitude accuracy is added if the {@code location} has a valid vertical * accuracy; otherwise, does not add a corresponding accuracy. */ - private static void addMslAltitude(@NonNull MapParamsProto params, + private static void addMslAltitude(@NonNull MapParamsProto geoidHeightParams, @NonNull double[] geoidHeightsMeters, @NonNull Location location) { double h0 = geoidHeightsMeters[0]; double h1 = geoidHeightsMeters[1]; @@ -158,7 +172,7 @@ public final class AltitudeConverter { // employ the simplified unit square formulation. long s2CellId = S2CellIdUtils.fromLatLngDegrees(location.getLatitude(), location.getLongitude()); - double sizeIj = 1 << (S2CellIdUtils.MAX_LEVEL - params.mapS2Level); + double sizeIj = 1 << (S2CellIdUtils.MAX_LEVEL - geoidHeightParams.mapS2Level); double wi = (S2CellIdUtils.getI(s2CellId) % sizeIj) / sizeIj; double wj = (S2CellIdUtils.getJ(s2CellId) % sizeIj) / sizeIj; double offsetMeters = h0 + (h1 - h0) * wi + (h2 - h0) * wj + (h3 - h1 - h2 + h0) * wi * wj; @@ -167,8 +181,8 @@ public final class AltitudeConverter { if (location.hasVerticalAccuracy()) { double verticalAccuracyMeters = location.getVerticalAccuracyMeters(); if (Double.isFinite(verticalAccuracyMeters) && verticalAccuracyMeters >= 0) { - location.setMslAltitudeAccuracyMeters( - (float) Math.hypot(verticalAccuracyMeters, params.modelRmseMeters)); + location.setMslAltitudeAccuracyMeters((float) Math.hypot(verticalAccuracyMeters, + geoidHeightParams.modelRmseMeters)); } } } @@ -191,10 +205,11 @@ public final class AltitudeConverter { public void addMslAltitudeToLocation(@NonNull Context context, @NonNull Location location) throws IOException { validate(location); - MapParamsProto params = GeoidHeightMap.getParams(context); - long[] s2CellIds = findMapSquare(params, location); - double[] geoidHeightsMeters = mGeoidHeightMap.readGeoidHeights(params, context, s2CellIds); - addMslAltitude(params, geoidHeightsMeters, location); + MapParamsProto geoidHeightParams = GeoidMap.getGeoidHeightParams(context); + long[] mapCells = findMapSquare(geoidHeightParams, location); + double[] geoidHeightsMeters = mGeoidMap.readGeoidHeights(geoidHeightParams, context, + mapCells); + addMslAltitude(geoidHeightParams, geoidHeightsMeters, location); } /** @@ -206,18 +221,68 @@ public final class AltitudeConverter { */ public boolean addMslAltitudeToLocation(@NonNull Location location) { validate(location); - MapParamsProto params = GeoidHeightMap.getParams(); - if (params == null) { + MapParamsProto geoidHeightParams = GeoidMap.getGeoidHeightParams(); + if (geoidHeightParams == null) { return false; } - long[] s2CellIds = findMapSquare(params, location); - double[] geoidHeightsMeters = mGeoidHeightMap.readGeoidHeights(params, s2CellIds); + long[] mapCells = findMapSquare(geoidHeightParams, location); + double[] geoidHeightsMeters = mGeoidMap.readGeoidHeights(geoidHeightParams, mapCells); if (geoidHeightsMeters == null) { return false; } - addMslAltitude(params, geoidHeightsMeters, location); + addMslAltitude(geoidHeightParams, geoidHeightsMeters, location); return true; } + + /** + * Returns the geoid height (a.k.a. geoid undulation) at the location specified in {@code + * request}. The geoid height at a location is defined as the difference between an altitude + * measured above the World Geodetic System 1984 reference ellipsoid (WGS84) and its + * corresponding Mean Sea Level altitude. + * + * <p>Must be called off the main thread as data may be loaded from raw assets. + * + * @throws IOException if an I/O error occurs when loading data from raw assets. + * @throws IllegalArgumentException if the {@code request} has an invalid latitude or longitude. + * Specifically, the latitude must be between -90 and 90 (both + * inclusive), and the longitude must be between -180 and 180 + * (both inclusive). + * @hide + */ + @WorkerThread + public @NonNull GetGeoidHeightResponse getGeoidHeight(@NonNull Context context, + @NonNull GetGeoidHeightRequest request) throws IOException { + // Create a valid location from which the geoid height and its accuracy will be extracted. + Location location = new Location(""); + location.setLatitude(request.latitudeDegrees); + location.setLongitude(request.longitudeDegrees); + location.setAltitude(0.0); + location.setVerticalAccuracyMeters(0.0f); + + addMslAltitudeToLocation(context, location); + // The geoid height for a location with zero WGS84 altitude is equal in value to the + // negative of corresponding MSL altitude. + double geoidHeightMeters = -location.getMslAltitudeMeters(); + // The geoid height error for a location with zero vertical accuracy is equal in value to + // the corresponding MSL altitude accuracy. + float geoidHeightErrorMeters = location.getMslAltitudeAccuracyMeters(); + + MapParamsProto expirationDistanceParams = GeoidMap.getExpirationDistanceParams(context); + long s2CellId = S2CellIdUtils.fromLatLngDegrees(location.getLatitude(), + location.getLongitude()); + long[] mapCell = {S2CellIdUtils.getParent(s2CellId, expirationDistanceParams.mapS2Level)}; + double expirationDistanceMeters = mGeoidMap.readExpirationDistances( + expirationDistanceParams, context, mapCell)[0]; + float additionalGeoidHeightErrorMeters = (float) expirationDistanceParams.modelRmseMeters; + + GetGeoidHeightResponse response = new GetGeoidHeightResponse(); + response.geoidHeightMeters = geoidHeightMeters; + response.geoidHeightErrorMeters = geoidHeightErrorMeters; + response.expirationDistanceMeters = expirationDistanceMeters; + response.additionalGeoidHeightErrorMeters = additionalGeoidHeightErrorMeters; + response.success = true; + return response; + } } diff --git a/location/java/com/android/internal/location/altitude/GeoidHeightMap.java b/location/java/com/android/internal/location/altitude/GeoidMap.java index 8067050d9da3..9bf5689c1028 100644 --- a/location/java/com/android/internal/location/altitude/GeoidHeightMap.java +++ b/location/java/com/android/internal/location/altitude/GeoidMap.java @@ -34,11 +34,12 @@ import java.nio.ByteBuffer; import java.util.Objects; /** - * Manages a mapping of geoid heights associated with S2 cells, referred to as MAP CELLS. + * Manages a mapping of geoid heights and expiration distances associated with S2 cells, referred to + * as MAP CELLS. * * <p>Tiles are used extensively to reduce the number of entries needed to be stored in memory and - * on disk. A tile associates geoid heights with all map cells of a common parent at a specified S2 - * level. + * on disk. A tile associates geoid heights or expiration distances with all map cells of a common + * parent at a specified S2 level. * * <p>Since bilinear interpolation considers at most four map cells at a time, at most four tiles * are simultaneously stored in memory. These tiles, referred to as CACHE TILES, are each keyed by @@ -48,42 +49,79 @@ import java.util.Objects; * The latter tiles, referred to as DISK TILES, are each keyed by its common parent's S2 cell token, * referred to as a DISK TOKEN. */ -public final class GeoidHeightMap { +public final class GeoidMap { - private static final Object sLock = new Object(); + private static final Object GEOID_HEIGHT_PARAMS_LOCK = new Object(); - @GuardedBy("sLock") + private static final Object EXPIRATION_DISTANCE_PARAMS_LOCK = new Object(); + + @GuardedBy("GEOID_HEIGHT_PARAMS_LOCK") + @Nullable + private static MapParamsProto sGeoidHeightParams; + + @GuardedBy("EXPIRATION_DISTANCE_PARAMS_LOCK") @Nullable - private static MapParamsProto sParams; + private static MapParamsProto sExpirationDistanceParams; + + /** + * Defines a cache large enough to hold all geoid height cache tiles needed for interpolation. + */ + private final LruCache<Long, S2TileProto> mGeoidHeightCacheTiles = new LruCache<>(4); - /** Defines a cache large enough to hold all cache tiles needed for interpolation. */ - private final LruCache<Long, S2TileProto> mCacheTiles = new LruCache<>(4); + /** + * Defines a cache large enough to hold all expiration distance cache tiles needed for + * interpolation. + */ + private final LruCache<Long, S2TileProto> mExpirationDistanceCacheTiles = new LruCache<>(4); /** - * Returns the singleton parameter instance for a spherically projected geoid height map and its - * corresponding tile management. + * Returns the singleton parameter instance for geoid height parameters of a spherically + * projected map. */ @NonNull - public static MapParamsProto getParams(@NonNull Context context) throws IOException { - synchronized (sLock) { - if (sParams == null) { - try (InputStream is = context.getApplicationContext().getAssets().open( - "geoid_height_map/map-params.pb")) { - sParams = MapParamsProto.parseFrom(is.readAllBytes()); - } + public static MapParamsProto getGeoidHeightParams(@NonNull Context context) throws IOException { + synchronized (GEOID_HEIGHT_PARAMS_LOCK) { + if (sGeoidHeightParams == null) { + // TODO: b/304375846 - Configure with disk tile prefix once resources are updated. + sGeoidHeightParams = parseParams(context); + } + return sGeoidHeightParams; + } + } + + /** + * Returns the singleton parameter instance for expiration distance parameters of a spherically + * projected + * map. + */ + @NonNull + public static MapParamsProto getExpirationDistanceParams(@NonNull Context context) + throws IOException { + synchronized (EXPIRATION_DISTANCE_PARAMS_LOCK) { + if (sExpirationDistanceParams == null) { + // TODO: b/304375846 - Configure with disk tile prefix once resources are updated. + sExpirationDistanceParams = parseParams(context); } - return sParams; + return sExpirationDistanceParams; + } + } + + @NonNull + private static MapParamsProto parseParams(@NonNull Context context) throws IOException { + try (InputStream is = context.getApplicationContext().getAssets().open( + "geoid_height_map/map-params.pb")) { + return MapParamsProto.parseFrom(is.readAllBytes()); } } /** - * Same as {@link #getParams(Context)} except that null is returned if the singleton parameter - * instance is not yet initialized. + * Same as {@link #getGeoidHeightParams(Context)} except that null is returned if the singleton + * parameter instance is not yet initialized. */ @Nullable - public static MapParamsProto getParams() { - synchronized (sLock) { - return sParams; + public static MapParamsProto getGeoidHeightParams() { + synchronized (GEOID_HEIGHT_PARAMS_LOCK) { + return sGeoidHeightParams; } } @@ -93,18 +131,17 @@ public final class GeoidHeightMap { @NonNull private static String getDiskToken(@NonNull MapParamsProto params, long s2CellId) { - return S2CellIdUtils.getToken( - S2CellIdUtils.getParent(s2CellId, params.diskTileS2Level)); + return S2CellIdUtils.getToken(S2CellIdUtils.getParent(s2CellId, params.diskTileS2Level)); } /** * Adds to {@code values} values in the unit interval [0, 1] for the map cells identified by - * {@code s2CellIds}. Returns true if values are present for all IDs; otherwise, returns false - * and adds NaNs for absent values. + * {@code s2CellIds}. Returns true if values are present for all IDs; otherwise, adds NaNs for + * absent values and returns false. */ private static boolean getUnitIntervalValues(@NonNull MapParamsProto params, - @NonNull TileFunction tileFunction, - @NonNull long[] s2CellIds, @NonNull double[] values) { + @NonNull TileFunction tileFunction, @NonNull long[] s2CellIds, + @NonNull double[] values) { int len = s2CellIds.length; S2TileProto[] tiles = new S2TileProto[len]; @@ -137,9 +174,8 @@ public final class GeoidHeightMap { @SuppressWarnings("ReferenceEquality") private static void mergeByteBufferValues(@NonNull MapParamsProto params, - @NonNull long[] s2CellIds, - @NonNull S2TileProto[] tiles, - int tileIndex, @NonNull double[] values) { + @NonNull long[] s2CellIds, @NonNull S2TileProto[] tiles, int tileIndex, + @NonNull double[] values) { byte[] bytes = tiles[tileIndex].byteBuffer; if (bytes == null || bytes.length == 0) { return; @@ -163,24 +199,22 @@ public final class GeoidHeightMap { } private static void mergeByteJpegValues(@NonNull MapParamsProto params, - @NonNull long[] s2CellIds, - @NonNull S2TileProto[] tiles, - int tileIndex, @NonNull double[] values) { + @NonNull long[] s2CellIds, @NonNull S2TileProto[] tiles, int tileIndex, + @NonNull double[] values) { mergeByteImageValues(params, tiles[tileIndex].byteJpeg, s2CellIds, tiles, tileIndex, values); } private static void mergeBytePngValues(@NonNull MapParamsProto params, - @NonNull long[] s2CellIds, - @NonNull S2TileProto[] tiles, - int tileIndex, @NonNull double[] values) { + @NonNull long[] s2CellIds, @NonNull S2TileProto[] tiles, int tileIndex, + @NonNull double[] values) { mergeByteImageValues(params, tiles[tileIndex].bytePng, s2CellIds, tiles, tileIndex, values); } @SuppressWarnings("ReferenceEquality") private static void mergeByteImageValues(@NonNull MapParamsProto params, @NonNull byte[] bytes, - @NonNull long[] s2CellIds, - @NonNull S2TileProto[] tiles, int tileIndex, @NonNull double[] values) { + @NonNull long[] s2CellIds, @NonNull S2TileProto[] tiles, int tileIndex, + @NonNull double[] values) { if (bytes == null || bytes.length == 0) { return; } @@ -219,7 +253,7 @@ public final class GeoidHeightMap { * ID. */ private static void validate(@NonNull MapParamsProto params, @NonNull long[] s2CellIds) { - Preconditions.checkArgument(s2CellIds.length == 4); + Preconditions.checkArgument(s2CellIds.length <= 4); for (long s2CellId : s2CellIds) { Preconditions.checkArgument(S2CellIdUtils.getLevel(s2CellId) == params.mapS2Level); } @@ -233,15 +267,38 @@ public final class GeoidHeightMap { @NonNull public double[] readGeoidHeights(@NonNull MapParamsProto params, @NonNull Context context, @NonNull long[] s2CellIds) throws IOException { + return readMapValues(params, context, s2CellIds, mGeoidHeightCacheTiles); + } + + /** + * Returns the expiration distances in meters associated with the map cells identified by + * {@code s2CellIds}. Throws an {@link IOException} if a geoid height cannot be calculated for + * an ID. + */ + @NonNull + public double[] readExpirationDistances(@NonNull MapParamsProto params, + @NonNull Context context, @NonNull long[] s2CellIds) throws IOException { + return readMapValues(params, context, s2CellIds, mExpirationDistanceCacheTiles); + } + + /** + * Returns the map values in meters associated with the map cells identified by + * {@code s2CellIds}. Throws an {@link IOException} if a map value cannot be calculated for an + * ID. + */ + @NonNull + private static double[] readMapValues(@NonNull MapParamsProto params, @NonNull Context context, + @NonNull long[] s2CellIds, @NonNull LruCache<Long, S2TileProto> cacheTiles) + throws IOException { validate(params, s2CellIds); - double[] heightsMeters = new double[s2CellIds.length]; - if (getGeoidHeights(params, mCacheTiles::get, s2CellIds, heightsMeters)) { - return heightsMeters; + double[] mapValuesMeters = new double[s2CellIds.length]; + if (getMapValues(params, cacheTiles::get, s2CellIds, mapValuesMeters)) { + return mapValuesMeters; } - TileFunction loadedTiles = loadFromCacheAndDisk(params, context, s2CellIds); - if (getGeoidHeights(params, loadedTiles, s2CellIds, heightsMeters)) { - return heightsMeters; + TileFunction loadedTiles = loadFromCacheAndDisk(params, context, s2CellIds, cacheTiles); + if (getMapValues(params, loadedTiles, s2CellIds, mapValuesMeters)) { + return mapValuesMeters; } throw new IOException("Unable to calculate geoid heights from raw assets."); } @@ -255,32 +312,33 @@ public final class GeoidHeightMap { public double[] readGeoidHeights(@NonNull MapParamsProto params, @NonNull long[] s2CellIds) { validate(params, s2CellIds); double[] heightsMeters = new double[s2CellIds.length]; - if (getGeoidHeights(params, mCacheTiles::get, s2CellIds, heightsMeters)) { + if (getMapValues(params, mGeoidHeightCacheTiles::get, s2CellIds, heightsMeters)) { return heightsMeters; } return null; } /** - * Adds to {@code heightsMeters} the geoid heights in meters associated with the map cells + * Adds to {@code mapValuesMeters} the map values in meters associated with the map cells * identified by {@code s2CellIds}. Returns true if heights are present for all IDs; otherwise, - * returns false and adds NaNs for absent heights. + * adds NaNs for absent heights and returns false. */ - private boolean getGeoidHeights(@NonNull MapParamsProto params, + private static boolean getMapValues(@NonNull MapParamsProto params, @NonNull TileFunction tileFunction, @NonNull long[] s2CellIds, - @NonNull double[] heightsMeters) { - boolean allFound = getUnitIntervalValues(params, tileFunction, s2CellIds, heightsMeters); - for (int i = 0; i < heightsMeters.length; i++) { + @NonNull double[] mapValuesMeters) { + boolean allFound = getUnitIntervalValues(params, tileFunction, s2CellIds, mapValuesMeters); + for (int i = 0; i < mapValuesMeters.length; i++) { // NaNs are properly preserved. - heightsMeters[i] *= params.modelAMeters; - heightsMeters[i] += params.modelBMeters; + mapValuesMeters[i] *= params.modelAMeters; + mapValuesMeters[i] += params.modelBMeters; } return allFound; } @NonNull - private TileFunction loadFromCacheAndDisk(@NonNull MapParamsProto params, - @NonNull Context context, @NonNull long[] s2CellIds) throws IOException { + private static TileFunction loadFromCacheAndDisk(@NonNull MapParamsProto params, + @NonNull Context context, @NonNull long[] s2CellIds, + @NonNull LruCache<Long, S2TileProto> cacheTiles) throws IOException { int len = s2CellIds.length; // Enable batch loading by finding all cache keys upfront. @@ -296,7 +354,7 @@ public final class GeoidHeightMap { if (diskTokens[i] != null) { continue; } - loadedTiles[i] = mCacheTiles.get(cacheKeys[i]); + loadedTiles[i] = cacheTiles.get(cacheKeys[i]); diskTokens[i] = getDiskToken(params, cacheKeys[i]); // Batch across common cache key. @@ -319,7 +377,7 @@ public final class GeoidHeightMap { "geoid_height_map/tile-" + diskTokens[i] + ".pb")) { tile = S2TileProto.parseFrom(is.readAllBytes()); } - mergeFromDiskTile(params, tile, cacheKeys, diskTokens, i, loadedTiles); + mergeFromDiskTile(params, tile, cacheKeys, diskTokens, i, loadedTiles, cacheTiles); } return cacheKey -> { @@ -332,9 +390,10 @@ public final class GeoidHeightMap { }; } - private void mergeFromDiskTile(@NonNull MapParamsProto params, @NonNull S2TileProto diskTile, - @NonNull long[] cacheKeys, @NonNull String[] diskTokens, int diskTokenIndex, - @NonNull S2TileProto[] loadedTiles) throws IOException { + private static void mergeFromDiskTile(@NonNull MapParamsProto params, + @NonNull S2TileProto diskTile, @NonNull long[] cacheKeys, @NonNull String[] diskTokens, + int diskTokenIndex, @NonNull S2TileProto[] loadedTiles, + @NonNull LruCache<Long, S2TileProto> cacheTiles) throws IOException { int len = cacheKeys.length; int numMapCellsPerCacheTile = 1 << (2 * (params.mapS2Level - params.cacheTileS2Level)); @@ -375,7 +434,7 @@ public final class GeoidHeightMap { } // Side load into tile cache. - mCacheTiles.put(cacheKeys[i], loadedTiles[i]); + cacheTiles.put(cacheKeys[i], loadedTiles[i]); } } diff --git a/nfc/jarjar-rules.txt b/nfc/jarjar-rules.txt index 4cd652d6af7f..99ae14414dad 100644 --- a/nfc/jarjar-rules.txt +++ b/nfc/jarjar-rules.txt @@ -4,6 +4,7 @@ rule android.content.ComponentNameProto* com.android.nfc.x.@0 rule android.content.IntentProto* com.android.nfc.x.@0 rule android.content.IntentFilterProto* com.android.nfc.x.@0 rule android.content.AuthorityEntryProto* com.android.nfc.x.@0 +rule android.content.UriRelativeFilter* com.android.nfc.x.@0 rule android.nfc.cardemulation.AidGroupProto* com.android.nfc.x.@0 rule android.nfc.cardemulation.ApduServiceInfoProto* com.android.nfc.x.@0 rule android.nfc.cardemulation.NfcFServiceInfoProto* com.android.nfc.x.@0 diff --git a/nfc/java/android/nfc/cardemulation/CardEmulation.java b/nfc/java/android/nfc/cardemulation/CardEmulation.java index 9d38e4c5b297..1f41b812164c 100644 --- a/nfc/java/android/nfc/cardemulation/CardEmulation.java +++ b/nfc/java/android/nfc/cardemulation/CardEmulation.java @@ -338,8 +338,10 @@ public final class CardEmulation { } } /** - * Sets whether the system should default to observe mode or not when - * the service is in the foreground or the default payment service. + * Sets whether the system should default to observe mode or not when the service is in the + * foreground or the default payment service. The default is to not enable observe mode when + * a service either the foreground default service or the default payment service so not + * calling this method will preserve that behavior. * * @param service The component name of the service * @param enable Whether the servic should default to observe mode or not diff --git a/packages/CrashRecovery/aconfig/flags.aconfig b/packages/CrashRecovery/aconfig/flags.aconfig new file mode 100644 index 000000000000..563626634068 --- /dev/null +++ b/packages/CrashRecovery/aconfig/flags.aconfig @@ -0,0 +1,9 @@ +package: "android.crashrecovery.flags" + +flag { + name: "recoverability_detection" + namespace: "package_watchdog" + description: "Feature flag for recoverability detection" + bug: "310236690" + is_fixed_read_only: true +}
\ No newline at end of file diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlow.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlow.kt index e099f1124bf1..0a424bc4230a 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlow.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlow.kt @@ -37,10 +37,11 @@ private const val TAG = "BroadcastReceiverFlow" fun Context.broadcastReceiverFlow(intentFilter: IntentFilter): Flow<Intent> = callbackFlow { val broadcastReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { + Log.d(TAG, "onReceive: $intent") trySend(intent) } } - registerReceiver(broadcastReceiver, intentFilter, Context.RECEIVER_NOT_EXPORTED) + registerReceiver(broadcastReceiver, intentFilter, Context.RECEIVER_VISIBLE_TO_INSTANT_APPS) awaitClose { unregisterReceiver(broadcastReceiver) } }.catch { e -> diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlowTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlowTest.kt index eef5225aef42..772f925c0a77 100644 --- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlowTest.kt +++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlowTest.kt @@ -43,7 +43,7 @@ class BroadcastReceiverFlowTest { private val context = mock<Context> { on { - registerReceiver(any(), eq(INTENT_FILTER), eq(Context.RECEIVER_NOT_EXPORTED)) + registerReceiver(any(), eq(INTENT_FILTER), eq(Context.RECEIVER_VISIBLE_TO_INSTANT_APPS)) } doAnswer { registeredBroadcastReceiver = it.arguments[0] as BroadcastReceiver null diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt new file mode 100644 index 000000000000..3355fb395ca0 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.volume.data.repository + +import android.media.AudioManager +import com.android.internal.util.ConcurrentUtils +import kotlin.coroutines.CoroutineContext +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.launch + +/** Provides audio managing functionality and data. */ +interface AudioRepository { + + /** Current [AudioManager.getMode]. */ + val mode: StateFlow<Int> +} + +class AudioRepositoryImpl( + private val audioManager: AudioManager, + backgroundCoroutineContext: CoroutineContext, + coroutineScope: CoroutineScope, +) : AudioRepository { + + override val mode: StateFlow<Int> = + callbackFlow { + val listener = + AudioManager.OnModeChangedListener { newMode -> launch { send(newMode) } } + audioManager.addOnModeChangedListener(ConcurrentUtils.DIRECT_EXECUTOR, listener) + awaitClose { audioManager.removeOnModeChangedListener(listener) } + } + .flowOn(backgroundCoroutineContext) + .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), audioManager.mode) +} diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioModeInteractor.kt b/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioModeInteractor.kt new file mode 100644 index 000000000000..053c59b15067 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioModeInteractor.kt @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.volume.domain.interactor + +import android.media.AudioManager +import com.android.settingslib.volume.data.repository.AudioRepository +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map + +class AudioModeInteractor(repository: AudioRepository) { + + private val ongoingCallModes = + setOf( + AudioManager.MODE_RINGTONE, + AudioManager.MODE_IN_CALL, + AudioManager.MODE_IN_COMMUNICATION, + ) + + /** Returns if current [AudioManager.getMode] call is an ongoing call */ + val isOngoingCall: Flow<Boolean> = repository.mode.map { it in ongoingCallModes } +} diff --git a/packages/SettingsLib/tests/integ/Android.bp b/packages/SettingsLib/tests/integ/Android.bp index 644b72c383ac..ce3a7baf6be6 100644 --- a/packages/SettingsLib/tests/integ/Android.bp +++ b/packages/SettingsLib/tests/integ/Android.bp @@ -57,6 +57,7 @@ android_test { "SettingsLibSettingsSpinner", "SettingsLibUsageProgressBarPreference", "settingslib_media_flags_lib", + "kotlinx_coroutines_test", ], dxflags: ["--multi-dex"], diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeAudioRepository.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeAudioRepository.kt new file mode 100644 index 000000000000..686362fadf02 --- /dev/null +++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeAudioRepository.kt @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.volume.data.repository + +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow + +class FakeAudioRepository : AudioRepository { + + private val mutableMode = MutableStateFlow(0) + override val mode: StateFlow<Int> + get() = mutableMode.asStateFlow() + + fun setMode(newMode: Int) { + mutableMode.value = newMode + } +} diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/domain/interactor/AudioModeInteractorTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/domain/interactor/AudioModeInteractorTest.kt new file mode 100644 index 000000000000..3bc1edc9c944 --- /dev/null +++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/domain/interactor/AudioModeInteractorTest.kt @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.volume.domain.interactor + +import android.media.AudioManager +import androidx.test.filters.SmallTest +import androidx.test.runner.AndroidJUnit4 +import com.android.settingslib.volume.data.repository.FakeAudioRepository +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@OptIn(ExperimentalCoroutinesApi::class) +@RunWith(AndroidJUnit4::class) +@SmallTest +class AudioModeInteractorTest { + + private val testScope = TestScope() + private val fakeAudioRepository = FakeAudioRepository() + + private val underTest = AudioModeInteractor(fakeAudioRepository) + + @Test + fun ongoingCallModes_isOnGoingCall() { + testScope.runTest { + for (mode in ongoingCallModes) { + var isOngoingCall = false + underTest.isOngoingCall.onEach { isOngoingCall = it }.launchIn(backgroundScope) + + fakeAudioRepository.setMode(mode) + runCurrent() + + assertThat(isOngoingCall).isTrue() + } + } + } + + @Test + fun notOngoingCallModes_isNotOnGoingCall() { + testScope.runTest { + var isOngoingCall = true + underTest.isOngoingCall.onEach { isOngoingCall = it }.launchIn(backgroundScope) + + fakeAudioRepository.setMode(AudioManager.MODE_CURRENT) + runCurrent() + + assertThat(isOngoingCall).isFalse() + } + } + + private companion object { + private val ongoingCallModes = + setOf( + AudioManager.MODE_RINGTONE, + AudioManager.MODE_IN_CALL, + AudioManager.MODE_IN_COMMUNICATION, + ) + } +} diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index d38454221f76..cdb4aea6ee79 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -561,7 +561,7 @@ <uses-permission android:name="android.permission.TEST_BIOMETRIC" /> <!-- Permission required for CTS test - android.server.biometrics --> - <uses-permission android:name="android.permission.MANAGE_BIOMETRIC_DIALOG" /> + <uses-permission android:name="android.permission.SET_BIOMETRIC_DIALOG_LOGO" /> <!-- Permission required for CTS test - android.server.biometrics --> <uses-permission android:name="android.permission.USE_BACKGROUND_FACE_AUTHENTICATION" /> @@ -902,6 +902,9 @@ <!-- Permission required for BinaryTransparencyService shell API and host test --> <uses-permission android:name="android.permission.GET_BACKGROUND_INSTALLED_PACKAGES" /> + <!-- Permissions required for CTS test - CtsPermissionUiTestCases --> + <uses-permission android:name="android.permission.MANAGE_ENHANCED_CONFIRMATION_STATES" /> + <application android:label="@string/app_label" android:theme="@android:style/Theme.DeviceDefault.DayNight" diff --git a/packages/SystemUI/aconfig/accessibility.aconfig b/packages/SystemUI/aconfig/accessibility.aconfig index 7ba889bc8fee..866aa8945525 100644 --- a/packages/SystemUI/aconfig/accessibility.aconfig +++ b/packages/SystemUI/aconfig/accessibility.aconfig @@ -17,6 +17,13 @@ flag { } flag { + name: "floating_menu_drag_to_edit" + namespace: "accessibility" + description: "adds a second drag button to allow the user edit the shortcut." + bug: "297583708" +} + +flag { name: "floating_menu_ime_displacement_animation" namespace: "accessibility" description: "Adds an animation for when the FAB is displaced by an IME becoming visible." diff --git a/packages/SystemUI/aconfig/biometrics_framework.aconfig b/packages/SystemUI/aconfig/biometrics_framework.aconfig index 5fd3b485e9ed..7cc0c83abfea 100644 --- a/packages/SystemUI/aconfig/biometrics_framework.aconfig +++ b/packages/SystemUI/aconfig/biometrics_framework.aconfig @@ -7,4 +7,11 @@ flag { namespace: "biometrics_framework" description: "Adds talkback directional guidance when using UDFPS with biometric prompt" bug: "310044658" +} + +flag { + name: "constraint_bp" + namespace: "biometrics_framework" + description: "Refactors Biometric Prompt to use a ConstraintLayout" + bug: "288175072" }
\ No newline at end of file diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt index 761e74e52237..576596fa67fe 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt @@ -48,7 +48,6 @@ import androidx.compose.foundation.lazy.grid.rememberLazyGridState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Add -import androidx.compose.material.icons.filled.Edit import androidx.compose.material.icons.outlined.Delete import androidx.compose.material.icons.outlined.Edit import androidx.compose.material.icons.outlined.TouchApp @@ -60,7 +59,6 @@ import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults import androidx.compose.material3.FilledIconButton import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton import androidx.compose.material3.IconButtonColors import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedButton @@ -130,12 +128,11 @@ fun CommunalHub( val gridState = rememberLazyGridState() val contentListState = rememberContentListState(widgetConfigurator, communalContent, viewModel) val reorderingWidgets by viewModel.reorderingWidgets.collectAsState() - val selectedIndex = viewModel.selectedIndex.collectAsState() + val selectedKey = viewModel.selectedKey.collectAsState() val removeButtonEnabled by remember { - derivedStateOf { selectedIndex.value != null || reorderingWidgets } + derivedStateOf { selectedKey.value != null || reorderingWidgets } } - val (isButtonToEditWidgetsShowing, setIsButtonToEditWidgetsShowing) = - remember { mutableStateOf(false) } + var isButtonToEditWidgetsShowing by remember { mutableStateOf(false) } val contentPadding = gridContentPadding(viewModel.isEditMode, toolbarSize) val contentOffset = beforeContentPadding(contentPadding).toOffset() @@ -150,22 +147,30 @@ fun CommunalHub( if (!viewModel.isEditMode) return@pointerInput observeTapsWithoutConsuming { offset -> val adjustedOffset = offset - contentOffset - val index = - gridState.layoutInfo.visibleItemsInfo - .firstItemAtOffset(adjustedOffset) - ?.index - val newIndex = - if (index?.let(contentListState::isItemEditable) == true) { - index - } else { - null - } - viewModel.setSelectedIndex(newIndex) + val index = firstIndexAtOffset(gridState, adjustedOffset) + val key = index?.let { keyAtIndexIfEditable(contentListState.list, index) } + viewModel.setSelectedKey(key) } } .thenIf(!viewModel.isEditMode) { - Modifier.pointerInput(Unit) { - detectLongPressGesture { offset -> setIsButtonToEditWidgetsShowing(true) } + Modifier.pointerInput( + gridState, + contentOffset, + communalContent, + gridCoordinates + ) { + detectLongPressGesture { offset -> + isButtonToEditWidgetsShowing = true + + // Deduct both grid offset relative to its container and content offset. + val adjustedOffset = + gridCoordinates?.let { + offset - it.positionInWindow() - contentOffset + } + val index = adjustedOffset?.let { firstIndexAtOffset(gridState, it) } + val key = index?.let { keyAtIndexIfEditable(communalContent, index) } + viewModel.setSelectedKey(key) + } } }, ) { @@ -186,7 +191,7 @@ fun CommunalHub( onOpenWidgetPicker = onOpenWidgetPicker, gridState = gridState, contentListState = contentListState, - selectedIndex = selectedIndex, + selectedKey = selectedKey, widgetConfigurator = widgetConfigurator, ) @@ -198,18 +203,18 @@ fun CommunalHub( onEditDone = onEditDone, onOpenWidgetPicker = onOpenWidgetPicker, onRemoveClicked = { - selectedIndex.value?.let { index -> - contentListState.onRemove(index) + val index = + selectedKey.value?.let { key -> + contentListState.list.indexOfFirst { it.key == key } + } + index?.let { + contentListState.onRemove(it) contentListState.onSaveList() - viewModel.setSelectedIndex(null) + viewModel.setSelectedKey(null) } }, removeEnabled = removeButtonEnabled ) - } else { - IconButton(onClick = viewModel::onOpenWidgetEditor) { - Icon(Icons.Default.Edit, stringResource(R.string.button_to_open_widget_editor)) - } } if (isPopupOnDismissCtaShowing) { @@ -219,10 +224,10 @@ fun CommunalHub( if (isButtonToEditWidgetsShowing) { ButtonToEditWidgets( onClick = { - setIsButtonToEditWidgetsShowing(false) - viewModel.onOpenWidgetEditor() + isButtonToEditWidgetsShowing = false + viewModel.onOpenWidgetEditor(selectedKey.value) }, - onHide = { setIsButtonToEditWidgetsShowing(false) }, + onHide = { isButtonToEditWidgetsShowing = false }, ) } @@ -244,7 +249,7 @@ private fun BoxScope.CommunalHubLazyGrid( communalContent: List<CommunalContentModel>, viewModel: BaseCommunalViewModel, contentPadding: PaddingValues, - selectedIndex: State<Int?>, + selectedKey: State<String?>, contentOffset: Offset, gridState: LazyGridState, contentListState: ContentListState, @@ -253,7 +258,8 @@ private fun BoxScope.CommunalHubLazyGrid( onOpenWidgetPicker: (() -> Unit)? = null, widgetConfigurator: WidgetConfigurator?, ) { - var gridModifier = Modifier.align(Alignment.CenterStart) + var gridModifier = + Modifier.align(Alignment.CenterStart).onGloballyPositioned { setGridCoordinates(it) } var list = communalContent var dragDropState: GridDragDropState? = null if (viewModel.isEditMode && viewModel is CommunalEditModeViewModel) { @@ -266,10 +272,7 @@ private fun BoxScope.CommunalHubLazyGrid( updateDragPositionForRemove = updateDragPositionForRemove ) gridModifier = - gridModifier - .fillMaxSize() - .dragContainer(dragDropState, contentOffset, viewModel) - .onGloballyPositioned { setGridCoordinates(it) } + gridModifier.fillMaxSize().dragContainer(dragDropState, contentOffset, viewModel) // for widgets dropped from other activities val dragAndDropTargetState = rememberDragAndDropTargetState( @@ -307,7 +310,8 @@ private fun BoxScope.CommunalHubLazyGrid( list[index].size.dp().value, ) if (viewModel.isEditMode && dragDropState != null) { - val selected by remember(index) { derivedStateOf { index == selectedIndex.value } } + val selected by + remember(index) { derivedStateOf { list[index].key == selectedKey.value } } DraggableItem( dragDropState = dragDropState, selected = selected, @@ -378,7 +382,7 @@ private fun Toolbar( colors = filledButtonColors(), contentPadding = Dimensions.ButtonPadding ) { - Icon(Icons.Default.Add, stringResource(R.string.button_to_open_widget_editor)) + Icon(Icons.Default.Add, stringResource(R.string.hub_mode_add_widget_button_text)) Spacer(spacerModifier) Text( text = stringResource(R.string.hub_mode_add_widget_button_text), @@ -832,6 +836,13 @@ private fun CommunalContentSize.dp(): Dp { } } +private fun firstIndexAtOffset(gridState: LazyGridState, offset: Offset): Int? = + gridState.layoutInfo.visibleItemsInfo.firstItemAtOffset(offset)?.index + +/** Returns the key of item if it's editable at the given index. Only widget is editable. */ +private fun keyAtIndexIfEditable(list: List<CommunalContentModel>, index: Int): String? = + if (index in list.indices && list[index].isWidget()) list[index].key else null + data class ContentPaddingInPx(val start: Float, val top: Float) { fun toOffset(): Offset = Offset(start, top) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt index a083e7cf22c7..b1224ffa702e 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt @@ -633,6 +633,14 @@ class CommunalInteractorTest : SysuiTestCase() { verify(editWidgetsActivityStarter).startActivity() } + @Test + fun showWidgetEditor_withPreselectedKey_startsActivity() = + testScope.runTest { + val widgetKey = CommunalContentModel.KEY.widget(123) + underTest.showWidgetEditor(preselectedKey = widgetKey) + verify(editWidgetsActivityStarter).startActivity(widgetKey) + } + private fun smartspaceTimer(id: String, timestamp: Long = 0L): SmartspaceTarget { val timer = mock(SmartspaceTarget::class.java) whenever(timer.smartspaceTargetId).thenReturn(id) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt index a2dec5ff8830..273d1cd55626 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt @@ -129,6 +129,19 @@ class CommunalEditModeViewModelTest : SysuiTestCase() { } @Test + fun selectedKey_onReorderWidgets_isCleared() = + testScope.runTest { + val selectedKey by collectLastValue(underTest.selectedKey) + + val key = CommunalContentModel.KEY.widget(123) + underTest.setSelectedKey(key) + assertThat(selectedKey).isEqualTo(key) + + underTest.onReorderWidgetStart() + assertThat(selectedKey).isNull() + } + + @Test fun reorderWidget_uiEventLogging_start() { underTest.onReorderWidgetStart() verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_REORDER_WIDGET_START) diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml index 3839dd98cdd3..307a6192a570 100644 --- a/packages/SystemUI/res/values/colors.xml +++ b/packages/SystemUI/res/values/colors.xml @@ -29,6 +29,9 @@ <color name="status_bar_icons_hover_color_light">#38FFFFFF</color> <!-- 22% white --> <color name="status_bar_icons_hover_color_dark">#38000000</color> <!-- 22% black --> + <!-- The dark background color behind the shade --> + <color name="shade_scrim_background_dark">@*android:color/black</color> + <!-- The color of the background in the separated list of the Global Actions menu --> <color name="global_actions_separated_background">#F5F5F5</color> diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml index 8ec5ccd7a080..2ab0813300e3 100644 --- a/packages/SystemUI/res/values/ids.xml +++ b/packages/SystemUI/res/values/ids.xml @@ -184,6 +184,7 @@ <item type="id" name="action_move_to_edge_and_hide"/> <item type="id" name="action_move_out_edge_and_show"/> <item type="id" name="action_remove_menu"/> + <item type="id" name="action_edit"/> <!-- rounded corner view id --> <item type="id" name="rounded_corner_top_left"/> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 2b43360f0689..8fa4a9e3932f 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -1077,8 +1077,6 @@ <!-- Indicator on keyguard to start the communal tutorial. [CHAR LIMIT=100] --> <string name="communal_tutorial_indicator_text">Swipe left to start the communal tutorial</string> - <!-- Description for the button that opens the widget editor on click. [CHAR LIMIT=50] --> - <string name="button_to_open_widget_editor">Open the widget editor</string> <!-- Text for CTA button that launches the hub mode widget editor on click. [CHAR LIMIT=50] --> <string name="cta_tile_button_to_open_widget_editor">Customize</string> <!-- Text for CTA button that dismisses the tile on click. [CHAR LIMIT=50] --> @@ -2560,6 +2558,8 @@ <string name="accessibility_floating_button_action_remove_menu">Remove</string> <!-- Action in accessibility menu to toggle on/off the accessibility feature. [CHAR LIMIT=30]--> <string name="accessibility_floating_button_action_double_tap_to_toggle">toggle</string> + <!-- Action in accessibility menu to open the shortcut edit menu" [CHAR LIMIT=30]--> + <string name="accessibility_floating_button_action_edit">Edit</string> <!-- Device Controls strings --> <!-- Device Controls, Quick Settings tile title [CHAR LIMIT=30] --> diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationController.java index 568b24dbd4f3..7fd72ec8ce93 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationController.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationController.java @@ -16,127 +16,138 @@ package com.android.systemui.accessibility.floatingmenu; +import static android.R.id.empty; + import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; +import android.util.ArrayMap; +import android.util.Pair; import android.view.MotionEvent; import androidx.annotation.NonNull; import androidx.dynamicanimation.animation.DynamicAnimation; import com.android.internal.annotations.VisibleForTesting; +import com.android.systemui.Flags; +import com.android.wm.shell.common.bubbles.DismissCircleView; import com.android.wm.shell.common.bubbles.DismissView; import com.android.wm.shell.common.magnetictarget.MagnetizedObject; +import java.util.Map; +import java.util.Objects; + /** * Controls the interaction between {@link MagnetizedObject} and * {@link MagnetizedObject.MagneticTarget}. */ class DragToInteractAnimationController { - private static final boolean ENABLE_FLING_TO_DISMISS_MENU = false; private static final float COMPLETELY_OPAQUE = 1.0f; private static final float COMPLETELY_TRANSPARENT = 0.0f; private static final float CIRCLE_VIEW_DEFAULT_SCALE = 1.0f; private static final float ANIMATING_MAX_ALPHA = 0.7f; + private final DragToInteractView mInteractView; private final DismissView mDismissView; private final MenuView mMenuView; - private final ValueAnimator mDismissAnimator; - private final MagnetizedObject<?> mMagnetizedObject; - private float mMinDismissSize; + + /** + * MagnetizedObject cannot differentiate between its MagnetizedTargets, + * so we need an object & an animator for every interactable. + */ + private final ArrayMap<Integer, Pair<MagnetizedObject<MenuView>, ValueAnimator>> mInteractMap; + + private float mMinInteractSize; private float mSizePercent; - DragToInteractAnimationController(DismissView dismissView, MenuView menuView) { - mDismissView = dismissView; - mDismissView.setPivotX(dismissView.getWidth() / 2.0f); - mDismissView.setPivotY(dismissView.getHeight() / 2.0f); + DragToInteractAnimationController(DragToInteractView interactView, MenuView menuView) { + mDismissView = null; + mInteractView = interactView; + mInteractView.setPivotX(interactView.getWidth() / 2.0f); + mInteractView.setPivotY(interactView.getHeight() / 2.0f); mMenuView = menuView; updateResources(); - mDismissAnimator = ValueAnimator.ofFloat(COMPLETELY_OPAQUE, COMPLETELY_TRANSPARENT); - mDismissAnimator.addUpdateListener(dismissAnimation -> { - final float animatedValue = (float) dismissAnimation.getAnimatedValue(); - final float scaleValue = Math.max(animatedValue, mSizePercent); - dismissView.getCircle().setScaleX(scaleValue); - dismissView.getCircle().setScaleY(scaleValue); - - menuView.setAlpha(Math.max(animatedValue, ANIMATING_MAX_ALPHA)); + mInteractMap = new ArrayMap<>(); + interactView.getInteractMap().forEach((viewId, pair) -> { + DismissCircleView circleView = pair.getFirst(); + createMagnetizedObjectAndAnimator(circleView); }); + } - mDismissAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(@NonNull Animator animation, boolean isReverse) { - super.onAnimationEnd(animation, isReverse); + DragToInteractAnimationController(DismissView dismissView, MenuView menuView) { + mDismissView = dismissView; + mInteractView = null; + mDismissView.setPivotX(dismissView.getWidth() / 2.0f); + mDismissView.setPivotY(dismissView.getHeight() / 2.0f); + mMenuView = menuView; - if (isReverse) { - mDismissView.getCircle().setScaleX(CIRCLE_VIEW_DEFAULT_SCALE); - mDismissView.getCircle().setScaleY(CIRCLE_VIEW_DEFAULT_SCALE); - mMenuView.setAlpha(COMPLETELY_OPAQUE); - } - } - }); + updateResources(); - mMagnetizedObject = - new MagnetizedObject<MenuView>(mMenuView.getContext(), mMenuView, - new MenuAnimationController.MenuPositionProperty( - DynamicAnimation.TRANSLATION_X), - new MenuAnimationController.MenuPositionProperty( - DynamicAnimation.TRANSLATION_Y)) { - @Override - public void getLocationOnScreen(MenuView underlyingObject, int[] loc) { - underlyingObject.getLocationOnScreen(loc); - } - - @Override - public float getHeight(MenuView underlyingObject) { - return underlyingObject.getHeight(); - } - - @Override - public float getWidth(MenuView underlyingObject) { - return underlyingObject.getWidth(); - } - }; - - final MagnetizedObject.MagneticTarget magneticTarget = new MagnetizedObject.MagneticTarget( - dismissView.getCircle(), (int) mMinDismissSize); - mMagnetizedObject.addTarget(magneticTarget); - mMagnetizedObject.setFlingToTargetEnabled(ENABLE_FLING_TO_DISMISS_MENU); + mInteractMap = new ArrayMap<>(); + createMagnetizedObjectAndAnimator(dismissView.getCircle()); } - void showDismissView(boolean show) { - if (show) { - mDismissView.show(); - } else { - mDismissView.hide(); + void showInteractView(boolean show) { + if (Flags.floatingMenuDragToEdit() && mInteractView != null) { + if (show) { + mInteractView.show(); + } else { + mInteractView.hide(); + } + } else if (mDismissView != null) { + if (show) { + mDismissView.show(); + } else { + mDismissView.hide(); + } } } void setMagnetListener(MagnetizedObject.MagnetListener magnetListener) { - mMagnetizedObject.setMagnetListener(magnetListener); + mInteractMap.forEach((viewId, pair) -> { + MagnetizedObject<?> magnetizedObject = pair.first; + magnetizedObject.setMagnetListener(magnetListener); + }); } @VisibleForTesting - MagnetizedObject.MagnetListener getMagnetListener() { - return mMagnetizedObject.getMagnetListener(); + MagnetizedObject.MagnetListener getMagnetListener(int id) { + return Objects.requireNonNull(mInteractMap.get(id)).first.getMagnetListener(); } void maybeConsumeDownMotionEvent(MotionEvent event) { - mMagnetizedObject.maybeConsumeMotionEvent(event); + mInteractMap.forEach((viewId, pair) -> { + MagnetizedObject<?> magnetizedObject = pair.first; + magnetizedObject.maybeConsumeMotionEvent(event); + }); + } + + private int maybeConsumeMotionEvent(MotionEvent event) { + for (Map.Entry<Integer, Pair<MagnetizedObject<MenuView>, ValueAnimator>> set: + mInteractMap.entrySet()) { + MagnetizedObject<MenuView> magnetizedObject = set.getValue().first; + if (magnetizedObject.maybeConsumeMotionEvent(event)) { + return set.getKey(); + } + } + return empty; } /** - * This used to pass {@link MotionEvent#ACTION_DOWN} to the magnetized object to check if it was - * within the magnetic field. It should be used in the {@link MenuListViewTouchHandler}. + * This used to pass {@link MotionEvent#ACTION_DOWN} to the magnetized objects + * to check if it was within a magnetic field. + * It should be used in the {@link MenuListViewTouchHandler}. * * @param event that move the magnetized object which is also the menu list view. - * @return true if the location of the motion events moves within the magnetic field of a - * target, but false if didn't set + * @return id of a target if the location of the motion events moves + * within the field of the target, otherwise it returns{@link android.R.id#empty}. + * <p> * {@link DragToInteractAnimationController#setMagnetListener(MagnetizedObject.MagnetListener)}. */ - boolean maybeConsumeMoveMotionEvent(MotionEvent event) { - return mMagnetizedObject.maybeConsumeMotionEvent(event); + int maybeConsumeMoveMotionEvent(MotionEvent event) { + return maybeConsumeMotionEvent(event); } /** @@ -144,31 +155,93 @@ class DragToInteractAnimationController { * within the magnetic field. It should be used in the {@link MenuListViewTouchHandler}. * * @param event that move the magnetized object which is also the menu list view. - * @return true if the location of the motion events moves within the magnetic field of a - * target, but false if didn't set + * @return id of a target if the location of the motion events moves + * within the field of the target, otherwise it returns{@link android.R.id#empty}. * {@link DragToInteractAnimationController#setMagnetListener(MagnetizedObject.MagnetListener)}. */ - boolean maybeConsumeUpMotionEvent(MotionEvent event) { - return mMagnetizedObject.maybeConsumeMotionEvent(event); + int maybeConsumeUpMotionEvent(MotionEvent event) { + return maybeConsumeMotionEvent(event); } - void animateDismissMenu(boolean scaleUp) { + void animateInteractMenu(int targetViewId, boolean scaleUp) { + Pair<MagnetizedObject<MenuView>, ValueAnimator> value = mInteractMap.get(targetViewId); + if (value == null) { + return; + } + ValueAnimator animator = value.second; if (scaleUp) { - mDismissAnimator.start(); + animator.start(); } else { - mDismissAnimator.reverse(); + animator.reverse(); } } void updateResources() { - final float maxDismissSize = mDismissView.getResources().getDimensionPixelSize( + final float maxInteractSize = mMenuView.getResources().getDimensionPixelSize( com.android.wm.shell.R.dimen.dismiss_circle_size); - mMinDismissSize = mDismissView.getResources().getDimensionPixelSize( + mMinInteractSize = mMenuView.getResources().getDimensionPixelSize( com.android.wm.shell.R.dimen.dismiss_circle_small); - mSizePercent = mMinDismissSize / maxDismissSize; + mSizePercent = mMinInteractSize / maxInteractSize; } - interface DismissCallback { - void onDismiss(); + /** + * Creates a magnetizedObject & valueAnimator pair for the provided circleView, + * and adds them to the interactMap. + * + * @param circleView circleView to create objects for. + */ + private void createMagnetizedObjectAndAnimator(DismissCircleView circleView) { + MagnetizedObject<MenuView> magnetizedObject = new MagnetizedObject<MenuView>( + mMenuView.getContext(), mMenuView, + new MenuAnimationController.MenuPositionProperty( + DynamicAnimation.TRANSLATION_X), + new MenuAnimationController.MenuPositionProperty( + DynamicAnimation.TRANSLATION_Y)) { + @Override + public void getLocationOnScreen(MenuView underlyingObject, @NonNull int[] loc) { + underlyingObject.getLocationOnScreen(loc); + } + + @Override + public float getHeight(MenuView underlyingObject) { + return underlyingObject.getHeight(); + } + + @Override + public float getWidth(MenuView underlyingObject) { + return underlyingObject.getWidth(); + } + }; + // Avoid unintended selection of an object / option + magnetizedObject.setFlingToTargetEnabled(false); + magnetizedObject.addTarget(new MagnetizedObject.MagneticTarget( + circleView, (int) mMinInteractSize)); + + final ValueAnimator animator = + ValueAnimator.ofFloat(COMPLETELY_OPAQUE, COMPLETELY_TRANSPARENT); + + animator.addUpdateListener(dismissAnimation -> { + final float animatedValue = (float) dismissAnimation.getAnimatedValue(); + final float scaleValue = Math.max(animatedValue, mSizePercent); + circleView.setScaleX(scaleValue); + circleView.setScaleY(scaleValue); + + mMenuView.setAlpha(Math.max(animatedValue, ANIMATING_MAX_ALPHA)); + }); + + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(@NonNull Animator animation, boolean isReverse) { + super.onAnimationEnd(animation, isReverse); + + if (isReverse) { + circleView.setScaleX(CIRCLE_VIEW_DEFAULT_SCALE); + circleView.setScaleY(CIRCLE_VIEW_DEFAULT_SCALE); + mMenuView.setAlpha(COMPLETELY_OPAQUE); + } + } + }); + + mInteractMap.put(circleView.getId(), new Pair<>(magnetizedObject, animator)); } } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractView.kt b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractView.kt new file mode 100644 index 000000000000..0ef3d200d1fa --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractView.kt @@ -0,0 +1,322 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.accessibility.floatingmenu + +import android.animation.ObjectAnimator +import android.content.Context +import android.graphics.Color +import android.graphics.drawable.GradientDrawable +import android.util.ArrayMap +import android.util.IntProperty +import android.util.Log +import android.view.Gravity +import android.view.View +import android.view.ViewGroup +import android.view.WindowInsets +import android.view.WindowManager +import android.widget.FrameLayout +import android.widget.LinearLayout +import android.widget.Space +import androidx.annotation.ColorRes +import androidx.annotation.DimenRes +import androidx.annotation.DrawableRes +import androidx.core.content.ContextCompat +import androidx.dynamicanimation.animation.DynamicAnimation +import androidx.dynamicanimation.animation.SpringForce.DAMPING_RATIO_LOW_BOUNCY +import androidx.dynamicanimation.animation.SpringForce.STIFFNESS_LOW +import com.android.wm.shell.R +import com.android.wm.shell.animation.PhysicsAnimator +import com.android.wm.shell.common.bubbles.DismissCircleView +import com.android.wm.shell.common.bubbles.DismissView + +/** + * View that handles interactions between DismissCircleView and BubbleStackView. + * + * @note [setup] method should be called after initialisation + */ +class DragToInteractView(context: Context) : FrameLayout(context) { + /** + * The configuration is used to provide module specific resource ids + * + * @see [setup] method + */ + data class Config( + /** dimen resource id of the dismiss target circle view size */ + @DimenRes val targetSizeResId: Int, + /** dimen resource id of the icon size in the dismiss target */ + @DimenRes val iconSizeResId: Int, + /** dimen resource id of the bottom margin for the dismiss target */ + @DimenRes var bottomMarginResId: Int, + /** dimen resource id of the height for dismiss area gradient */ + @DimenRes val floatingGradientHeightResId: Int, + /** color resource id of the dismiss area gradient color */ + @ColorRes val floatingGradientColorResId: Int, + /** drawable resource id of the dismiss target background */ + @DrawableRes val backgroundResId: Int, + /** drawable resource id of the icon for the dismiss target */ + @DrawableRes val iconResId: Int + ) + + companion object { + private const val SHOULD_SETUP = "The view isn't ready. Should be called after `setup`" + private val TAG = DragToInteractView::class.simpleName + } + + // START DragToInteractView modification + // We could technically access each DismissCircleView from their Animator, + // but the animators only store a weak reference to their targets. This is safer. + var interactMap = ArrayMap<Int, Pair<DismissCircleView, PhysicsAnimator<DismissCircleView>>>() + // END DragToInteractView modification + var isShowing = false + var config: Config? = null + + private val spring = PhysicsAnimator.SpringConfig(STIFFNESS_LOW, DAMPING_RATIO_LOW_BOUNCY) + private val INTERACT_SCRIM_FADE_MS = 200L + private var wm: WindowManager = + context.getSystemService(Context.WINDOW_SERVICE) as WindowManager + private var gradientDrawable: GradientDrawable? = null + + private val GRADIENT_ALPHA: IntProperty<GradientDrawable> = + object : IntProperty<GradientDrawable>("alpha") { + override fun setValue(d: GradientDrawable, percent: Int) { + d.alpha = percent + } + override fun get(d: GradientDrawable): Int { + return d.alpha + } + } + + init { + clipToPadding = false + clipChildren = false + visibility = View.INVISIBLE + + // START DragToInteractView modification + // Resources included within implementation as we aren't concerned with decoupling them. + setup( + Config( + targetSizeResId = R.dimen.dismiss_circle_size, + iconSizeResId = R.dimen.dismiss_target_x_size, + bottomMarginResId = R.dimen.floating_dismiss_bottom_margin, + floatingGradientHeightResId = R.dimen.floating_dismiss_gradient_height, + floatingGradientColorResId = android.R.color.system_neutral1_900, + backgroundResId = R.drawable.dismiss_circle_background, + iconResId = R.drawable.pip_ic_close_white + ) + ) + // END DragToInteractView modification + } + + /** + * Sets up view with the provided resource ids. + * + * Decouples resource dependency in order to be used externally (e.g. Launcher). Usually called + * with default params in module specific extension: + * + * @see [DismissView.setup] in DismissViewExt.kt + */ + fun setup(config: Config) { + this.config = config + + // Setup layout + layoutParams = + LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + resources.getDimensionPixelSize(config.floatingGradientHeightResId), + Gravity.BOTTOM + ) + updatePadding() + + // Setup gradient + gradientDrawable = createGradient(color = config.floatingGradientColorResId) + background = gradientDrawable + + // START DragToInteractView modification + + // Setup LinearLayout. Added to organize multiple circles. + val linearLayout = LinearLayout(context) + linearLayout.layoutParams = + LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT + ) + linearLayout.weightSum = 0f + addView(linearLayout) + + // Setup DismissCircleView. Code block replaced with repeatable functions + addSpace(linearLayout) + addCircle( + config, + com.android.systemui.res.R.id.action_remove_menu, + R.drawable.pip_ic_close_white, + linearLayout + ) + addCircle( + config, + com.android.systemui.res.R.id.action_edit, + com.android.systemui.res.R.drawable.ic_screenshot_edit, + linearLayout + ) + // END DragToInteractView modification + } + + /** Animates this view in. */ + fun show() { + if (isShowing) return + val gradientDrawable = checkExists(gradientDrawable) ?: return + isShowing = true + visibility = View.VISIBLE + val alphaAnim = + ObjectAnimator.ofInt(gradientDrawable, GRADIENT_ALPHA, gradientDrawable.alpha, 255) + alphaAnim.duration = INTERACT_SCRIM_FADE_MS + alphaAnim.start() + + // START DragToInteractView modification + interactMap.forEach { + val animator = it.value.second + animator.cancel() + animator.spring(DynamicAnimation.TRANSLATION_Y, 0f, spring).start() + } + // END DragToInteractView modification + } + + /** + * Animates this view out, as well as the circle that encircles the bubbles, if they were + * dragged into the target and encircled. + */ + fun hide() { + if (!isShowing) return + val gradientDrawable = checkExists(gradientDrawable) ?: return + isShowing = false + val alphaAnim = + ObjectAnimator.ofInt(gradientDrawable, GRADIENT_ALPHA, gradientDrawable.alpha, 0) + alphaAnim.duration = INTERACT_SCRIM_FADE_MS + alphaAnim.start() + + // START DragToInteractView modification + interactMap.forEach { + val animator = it.value.second + animator + .spring(DynamicAnimation.TRANSLATION_Y, height.toFloat(), spring) + .withEndActions({ visibility = View.INVISIBLE }) + .start() + } + // END DragToInteractView modification + } + + /** Cancels the animator for the dismiss target. */ + fun cancelAnimators() { + // START DragToInteractView modification + interactMap.forEach { + val animator = it.value.second + animator.cancel() + } + // END DragToInteractView modification + } + + fun updateResources() { + val config = checkExists(config) ?: return + updatePadding() + layoutParams.height = resources.getDimensionPixelSize(config.floatingGradientHeightResId) + val targetSize = resources.getDimensionPixelSize(config.targetSizeResId) + + // START DragToInteractView modification + interactMap.forEach { + val circle = it.value.first + circle.layoutParams.width = targetSize + circle.layoutParams.height = targetSize + circle.requestLayout() + } + // END DragToInteractView modification + } + + private fun createGradient(@ColorRes color: Int): GradientDrawable { + val gradientColor = ContextCompat.getColor(context, color) + val alpha = 0.7f * 255 + val gradientColorWithAlpha = + Color.argb( + alpha.toInt(), + Color.red(gradientColor), + Color.green(gradientColor), + Color.blue(gradientColor) + ) + val gd = + GradientDrawable( + GradientDrawable.Orientation.BOTTOM_TOP, + intArrayOf(gradientColorWithAlpha, Color.TRANSPARENT) + ) + gd.setDither(true) + gd.alpha = 0 + return gd + } + + private fun updatePadding() { + val config = checkExists(config) ?: return + val insets: WindowInsets = wm.currentWindowMetrics.windowInsets + val navInset = insets.getInsetsIgnoringVisibility(WindowInsets.Type.navigationBars()) + setPadding( + 0, + 0, + 0, + navInset.bottom + resources.getDimensionPixelSize(config.bottomMarginResId) + ) + } + + /** + * Checks if the value is set up and exists, if not logs an exception. Used for convenient + * logging in case `setup` wasn't called before + * + * @return value provided as argument + */ + private fun <T> checkExists(value: T?): T? { + if (value == null) Log.e(TAG, SHOULD_SETUP) + return value + } + + // START DragToInteractView modification + private fun addSpace(parent: LinearLayout) { + val space = Space(context) + // Spaces are weighted equally to space out circles evenly + space.layoutParams = + LinearLayout.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT, + 1f + ) + parent.addView(space) + parent.weightSum = parent.weightSum + 1f + } + + private fun addCircle(config: Config, id: Int, iconResId: Int, parent: LinearLayout) { + val targetSize = resources.getDimensionPixelSize(config.targetSizeResId) + val circleLayoutParams = LinearLayout.LayoutParams(targetSize, targetSize, 0f) + circleLayoutParams.gravity = Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL + val circle = DismissCircleView(context) + circle.id = id + circle.setup(config.backgroundResId, iconResId, config.iconSizeResId) + circle.layoutParams = circleLayoutParams + + // Initial position with circle offscreen so it's animated up + circle.translationY = + resources.getDimensionPixelSize(config.floatingGradientHeightResId).toFloat() + + interactMap[circle.id] = Pair(circle, PhysicsAnimator.getInstance(circle)) + parent.addView(circle) + addSpace(parent) + } + // END DragToInteractView modification +} diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java index a2705584d76a..d3e85e092b3a 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java @@ -37,7 +37,6 @@ import androidx.dynamicanimation.animation.SpringForce; import androidx.recyclerview.widget.RecyclerView; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.util.Preconditions; import com.android.systemui.Flags; import java.util.HashMap; @@ -73,7 +72,6 @@ class MenuAnimationController { private final ValueAnimator mFadeOutAnimator; private final Handler mHandler; private boolean mIsFadeEffectEnabled; - private DragToInteractAnimationController.DismissCallback mDismissCallback; private Runnable mSpringAnimationsEndAction; // Cache the animations state of {@link DynamicAnimation.TRANSLATION_X} and {@link @@ -170,11 +168,6 @@ class MenuAnimationController { mSpringAnimationsEndAction = runnable; } - void setDismissCallback( - DragToInteractAnimationController.DismissCallback dismissCallback) { - mDismissCallback = dismissCallback; - } - void moveToTopLeftPosition() { mMenuView.updateMenuMoveToTucked(/* isMoveToTucked= */ false); final Rect draggableBounds = mMenuView.getMenuDraggableBounds(); @@ -205,13 +198,6 @@ class MenuAnimationController { constrainPositionAndUpdate(position, /* writeToPosition = */ true); } - void removeMenu() { - Preconditions.checkArgument(mDismissCallback != null, - "The dismiss callback should be initialized first."); - - mDismissCallback.onDismiss(); - } - void flingMenuThenSpringToEdge(float x, float velocityX, float velocityY) { final boolean shouldMenuFlingLeft = isOnLeftSide() ? velocityX < ESCAPE_VELOCITY @@ -334,8 +320,6 @@ class MenuAnimationController { moveToEdgeAndHide(); return true; } - - fadeOutIfEnabled(); return false; } @@ -453,8 +437,6 @@ class MenuAnimationController { mMenuView.onBoundsInParentChanged((int) position.x, (int) position.y); constrainPositionAndUpdate(position, writeToPosition); - fadeOutIfEnabled(); - if (mSpringAnimationsEndAction != null) { mSpringAnimationsEndAction.run(); } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegate.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegate.java index 9c22a7738ad6..975a6020430d 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegate.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegate.java @@ -27,6 +27,7 @@ import androidx.annotation.NonNull; import androidx.core.view.accessibility.AccessibilityNodeInfoCompat; import androidx.recyclerview.widget.RecyclerViewAccessibilityDelegate; +import com.android.systemui.Flags; import com.android.systemui.res.R; /** @@ -35,15 +36,18 @@ import com.android.systemui.res.R; */ class MenuItemAccessibilityDelegate extends RecyclerViewAccessibilityDelegate.ItemDelegate { private final MenuAnimationController mAnimationController; + private final MenuViewLayer mMenuViewLayer; MenuItemAccessibilityDelegate(@NonNull RecyclerViewAccessibilityDelegate recyclerViewDelegate, - MenuAnimationController animationController) { + MenuAnimationController animationController, MenuViewLayer menuViewLayer) { super(recyclerViewDelegate); mAnimationController = animationController; + mMenuViewLayer = menuViewLayer; } @Override - public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) { + public void onInitializeAccessibilityNodeInfo( + @NonNull View host, @NonNull AccessibilityNodeInfoCompat info) { super.onInitializeAccessibilityNodeInfo(host, info); final Resources res = host.getResources(); @@ -90,6 +94,15 @@ class MenuItemAccessibilityDelegate extends RecyclerViewAccessibilityDelegate.It R.id.action_remove_menu, res.getString(R.string.accessibility_floating_button_action_remove_menu)); info.addAction(removeMenu); + + if (Flags.floatingMenuDragToEdit()) { + final AccessibilityNodeInfoCompat.AccessibilityActionCompat edit = + new AccessibilityNodeInfoCompat.AccessibilityActionCompat( + R.id.action_edit, + res.getString( + R.string.accessibility_floating_button_action_remove_menu)); + info.addAction(edit); + } } @Override @@ -132,8 +145,8 @@ class MenuItemAccessibilityDelegate extends RecyclerViewAccessibilityDelegate.It return true; } - if (action == R.id.action_remove_menu) { - mAnimationController.removeMenu(); + if (action == R.id.action_remove_menu || action == R.id.action_edit) { + mMenuViewLayer.dispatchAccessibilityAction(action); return true; } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java index 52e7b91d373e..75191685b119 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java @@ -16,6 +16,8 @@ package com.android.systemui.accessibility.floatingmenu; +import static android.R.id.empty; + import android.graphics.PointF; import android.view.MotionEvent; import android.view.VelocityTracker; @@ -78,10 +80,9 @@ class MenuListViewTouchHandler implements RecyclerView.OnItemTouchListener { mMenuAnimationController.onDraggingStart(); } - mDragToInteractAnimationController.showDismissView(/* show= */ true); - - if (!mDragToInteractAnimationController.maybeConsumeMoveMotionEvent( - motionEvent)) { + mDragToInteractAnimationController.showInteractView(/* show= */ true); + if (mDragToInteractAnimationController.maybeConsumeMoveMotionEvent(motionEvent) + == empty) { mMenuAnimationController.moveToPositionX(mMenuTranslationDown.x + dx); mMenuAnimationController.moveToPositionYIfNeeded( mMenuTranslationDown.y + dy); @@ -94,21 +95,19 @@ class MenuListViewTouchHandler implements RecyclerView.OnItemTouchListener { final float endX = mMenuTranslationDown.x + dx; mIsDragging = false; + mDragToInteractAnimationController.showInteractView(/* show= */ false); + if (mMenuAnimationController.maybeMoveToEdgeAndHide(endX)) { - mDragToInteractAnimationController.showDismissView(/* show= */ false); mMenuAnimationController.fadeOutIfEnabled(); - return true; } - if (!mDragToInteractAnimationController.maybeConsumeUpMotionEvent( - motionEvent)) { + if (mDragToInteractAnimationController.maybeConsumeUpMotionEvent(motionEvent) + == empty) { mVelocityTracker.computeCurrentVelocity(VELOCITY_UNIT_SECONDS); mMenuAnimationController.flingMenuThenSpringToEdge(endX, mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity()); - mDragToInteractAnimationController.showDismissView(/* show= */ false); } - // Avoid triggering the listener of the item. return true; } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java index 76808cb7ab7b..334cc87144f9 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java @@ -21,24 +21,28 @@ import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; import android.annotation.SuppressLint; import android.content.ComponentCallbacks; import android.content.Context; +import android.content.Intent; import android.content.res.Configuration; import android.graphics.PointF; import android.graphics.Rect; import android.graphics.drawable.GradientDrawable; +import android.os.Bundle; +import android.os.UserHandle; +import android.provider.Settings; +import android.provider.SettingsStringUtil; import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.widget.FrameLayout; import androidx.annotation.NonNull; -import androidx.core.view.AccessibilityDelegateCompat; import androidx.lifecycle.Observer; import androidx.recyclerview.widget.DiffUtil; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; -import androidx.recyclerview.widget.RecyclerViewAccessibilityDelegate; import com.android.internal.accessibility.dialog.AccessibilityTarget; import com.android.systemui.Flags; +import com.android.systemui.util.settings.SecureSettings; import java.util.ArrayList; import java.util.Collections; @@ -72,26 +76,20 @@ class MenuView extends FrameLayout implements private final MenuAnimationController mMenuAnimationController; private OnTargetFeaturesChangeListener mFeaturesChangeListener; private OnMoveToTuckedListener mMoveToTuckedListener; + private SecureSettings mSecureSettings; - MenuView(Context context, MenuViewModel menuViewModel, MenuViewAppearance menuViewAppearance) { + MenuView(Context context, MenuViewModel menuViewModel, MenuViewAppearance menuViewAppearance, + SecureSettings secureSettings) { super(context); mMenuViewModel = menuViewModel; mMenuViewAppearance = menuViewAppearance; + mSecureSettings = secureSettings; mMenuAnimationController = new MenuAnimationController(this, menuViewAppearance); mAdapter = new AccessibilityTargetAdapter(mTargetFeatures); mTargetFeaturesView = new RecyclerView(context); mTargetFeaturesView.setAdapter(mAdapter); mTargetFeaturesView.setLayoutManager(new LinearLayoutManager(context)); - mTargetFeaturesView.setAccessibilityDelegateCompat( - new RecyclerViewAccessibilityDelegate(mTargetFeaturesView) { - @NonNull - @Override - public AccessibilityDelegateCompat getItemDelegate() { - return new MenuItemAccessibilityDelegate(/* recyclerViewDelegate= */ this, - mMenuAnimationController); - } - }); setLayoutParams(new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT)); // Avoid drawing out of bounds of the parent view setClipToOutline(true); @@ -278,6 +276,7 @@ class MenuView extends FrameLayout implements if (mFeaturesChangeListener != null) { mFeaturesChangeListener.onChange(newTargetFeatures); } + mMenuAnimationController.fadeOutIfEnabled(); } @@ -306,6 +305,10 @@ class MenuView extends FrameLayout implements return mMenuViewAppearance.getMenuPosition(); } + RecyclerView getTargetFeaturesView() { + return mTargetFeaturesView; + } + void persistPositionAndUpdateEdge(Position percentagePosition) { mMenuViewModel.updateMenuSavingPosition(percentagePosition); mMenuViewAppearance.setPercentagePosition(percentagePosition); @@ -424,6 +427,35 @@ class MenuView extends FrameLayout implements onPositionChanged(); } + void gotoEditScreen() { + if (!Flags.floatingMenuDragToEdit()) { + return; + } + mMenuAnimationController.flingMenuThenSpringToEdge( + getMenuPosition().x, 100f, 0f); + mContext.startActivity(getIntentForEditScreen()); + } + + Intent getIntentForEditScreen() { + List<String> targets = new SettingsStringUtil.ColonDelimitedSet.OfStrings( + mSecureSettings.getStringForUser( + Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, + UserHandle.USER_CURRENT)).stream().toList(); + + Intent intent = new Intent( + Settings.ACTION_ACCESSIBILITY_SHORTCUT_SETTINGS); + Bundle args = new Bundle(); + Bundle fragmentArgs = new Bundle(); + fragmentArgs.putStringArray("targets", targets.toArray(new String[0])); + args.putBundle(":settings:show_fragment_args", fragmentArgs); + // TODO: b/318748373 - The fragment should set its own title using the targets + args.putString( + ":settings:show_fragment_title", "Accessibility Shortcut"); + intent.replaceExtras(args); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); + return intent; + } + private InstantInsetLayerDrawable getContainerViewInsetLayer() { return (InstantInsetLayerDrawable) getBackground(); } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java index 97999cc19dc8..bb5364d798da 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java @@ -59,7 +59,10 @@ import android.widget.FrameLayout; import android.widget.TextView; import androidx.annotation.NonNull; +import androidx.core.view.AccessibilityDelegateCompat; import androidx.lifecycle.Observer; +import androidx.recyclerview.widget.RecyclerView; +import androidx.recyclerview.widget.RecyclerViewAccessibilityDelegate; import com.android.internal.accessibility.dialog.AccessibilityTarget; import com.android.internal.annotations.VisibleForTesting; @@ -94,6 +97,8 @@ class MenuViewLayer extends FrameLayout implements private final MenuListViewTouchHandler mMenuListViewTouchHandler; private final MenuMessageView mMessageView; private final DismissView mDismissView; + private final DragToInteractView mDragToInteractView; + private final MenuViewAppearance mMenuViewAppearance; private final MenuAnimationController mMenuAnimationController; private final AccessibilityManager mAccessibilityManager; @@ -178,7 +183,10 @@ class MenuViewLayer extends FrameLayout implements }; MenuViewLayer(@NonNull Context context, WindowManager windowManager, - AccessibilityManager accessibilityManager, IAccessibilityFloatingMenu floatingMenu, + AccessibilityManager accessibilityManager, + MenuViewModel menuViewModel, + MenuViewAppearance menuViewAppearance, MenuView menuView, + IAccessibilityFloatingMenu floatingMenu, SecureSettings secureSettings) { super(context); @@ -190,43 +198,52 @@ class MenuViewLayer extends FrameLayout implements mFloatingMenu = floatingMenu; mSecureSettings = secureSettings; - mMenuViewModel = new MenuViewModel(context, accessibilityManager, secureSettings); - mMenuViewAppearance = new MenuViewAppearance(context, windowManager); - mMenuView = new MenuView(context, mMenuViewModel, mMenuViewAppearance); + mMenuViewModel = menuViewModel; + mMenuViewAppearance = menuViewAppearance; + mMenuView = menuView; + RecyclerView targetFeaturesView = mMenuView.getTargetFeaturesView(); + targetFeaturesView.setAccessibilityDelegateCompat( + new RecyclerViewAccessibilityDelegate(targetFeaturesView) { + @NonNull + @Override + public AccessibilityDelegateCompat getItemDelegate() { + return new MenuItemAccessibilityDelegate(/* recyclerViewDelegate= */ this, + mMenuAnimationController, MenuViewLayer.this); + } + }); mMenuAnimationController = mMenuView.getMenuAnimationController(); - if (Flags.floatingMenuDragToHide()) { - mMenuAnimationController.setDismissCallback(this::hideMenuAndShowNotification); - } else { - mMenuAnimationController.setDismissCallback(this::hideMenuAndShowMessage); - } mMenuAnimationController.setSpringAnimationsEndAction(this::onSpringAnimationsEndAction); mDismissView = new DismissView(context); + mDragToInteractView = new DragToInteractView(context); DismissViewUtils.setup(mDismissView); + mDismissView.getCircle().setId(R.id.action_remove_menu); mNotificationFactory = new MenuNotificationFactory(context); mNotificationManager = context.getSystemService(NotificationManager.class); - mDragToInteractAnimationController = new DragToInteractAnimationController( - mDismissView, mMenuView); + + if (Flags.floatingMenuDragToEdit()) { + mDragToInteractAnimationController = new DragToInteractAnimationController( + mDragToInteractView, mMenuView); + } else { + mDragToInteractAnimationController = new DragToInteractAnimationController( + mDismissView, mMenuView); + } mDragToInteractAnimationController.setMagnetListener(new MagnetizedObject.MagnetListener() { @Override public void onStuckToTarget(@NonNull MagnetizedObject.MagneticTarget target) { - mDragToInteractAnimationController.animateDismissMenu(/* scaleUp= */ true); + mDragToInteractAnimationController.animateInteractMenu( + target.getTargetView().getId(), /* scaleUp= */ true); } @Override public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target, float velocityX, float velocityY, boolean wasFlungOut) { - mDragToInteractAnimationController.animateDismissMenu(/* scaleUp= */ false); + mDragToInteractAnimationController.animateInteractMenu( + target.getTargetView().getId(), /* scaleUp= */ false); } @Override public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) { - if (Flags.floatingMenuDragToHide()) { - hideMenuAndShowNotification(); - } else { - hideMenuAndShowMessage(); - } - mDismissView.hide(); - mDragToInteractAnimationController.animateDismissMenu(/* scaleUp= */ false); + dispatchAccessibilityAction(target.getTargetView().getId()); } }); @@ -262,7 +279,11 @@ class MenuViewLayer extends FrameLayout implements }); addView(mMenuView, LayerIndex.MENU_VIEW); - addView(mDismissView, LayerIndex.DISMISS_VIEW); + if (Flags.floatingMenuDragToEdit()) { + addView(mDragToInteractView, LayerIndex.DISMISS_VIEW); + } else { + addView(mDismissView, LayerIndex.DISMISS_VIEW); + } addView(mMessageView, LayerIndex.MESSAGE_VIEW); if (Flags.floatingMenuAnimatedTuck()) { @@ -272,6 +293,7 @@ class MenuViewLayer extends FrameLayout implements @Override public void onConfigurationChanged(@NonNull Configuration newConfig) { + mDragToInteractView.updateResources(); mDismissView.updateResources(); mDragToInteractAnimationController.updateResources(); } @@ -428,6 +450,23 @@ class MenuViewLayer extends FrameLayout implements } } + void dispatchAccessibilityAction(int id) { + if (id == R.id.action_remove_menu) { + if (Flags.floatingMenuDragToHide()) { + hideMenuAndShowNotification(); + } else { + hideMenuAndShowMessage(); + } + } else if (id == R.id.action_edit + && Flags.floatingMenuDragToEdit()) { + mMenuView.gotoEditScreen(); + } + mDismissView.hide(); + mDragToInteractView.hide(); + mDragToInteractAnimationController.animateInteractMenu( + id, /* scaleUp= */ false); + } + private CharSequence getMigrationMessage() { final Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_DETAILS_SETTINGS); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); @@ -475,7 +514,8 @@ class MenuViewLayer extends FrameLayout implements mEduTooltipView = Optional.empty(); } - private void hideMenuAndShowMessage() { + @VisibleForTesting + void hideMenuAndShowMessage() { final int delayTime = mAccessibilityManager.getRecommendedTimeoutMillis( SHOW_MESSAGE_DELAY_MS, AccessibilityManager.FLAG_CONTENT_TEXT @@ -485,7 +525,8 @@ class MenuViewLayer extends FrameLayout implements mMenuAnimationController.startShrinkAnimation(() -> mMenuView.setVisibility(GONE)); } - private void hideMenuAndShowNotification() { + @VisibleForTesting + void hideMenuAndShowNotification() { mMenuAnimationController.startShrinkAnimation(() -> mMenuView.setVisibility(GONE)); showNotification(); } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java index 1f549525256b..bc9d1ffd259b 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java @@ -39,7 +39,16 @@ class MenuViewLayerController implements IAccessibilityFloatingMenu { MenuViewLayerController(Context context, WindowManager windowManager, AccessibilityManager accessibilityManager, SecureSettings secureSettings) { mWindowManager = windowManager; - mMenuViewLayer = new MenuViewLayer(context, windowManager, accessibilityManager, this, + + MenuViewModel menuViewModel = new MenuViewModel( + context, accessibilityManager, secureSettings); + MenuViewAppearance menuViewAppearance = new MenuViewAppearance(context, windowManager); + + mMenuViewLayer = new MenuViewLayer(context, windowManager, accessibilityManager, + menuViewModel, + menuViewAppearance, + new MenuView(context, menuViewModel, menuViewAppearance, secureSettings), + this, secureSettings); } diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt index 28adb77f00e0..89e31f383c43 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt @@ -176,8 +176,8 @@ constructor( } /** Show the widget editor Activity. */ - fun showWidgetEditor() { - editWidgetsActivityStarter.startActivity() + fun showWidgetEditor(preselectedKey: String? = null) { + editWidgetsActivityStarter.startActivity(preselectedKey) } /** Dismiss the CTA tile from the hub in view mode. */ diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt index acd6cb09e241..ae019a187bae 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt @@ -128,4 +128,6 @@ sealed interface CommunalContentModel { } } } + + fun isWidget() = this is Widget } diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt index 1e64d3f9cedc..91df8289ca64 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt @@ -40,11 +40,11 @@ abstract class BaseCommunalViewModel( /** Whether widgets are currently being re-ordered. */ open val reorderingWidgets: StateFlow<Boolean> = MutableStateFlow(false) - private val _selectedIndex: MutableStateFlow<Int?> = MutableStateFlow(null) + private val _selectedKey: MutableStateFlow<String?> = MutableStateFlow(null) - /** The index of the currently selected item, or null if no item selected. */ - val selectedIndex: StateFlow<Int?> - get() = _selectedIndex + /** The key of the currently selected item, or null if no item selected. */ + val selectedKey: StateFlow<String?> + get() = _selectedKey fun onSceneChanged(scene: CommunalSceneKey) { communalInteractor.onSceneChanged(scene) @@ -94,8 +94,8 @@ abstract class BaseCommunalViewModel( */ open fun onReorderWidgets(widgetIdToPriorityMap: Map<Int, Int>) {} - /** Called as the UI requests opening the widget editor. */ - open fun onOpenWidgetEditor() {} + /** Called as the UI requests opening the widget editor with an optional preselected widget. */ + open fun onOpenWidgetEditor(preselectedKey: String? = null) {} /** Called as the UI requests to dismiss the CTA tile. */ open fun onDismissCtaTile() {} @@ -109,8 +109,8 @@ abstract class BaseCommunalViewModel( /** Called as the user cancels dragging a widget to reorder. */ open fun onReorderWidgetCancel() {} - /** Set the index of the currently selected item */ - fun setSelectedIndex(index: Int?) { - _selectedIndex.value = index + /** Set the key of the currently selected item */ + fun setSelectedKey(key: String?) { + _selectedKey.value = key } } diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt index 4b98f1ae4fe8..ebcfb8bb8209 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt @@ -29,7 +29,6 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.onEach /** The view model for communal hub in edit mode. */ @SysUISingleton @@ -44,10 +43,9 @@ constructor( // Only widgets are editable. The CTA tile comes last in the list and remains visible. override val communalContent: Flow<List<CommunalContentModel>> = - communalInteractor.widgetContent - // Clear the selected index when the list is updated. - .onEach { setSelectedIndex(null) } - .map { widgets -> widgets + listOf(CommunalContentModel.CtaTileInEditMode()) } + communalInteractor.widgetContent.map { widgets -> + widgets + listOf(CommunalContentModel.CtaTileInEditMode()) + } private val _reorderingWidgets = MutableStateFlow(false) @@ -61,7 +59,7 @@ constructor( override fun onReorderWidgetStart() { // Clear selection status - setSelectedIndex(null) + setSelectedKey(null) _reorderingWidgets.value = true uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_REORDER_WIDGET_START) } diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt index a909383f4a2c..d7a3705248ff 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt @@ -81,7 +81,8 @@ constructor( } } - override fun onOpenWidgetEditor() = communalInteractor.showWidgetEditor() + override fun onOpenWidgetEditor(preselectedKey: String?) = + communalInteractor.showWidgetEditor(preselectedKey) override fun onDismissCtaTile() { scope.launch { diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt index a2575439e4b2..ad1327e90710 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt @@ -47,6 +47,7 @@ constructor( private const val EXTRA_FILTER_STRATEGY = "filter_strategy" private const val FILTER_STRATEGY_GLANCEABLE_HUB = 1 private const val TAG = "EditWidgetsActivity" + const val EXTRA_PRESELECTED_KEY = "preselected_key" } private val widgetConfigurator by lazy { widgetConfiguratorFactory.create(this) } @@ -92,6 +93,9 @@ constructor( windowInsetsController?.hide(WindowInsets.Type.systemBars()) window.setDecorFitsSystemWindows(false) + val preselectedKey = intent.getStringExtra(EXTRA_PRESELECTED_KEY) + communalViewModel.setSelectedKey(preselectedKey) + setCommunalEditWidgetActivityContent( activity = this, viewModel = communalViewModel, diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivityStarter.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivityStarter.kt index 55acad0e9ed5..d1843afbc1c2 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivityStarter.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivityStarter.kt @@ -18,12 +18,13 @@ package com.android.systemui.communal.widgets import android.content.Context import android.content.Intent +import com.android.systemui.communal.widgets.EditWidgetsActivity.Companion.EXTRA_PRESELECTED_KEY import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.plugins.ActivityStarter import javax.inject.Inject interface EditWidgetsActivityStarter { - fun startActivity() + fun startActivity(preselectedKey: String? = null) } class EditWidgetsActivityStarterImpl @@ -33,10 +34,11 @@ constructor( private val activityStarter: ActivityStarter, ) : EditWidgetsActivityStarter { - override fun startActivity() { + override fun startActivity(preselectedKey: String?) { activityStarter.startActivityDismissingKeyguard( Intent(applicationContext, EditWidgetsActivity::class.java) - .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK), + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK) + .apply { preselectedKey?.let { putExtra(EXTRA_PRESELECTED_KEY, preselectedKey) } }, /* onlyProvisioned = */ true, /* dismissShade = */ true, ) diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/BluetoothDialog.java b/packages/SystemUI/src/com/android/systemui/keyboard/BluetoothDialogDelegate.java index 5deea9bc737a..98642d75f907 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/BluetoothDialog.java +++ b/packages/SystemUI/src/com/android/systemui/keyboard/BluetoothDialogDelegate.java @@ -16,17 +16,25 @@ package com.android.systemui.keyboard; -import android.content.Context; import android.view.WindowManager; import com.android.systemui.statusbar.phone.SystemUIDialog; -public class BluetoothDialog extends SystemUIDialog { +import javax.inject.Inject; - public BluetoothDialog(Context context) { - super(context); +public class BluetoothDialogDelegate implements SystemUIDialog.Delegate{ - getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG); - setShowForAllUsers(true); + private final SystemUIDialog.Factory mSystemUIDialogFactory; + @Inject + public BluetoothDialogDelegate(SystemUIDialog.Factory systemUIDialogFactory) { + mSystemUIDialogFactory = systemUIDialogFactory; + } + + @Override + public SystemUIDialog createDialog() { + SystemUIDialog dialog = mSystemUIDialogFactory.create(this); + dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG); + dialog.setShowForAllUsers(true); + return dialog; } } diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java index 1cdbe6fed58a..17e3ca6f1a8c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java +++ b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java @@ -52,6 +52,7 @@ import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; import com.android.systemui.CoreStartable; import com.android.systemui.res.R; import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.statusbar.phone.SystemUIDialog; import com.android.systemui.util.settings.SecureSettings; import java.io.PrintWriter; @@ -109,6 +110,7 @@ public class KeyboardUI implements CoreStartable, InputManager.OnTabletModeChang private final Provider<LocalBluetoothManager> mBluetoothManagerProvider; private final SecureSettings mSecureSettings; + private final BluetoothDialogDelegate mBluetoothDialogDelegate; private boolean mEnabled; private String mKeyboardName; @@ -121,16 +123,20 @@ public class KeyboardUI implements CoreStartable, InputManager.OnTabletModeChang private int mInTabletMode = InputManager.SWITCH_STATE_UNKNOWN; private int mScanAttempt = 0; private ScanCallback mScanCallback; - private BluetoothDialog mDialog; + private SystemUIDialog mDialog; private int mState; @Inject - public KeyboardUI(Context context, Provider<LocalBluetoothManager> bluetoothManagerProvider, - SecureSettings secureSettings) { + public KeyboardUI( + Context context, + Provider<LocalBluetoothManager> bluetoothManagerProvider, + SecureSettings secureSettings, + BluetoothDialogDelegate bluetoothDialogDelegate) { mContext = context; this.mBluetoothManagerProvider = bluetoothManagerProvider; mSecureSettings = secureSettings; + mBluetoothDialogDelegate = bluetoothDialogDelegate; } @Override @@ -437,7 +443,7 @@ public class KeyboardUI implements CoreStartable, InputManager.OnTabletModeChang new BluetoothDialogClickListener(); DialogInterface.OnDismissListener dismissListener = new BluetoothDialogDismissListener(); - mDialog = new BluetoothDialog(mContext); + mDialog = mBluetoothDialogDelegate.createDialog(); mDialog.setTitle(R.string.enable_bluetooth_title); mDialog.setMessage(R.string.enable_bluetooth_message); mDialog.setPositiveButton( diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/StickyKeysLogger.kt b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/StickyKeysLogger.kt index 4ed812010100..5ef5ef19c809 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/StickyKeysLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/StickyKeysLogger.kt @@ -43,4 +43,13 @@ class StickyKeysLogger @Inject constructor(@KeyboardLog private val buffer: LogB { "new sticky keys state received: $str1" } ) } -}
\ No newline at end of file + + fun logNewSettingValue(enabled: Boolean) { + buffer.log( + TAG, + LogLevel.INFO, + { bool1 = enabled }, + { "sticky key setting changed, new state: ${if (bool1) "enabled" else "disabled"}" } + ) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt index 34d288815570..ec29bd6014ef 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt @@ -19,8 +19,10 @@ package com.android.systemui.keyboard.stickykeys.data.repository import android.hardware.input.InputManager import android.hardware.input.InputManager.StickyModifierStateListener import android.hardware.input.StickyModifierState +import android.provider.Settings import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.keyboard.stickykeys.StickyKeysLogger import com.android.systemui.keyboard.stickykeys.shared.model.Locked @@ -30,14 +32,19 @@ import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.ALT_GR import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.CTRL import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.META import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.SHIFT +import com.android.systemui.user.data.repository.UserRepository +import com.android.systemui.util.settings.SecureSettings +import com.android.systemui.util.settings.SettingsProxyExt.observerFlow import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.onStart import javax.inject.Inject interface StickyKeysRepository { @@ -45,11 +52,15 @@ interface StickyKeysRepository { val settingEnabled: Flow<Boolean> } +@SysUISingleton +@OptIn(ExperimentalCoroutinesApi::class) class StickyKeysRepositoryImpl @Inject constructor( private val inputManager: InputManager, @Background private val backgroundDispatcher: CoroutineDispatcher, + private val secureSettings: SecureSettings, + userRepository: UserRepository, private val stickyKeysLogger: StickyKeysLogger, ) : StickyKeysRepository { @@ -66,8 +77,26 @@ constructor( .onEach { stickyKeysLogger.logNewStickyKeysReceived(it) } .flowOn(backgroundDispatcher) - // TODO(b/319837892): Implement reading actual setting - override val settingEnabled: StateFlow<Boolean> = MutableStateFlow(true) + override val settingEnabled: Flow<Boolean> = + userRepository.selectedUserInfo + .flatMapLatest { stickyKeySettingObserver(it.id) } + .flowOn(backgroundDispatcher) + + private fun stickyKeySettingObserver(userId: Int): Flow<Boolean> { + return secureSettings + .observerFlow(userId, SETTING_KEY) + .onStart { emit(Unit) } + .map { isSettingEnabledForCurrentUser(userId) } + .distinctUntilChanged() + .onEach { stickyKeysLogger.logNewSettingValue(it) } + } + + private fun isSettingEnabledForCurrentUser(userId: Int) = + secureSettings.getIntForUser( + /* name= */ SETTING_KEY, + /* default= */ 0, + /* userHandle= */ userId + ) != 0 private fun toStickyKeysMap(state: StickyModifierState): LinkedHashMap<ModifierKey, Locked> { val keys = linkedMapOf<ModifierKey, Locked>() @@ -88,5 +117,6 @@ constructor( companion object { const val TAG = "StickyKeysRepositoryImpl" + const val SETTING_KEY = Settings.Secure.ACCESSIBILITY_STICKY_KEYS } } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt index 93a6eeed3667..b3d848c2d23b 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt @@ -371,7 +371,6 @@ internal constructor( // Receiving a CANCEL implies that something else intercepted // the gesture, i.e., the user did not cancel their gesture. // Therefore, disappear immediately, with minimum fanfare. - interactionJankMonitor.cancel(CUJ_BACK_PANEL_ARROW) updateArrowState(GestureState.GONE) velocityTracker = null } @@ -883,16 +882,6 @@ internal constructor( previousState = currentState currentState = newState - // First, update the jank tracker - when (currentState) { - GestureState.ENTRY -> { - interactionJankMonitor.cancel(CUJ_BACK_PANEL_ARROW) - interactionJankMonitor.begin(mView, CUJ_BACK_PANEL_ARROW) - } - GestureState.GONE -> interactionJankMonitor.end(CUJ_BACK_PANEL_ARROW) - else -> {} - } - when (currentState) { GestureState.CANCELLED -> { backCallback.cancelBack() diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java index a9dd25bc403d..f173900caa8c 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java @@ -16,6 +16,7 @@ package com.android.systemui.recents; +import static android.app.Flags.keyguardPrivateNotifications; import static android.content.Intent.ACTION_PACKAGE_ADDED; import static android.content.Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST; import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY; @@ -81,6 +82,7 @@ import com.android.internal.logging.UiEventLogger; import com.android.internal.util.ScreenshotHelper; import com.android.internal.util.ScreenshotRequest; import com.android.systemui.Dumpable; +import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dump.DumpManager; @@ -167,9 +169,10 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis private final Optional<UnfoldTransitionProgressForwarder> mUnfoldTransitionProgressForwarder; private final UiEventLogger mUiEventLogger; private final DisplayTracker mDisplayTracker; - private Region mActiveNavBarRegion; + private final BroadcastDispatcher mBroadcastDispatcher; + private IOverviewProxy mOverviewProxy; private int mConnectionBackoffAttempts; private boolean mBound; @@ -419,6 +422,21 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis retryConnectionWithBackoff(); }; + private final BroadcastReceiver mUserEventReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (Objects.equals(intent.getAction(), Intent.ACTION_USER_UNLOCKED)) { + if (keyguardPrivateNotifications()) { + // Start the overview connection to the launcher service + // Connect if user hasn't connected yet + if (getProxy() == null) { + startConnectionToCurrentUser(); + } + } + } + } + }; + private final BroadcastReceiver mLauncherStateChangedReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -586,7 +604,8 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis FeatureFlags featureFlags, SceneContainerFlags sceneContainerFlags, DumpManager dumpManager, - Optional<UnfoldTransitionProgressForwarder> unfoldTransitionProgressForwarder + Optional<UnfoldTransitionProgressForwarder> unfoldTransitionProgressForwarder, + BroadcastDispatcher broadcastDispatcher ) { // b/241601880: This component shouldn't be running for a non-primary user if (!Process.myUserHandle().equals(UserHandle.SYSTEM)) { @@ -615,6 +634,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis mUiEventLogger = uiEventLogger; mDisplayTracker = displayTracker; mUnfoldTransitionProgressForwarder = unfoldTransitionProgressForwarder; + mBroadcastDispatcher = broadcastDispatcher; if (!KeyguardWmStateRefactor.isEnabled()) { mSysuiUnlockAnimationController = sysuiUnlockAnimationController; @@ -635,6 +655,12 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis filter.addAction(Intent.ACTION_PACKAGE_CHANGED); mContext.registerReceiver(mLauncherStateChangedReceiver, filter); + if (keyguardPrivateNotifications()) { + mBroadcastDispatcher.registerReceiver(mUserEventReceiver, + new IntentFilter(Intent.ACTION_USER_UNLOCKED), + null /* executor */, UserHandle.ALL); + } + // Listen for status bar state changes statusBarWinController.registerCallback(mStatusBarWindowCallback); mScreenshotHelper = new ScreenshotHelper(context); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java index 24ac70e63e46..2a4753def463 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java @@ -234,10 +234,12 @@ public class NotificationLockscreenUserManagerImpl implements } else if (profileAvailabilityActions(action)) { updateCurrentProfilesCache(); } else if (Objects.equals(action, Intent.ACTION_USER_UNLOCKED)) { - // Start the overview connection to the launcher service - // Connect if user hasn't connected yet - if (mOverviewProxyServiceLazy.get().getProxy() == null) { - mOverviewProxyServiceLazy.get().startConnectionToCurrentUser(); + if (!keyguardPrivateNotifications()) { + // Start the overview connection to the launcher service + // Connect if user hasn't connected yet + if (mOverviewProxyServiceLazy.get().getProxy() == null) { + mOverviewProxyServiceLazy.get().startConnectionToCurrentUser(); + } } } else if (Objects.equals(action, NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION)) { final IntentSender intentSender = intent.getParcelableExtra( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java index aabe4a0d66f9..3f20eaf45260 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java @@ -763,10 +763,15 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump // see: b/186644628 mNotificationsScrim.setDrawableBounds(left - 1, top, right + 1, bottom); mScrimBehind.setBottomEdgePosition((int) top); + } else { + mNotificationsScrim.setDrawableBounds(left, top, right, bottom); + } + + // Only clip if the notif scrim is visible + if (mNotificationsAlpha > 0f) { mKeyguardInteractor.setTopClippingBounds((int) top); } else { mKeyguardInteractor.setTopClippingBounds(null); - mNotificationsScrim.setDrawableBounds(left, top, right, bottom); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java index e3b65ab27f48..61bd112121bc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java @@ -20,6 +20,7 @@ import android.graphics.Color; import android.os.Trace; import com.android.systemui.dock.DockManager; +import com.android.systemui.res.R; import com.android.systemui.scrim.ScrimView; import com.android.systemui.statusbar.notification.stack.StackStateAnimator; @@ -39,8 +40,8 @@ public enum ScrimState { OFF { @Override public void prepare(ScrimState previousState) { - mFrontTint = Color.BLACK; - mBehindTint = Color.BLACK; + mFrontTint = mBackgroundColor; + mBehindTint = mBackgroundColor; mFrontAlpha = 1f; mBehindAlpha = 1f; @@ -74,15 +75,15 @@ public enum ScrimState { } else { mAnimationDuration = ScrimController.ANIMATION_DURATION; } - mFrontTint = Color.BLACK; - mBehindTint = Color.BLACK; - mNotifTint = mClipQsScrim ? Color.BLACK : Color.TRANSPARENT; + mFrontTint = mBackgroundColor; + mBehindTint = mBackgroundColor; + mNotifTint = mClipQsScrim ? mBackgroundColor : Color.TRANSPARENT; mFrontAlpha = 0; mBehindAlpha = mClipQsScrim ? 1 : mScrimBehindAlphaKeyguard; mNotifAlpha = mClipQsScrim ? mScrimBehindAlphaKeyguard : 0; if (mClipQsScrim) { - updateScrimColor(mScrimBehind, 1f /* alpha */, Color.BLACK); + updateScrimColor(mScrimBehind, 1f /* alpha */, mBackgroundColor); } } }, @@ -93,10 +94,10 @@ public enum ScrimState { // notif scrim alpha values are determined by ScrimController#applyState // based on the shade expansion - mFrontTint = Color.BLACK; + mFrontTint = mBackgroundColor; mFrontAlpha = .66f; - mBehindTint = Color.BLACK; + mBehindTint = mBackgroundColor; mBehindAlpha = 1f; } }, @@ -110,7 +111,7 @@ public enum ScrimState { mBehindTint = previousState.mBehindTint; mBehindAlpha = previousState.mBehindAlpha; - mFrontTint = Color.BLACK; + mFrontTint = mBackgroundColor; mFrontAlpha = .66f; } }, @@ -122,7 +123,7 @@ public enum ScrimState { @Override public void prepare(ScrimState previousState) { mBehindAlpha = mClipQsScrim ? 1 : mDefaultScrimAlpha; - mBehindTint = mClipQsScrim ? Color.BLACK : mSurfaceColor; + mBehindTint = mClipQsScrim ? mBackgroundColor : mSurfaceColor; mNotifAlpha = mClipQsScrim ? mDefaultScrimAlpha : 0; mNotifTint = Color.TRANSPARENT; mFrontAlpha = 0f; @@ -154,10 +155,10 @@ public enum ScrimState { mBehindAlpha = mClipQsScrim ? 1 : mDefaultScrimAlpha; mNotifAlpha = 1f; mFrontAlpha = 0f; - mBehindTint = mClipQsScrim ? Color.TRANSPARENT : Color.BLACK; + mBehindTint = mClipQsScrim ? Color.TRANSPARENT : mBackgroundColor; if (mClipQsScrim) { - updateScrimColor(mScrimBehind, 1f /* alpha */, Color.BLACK); + updateScrimColor(mScrimBehind, 1f /* alpha */, mBackgroundColor); } } }, @@ -184,11 +185,11 @@ public enum ScrimState { final boolean isDocked = mDockManager.isDocked(); mBlankScreen = mDisplayRequiresBlanking; - mFrontTint = Color.BLACK; + mFrontTint = mBackgroundColor; mFrontAlpha = (alwaysOnEnabled || isDocked || quickPickupEnabled) ? mAodFrontScrimAlpha : 1f; - mBehindTint = Color.BLACK; + mBehindTint = mBackgroundColor; mBehindAlpha = ScrimController.TRANSPARENT; mAnimationDuration = ScrimController.ANIMATION_DURATION_LONG; @@ -222,8 +223,8 @@ public enum ScrimState { @Override public void prepare(ScrimState previousState) { mFrontAlpha = mAodFrontScrimAlpha; - mBehindTint = Color.BLACK; - mFrontTint = Color.BLACK; + mBehindTint = mBackgroundColor; + mFrontTint = mBackgroundColor; mBlankScreen = mDisplayRequiresBlanking; mAnimationDuration = mWakeLockScreenSensorActive ? ScrimController.ANIMATION_DURATION_LONG : ScrimController.ANIMATION_DURATION; @@ -231,7 +232,7 @@ public enum ScrimState { @Override public float getMaxLightRevealScrimAlpha() { return mWakeLockScreenSensorActive ? ScrimController.WAKE_SENSOR_SCRIM_ALPHA - : AOD.getMaxLightRevealScrimAlpha(); + : AOD.getMaxLightRevealScrimAlpha(); } }, @@ -245,7 +246,6 @@ public enum ScrimState { mBehindAlpha = mClipQsScrim ? 1 : 0; mNotifAlpha = 0; mFrontAlpha = 0; - mAnimationDuration = mKeyguardFadingAway ? mKeyguardFadingAwayDuration : CentralSurfaces.FADE_KEYGUARD_DURATION; @@ -259,22 +259,22 @@ public enum ScrimState { && !fromAod; mFrontTint = Color.TRANSPARENT; - mBehindTint = Color.BLACK; + mBehindTint = mBackgroundColor; mBlankScreen = false; if (mDisplayRequiresBlanking && previousState == ScrimState.AOD) { // Set all scrims black, before they fade transparent. - updateScrimColor(mScrimInFront, 1f /* alpha */, Color.BLACK /* tint */); - updateScrimColor(mScrimBehind, 1f /* alpha */, Color.BLACK /* tint */); + updateScrimColor(mScrimInFront, 1f /* alpha */, mBackgroundColor /* tint */); + updateScrimColor(mScrimBehind, 1f /* alpha */, mBackgroundColor /* tint */); // Scrims should still be black at the end of the transition. - mFrontTint = Color.BLACK; - mBehindTint = Color.BLACK; + mFrontTint = mBackgroundColor; + mBehindTint = mBackgroundColor; mBlankScreen = true; } if (mClipQsScrim) { - updateScrimColor(mScrimBehind, 1f /* alpha */, Color.BLACK); + updateScrimColor(mScrimBehind, 1f /* alpha */, mBackgroundColor); } } }, @@ -283,8 +283,8 @@ public enum ScrimState { @Override public void prepare(ScrimState previousState) { mFrontTint = Color.TRANSPARENT; - mBehindTint = Color.BLACK; - mNotifTint = mClipQsScrim ? Color.BLACK : Color.TRANSPARENT; + mBehindTint = mBackgroundColor; + mNotifTint = mClipQsScrim ? mBackgroundColor : Color.TRANSPARENT; mFrontAlpha = 0; mBehindAlpha = mClipQsScrim ? 1 : 0; @@ -293,7 +293,7 @@ public enum ScrimState { mBlankScreen = false; if (mClipQsScrim) { - updateScrimColor(mScrimBehind, 1f /* alpha */, Color.BLACK); + updateScrimColor(mScrimBehind, 1f /* alpha */, mBackgroundColor); } } }; @@ -327,9 +327,11 @@ public enum ScrimState { boolean mKeyguardFadingAway; long mKeyguardFadingAwayDuration; boolean mClipQsScrim; + int mBackgroundColor; public void init(ScrimView scrimInFront, ScrimView scrimBehind, DozeParameters dozeParameters, DockManager dockManager) { + mBackgroundColor = scrimBehind.getContext().getColor(R.color.shade_scrim_background_dark); mScrimInFront = scrimInFront; mScrimBehind = scrimBehind; diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt new file mode 100644 index 000000000000..8d5e55a2917e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.volume.dagger + +import android.media.AudioManager +import com.android.settingslib.volume.data.repository.AudioRepository +import com.android.settingslib.volume.data.repository.AudioRepositoryImpl +import com.android.settingslib.volume.domain.interactor.AudioModeInteractor +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Background +import dagger.Module +import dagger.Provides +import kotlin.coroutines.CoroutineContext +import kotlinx.coroutines.CoroutineScope + +/** Dagger module for audio code in the volume package */ +@Module +interface AudioModule { + + companion object { + + @Provides + fun provideAudioRepository( + audioManager: AudioManager, + @Background coroutineContext: CoroutineContext, + @Application coroutineScope: CoroutineScope, + ): AudioRepository = AudioRepositoryImpl(audioManager, coroutineContext, coroutineScope) + + @Provides + fun provideAudioModeInteractor(repository: AudioRepository): AudioModeInteractor = + AudioModeInteractor(repository) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java index b1bfbe0016e1..c842e5f295b9 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java +++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java @@ -60,6 +60,9 @@ import kotlinx.coroutines.CoroutineScope; /** Dagger Module for code in the volume package. */ @Module( + includes = { + AudioModule.class, + }, subcomponents = { VolumePanelComponent.class } diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationControllerTest.java index 9bcab57bec87..90878169c201 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationControllerTest.java @@ -16,10 +16,12 @@ package com.android.systemui.accessibility.floatingmenu; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; +import android.annotation.NonNull; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.WindowManager; @@ -27,10 +29,12 @@ import android.view.accessibility.AccessibilityManager; import androidx.test.filters.SmallTest; +import com.android.systemui.Flags; import com.android.systemui.SysuiTestCase; +import com.android.systemui.accessibility.utils.TestUtils; import com.android.systemui.util.settings.SecureSettings; -import com.android.wm.shell.bubbles.DismissViewUtils; import com.android.wm.shell.common.bubbles.DismissView; +import com.android.wm.shell.common.magnetictarget.MagnetizedObject; import org.junit.Before; import org.junit.Rule; @@ -46,6 +50,7 @@ import org.mockito.junit.MockitoRule; @TestableLooper.RunWithLooper public class DragToInteractAnimationControllerTest extends SysuiTestCase { private DragToInteractAnimationController mDragToInteractAnimationController; + private DragToInteractView mInteractView; private DismissView mDismissView; @Rule @@ -57,29 +62,72 @@ public class DragToInteractAnimationControllerTest extends SysuiTestCase { @Before public void setUp() throws Exception { final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class); + final SecureSettings mockSecureSettings = TestUtils.mockSecureSettings(); final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager, - mock(SecureSettings.class)); + mockSecureSettings); final MenuViewAppearance stubMenuViewAppearance = new MenuViewAppearance(mContext, stubWindowManager); - final MenuView stubMenuView = new MenuView(mContext, stubMenuViewModel, - stubMenuViewAppearance); + final MenuView stubMenuView = spy(new MenuView(mContext, stubMenuViewModel, + stubMenuViewAppearance, mockSecureSettings)); + mInteractView = spy(new DragToInteractView(mContext)); mDismissView = spy(new DismissView(mContext)); - DismissViewUtils.setup(mDismissView); - mDragToInteractAnimationController = new DragToInteractAnimationController( - mDismissView, stubMenuView); + + if (Flags.floatingMenuDragToEdit()) { + mDragToInteractAnimationController = new DragToInteractAnimationController( + mInteractView, stubMenuView); + } else { + mDragToInteractAnimationController = new DragToInteractAnimationController( + mDismissView, stubMenuView); + } + + mDragToInteractAnimationController.setMagnetListener(new MagnetizedObject.MagnetListener() { + @Override + public void onStuckToTarget(@NonNull MagnetizedObject.MagneticTarget target) { + + } + + @Override + public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target, + float velX, float velY, boolean wasFlungOut) { + + } + + @Override + public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) { + + } + }); } @Test - public void showDismissView_success() { - mDragToInteractAnimationController.showDismissView(true); + @DisableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT) + public void showDismissView_success_old() { + mDragToInteractAnimationController.showInteractView(true); verify(mDismissView).show(); } @Test - public void hideDismissView_success() { - mDragToInteractAnimationController.showDismissView(false); + @DisableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT) + public void hideDismissView_success_old() { + mDragToInteractAnimationController.showInteractView(false); verify(mDismissView).hide(); } + + @Test + @EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT) + public void showDismissView_success() { + mDragToInteractAnimationController.showInteractView(true); + + verify(mInteractView).show(); + } + + @Test + @EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT) + public void hideDismissView_success() { + mDragToInteractAnimationController.showInteractView(false); + + verify(mInteractView).hide(); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java index 215f93d1e163..e0df1e0e5586 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java @@ -17,6 +17,7 @@ package com.android.systemui.accessibility.floatingmenu; import static com.google.common.truth.Truth.assertThat; + import static org.mockito.Mockito.any; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; @@ -42,6 +43,7 @@ import androidx.test.filters.SmallTest; import com.android.systemui.Flags; import com.android.systemui.Prefs; import com.android.systemui.SysuiTestCase; +import com.android.systemui.accessibility.utils.TestUtils; import com.android.systemui.util.settings.SecureSettings; import org.junit.After; @@ -79,10 +81,12 @@ public class MenuAnimationControllerTest extends SysuiTestCase { final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class); final MenuViewAppearance stubMenuViewAppearance = new MenuViewAppearance(mContext, stubWindowManager); + final SecureSettings secureSettings = TestUtils.mockSecureSettings(); final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager, - mock(SecureSettings.class)); + secureSettings); - mMenuView = spy(new MenuView(mContext, stubMenuViewModel, stubMenuViewAppearance)); + mMenuView = spy(new MenuView(mContext, stubMenuViewModel, stubMenuViewAppearance, + secureSettings)); mViewPropertyAnimator = spy(mMenuView.animate()); doReturn(mViewPropertyAnimator).when(mMenuView).animate(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java index 9c8de302c5e1..c2ed7d4a9d3c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java @@ -22,10 +22,13 @@ import static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.ACTIO import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import android.graphics.Rect; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.WindowManager; @@ -37,7 +40,9 @@ import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerViewAccessibilityDelegate; import androidx.test.filters.SmallTest; +import com.android.systemui.Flags; import com.android.systemui.SysuiTestCase; +import com.android.systemui.accessibility.utils.TestUtils; import com.android.systemui.res.R; import com.android.systemui.util.settings.SecureSettings; @@ -49,6 +54,8 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; +import java.util.concurrent.atomic.AtomicBoolean; + /** Tests for {@link MenuItemAccessibilityDelegate}. */ @SmallTest @TestableLooper.RunWithLooper @@ -59,17 +66,16 @@ public class MenuItemAccessibilityDelegateTest extends SysuiTestCase { @Mock private AccessibilityManager mAccessibilityManager; - @Mock - private SecureSettings mSecureSettings; - @Mock - private DragToInteractAnimationController.DismissCallback mStubDismissCallback; - + private final SecureSettings mSecureSettings = TestUtils.mockSecureSettings(); private RecyclerView mStubListView; private MenuView mMenuView; + private MenuViewLayer mMenuViewLayer; private MenuItemAccessibilityDelegate mMenuItemAccessibilityDelegate; private MenuAnimationController mMenuAnimationController; private final Rect mDraggableBounds = new Rect(100, 200, 300, 400); + private final AtomicBoolean mEditReceived = new AtomicBoolean(false); + @Before public void setUp() { final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class); @@ -80,20 +86,28 @@ public class MenuItemAccessibilityDelegateTest extends SysuiTestCase { final int halfScreenHeight = stubWindowManager.getCurrentWindowMetrics().getBounds().height() / 2; - mMenuView = spy(new MenuView(mContext, stubMenuViewModel, stubMenuViewAppearance)); + mMenuView = spy(new MenuView(mContext, stubMenuViewModel, stubMenuViewAppearance, + mSecureSettings)); mMenuView.setTranslationY(halfScreenHeight); + mMenuViewLayer = spy(new MenuViewLayer( + mContext, stubWindowManager, mAccessibilityManager, + stubMenuViewModel, stubMenuViewAppearance, mMenuView, + mock(IAccessibilityFloatingMenu.class), mSecureSettings)); + doReturn(mDraggableBounds).when(mMenuView).getMenuDraggableBounds(); mStubListView = new RecyclerView(mContext); mMenuAnimationController = spy(new MenuAnimationController(mMenuView, stubMenuViewAppearance)); mMenuItemAccessibilityDelegate = new MenuItemAccessibilityDelegate(new RecyclerViewAccessibilityDelegate( - mStubListView), mMenuAnimationController); + mStubListView), mMenuAnimationController, mMenuViewLayer); + mEditReceived.set(false); } @Test - public void getAccessibilityActionList_matchSize() { + @DisableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT) + public void getAccessibilityActionList_matchSize_withoutEdit() { final AccessibilityNodeInfoCompat info = new AccessibilityNodeInfoCompat(new AccessibilityNodeInfo()); @@ -103,6 +117,17 @@ public class MenuItemAccessibilityDelegateTest extends SysuiTestCase { } @Test + @EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT) + public void getAccessibilityActionList_matchSize() { + final AccessibilityNodeInfoCompat info = + new AccessibilityNodeInfoCompat(new AccessibilityNodeInfo()); + + mMenuItemAccessibilityDelegate.onInitializeAccessibilityNodeInfo(mStubListView, info); + + assertThat(info.getActionList().size()).isEqualTo(7); + } + + @Test public void performMoveTopLeftAction_matchPosition() { final boolean moveTopLeftAction = mMenuItemAccessibilityDelegate.performAccessibilityAction(mStubListView, @@ -169,13 +194,22 @@ public class MenuItemAccessibilityDelegateTest extends SysuiTestCase { @Test public void performRemoveMenuAction_success() { - mMenuAnimationController.setDismissCallback(mStubDismissCallback); final boolean removeMenuAction = mMenuItemAccessibilityDelegate.performAccessibilityAction(mStubListView, R.id.action_remove_menu, null); assertThat(removeMenuAction).isTrue(); - verify(mMenuAnimationController).removeMenu(); + verify(mMenuViewLayer).dispatchAccessibilityAction(R.id.action_remove_menu); + } + + @Test + public void performEditAction_success() { + final boolean editAction = + mMenuItemAccessibilityDelegate.performAccessibilityAction(mStubListView, + R.id.action_edit, null); + + assertThat(editAction).isTrue(); + verify(mMenuViewLayer).dispatchAccessibilityAction(R.id.action_edit); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java index e1522f5f6751..9e8c6b3395e2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java @@ -16,6 +16,7 @@ package com.android.systemui.accessibility.floatingmenu; +import static android.R.id.empty; import static android.view.View.OVER_SCROLL_NEVER; import static com.google.common.truth.Truth.assertThat; @@ -27,6 +28,8 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.MotionEvent; @@ -38,10 +41,11 @@ import androidx.recyclerview.widget.RecyclerView; import androidx.test.filters.SmallTest; import com.android.internal.accessibility.dialog.AccessibilityTarget; +import com.android.systemui.Flags; import com.android.systemui.SysuiTestCase; import com.android.systemui.accessibility.MotionEventHelper; +import com.android.systemui.accessibility.utils.TestUtils; import com.android.systemui.util.settings.SecureSettings; -import com.android.wm.shell.bubbles.DismissViewUtils; import com.android.wm.shell.common.bubbles.DismissView; import org.junit.After; @@ -71,6 +75,7 @@ public class MenuListViewTouchHandlerTest extends SysuiTestCase { private DragToInteractAnimationController mDragToInteractAnimationController; private RecyclerView mStubListView; private DismissView mDismissView; + private DragToInteractView mInteractView; @Rule public MockitoRule mockito = MockitoJUnit.rule(); @@ -81,19 +86,28 @@ public class MenuListViewTouchHandlerTest extends SysuiTestCase { @Before public void setUp() throws Exception { final WindowManager windowManager = mContext.getSystemService(WindowManager.class); + final SecureSettings secureSettings = TestUtils.mockSecureSettings(); final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager, - mock(SecureSettings.class)); + secureSettings); final MenuViewAppearance stubMenuViewAppearance = new MenuViewAppearance(mContext, windowManager); - mStubMenuView = new MenuView(mContext, stubMenuViewModel, stubMenuViewAppearance); + mStubMenuView = new MenuView(mContext, stubMenuViewModel, stubMenuViewAppearance, + secureSettings); mStubMenuView.setTranslationX(0); mStubMenuView.setTranslationY(0); mMenuAnimationController = spy(new MenuAnimationController( mStubMenuView, stubMenuViewAppearance)); + mInteractView = spy(new DragToInteractView(mContext)); mDismissView = spy(new DismissView(mContext)); - DismissViewUtils.setup(mDismissView); - mDragToInteractAnimationController = - spy(new DragToInteractAnimationController(mDismissView, mStubMenuView)); + + if (Flags.floatingMenuDragToEdit()) { + mDragToInteractAnimationController = spy(new DragToInteractAnimationController( + mInteractView, mStubMenuView)); + } else { + mDragToInteractAnimationController = spy(new DragToInteractAnimationController( + mDismissView, mStubMenuView)); + } + mTouchHandler = new MenuListViewTouchHandler(mMenuAnimationController, mDragToInteractAnimationController); final AccessibilityTargetAdapter stubAdapter = new AccessibilityTargetAdapter(mStubTargets); @@ -115,7 +129,7 @@ public class MenuListViewTouchHandlerTest extends SysuiTestCase { @Test public void onActionMoveEvent_notConsumedEvent_shouldMoveToPosition() { - doReturn(false).when(mDragToInteractAnimationController).maybeConsumeMoveMotionEvent( + doReturn(empty).when(mDragToInteractAnimationController).maybeConsumeMoveMotionEvent( any(MotionEvent.class)); final int offset = 100; final MotionEvent stubDownEvent = @@ -136,6 +150,7 @@ public class MenuListViewTouchHandlerTest extends SysuiTestCase { } @Test + @DisableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT) public void onActionMoveEvent_shouldShowDismissView() { final int offset = 100; final MotionEvent stubDownEvent = @@ -154,6 +169,25 @@ public class MenuListViewTouchHandlerTest extends SysuiTestCase { } @Test + @EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT) + public void onActionMoveEvent_shouldShowInteractView() { + final int offset = 100; + final MotionEvent stubDownEvent = + mMotionEventHelper.obtainMotionEvent(/* downTime= */ 0, /* eventTime= */ 1, + MotionEvent.ACTION_DOWN, mStubMenuView.getTranslationX(), + mStubMenuView.getTranslationY()); + final MotionEvent stubMoveEvent = + mMotionEventHelper.obtainMotionEvent(/* downTime= */ 0, /* eventTime= */ 3, + MotionEvent.ACTION_MOVE, mStubMenuView.getTranslationX() + offset, + mStubMenuView.getTranslationY() + offset); + + mTouchHandler.onInterceptTouchEvent(mStubListView, stubDownEvent); + mTouchHandler.onInterceptTouchEvent(mStubListView, stubMoveEvent); + + verify(mInteractView).show(); + } + + @Test public void dragAndDrop_shouldFlingMenuThenSpringToEdge() { final int offset = 100; final MotionEvent stubDownEvent = diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java index bc9a0a5484ac..4a1bdbcc9b48 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java @@ -30,6 +30,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; @@ -72,6 +73,8 @@ import com.android.internal.messages.nano.SystemMessageProto; import com.android.systemui.Flags; import com.android.systemui.SysuiTestCase; import com.android.systemui.SysuiTestableContext; +import com.android.systemui.accessibility.utils.TestUtils; +import com.android.systemui.res.R; import com.android.systemui.util.settings.SecureSettings; import com.android.wm.shell.common.magnetictarget.MagnetizedObject; @@ -81,6 +84,7 @@ import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; +import org.mockito.ArgumentMatcher; import org.mockito.Mock; import org.mockito.Spy; import org.mockito.junit.MockitoJUnit; @@ -122,18 +126,17 @@ public class MenuViewLayerTest extends SysuiTestCase { private SysuiTestableContext mSpyContext = getContext(); @Mock private IAccessibilityFloatingMenu mFloatingMenu; - - @Mock - private SecureSettings mSecureSettings; - @Mock private WindowManager mStubWindowManager; - @Mock private AccessibilityManager mStubAccessibilityManager; + private final SecureSettings mSecureSettings = TestUtils.mockSecureSettings(); private final NotificationManager mMockNotificationManager = mock(NotificationManager.class); + private final ArgumentMatcher<IntentFilter> mNotificationMatcher = + (arg) -> arg.hasAction(ACTION_UNDO) && arg.hasAction(ACTION_DELETE); + @Before public void setUp() throws Exception { mSpyContext.addMockSystemService(Context.NOTIFICATION_SERVICE, mMockNotificationManager); @@ -145,8 +148,16 @@ public class MenuViewLayerTest extends SysuiTestCase { new WindowMetrics(mDisplayBounds, fakeDisplayInsets(), /* density = */ 0.0f)); doReturn(mWindowMetrics).when(mStubWindowManager).getCurrentWindowMetrics(); - mMenuViewLayer = new MenuViewLayer(mSpyContext, mStubWindowManager, - mStubAccessibilityManager, mFloatingMenu, mSecureSettings); + MenuViewModel menuViewModel = new MenuViewModel( + mSpyContext, mStubAccessibilityManager, mSecureSettings); + MenuViewAppearance menuViewAppearance = new MenuViewAppearance( + mSpyContext, mStubWindowManager); + mMenuView = spy( + new MenuView(mSpyContext, menuViewModel, menuViewAppearance, mSecureSettings)); + + mMenuViewLayer = spy(new MenuViewLayer(mSpyContext, mStubWindowManager, + mStubAccessibilityManager, menuViewModel, menuViewAppearance, mMenuView, + mFloatingMenu, mSecureSettings)); mMenuView = (MenuView) mMenuViewLayer.getChildAt(LayerIndex.MENU_VIEW); mMenuAnimationController = mMenuView.getMenuAnimationController(); @@ -236,6 +247,27 @@ public class MenuViewLayerTest extends SysuiTestCase { } @Test + @EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT) + public void onEditAction_gotoEditScreen_isCalled() { + mMenuViewLayer.dispatchAccessibilityAction(R.id.action_edit); + verify(mMenuView).gotoEditScreen(); + } + + @Test + @EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_HIDE) + public void onDismissAction_hideMenuAndShowNotification() { + mMenuViewLayer.dispatchAccessibilityAction(R.id.action_remove_menu); + verify(mMenuViewLayer).hideMenuAndShowNotification(); + } + + @Test + @DisableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_HIDE) + public void onDismissAction_hideMenuAndShowMessage() { + mMenuViewLayer.dispatchAccessibilityAction(R.id.action_remove_menu); + verify(mMenuViewLayer).hideMenuAndShowMessage(); + } + + @Test public void showingImeInsetsChange_notOverlapOnIme_menuKeepOriginalPosition() { final float menuTop = STATUS_BAR_HEIGHT + 100; mMenuAnimationController.moveAndPersistPosition(new PointF(0, menuTop)); @@ -307,19 +339,13 @@ public class MenuViewLayerTest extends SysuiTestCase { @Test @EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_HIDE) public void onReleasedInTarget_hideMenuAndShowNotificationWithExpectedActions() { - dragMenuThenReleasedInTarget(); + dragMenuThenReleasedInTarget(R.id.action_remove_menu); verify(mMockNotificationManager).notify( eq(SystemMessageProto.SystemMessage.NOTE_A11Y_FLOATING_MENU_HIDDEN), any(Notification.class)); - ArgumentCaptor<IntentFilter> intentFilterCaptor = ArgumentCaptor.forClass( - IntentFilter.class); verify(mSpyContext).registerReceiver( - any(BroadcastReceiver.class), - intentFilterCaptor.capture(), - anyInt()); - assertThat(intentFilterCaptor.getValue().matchAction(ACTION_UNDO)).isTrue(); - assertThat(intentFilterCaptor.getValue().matchAction(ACTION_DELETE)).isTrue(); + any(BroadcastReceiver.class), argThat(mNotificationMatcher), anyInt()); } @Test @@ -327,10 +353,10 @@ public class MenuViewLayerTest extends SysuiTestCase { public void receiveActionUndo_dismissNotificationAndMenuVisible() { ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor = ArgumentCaptor.forClass( BroadcastReceiver.class); - dragMenuThenReleasedInTarget(); + dragMenuThenReleasedInTarget(R.id.action_remove_menu); verify(mSpyContext).registerReceiver(broadcastReceiverCaptor.capture(), - any(IntentFilter.class), anyInt()); + argThat(mNotificationMatcher), anyInt()); broadcastReceiverCaptor.getValue().onReceive(mSpyContext, new Intent(ACTION_UNDO)); verify(mSpyContext).unregisterReceiver(broadcastReceiverCaptor.getValue()); @@ -344,10 +370,10 @@ public class MenuViewLayerTest extends SysuiTestCase { public void receiveActionDelete_dismissNotificationAndHideMenu() { ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor = ArgumentCaptor.forClass( BroadcastReceiver.class); - dragMenuThenReleasedInTarget(); + dragMenuThenReleasedInTarget(R.id.action_remove_menu); verify(mSpyContext).registerReceiver(broadcastReceiverCaptor.capture(), - any(IntentFilter.class), anyInt()); + argThat(mNotificationMatcher), anyInt()); broadcastReceiverCaptor.getValue().onReceive(mSpyContext, new Intent(ACTION_DELETE)); verify(mSpyContext).unregisterReceiver(broadcastReceiverCaptor.getValue()); @@ -423,10 +449,12 @@ public class MenuViewLayerTest extends SysuiTestCase { }); } - private void dragMenuThenReleasedInTarget() { + private void dragMenuThenReleasedInTarget(int id) { MagnetizedObject.MagnetListener magnetListener = - mMenuViewLayer.getDragToInteractAnimationController().getMagnetListener(); + mMenuViewLayer.getDragToInteractAnimationController().getMagnetListener(id); + View view = mock(View.class); + when(view.getId()).thenReturn(id); magnetListener.onReleasedInTarget( - new MagnetizedObject.MagneticTarget(mock(View.class), 200)); + new MagnetizedObject.MagneticTarget(view, 200)); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java index 8da6cf98d76f..7c97f53d539d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java @@ -17,15 +17,19 @@ package com.android.systemui.accessibility.floatingmenu; import static android.app.UiModeManager.MODE_NIGHT_YES; + import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Mockito.mock; + +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import android.app.UiModeManager; +import android.content.Intent; import android.graphics.Rect; import android.graphics.drawable.GradientDrawable; import android.platform.test.annotations.EnableFlags; +import android.provider.Settings; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.WindowManager; @@ -36,6 +40,8 @@ import androidx.test.filters.SmallTest; import com.android.systemui.Flags; import com.android.systemui.Prefs; import com.android.systemui.SysuiTestCase; +import com.android.systemui.SysuiTestableContext; +import com.android.systemui.accessibility.utils.TestUtils; import com.android.systemui.util.settings.SecureSettings; import org.junit.After; @@ -65,17 +71,23 @@ public class MenuViewTest extends SysuiTestCase { @Mock private AccessibilityManager mAccessibilityManager; + private SysuiTestableContext mSpyContext; + @Before public void setUp() throws Exception { mUiModeManager = mContext.getSystemService(UiModeManager.class); mNightMode = mUiModeManager.getNightMode(); mUiModeManager.setNightMode(MODE_NIGHT_YES); + + mSpyContext = spy(mContext); + final SecureSettings secureSettings = TestUtils.mockSecureSettings(); final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager, - mock(SecureSettings.class)); + secureSettings); final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class); - mStubMenuViewAppearance = new MenuViewAppearance(mContext, stubWindowManager); - mMenuView = spy(new MenuView(mContext, stubMenuViewModel, mStubMenuViewAppearance)); - mLastPosition = Prefs.getString(mContext, + mStubMenuViewAppearance = new MenuViewAppearance(mSpyContext, stubWindowManager); + mMenuView = spy(new MenuView(mSpyContext, stubMenuViewModel, mStubMenuViewAppearance, + secureSettings)); + mLastPosition = Prefs.getString(mSpyContext, Prefs.Key.ACCESSIBILITY_FLOATING_MENU_POSITION, /* defaultValue= */ null); } @@ -154,6 +166,25 @@ public class MenuViewTest extends SysuiTestCase { assertThat(radiiAnimator.isStarted()).isTrue(); } + @Test + public void getIntentForEditScreen_validate() { + Intent intent = mMenuView.getIntentForEditScreen(); + String[] targets = intent.getBundleExtra( + ":settings:show_fragment_args").getStringArray("targets"); + + assertThat(intent.getAction()).isEqualTo(Settings.ACTION_ACCESSIBILITY_SHORTCUT_SETTINGS); + assertThat(targets).asList().containsExactlyElementsIn(TestUtils.TEST_BUTTON_TARGETS); + } + + @Test + @EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT) + public void gotoEditScreen_sendsIntent() { + // Notably, this shouldn't crash the settings app, + // because the button target args are configured. + mMenuView.gotoEditScreen(); + verify(mSpyContext).startActivity(any()); + } + private InstantInsetLayerDrawable getMenuViewInsetLayer() { return (InstantInsetLayerDrawable) mMenuView.getBackground(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/utils/TestUtils.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/utils/TestUtils.java index 10c8caa4fd27..8399fa85bfb1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/utils/TestUtils.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/utils/TestUtils.java @@ -16,11 +16,27 @@ package com.android.systemui.accessibility.utils; +import static com.android.internal.accessibility.common.ShortcutConstants.SERVICES_SEPARATOR; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.content.ComponentName; import android.os.SystemClock; +import android.os.UserHandle; +import android.provider.Settings; + +import com.android.systemui.util.settings.SecureSettings; +import java.util.Set; +import java.util.StringJoiner; import java.util.function.BooleanSupplier; public class TestUtils { + private static final ComponentName TEST_COMPONENT_A = new ComponentName("pkg", "A"); + private static final ComponentName TEST_COMPONENT_B = new ComponentName("pkg", "B"); + public static final String[] TEST_BUTTON_TARGETS = { + TEST_COMPONENT_A.flattenToString(), TEST_COMPONENT_B.flattenToString()}; public static long DEFAULT_CONDITION_DURATION = 5_000; /** @@ -55,4 +71,28 @@ public class TestUtils { SystemClock.sleep(sleepMs); } } + + /** + * Returns a mock secure settings configured to return information needed for tests. + * Currently, this only includes button targets. + */ + public static SecureSettings mockSecureSettings() { + SecureSettings secureSettings = mock(SecureSettings.class); + + final String targets = getShortcutTargets( + Set.of(TEST_COMPONENT_A, TEST_COMPONENT_B)); + when(secureSettings.getStringForUser( + Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, + UserHandle.USER_CURRENT)).thenReturn(targets); + + return secureSettings; + } + + private static String getShortcutTargets(Set<ComponentName> components) { + final StringJoiner stringJoiner = new StringJoiner(String.valueOf(SERVICES_SEPARATOR)); + for (ComponentName target : components) { + stringJoiner.add(target.flattenToString()); + } + return stringJoiner.toString(); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepositoryImplTest.kt new file mode 100644 index 000000000000..ed80a869dda8 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepositoryImplTest.kt @@ -0,0 +1,98 @@ +package com.android.systemui.keyboard.stickykeys.data.repository + +import android.content.pm.UserInfo +import android.hardware.input.InputManager +import android.provider.Settings.Secure.ACCESSIBILITY_STICKY_KEYS +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.coroutines.collectValues +import com.android.systemui.keyboard.stickykeys.StickyKeysLogger +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.user.data.repository.fakeUserRepository +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.settings.FakeSettings +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(JUnit4::class) +class StickyKeysRepositoryImplTest : SysuiTestCase() { + + private val dispatcher = StandardTestDispatcher() + private val testScope = TestScope(dispatcher) + private val secureSettings = FakeSettings() + private val userRepository = Kosmos().fakeUserRepository + private lateinit var stickyKeysRepository: StickyKeysRepositoryImpl + + @Before + fun setup() { + stickyKeysRepository = StickyKeysRepositoryImpl( + mock<InputManager>(), + dispatcher, + secureSettings, + userRepository, + mock<StickyKeysLogger>() + ) + userRepository.setUserInfos(USER_INFOS) + setStickyKeySettingForUser(enabled = true, userInfo = SETTING_ENABLED_USER) + setStickyKeySettingForUser(enabled = false, userInfo = SETTING_DISABLED_USER) + } + + @Test + fun settingEnabledEmitsValueForCurrentUser() { + testScope.runTest { + userRepository.setSelectedUserInfo(SETTING_ENABLED_USER) + + val enabled by collectLastValue(stickyKeysRepository.settingEnabled) + + assertThat(enabled).isTrue() + } + } + + @Test + fun settingEnabledEmitsNewValueWhenSettingChanges() { + testScope.runTest { + userRepository.setSelectedUserInfo(SETTING_ENABLED_USER) + val enabled by collectValues(stickyKeysRepository.settingEnabled) + runCurrent() + + setStickyKeySettingForUser(enabled = false, userInfo = SETTING_ENABLED_USER) + + assertThat(enabled).containsExactly(true, false).inOrder() + } + } + + @Test + fun settingEnabledEmitsValueForNewUserWhenUserChanges() { + testScope.runTest { + userRepository.setSelectedUserInfo(SETTING_ENABLED_USER) + val enabled by collectLastValue(stickyKeysRepository.settingEnabled) + runCurrent() + + userRepository.setSelectedUserInfo(SETTING_DISABLED_USER) + + assertThat(enabled).isFalse() + } + } + + private fun setStickyKeySettingForUser(enabled: Boolean, userInfo: UserInfo) { + val newValue = if (enabled) "1" else "0" + secureSettings.putStringForUser(ACCESSIBILITY_STICKY_KEYS, newValue, userInfo.id) + } + + private companion object { + val SETTING_ENABLED_USER = UserInfo(/* id= */ 0, "user1", /* flags= */ 0) + val SETTING_DISABLED_USER = UserInfo(/* id= */ 1, "user2", /* flags= */ 0) + val USER_INFOS = listOf(SETTING_ENABLED_USER, SETTING_DISABLED_USER) + } +}
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt index 8a713688acf9..6eebb6d19e5e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt @@ -18,6 +18,7 @@ package com.android.systemui.keyboard.stickykeys.ui.viewmodel import android.hardware.input.InputManager import android.hardware.input.StickyModifierState +import android.provider.Settings.Secure.ACCESSIBILITY_STICKY_KEYS import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue @@ -31,8 +32,11 @@ import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.ALT_GR import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.CTRL import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.META import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.SHIFT +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.user.data.repository.fakeUserRepository import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.mock +import com.android.systemui.util.settings.FakeSettings import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.StandardTestDispatcher @@ -57,6 +61,8 @@ class StickyKeysIndicatorViewModelTest : SysuiTestCase() { private lateinit var viewModel: StickyKeysIndicatorViewModel private val inputManager = mock<InputManager>() private val keyboardRepository = FakeKeyboardRepository() + private val secureSettings = FakeSettings() + private val userRepository = Kosmos().fakeUserRepository private val captor = ArgumentCaptor.forClass(InputManager.StickyModifierStateListener::class.java) @@ -65,8 +71,11 @@ class StickyKeysIndicatorViewModelTest : SysuiTestCase() { val stickyKeysRepository = StickyKeysRepositoryImpl( inputManager, dispatcher, + secureSettings, + userRepository, mock<StickyKeysLogger>() ) + setStickyKeySetting(enabled = false) viewModel = StickyKeysIndicatorViewModel( stickyKeysRepository = stickyKeysRepository, @@ -76,13 +85,24 @@ class StickyKeysIndicatorViewModelTest : SysuiTestCase() { } @Test - fun startsListeningToStickyKeysOnlyWhenKeyboardIsConnected() { + fun doesntListenToStickyKeysOnlyWhenKeyboardIsConnected() { testScope.runTest { collectLastValue(viewModel.indicatorContent) + + keyboardRepository.setIsAnyKeyboardConnected(true) runCurrent() + verifyZeroInteractions(inputManager) + } + } + @Test + fun startsListeningToStickyKeysOnlyWhenKeyboardIsConnectedAndSettingIsOn() { + testScope.runTest { + collectLastValue(viewModel.indicatorContent) keyboardRepository.setIsAnyKeyboardConnected(true) + + setStickyKeySetting(enabled = true) runCurrent() verify(inputManager) @@ -93,11 +113,31 @@ class StickyKeysIndicatorViewModelTest : SysuiTestCase() { } } + private fun setStickyKeySetting(enabled: Boolean) { + val newValue = if (enabled) "1" else "0" + val defaultUser = userRepository.getSelectedUserInfo().id + secureSettings.putStringForUser(ACCESSIBILITY_STICKY_KEYS, newValue, defaultUser) + } + + @Test + fun stopsListeningToStickyKeysWhenStickyKeySettingsIsTurnedOff() { + testScope.runTest { + collectLastValue(viewModel.indicatorContent) + setStickyKeysActive() + runCurrent() + + setStickyKeySetting(enabled = false) + runCurrent() + + verify(inputManager).unregisterStickyModifierStateListener(any()) + } + } + @Test fun stopsListeningToStickyKeysWhenKeyboardDisconnects() { testScope.runTest { collectLastValue(viewModel.indicatorContent) - keyboardRepository.setIsAnyKeyboardConnected(true) + setStickyKeysActive() runCurrent() keyboardRepository.setIsAnyKeyboardConnected(false) @@ -111,7 +151,7 @@ class StickyKeysIndicatorViewModelTest : SysuiTestCase() { fun emitsStickyKeysListWhenStickyKeyIsPressed() { testScope.runTest { val stickyKeys by collectLastValue(viewModel.indicatorContent) - keyboardRepository.setIsAnyKeyboardConnected(true) + setStickyKeysActive() setStickyKeys(mapOf(ALT to false)) @@ -123,7 +163,7 @@ class StickyKeysIndicatorViewModelTest : SysuiTestCase() { fun emitsEmptyListWhenNoStickyKeysAreActive() { testScope.runTest { val stickyKeys by collectLastValue(viewModel.indicatorContent) - keyboardRepository.setIsAnyKeyboardConnected(true) + setStickyKeysActive() setStickyKeys(emptyMap()) @@ -135,7 +175,7 @@ class StickyKeysIndicatorViewModelTest : SysuiTestCase() { fun passesAllStickyKeysToDialog() { testScope.runTest { val stickyKeys by collectLastValue(viewModel.indicatorContent) - keyboardRepository.setIsAnyKeyboardConnected(true) + setStickyKeysActive() setStickyKeys(mapOf( ALT to false, @@ -154,7 +194,7 @@ class StickyKeysIndicatorViewModelTest : SysuiTestCase() { fun showsOnlyLockedStateIfKeyIsStickyAndLocked() { testScope.runTest { val stickyKeys by collectLastValue(viewModel.indicatorContent) - keyboardRepository.setIsAnyKeyboardConnected(true) + setStickyKeysActive() setStickyKeys(mapOf( ALT to false, @@ -168,7 +208,7 @@ class StickyKeysIndicatorViewModelTest : SysuiTestCase() { fun doesNotChangeOrderOfKeysIfTheyBecomeLocked() { testScope.runTest { val stickyKeys by collectLastValue(viewModel.indicatorContent) - keyboardRepository.setIsAnyKeyboardConnected(true) + setStickyKeysActive() setStickyKeys(mapOf( META to false, @@ -186,6 +226,11 @@ class StickyKeysIndicatorViewModelTest : SysuiTestCase() { } } + private fun setStickyKeysActive() { + keyboardRepository.setIsAnyKeyboardConnected(true) + setStickyKeySetting(enabled = true) + } + private fun TestScope.setStickyKeys(keys: Map<ModifierKey, Boolean>) { runCurrent() verify(inputManager).registerStickyModifierStateListener(any(), captor.capture()) diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/LeftRightArrowPressedListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/LeftRightArrowPressedListenerTest.kt index 60eb3aec190f..40eccad94340 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/LeftRightArrowPressedListenerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/LeftRightArrowPressedListenerTest.kt @@ -37,7 +37,7 @@ class LeftRightArrowPressedListenerTest : SysuiTestCase() { object : Consumer<Int> { var lastValue: Int? = null - override fun accept(keyCode: Int?) { + override fun accept(keyCode: Int) { lastValue = keyCode } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt index e9f21329bfbc..fdbba905d94c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt @@ -27,6 +27,7 @@ import androidx.test.filters.SmallTest import com.android.internal.app.AssistUtils import com.android.internal.logging.UiEventLogger import com.android.systemui.SysuiTestCase +import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dump.DumpManager import com.android.systemui.flags.FakeFeatureFlags @@ -109,6 +110,8 @@ class OverviewProxyServiceTest : SysuiTestCase() { @Mock private lateinit var unfoldTransitionProgressForwarder: Optional<UnfoldTransitionProgressForwarder> + @Mock + private lateinit var broadcastDispatcher: BroadcastDispatcher @Before fun setUp() { @@ -158,7 +161,8 @@ class OverviewProxyServiceTest : SysuiTestCase() { featureFlags, FakeSceneContainerFlags(), dumpManager, - unfoldTransitionProgressForwarder + unfoldTransitionProgressForwarder, + broadcastDispatcher ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java index b3a47d77a307..423cc8478dda 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java @@ -31,6 +31,7 @@ import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -1683,6 +1684,26 @@ public class ScrimControllerTest extends SysuiTestCase { } @Test + public void notificationBoundsTopGetsPassedToKeyguard() { + mScrimController.transitionTo(SHADE_LOCKED); + mScrimController.setQsPosition(1f, 0); + finishAnimationsImmediately(); + + mScrimController.setNotificationsBounds(0f, 100f, 0f, 0f); + verify(mKeyguardInteractor).setTopClippingBounds(eq(100)); + } + + @Test + public void notificationBoundsTopDoesNotGetPassedToKeyguardWhenNotifScrimIsNotVisible() { + mScrimController.setKeyguardOccluded(true); + mScrimController.transitionTo(ScrimState.KEYGUARD); + finishAnimationsImmediately(); + + mScrimController.setNotificationsBounds(0f, 100f, 0f, 0f); + verify(mKeyguardInteractor).setTopClippingBounds(eq(null)); + } + + @Test public void transitionToDreaming() { mScrimController.setRawPanelExpansionFraction(0f); mScrimController.setBouncerHiddenFraction(KeyguardBouncerConstants.EXPANSION_HIDDEN); diff --git a/services/autofill/java/com/android/server/autofill/RemoteFillService.java b/services/autofill/java/com/android/server/autofill/RemoteFillService.java index 5c93991bef8c..f914ed54fbb1 100644 --- a/services/autofill/java/com/android/server/autofill/RemoteFillService.java +++ b/services/autofill/java/com/android/server/autofill/RemoteFillService.java @@ -31,9 +31,12 @@ import android.os.Handler; import android.os.ICancellationSignal; import android.os.RemoteException; import android.service.autofill.AutofillService; +import android.service.autofill.ConvertCredentialRequest; +import android.service.autofill.ConvertCredentialResponse; import android.service.autofill.FillRequest; import android.service.autofill.FillResponse; import android.service.autofill.IAutoFillService; +import android.service.autofill.IConvertCredentialCallback; import android.service.autofill.IFillCallback; import android.service.autofill.ISaveCallback; import android.service.autofill.SaveRequest; @@ -69,6 +72,7 @@ final class RemoteFillService extends ServiceConnector.Impl<IAutoFillService> { private int mPendingFillRequestId = INVALID_REQUEST_ID; private AtomicReference<IFillCallback> mFillCallback; private AtomicReference<ISaveCallback> mSaveCallback; + private AtomicReference<IConvertCredentialCallback> mConvertCredentialCallback; private final ComponentName mComponentName; private final boolean mIsCredentialAutofillService; @@ -81,13 +85,20 @@ final class RemoteFillService extends ServiceConnector.Impl<IAutoFillService> { extends AbstractRemoteService.VultureCallback<RemoteFillService> { void onFillRequestSuccess(int requestId, @Nullable FillResponse response, @NonNull String servicePackageName, int requestFlags); + void onFillRequestFailure(int requestId, @Nullable CharSequence message); + void onFillRequestTimeout(int requestId); + void onSaveRequestSuccess(@NonNull String servicePackageName, @Nullable IntentSender intentSender); + // TODO(b/80093094): add timeout here too? void onSaveRequestFailure(@Nullable CharSequence message, @NonNull String servicePackageName); + + void onConvertCredentialRequestSuccess(@NonNull ConvertCredentialResponse + convertCredentialResponse); } RemoteFillService(Context context, ComponentName componentName, int userId, @@ -211,6 +222,32 @@ final class RemoteFillService extends ServiceConnector.Impl<IAutoFillService> { } } + static class IConvertCredentialCallbackDelegate extends IConvertCredentialCallback.Stub { + + private WeakReference<IConvertCredentialCallback> mCallbackWeakRef; + + IConvertCredentialCallbackDelegate(IConvertCredentialCallback callback) { + mCallbackWeakRef = new WeakReference(callback); + } + + @Override + public void onSuccess(ConvertCredentialResponse convertCredentialResponse) + throws RemoteException { + IConvertCredentialCallback callback = mCallbackWeakRef.get(); + if (callback != null) { + callback.onSuccess(convertCredentialResponse); + } + } + + @Override + public void onFailure(CharSequence message) throws RemoteException { + IConvertCredentialCallback callback = mCallbackWeakRef.get(); + if (callback != null) { + callback.onFailure(message); + } + } + } + /** * Wraps an {@link IFillCallback} object using weak reference. * @@ -237,6 +274,18 @@ final class RemoteFillService extends ServiceConnector.Impl<IAutoFillService> { return callback; } + /** + * Wraps an {@link IConvertCredentialCallback} object using weak reference + */ + private IConvertCredentialCallback maybeWrapWithWeakReference( + IConvertCredentialCallback callback) { + if (remoteFillServiceUseWeakReference()) { + mConvertCredentialCallback = new AtomicReference<>(callback); + return new IConvertCredentialCallbackDelegate(callback); + } + return callback; + } + public void onFillCredentialRequest(@NonNull FillRequest request, IAutoFillManagerClient autofillCallback) { if (sVerbose) { @@ -378,6 +427,52 @@ final class RemoteFillService extends ServiceConnector.Impl<IAutoFillService> { })); } + public void onConvertCredentialRequest( + @NonNull ConvertCredentialRequest convertCredentialRequest) { + if (sVerbose) Slog.v(TAG, "calling onConvertCredentialRequest()"); + CompletableFuture<ConvertCredentialResponse> + connectThenConvertCredentialRequest = postAsync( + remoteService -> { + if (sVerbose) { + Slog.v(TAG, "calling onConvertCredentialRequest()"); + } + CompletableFuture<ConvertCredentialResponse> + convertCredentialCompletableFuture = new CompletableFuture<>(); + remoteService.onConvertCredentialRequest(convertCredentialRequest, + maybeWrapWithWeakReference( + new IConvertCredentialCallback.Stub() { + @Override + public void onSuccess(ConvertCredentialResponse + convertCredentialResponse) { + convertCredentialCompletableFuture + .complete(convertCredentialResponse); + } + + @Override + public void onFailure(CharSequence message) { + String errorMessage = + message == null ? "" : + String.valueOf(message); + convertCredentialCompletableFuture + .completeExceptionally( + new RuntimeException(errorMessage)); + } + }) + ); + return convertCredentialCompletableFuture; + }).orTimeout(TIMEOUT_REMOTE_REQUEST_MILLIS, TimeUnit.MILLISECONDS); + + connectThenConvertCredentialRequest.whenComplete( + (res, err) -> Handler.getMain().post(() -> { + if (err == null) { + mCallbacks.onConvertCredentialRequestSuccess(res); + } else { + // TODO: Add a callback function to log this failure + Slog.e(TAG, "Error calling on convert credential request", err); + } + })); + } + public void onSaveRequest(@NonNull SaveRequest request) { postAsync(service -> { if (sVerbose) Slog.v(TAG, "calling onSaveRequest()"); diff --git a/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java b/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java index 123470304783..0af703e415ff 100644 --- a/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java +++ b/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java @@ -25,6 +25,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.IntentSender; import android.os.Bundle; +import android.service.autofill.ConvertCredentialResponse; import android.service.autofill.FillRequest; import android.service.autofill.FillResponse; import android.util.Slog; @@ -98,6 +99,12 @@ final class SecondaryProviderHandler implements RemoteFillService.FillServiceCal } + @Override + public void onConvertCredentialRequestSuccess(@NonNull ConvertCredentialResponse + convertCredentialResponse) { + + } + /** * Requests a new fill response. */ diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index f3b74ea00a58..049feeed2fa1 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -24,6 +24,7 @@ import static android.service.autofill.Dataset.PICK_REASON_PCC_DETECTION_PREFERR import static android.service.autofill.Dataset.PICK_REASON_PROVIDER_DETECTION_ONLY; import static android.service.autofill.Dataset.PICK_REASON_PROVIDER_DETECTION_PREFERRED_WITH_PCC; import static android.service.autofill.Dataset.PICK_REASON_UNKNOWN; +import static android.service.autofill.FillEventHistory.Event.UI_TYPE_CREDMAN_BOTTOM_SHEET; import static android.service.autofill.FillEventHistory.Event.UI_TYPE_DIALOG; import static android.service.autofill.FillEventHistory.Event.UI_TYPE_INLINE; import static android.service.autofill.FillEventHistory.Event.UI_TYPE_MENU; @@ -44,6 +45,7 @@ import static android.view.autofill.AutofillManager.ACTION_VIEW_ENTERED; import static android.view.autofill.AutofillManager.ACTION_VIEW_EXITED; import static android.view.autofill.AutofillManager.COMMIT_REASON_SESSION_DESTROYED; import static android.view.autofill.AutofillManager.COMMIT_REASON_UNKNOWN; +import static android.view.autofill.AutofillManager.EXTRA_AUTOFILL_REQUEST_ID; import static android.view.autofill.AutofillManager.FLAG_SMART_SUGGESTION_SYSTEM; import static android.view.autofill.AutofillManager.getSmartSuggestionModeToString; @@ -130,6 +132,7 @@ import android.service.assist.classification.FieldClassificationResponse; import android.service.autofill.AutofillFieldClassificationService.Scores; import android.service.autofill.AutofillService; import android.service.autofill.CompositeUserData; +import android.service.autofill.ConvertCredentialResponse; import android.service.autofill.Dataset; import android.service.autofill.Dataset.DatasetEligibleReason; import android.service.autofill.Field; @@ -2429,6 +2432,29 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState removeFromService(); } + // FillServiceCallbacks + @Override + public void onConvertCredentialRequestSuccess(@NonNull ConvertCredentialResponse + convertCredentialResponse) { + Dataset dataset = convertCredentialResponse.getDataset(); + Bundle clientState = convertCredentialResponse.getClientState(); + if (dataset != null) { + int requestId = -1; + if (clientState != null) { + requestId = clientState.getInt(EXTRA_AUTOFILL_REQUEST_ID); + } else { + Slog.e(TAG, "onConvertCredentialRequestSuccess(): client state is null, this " + + "would cause loss in logging."); + } + // TODO: Add autofill related logging; consider whether to log the index + fill(requestId, /* datasetIndex=*/ -1, dataset, UI_TYPE_CREDMAN_BOTTOM_SHEET); + } else { + // TODO: Add logging to log this error case + Slog.e(TAG, "onConvertCredentialRequestSuccess(): dataset inside response is " + + "null"); + } + } + /** * Gets the {@link FillContext} for a request. * diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java index 2e01ced2022b..5019428c5323 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java @@ -368,8 +368,10 @@ public class CompanionDeviceManagerService extends SystemService { if (blueToothDevices != null) { for (BluetoothDevice bluetoothDevice : blueToothDevices) { - final List<ParcelUuid> deviceUuids = bluetoothDevice.getUuids() == null - ? Collections.emptyList() : Arrays.asList(bluetoothDevice.getUuids()); + final ParcelUuid[] bluetoothDeviceUuids = bluetoothDevice.getUuids(); + + final List<ParcelUuid> deviceUuids = ArrayUtils.isEmpty(bluetoothDeviceUuids) + ? Collections.emptyList() : Arrays.asList(bluetoothDeviceUuids); for (AssociationInfo ai: mAssociationStore.getAssociationsByAddress(bluetoothDevice.getAddress())) { diff --git a/services/companion/java/com/android/server/companion/InactiveAssociationsRemovalService.java b/services/companion/java/com/android/server/companion/InactiveAssociationsRemovalService.java index 3482863032f9..aac628cab403 100644 --- a/services/companion/java/com/android/server/companion/InactiveAssociationsRemovalService.java +++ b/services/companion/java/com/android/server/companion/InactiveAssociationsRemovalService.java @@ -36,7 +36,8 @@ import com.android.server.LocalServices; * will be killed if association/role are revoked. */ public class InactiveAssociationsRemovalService extends JobService { - private static final int JOB_ID = InactiveAssociationsRemovalService.class.hashCode(); + private static final String JOB_NAMESPACE = "companion"; + private static final int JOB_ID = 1; private static final long ONE_DAY_INTERVAL = DAYS.toMillis(1); @Override @@ -61,7 +62,8 @@ public class InactiveAssociationsRemovalService extends JobService { static void schedule(Context context) { Slog.i(TAG, "Scheduling the Association Removal job"); - final JobScheduler jobScheduler = context.getSystemService(JobScheduler.class); + final JobScheduler jobScheduler = + context.getSystemService(JobScheduler.class).forNamespace(JOB_NAMESPACE); final JobInfo job = new JobInfo.Builder(JOB_ID, new ComponentName(context, InactiveAssociationsRemovalService.class)) .setRequiresCharging(true) @@ -71,4 +73,3 @@ public class InactiveAssociationsRemovalService extends JobService { jobScheduler.schedule(job); } } - diff --git a/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java b/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java index 7eca1193ca12..c514f3ef29d0 100644 --- a/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java +++ b/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java @@ -38,6 +38,7 @@ import android.util.Slog; import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; +import com.android.internal.util.ArrayUtils; import com.android.server.companion.AssociationStore; import com.android.server.companion.ObservableUuid; import com.android.server.companion.ObservableUuidStore; @@ -172,8 +173,10 @@ public class BluetoothCompanionDeviceConnectionListener mAssociationStore.getAssociationsByAddress(device.getAddress()); final List<ObservableUuid> observableUuids = mObservableUuidStore.getObservableUuidsForUser(userId); - final List<ParcelUuid> deviceUuids = device.getUuids() == null - ? Collections.emptyList() : Arrays.asList(device.getUuids()); + final ParcelUuid[] bluetoothDeviceUuids = device.getUuids(); + + final List<ParcelUuid> deviceUuids = ArrayUtils.isEmpty(bluetoothDeviceUuids) + ? Collections.emptyList() : Arrays.asList(bluetoothDeviceUuids); if (DEBUG) { Log.d(TAG, "onDevice_ConnectivityChanged() " + btDeviceToString(device) diff --git a/services/core/Android.bp b/services/core/Android.bp index a54a48a7e84e..8e35b7455d02 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -139,6 +139,7 @@ java_library_static { libs: [ "services.net", + "android.frameworks.location.altitude-V2-java", "android.hardware.common-V2-java", "android.hardware.light-V2.0-java", "android.hardware.gnss-V2-java", @@ -160,7 +161,6 @@ java_library_static { ], static_libs: [ - "android.frameworks.location.altitude-V2-java", // AIDL "android.frameworks.vibrator-V1-java", // AIDL "android.hardware.authsecret-V1.0-java", "android.hardware.authsecret-V1-java", diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index afb8345249b1..adc0255743c2 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -181,7 +181,6 @@ import android.content.pm.ParceledListSlice; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.content.pm.ServiceInfo.ForegroundServiceType; -import android.os.Binder; import android.os.Build; import android.os.Build.VERSION_CODES; import android.os.Bundle; @@ -306,6 +305,57 @@ public final class ActiveServices { @interface FgsStopReason {} /** + * The policy to be applied to the service bindings; this one means it follows the legacy + * behavior. + */ + static final int SERVICE_BIND_OOMADJ_POLICY_LEGACY = 0; + + /** + * The policy to be applied to the service bindings; this one means we'll skip + * updating the target process's oom adj score / process state for its {@link Service#onCreate}. + */ + static final int SERVICE_BIND_OOMADJ_POLICY_SKIP_OOM_UPDATE_ON_CREATE = 1; + + /** + * The policy to be applied to the service bindings; this one means we'll skip + * updating the target process's oom adj score / process state for its {@link Service#onBind}. + */ + static final int SERVICE_BIND_OOMADJ_POLICY_SKIP_OOM_UPDATE_ON_BIND = 1 << 1; + + /** + * The policy to be applied to the service bindings; this one means we'll skip + * updating the target process's oom adj score / process state on setting up the service + * connection between the client and the service host process. + */ + static final int SERVICE_BIND_OOMADJ_POLICY_SKIP_OOM_UPDATE_ON_CONNECT = 1 << 2; + /** + * The policy to be applied to the service bindings; this one means the caller + * will be frozen upon calling the bindService APIs. + */ + static final int SERVICE_BIND_OOMADJ_POLICY_FREEZE_CALLER = 1 << 3; + + @IntDef(flag = true, prefix = { "SERVICE_BIND_OOMADJ_POLICY_" }, value = { + SERVICE_BIND_OOMADJ_POLICY_LEGACY, + SERVICE_BIND_OOMADJ_POLICY_SKIP_OOM_UPDATE_ON_CREATE, + SERVICE_BIND_OOMADJ_POLICY_SKIP_OOM_UPDATE_ON_BIND, + SERVICE_BIND_OOMADJ_POLICY_SKIP_OOM_UPDATE_ON_CONNECT, + SERVICE_BIND_OOMADJ_POLICY_FREEZE_CALLER, + }) + @Retention(RetentionPolicy.SOURCE) + @interface ServiceBindingOomAdjPolicy {} + + @ServiceBindingOomAdjPolicy + static final int DEFAULT_SERVICE_NO_BUMP_BIND_POLICY_FLAG = + SERVICE_BIND_OOMADJ_POLICY_SKIP_OOM_UPDATE_ON_CREATE + | SERVICE_BIND_OOMADJ_POLICY_SKIP_OOM_UPDATE_ON_BIND + | SERVICE_BIND_OOMADJ_POLICY_SKIP_OOM_UPDATE_ON_CONNECT; + + @ServiceBindingOomAdjPolicy + static final int DEFAULT_SERVICE_CACHED_BIND_POLICY_FLAG = + SERVICE_BIND_OOMADJ_POLICY_SKIP_OOM_UPDATE_ON_CREATE + | SERVICE_BIND_OOMADJ_POLICY_SKIP_OOM_UPDATE_ON_BIND; + + /** * Disables foreground service background starts from BOOT_COMPLETED broadcasts for all types * except: * <ul> @@ -1244,7 +1294,7 @@ public final class ActiveServices { @Override public void onResult(Bundle result) { synchronized (mAm) { - final long identity = Binder.clearCallingIdentity(); + final long identity = mAm.mInjector.clearCallingIdentity(); try { if (!mPendingServices.contains(r)) { return; @@ -1263,7 +1313,8 @@ public final class ActiveServices { false /* whileRestarting */, false /* permissionsReviewRequired */, false /* packageFrozen */, - true /* enqueueOomAdj */); + true /* enqueueOomAdj */, + SERVICE_BIND_OOMADJ_POLICY_LEGACY); } catch (RemoteException e) { /* ignore - local call */ } finally { @@ -1275,7 +1326,7 @@ public final class ActiveServices { unbindServiceLocked(connection); } } finally { - Binder.restoreCallingIdentity(identity); + mAm.mInjector.restoreCallingIdentity(identity); } } } @@ -1353,7 +1404,8 @@ public final class ActiveServices { false /* whileRestarting */, false /* permissionsReviewRequired */, false /* packageFrozen */, - true /* enqueueOomAdj */); + true /* enqueueOomAdj */, + SERVICE_BIND_OOMADJ_POLICY_LEGACY); } catch (TransactionTooLargeException e) { /* ignore - local call */ } finally { @@ -1431,7 +1483,8 @@ public final class ActiveServices { false /* whileRestarting */, false /* permissionsReviewRequired */, false /* packageFrozen */, - true /* enqueueOomAdj */); + true /* enqueueOomAdj */, + SERVICE_BIND_OOMADJ_POLICY_LEGACY); /* Will be a no-op if nothing pending */ mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_SERVICE); if (error != null) { @@ -1550,22 +1603,22 @@ public final class ActiveServices { if (caller != null && callerApp == null) { throw new SecurityException( "Unable to find app for caller " + caller - + " (pid=" + Binder.getCallingPid() + + " (pid=" + mAm.mInjector.getCallingPid() + ") when stopping service " + service); } // If this service is active, make sure it is stopped. ServiceLookupResult r = retrieveServiceLocked(service, instanceName, isSdkSandboxService, sdkSandboxClientAppUid, sdkSandboxClientAppPackage, resolvedType, null, - Binder.getCallingPid(), Binder.getCallingUid(), userId, false, false, false, false, - null, false, false); + mAm.mInjector.getCallingPid(), mAm.mInjector.getCallingUid(), + userId, false, false, false, false, null, false, false); if (r != null) { if (r.record != null) { - final long origId = Binder.clearCallingIdentity(); + final long origId = mAm.mInjector.clearCallingIdentity(); try { stopServiceLocked(r.record, false); } finally { - Binder.restoreCallingIdentity(origId); + mAm.mInjector.restoreCallingIdentity(origId); } return 1; } @@ -1649,7 +1702,7 @@ public final class ActiveServices { IBinder peekServiceLocked(Intent service, String resolvedType, String callingPackage) { ServiceLookupResult r = retrieveServiceLocked(service, null, resolvedType, callingPackage, - Binder.getCallingPid(), Binder.getCallingUid(), + mAm.mInjector.getCallingPid(), mAm.mInjector.getCallingUid(), UserHandle.getCallingUserId(), false, false, false, false, false, false); IBinder ret = null; @@ -1658,8 +1711,8 @@ public final class ActiveServices { if (r.record == null) { throw new SecurityException( "Permission Denial: Accessing service" - + " from pid=" + Binder.getCallingPid() - + ", uid=" + Binder.getCallingUid() + + " from pid=" + mAm.mInjector.getCallingPid() + + ", uid=" + mAm.mInjector.getCallingUid() + " requires " + r.permission); } IntentBindRecord ib = r.record.bindings.get(r.record.intent); @@ -1719,9 +1772,9 @@ public final class ActiveServices { } } r.callStart = false; - final long origId = Binder.clearCallingIdentity(); + final long origId = mAm.mInjector.clearCallingIdentity(); bringDownServiceIfNeededLocked(r, false, false, false, "stopServiceToken"); - Binder.restoreCallingIdentity(origId); + mAm.mInjector.restoreCallingIdentity(origId); return true; } return false; @@ -1734,14 +1787,14 @@ public final class ActiveServices { public void setServiceForegroundLocked(ComponentName className, IBinder token, int id, Notification notification, int flags, int foregroundServiceType) { final int userId = UserHandle.getCallingUserId(); - final long origId = Binder.clearCallingIdentity(); + final long origId = mAm.mInjector.clearCallingIdentity(); try { ServiceRecord r = findServiceLocked(className, token, userId); if (r != null) { setServiceForegroundInnerLocked(r, id, notification, flags, foregroundServiceType); } } finally { - Binder.restoreCallingIdentity(origId); + mAm.mInjector.restoreCallingIdentity(origId); } } @@ -1753,7 +1806,7 @@ public final class ActiveServices { */ public int getForegroundServiceTypeLocked(ComponentName className, IBinder token) { final int userId = UserHandle.getCallingUserId(); - final long origId = Binder.clearCallingIdentity(); + final long origId = mAm.mInjector.clearCallingIdentity(); int ret = ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE; try { ServiceRecord r = findServiceLocked(className, token, userId); @@ -1761,7 +1814,7 @@ public final class ActiveServices { ret = r.foregroundServiceType; } } finally { - Binder.restoreCallingIdentity(origId); + mAm.mInjector.restoreCallingIdentity(origId); } return ret; } @@ -3483,7 +3536,7 @@ public final class ActiveServices { boolean shouldServiceTimeOutLocked(ComponentName className, IBinder token) { final int userId = UserHandle.getCallingUserId(); - final long ident = Binder.clearCallingIdentity(); + final long ident = mAm.mInjector.clearCallingIdentity(); try { ServiceRecord sr = findServiceLocked(className, token, userId); if (sr == null) { @@ -3492,7 +3545,7 @@ public final class ActiveServices { final long nowUptime = SystemClock.uptimeMillis(); return sr.shouldTriggerShortFgsTimeout(nowUptime); } finally { - Binder.restoreCallingIdentity(ident); + mAm.mInjector.restoreCallingIdentity(ident); } } @@ -3636,8 +3689,8 @@ public final class ActiveServices { if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "bindService: " + service + " type=" + resolvedType + " conn=" + connection.asBinder() + " flags=0x" + Long.toHexString(flags)); - final int callingPid = Binder.getCallingPid(); - final int callingUid = Binder.getCallingUid(); + final int callingPid = mAm.mInjector.getCallingPid(); + final int callingUid = mAm.mInjector.getCallingUid(); final ProcessRecord callerApp = mAm.getRecordForAppLOSP(caller); if (callerApp == null) { throw new SecurityException( @@ -3778,7 +3831,7 @@ public final class ActiveServices { && !requestStartTargetPermissionsReviewIfNeededLocked(s, callingPackage, null, callingUid, service, callerFg, userId, true, connection); - final long origId = Binder.clearCallingIdentity(); + final long origId = mAm.mInjector.clearCallingIdentity(); try { if (unscheduleServiceRestartLocked(s, callerApp.info.uid, false)) { @@ -3859,12 +3912,34 @@ public final class ActiveServices { } clist.add(c); + final boolean isolated = (s.serviceInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) != 0; + final ProcessRecord hostApp = isolated + ? null + : mAm.getProcessRecordLocked(s.processName, s.appInfo.uid); + final int serviceBindingOomAdjPolicy = hostApp != null + ? getServiceBindingOomAdjPolicyForAddLocked(b.client, hostApp, c) + : SERVICE_BIND_OOMADJ_POLICY_LEGACY; + + final boolean shouldFreezeCaller = !packageFrozen && !permissionsReviewRequired + && (serviceBindingOomAdjPolicy & SERVICE_BIND_OOMADJ_POLICY_FREEZE_CALLER) != 0 + && callerApp.isFreezable(); + + if (shouldFreezeCaller) { + // Freeze the caller immediately, so the following #onBind/#onConnected will be + // queued up in the app side as they're one way calls. And we'll also hold off + // the service timeout timer until the process is unfrozen. + mAm.mOomAdjuster.updateAppFreezeStateLSP(callerApp, OOM_ADJ_REASON_BIND_SERVICE, + true); + } + boolean needOomAdj = false; if (c.hasFlag(Context.BIND_AUTO_CREATE)) { s.lastActivity = SystemClock.uptimeMillis(); - needOomAdj = true; + needOomAdj = (serviceBindingOomAdjPolicy + & SERVICE_BIND_OOMADJ_POLICY_SKIP_OOM_UPDATE_ON_CREATE) == 0; if (bringUpServiceLocked(s, service.getFlags(), callerFg, false, - permissionsReviewRequired, packageFrozen, true) != null) { + permissionsReviewRequired, packageFrozen, true, serviceBindingOomAdjPolicy) + != null) { mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_BIND_SERVICE); return 0; } @@ -3886,8 +3961,11 @@ public final class ActiveServices { || (callerApp.mState.getCurProcState() <= PROCESS_STATE_TOP && c.hasFlag(Context.BIND_TREAT_LIKE_ACTIVITY)), b.client); - needOomAdj = true; - mAm.enqueueOomAdjTargetLocked(s.app); + if ((serviceBindingOomAdjPolicy + & SERVICE_BIND_OOMADJ_POLICY_SKIP_OOM_UPDATE_ON_CONNECT) == 0) { + needOomAdj = true; + mAm.enqueueOomAdjTargetLocked(s.app); + } } if (needOomAdj) { mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_BIND_SERVICE); @@ -3937,10 +4015,12 @@ public final class ActiveServices { // and the service had previously asked to be told when // rebound, then do so. if (b.intent.apps.size() == 1 && b.intent.doRebind) { - requestServiceBindingLocked(s, b.intent, callerFg, true); + requestServiceBindingLocked(s, b.intent, callerFg, true, + serviceBindingOomAdjPolicy); } } else if (!b.intent.requested) { - requestServiceBindingLocked(s, b.intent, callerFg, false); + requestServiceBindingLocked(s, b.intent, callerFg, false, + serviceBindingOomAdjPolicy); } maybeLogBindCrossProfileService(userId, callingPackage, callerApp.info.uid); @@ -3948,7 +4028,7 @@ public final class ActiveServices { getServiceMapLocked(s.userId).ensureNotStartingBackgroundLocked(s); } finally { - Binder.restoreCallingIdentity(origId); + mAm.mInjector.restoreCallingIdentity(origId); } notifyBindingServiceEventLocked(callerApp, callingPackage); @@ -3982,7 +4062,7 @@ public final class ActiveServices { } void publishServiceLocked(ServiceRecord r, Intent intent, IBinder service) { - final long origId = Binder.clearCallingIdentity(); + final long origId = mAm.mInjector.clearCallingIdentity(); try { if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "PUBLISHING " + r + " " + intent + ": " + service); @@ -4025,10 +4105,11 @@ public final class ActiveServices { } serviceDoneExecutingLocked(r, mDestroyingServices.contains(r), false, false, - OOM_ADJ_REASON_EXECUTING_SERVICE); + !Flags.serviceBindingOomAdjPolicy() || b == null || !b.mSkippedOomAdj + ? OOM_ADJ_REASON_EXECUTING_SERVICE : OOM_ADJ_REASON_NONE); } } finally { - Binder.restoreCallingIdentity(origId); + mAm.mInjector.restoreCallingIdentity(origId); } } @@ -4078,8 +4159,8 @@ public final class ActiveServices { return false; } - final int callingPid = Binder.getCallingPid(); - final long origId = Binder.clearCallingIdentity(); + final int callingPid = mAm.mInjector.getCallingPid(); + final long origId = mAm.mInjector.clearCallingIdentity(); try { if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) { String info; @@ -4092,9 +4173,10 @@ public final class ActiveServices { Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "unbindServiceLocked: " + info); } + boolean needOomAdj = false; while (clist.size() > 0) { ConnectionRecord r = clist.get(0); - removeConnectionLocked(r, null, null, true); + int serviceBindingOomAdjPolicy = removeConnectionLocked(r, null, null, true); if (clist.size() > 0 && clist.get(0) == r) { // In case it didn't get removed above, do it now. Slog.wtf(TAG, "Connection " + r + " not removed for binder " + binder); @@ -4112,22 +4194,28 @@ public final class ActiveServices { psr.setTreatLikeActivity(true); mAm.updateLruProcessLocked(app, true, null); } - mAm.enqueueOomAdjTargetLocked(app); + // If the bindee is more important than the binder, we may skip the OomAdjuster. + if (serviceBindingOomAdjPolicy == SERVICE_BIND_OOMADJ_POLICY_LEGACY) { + mAm.enqueueOomAdjTargetLocked(app); + needOomAdj = true; + } } } - mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_UNBIND_SERVICE); + if (needOomAdj) { + mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_UNBIND_SERVICE); + } } finally { Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); - Binder.restoreCallingIdentity(origId); + mAm.mInjector.restoreCallingIdentity(origId); } return true; } void unbindFinishedLocked(ServiceRecord r, Intent intent, boolean doRebind) { - final long origId = Binder.clearCallingIdentity(); + final long origId = mAm.mInjector.clearCallingIdentity(); try { if (r != null) { Intent.FilterComparison filter @@ -4138,6 +4226,7 @@ public final class ActiveServices { + (b != null ? b.apps.size() : 0)); boolean inDestroying = mDestroyingServices.contains(r); + boolean skipOomAdj = false; if (b != null) { if (b.apps.size() > 0 && !inDestroying) { // Applications have already bound since the last @@ -4152,7 +4241,8 @@ public final class ActiveServices { } } try { - requestServiceBindingLocked(r, b, inFg, true); + requestServiceBindingLocked(r, b, inFg, true, + SERVICE_BIND_OOMADJ_POLICY_LEGACY); } catch (TransactionTooLargeException e) { // Don't pass this back to ActivityThread, it's unrelated. } @@ -4161,13 +4251,14 @@ public final class ActiveServices { // a new client. b.doRebind = true; } + skipOomAdj = Flags.serviceBindingOomAdjPolicy() && b.mSkippedOomAdj; } serviceDoneExecutingLocked(r, inDestroying, false, false, - OOM_ADJ_REASON_UNBIND_SERVICE); + skipOomAdj ? OOM_ADJ_REASON_NONE : OOM_ADJ_REASON_UNBIND_SERVICE); } } finally { - Binder.restoreCallingIdentity(origId); + mAm.mInjector.restoreCallingIdentity(origId); } } @@ -4503,7 +4594,7 @@ public final class ActiveServices { userId = 0; smap = getServiceMapLocked(0); // Bypass INTERACT_ACROSS_USERS permission check - final long token = Binder.clearCallingIdentity(); + final long token = mAm.mInjector.clearCallingIdentity(); try { ResolveInfo rInfoForUserId0 = mAm.getPackageManagerInternal().resolveService(service, @@ -4516,7 +4607,7 @@ public final class ActiveServices { } sInfo = rInfoForUserId0.serviceInfo; } finally { - Binder.restoreCallingIdentity(token); + mAm.mInjector.restoreCallingIdentity(token); } } sInfo = new ServiceInfo(sInfo); @@ -4645,7 +4736,8 @@ public final class ActiveServices { * @return {@code true} if it performed oomAdjUpdate. */ private boolean bumpServiceExecutingLocked( - ServiceRecord r, boolean fg, String why, @OomAdjReason int oomAdjReason) { + ServiceRecord r, boolean fg, String why, @OomAdjReason int oomAdjReason, + boolean skipTimeoutIfPossible) { if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, ">>> EXECUTING " + why + " of " + r + " in app " + r.app); else if (DEBUG_SERVICE_EXECUTING) Slog.v(TAG_SERVICE_EXECUTING, ">>> EXECUTING " @@ -4669,6 +4761,10 @@ public final class ActiveServices { timeoutNeeded = false; } + // If the process is frozen or to be frozen, and we want to skip the timeout, skip it. + final boolean shouldSkipTimeout = skipTimeoutIfPossible && r.app != null + && (r.app.mOptRecord.isPendingFreeze() || r.app.mOptRecord.isFrozen()); + ProcessServiceRecord psr; if (r.executeNesting == 0) { r.executeFg = fg; @@ -4684,7 +4780,11 @@ public final class ActiveServices { psr.startExecutingService(r); psr.setExecServicesFg(psr.shouldExecServicesFg() || fg); if (timeoutNeeded && psr.numberOfExecutingServices() == 1) { - scheduleServiceTimeoutLocked(r.app); + if (!shouldSkipTimeout) { + scheduleServiceTimeoutLocked(r.app); + } else { + r.app.mServices.noteScheduleServiceTimeoutPending(true); + } } } } else if (r.app != null && fg) { @@ -4692,7 +4792,11 @@ public final class ActiveServices { if (!psr.shouldExecServicesFg()) { psr.setExecServicesFg(true); if (timeoutNeeded) { - scheduleServiceTimeoutLocked(r.app); + if (!shouldSkipTimeout) { + scheduleServiceTimeoutLocked(r.app); + } else { + r.app.mServices.noteScheduleServiceTimeoutPending(true); + } } } } @@ -4712,16 +4816,22 @@ public final class ActiveServices { } private final boolean requestServiceBindingLocked(ServiceRecord r, IntentBindRecord i, - boolean execInFg, boolean rebind) throws TransactionTooLargeException { + boolean execInFg, boolean rebind, + @ServiceBindingOomAdjPolicy int serviceBindingOomAdjPolicy) + throws TransactionTooLargeException { if (r.app == null || r.app.getThread() == null) { // If service is not currently running, can't yet bind. return false; } if (DEBUG_SERVICE) Slog.d(TAG_SERVICE, "requestBind " + i + ": requested=" + i.requested + " rebind=" + rebind); + final boolean skipOomAdj = (serviceBindingOomAdjPolicy + & SERVICE_BIND_OOMADJ_POLICY_SKIP_OOM_UPDATE_ON_BIND) != 0; if ((!i.requested || rebind) && i.apps.size() > 0) { try { - bumpServiceExecutingLocked(r, execInFg, "bind", OOM_ADJ_REASON_BIND_SERVICE); + i.mSkippedOomAdj = !bumpServiceExecutingLocked(r, execInFg, "bind", + skipOomAdj ? OOM_ADJ_REASON_NONE : OOM_ADJ_REASON_BIND_SERVICE, + skipOomAdj /* skipTimeoutIfPossible */); if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) { Trace.instant(Trace.TRACE_TAG_ACTIVITY_MANAGER, "requestServiceBinding=" + i.intent.getIntent() + ". bindSeq=" + mBindServiceSeqCounter); @@ -4738,14 +4848,14 @@ public final class ActiveServices { if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Crashed while binding " + r, e); final boolean inDestroying = mDestroyingServices.contains(r); serviceDoneExecutingLocked(r, inDestroying, inDestroying, false, - OOM_ADJ_REASON_UNBIND_SERVICE); + skipOomAdj ? OOM_ADJ_REASON_NONE : OOM_ADJ_REASON_UNBIND_SERVICE); throw e; } catch (RemoteException e) { if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Crashed while binding " + r); // Keep the executeNesting count accurate. final boolean inDestroying = mDestroyingServices.contains(r); serviceDoneExecutingLocked(r, inDestroying, inDestroying, false, - OOM_ADJ_REASON_UNBIND_SERVICE); + skipOomAdj ? OOM_ADJ_REASON_NONE : OOM_ADJ_REASON_UNBIND_SERVICE); return false; } } @@ -5117,7 +5227,7 @@ public final class ActiveServices { } try { bringUpServiceLocked(r, r.intent.getIntent().getFlags(), r.createdFromFg, true, false, - false, true); + false, true, SERVICE_BIND_OOMADJ_POLICY_LEGACY); } catch (TransactionTooLargeException e) { // Ignore, it's been logged and nothing upstack cares. } finally { @@ -5217,7 +5327,7 @@ public final class ActiveServices { private String bringUpServiceLocked(ServiceRecord r, int intentFlags, boolean execInFg, boolean whileRestarting, boolean permissionsReviewRequired, boolean packageFrozen, - boolean enqueueOomAdj) + boolean enqueueOomAdj, @ServiceBindingOomAdjPolicy int serviceBindingOomAdjPolicy) throws TransactionTooLargeException { try { if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) { @@ -5225,7 +5335,8 @@ public final class ActiveServices { "bringUpServiceLocked: " + r.shortInstanceName); } return bringUpServiceInnerLocked(r, intentFlags, execInFg, whileRestarting, - permissionsReviewRequired, packageFrozen, enqueueOomAdj); + permissionsReviewRequired, packageFrozen, enqueueOomAdj, + serviceBindingOomAdjPolicy); } finally { Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); } @@ -5233,7 +5344,7 @@ public final class ActiveServices { private String bringUpServiceInnerLocked(ServiceRecord r, int intentFlags, boolean execInFg, boolean whileRestarting, boolean permissionsReviewRequired, boolean packageFrozen, - boolean enqueueOomAdj) + boolean enqueueOomAdj, @ServiceBindingOomAdjPolicy int serviceBindingOomAdjPolicy) throws TransactionTooLargeException { if (r.app != null && r.app.isThreadReady()) { sendServiceArgsLocked(r, execInFg, false); @@ -5317,7 +5428,7 @@ public final class ActiveServices { app.addPackage(r.appInfo.packageName, r.appInfo.longVersionCode, mAm.mProcessStats); realStartServiceLocked(r, app, thread, pid, uidRecord, execInFg, - enqueueOomAdj); + enqueueOomAdj, serviceBindingOomAdjPolicy); return null; } catch (TransactionTooLargeException e) { throw e; @@ -5347,7 +5458,7 @@ public final class ActiveServices { "realStartServiceLocked: " + r.shortInstanceName); } realStartServiceLocked(r, app, thread, pid, uidRecord, execInFg, - enqueueOomAdj); + enqueueOomAdj, SERVICE_BIND_OOMADJ_POLICY_LEGACY); return null; } catch (TransactionTooLargeException e) { throw e; @@ -5452,16 +5563,61 @@ public final class ActiveServices { return HostingRecord.TRIGGER_TYPE_UNKNOWN; } - private final void requestServiceBindingsLocked(ServiceRecord r, boolean execInFg) + private void requestServiceBindingsLocked(ServiceRecord r, boolean execInFg, + @ServiceBindingOomAdjPolicy int serviceBindingOomAdjPolicy) throws TransactionTooLargeException { for (int i=r.bindings.size()-1; i>=0; i--) { IntentBindRecord ibr = r.bindings.valueAt(i); - if (!requestServiceBindingLocked(r, ibr, execInFg, false)) { + if (!requestServiceBindingLocked(r, ibr, execInFg, false, serviceBindingOomAdjPolicy)) { break; } } } + @ServiceBindingOomAdjPolicy + private int getServiceBindingOomAdjPolicyForAddLocked(ProcessRecord clientApp, + ProcessRecord hostApp, ConnectionRecord cr) { + @ServiceBindingOomAdjPolicy int policy = SERVICE_BIND_OOMADJ_POLICY_LEGACY; + if (Flags.serviceBindingOomAdjPolicy() && clientApp != null && hostApp != null) { + if (clientApp == hostApp) { + policy = DEFAULT_SERVICE_NO_BUMP_BIND_POLICY_FLAG; + } else if (clientApp.isCached()) { + policy = DEFAULT_SERVICE_NO_BUMP_BIND_POLICY_FLAG; + if (clientApp.isFreezable()) { + policy |= SERVICE_BIND_OOMADJ_POLICY_FREEZE_CALLER; + } + } + if ((policy & SERVICE_BIND_OOMADJ_POLICY_SKIP_OOM_UPDATE_ON_CONNECT) == 0) { + // Binding between two different processes. + // Check if the caller has a better process state, oom adj score, + // or if the caller has more capabilities. + if (!mAm.mOomAdjuster.evaluateServiceConnectionAdd(clientApp, hostApp, cr)) { + // Running an oom adjuster won't be give the host app a better score, skip it. + policy = DEFAULT_SERVICE_NO_BUMP_BIND_POLICY_FLAG; + } + } + } + return policy; + } + + @ServiceBindingOomAdjPolicy + private int getServiceBindingOomAdjPolicyForRemovalLocked(ProcessRecord clientApp, + ProcessRecord hostApp, ConnectionRecord cr) { + @ServiceBindingOomAdjPolicy int policy = SERVICE_BIND_OOMADJ_POLICY_LEGACY; + if (Flags.serviceBindingOomAdjPolicy() && clientApp != null && hostApp != null + && cr != null) { + if (clientApp == hostApp) { + policy = DEFAULT_SERVICE_NO_BUMP_BIND_POLICY_FLAG; + } else { + if (!mAm.mOomAdjuster.evaluateServiceConnectionRemoval(clientApp, hostApp, cr)) { + // Running an oom adjuster won't be give the host app a better score, skip it. + policy = DEFAULT_SERVICE_NO_BUMP_BIND_POLICY_FLAG; + } + } + } + return policy; + } + /** * Note the name of this method should not be confused with the started services concept. * The "start" here means bring up the instance in the client, and this method is called @@ -5469,7 +5625,8 @@ public final class ActiveServices { */ private void realStartServiceLocked(ServiceRecord r, ProcessRecord app, IApplicationThread thread, int pid, UidRecord uidRecord, boolean execInFg, - boolean enqueueOomAdj) throws RemoteException { + boolean enqueueOomAdj, @ServiceBindingOomAdjPolicy int serviceBindingOomAdjPolicy) + throws RemoteException { if (thread == null) { throw new RemoteException(); } @@ -5478,17 +5635,28 @@ public final class ActiveServices { + ", ProcessRecord.uid = " + app.uid); r.setProcess(app, thread, pid, uidRecord); r.restartTime = r.lastActivity = SystemClock.uptimeMillis(); - + final boolean skipOomAdj = (serviceBindingOomAdjPolicy + & SERVICE_BIND_OOMADJ_POLICY_SKIP_OOM_UPDATE_ON_CREATE) != 0; final ProcessServiceRecord psr = app.mServices; final boolean newService = psr.startService(r); bumpServiceExecutingLocked(r, execInFg, "create", - OOM_ADJ_REASON_NONE /* use "none" to avoid extra oom adj */); + OOM_ADJ_REASON_NONE /* use "none" to avoid extra oom adj */, + skipOomAdj /* skipTimeoutIfPossible */); mAm.updateLruProcessLocked(app, false, null); updateServiceForegroundLocked(psr, /* oomAdj= */ false); - // Force an immediate oomAdjUpdate, so the client app could be in the correct process state - // before doing any service related transactions - mAm.enqueueOomAdjTargetLocked(app); - mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_SERVICE); + // Skip the oom adj update if it's a self-binding, the Service#onCreate() will be running + // at its current adj score. + if (!skipOomAdj) { + // Force an immediate oomAdjUpdate, so the host app could be in the correct + // process state before doing any service related transactions + mAm.enqueueOomAdjTargetLocked(app); + mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_SERVICE); + } else { + // Since we skipped the oom adj update, the Service#onCreate() might be running in + // the cached state, if the service process drops into the cached state after the call. + // But there is still a grace period before freezing it, so we should be fine + // in terms of not getting an ANR. + } boolean created = false; try { @@ -5523,7 +5691,7 @@ public final class ActiveServices { // Keep the executeNesting count accurate. final boolean inDestroying = mDestroyingServices.contains(r); serviceDoneExecutingLocked(r, inDestroying, inDestroying, false, - OOM_ADJ_REASON_STOP_SERVICE); + skipOomAdj ? OOM_ADJ_REASON_NONE : OOM_ADJ_REASON_STOP_SERVICE); // Cleanup. if (newService) { @@ -5542,7 +5710,7 @@ public final class ActiveServices { psr.mAllowlistManager = true; } - requestServiceBindingsLocked(r, execInFg); + requestServiceBindingsLocked(r, execInFg, serviceBindingOomAdjPolicy); updateServiceClientActivitiesLocked(psr, null, true); @@ -5610,7 +5778,8 @@ public final class ActiveServices { UserHandle.getAppId(r.appInfo.uid) ); bumpServiceExecutingLocked(r, execInFg, "start", - OOM_ADJ_REASON_NONE /* use "none" to avoid extra oom adj */); + OOM_ADJ_REASON_NONE /* use "none" to avoid extra oom adj */, + false /* skipTimeoutIfPossible */); if (r.fgRequired && !r.fgWaiting) { if (!r.isForeground) { if (DEBUG_BACKGROUND_CHECK) { @@ -5753,7 +5922,8 @@ public final class ActiveServices { if (ibr.hasBound) { try { oomAdjusted |= bumpServiceExecutingLocked(r, false, "bring down unbind", - OOM_ADJ_REASON_UNBIND_SERVICE); + OOM_ADJ_REASON_UNBIND_SERVICE, + false /* skipTimeoutIfPossible */); ibr.hasBound = false; ibr.requested = false; r.app.getThread().scheduleUnbindService(r, @@ -5909,7 +6079,8 @@ public final class ActiveServices { } else { try { oomAdjusted |= bumpServiceExecutingLocked(r, false, "destroy", - oomAdjusted ? 0 : OOM_ADJ_REASON_STOP_SERVICE); + oomAdjusted ? 0 : OOM_ADJ_REASON_STOP_SERVICE, + false /* skipTimeoutIfPossible */); mDestroyingServices.add(r); r.destroying = true; r.app.getThread().scheduleStopService(r); @@ -5992,11 +6163,17 @@ public final class ActiveServices { } } - void removeConnectionLocked(ConnectionRecord c, ProcessRecord skipApp, + /** + * @return The ServiceBindingOomAdjPolicy used in this removal. + */ + @ServiceBindingOomAdjPolicy + int removeConnectionLocked(ConnectionRecord c, ProcessRecord skipApp, ActivityServiceConnectionsHolder skipAct, boolean enqueueOomAdj) { IBinder binder = c.conn.asBinder(); AppBindRecord b = c.binding; ServiceRecord s = b.service; + @ServiceBindingOomAdjPolicy int serviceBindingOomAdjPolicy = + SERVICE_BIND_OOMADJ_POLICY_LEGACY; ArrayList<ConnectionRecord> clist = s.getConnections().get(binder); if (clist != null) { clist.remove(c); @@ -6055,8 +6232,14 @@ public final class ActiveServices { + ": shouldUnbind=" + b.intent.hasBound); if (s.app != null && s.app.isThreadReady() && b.intent.apps.size() == 0 && b.intent.hasBound) { + serviceBindingOomAdjPolicy = getServiceBindingOomAdjPolicyForRemovalLocked(b.client, + s.app, c); + final boolean skipOomAdj = (serviceBindingOomAdjPolicy + & SERVICE_BIND_OOMADJ_POLICY_SKIP_OOM_UPDATE_ON_CONNECT) != 0; try { - bumpServiceExecutingLocked(s, false, "unbind", OOM_ADJ_REASON_UNBIND_SERVICE); + b.intent.mSkippedOomAdj = !bumpServiceExecutingLocked(s, false, "unbind", + skipOomAdj ? OOM_ADJ_REASON_NONE : OOM_ADJ_REASON_UNBIND_SERVICE, + skipOomAdj /* skipTimeoutIfPossible */); if (b.client != s.app && c.notHasFlag(Context.BIND_WAIVE_PRIORITY) && s.app.mState.getSetProcState() <= PROCESS_STATE_HEAVY_WEIGHT) { // If this service's process is not already in the cached list, @@ -6096,12 +6279,14 @@ public final class ActiveServices { "removeConnection"); } } + return serviceBindingOomAdjPolicy; } void serviceDoneExecutingLocked(ServiceRecord r, int type, int startId, int res, - boolean enqueueOomAdj) { + boolean enqueueOomAdj, Intent intent) { boolean inDestroying = mDestroyingServices.contains(r); if (r != null) { + boolean skipOomAdj = false; if (type == ActivityThread.SERVICE_DONE_EXECUTING_START) { // This is a call from a service start... take care of // book-keeping. @@ -6177,14 +6362,19 @@ public final class ActiveServices { // Fake it to keep from ANR due to orphaned entry. r.executeNesting = 1; } + } else if (type == ActivityThread.SERVICE_DONE_EXECUTING_REBIND + || type == ActivityThread.SERVICE_DONE_EXECUTING_UNBIND) { + final Intent.FilterComparison filter = new Intent.FilterComparison(intent); + final IntentBindRecord b = r.bindings.get(filter); + skipOomAdj = Flags.serviceBindingOomAdjPolicy() && b != null && b.mSkippedOomAdj; } - final long origId = Binder.clearCallingIdentity(); + final long origId = mAm.mInjector.clearCallingIdentity(); serviceDoneExecutingLocked(r, inDestroying, inDestroying, enqueueOomAdj, - OOM_ADJ_REASON_EXECUTING_SERVICE); - Binder.restoreCallingIdentity(origId); + skipOomAdj ? OOM_ADJ_REASON_NONE : OOM_ADJ_REASON_EXECUTING_SERVICE); + mAm.mInjector.restoreCallingIdentity(origId); } else { Slog.w(TAG, "Done executing unknown service from pid " - + Binder.getCallingPid()); + + mAm.mInjector.getCallingPid()); } } @@ -6236,10 +6426,17 @@ public final class ActiveServices { mDestroyingServices.remove(r); r.bindings.clear(); } - if (enqueueOomAdj) { - mAm.enqueueOomAdjTargetLocked(r.app); + boolean oomAdjusted = false; + if (oomAdjReason != OOM_ADJ_REASON_NONE) { + if (enqueueOomAdj) { + mAm.enqueueOomAdjTargetLocked(r.app); + } else { + mAm.updateOomAdjLocked(r.app, oomAdjReason); + } + oomAdjusted = true; } else { - mAm.updateOomAdjLocked(r.app, oomAdjReason); + // Skip oom adj if it wasn't bumped during the bumpServiceExecutingLocked() + oomAdjusted = false; } } r.executeFg = false; @@ -6296,7 +6493,7 @@ public final class ActiveServices { "realStartServiceLocked: " + sr.shortInstanceName); } realStartServiceLocked(sr, proc, thread, pid, uidRecord, sr.createdFromFg, - true); + true, SERVICE_BIND_OOMADJ_POLICY_LEGACY); } finally { Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); } @@ -6786,6 +6983,7 @@ public final class ActiveServices { } psr.stopAllExecutingServices(); + psr.noteScheduleServiceTimeoutPending(false); } ActivityManager.RunningServiceInfo makeRunningServiceInfoLocked(ServiceRecord r) { @@ -6836,7 +7034,7 @@ public final class ActiveServices { ArrayList<ActivityManager.RunningServiceInfo> res = new ArrayList<ActivityManager.RunningServiceInfo>(); - final long ident = Binder.clearCallingIdentity(); + final long ident = mAm.mInjector.clearCallingIdentity(); try { if (canInteractAcrossUsers) { int[] users = mAm.mUserController.getUsers(); @@ -6878,14 +7076,14 @@ public final class ActiveServices { } } } finally { - Binder.restoreCallingIdentity(ident); + mAm.mInjector.restoreCallingIdentity(ident); } return res; } public PendingIntent getRunningServiceControlPanelLocked(ComponentName name) { - int userId = UserHandle.getUserId(Binder.getCallingUid()); + int userId = UserHandle.getUserId(mAm.mInjector.getCallingUid()); ServiceRecord r = getServiceByNameLocked(name, userId); if (r != null) { ArrayMap<IBinder, ArrayList<ConnectionRecord>> connections = r.getConnections(); @@ -7077,6 +7275,7 @@ public final class ActiveServices { final long delay = proc.mServices.shouldExecServicesFg() ? mAm.mConstants.SERVICE_TIMEOUT : mAm.mConstants.SERVICE_BACKGROUND_TIMEOUT; mActiveServiceAnrTimer.start(proc, delay); + proc.mServices.noteScheduleServiceTimeoutPending(false); } void scheduleServiceForegroundTransitionTimeoutLocked(ServiceRecord r) { diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index ca04e41769ee..74902f76b617 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -1751,7 +1751,7 @@ public class ActivityManagerService extends IActivityManager.Stub @GuardedBy("mProcLock") private long mLastBinderHeavyHitterAutoSamplerStart = 0L; - final AppProfiler mAppProfiler; + AppProfiler mAppProfiler; private static final int INDEX_NATIVE_PSS = 0; private static final int INDEX_NATIVE_SWAP_PSS = 1; @@ -2496,7 +2496,7 @@ public class ActivityManagerService extends IActivityManager.Stub mInjector = injector; mContext = mInjector.getContext(); mUiContext = null; - mAppErrors = null; + mAppErrors = injector.getAppErrors(); mPackageWatchdog = null; mAppOpsService = mInjector.getAppOpsService(null /* recentAccessesFile */, null /* storageFile */, null /* handler */); @@ -2514,7 +2514,7 @@ public class ActivityManagerService extends IActivityManager.Stub ? new OomAdjusterModernImpl(this, mProcessList, activeUids, handlerThread) : new OomAdjuster(this, mProcessList, activeUids, handlerThread); - mIntentFirewall = null; + mIntentFirewall = injector.getIntentFirewall(); mProcessStats = new ProcessStatsService(this, mContext.getCacheDir()); mCpHelper = new ContentProviderHelper(this, false); mServices = mInjector.getActiveServices(this); @@ -13889,13 +13889,15 @@ public class ActivityManagerService extends IActivityManager.Stub } } - public void serviceDoneExecuting(IBinder token, int type, int startId, int res) { + @Override + public void serviceDoneExecuting(IBinder token, int type, int startId, int res, Intent intent) { synchronized(this) { if (!(token instanceof ServiceRecord)) { Slog.e(TAG, "serviceDoneExecuting: Invalid service token=" + token); throw new IllegalArgumentException("Invalid service token"); } - mServices.serviceDoneExecutingLocked((ServiceRecord) token, type, startId, res, false); + mServices.serviceDoneExecutingLocked((ServiceRecord) token, type, startId, res, false, + intent); } } @@ -20236,6 +20238,36 @@ public class ActivityManagerService extends IActivityManager.Stub } return broadcastQueues; } + + /** @see Binder#getCallingUid */ + public int getCallingUid() { + return Binder.getCallingUid(); + } + + /** @see Binder#getCallingPid */ + public int getCallingPid() { + return Binder.getCallingUid(); + } + + /** @see Binder#clearCallingIdentity */ + public long clearCallingIdentity() { + return Binder.clearCallingIdentity(); + } + + /** @see Binder#clearCallingIdentity */ + public void restoreCallingIdentity(long ident) { + Binder.restoreCallingIdentity(ident); + } + + /** @return the default instance of AppErrors */ + public AppErrors getAppErrors() { + return null; + } + + /** @return the default instance of intent firewall */ + public IntentFirewall getIntentFirewall() { + return null; + } } @Override diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java index 626b70b51093..d92a24b82764 100644 --- a/services/core/java/com/android/server/am/CachedAppOptimizer.java +++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java @@ -1419,6 +1419,11 @@ public final class CachedAppOptimizer { } @GuardedBy({"mAm", "mProcLock"}) + void freezeAppAsyncAtEarliestLSP(ProcessRecord app) { + freezeAppAsyncLSP(app, updateEarliestFreezableTime(app, 0)); + } + + @GuardedBy({"mAm", "mProcLock"}) void freezeAppAsyncInternalLSP(ProcessRecord app, @UptimeMillisLong long delayMillis, boolean force) { final ProcessCachedOptimizerRecord opt = app.mOptRecord; @@ -1714,6 +1719,14 @@ public final class CachedAppOptimizer { compactApp(frozenProc, CompactProfile.FULL, CompactSource.APP, false); } } + frozenProc.onProcessFrozen(); + } + + /** + * Callback received when an attempt to freeze a process is cancelled (failed). + */ + void onProcessFrozenCancelled(ProcessRecord app) { + app.onProcessFrozenCancelled(); } /** @@ -2203,6 +2216,8 @@ public final class CachedAppOptimizer { onProcessFrozen(proc); removeMessages(DEADLOCK_WATCHDOG_MSG); sendEmptyMessageDelayed(DEADLOCK_WATCHDOG_MSG, FREEZE_DEADLOCK_TIMEOUT_MS); + } else { + onProcessFrozenCancelled(proc); } } break; case REPORT_UNFREEZE_MSG: { @@ -2460,7 +2475,7 @@ public final class CachedAppOptimizer { pr = mAm.mPidsSelfLocked.get(blocked); } if (pr != null - && pr.mState.getCurAdj() < ProcessList.CACHED_APP_MIN_ADJ) { + && pr.mState.getCurAdj() < ProcessList.FREEZER_CUTOFF_ADJ) { Slog.d(TAG_AM, app.processName + " (" + pid + ") blocks " + pr.processName + " (" + blocked + ")"); // Found at least one blocked non-cached process diff --git a/services/core/java/com/android/server/am/ContentProviderHelper.java b/services/core/java/com/android/server/am/ContentProviderHelper.java index 30f21a65b5b1..cb7898d8b862 100644 --- a/services/core/java/com/android/server/am/ContentProviderHelper.java +++ b/services/core/java/com/android/server/am/ContentProviderHelper.java @@ -35,6 +35,7 @@ import static com.android.internal.util.FrameworkStatsLog.PROVIDER_ACQUISITION_E import static com.android.internal.util.FrameworkStatsLog.PROVIDER_ACQUISITION_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_MU; import static com.android.server.am.ActivityManagerService.TAG_MU; +import static com.android.server.am.Flags.serviceBindingOomAdjPolicy; import android.annotation.Nullable; import android.annotation.UserIdInt; @@ -319,8 +320,10 @@ public class ContentProviderHelper { checkTime(startTime, "getContentProviderImpl: before updateOomAdj"); final int verifiedAdj = cpr.proc.mState.getVerifiedAdj(); - boolean success = mService.updateOomAdjLocked(cpr.proc, - OOM_ADJ_REASON_GET_PROVIDER); + boolean success = !serviceBindingOomAdjPolicy() + || mService.mOomAdjuster.evaluateProviderConnectionAdd(r, cpr.proc) + ? mService.updateOomAdjLocked(cpr.proc, OOM_ADJ_REASON_GET_PROVIDER) + : true; // XXX things have changed so updateOomAdjLocked doesn't actually tell us // if the process has been successfully adjusted. So to reduce races with // it, we will check whether the process still exists. Note that this doesn't @@ -1529,7 +1532,9 @@ public class ContentProviderHelper { } mService.stopAssociationLocked(conn.client.uid, conn.client.processName, cpr.uid, cpr.appInfo.longVersionCode, cpr.name, cpr.info.processName); - if (updateOomAdj) { + if (updateOomAdj && (!serviceBindingOomAdjPolicy() + || mService.mOomAdjuster.evaluateProviderConnectionRemoval(conn.client, + cpr.proc))) { mService.updateOomAdjLocked(conn.provider.proc, OOM_ADJ_REASON_REMOVE_PROVIDER); } } diff --git a/services/core/java/com/android/server/am/IntentBindRecord.java b/services/core/java/com/android/server/am/IntentBindRecord.java index abc7ab110f89..db47e3f26a6d 100644 --- a/services/core/java/com/android/server/am/IntentBindRecord.java +++ b/services/core/java/com/android/server/am/IntentBindRecord.java @@ -46,9 +46,17 @@ final class IntentBindRecord { boolean hasBound; /** Set when the service's onUnbind() has asked to be told about new clients. */ boolean doRebind; - + String stringName; // caching of toString - + + /** + * Mark if we've skipped oom adj update before calling into the {@link Service#onBind()} + * or {@link Service#onUnbind()}. + * + * <p>If it's true, we'll skip the oom adj update too during the serviceDoneExecuting. + */ + boolean mSkippedOomAdj; + void dump(PrintWriter pw, String prefix) { pw.print(prefix); pw.print("service="); pw.println(service); dumpInService(pw, prefix); diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java index ef7a0e058db0..862542e3d3d0 100644 --- a/services/core/java/com/android/server/am/OomAdjuster.java +++ b/services/core/java/com/android/server/am/OomAdjuster.java @@ -103,6 +103,7 @@ import static com.android.server.am.ProcessList.CACHED_APP_IMPORTANCE_LEVELS; import static com.android.server.am.ProcessList.CACHED_APP_MAX_ADJ; import static com.android.server.am.ProcessList.CACHED_APP_MIN_ADJ; import static com.android.server.am.ProcessList.FOREGROUND_APP_ADJ; +import static com.android.server.am.ProcessList.FREEZER_CUTOFF_ADJ; import static com.android.server.am.ProcessList.HEAVY_WEIGHT_APP_ADJ; import static com.android.server.am.ProcessList.HOME_APP_ADJ; import static com.android.server.am.ProcessList.INVALID_ADJ; @@ -2309,7 +2310,7 @@ public class OomAdjuster { } computeServiceHostOomAdjLSP(cr, app, cr.binding.client, now, topApp, doingAll, - cycleReEval, computeClients, oomAdjReason, cachedAdj, true); + cycleReEval, computeClients, oomAdjReason, cachedAdj, true, false); adj = state.getCurRawAdj(); procState = state.getCurRawProcState(); @@ -2341,7 +2342,7 @@ public class OomAdjuster { ContentProviderConnection conn = cpr.connections.get(i); ProcessRecord client = conn.client; computeProviderHostOomAdjLSP(conn, app, client, now, topApp, doingAll, - cycleReEval, computeClients, oomAdjReason, cachedAdj, true); + cycleReEval, computeClients, oomAdjReason, cachedAdj, true, false); adj = state.getCurRawAdj(); procState = state.getCurRawProcState(); @@ -2558,17 +2559,18 @@ public class OomAdjuster { } @GuardedBy({"mService", "mProcLock"}) - protected void computeServiceHostOomAdjLSP(ConnectionRecord cr, ProcessRecord app, + protected boolean computeServiceHostOomAdjLSP(ConnectionRecord cr, ProcessRecord app, ProcessRecord client, long now, ProcessRecord topApp, boolean doingAll, boolean cycleReEval, boolean computeClients, int oomAdjReason, int cachedAdj, - boolean couldRecurse) { + boolean couldRecurse, boolean dryRun) { if (app.isPendingFinishAttach()) { // We've set the attaching process state in the computeInitialOomAdjLSP. Skip it here. - return; + return false; } final ProcessStateRecord state = app.mState; ProcessStateRecord cstate = client.mState; + boolean updated = false; if (couldRecurse) { if (app.isSdkSandbox && cr.binding.attributedClient != null) { @@ -2599,19 +2601,25 @@ public class OomAdjuster { final int prevRawAdj = adj; final int prevProcState = procState; final int prevSchedGroup = schedGroup; + final int prevCapability = capability; final int appUid = app.info.uid; final int logUid = mService.mCurOomAdjUid; - state.setCurBoundByNonBgRestrictedApp(state.isCurBoundByNonBgRestrictedApp() - || cstate.isCurBoundByNonBgRestrictedApp() - || clientProcState <= PROCESS_STATE_BOUND_TOP - || (clientProcState == PROCESS_STATE_FOREGROUND_SERVICE - && !cstate.isBackgroundRestricted())); + if (!dryRun) { + state.setCurBoundByNonBgRestrictedApp(state.isCurBoundByNonBgRestrictedApp() + || cstate.isCurBoundByNonBgRestrictedApp() + || clientProcState <= PROCESS_STATE_BOUND_TOP + || (clientProcState == PROCESS_STATE_FOREGROUND_SERVICE + && !cstate.isBackgroundRestricted())); + } if (client.mOptRecord.shouldNotFreeze()) { // Propagate the shouldNotFreeze flag down the bindings. - app.mOptRecord.setShouldNotFreeze(true); + if (app.mOptRecord.setShouldNotFreeze(true, dryRun)) { + // Bail out early, as we only care about the return value for a dryrun. + return true; + } } boolean trackedProcState = false; @@ -2653,7 +2661,7 @@ public class OomAdjuster { } if (couldRecurse && shouldSkipDueToCycle(app, cstate, procState, adj, cycleReEval)) { - return; + return false; } if (clientProcState >= PROCESS_STATE_CACHED_ACTIVITY) { @@ -2666,7 +2674,10 @@ public class OomAdjuster { if (cr.hasFlag(Context.BIND_ALLOW_OOM_MANAGEMENT)) { // Similar to BIND_WAIVE_PRIORITY, keep it unfrozen. if (clientAdj < CACHED_APP_MIN_ADJ) { - app.mOptRecord.setShouldNotFreeze(true); + if (app.mOptRecord.setShouldNotFreeze(true, dryRun)) { + // Bail out early, as we only care about the return value for a dryrun. + return true; + } } // Not doing bind OOM management, so treat // this guy more like a started service. @@ -2678,7 +2689,10 @@ public class OomAdjuster { if (adj > clientAdj) { adjType = "cch-bound-ui-services"; } - state.setCached(false); + if (state.setCached(false, dryRun)) { + // Bail out early, as we only care about the return value for a dryrun. + return true; + } clientAdj = adj; clientProcState = procState; } else { @@ -2721,7 +2735,9 @@ public class OomAdjuster { newAdj = PERSISTENT_SERVICE_ADJ; schedGroup = SCHED_GROUP_DEFAULT; procState = ActivityManager.PROCESS_STATE_PERSISTENT; - cr.trackProcState(procState, mAdjSeq); + if (!dryRun) { + cr.trackProcState(procState, mAdjSeq); + } trackedProcState = true; } } else if (cr.hasFlag(Context.BIND_NOT_PERCEPTIBLE) @@ -2762,11 +2778,16 @@ public class OomAdjuster { } } if (!cstate.isCached()) { - state.setCached(false); + if (state.setCached(false, dryRun)) { + // Bail out early, as we only care about the return value for a dryrun. + return true; + } } if (adj > newAdj) { adj = newAdj; - state.setCurRawAdj(adj); + if (state.setCurRawAdj(adj, dryRun)) { + // Bail out early, as we only care about the return value for a dryrun. + } adjType = "service"; } } @@ -2833,25 +2854,35 @@ public class OomAdjuster { if (cr.hasFlag(Context.BIND_SCHEDULE_LIKE_TOP_APP) && clientIsSystem) { schedGroup = SCHED_GROUP_TOP_APP; - state.setScheduleLikeTopApp(true); + if (dryRun) { + if (prevSchedGroup < schedGroup) { + // Bail out early, as we only care about the return value for a dryrun. + return true; + } + } else { + state.setScheduleLikeTopApp(true); + } } - if (!trackedProcState) { + if (!trackedProcState && !dryRun) { cr.trackProcState(clientProcState, mAdjSeq); } if (procState > clientProcState) { procState = clientProcState; - state.setCurRawProcState(procState); + if (state.setCurRawProcState(procState, dryRun)) { + // Bail out early, as we only care about the return value for a dryrun. + return true; + } if (adjType == null) { adjType = "service"; } } if (procState < PROCESS_STATE_IMPORTANT_BACKGROUND - && cr.hasFlag(Context.BIND_SHOWING_UI)) { + && cr.hasFlag(Context.BIND_SHOWING_UI) && !dryRun) { app.setPendingUiClean(true); } - if (adjType != null) { + if (adjType != null && !dryRun) { state.setAdjType(adjType); state.setAdjTypeCode(ActivityManager.RunningAppProcessInfo .REASON_SERVICE_IN_USE); @@ -2876,11 +2907,16 @@ public class OomAdjuster { // bound by an unfrozen app via a WPRI binding has to remain // unfrozen. if (clientAdj < CACHED_APP_MIN_ADJ) { - app.mOptRecord.setShouldNotFreeze(true); + if (app.mOptRecord.setShouldNotFreeze(true, dryRun)) { + // Bail out early, as we only care about the return value for a dryrun. + return true; + } } } if (cr.hasFlag(Context.BIND_TREAT_LIKE_ACTIVITY)) { - app.mServices.setTreatLikeActivity(true); + if (!dryRun) { + app.mServices.setTreatLikeActivity(true); + } if (clientProcState <= PROCESS_STATE_CACHED_ACTIVITY && procState > PROCESS_STATE_CACHED_ACTIVITY) { // This is a cached process, but somebody wants us to treat it like it has @@ -2894,7 +2930,9 @@ public class OomAdjuster { if (a != null && adj > FOREGROUND_APP_ADJ && a.isActivityVisible()) { adj = FOREGROUND_APP_ADJ; - state.setCurRawAdj(adj); + if (state.setCurRawAdj(adj, dryRun)) { + return true; + } if (cr.notHasFlag(Context.BIND_NOT_FOREGROUND)) { if (cr.hasFlag(Context.BIND_IMPORTANT)) { schedGroup = SCHED_GROUP_TOP_APP_BOUND; @@ -2902,16 +2940,18 @@ public class OomAdjuster { schedGroup = SCHED_GROUP_DEFAULT; } } - state.setCached(false); - state.setAdjType("service"); - state.setAdjTypeCode(ActivityManager.RunningAppProcessInfo - .REASON_SERVICE_IN_USE); - state.setAdjSource(a); - state.setAdjSourceProcState(procState); - state.setAdjTarget(cr.binding.service.instanceName); - if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { - reportOomAdjMessageLocked(TAG_OOM_ADJ, - "Raise to service w/activity: " + app); + if (!dryRun) { + state.setCached(false); + state.setAdjType("service"); + state.setAdjTypeCode(ActivityManager.RunningAppProcessInfo + .REASON_SERVICE_IN_USE); + state.setAdjSource(a); + state.setAdjSourceProcState(procState); + state.setAdjTarget(cr.binding.service.instanceName); + if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { + reportOomAdjMessageLocked(TAG_OOM_ADJ, + "Raise to service w/activity: " + app); + } } } } @@ -2922,7 +2962,15 @@ public class OomAdjuster { if (procState > PROCESS_STATE_BOUND_FOREGROUND_SERVICE) { capability &= ~PROCESS_CAPABILITY_BFSL; } + if (!updated) { + updated = adj < prevRawAdj || procState < prevProcState || schedGroup > prevSchedGroup + || (capability != prevCapability + && (capability & prevCapability) == prevCapability); + } + if (dryRun) { + return updated; + } if (adj < prevRawAdj) { schedGroup = setIntermediateAdjLSP(app, adj, prevRawAdj, schedGroup); } @@ -2935,15 +2983,16 @@ public class OomAdjuster { state.setCurCapability(capability); state.setEmpty(false); + return updated; } - protected void computeProviderHostOomAdjLSP(ContentProviderConnection conn, ProcessRecord app, - ProcessRecord client, long now, ProcessRecord topApp, boolean doingAll, - boolean cycleReEval, boolean computeClients, int oomAdjReason, int cachedAdj, - boolean couldRecurse) { + protected boolean computeProviderHostOomAdjLSP(ContentProviderConnection conn, + ProcessRecord app, ProcessRecord client, long now, ProcessRecord topApp, + boolean doingAll, boolean cycleReEval, boolean computeClients, int oomAdjReason, + int cachedAdj, boolean couldRecurse, boolean dryRun) { if (app.isPendingFinishAttach()) { // We've set the attaching process state in the computeInitialOomAdjLSP. Skip it here. - return; + return false; } final ProcessStateRecord state = app.mState; @@ -2951,7 +3000,7 @@ public class OomAdjuster { if (client == app) { // Being our own client is not interesting. - return; + return false; } if (couldRecurse) { if (computeClients) { @@ -2964,7 +3013,7 @@ public class OomAdjuster { if (shouldSkipDueToCycle(app, cstate, state.getCurRawProcState(), state.getCurRawAdj(), cycleReEval)) { - return; + return false; } } @@ -2979,6 +3028,7 @@ public class OomAdjuster { final int prevRawAdj = adj; final int prevProcState = procState; final int prevSchedGroup = schedGroup; + final int prevCapability = capability; final int appUid = app.info.uid; final int logUid = mService.mCurOomAdjUid; @@ -2995,14 +3045,19 @@ public class OomAdjuster { } if (client.mOptRecord.shouldNotFreeze()) { // Propagate the shouldNotFreeze flag down the bindings. - app.mOptRecord.setShouldNotFreeze(true); + if (app.mOptRecord.setShouldNotFreeze(true, dryRun)) { + // Bail out early, as we only care about the return value for a dryrun. + return true; + } } - state.setCurBoundByNonBgRestrictedApp(state.isCurBoundByNonBgRestrictedApp() - || cstate.isCurBoundByNonBgRestrictedApp() - || clientProcState <= PROCESS_STATE_BOUND_TOP - || (clientProcState == PROCESS_STATE_FOREGROUND_SERVICE - && !cstate.isBackgroundRestricted())); + if (!dryRun) { + state.setCurBoundByNonBgRestrictedApp(state.isCurBoundByNonBgRestrictedApp() + || cstate.isCurBoundByNonBgRestrictedApp() + || clientProcState <= PROCESS_STATE_BOUND_TOP + || (clientProcState == PROCESS_STATE_FOREGROUND_SERVICE + && !cstate.isBackgroundRestricted())); + } String adjType = null; if (adj > clientAdj) { @@ -3011,10 +3066,16 @@ public class OomAdjuster { adjType = "cch-ui-provider"; } else { adj = Math.max(clientAdj, FOREGROUND_APP_ADJ); - state.setCurRawAdj(adj); + if (state.setCurRawAdj(adj, dryRun)) { + // Bail out early, as we only care about the return value for a dryrun. + return true; + } adjType = "provider"; } - state.setCached(state.isCached() & cstate.isCached()); + if (state.setCached(state.isCached() & cstate.isCached(), dryRun)) { + // Bail out early, as we only care about the return value for a dryrun. + return true; + } } if (clientProcState <= PROCESS_STATE_FOREGROUND_SERVICE) { @@ -3028,15 +3089,20 @@ public class OomAdjuster { } } - conn.trackProcState(clientProcState, mAdjSeq); + if (!dryRun) { + conn.trackProcState(clientProcState, mAdjSeq); + } if (procState > clientProcState) { procState = clientProcState; - state.setCurRawProcState(procState); + if (state.setCurRawProcState(procState, dryRun)) { + // Bail out early, as we only care about the return value for a dryrun. + return true; + } } if (cstate.getCurrentSchedulingGroup() > schedGroup) { schedGroup = SCHED_GROUP_DEFAULT; } - if (adjType != null) { + if (adjType != null && !dryRun) { state.setAdjType(adjType); state.setAdjTypeCode(ActivityManager.RunningAppProcessInfo .REASON_PROVIDER_IN_USE); @@ -3056,6 +3122,12 @@ public class OomAdjuster { capability &= ~PROCESS_CAPABILITY_BFSL; } + if (dryRun && (adj < prevRawAdj || procState < prevProcState || schedGroup > prevSchedGroup + || (capability != prevCapability + && (capability & prevCapability) == prevCapability))) { + return true; + } + if (adj < prevRawAdj) { schedGroup = setIntermediateAdjLSP(app, adj, prevRawAdj, schedGroup); } @@ -3068,6 +3140,7 @@ public class OomAdjuster { state.setCurCapability(capability); state.setEmpty(false); + return false; } protected int getDefaultCapability(ProcessRecord app, int procState) { @@ -3342,7 +3415,7 @@ public class OomAdjuster { changes |= ActivityManagerService.ProcessChangeItem.CHANGE_ACTIVITIES; } - updateAppFreezeStateLSP(app, oomAdjReson); + updateAppFreezeStateLSP(app, oomAdjReson, false); if (state.getReportedProcState() != state.getCurProcState()) { state.setReportedProcState(state.getCurProcState()); @@ -3727,7 +3800,8 @@ public class OomAdjuster { } @GuardedBy({"mService", "mProcLock"}) - private void updateAppFreezeStateLSP(ProcessRecord app, @OomAdjReason int oomAdjReason) { + void updateAppFreezeStateLSP(ProcessRecord app, @OomAdjReason int oomAdjReason, + boolean immediate) { if (!mCachedAppOptimizer.useFreezer()) { return; } @@ -3746,10 +3820,14 @@ public class OomAdjuster { final ProcessStateRecord state = app.mState; // Use current adjustment when freezing, set adjustment when unfreezing. - if (state.getCurAdj() >= CACHED_APP_MIN_ADJ && !opt.isFrozen() + if (state.getCurAdj() >= FREEZER_CUTOFF_ADJ && !opt.isFrozen() && !opt.shouldNotFreeze()) { - mCachedAppOptimizer.freezeAppAsyncLSP(app); - } else if (state.getSetAdj() < CACHED_APP_MIN_ADJ) { + if (!immediate) { + mCachedAppOptimizer.freezeAppAsyncLSP(app); + } else { + mCachedAppOptimizer.freezeAppAsyncAtEarliestLSP(app); + } + } else if (state.getSetAdj() < FREEZER_CUTOFF_ADJ) { mCachedAppOptimizer.unfreezeAppLSP(app, CachedAppOptimizer.getUnfreezeReasonCodeFromOomAdjReason(oomAdjReason)); } @@ -3826,4 +3904,89 @@ public class OomAdjuster { // The caller will set the initial value in this implementation. return app.mState.isCurBoundByNonBgRestrictedApp(); } + + /** + * Evaluate the service connection, return {@code true} if the client will change the state + * of the service host process by the given connection. + */ + @GuardedBy("mService") + boolean evaluateServiceConnectionAdd(ProcessRecord client, ProcessRecord app, + ConnectionRecord cr) { + if (evaluateConnectionPrelude(client, app)) { + return true; + } + if (app.getSetAdj() <= client.getSetAdj() + && app.getSetProcState() <= client.getSetProcState() + && ((app.getSetCapability() & client.getSetCapability()) + == client.getSetCapability() + || cr.notHasFlag(Context.BIND_INCLUDE_CAPABILITIES + | Context.BIND_BYPASS_USER_NETWORK_RESTRICTIONS))) { + // The service host process has better states than the client, so no change. + return false; + } + // Take a dry run of the computeServiceHostOomAdjLSP, this would't be expensive + // since it's only evaluating one service connection. + return computeServiceHostOomAdjLSP(cr, app, client, SystemClock.uptimeMillis(), + mService.getTopApp(), false, false, false, OOM_ADJ_REASON_NONE, + CACHED_APP_MIN_ADJ, false, true /* dryRun */); + } + + @GuardedBy("mService") + boolean evaluateServiceConnectionRemoval(ProcessRecord client, ProcessRecord app, + ConnectionRecord cr) { + if (evaluateConnectionPrelude(client, app)) { + return true; + } + + if (app.getSetAdj() < client.getSetAdj() + && app.getSetProcState() < client.getSetProcState()) { + // The service host process has better states than the client. + if (((app.getSetCapability() & client.getSetCapability()) == PROCESS_CAPABILITY_NONE) + || cr.notHasFlag(Context.BIND_INCLUDE_CAPABILITIES + | Context.BIND_BYPASS_USER_NETWORK_RESTRICTIONS)) { + // The service host app doesn't get any capabilities from the client. + return false; + } + } + return true; + } + + @GuardedBy("mService") + boolean evaluateProviderConnectionAdd(ProcessRecord client, ProcessRecord app) { + if (evaluateConnectionPrelude(client, app)) { + return true; + } + if (app.getSetAdj() <= client.getSetAdj() + && app.getSetProcState() <= client.getSetProcState()) { + // The provider host process has better states than the client, so no change. + return false; + } + return computeProviderHostOomAdjLSP(null, app, client, SystemClock.uptimeMillis(), + mService.getTopApp(), false, false, false, OOM_ADJ_REASON_NONE, CACHED_APP_MIN_ADJ, + false, true /* dryRun */); + } + + @GuardedBy("mService") + boolean evaluateProviderConnectionRemoval(ProcessRecord client, ProcessRecord app) { + if (evaluateConnectionPrelude(client, app)) { + return true; + } + if (app.getSetAdj() < client.getSetAdj() + && app.getSetProcState() < client.getSetProcState()) { + // The provider host process has better states than the client, so no change. + return false; + } + return true; + } + + private boolean evaluateConnectionPrelude(ProcessRecord client, ProcessRecord app) { + if (client == null || app == null) { + return true; + } + if (app.isSdkSandbox || app.isolated || app.isKilledByAm() || app.isKilled()) { + // Let's always re-evaluate them for now. + return true; + } + return false; + } } diff --git a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java index 5a3fbe9a66ac..f85b03e8b4eb 100644 --- a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java +++ b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java @@ -1002,7 +1002,7 @@ public class OomAdjusterModernImpl extends OomAdjuster { computeServiceHostOomAdjLSP(cr, service, app, now, topApp, fullUpdate, false, false, - oomAdjReason, cachedAdj, false); + oomAdjReason, cachedAdj, false, false); } for (int i = psr.numberOfSdkSandboxConnections() - 1; i >= 0; i--) { @@ -1018,7 +1018,7 @@ public class OomAdjusterModernImpl extends OomAdjuster { } computeServiceHostOomAdjLSP(cr, service, app, now, topApp, fullUpdate, false, false, - oomAdjReason, cachedAdj, false); + oomAdjReason, cachedAdj, false, false); } final ProcessProviderRecord ppr = app.mProviders; @@ -1035,7 +1035,7 @@ public class OomAdjusterModernImpl extends OomAdjuster { } computeProviderHostOomAdjLSP(cpc, provider, app, now, topApp, fullUpdate, false, false, - oomAdjReason, cachedAdj, false); + oomAdjReason, cachedAdj, false, false); } } } diff --git a/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java b/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java index f5c5ea8ae55a..a8fe734f59b7 100644 --- a/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java +++ b/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java @@ -274,7 +274,20 @@ final class ProcessCachedOptimizerRecord { @GuardedBy("mProcLock") void setShouldNotFreeze(boolean shouldNotFreeze) { + setShouldNotFreeze(shouldNotFreeze, false); + } + + /** + * @return {@code true} if it's a dry run and it's going to unfreeze the process + * if it was a real run. + */ + @GuardedBy("mProcLock") + boolean setShouldNotFreeze(boolean shouldNotFreeze, boolean dryRun) { + if (dryRun) { + return mFrozen && !shouldNotFreeze; + } mShouldNotFreeze = shouldNotFreeze; + return false; } @GuardedBy("mProcLock") diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index f5c34a5da1c1..10cd6e5bb136 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -371,6 +371,12 @@ public final class ProcessList { private static final long LMKD_RECONNECT_DELAY_MS = 1000; /** + * The cuttoff adj for the freezer, app processes with adj greater than this value will be + * eligible for the freezer. + */ + static final int FREEZER_CUTOFF_ADJ = CACHED_APP_MIN_ADJ; + + /** * Apps have no access to the private data directories of any other app, even if the other * app has made them world-readable. */ diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java index e5c4a66562c3..de6f034b62ee 100644 --- a/services/core/java/com/android/server/am/ProcessRecord.java +++ b/services/core/java/com/android/server/am/ProcessRecord.java @@ -720,6 +720,11 @@ class ProcessRecord implements WindowProcessListener { return mState.getSetProcState(); } + @GuardedBy(anyOf = {"mService", "mProcLock"}) + int getSetCapability() { + return mState.getSetCapability(); + } + @GuardedBy({"mService", "mProcLock"}) public void makeActive(IApplicationThread thread, ProcessStatsService tracker) { mProfile.onProcessActive(thread, tracker); @@ -1401,8 +1406,12 @@ class ProcessRecord implements WindowProcessListener { void onProcessUnfrozen() { mProfile.onProcessUnfrozen(); + mServices.onProcessUnfrozen(); } + void onProcessFrozenCancelled() { + mServices.onProcessFrozenCancelled(); + } /* * Delete all packages from list except the package indicated in info @@ -1644,6 +1653,13 @@ class ProcessRecord implements WindowProcessListener { return mWasForceStopped; } + boolean isFreezable() { + return mService.mOomAdjuster.mCachedAppOptimizer.useFreezer() + && !mOptRecord.isFreezeExempt() + && !mOptRecord.shouldNotFreeze() + && mState.getCurAdj() >= ProcessList.FREEZER_CUTOFF_ADJ; + } + /** * Traverses all client processes and feed them to consumer. */ diff --git a/services/core/java/com/android/server/am/ProcessServiceRecord.java b/services/core/java/com/android/server/am/ProcessServiceRecord.java index f5f2b102596b..57d233e7c503 100644 --- a/services/core/java/com/android/server/am/ProcessServiceRecord.java +++ b/services/core/java/com/android/server/am/ProcessServiceRecord.java @@ -19,6 +19,8 @@ package com.android.server.am; import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_BOUND_SERVICE; import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_FOREGROUND_SERVICE; +import static com.android.server.am.Flags.serviceBindingOomAdjPolicy; + import android.annotation.Nullable; import android.app.ActivityManager; import android.content.Context; @@ -144,6 +146,11 @@ final class ProcessServiceRecord { */ private ArraySet<Integer> mBoundClientUids = new ArraySet<>(); + /** + * The process should schedule a service timeout timer but haven't done so. + */ + private boolean mScheduleServiceTimeoutPending; + final ProcessRecord mApp; private final ActivityManagerService mService; @@ -657,6 +664,41 @@ final class ProcessServiceRecord { setHasClientActivities(false); } + @GuardedBy("mService") + void noteScheduleServiceTimeoutPending(boolean pending) { + mScheduleServiceTimeoutPending = pending; + } + + @GuardedBy("mService") + boolean isScheduleServiceTimeoutPending() { + return mScheduleServiceTimeoutPending; + } + + @GuardedBy("mService") + void onProcessUnfrozen() { + scheduleServiceTimeoutIfNeededLocked(); + } + + @GuardedBy("mService") + void onProcessFrozenCancelled() { + scheduleServiceTimeoutIfNeededLocked(); + } + + @GuardedBy("mService") + private void scheduleServiceTimeoutIfNeededLocked() { + if (!serviceBindingOomAdjPolicy()) { + return; + } + if (mScheduleServiceTimeoutPending && mExecutingServices.size() > 0) { + mService.mServices.scheduleServiceTimeoutLocked(mApp); + // We'll need to reset the executingStart since the app was frozen. + final long now = SystemClock.uptimeMillis(); + for (int i = 0, size = mExecutingServices.size(); i < size; i++) { + mExecutingServices.valueAt(i).executingStart = now; + } + } + } + void dump(PrintWriter pw, String prefix, long nowUptime) { if (mHasForegroundServices || mApp.mState.getForcingToImportant() != null) { pw.print(prefix); pw.print("mHasForegroundServices="); pw.print(mHasForegroundServices); @@ -701,5 +743,10 @@ final class ProcessServiceRecord { pw.print(prefix); pw.print(" - "); pw.println(mConnections.valueAt(i)); } } + if (serviceBindingOomAdjPolicy()) { + pw.print(prefix); + pw.print("scheduleServiceTimeoutPending="); + pw.println(mScheduleServiceTimeoutPending); + } } } diff --git a/services/core/java/com/android/server/am/ProcessStateRecord.java b/services/core/java/com/android/server/am/ProcessStateRecord.java index 3391ec7c4e68..8362eaf76d55 100644 --- a/services/core/java/com/android/server/am/ProcessStateRecord.java +++ b/services/core/java/com/android/server/am/ProcessStateRecord.java @@ -479,9 +479,22 @@ final class ProcessStateRecord { @GuardedBy({"mService", "mProcLock"}) void setCurRawAdj(int curRawAdj) { + setCurRawAdj(curRawAdj, false); + } + + /** + * @return {@code true} if it's a dry run and it's going to bump the adj score of the process + * if it was a real run. + */ + @GuardedBy({"mService", "mProcLock"}) + boolean setCurRawAdj(int curRawAdj, boolean dryRun) { + if (dryRun) { + return mCurRawAdj > curRawAdj; + } mCurRawAdj = curRawAdj; mApp.getWindowProcessController().setPerceptible( curRawAdj <= ProcessList.PERCEPTIBLE_APP_ADJ); + return false; } @GuardedBy(anyOf = {"mService", "mProcLock"}) @@ -594,7 +607,20 @@ final class ProcessStateRecord { @GuardedBy({"mService", "mProcLock"}) void setCurRawProcState(int curRawProcState) { + setCurRawProcState(curRawProcState, false); + } + + /** + * @return {@code true} if it's a dry run and it's going to bump the procstate of the process + * if it was a real run. + */ + @GuardedBy({"mService", "mProcLock"}) + boolean setCurRawProcState(int curRawProcState, boolean dryRun) { + if (dryRun) { + return mCurRawProcState > curRawProcState; + } mCurRawProcState = curRawProcState; + return false; } @GuardedBy(anyOf = {"mService", "mProcLock"}) @@ -900,7 +926,20 @@ final class ProcessStateRecord { @GuardedBy("mService") void setCached(boolean cached) { + setCached(cached, false); + } + + /** + * @return {@code true} if it's a dry run and it's going to uncache the process + * if it was a real run. + */ + @GuardedBy("mService") + boolean setCached(boolean cached, boolean dryRun) { + if (dryRun) { + return mCached && !cached; + } mCached = cached; + return false; } @GuardedBy("mService") diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig index 654aebd89de2..31d9cc927223 100644 --- a/services/core/java/com/android/server/am/flags.aconfig +++ b/services/core/java/com/android/server/am/flags.aconfig @@ -35,3 +35,10 @@ flag { description: "Enable the new FGS restriction logic" bug: "276963716" } + +flag { + name: "service_binding_oom_adj_policy" + namespace: "backstage_power" + description: "Optimize the service bindings by different policies like skipping oom adjuster" + bug: "318717054" +} diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java index 21e6bac53cde..3f3540e2868c 100644 --- a/services/core/java/com/android/server/biometrics/AuthService.java +++ b/services/core/java/com/android/server/biometrics/AuthService.java @@ -20,7 +20,7 @@ package com.android.server.biometrics; // TODO(b/141025588): Create separate internal and external permissions for AuthService. // TODO(b/141025588): Get rid of the USE_FINGERPRINT permission. -import static android.Manifest.permission.MANAGE_BIOMETRIC_DIALOG; +import static android.Manifest.permission.SET_BIOMETRIC_DIALOG_LOGO; import static android.Manifest.permission.TEST_BIOMETRIC; import static android.Manifest.permission.USE_BIOMETRIC; import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL; @@ -305,7 +305,7 @@ public class AuthService extends SystemService { if (promptInfo.containsPrivateApiConfigurations()) { checkInternalPermission(); } - if (promptInfo.containsManageBioApiConfigurations()) { + if (promptInfo.containsSetLogoApiConfigurations()) { checkManageBiometricPermission(); } @@ -997,8 +997,8 @@ public class AuthService extends SystemService { } private void checkManageBiometricPermission() { - getContext().enforceCallingOrSelfPermission(MANAGE_BIOMETRIC_DIALOG, - "Must have MANAGE_BIOMETRIC_DIALOG permission"); + getContext().enforceCallingOrSelfPermission(SET_BIOMETRIC_DIALOG_LOGO, + "Must have SET_BIOMETRIC_DIALOG_LOGO permission"); } private void checkPermission() { diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index 9cf9119aff82..245fcea06eac 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -2776,17 +2776,17 @@ public final class DisplayManagerService extends SystemService { } private ScreenCapture.ScreenshotHardwareBuffer userScreenshotInternal(int displayId) { + final ScreenCapture.DisplayCaptureArgs captureArgs; synchronized (mSyncRoot) { final IBinder token = getDisplayToken(displayId); if (token == null) { return null; } - final ScreenCapture.DisplayCaptureArgs captureArgs = - new ScreenCapture.DisplayCaptureArgs.Builder(token) + captureArgs = new ScreenCapture.DisplayCaptureArgs.Builder(token) .build(); - return ScreenCapture.captureDisplay(captureArgs); } + return ScreenCapture.captureDisplay(captureArgs); } @VisibleForTesting diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index d72ca7d92894..4767ebd0aab0 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -115,7 +115,6 @@ import android.util.PrintWriterPrinter; import android.util.Printer; import android.util.Slog; import android.util.SparseArray; -import android.util.SparseBooleanArray; import android.util.proto.ProtoOutputStream; import android.view.InputChannel; import android.view.InputDevice; @@ -281,8 +280,6 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @NonNull private InputMethodSettings mSettings; final SettingsObserver mSettingsObserver; - private final SparseBooleanArray mLoggedDeniedGetInputMethodWindowVisibleHeightForUid = - new SparseBooleanArray(0); final WindowManagerInternal mWindowManagerInternal; private final ActivityManagerInternal mActivityManagerInternal; final PackageManagerInternal mPackageManagerInternal; @@ -1354,13 +1351,6 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub clearPackageChangeState(); } - @Override - public void onUidRemoved(int uid) { - synchronized (ImfLock.class) { - mLoggedDeniedGetInputMethodWindowVisibleHeightForUid.delete(uid); - } - } - private void clearPackageChangeState() { // No need to lock them because we access these fields only on getRegisteredHandler(). mChangedPackages.clear(); @@ -2399,16 +2389,6 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @StartInputReason int startInputReason, int unverifiedTargetSdkVersion, @NonNull ImeOnBackInvokedDispatcher imeDispatcher) { - String selectedMethodId = getSelectedMethodIdLocked(); - - if (!mSystemReady) { - // If the system is not yet ready, we shouldn't be running third - // party code. - return new InputBindResult( - InputBindResult.ResultCode.ERROR_SYSTEM_NOT_READY, - null, null, null, selectedMethodId, getSequenceNumberLocked(), false); - } - if (!InputMethodUtils.checkIfPackageBelongsToUid(mPackageManagerInternal, cs.mUid, editorInfo.packageName)) { Slog.e(TAG, "Rejecting this client as it reported an invalid package name." @@ -2429,6 +2409,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub // Potentially override the selected input method if the new display belongs to a virtual // device with a custom IME. + String selectedMethodId = getSelectedMethodIdLocked(); if (oldDisplayIdToShowIme != mDisplayIdToShowIme) { final String deviceMethodId = computeCurrentDeviceMethodIdLocked(selectedMethodId); if (deviceMethodId == null) { @@ -3673,7 +3654,6 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub + "specified for cross-user startInputOrWindowGainedFocus()"); } } - if (windowToken == null) { Slog.e(TAG, "windowToken cannot be null."); return InputBindResult.NULL; @@ -3685,6 +3665,14 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub "InputMethodManagerService#startInputOrWindowGainedFocus"); final InputBindResult result; synchronized (ImfLock.class) { + if (!mSystemReady) { + // If the system is not yet ready, we shouldn't be running third arty code. + return new InputBindResult( + InputBindResult.ResultCode.ERROR_SYSTEM_NOT_READY, + null /* method */, null /* accessibilitySessions */, null /* channel */, + getSelectedMethodIdLocked(), getSequenceNumberLocked(), + false /* isInputMethodSuppressingSpellChecker */); + } final long ident = Binder.clearCallingIdentity(); try { result = startInputOrWindowGainedFocusInternalLocked(startInputReason, @@ -4276,10 +4264,6 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub synchronized (ImfLock.class) { if (!canInteractWithImeLocked(callingUid, client, "getInputMethodWindowVisibleHeight", null /* statsToken */)) { - if (!mLoggedDeniedGetInputMethodWindowVisibleHeightForUid.get(callingUid)) { - EventLog.writeEvent(0x534e4554, "204906124", callingUid, ""); - mLoggedDeniedGetInputMethodWindowVisibleHeightForUid.put(callingUid, true); - } return 0; } // This should probably use the caller's display id, but because this is unsupported diff --git a/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java b/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java index 5e38bca78a7c..2522f7b82353 100644 --- a/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java +++ b/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java @@ -27,6 +27,7 @@ import static java.lang.Math.max; import android.annotation.Nullable; import android.location.Location; +import android.location.LocationRequest; import android.location.LocationResult; import android.location.provider.ProviderRequest; import android.os.SystemClock; @@ -179,6 +180,7 @@ public final class StationaryThrottlingLocationProvider extends DelegateLocation private void onThrottlingChangedLocked(boolean deliverImmediate) { long throttlingIntervalMs = INTERVAL_DISABLED; if (mDeviceStationary && mDeviceIdle && !mIncomingRequest.isLocationSettingsIgnored() + && mIncomingRequest.getQuality() != LocationRequest.QUALITY_HIGH_ACCURACY && mLastLocation != null && mLastLocation.getElapsedRealtimeAgeMillis(mDeviceStationaryRealtimeMs) <= MAX_STATIONARY_LOCATION_AGE_MS) { diff --git a/services/core/java/com/android/server/pm/BACKGROUND_INSTALL_OWNERS b/services/core/java/com/android/server/pm/BACKGROUND_INSTALL_OWNERS new file mode 100644 index 000000000000..baa41a55c519 --- /dev/null +++ b/services/core/java/com/android/server/pm/BACKGROUND_INSTALL_OWNERS @@ -0,0 +1,2 @@ +georgechan@google.com +wenhaowang@google.com
\ No newline at end of file diff --git a/services/core/java/com/android/server/pm/OWNERS b/services/core/java/com/android/server/pm/OWNERS index 84324f2524fc..c8bc56ce7dcd 100644 --- a/services/core/java/com/android/server/pm/OWNERS +++ b/services/core/java/com/android/server/pm/OWNERS @@ -51,3 +51,5 @@ per-file ShortcutRequestPinProcessor.java = omakoto@google.com, yamasani@google. per-file ShortcutService.java = omakoto@google.com, yamasani@google.com, sunnygoyal@google.com, mett@google.com, pinyaoting@google.com per-file ShortcutUser.java = omakoto@google.com, yamasani@google.com, sunnygoyal@google.com, mett@google.com, pinyaoting@google.com +# background install control service +per-file BackgroundInstall* = file:BACKGROUND_INSTALL_OWNERS
\ No newline at end of file diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index a97652c8e167..7e3254de2385 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -3437,7 +3437,7 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile } str.close(); - } catch (IOException | XmlPullParserException e) { + } catch (IOException | XmlPullParserException | ArrayIndexOutOfBoundsException e) { // Remove corrupted file and retry. atomicFile.failRead(str, e); diff --git a/services/core/java/com/android/server/pm/UserTypeFactory.java b/services/core/java/com/android/server/pm/UserTypeFactory.java index 14db70e5f72e..23d0230a6080 100644 --- a/services/core/java/com/android/server/pm/UserTypeFactory.java +++ b/services/core/java/com/android/server/pm/UserTypeFactory.java @@ -288,6 +288,28 @@ public final class UserTypeFactory { * configuration. */ private static UserTypeDetails.Builder getDefaultTypeProfilePrivate() { + UserProperties.Builder userPropertiesBuilder = new UserProperties.Builder() + .setStartWithParent(true) + .setCredentialShareableWithParent(true) + .setAuthAlwaysRequiredToDisableQuietMode(true) + .setAllowStoppingUserWithDelayedLocking(true) + .setMediaSharedWithParent(false) + .setShowInLauncher(UserProperties.SHOW_IN_LAUNCHER_SEPARATE) + .setShowInSettings(UserProperties.SHOW_IN_SETTINGS_SEPARATE) + .setShowInQuietMode( + UserProperties.SHOW_IN_QUIET_MODE_HIDDEN) + .setShowInSharingSurfaces( + UserProperties.SHOW_IN_SHARING_SURFACES_SEPARATE) + .setCrossProfileIntentFilterAccessControl( + UserProperties.CROSS_PROFILE_INTENT_FILTER_ACCESS_LEVEL_SYSTEM) + .setInheritDevicePolicy(UserProperties.INHERIT_DEVICE_POLICY_FROM_PARENT) + .setCrossProfileContentSharingStrategy( + UserProperties.CROSS_PROFILE_CONTENT_SHARING_DELEGATE_FROM_PARENT); + if (android.multiuser.Flags.supportHidingProfiles()) { + userPropertiesBuilder.setProfileApiVisibility( + UserProperties.PROFILE_API_VISIBILITY_HIDDEN); + } + return new UserTypeDetails.Builder() .setName(USER_TYPE_PROFILE_PRIVATE) .setBaseType(FLAG_PROFILE) @@ -306,23 +328,7 @@ public final class UserTypeFactory { .setDarkThemeBadgeColors( R.color.white) .setDefaultRestrictions(getDefaultProfileRestrictions()) - .setDefaultUserProperties(new UserProperties.Builder() - .setStartWithParent(true) - .setCredentialShareableWithParent(true) - .setAuthAlwaysRequiredToDisableQuietMode(true) - .setAllowStoppingUserWithDelayedLocking(true) - .setMediaSharedWithParent(false) - .setShowInLauncher(UserProperties.SHOW_IN_LAUNCHER_SEPARATE) - .setShowInSettings(UserProperties.SHOW_IN_SETTINGS_SEPARATE) - .setShowInQuietMode( - UserProperties.SHOW_IN_QUIET_MODE_HIDDEN) - .setShowInSharingSurfaces( - UserProperties.SHOW_IN_SHARING_SURFACES_SEPARATE) - .setCrossProfileIntentFilterAccessControl( - UserProperties.CROSS_PROFILE_INTENT_FILTER_ACCESS_LEVEL_SYSTEM) - .setInheritDevicePolicy(UserProperties.INHERIT_DEVICE_POLICY_FROM_PARENT) - .setCrossProfileContentSharingStrategy( - UserProperties.CROSS_PROFILE_CONTENT_SHARING_DELEGATE_FROM_PARENT)); + .setDefaultUserProperties(userPropertiesBuilder); } /** diff --git a/services/core/java/com/android/server/pm/VerifyingSession.java b/services/core/java/com/android/server/pm/VerifyingSession.java index f0ff85df13d1..dd2b409c7100 100644 --- a/services/core/java/com/android/server/pm/VerifyingSession.java +++ b/services/core/java/com/android/server/pm/VerifyingSession.java @@ -357,7 +357,8 @@ final class VerifyingSession { verifierUser = UserHandle.of(mPm.mUserManager.getCurrentUserId()); } // TODO(b/300965895): Remove when inconsistencies loading classpaths from apex for - // user > 1 are fixed. + // user > 1 are fixed. Tests should cover verifiers from apex classpaths run on + // primary user, secondary user and work profile. if (pkgLite.isSdkLibrary) { verifierUser = UserHandle.SYSTEM; } diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java index d0fe9647618a..6ed2d3126455 100644 --- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java +++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java @@ -421,6 +421,11 @@ public class PackageInfoUtils { if (ai.isArchived) { ai.nonLocalizedLabel = state.getArchiveState().getActivityInfos().get(0).getTitle(); } + if (!state.isInstalled() && !state.dataExists() + && android.content.pm.Flags.nullableDataDir()) { + // The data dir has been deleted + ai.dataDir = null; + } } @Nullable diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index d9fa01e64a68..03d55d9edfda 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -53,6 +53,7 @@ import static android.app.WindowConfiguration.activityTypeToString; import static android.app.admin.DevicePolicyResources.Drawables.Source.PROFILE_SWITCH_ANIMATION; import static android.app.admin.DevicePolicyResources.Drawables.Style.OUTLINE; import static android.app.admin.DevicePolicyResources.Drawables.WORK_PROFILE_ICON; +import static android.content.Context.CONTEXT_RESTRICTED; import static android.content.Intent.ACTION_MAIN; import static android.content.Intent.CATEGORY_HOME; import static android.content.Intent.CATEGORY_LAUNCHER; @@ -2231,7 +2232,17 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A mOptInOnBackInvoked = WindowOnBackInvokedDispatcher .isOnBackInvokedCallbackEnabled(info, info.applicationInfo, - () -> ent != null ? ent.array : null, false); + () -> { + Context appContext = null; + try { + appContext = mAtmService.mContext.createPackageContextAsUser( + info.packageName, CONTEXT_RESTRICTED, + UserHandle.of(mUserId)); + appContext.setTheme(theme); + } catch (PackageManager.NameNotFoundException ignore) { + } + return appContext; + }); } /** diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java index 4681396affa5..5423c6636786 100644 --- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java +++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java @@ -997,7 +997,7 @@ public class BackgroundActivityStartController { BalVerdict balAllowedForUid = proc.areBackgroundActivityStartsAllowed( state.mAppSwitchState); if (balAllowedForUid.allows()) { - return balAllowedForCaller.withProcessInfo("process", proc); + return balAllowedForUid.withProcessInfo("process", proc); } } } diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index e7ecf520425d..5dcd335baf2d 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -160,6 +160,7 @@ import static com.android.server.wm.utils.DisplayInfoOverrides.WM_OVERRIDE_FIELD import static com.android.server.wm.utils.DisplayInfoOverrides.copyDisplayInfoFields; import static com.android.server.wm.utils.RegionUtils.forEachRectReverse; import static com.android.server.wm.utils.RegionUtils.rectListToRegion; +import static com.android.window.flags.Flags.deferDisplayUpdates; import static com.android.window.flags.Flags.explicitRefreshRateHints; import android.annotation.IntDef; @@ -174,7 +175,6 @@ import android.content.pm.ActivityInfo.ScreenOrientation; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; import android.content.res.Resources; -import android.graphics.Bitmap; import android.graphics.ColorSpace; import android.graphics.Insets; import android.graphics.Matrix; @@ -246,7 +246,6 @@ import android.view.inputmethod.ImeTracker; import android.window.DisplayWindowPolicyController; import android.window.IDisplayAreaOrganizer; import android.window.ScreenCapture; -import android.window.ScreenCapture.SynchronousScreenCaptureListener; import android.window.SystemPerformanceHinter; import android.window.TransitionRequestInfo; @@ -276,7 +275,6 @@ import java.util.Objects; import java.util.Set; import java.util.function.Consumer; import java.util.function.Predicate; -import static com.android.window.flags.Flags.deferDisplayUpdates; /** * Utility class for keeping track of the WindowStates and other pertinent contents of a @@ -5207,10 +5205,9 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } /** - * Takes a snapshot of the display. In landscape mode this grabs the whole screen. - * In portrait mode, it grabs the full screenshot. + * Creates a LayerCaptureArgs object to represent the entire DisplayContent */ - Bitmap screenshotDisplayLocked() { + ScreenCapture.LayerCaptureArgs getLayerCaptureArgs() { if (!mWmService.mPolicy.isScreenOn()) { if (DEBUG_SCREENSHOT) { Slog.i(TAG_WM, "Attempted to take screenshot while display was off."); @@ -5218,24 +5215,10 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp return null; } - SynchronousScreenCaptureListener syncScreenCapture = - ScreenCapture.createSyncCaptureListener(); - getBounds(mTmpRect); mTmpRect.offsetTo(0, 0); - ScreenCapture.LayerCaptureArgs args = - new ScreenCapture.LayerCaptureArgs.Builder(getSurfaceControl()) - .setSourceCrop(mTmpRect).build(); - - ScreenCapture.captureLayers(args, syncScreenCapture); - - final ScreenCapture.ScreenshotHardwareBuffer screenshotBuffer = - syncScreenCapture.getBuffer(); - final Bitmap bitmap = screenshotBuffer == null ? null : screenshotBuffer.asBitmap(); - if (bitmap == null) { - Slog.w(TAG_WM, "Failed to take screenshot"); - } - return bitmap; + return new ScreenCapture.LayerCaptureArgs.Builder(getSurfaceControl()) + .setSourceCrop(mTmpRect).build(); } @Override diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java index 10405ec7cd88..e027eb63f1d5 100644 --- a/services/core/java/com/android/server/wm/RecentTasks.java +++ b/services/core/java/com/android/server/wm/RecentTasks.java @@ -1135,16 +1135,17 @@ class RecentTasks { if (!mFreezeTaskListReordering) { // Simple case: this is not an affiliated task, so we just move it to the // front unless overridden by the provided activity options + int indexToAdd = findIndexToAdd(task); mTasks.remove(taskIndex); - mTasks.add(0, task); + mTasks.add(indexToAdd, task); if (taskIndex != 0) { // Only notify when position changes mTaskNotificationController.notifyTaskListUpdated(); } if (DEBUG_RECENTS) { - Slog.d(TAG_RECENTS, "addRecent: moving to top " + task - + " from " + taskIndex); + Slog.d(TAG_RECENTS, "addRecent: moving " + task + " to index " + + indexToAdd + " from " + taskIndex); } } notifyTaskPersisterLocked(task, false); @@ -1231,6 +1232,37 @@ class RecentTasks { notifyTaskPersisterLocked(task, false /* flush */); } + // Looks for a new index to move the recent Task. Note that the recent Task should not be + // placed higher than another recent Task that has higher hierarchical z-ordering. + private int findIndexToAdd(Task task) { + int indexToAdd = 0; + for (int i = 0; i < mTasks.size(); i++) { + final Task otherTask = mTasks.get(i); + if (task == otherTask) { + break; + } + + if (!otherTask.isAttached()) { + // Stop searching if not attached. + break; + } + + if (otherTask.inPinnedWindowingMode()) { + // Skip pip task without increasing index since pip is always on screen. + continue; + } + + // Stop searching if the task has higher z-ordering, or increase the index and + // continue the search. + if (task.compareTo(otherTask) > 0) { + break; + } + + indexToAdd = i + 1; + } + return indexToAdd; + } + /** * Add the task to the bottom if possible. */ diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java index 399815b70a06..6949a874b533 100644 --- a/services/core/java/com/android/server/wm/WallpaperController.java +++ b/services/core/java/com/android/server/wm/WallpaperController.java @@ -279,13 +279,14 @@ class WallpaperController { return null; } Point largestDisplaySize = new Point(); + float largestWidth = 0; List<DisplayInfo> possibleDisplayInfo = mService.getPossibleDisplayInfoLocked(DEFAULT_DISPLAY); for (int i = 0; i < possibleDisplayInfo.size(); i++) { DisplayInfo displayInfo = possibleDisplayInfo.get(i); - if (displayInfo.type == Display.TYPE_INTERNAL - && Math.max(displayInfo.logicalWidth, displayInfo.logicalHeight) - > Math.max(largestDisplaySize.x, largestDisplaySize.y)) { + float width = (float) displayInfo.logicalWidth / displayInfo.physicalXDpi; + if (displayInfo.type == Display.TYPE_INTERNAL && width > largestWidth) { + largestWidth = width; largestDisplaySize.set(displayInfo.logicalWidth, displayInfo.logicalHeight); } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 9650b8bc2281..426694d178af 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -4087,7 +4087,7 @@ public class WindowManagerService extends IWindowManager.Stub throw new SecurityException("Requires READ_FRAME_BUFFER permission"); } - final Bitmap bm; + ScreenCapture.LayerCaptureArgs captureArgs; synchronized (mGlobalLock) { final DisplayContent displayContent = mRoot.getDisplayContent(DEFAULT_DISPLAY); if (displayContent == null) { @@ -4095,12 +4095,30 @@ public class WindowManagerService extends IWindowManager.Stub Slog.i(TAG_WM, "Screenshot returning null. No Display for displayId=" + DEFAULT_DISPLAY); } - bm = null; + captureArgs = null; } else { - bm = displayContent.screenshotDisplayLocked(); + captureArgs = displayContent.getLayerCaptureArgs(); } } + final Bitmap bm; + if (captureArgs != null) { + ScreenCapture.SynchronousScreenCaptureListener syncScreenCapture = + ScreenCapture.createSyncCaptureListener(); + + ScreenCapture.captureLayers(captureArgs, syncScreenCapture); + + final ScreenCapture.ScreenshotHardwareBuffer screenshotBuffer = + syncScreenCapture.getBuffer(); + bm = screenshotBuffer == null ? null : screenshotBuffer.asBitmap(); + } else { + bm = null; + } + + if (bm == null) { + Slog.w(TAG_WM, "Failed to take screenshot"); + } + FgThread.getHandler().post(() -> { try { receiver.onHandleAssistScreenshot(bm); diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index 4ba52e4c0fd7..3f889c01bafb 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -1471,7 +1471,12 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub final int index = task.mChildren.indexOf(topTaskFragment); task.mChildren.remove(taskFragment); task.mChildren.add(index, taskFragment); - effects |= TRANSACT_EFFECTS_LIFECYCLE; + if (taskFragment.hasChild()) { + effects |= TRANSACT_EFFECTS_LIFECYCLE; + } else { + // Ensure that the child layers are updated if the TaskFragment is empty + task.assignChildLayers(); + } } } break; @@ -1486,7 +1491,12 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub if (task != null) { task.mChildren.remove(taskFragment); task.mChildren.add(0, taskFragment); - effects |= TRANSACT_EFFECTS_LIFECYCLE; + if (taskFragment.hasChild()) { + effects |= TRANSACT_EFFECTS_LIFECYCLE; + } else { + // Ensure that the child layers are updated if the TaskFragment is empty. + task.assignChildLayers(); + } } break; } @@ -1495,7 +1505,12 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub if (task != null) { task.mChildren.remove(taskFragment); task.mChildren.add(taskFragment); - effects |= TRANSACT_EFFECTS_LIFECYCLE; + if (taskFragment.hasChild()) { + effects |= TRANSACT_EFFECTS_LIFECYCLE; + } else { + // Ensure that the child layers are updated if the TaskFragment is empty. + task.assignChildLayers(); + } } break; } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index f288103bd954..519c9bb16eed 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -71,6 +71,7 @@ import static android.Manifest.permission.MANAGE_DEVICE_POLICY_STATUS_BAR; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_SUPPORT_MESSAGE; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_SYSTEM_DIALOGS; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_SYSTEM_UPDATES; +import static android.Manifest.permission.MANAGE_DEVICE_POLICY_THREAD_NETWORK; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_TIME; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_USB_DATA_SIGNALLING; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_USB_FILE_TRANSFER; @@ -484,6 +485,7 @@ import com.android.internal.widget.PasswordValidationError; import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; import com.android.net.module.util.ProxyUtils; +import com.android.net.thread.flags.Flags; import com.android.server.AlarmManagerInternal; import com.android.server.LocalManagerRegistry; import com.android.server.LocalServices; @@ -13339,6 +13341,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { UserManager.DISALLOW_SMS, new String[]{MANAGE_DEVICE_POLICY_SMS}); USER_RESTRICTION_PERMISSIONS.put( UserManager.DISALLOW_SYSTEM_ERROR_DIALOGS, new String[]{MANAGE_DEVICE_POLICY_SYSTEM_DIALOGS}); + if (Flags.threadUserRestrictionEnabled()) { + USER_RESTRICTION_PERMISSIONS.put( + UserManager.DISALLOW_THREAD_NETWORK, + new String[]{MANAGE_DEVICE_POLICY_THREAD_NETWORK}); + } USER_RESTRICTION_PERMISSIONS.put( UserManager.DISALLOW_ULTRA_WIDEBAND_RADIO, new String[]{MANAGE_DEVICE_POLICY_NEARBY_COMMUNICATION}); USER_RESTRICTION_PERMISSIONS.put( diff --git a/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt index 24d49523b9d1..3284cf19db43 100644 --- a/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt +++ b/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt @@ -129,7 +129,10 @@ class DevicePermissionPolicy : SchemePolicy() { val packageState = newState.externalState.packageStates[packageName] ?: return val androidPackage = packageState.androidPackage ?: return val appId = packageState.appId - val appIdPermissionFlags = newState.userStates[userId]!!.appIdDevicePermissionFlags + // The user may happen removed due to DeletePackageHelper.removeUnusedPackagesLPw() calling + // deletePackageX() asynchronously. + val userState = newState.userStates[userId] ?: return + val devicePermissionFlags = userState.appIdDevicePermissionFlags[appId] ?: return androidPackage.requestedPermissions.forEach { permissionName -> val isRequestedByOtherPackages = anyPackageInAppId(appId) { @@ -139,7 +142,7 @@ class DevicePermissionPolicy : SchemePolicy() { if (isRequestedByOtherPackages) { return@forEach } - appIdPermissionFlags[appId]?.forEachIndexed { _, deviceId, _ -> + devicePermissionFlags.forEachIndexed { _, deviceId, _ -> setPermissionFlags(appId, deviceId, userId, permissionName, 0) } } diff --git a/services/tests/PackageManagerServiceTests/server/Android.bp b/services/tests/PackageManagerServiceTests/server/Android.bp index 3aca1cafbf75..f8accc309931 100644 --- a/services/tests/PackageManagerServiceTests/server/Android.bp +++ b/services/tests/PackageManagerServiceTests/server/Android.bp @@ -103,6 +103,7 @@ android_test { ":PackageParserTestApp4", ":PackageParserTestApp5", ":PackageParserTestApp6", + ":PackageParserTestApp7", ], resource_zips: [":PackageManagerServiceServerTests_apks_as_resources"], diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java index 71f5c754f22f..a0e0e1ef36ee 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java @@ -15,6 +15,17 @@ */ package com.android.server.pm; +import static android.content.UriRelativeFilter.PATH; +import static android.content.UriRelativeFilter.QUERY; +import static android.content.UriRelativeFilter.FRAGMENT; +import static android.content.UriRelativeFilterGroup.ACTION_ALLOW; +import static android.content.UriRelativeFilterGroup.ACTION_BLOCK; +import static android.os.PatternMatcher.PATTERN_ADVANCED_GLOB; +import static android.os.PatternMatcher.PATTERN_LITERAL; +import static android.os.PatternMatcher.PATTERN_PREFIX; +import static android.os.PatternMatcher.PATTERN_SIMPLE_GLOB; +import static android.os.PatternMatcher.PATTERN_SUFFIX; + import static com.android.internal.pm.permission.CompatibilityPermissionInfo.COMPAT_PERMS; import static com.google.common.truth.Truth.assertThat; @@ -36,11 +47,15 @@ import static java.util.stream.Collectors.toList; import android.annotation.NonNull; import android.content.Context; +import android.content.IntentFilter; +import android.content.UriRelativeFilter; +import android.content.UriRelativeFilterGroup; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.ConfigurationInfo; import android.content.pm.FeatureGroupInfo; import android.content.pm.FeatureInfo; +import android.content.pm.Flags; import android.content.pm.PackageInfo; import android.content.pm.PackageManager.Property; import android.content.pm.ServiceInfo; @@ -50,6 +65,9 @@ import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import android.platform.test.annotations.Presubmit; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.util.ArraySet; import androidx.annotation.Nullable; @@ -106,6 +124,7 @@ import java.lang.reflect.Field; import java.nio.file.Files; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; @@ -123,6 +142,9 @@ public class PackageParserTest { @Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder(); + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + private File mTmpDir; private static final File FRAMEWORK = new File("/system/framework/framework-res.apk"); private static final String TEST_APP1_APK = "PackageParserTestApp1.apk"; @@ -131,6 +153,7 @@ public class PackageParserTest { private static final String TEST_APP4_APK = "PackageParserTestApp4.apk"; private static final String TEST_APP5_APK = "PackageParserTestApp5.apk"; private static final String TEST_APP6_APK = "PackageParserTestApp6.apk"; + private static final String TEST_APP7_APK = "PackageParserTestApp7.apk"; private static final String PACKAGE_NAME = "com.android.servicestests.apps.packageparserapp"; @Before @@ -375,6 +398,87 @@ public class PackageParserTest { assertNotEquals("$automotive", actualDisplayCategory); } + @Test + @RequiresFlagsEnabled(Flags.FLAG_RELATIVE_REFERENCE_INTENT_FILTERS) + public void testParseUriRelativeFilterGroups() throws Exception { + final File testFile = extractFile(TEST_APP7_APK); + try { + final ParsedPackage pkg = new TestPackageParser2().parsePackage(testFile, 0, false); + final List<ParsedActivity> activities = pkg.getActivities(); + final List<ParsedIntentInfo> intents = activities.get(0).getIntents(); + final IntentFilter intentFilter = intents.get(0).getIntentFilter(); + assertEquals(7, intentFilter.countUriRelativeFilterGroups()); + + UriRelativeFilterGroup group = intentFilter.getUriRelativeFilterGroup(0); + Collection<UriRelativeFilter> filters = group.getUriRelativeFilters(); + assertEquals(ACTION_BLOCK, group.getAction()); + assertEquals(3, filters.size()); + assertTrue(filters.contains(new UriRelativeFilter(PATH, PATTERN_PREFIX, "/gizmos"))); + assertTrue(filters.contains(new UriRelativeFilter(QUERY, PATTERN_SIMPLE_GLOB, + ".*query=string.*"))); + assertTrue(filters.contains(new UriRelativeFilter(FRAGMENT, PATTERN_LITERAL, + "fragment"))); + + group = intentFilter.getUriRelativeFilterGroup(1); + filters = group.getUriRelativeFilters(); + assertEquals(ACTION_ALLOW, group.getAction()); + assertEquals(2, filters.size()); + assertTrue(filters.contains(new UriRelativeFilter(QUERY, PATTERN_LITERAL, + "query=string"))); + assertTrue(filters.contains(new UriRelativeFilter(FRAGMENT, PATTERN_SUFFIX, + "fragment"))); + + group = intentFilter.getUriRelativeFilterGroup(2); + filters = group.getUriRelativeFilters(); + assertEquals(ACTION_ALLOW, group.getAction()); + assertTrue(filters.contains(new UriRelativeFilter(PATH, PATTERN_LITERAL, "/gizmos"))); + assertTrue(filters.contains(new UriRelativeFilter(QUERY, PATTERN_LITERAL, + ".*query=string.*"))); + assertTrue(filters.contains(new UriRelativeFilter(FRAGMENT, PATTERN_LITERAL, + "fragment"))); + + group = intentFilter.getUriRelativeFilterGroup(3); + filters = group.getUriRelativeFilters(); + assertEquals(ACTION_ALLOW, group.getAction()); + assertTrue(filters.contains(new UriRelativeFilter(PATH, PATTERN_PREFIX, "/gizmos"))); + assertTrue(filters.contains(new UriRelativeFilter(QUERY, PATTERN_PREFIX, + ".*query=string.*"))); + assertTrue(filters.contains(new UriRelativeFilter(FRAGMENT, PATTERN_PREFIX, + "fragment"))); + + group = intentFilter.getUriRelativeFilterGroup(4); + filters = group.getUriRelativeFilters(); + assertEquals(ACTION_ALLOW, group.getAction()); + assertTrue(filters.contains(new UriRelativeFilter(PATH, PATTERN_SIMPLE_GLOB, + "/gizmos"))); + assertTrue(filters.contains(new UriRelativeFilter(QUERY, PATTERN_SIMPLE_GLOB, + ".*query=string.*"))); + assertTrue(filters.contains(new UriRelativeFilter(FRAGMENT, PATTERN_SIMPLE_GLOB, + "fragment"))); + + group = intentFilter.getUriRelativeFilterGroup(5); + filters = group.getUriRelativeFilters(); + assertEquals(ACTION_ALLOW, group.getAction()); + assertTrue(filters.contains(new UriRelativeFilter(PATH, PATTERN_ADVANCED_GLOB, + "/gizmos"))); + assertTrue(filters.contains(new UriRelativeFilter(QUERY, PATTERN_ADVANCED_GLOB, + ".*query=string.*"))); + assertTrue(filters.contains(new UriRelativeFilter(FRAGMENT, PATTERN_ADVANCED_GLOB, + "fragment"))); + + group = intentFilter.getUriRelativeFilterGroup(6); + filters = group.getUriRelativeFilters(); + assertEquals(ACTION_ALLOW, group.getAction()); + assertTrue(filters.contains(new UriRelativeFilter(PATH, PATTERN_SUFFIX, "/gizmos"))); + assertTrue(filters.contains(new UriRelativeFilter(QUERY, PATTERN_SUFFIX, + ".*query=string.*"))); + assertTrue(filters.contains(new UriRelativeFilter(FRAGMENT, PATTERN_SUFFIX, + "fragment"))); + } finally { + testFile.delete(); + } + } + private static final int PROPERTY_TYPE_BOOLEAN = 1; private static final int PROPERTY_TYPE_FLOAT = 2; private static final int PROPERTY_TYPE_INTEGER = 3; diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ServiceBindingOomAdjPolicyTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ServiceBindingOomAdjPolicyTest.java new file mode 100644 index 000000000000..2f12a3b858d2 --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/am/ServiceBindingOomAdjPolicyTest.java @@ -0,0 +1,677 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.am; + +import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_MICROPHONE; +import static android.app.ActivityManager.PROCESS_CAPABILITY_NONE; +import static android.app.ActivityManager.PROCESS_STATE_CACHED_EMPTY; +import static android.app.ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE; +import static android.app.ActivityManager.PROCESS_STATE_HOME; +import static android.app.ActivityManager.PROCESS_STATE_LAST_ACTIVITY; +import static android.app.ActivityManager.PROCESS_STATE_SERVICE; +import static android.content.Context.BIND_AUTO_CREATE; +import static android.content.Context.BIND_INCLUDE_CAPABILITIES; +import static android.content.Context.BIND_WAIVE_PRIORITY; +import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED; +import static android.os.UserHandle.USER_SYSTEM; +import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT; + +import static com.android.server.am.ProcessList.HOME_APP_ADJ; +import static com.android.server.am.ProcessList.PERCEPTIBLE_APP_ADJ; +import static com.android.server.am.ProcessList.SERVICE_ADJ; +import static com.android.server.am.ProcessList.CACHED_APP_MIN_ADJ; + +import static org.junit.Assert.assertNotEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.app.IApplicationThread; +import android.app.IServiceConnection; +import android.app.usage.UsageStatsManagerInternal; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManagerInternal; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.IBinder; +import android.platform.test.annotations.Presubmit; +import android.platform.test.flag.junit.SetFlagsRule; + +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.server.DropBoxManagerInternal; +import com.android.server.LocalServices; +import com.android.server.am.ActivityManagerService.Injector; +import com.android.server.am.ApplicationExitInfoTest.ServiceThreadRule; +import com.android.server.appop.AppOpsService; +import com.android.server.firewall.IntentFirewall; +import com.android.server.wm.ActivityTaskManagerService; +import com.android.server.wm.WindowProcessController; + +import org.junit.After; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.verification.VerificationMode; + +import java.io.File; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.function.Consumer; + +/** + * Test class for the service timeout. + * + * Build/Install/Run: + * atest ServiceBindingOomAdjPolicyTest + */ +@Presubmit +public final class ServiceBindingOomAdjPolicyTest { + private static final String TAG = ServiceBindingOomAdjPolicyTest.class.getSimpleName(); + + private static final String TEST_APP1_NAME = "com.example.foo"; + private static final String TEST_SERVICE1_NAME = "com.example.foo.Foobar"; + private static final int TEST_APP1_UID = 10123; + private static final int TEST_APP1_PID = 12345; + + private static final String TEST_APP2_NAME = "com.example.bar"; + private static final String TEST_SERVICE2_NAME = "com.example.bar.Buz"; + private static final int TEST_APP2_UID = 10124; + private static final int TEST_APP2_PID = 12346; + + @Rule + public final ServiceThreadRule mServiceThreadRule = new ServiceThreadRule(); + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT); + + private Context mContext; + private HandlerThread mHandlerThread; + + @Mock + private AppOpsService mAppOpsService; + @Mock + private DropBoxManagerInternal mDropBoxManagerInt; + @Mock + private PackageManagerInternal mPackageManagerInt; + @Mock + private UsageStatsManagerInternal mUsageStatsManagerInt; + @Mock + private AppErrors mAppErrors; + @Mock + private IntentFirewall mIntentFirewall; + + private ActivityManagerService mAms; + private ProcessList mProcessList; + private ActiveServices mActiveServices; + + private int mCurrentCallingUid; + private int mCurrentCallingPid; + + /** Run at the test class initialization */ + @BeforeClass + public static void setUpOnce() { + System.setProperty("dexmaker.share_classloader", "true"); + } + + @SuppressWarnings("GuardedBy") + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + mContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + + mHandlerThread = new HandlerThread(TAG); + mHandlerThread.start(); + final ProcessList realProcessList = new ProcessList(); + mProcessList = spy(realProcessList); + + LocalServices.removeServiceForTest(DropBoxManagerInternal.class); + LocalServices.addService(DropBoxManagerInternal.class, mDropBoxManagerInt); + LocalServices.removeServiceForTest(PackageManagerInternal.class); + LocalServices.addService(PackageManagerInternal.class, mPackageManagerInt); + doReturn(new ComponentName("", "")).when(mPackageManagerInt).getSystemUiServiceComponent(); + + final ActivityManagerService realAms = new ActivityManagerService( + new TestInjector(mContext), mServiceThreadRule.getThread()); + final ActivityTaskManagerService realAtm = new ActivityTaskManagerService(mContext); + realAtm.initialize(null, null, mContext.getMainLooper()); + realAms.mActivityTaskManager = spy(realAtm); + realAms.mAtmInternal = spy(realAms.mActivityTaskManager.getAtmInternal()); + realAms.mOomAdjuster = spy(realAms.mOomAdjuster); + realAms.mOomAdjuster.mCachedAppOptimizer = spy(realAms.mOomAdjuster.mCachedAppOptimizer); + realAms.mPackageManagerInt = mPackageManagerInt; + realAms.mUsageStatsService = mUsageStatsManagerInt; + realAms.mAppProfiler = spy(realAms.mAppProfiler); + realAms.mProcessesReady = true; + mAms = spy(realAms); + realProcessList.mService = mAms; + + doReturn(false).when(mPackageManagerInt).filterAppAccess(anyString(), anyInt(), anyInt()); + doReturn(true).when(mIntentFirewall).checkService(any(), any(), anyInt(), anyInt(), any(), + any()); + doReturn(false).when(mAms.mAtmInternal).hasSystemAlertWindowPermission(anyInt(), anyInt(), + any()); + doReturn(true).when(mAms.mOomAdjuster.mCachedAppOptimizer).useFreezer(); + doNothing().when(mAms.mOomAdjuster.mCachedAppOptimizer).freezeAppAsyncInternalLSP( + any(), anyLong(), anyBoolean()); + doReturn(false).when(mAms.mAppProfiler).updateLowMemStateLSP(anyInt(), anyInt(), + anyInt(), anyLong()); + + mCurrentCallingUid = TEST_APP1_UID; + mCurrentCallingPid = TEST_APP1_PID; + } + + @After + public void tearDown() throws Exception { + LocalServices.removeServiceForTest(DropBoxManagerInternal.class); + LocalServices.removeServiceForTest(PackageManagerInternal.class); + mHandlerThread.quit(); + } + + @Test + public void testServiceSelfBindingOomAdj() throws Exception { + // Enable the flags. + mSetFlagsRule.enableFlags(Flags.FLAG_SERVICE_BINDING_OOM_ADJ_POLICY); + + // Verify that there should be 0 oom adj updates. + performTestServiceSelfBindingOomAdj(never(), never()); + + // Disable the flags. + mSetFlagsRule.disableFlags(Flags.FLAG_SERVICE_BINDING_OOM_ADJ_POLICY); + + // Verify that there should be at least 1 oom adj update. + performTestServiceSelfBindingOomAdj(atLeastOnce(), atLeastOnce()); + } + + @SuppressWarnings("GuardedBy") + private void performTestServiceSelfBindingOomAdj(VerificationMode bindMode, + VerificationMode unbindMode) throws Exception { + final ProcessRecord app = addProcessRecord( + TEST_APP1_PID, // pid + TEST_APP1_UID, // uid + PROCESS_STATE_SERVICE, // procstate + SERVICE_ADJ, // adj + PROCESS_CAPABILITY_NONE, // capabilities + TEST_APP1_NAME // packageName + ); + final Intent serviceIntent = createServiceIntent(TEST_APP1_NAME, TEST_SERVICE1_NAME, + TEST_APP1_UID); + final IServiceConnection serviceConnection = mock(IServiceConnection.class); + + // Make a self binding. + assertNotEquals(0, mAms.bindService( + app.getThread(), // caller + null, // token + serviceIntent, // service + null, // resolveType + serviceConnection, // connection + BIND_AUTO_CREATE, // flags + TEST_APP1_NAME, // callingPackage + USER_SYSTEM // userId + )); + + verify(mAms.mOomAdjuster, bindMode).updateOomAdjPendingTargetsLocked(anyInt()); + clearInvocations(mAms.mOomAdjuster); + + // Unbind the service. + mAms.unbindService(serviceConnection); + + verify(mAms.mOomAdjuster, unbindMode).updateOomAdjPendingTargetsLocked(anyInt()); + clearInvocations(mAms.mOomAdjuster); + + removeProcessRecord(app); + } + + @Test + public void testServiceDistinctBindingOomAdjMoreImportant() throws Exception { + // Enable the flags. + mSetFlagsRule.enableFlags(Flags.FLAG_SERVICE_BINDING_OOM_ADJ_POLICY); + + // Verify that there should be at least 1 oom adj update + // because the client is more important. + performTestServiceDistinctBindingOomAdj(TEST_APP1_PID, TEST_APP1_UID, + PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ, + PROCESS_CAPABILITY_NONE, TEST_APP1_NAME, + this::setHasForegroundServices, + TEST_APP2_PID, TEST_APP2_UID, PROCESS_STATE_HOME, + HOME_APP_ADJ, PROCESS_CAPABILITY_NONE, TEST_APP2_NAME, TEST_SERVICE2_NAME, + this::setHomeProcess, + BIND_AUTO_CREATE, + atLeastOnce(), atLeastOnce()); + + // Disable the flags. + mSetFlagsRule.disableFlags(Flags.FLAG_SERVICE_BINDING_OOM_ADJ_POLICY); + + // Verify that there should be at least 1 oom adj update + // because the client is more important. + performTestServiceDistinctBindingOomAdj(TEST_APP1_PID, TEST_APP1_UID, + PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ, + PROCESS_CAPABILITY_NONE, TEST_APP1_NAME, + this::setHasForegroundServices, + TEST_APP2_PID, TEST_APP2_UID, PROCESS_STATE_HOME, + HOME_APP_ADJ, PROCESS_CAPABILITY_NONE, TEST_APP2_NAME, TEST_SERVICE2_NAME, + this::setHomeProcess, + BIND_AUTO_CREATE, + atLeastOnce(), atLeastOnce()); + } + + @Test + public void testServiceDistinctBindingOomAdjLessImportant() throws Exception { + // Enable the flags. + mSetFlagsRule.enableFlags(Flags.FLAG_SERVICE_BINDING_OOM_ADJ_POLICY); + + // Verify that there should be 0 oom adj update + performTestServiceDistinctBindingOomAdj(TEST_APP1_PID, TEST_APP1_UID, + PROCESS_STATE_HOME, HOME_APP_ADJ, PROCESS_CAPABILITY_NONE, TEST_APP1_NAME, + this::setHomeProcess, + TEST_APP2_PID, TEST_APP2_UID, PROCESS_STATE_FOREGROUND_SERVICE, + PERCEPTIBLE_APP_ADJ, PROCESS_CAPABILITY_NONE, TEST_APP2_NAME, TEST_SERVICE2_NAME, + this::setHasForegroundServices, + BIND_AUTO_CREATE, + never(), never()); + + // Disable the flags. + mSetFlagsRule.disableFlags(Flags.FLAG_SERVICE_BINDING_OOM_ADJ_POLICY); + + // Verify that there should be at least 1 oom adj update + performTestServiceDistinctBindingOomAdj(TEST_APP1_PID, TEST_APP1_UID, + PROCESS_STATE_HOME, HOME_APP_ADJ, PROCESS_CAPABILITY_NONE, TEST_APP1_NAME, + this::setHomeProcess, + TEST_APP2_PID, TEST_APP2_UID, PROCESS_STATE_FOREGROUND_SERVICE, + PERCEPTIBLE_APP_ADJ, PROCESS_CAPABILITY_NONE, TEST_APP2_NAME, TEST_SERVICE2_NAME, + this::setHasForegroundServices, + BIND_AUTO_CREATE, + atLeastOnce(), atLeastOnce()); + } + + @Test + public void testServiceDistinctBindingOomAdjWaivePriority() throws Exception { + // Enable the flags. + mSetFlagsRule.enableFlags(Flags.FLAG_SERVICE_BINDING_OOM_ADJ_POLICY); + + // Verify that there should be 0 oom adj update for binding + // because we're using the BIND_WAIVE_PRIORITY; + // but for the unbinding, because client is better than service, we can't skip it safely. + performTestServiceDistinctBindingOomAdj(TEST_APP1_PID, TEST_APP1_UID, + PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ, + PROCESS_CAPABILITY_NONE, TEST_APP1_NAME, + this::setHasForegroundServices, + TEST_APP2_PID, TEST_APP2_UID, PROCESS_STATE_HOME, + HOME_APP_ADJ, PROCESS_CAPABILITY_NONE, TEST_APP2_NAME, TEST_SERVICE2_NAME, + this::setHomeProcess, + BIND_AUTO_CREATE | BIND_WAIVE_PRIORITY, + never(), atLeastOnce()); + + // Verify that there should be 0 oom adj update + // because we're using the BIND_WAIVE_PRIORITY; + performTestServiceDistinctBindingOomAdj(TEST_APP1_PID, TEST_APP1_UID, + PROCESS_STATE_HOME, HOME_APP_ADJ, PROCESS_CAPABILITY_NONE, TEST_APP1_NAME, + this::setHomeProcess, + TEST_APP2_PID, TEST_APP2_UID, PROCESS_STATE_FOREGROUND_SERVICE, + PERCEPTIBLE_APP_ADJ, PROCESS_CAPABILITY_NONE, TEST_APP2_NAME, TEST_SERVICE2_NAME, + this::setHasForegroundServices, + BIND_AUTO_CREATE | BIND_WAIVE_PRIORITY, + never(), never()); + + // Disable the flags. + mSetFlagsRule.disableFlags(Flags.FLAG_SERVICE_BINDING_OOM_ADJ_POLICY); + + // Verify that there should be at least 1 oom adj update + // because the client is more important. + performTestServiceDistinctBindingOomAdj(TEST_APP1_PID, TEST_APP1_UID, + PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ, + PROCESS_CAPABILITY_NONE, TEST_APP1_NAME, + this::setHasForegroundServices, + TEST_APP2_PID, TEST_APP2_UID, PROCESS_STATE_HOME, + HOME_APP_ADJ, PROCESS_CAPABILITY_NONE, TEST_APP2_NAME, TEST_SERVICE2_NAME, + this::setHomeProcess, + BIND_AUTO_CREATE, + atLeastOnce(), atLeastOnce()); + } + + @Test + public void testServiceDistinctBindingOomAdjNoIncludeCapabilities() throws Exception { + // Enable the flags. + mSetFlagsRule.enableFlags(Flags.FLAG_SERVICE_BINDING_OOM_ADJ_POLICY); + + // Verify that there should be 0 oom adj update + // because we didn't specify the "BIND_INCLUDE_CAPABILITIES" + performTestServiceDistinctBindingOomAdj(TEST_APP1_PID, TEST_APP1_UID, + PROCESS_STATE_HOME, HOME_APP_ADJ, + PROCESS_CAPABILITY_FOREGROUND_MICROPHONE, TEST_APP1_NAME, + this::setHomeProcess, + TEST_APP2_PID, TEST_APP2_UID, PROCESS_STATE_FOREGROUND_SERVICE, + PERCEPTIBLE_APP_ADJ, PROCESS_CAPABILITY_NONE, TEST_APP2_NAME, TEST_SERVICE2_NAME, + this::setHasForegroundServices, + BIND_AUTO_CREATE, + never(), never()); + + // Disable the flags. + mSetFlagsRule.disableFlags(Flags.FLAG_SERVICE_BINDING_OOM_ADJ_POLICY); + + // Verify that there should be at least 1 oom adj update + performTestServiceDistinctBindingOomAdj(TEST_APP1_PID, TEST_APP1_UID, + PROCESS_STATE_HOME, HOME_APP_ADJ, + PROCESS_CAPABILITY_FOREGROUND_MICROPHONE, TEST_APP1_NAME, + this::setHomeProcess, + TEST_APP2_PID, TEST_APP2_UID, PROCESS_STATE_FOREGROUND_SERVICE, + PERCEPTIBLE_APP_ADJ, PROCESS_CAPABILITY_NONE, TEST_APP2_NAME, TEST_SERVICE2_NAME, + this::setHasForegroundServices, + BIND_AUTO_CREATE, + atLeastOnce(), atLeastOnce()); + } + + @Test + public void testServiceDistinctBindingOomAdjWithIncludeCapabilities() throws Exception { + // Enable the flags. + mSetFlagsRule.enableFlags(Flags.FLAG_SERVICE_BINDING_OOM_ADJ_POLICY); + + // Verify that there should be at least 1 oom adj update + // because we use the "BIND_INCLUDE_CAPABILITIES" + performTestServiceDistinctBindingOomAdj(TEST_APP1_PID, TEST_APP1_UID, + PROCESS_STATE_HOME, HOME_APP_ADJ, + PROCESS_CAPABILITY_FOREGROUND_MICROPHONE, TEST_APP1_NAME, + this::setHomeProcess, + TEST_APP2_PID, TEST_APP2_UID, PROCESS_STATE_FOREGROUND_SERVICE, + PERCEPTIBLE_APP_ADJ, PROCESS_CAPABILITY_NONE, TEST_APP2_NAME, TEST_SERVICE2_NAME, + this::setHasForegroundServices, + BIND_AUTO_CREATE | BIND_INCLUDE_CAPABILITIES, + atLeastOnce(), atLeastOnce()); + + // Disable the flags. + mSetFlagsRule.disableFlags(Flags.FLAG_SERVICE_BINDING_OOM_ADJ_POLICY); + + // Verify that there should be at least 1 oom adj update + performTestServiceDistinctBindingOomAdj(TEST_APP1_PID, TEST_APP1_UID, + PROCESS_STATE_HOME, HOME_APP_ADJ, + PROCESS_CAPABILITY_FOREGROUND_MICROPHONE, TEST_APP1_NAME, + this::setHomeProcess, + TEST_APP2_PID, TEST_APP2_UID, PROCESS_STATE_FOREGROUND_SERVICE, + PERCEPTIBLE_APP_ADJ, PROCESS_CAPABILITY_NONE, TEST_APP2_NAME, TEST_SERVICE2_NAME, + this::setHasForegroundServices, + BIND_AUTO_CREATE | BIND_INCLUDE_CAPABILITIES, + atLeastOnce(), atLeastOnce()); + } + + @Test + public void testServiceDistinctBindingOomAdjFreezeCaller() throws Exception { + // Enable the flags. + mSetFlagsRule.enableFlags(Flags.FLAG_SERVICE_BINDING_OOM_ADJ_POLICY); + + // Verify that there should be 0 oom adj update + performTestServiceDistinctBindingOomAdj(TEST_APP1_PID, TEST_APP1_UID, + PROCESS_STATE_CACHED_EMPTY, CACHED_APP_MIN_ADJ, PROCESS_CAPABILITY_NONE, + TEST_APP1_NAME, null, + TEST_APP2_PID, TEST_APP2_UID, PROCESS_STATE_FOREGROUND_SERVICE, + PERCEPTIBLE_APP_ADJ, PROCESS_CAPABILITY_NONE, TEST_APP2_NAME, TEST_SERVICE2_NAME, + this::setHasForegroundServices, + BIND_AUTO_CREATE, + never(), never()); + + // Disable the flags. + mSetFlagsRule.disableFlags(Flags.FLAG_SERVICE_BINDING_OOM_ADJ_POLICY); + + // Verify that there should be at least 1 oom adj update + performTestServiceDistinctBindingOomAdj(TEST_APP1_PID, TEST_APP1_UID, + PROCESS_STATE_CACHED_EMPTY, CACHED_APP_MIN_ADJ, PROCESS_CAPABILITY_NONE, + TEST_APP1_NAME, null, + TEST_APP2_PID, TEST_APP2_UID, PROCESS_STATE_FOREGROUND_SERVICE, + PERCEPTIBLE_APP_ADJ, PROCESS_CAPABILITY_NONE, TEST_APP2_NAME, TEST_SERVICE2_NAME, + this::setHasForegroundServices, + BIND_AUTO_CREATE, + atLeastOnce(), atLeastOnce()); + } + + @SuppressWarnings("GuardedBy") + private void performTestServiceDistinctBindingOomAdj(int clientPid, int clientUid, + int clientProcState, int clientAdj, int clientCap, String clientPackageName, + Consumer<ProcessRecord> clientAppFixer, + int servicePid, int serviceUid, int serviceProcState, int serviceAdj, + int serviceCap, String servicePackageName, String serviceName, + Consumer<ProcessRecord> serviceAppFixer, int bindingFlags, + VerificationMode bindMode, VerificationMode unbindMode) throws Exception { + final ProcessRecord clientApp = addProcessRecord( + clientPid, + clientUid, + clientProcState, + clientAdj, + clientCap, + clientPackageName + ); + final ProcessRecord serviceApp = addProcessRecord( + servicePid, + serviceUid, + serviceProcState, + serviceAdj, + serviceCap, + servicePackageName + ); + final Intent serviceIntent = createServiceIntent(servicePackageName, serviceName, + serviceUid); + final IServiceConnection serviceConnection = mock(IServiceConnection.class); + if (clientAppFixer != null) clientAppFixer.accept(clientApp); + if (serviceAppFixer != null) serviceAppFixer.accept(serviceApp); + + // Make a self binding. + assertNotEquals(0, mAms.bindService( + clientApp.getThread(), // caller + null, // token + serviceIntent, // service + null, // resolveType + serviceConnection, // connection + bindingFlags, // flags + clientPackageName, // callingPackage + USER_SYSTEM // userId + )); + + verify(mAms.mOomAdjuster, bindMode).updateOomAdjPendingTargetsLocked(anyInt()); + clearInvocations(mAms.mOomAdjuster); + + if (clientApp.isFreezable()) { + verify(mAms.mOomAdjuster.mCachedAppOptimizer, + times(Flags.serviceBindingOomAdjPolicy() ? 1 : 0)) + .freezeAppAsyncInternalLSP(eq(clientApp), eq(0L), anyBoolean()); + clearInvocations(mAms.mOomAdjuster.mCachedAppOptimizer); + } + + // Unbind the service. + mAms.unbindService(serviceConnection); + + verify(mAms.mOomAdjuster, unbindMode).updateOomAdjPendingTargetsLocked(anyInt()); + clearInvocations(mAms.mOomAdjuster); + + removeProcessRecord(clientApp); + removeProcessRecord(serviceApp); + } + + private void setHasForegroundServices(ProcessRecord app) { + app.mServices.setHasForegroundServices(true, + FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED, false); + } + + private void setHomeProcess(ProcessRecord app) { + final WindowProcessController wpc = app.getWindowProcessController(); + doReturn(true).when(wpc).isHomeProcess(); + } + + @SuppressWarnings("GuardedBy") + private ProcessRecord addProcessRecord(int pid, int uid, int procState, int adj, int cap, + String packageName) { + final IApplicationThread appThread = mock(IApplicationThread.class); + final IBinder threadBinder = mock(IBinder.class); + final ProcessRecord app = makeProcessRecord(pid, uid, uid, null, 0, + procState, adj, cap, 0L, 0L, packageName, packageName, mAms); + + app.makeActive(appThread, mAms.mProcessStats); + doReturn(threadBinder).when(appThread).asBinder(); + mProcessList.addProcessNameLocked(app); + mProcessList.updateLruProcessLocked(app, false, null); + + setFieldValue(ProcessRecord.class, app, "mWindowProcessController", + mock(WindowProcessController.class)); + + doReturn(app.getSetCapability()).when(mAms.mOomAdjuster).getDefaultCapability( + eq(app), anyInt()); + + return app; + } + + @SuppressWarnings("GuardedBy") + private Intent createServiceIntent(String packageName, String serviceName, int serviceUid) { + final ComponentName compName = new ComponentName(packageName, serviceName); + final Intent serviceIntent = new Intent().setComponent(compName); + final ResolveInfo rInfo = new ResolveInfo(); + rInfo.serviceInfo = makeServiceInfo(compName.getClassName(), compName.getPackageName(), + serviceUid); + doReturn(rInfo).when(mPackageManagerInt).resolveService(any(Intent.class), any(), + anyLong(), anyInt(), anyInt()); + + return serviceIntent; + } + + @SuppressWarnings("GuardedBy") + private void removeProcessRecord(ProcessRecord app) { + app.setKilled(true); + mProcessList.removeProcessNameLocked(app.processName, app.uid); + mProcessList.removeLruProcessLocked(app); + } + + @SuppressWarnings("GuardedBy") + private ProcessRecord makeProcessRecord(int pid, int uid, int packageUid, Integer definingUid, + int connectionGroup, int procState, int adj, int cap, long pss, long rss, + String processName, String packageName, ActivityManagerService ams) { + final ProcessRecord app = ApplicationExitInfoTest.makeProcessRecord(pid, uid, packageUid, + definingUid, connectionGroup, procState, pss, rss, processName, packageName, ams); + app.mState.setCurProcState(procState); + app.mState.setSetProcState(procState); + app.mState.setCurAdj(adj); + app.mState.setSetAdj(adj); + app.mState.setCurCapability(cap); + app.mState.setSetCapability(cap); + app.mState.setCached(procState >= PROCESS_STATE_LAST_ACTIVITY || adj >= CACHED_APP_MIN_ADJ); + return app; + } + + @SuppressWarnings("GuardedBy") + private ServiceInfo makeServiceInfo(String serviceName, String packageName, int packageUid) { + final ServiceInfo sInfo = new ServiceInfo(); + sInfo.name = serviceName; + sInfo.processName = packageName; + sInfo.packageName = packageName; + sInfo.applicationInfo = new ApplicationInfo(); + sInfo.applicationInfo.uid = packageUid; + sInfo.applicationInfo.packageName = packageName; + sInfo.exported = true; + return sInfo; + } + + private static <T> void setFieldValue(Class clazz, Object obj, String fieldName, T val) { + try { + Field field = clazz.getDeclaredField(fieldName); + field.setAccessible(true); + Field mfield = Field.class.getDeclaredField("accessFlags"); + mfield.setAccessible(true); + mfield.setInt(field, mfield.getInt(field) & ~(Modifier.FINAL | Modifier.PRIVATE)); + field.set(obj, val); + } catch (NoSuchFieldException | IllegalAccessException e) { + } + } + + private class TestInjector extends Injector { + TestInjector(Context context) { + super(context); + } + + @Override + public AppOpsService getAppOpsService(File recentAccessesFile, File storageFile, + Handler handler) { + return mAppOpsService; + } + + @Override + public Handler getUiHandler(ActivityManagerService service) { + return mHandlerThread.getThreadHandler(); + } + + @Override + public ProcessList getProcessList(ActivityManagerService service) { + return mProcessList; + } + + @Override + public ActiveServices getActiveServices(ActivityManagerService service) { + if (mActiveServices == null) { + mActiveServices = spy(new ActiveServices(service)); + } + return mActiveServices; + } + + @Override + public int getCallingUid() { + return mCurrentCallingUid; + } + + @Override + public int getCallingPid() { + return mCurrentCallingPid; + } + + @Override + public long clearCallingIdentity() { + return (((long) mCurrentCallingUid) << 32) | mCurrentCallingPid; + } + + @Override + public void restoreCallingIdentity(long ident) { + } + + @Override + public AppErrors getAppErrors() { + return mAppErrors; + } + + @Override + public IntentFirewall getIntentFirewall() { + return mIntentFirewall; + } + } + + // TODO: [b/302724778] Remove manual JNI load + static { + System.loadLibrary("mockingservicestestjni"); + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java index 293391f43828..c6608e61fc62 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java @@ -45,6 +45,7 @@ import static com.android.server.job.controllers.JobStatus.CONSTRAINT_WITHIN_QUO import static com.android.server.job.controllers.JobStatus.NO_EARLIEST_RUNTIME; import static com.android.server.job.controllers.JobStatus.NO_LATEST_RUNTIME; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -78,6 +79,7 @@ import org.mockito.quality.Strictness; import java.time.Clock; import java.time.ZoneOffset; +import java.util.Arrays; @RunWith(AndroidJUnit4.class) public class JobStatusTest { @@ -138,6 +140,35 @@ public class JobStatusTest { } @Test + public void testApplyBasicPiiFilters_email() { + assertEquals("[EMAIL]", JobStatus.applyBasicPiiFilters("test@email.com")); + assertEquals("[EMAIL]", JobStatus.applyBasicPiiFilters("test+plus@email.com")); + assertEquals("[EMAIL]", JobStatus.applyBasicPiiFilters("t.e_st+plus-minus@email.com")); + + assertEquals("prefix:[EMAIL]", JobStatus.applyBasicPiiFilters("prefix:test@email.com")); + + assertEquals("not-an-email", JobStatus.applyBasicPiiFilters("not-an-email")); + } + + @Test + public void testApplyBasicPiiFilters_mixture() { + assertEquals("[PHONE]:[EMAIL]", + JobStatus.applyBasicPiiFilters("123-456-7890:test+plus@email.com")); + assertEquals("prefix:[PHONE]:[EMAIL]", + JobStatus.applyBasicPiiFilters("prefix:123-456-7890:test+plus@email.com")); + } + + @Test + public void testApplyBasicPiiFilters_phone() { + assertEquals("[PHONE]", JobStatus.applyBasicPiiFilters("123-456-7890")); + assertEquals("[PHONE]", JobStatus.applyBasicPiiFilters("+1-234-567-8900")); + + assertEquals("prefix:[PHONE]", JobStatus.applyBasicPiiFilters("prefix:123-456-7890")); + + assertEquals("not-a-phone-number", JobStatus.applyBasicPiiFilters("not-a-phone-number")); + } + + @Test public void testCanRunInBatterySaver_regular() { final JobInfo jobInfo = new JobInfo.Builder(101, new ComponentName("foo", "bar")).build(); @@ -245,6 +276,42 @@ public class JobStatusTest { } @Test + public void testGetFilteredDebugTags() { + final JobInfo jobInfo = new JobInfo.Builder(101, new ComponentName("foo", "bar")) + .addDebugTag("test@email.com") + .addDebugTag("123-456-7890") + .addDebugTag("random") + .build(); + JobStatus job = createJobStatus(jobInfo); + String[] expected = new String[]{"[EMAIL]", "[PHONE]", "random"}; + String[] result = job.getFilteredDebugTags(); + Arrays.sort(expected); + Arrays.sort(result); + assertArrayEquals(expected, result); + } + + @Test + public void testGetFilteredTraceTag() { + JobInfo jobInfo = new JobInfo.Builder(101, new ComponentName("foo", "bar")) + .setTraceTag("test@email.com") + .build(); + JobStatus job = createJobStatus(jobInfo); + assertEquals("[EMAIL]", job.getFilteredTraceTag()); + + jobInfo = new JobInfo.Builder(101, new ComponentName("foo", "bar")) + .setTraceTag("123-456-7890") + .build(); + job = createJobStatus(jobInfo); + assertEquals("[PHONE]", job.getFilteredTraceTag()); + + jobInfo = new JobInfo.Builder(101, new ComponentName("foo", "bar")) + .setTraceTag("random") + .build(); + job = createJobStatus(jobInfo); + assertEquals("random", job.getFilteredTraceTag()); + } + + @Test public void testIsUserVisibleJob() { JobInfo jobInfo = new JobInfo.Builder(101, new ComponentName("foo", "bar")) .setUserInitiated(false) diff --git a/services/tests/mockingservicestests/src/com/android/server/location/altitude/AltitudeConverterTest.java b/services/tests/mockingservicestests/src/com/android/server/location/altitude/AltitudeConverterTest.java index 8d9a6c510576..9a143d5b3743 100644 --- a/services/tests/mockingservicestests/src/com/android/server/location/altitude/AltitudeConverterTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/location/altitude/AltitudeConverterTest.java @@ -21,6 +21,8 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertThrows; import android.content.Context; +import android.frameworks.location.altitude.GetGeoidHeightRequest; +import android.frameworks.location.altitude.GetGeoidHeightResponse; import android.location.Location; import android.location.altitude.AltitudeConverter; @@ -176,4 +178,20 @@ public class AltitudeConverterTest { assertThrows(IllegalArgumentException.class, () -> mAltitudeConverter.addMslAltitudeToLocation(mContext, location)); } + + @Test + public void testGetGeoidHeight_expectedBehavior() throws IOException { + GetGeoidHeightRequest request = new GetGeoidHeightRequest(); + request.latitudeDegrees = -35.334815; + request.longitudeDegrees = -45; + // Requires data to be loaded from raw assets. + GetGeoidHeightResponse response = mAltitudeConverter.getGeoidHeight(mContext, request); + assertThat(response.geoidHeightMeters).isWithin(2).of(-5.0622); + assertThat(response.geoidHeightErrorMeters).isGreaterThan(0f); + assertThat(response.geoidHeightErrorMeters).isLessThan(1f); + assertThat(response.expirationDistanceMeters).isWithin(1).of(-6.33); + assertThat(response.additionalGeoidHeightErrorMeters).isGreaterThan(0f); + assertThat(response.additionalGeoidHeightErrorMeters).isLessThan(1f); + assertThat(response.success).isTrue(); + } } diff --git a/services/tests/mockingservicestests/src/com/android/server/location/provider/StationaryThrottlingLocationProviderTest.java b/services/tests/mockingservicestests/src/com/android/server/location/provider/StationaryThrottlingLocationProviderTest.java index 4eba21934a4e..efab19cc9663 100644 --- a/services/tests/mockingservicestests/src/com/android/server/location/provider/StationaryThrottlingLocationProviderTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/location/provider/StationaryThrottlingLocationProviderTest.java @@ -29,6 +29,7 @@ import static org.mockito.MockitoAnnotations.initMocks; import android.content.Context; import android.location.Location; +import android.location.LocationRequest; import android.location.LocationResult; import android.location.provider.ProviderRequest; import android.platform.test.annotations.Presubmit; @@ -218,4 +219,22 @@ public class StationaryThrottlingLocationProviderTest { verify(mDelegate, never()).onSetRequest(ProviderRequest.EMPTY_REQUEST); verify(mListener, after(75).times(1)).onReportLocation(any(LocationResult.class)); } + + @Test + public void testNoThrottle_highAccuracy() { + ProviderRequest request = new ProviderRequest.Builder().setIntervalMillis( + 50).setQuality(LocationRequest.QUALITY_HIGH_ACCURACY).build(); + + mProvider.getController().setRequest(request); + verify(mDelegate).onSetRequest(request); + + LocationResult loc = createLocationResult("test_provider", mRandom); + mDelegateProvider.reportLocation(loc); + verify(mListener, times(1)).onReportLocation(loc); + + mInjector.getDeviceStationaryHelper().setStationary(true); + mInjector.getDeviceIdleHelper().setIdle(true); + verify(mDelegate, never()).onSetRequest(ProviderRequest.EMPTY_REQUEST); + verify(mListener, after(75).times(1)).onReportLocation(any(LocationResult.class)); + } } diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java index d7ed7c2d6469..8d8dc9cc45b1 100644 --- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java @@ -23,6 +23,7 @@ import static org.testng.Assert.assertThrows; import android.content.pm.UserProperties; import android.os.Parcel; import android.platform.test.annotations.Presubmit; +import android.platform.test.flag.junit.SetFlagsRule; import android.util.Xml; import androidx.test.filters.MediumTest; @@ -31,6 +32,7 @@ import androidx.test.runner.AndroidJUnit4; import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -52,10 +54,13 @@ import java.util.function.Supplier; @RunWith(AndroidJUnit4.class) @MediumTest public class UserManagerServiceUserPropertiesTest { + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); /** Test that UserProperties can properly read the xml information that it writes. */ @Test public void testWriteReadXml() throws Exception { + mSetFlagsRule.enableFlags(android.multiuser.Flags.FLAG_SUPPORT_HIDING_PROFILES); final UserProperties defaultProps = new UserProperties.Builder() .setShowInLauncher(21) .setStartWithParent(false) @@ -73,6 +78,7 @@ public class UserManagerServiceUserPropertiesTest { .setDeleteAppWithParent(false) .setAlwaysVisible(false) .setCrossProfileContentSharingStrategy(0) + .setProfileApiVisibility(34) .build(); final UserProperties actualProps = new UserProperties(defaultProps); actualProps.setShowInLauncher(14); @@ -90,6 +96,7 @@ public class UserManagerServiceUserPropertiesTest { actualProps.setDeleteAppWithParent(true); actualProps.setAlwaysVisible(true); actualProps.setCrossProfileContentSharingStrategy(1); + actualProps.setProfileApiVisibility(36); // Write the properties to xml. final ByteArrayOutputStream baos = new ByteArrayOutputStream(); @@ -114,6 +121,7 @@ public class UserManagerServiceUserPropertiesTest { /** Tests parcelling an object in which all properties are present. */ @Test public void testParcelUnparcel() throws Exception { + mSetFlagsRule.enableFlags(android.multiuser.Flags.FLAG_SUPPORT_HIDING_PROFILES); final UserProperties originalProps = new UserProperties.Builder() .setShowInLauncher(2145) .build(); @@ -124,6 +132,7 @@ public class UserManagerServiceUserPropertiesTest { /** Tests copying a UserProperties object varying permissions. */ @Test public void testCopyLacksPermissions() throws Exception { + mSetFlagsRule.enableFlags(android.multiuser.Flags.FLAG_SUPPORT_HIDING_PROFILES); final UserProperties defaultProps = new UserProperties.Builder() .setShowInLauncher(2145) .setStartWithParent(true) @@ -134,6 +143,7 @@ public class UserManagerServiceUserPropertiesTest { .setAuthAlwaysRequiredToDisableQuietMode(false) .setAllowStoppingUserWithDelayedLocking(false) .setAlwaysVisible(true) + .setProfileApiVisibility(110) .build(); final UserProperties orig = new UserProperties(defaultProps); orig.setShowInLauncher(2841); @@ -209,6 +219,8 @@ public class UserManagerServiceUserPropertiesTest { copy::isCredentialShareableWithParent, true); assertEqualGetterOrThrows(orig::getCrossProfileContentSharingStrategy, copy::getCrossProfileContentSharingStrategy, true); + assertEqualGetterOrThrows(orig::getProfileApiVisibility, copy::getProfileApiVisibility, + true); } /** @@ -270,5 +282,6 @@ public class UserManagerServiceUserPropertiesTest { assertThat(expected.getAlwaysVisible()).isEqualTo(actual.getAlwaysVisible()); assertThat(expected.getCrossProfileContentSharingStrategy()) .isEqualTo(actual.getCrossProfileContentSharingStrategy()); + assertThat(expected.getProfileApiVisibility()).isEqualTo(actual.getProfileApiVisibility()); } } diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java index 70837061b0bb..1ee604e78d5f 100644 --- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java @@ -41,6 +41,7 @@ import android.content.res.XmlResourceParser; import android.os.Bundle; import android.os.UserManager; import android.platform.test.annotations.Presubmit; +import android.platform.test.flag.junit.SetFlagsRule; import android.util.ArrayMap; import androidx.test.InstrumentationRegistry; @@ -50,6 +51,7 @@ import androidx.test.runner.AndroidJUnit4; import com.android.frameworks.servicestests.R; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -71,9 +73,11 @@ public class UserManagerServiceUserTypeTest { public void setup() { mResources = InstrumentationRegistry.getTargetContext().getResources(); } - + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); @Test public void testUserTypeBuilder_createUserType() { + mSetFlagsRule.enableFlags(android.multiuser.Flags.FLAG_SUPPORT_HIDING_PROFILES); final Bundle restrictions = makeRestrictionsBundle("r1", "r2"); final Bundle systemSettings = makeSettingsBundle("s1", "s2"); final Bundle secureSettings = makeSettingsBundle("secure_s1", "secure_s2"); @@ -97,7 +101,8 @@ public class UserManagerServiceUserTypeTest { .setInheritDevicePolicy(340) .setDeleteAppWithParent(true) .setAlwaysVisible(true) - .setCrossProfileContentSharingStrategy(1); + .setCrossProfileContentSharingStrategy(1) + .setProfileApiVisibility(34); final UserTypeDetails type = new UserTypeDetails.Builder() .setName("a.name") @@ -180,6 +185,7 @@ public class UserManagerServiceUserTypeTest { assertTrue(type.getDefaultUserPropertiesReference().getAlwaysVisible()); assertEquals(1, type.getDefaultUserPropertiesReference() .getCrossProfileContentSharingStrategy()); + assertEquals(34, type.getDefaultUserPropertiesReference().getProfileApiVisibility()); assertEquals(23, type.getBadgeLabel(0)); assertEquals(24, type.getBadgeLabel(1)); @@ -199,6 +205,7 @@ public class UserManagerServiceUserTypeTest { @Test public void testUserTypeBuilder_defaults() { + mSetFlagsRule.enableFlags(android.multiuser.Flags.FLAG_SUPPORT_HIDING_PROFILES); UserTypeDetails type = new UserTypeDetails.Builder() .setName("name") // Required (no default allowed) .setBaseType(FLAG_FULL) // Required (no default allowed) @@ -238,6 +245,8 @@ public class UserManagerServiceUserTypeTest { props.getShowInQuietMode()); assertEquals(UserProperties.CROSS_PROFILE_CONTENT_SHARING_NO_DELEGATION, props.getCrossProfileContentSharingStrategy()); + assertEquals(UserProperties.PROFILE_API_VISIBILITY_VISIBLE, + props.getProfileApiVisibility()); assertFalse(type.hasBadge()); } @@ -310,6 +319,7 @@ public class UserManagerServiceUserTypeTest { /** Tests {@link UserTypeFactory#customizeBuilders} for a reasonable xml file. */ @Test public void testUserTypeFactoryCustomize_profile() throws Exception { + mSetFlagsRule.enableFlags(android.multiuser.Flags.FLAG_SUPPORT_HIDING_PROFILES); final String userTypeAosp1 = "android.test.1"; // Profile user that is not customized final String userTypeAosp2 = "android.test.2"; // Profile user that is customized final String userTypeOem1 = "custom.test.1"; // Custom-defined profile @@ -332,7 +342,8 @@ public class UserManagerServiceUserTypeTest { .setShowInQuietMode(24) .setDeleteAppWithParent(true) .setAlwaysVisible(false) - .setCrossProfileContentSharingStrategy(1); + .setCrossProfileContentSharingStrategy(1) + .setProfileApiVisibility(36); final ArrayMap<String, UserTypeDetails.Builder> builders = new ArrayMap<>(); builders.put(userTypeAosp1, new UserTypeDetails.Builder() @@ -383,6 +394,7 @@ public class UserManagerServiceUserTypeTest { assertFalse(aospType.getDefaultUserPropertiesReference().getAlwaysVisible()); assertEquals(1, aospType.getDefaultUserPropertiesReference() .getCrossProfileContentSharingStrategy()); + assertEquals(36, aospType.getDefaultUserPropertiesReference().getProfileApiVisibility()); // userTypeAosp2 should be modified. aospType = builders.get(userTypeAosp2).createUserTypeDetails(); @@ -439,6 +451,7 @@ public class UserManagerServiceUserTypeTest { assertTrue(aospType.getDefaultUserPropertiesReference().getAlwaysVisible()); assertEquals(0, aospType.getDefaultUserPropertiesReference() .getCrossProfileContentSharingStrategy()); + assertEquals(36, aospType.getDefaultUserPropertiesReference().getProfileApiVisibility()); // userTypeOem1 should be created. UserTypeDetails.Builder customType = builders.get(userTypeOem1); diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java index 06be456be0db..db561c436269 100644 --- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java @@ -39,6 +39,7 @@ import android.os.UserHandle; import android.os.UserManager; import android.platform.test.annotations.Postsubmit; import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.SetFlagsRule; import android.provider.Settings; import android.test.suitebuilder.annotation.LargeTest; import android.test.suitebuilder.annotation.MediumTest; @@ -55,6 +56,7 @@ import com.google.common.collect.Range; import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -97,6 +99,8 @@ public final class UserManagerTest { private UserSwitchWaiter mUserSwitchWaiter; private UserRemovalWaiter mUserRemovalWaiter; private int mOriginalCurrentUserId; + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); @Before public void setUp() throws Exception { @@ -168,6 +172,7 @@ public final class UserManagerTest { @Test public void testCloneUser() throws Exception { + mSetFlagsRule.enableFlags(android.multiuser.Flags.FLAG_SUPPORT_HIDING_PROFILES); assumeCloneEnabled(); UserHandle mainUser = mUserManager.getMainUser(); assumeTrue("Main user is null", mainUser != null); @@ -224,6 +229,7 @@ public final class UserManagerTest { .isEqualTo(cloneUserProperties.getCrossProfileContentSharingStrategy()); assertThrows(SecurityException.class, cloneUserProperties::getDeleteAppWithParent); assertThrows(SecurityException.class, cloneUserProperties::getAlwaysVisible); + assertThrows(SecurityException.class, cloneUserProperties::getProfileApiVisibility); compareDrawables(mUserManager.getUserBadge(), Resources.getSystem().getDrawable(userTypeDetails.getBadgePlain())); @@ -305,6 +311,7 @@ public final class UserManagerTest { @Test public void testPrivateProfile() throws Exception { + mSetFlagsRule.enableFlags(android.multiuser.Flags.FLAG_SUPPORT_HIDING_PROFILES); UserHandle mainUser = mUserManager.getMainUser(); assumeTrue("Main user is null", mainUser != null); // Get the default properties for private profile user type. @@ -346,7 +353,8 @@ public final class UserManagerTest { assertThrows(SecurityException.class, privateProfileUserProperties::getDeleteAppWithParent); assertThrows(SecurityException.class, privateProfileUserProperties::getAllowStoppingUserWithDelayedLocking); - + assertThrows(SecurityException.class, + privateProfileUserProperties::getProfileApiVisibility); compareDrawables(mUserManager.getUserBadge(), Resources.getSystem().getDrawable(userTypeDetails.getBadgePlain())); diff --git a/services/tests/servicestests/test-apps/PackageParserApp/Android.bp b/services/tests/servicestests/test-apps/PackageParserApp/Android.bp index 3e78f9a9674a..131b380d9215 100644 --- a/services/tests/servicestests/test-apps/PackageParserApp/Android.bp +++ b/services/tests/servicestests/test-apps/PackageParserApp/Android.bp @@ -102,3 +102,17 @@ android_test_helper_app { resource_dirs: ["res"], manifest: "AndroidManifestApp6.xml", } + +android_test_helper_app { + name: "PackageParserTestApp7", + sdk_version: "current", + srcs: ["**/*.java"], + dex_preopt: { + enabled: false, + }, + optimize: { + enabled: false, + }, + resource_dirs: ["res"], + manifest: "AndroidManifestApp7.xml", +} diff --git a/services/tests/servicestests/test-apps/PackageParserApp/AndroidManifestApp7.xml b/services/tests/servicestests/test-apps/PackageParserApp/AndroidManifestApp7.xml new file mode 100644 index 000000000000..cb87a48eb524 --- /dev/null +++ b/services/tests/servicestests/test-apps/PackageParserApp/AndroidManifestApp7.xml @@ -0,0 +1,67 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2024 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.servicestests.apps.packageparserapp" > + + <application> + <activity android:name=".TestActivity" + android:exported="true" > + <intent-filter> + <action android:name="android.intent.action.VIEW" /> + <category android:name="android.intent.category.DEFAULT" /> + <category android:name="android.intent.category.BROWSABLE" /> + <data android:scheme="http" + android:host="www.example.com" /> + <uri-relative-filter-group android:allow="false"> + <data android:pathPrefix="/gizmos" /> + <data android:queryPattern=".*query=string.*" /> + <data android:fragment="fragment" /> + </uri-relative-filter-group> + <uri-relative-filter-group> + <data android:query="query=string" /> + <data android:fragmentSuffix="fragment" /> + </uri-relative-filter-group> + <uri-relative-filter-group> + <data android:path="/gizmos" /> + <data android:query=".*query=string.*" /> + <data android:fragment="fragment" /> + </uri-relative-filter-group> + <uri-relative-filter-group> + <data android:pathPrefix="/gizmos" /> + <data android:queryPrefix=".*query=string.*" /> + <data android:fragmentPrefix="fragment" /> + </uri-relative-filter-group> + <uri-relative-filter-group> + <data android:pathPattern="/gizmos" /> + <data android:queryPattern=".*query=string.*" /> + <data android:fragmentPattern="fragment" /> + </uri-relative-filter-group> + <uri-relative-filter-group> + <data android:pathAdvancedPattern="/gizmos" /> + <data android:queryAdvancedPattern=".*query=string.*" /> + <data android:fragmentAdvancedPattern="fragment" /> + </uri-relative-filter-group> + <uri-relative-filter-group> + <data android:pathSuffix="/gizmos" /> + <data android:querySuffix=".*query=string.*" /> + <data android:fragmentSuffix="fragment" /> + </uri-relative-filter-group> + </intent-filter> + </activity> + </application> + +</manifest>
\ No newline at end of file diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java b/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java index 6e5baee3dc67..37e0818eb083 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java @@ -46,6 +46,7 @@ import android.content.Intent; import android.os.Binder; import android.os.Bundle; import android.os.IBinder; +import android.os.IRemoteCallback; import android.platform.test.annotations.Presubmit; import android.util.Log; import android.util.Rational; @@ -63,6 +64,7 @@ import java.util.ArrayList; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; /** * Build/Install/Run: @@ -119,6 +121,29 @@ public class ActivityOptionsTest { } @Test + public void testAbortListenerCalled() { + AtomicBoolean callbackCalled = new AtomicBoolean(false); + + ActivityOptions options = ActivityOptions.makeBasic(); + options.setOnAnimationAbortListener(new IRemoteCallback.Stub() { + @Override + public void sendResult(Bundle data) { + callbackCalled.set(true); + } + }); + + // Verify that the callback is called on abort + options.abort(); + assertTrue(callbackCalled.get()); + + // Verify that the callback survives saving to bundle + ActivityOptions optionsCopy = ActivityOptions.fromBundle(options.toBundle()); + callbackCalled.set(false); + optionsCopy.abort(); + assertTrue(callbackCalled.get()); + } + + @Test public void testTransferLaunchCookie() { final Binder cookie = new Binder(); final ActivityOptions options = ActivityOptions.makeBasic(); @@ -279,7 +304,9 @@ public class ActivityOptionsTest { case "android.activity.pendingIntentCreatorBackgroundActivityStartMode": // KEY_PENDING_INTENT_CREATOR_BACKGROUND_ACTIVITY_START_MODE case "android.activity.launchCookie": // KEY_LAUNCH_COOKIE + case "android:activity.animAbortListener": // KEY_ANIM_ABORT_LISTENER // Existing keys + break; default: unknownKeys.add(key); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTransactionTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTransactionTests.java index d2552718f218..e9ece5dbdcc4 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTransactionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTransactionTests.java @@ -16,6 +16,9 @@ package com.android.server.wm; +import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; + import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static org.junit.Assert.assertEquals; @@ -23,6 +26,7 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.atLeast; +import static org.mockito.Mockito.times; import android.content.Intent; import android.platform.test.annotations.Presubmit; @@ -35,6 +39,9 @@ import androidx.test.filters.SmallTest; import org.junit.Test; import org.junit.runner.RunWith; +import java.util.ArrayList; +import java.util.List; + /** * Test class for {@link WindowContainerTransaction}. * @@ -45,7 +52,6 @@ import org.junit.runner.RunWith; @Presubmit @RunWith(WindowTestRunner.class) public class WindowContainerTransactionTests extends WindowTestsBase { - @Test public void testRemoveTask() { final Task rootTask = createTask(mDisplayContent); @@ -72,6 +78,123 @@ public class WindowContainerTransactionTests extends WindowTestsBase { verify(mAtm.getLockTaskController(), atLeast(1)).clearLockedTask(rootTask); } + @Test + public void testDesktopMode_tasksAreBroughtToFront() { + final TestDesktopOrganizer desktopOrganizer = new TestDesktopOrganizer(mAtm); + TaskDisplayArea tda = desktopOrganizer.mDefaultTDA; + List<ActivityRecord> activityRecords = new ArrayList<>(); + int numberOfTasks = 4; + desktopOrganizer.createFreeformTasksWithActivities(desktopOrganizer, + activityRecords, numberOfTasks); + + final Task task = createTask(mDisplayContent); + final ActivityRecord activity = createActivityRecord(mDisplayContent, task); + task.setWindowingMode(WINDOWING_MODE_FULLSCREEN); + + // Bring home to front of the tasks + desktopOrganizer.bringHomeToFront(); + + // Bring tasks in front of the home + WindowContainerTransaction wct = new WindowContainerTransaction(); + desktopOrganizer.bringDesktopTasksToFront(wct); + applyTransaction(wct); + + // Verify tasks are resumed and in correct z-order + verify(mRootWindowContainer, times(2)).ensureActivitiesVisible(); + for (int i = 0; i < numberOfTasks - 1; i++) { + assertTrue(tda.mChildren + .indexOf(desktopOrganizer.mTasks.get(i).getRootTask()) + < tda.mChildren.indexOf(desktopOrganizer.mTasks.get(i + 1).getRootTask())); + } + } + + @Test + public void testDesktopMode_moveTaskToDesktop() { + final TestDesktopOrganizer desktopOrganizer = new TestDesktopOrganizer(mAtm); + TaskDisplayArea tda = desktopOrganizer.mDefaultTDA; + List<ActivityRecord> activityRecords = new ArrayList<>(); + int numberOfTasks = 4; + desktopOrganizer.createFreeformTasksWithActivities(desktopOrganizer, + activityRecords, numberOfTasks); + + final Task task = createTask(mDisplayContent); + final ActivityRecord activity = createActivityRecord(mDisplayContent, task); + task.setWindowingMode(WINDOWING_MODE_FULLSCREEN); + + // Bring home to front of the tasks + desktopOrganizer.bringHomeToFront(); + + // Bring tasks in front of the home and newly moved task to on top of them + WindowContainerTransaction wct = new WindowContainerTransaction(); + desktopOrganizer.bringDesktopTasksToFront(wct); + desktopOrganizer.addMoveToDesktopChanges(wct, task, true); + wct.setBounds(task.getTaskInfo().token, desktopOrganizer.getDefaultDesktopTaskBounds()); + applyTransaction(wct); + + // Verify tasks are resumed + verify(mRootWindowContainer, times(2)).ensureActivitiesVisible(); + + // Tasks are in correct z-order + for (int i = 0; i < numberOfTasks - 1; i++) { + assertTrue(tda.mChildren + .indexOf(desktopOrganizer.mTasks.get(i).getRootTask()) + < tda.mChildren.indexOf(desktopOrganizer.mTasks.get(i + 1).getRootTask())); + } + // New task is on top of other tasks + assertTrue(tda.mChildren + .indexOf(desktopOrganizer.mTasks.get(3).getRootTask()) + < tda.mChildren.indexOf(task)); + + // New task is in freeform and has specified bounds + assertEquals(WINDOWING_MODE_FREEFORM, task.getWindowingMode()); + assertEquals(desktopOrganizer.getDefaultDesktopTaskBounds(), task.getBounds()); + } + + + @Test + public void testDesktopMode_moveTaskToFullscreen() { + final TestDesktopOrganizer desktopOrganizer = new TestDesktopOrganizer(mAtm); + List<ActivityRecord> activityRecords = new ArrayList<>(); + int numberOfTasks = 4; + desktopOrganizer.createFreeformTasksWithActivities(desktopOrganizer, + activityRecords, numberOfTasks); + + Task taskToMove = desktopOrganizer.mTasks.get(numberOfTasks - 1); + + // Bring tasks in front of the home and newly moved task to on top of them + WindowContainerTransaction wct = new WindowContainerTransaction(); + desktopOrganizer.addMoveToFullscreen(wct, taskToMove, false); + applyTransaction(wct); + + // New task is in freeform + assertEquals(WINDOWING_MODE_FULLSCREEN, taskToMove.getWindowingMode()); + } + + @Test + public void testDesktopMode_moveTaskToFront() { + final TestDesktopOrganizer desktopOrganizer = new TestDesktopOrganizer(mAtm); + TaskDisplayArea tda = desktopOrganizer.mDefaultTDA; + List<ActivityRecord> activityRecords = new ArrayList<>(); + int numberOfTasks = 5; + desktopOrganizer.createFreeformTasksWithActivities(desktopOrganizer, + activityRecords, numberOfTasks); + + // Bring task 2 on top of other tasks + WindowContainerTransaction wct = new WindowContainerTransaction(); + wct.reorder(desktopOrganizer.mTasks.get(2).getTaskInfo().token, true /* onTop */); + applyTransaction(wct); + + // Tasks are in correct z-order + assertTrue(tda.mChildren.indexOf(desktopOrganizer.mTasks.get(0).getRootTask()) + < tda.mChildren.indexOf(desktopOrganizer.mTasks.get(1).getRootTask())); + assertTrue(tda.mChildren.indexOf(desktopOrganizer.mTasks.get(1).getRootTask()) + < tda.mChildren.indexOf(desktopOrganizer.mTasks.get(3).getRootTask())); + assertTrue(tda.mChildren.indexOf(desktopOrganizer.mTasks.get(3).getRootTask()) + < tda.mChildren.indexOf(desktopOrganizer.mTasks.get(4).getRootTask())); + assertTrue(tda.mChildren.indexOf(desktopOrganizer.mTasks.get(4).getRootTask()) + < tda.mChildren.indexOf(desktopOrganizer.mTasks.get(2).getRootTask())); + } + private Task createTask(int taskId) { return new Task.Builder(mAtm) .setTaskId(taskId) @@ -87,3 +210,4 @@ public class WindowContainerTransactionTests extends WindowTestsBase { } } } + diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java index a0bafb64090f..be837441ef94 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java @@ -116,6 +116,7 @@ import android.window.StartingWindowRemovalInfo; import android.window.TaskFragmentOrganizer; import android.window.TransitionInfo; import android.window.TransitionRequestInfo; +import android.window.WindowContainerTransaction; import com.android.internal.policy.AttributeCache; import com.android.internal.util.ArrayUtils; @@ -1899,12 +1900,14 @@ class WindowTestsBase extends SystemServiceTestsBase { final int mDesktopModeDefaultWidthDp = 840; final int mDesktopModeDefaultHeightDp = 630; final int mDesktopDensity = 284; + final int mOverrideDensity = 285; final ActivityTaskManagerService mService; final TaskDisplayArea mDefaultTDA; List<Task> mTasks; final DisplayContent mDisplay; Rect mStableBounds; + Task mHomeTask; TestDesktopOrganizer(ActivityTaskManagerService service, DisplayContent display) { mService = service; @@ -1913,8 +1916,8 @@ class WindowTestsBase extends SystemServiceTestsBase { mService.mTaskOrganizerController.registerTaskOrganizer(this); mTasks = new ArrayList<>(); mStableBounds = display.getBounds(); + mHomeTask = mDefaultTDA.getRootHomeTask(); } - TestDesktopOrganizer(ActivityTaskManagerService service) { this(service, service.mTaskSupervisor.mRootWindowContainer.getDefaultDisplay()); } @@ -1929,8 +1932,10 @@ class WindowTestsBase extends SystemServiceTestsBase { } public Rect getDefaultDesktopTaskBounds() { - int width = (int) (mDesktopModeDefaultWidthDp * mDesktopDensity + 0.5f); - int height = (int) (mDesktopModeDefaultHeightDp * mDesktopDensity + 0.5f); + int width = (int) (mDesktopModeDefaultWidthDp + * (mOverrideDensity / mDesktopDensity) + 0.5f); + int height = (int) (mDesktopModeDefaultHeightDp + * (mOverrideDensity / mDesktopDensity) + 0.5f); Rect outBounds = new Rect(); outBounds.set(0, 0, width, height); @@ -1942,8 +1947,69 @@ class WindowTestsBase extends SystemServiceTestsBase { return outBounds; } + public void createFreeformTasksWithActivities(TestDesktopOrganizer desktopOrganizer, + List<ActivityRecord> activityRecords, int numberOfTasks) { + for (int i = 0; i < numberOfTasks; i++) { + Rect bounds = new Rect(desktopOrganizer.getDefaultDesktopTaskBounds()); + bounds.offset(20 * i, 20 * i); + desktopOrganizer.createTask(bounds); + } + + for (int i = 0; i < numberOfTasks; i++) { + activityRecords.add(new TaskBuilder(mService.mTaskSupervisor) + .setParentTask(desktopOrganizer.mTasks.get(i)) + .setCreateActivity(true) + .build() + .getTopMostActivity()); + } + + for (int i = 0; i < numberOfTasks; i++) { + activityRecords.get(i).setVisibleRequested(true); + } + + for (int i = 0; i < numberOfTasks; i++) { + assertEquals(desktopOrganizer.mTasks.get(i), activityRecords.get(i).getRootTask()); + } + } + + public void bringHomeToFront() { + WindowContainerTransaction wct = new WindowContainerTransaction(); + wct.reorder(mHomeTask.getTaskInfo().token, true /* onTop */); + applyTransaction(wct); + } + + public void bringDesktopTasksToFront(WindowContainerTransaction wct) { + for (Task task: mTasks) { + wct.reorder(task.getTaskInfo().token, true /* onTop */); + } + } + + public void addMoveToDesktopChanges(WindowContainerTransaction wct, Task task, + boolean overrideDensity) { + wct.setWindowingMode(task.getTaskInfo().token, WINDOWING_MODE_FREEFORM); + wct.reorder(task.getTaskInfo().token, true /* onTop */); + if (overrideDensity) { + wct.setDensityDpi(task.getTaskInfo().token, mOverrideDensity); + } + } + + public void addMoveToFullscreen(WindowContainerTransaction wct, Task task, + boolean overrideDensity) { + wct.setWindowingMode(task.getTaskInfo().token, WINDOWING_MODE_FULLSCREEN); + wct.setBounds(task.getTaskInfo().token, new Rect()); + if (overrideDensity) { + wct.setDensityDpi(task.getTaskInfo().token, mOverrideDensity); + } + } + + private void applyTransaction(@androidx.annotation.NonNull WindowContainerTransaction wct) { + if (!wct.isEmpty()) { + mService.mWindowOrganizerController.applyTransaction(wct); + } + } } + static TestWindowToken createTestWindowToken(int type, DisplayContent dc) { return createTestWindowToken(type, dc, false /* persistOnEmpty */); } diff --git a/telecomm/java/android/telecom/PhoneAccount.java b/telecomm/java/android/telecom/PhoneAccount.java index 63db29713825..b7706a926a3d 100644 --- a/telecomm/java/android/telecom/PhoneAccount.java +++ b/telecomm/java/android/telecom/PhoneAccount.java @@ -1283,6 +1283,17 @@ public final class PhoneAccount implements Parcelable { sb.append(mExtras); sb.append(" GroupId: "); sb.append(Log.pii(mGroupId)); + sb.append(" SC Restrictions: "); + if (hasSimultaneousCallingRestriction()) { + sb.append("[ "); + for (PhoneAccountHandle handle : mSimultaneousCallingRestriction) { + sb.append(handle); + sb.append(" "); + } + sb.append("]"); + } else { + sb.append("[NONE]"); + } sb.append("]"); return sb.toString(); } diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index cbd552454642..c1ceaef64d5d 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -25,6 +25,7 @@ import static com.android.internal.util.Preconditions.checkNotNull; import android.Manifest; import android.annotation.BytesLong; import android.annotation.CallbackExecutor; +import android.annotation.CurrentTimeMillisLong; import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.LongDef; @@ -18714,51 +18715,93 @@ public class TelephonyManager { * call diagnostic data * @hide */ - public static class EmergencyCallDiagnosticParams { + @SystemApi + @FlaggedApi(com.android.server.telecom.flags.Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES) + public static final class EmergencyCallDiagnosticParams { + public static final class Builder { + private boolean mCollectTelecomDumpSys; + private boolean mCollectTelephonyDumpsys; + + // If this is set to a value other than -1L, then the logcat collection is enabled. + // Logcat lines with this time or greater are collected how much is collected is + // dependent on internal implementation. Time represented as milliseconds since boot. + private long mLogcatStartTimeMillis = sUnsetLogcatStartTime; + + /** + * Allows enabling of telecom dumpsys collection. + * @param collectTelecomDumpsys Determines whether telecom dumpsys should be collected. + * @return Builder instance corresponding to the configured call diagnostic params. + */ + public @NonNull Builder setTelecomDumpSysCollectionEnabled( + boolean collectTelecomDumpsys) { + mCollectTelecomDumpSys = collectTelecomDumpsys; + return this; + } + + /** + * Allows enabling of telephony dumpsys collection. + * @param collectTelephonyDumpsys Determines if telephony dumpsys should be collected. + * @return Builder instance corresponding to the configured call diagnostic params. + */ + public @NonNull Builder setTelephonyDumpSysCollectionEnabled( + boolean collectTelephonyDumpsys) { + mCollectTelephonyDumpsys = collectTelephonyDumpsys; + return this; + } + + /** + * Allows enabling of logcat (system,radio) collection. + * @param startTimeMillis Enables logcat collection as of the indicated timestamp. + * @return Builder instance corresponding to the configured call diagnostic params. + */ + public @NonNull Builder setLogcatCollectionStartTimeMillis( + @CurrentTimeMillisLong long startTimeMillis) { + mLogcatStartTimeMillis = startTimeMillis; + return this; + } - private boolean mCollectTelecomDumpSys; - private boolean mCollectTelephonyDumpsys; - private boolean mCollectLogcat; + /** + * Build the EmergencyCallDiagnosticParams from the provided Builder config. + * @return {@link EmergencyCallDiagnosticParams} instance from provided builder. + */ + public @NonNull EmergencyCallDiagnosticParams build() { + return new EmergencyCallDiagnosticParams(mCollectTelecomDumpSys, + mCollectTelephonyDumpsys, mLogcatStartTimeMillis); + } + } - //logcat lines with this time or greater are collected - //how much is collected is dependent on internal implementation. - //Time represented as milliseconds since January 1, 1970 UTC + private boolean mCollectTelecomDumpSys; + private boolean mCollectTelephonyDumpsys; + private boolean mCollectLogcat; private long mLogcatStartTimeMillis; + private static long sUnsetLogcatStartTime = -1L; - public boolean isTelecomDumpSysCollectionEnabled() { - return mCollectTelecomDumpSys; + private EmergencyCallDiagnosticParams(boolean collectTelecomDumpSys, + boolean collectTelephonyDumpsys, long logcatStartTimeMillis) { + mCollectTelecomDumpSys = collectTelecomDumpSys; + mCollectTelephonyDumpsys = collectTelephonyDumpsys; + mLogcatStartTimeMillis = logcatStartTimeMillis; + mCollectLogcat = logcatStartTimeMillis != sUnsetLogcatStartTime; } - public void setTelecomDumpSysCollection(boolean collectTelecomDumpSys) { - mCollectTelecomDumpSys = collectTelecomDumpSys; + public boolean isTelecomDumpSysCollectionEnabled() { + return mCollectTelecomDumpSys; } public boolean isTelephonyDumpSysCollectionEnabled() { return mCollectTelephonyDumpsys; } - public void setTelephonyDumpSysCollection(boolean collectTelephonyDumpsys) { - mCollectTelephonyDumpsys = collectTelephonyDumpsys; - } - public boolean isLogcatCollectionEnabled() { return mCollectLogcat; } - public long getLogcatStartTime() + public long getLogcatCollectionStartTimeMillis() { return mLogcatStartTimeMillis; } - public void setLogcatCollection(boolean collectLogcat, long startTimeMillis) { - mCollectLogcat = collectLogcat; - if(mCollectLogcat) - { - mLogcatStartTimeMillis = startTimeMillis; - } - } - @Override public String toString() { return "EmergencyCallDiagnosticParams{" + @@ -18774,11 +18817,12 @@ public class TelephonyManager { * Request telephony to persist state for debugging emergency call failures. * * @param dropboxTag Tag to use when persisting data to dropbox service. - * - * @see params Parameters controlling what is collected + * @param params Parameters controlling what is collected. * * @hide */ + @SystemApi + @FlaggedApi(com.android.server.telecom.flags.Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES) @RequiresPermission(android.Manifest.permission.DUMP) public void persistEmergencyCallDiagnosticData(@NonNull String dropboxTag, @NonNull EmergencyCallDiagnosticParams params) { @@ -18791,7 +18835,7 @@ public class TelephonyManager { if (telephony != null) { telephony.persistEmergencyCallDiagnosticData(dropboxTag, params.isLogcatCollectionEnabled(), - params.getLogcatStartTime(), + params.getLogcatCollectionStartTimeMillis(), params.isTelecomDumpSysCollectionEnabled(), params.isTelephonyDumpSysCollectionEnabled()); } diff --git a/telephony/java/android/telephony/satellite/stub/INtnSignalStrengthConsumer.aidl b/telephony/java/android/telephony/satellite/stub/INtnSignalStrengthConsumer.aidl index b7712bd83cf6..655da740012b 100644 --- a/telephony/java/android/telephony/satellite/stub/INtnSignalStrengthConsumer.aidl +++ b/telephony/java/android/telephony/satellite/stub/INtnSignalStrengthConsumer.aidl @@ -16,7 +16,7 @@ package android.telephony.satellite.stub; -import android.telephony.satellite.NtnSignalStrength; +import android.telephony.satellite.stub.NtnSignalStrength; /** * Consumer pattern for a request that receives the signal strength of non-terrestrial network from diff --git a/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/EmbeddedWindowService.java b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/EmbeddedWindowService.java index 5aaf30a5b3a7..14230fe4c323 100644 --- a/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/EmbeddedWindowService.java +++ b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/EmbeddedWindowService.java @@ -134,7 +134,7 @@ public class EmbeddedWindowService extends Service { c.drawText("Remote", 250, 250, paint); surface.unlockCanvasAndPost(c); WindowManager wm = getSystemService(WindowManager.class); - mInputToken = wm.registerBatchedSurfaceControlInputReceiver(displayId, hostToken, + wm.registerBatchedSurfaceControlInputReceiver(displayId, hostToken, mSurfaceControl, Choreographer.getInstance(), event -> { Log.d(TAG, "onInputEvent-remote " + event); @@ -147,11 +147,9 @@ public class EmbeddedWindowService extends Service { @Override public void tearDownEmbeddedSurfaceControl() { if (mSurfaceControl != null) { - new SurfaceControl.Transaction().remove(mSurfaceControl); - } - if (mInputToken != null) { WindowManager wm = getSystemService(WindowManager.class); - wm.unregisterSurfaceControlInputReceiver(mInputToken); + wm.unregisterSurfaceControlInputReceiver(mSurfaceControl); + new SurfaceControl.Transaction().remove(mSurfaceControl).apply(); } } } diff --git a/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceInputTestActivity.java b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceInputTestActivity.java index e5f8f47aeecd..7330ec14011b 100644 --- a/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceInputTestActivity.java +++ b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceInputTestActivity.java @@ -50,10 +50,11 @@ public class SurfaceInputTestActivity extends Activity { private static final String TAG = "SurfaceInputTestActivity"; private SurfaceView mLocalSurfaceView; private SurfaceView mRemoteSurfaceView; - private IBinder mInputToken; private IAttachEmbeddedWindow mIAttachEmbeddedWindow; private SurfaceControl mParentSurfaceControl; + private SurfaceControl mLocalSurfaceControl; + private final ServiceConnection mConnection = new ServiceConnection() { // Called when the connection with the service is established public void onServiceConnected(ComponentName className, IBinder service) { @@ -112,30 +113,33 @@ public class SurfaceInputTestActivity extends Activity { @Override protected void onDestroy() { super.onDestroy(); - getWindowManager().unregisterSurfaceControlInputReceiver(mInputToken); + if (mLocalSurfaceControl != null) { + getWindowManager().unregisterSurfaceControlInputReceiver(mLocalSurfaceControl); + new SurfaceControl.Transaction().remove(mLocalSurfaceControl).apply(); + } } private void addLocalChildSurfaceControl(AttachedSurfaceControl attachedSurfaceControl) { - SurfaceControl surfaceControl = new SurfaceControl.Builder().setName("LocalSC") + mLocalSurfaceControl = new SurfaceControl.Builder().setName("LocalSC") .setBufferSize(100, 100).build(); - attachedSurfaceControl.buildReparentTransaction(surfaceControl) - .setVisibility(surfaceControl, true) - .setCrop(surfaceControl, new Rect(0, 0, 100, 100)) - .setPosition(surfaceControl, 250, 1000) - .setLayer(surfaceControl, 1).apply(); + attachedSurfaceControl.buildReparentTransaction(mLocalSurfaceControl) + .setVisibility(mLocalSurfaceControl, true) + .setCrop(mLocalSurfaceControl, new Rect(0, 0, 100, 100)) + .setPosition(mLocalSurfaceControl, 250, 1000) + .setLayer(mLocalSurfaceControl, 1).apply(); Paint paint = new Paint(); paint.setColor(Color.WHITE); paint.setTextSize(20); - Surface surface = new Surface(surfaceControl); + Surface surface = new Surface(mLocalSurfaceControl); Canvas c = surface.lockCanvas(null); c.drawColor(Color.GREEN); c.drawText("Local SC", 0, 0, paint); surface.unlockCanvasAndPost(c); WindowManager wm = getSystemService(WindowManager.class); - mInputToken = wm.registerBatchedSurfaceControlInputReceiver(getDisplayId(), - attachedSurfaceControl.getHostToken(), surfaceControl, + wm.registerBatchedSurfaceControlInputReceiver(getDisplayId(), + attachedSurfaceControl.getHostToken(), mLocalSurfaceControl, Choreographer.getInstance(), event -> { Log.d(TAG, "onInputEvent-sc " + event); return false; @@ -143,8 +147,6 @@ public class SurfaceInputTestActivity extends Activity { } private final SurfaceHolder.Callback mLocalSurfaceViewCallback = new SurfaceHolder.Callback() { - private IBinder mInputToken; - @Override public void surfaceCreated(@NonNull SurfaceHolder holder) { Paint paint = new Paint(); @@ -157,7 +159,7 @@ public class SurfaceInputTestActivity extends Activity { holder.unlockCanvasAndPost(c); WindowManager wm = getSystemService(WindowManager.class); - mInputToken = wm.registerBatchedSurfaceControlInputReceiver(getDisplayId(), + wm.registerBatchedSurfaceControlInputReceiver(getDisplayId(), mLocalSurfaceView.getHostToken(), mLocalSurfaceView.getSurfaceControl(), Choreographer.getInstance(), event -> { Log.d(TAG, "onInputEvent-local " + event); @@ -173,9 +175,8 @@ public class SurfaceInputTestActivity extends Activity { @Override public void surfaceDestroyed(@NonNull SurfaceHolder holder) { - if (mInputToken != null) { - getWindowManager().unregisterSurfaceControlInputReceiver(mInputToken); - } + getWindowManager().unregisterSurfaceControlInputReceiver( + mLocalSurfaceView.getSurfaceControl()); } }; diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp index 0b16e2c7efe4..d03f97e28156 100644 --- a/tools/aapt2/link/ManifestFixer.cpp +++ b/tools/aapt2/link/ManifestFixer.cpp @@ -415,6 +415,8 @@ bool ManifestFixer::BuildRules(xml::XmlActionExecutor* executor, android::IDiagn intent_filter_action["action"].Action(RequiredNameIsNotEmpty); intent_filter_action["category"].Action(RequiredNameIsNotEmpty); intent_filter_action["data"]; + intent_filter_action["uri-relative-filter-group"]; + intent_filter_action["uri-relative-filter-group"]["data"]; // Common <meta-data> actions. xml::XmlNodeAction meta_data_action; |