diff options
1458 files changed, 48939 insertions, 11239 deletions
diff --git a/Android.bp b/Android.bp index cc754f21902a..72bd6427fab2 100644 --- a/Android.bp +++ b/Android.bp @@ -136,6 +136,7 @@ filegroup { ":libcamera_client_aidl", ":libcamera_client_framework_aidl", ":libupdate_engine_aidl", + ":logd_aidl", ":resourcemanager_aidl", ":storaged_aidl", ":vold_aidl", @@ -177,6 +178,7 @@ java_library_with_nonpublic_deps { soong_config_variables: { include_nonpublic_framework_api: { static_libs: [ + "framework-auxiliary.impl", "framework-supplementalapi.impl", ], }, @@ -538,7 +540,9 @@ filegroup { visibility: ["//visibility:private"], } -// These defaults are used for both the jar stubs and the doc stubs. +// Defaults for all stubs that include the non-updatable framework. These defaults do not include +// module symbols, so will not compile correctly on their own. Users must add module APIs to the +// classpath (or sources) somehow. stubs_defaults { name: "android-non-updatable-stubs-defaults", srcs: [":android-non-updatable-stub-sources"], @@ -546,17 +550,14 @@ stubs_defaults { system_modules: "none", java_version: "1.8", arg_files: ["core/res/AndroidManifest.xml"], - // TODO(b/147699819): remove below aidl includes. aidl: { local_include_dirs: [ - "apex/media/aidl/stable", "media/aidl", "telephony/java", ], include_dirs: [ "frameworks/av/aidl", "frameworks/native/libs/permission/aidl", - "packages/modules/Connectivity/framework/aidl-export", ], }, // These are libs from framework-internal-utils that are required (i.e. being referenced) @@ -576,6 +577,30 @@ stubs_defaults { "android.hardware.usb.gadget-V1.0-java", "android.hardware.vibrator-V1.3-java", "framework-protos", + ], + filter_packages: packages_to_document, + high_mem: true, // Lots of sources => high memory use, see b/170701554 + installable: false, + annotations_enabled: true, + previous_api: ":android.api.public.latest", + merge_annotations_dirs: ["metalava-manual"], + defaults_visibility: ["//visibility:private"], + visibility: ["//frameworks/base/api"], +} + +// Defaults with module APIs in the classpath (mostly from prebuilts). +// Suitable for compiling android-non-updatable. +stubs_defaults { + name: "module-classpath-stubs-defaults", + aidl: { + local_include_dirs: [ + "apex/media/aidl/stable", + ], + include_dirs: [ + "packages/modules/Connectivity/framework/aidl-export", + ], + }, + libs: [ "art.module.public.api", "sdk_module-lib_current_framework-tethering", // There are a few classes from modules used by the core that @@ -586,14 +611,7 @@ stubs_defaults { // NOTE: The below can be removed once the prebuilt stub contains IKE. "sdk_system_current_android.net.ipsec.ike", ], - filter_packages: packages_to_document, - high_mem: true, // Lots of sources => high memory use, see b/170701554 - installable: false, - annotations_enabled: true, - previous_api: ":android.api.public.latest", - merge_annotations_dirs: ["metalava-manual"], defaults_visibility: ["//visibility:private"], - visibility: ["//frameworks/base/api"], } build = [ diff --git a/ApiDocs.bp b/ApiDocs.bp index 4aecc8fe4504..ca211c188dc8 100644 --- a/ApiDocs.bp +++ b/ApiDocs.bp @@ -57,7 +57,10 @@ framework_docs_only_libs = [ stubs_defaults { name: "android-non-updatable-doc-stubs-defaults", - defaults: ["android-non-updatable-stubs-defaults"], + defaults: [ + "android-non-updatable-stubs-defaults", + "module-classpath-stubs-defaults", + ], srcs: [ // No longer part of the stubs, but are included in the docs. ":android-test-base-sources", @@ -116,6 +119,7 @@ stubs_defaults { ":i18n.module.public.api{.public.stubs.source}", ":framework-appsearch-sources", + ":framework-auxiliary-sources", ":framework-connectivity-sources", ":framework-connectivity-tiramisu-updatable-sources", ":framework-graphics-srcs", diff --git a/StubLibraries.bp b/StubLibraries.bp index 77b26f80ab97..7f7380a7b41b 100644 --- a/StubLibraries.bp +++ b/StubLibraries.bp @@ -32,23 +32,15 @@ soong_config_module_type_import { } ///////////////////////////////////////////////////////////////////// -// Common metalava configs -///////////////////////////////////////////////////////////////////// - -stubs_defaults { - name: "metalava-non-updatable-api-stubs-default", - defaults: ["android-non-updatable-stubs-defaults"], - api_levels_annotations_enabled: false, - defaults_visibility: ["//visibility:private"], -} - -///////////////////////////////////////////////////////////////////// // These modules provide source files for the stub libraries ///////////////////////////////////////////////////////////////////// droidstubs { name: "api-stubs-docs-non-updatable", - defaults: ["metalava-non-updatable-api-stubs-default"], + defaults: [ + "android-non-updatable-stubs-defaults", + "module-classpath-stubs-defaults", + ], args: metalava_framework_docs_args, check_api: { current: { @@ -97,7 +89,10 @@ module_libs = " --show-annotation android.annotation.SystemApi\\(" + droidstubs { name: "system-api-stubs-docs-non-updatable", - defaults: ["metalava-non-updatable-api-stubs-default"], + defaults: [ + "android-non-updatable-stubs-defaults", + "module-classpath-stubs-defaults", + ], args: metalava_framework_docs_args + priv_apps, check_api: { current: { @@ -133,7 +128,10 @@ droidstubs { droidstubs { name: "test-api-stubs-docs-non-updatable", - defaults: ["metalava-non-updatable-api-stubs-default"], + defaults: [ + "android-non-updatable-stubs-defaults", + "module-classpath-stubs-defaults", + ], args: metalava_framework_docs_args + test + priv_apps_in_stubs, check_api: { current: { @@ -175,7 +173,10 @@ droidstubs { droidstubs { name: "module-lib-api-stubs-docs-non-updatable", - defaults: ["metalava-non-updatable-api-stubs-default"], + defaults: [ + "android-non-updatable-stubs-defaults", + "module-classpath-stubs-defaults", + ], args: metalava_framework_docs_args + priv_apps_in_stubs + module_libs, check_api: { current: { @@ -285,6 +286,7 @@ java_library_with_nonpublic_deps { soong_config_variables: { include_nonpublic_framework_api: { libs: [ + "framework-auxiliary.stubs", "framework-supplementalapi.stubs", ], }, @@ -302,6 +304,7 @@ java_library_with_nonpublic_deps { soong_config_variables: { include_nonpublic_framework_api: { libs: [ + "framework-auxiliary.stubs", "framework-supplementalapi.stubs", ], }, @@ -334,6 +337,7 @@ java_library_with_nonpublic_deps { soong_config_variables: { include_nonpublic_framework_api: { libs: [ + "framework-auxiliary.stubs", "framework-supplementalapi.stubs", ], }, @@ -362,6 +366,7 @@ java_library_with_nonpublic_deps { soong_config_variables: { include_nonpublic_framework_api: { static_libs: [ + "framework-auxiliary.stubs", "framework-supplementalapi.stubs", ], }, @@ -378,6 +383,7 @@ java_library_with_nonpublic_deps { soong_config_variables: { include_nonpublic_framework_api: { static_libs: [ + "framework-auxiliary.stubs", "framework-supplementalapi.stubs", ], }, @@ -410,6 +416,7 @@ java_library_with_nonpublic_deps { soong_config_variables: { include_nonpublic_framework_api: { static_libs: [ + "framework-auxiliary.stubs", "framework-supplementalapi.stubs", ], }, diff --git a/apct-tests/perftests/core/src/android/view/HandwritingInitiatorPerfTest.java b/apct-tests/perftests/core/src/android/view/HandwritingInitiatorPerfTest.java index cf94e9e0d384..4cd974141d26 100644 --- a/apct-tests/perftests/core/src/android/view/HandwritingInitiatorPerfTest.java +++ b/apct-tests/perftests/core/src/android/view/HandwritingInitiatorPerfTest.java @@ -25,7 +25,6 @@ import static android.view.MotionEvent.TOOL_TYPE_STYLUS; import android.app.Instrumentation; import android.content.Context; -import android.graphics.Rect; import android.perftests.utils.BenchmarkState; import android.perftests.utils.PerfStatusReporter; import android.view.inputmethod.EditorInfo; @@ -190,7 +189,7 @@ public class HandwritingInitiatorPerfTest { final View view = new View(mContext); final EditorInfo editorInfo = new EditorInfo(); while (state.keepRunning()) { - mHandwritingInitiator.onInputConnectionCreated(view, editorInfo); + mHandwritingInitiator.onInputConnectionCreated(view); state.pauseTiming(); mHandwritingInitiator.onInputConnectionClosed(view); state.resumeTiming(); @@ -201,24 +200,14 @@ public class HandwritingInitiatorPerfTest { public void onInputConnectionClosed() { final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); final View view = new View(mContext); - final EditorInfo editorInfo = new EditorInfo(); while (state.keepRunning()) { state.pauseTiming(); - mHandwritingInitiator.onInputConnectionCreated(view, editorInfo); + mHandwritingInitiator.onInputConnectionCreated(view); state.resumeTiming(); mHandwritingInitiator.onInputConnectionClosed(view); } } - @Test - public void updateEditorBoundary() { - final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); - final Rect rect = new Rect(0, 0, 100, 100); - while (state.keepRunning()) { - mHandwritingInitiator.updateEditorBound(rect); - } - } - private MotionEvent createMotionEvent(int action, int toolType, int x, int y, long eventTime) { MotionEvent.PointerProperties[] properties = MotionEvent.PointerProperties.createArray(1); properties[0].toolType = toolType; diff --git a/apex/blobstore/framework/java/android/app/blob/BlobInfo.java b/apex/blobstore/framework/java/android/app/blob/BlobInfo.java index ba92d95b483e..73ef310c7b40 100644 --- a/apex/blobstore/framework/java/android/app/blob/BlobInfo.java +++ b/apex/blobstore/framework/java/android/app/blob/BlobInfo.java @@ -48,6 +48,7 @@ public final class BlobInfo implements Parcelable { mLeaseInfos = leaseInfos; } + @SuppressWarnings("UnsafeParcelApi") private BlobInfo(Parcel in) { mId = in.readLong(); mExpiryTimeMs = in.readLong(); diff --git a/apex/jobscheduler/framework/java/android/app/AlarmManager.java b/apex/jobscheduler/framework/java/android/app/AlarmManager.java index 9c0c3657bff3..66767e21a2e7 100644 --- a/apex/jobscheduler/framework/java/android/app/AlarmManager.java +++ b/apex/jobscheduler/framework/java/android/app/AlarmManager.java @@ -1408,6 +1408,7 @@ public class AlarmManager { * Use the {@link #CREATOR} * @hide */ + @SuppressWarnings("UnsafeParcelApi") AlarmClockInfo(Parcel in) { mTriggerTime = in.readLong(); mShowIntent = in.readParcelable(PendingIntent.class.getClassLoader()); diff --git a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java index 0e6006a62397..b9673f25d680 100644 --- a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java +++ b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java @@ -881,6 +881,7 @@ public class JobInfo implements Parcelable { return hashCode; } + @SuppressWarnings("UnsafeParcelApi") private JobInfo(Parcel in) { jobId = in.readInt(); extras = in.readPersistableBundle(); 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 12a8654c61f7..d93ad3c0d8bc 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java @@ -1633,12 +1633,9 @@ public class JobSchedulerService extends com.android.server.SystemService for (int i=0; i<mActiveServices.size(); i++) { final JobServiceContext jsc = mActiveServices.get(i); final JobStatus job = jsc.getRunningJobLocked(); - if (job != null - && !job.canRunInDoze() - && !job.dozeWhitelisted - && !job.uidActive) { - // We will report active if we have a job running and it is not an exception - // due to being in the foreground or whitelisted. + if (job != null && !job.canRunInDoze()) { + // We will report active if we have a job running and it does not have an + // exception that allows it to run in Doze. active = true; break; } diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/DeviceIdleJobsController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/DeviceIdleJobsController.java index 090260cb8eb3..f6de109d7ec9 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/DeviceIdleJobsController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/DeviceIdleJobsController.java @@ -246,7 +246,7 @@ public final class DeviceIdleJobsController extends StateController { pw.print((jobStatus.satisfiedConstraints & JobStatus.CONSTRAINT_DEVICE_NOT_DOZING) != 0 ? " RUNNABLE" : " WAITING"); - if (jobStatus.dozeWhitelisted) { + if (jobStatus.appHasDozeExemption) { pw.print(" WHITELISTED"); } if (mAllowInIdleJobs.contains(jobStatus)) { @@ -273,7 +273,7 @@ public final class DeviceIdleJobsController extends StateController { proto.write(TrackedJob.SOURCE_PACKAGE_NAME, jobStatus.getSourcePackageName()); proto.write(TrackedJob.ARE_CONSTRAINTS_SATISFIED, (jobStatus.satisfiedConstraints & JobStatus.CONSTRAINT_DEVICE_NOT_DOZING) != 0); - proto.write(TrackedJob.IS_DOZE_WHITELISTED, jobStatus.dozeWhitelisted); + proto.write(TrackedJob.IS_DOZE_WHITELISTED, jobStatus.appHasDozeExemption); proto.write(TrackedJob.IS_ALLOWED_IN_DOZE, mAllowInIdleJobs.contains(jobStatus)); proto.end(jsToken); 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 f74a4facd24c..0eea70106608 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 @@ -267,7 +267,7 @@ public final class JobStatus { private final boolean mHasMediaBackupExemption; // Set to true if doze constraint was satisfied due to app being whitelisted. - public boolean dozeWhitelisted; + boolean appHasDozeExemption; // Set to true when the app is "active" per AppStateTracker public boolean uidActive; @@ -1179,7 +1179,8 @@ public final class JobStatus { * in Doze. */ public boolean canRunInDoze() { - return (getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0 + return appHasDozeExemption + || (getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0 || ((shouldTreatAsExpeditedJob() || startedAsExpeditedJob) && (mDynamicConstraints & CONSTRAINT_DEVICE_NOT_DOZING) == 0); } @@ -1243,7 +1244,7 @@ public final class JobStatus { /** @return true if the constraint was changed, false otherwise. */ boolean setDeviceNotDozingConstraintSatisfied(final long nowElapsed, boolean state, boolean whitelisted) { - dozeWhitelisted = whitelisted; + appHasDozeExemption = whitelisted; if (setConstraintSatisfied(CONSTRAINT_DEVICE_NOT_DOZING, nowElapsed, state)) { // The constraint was changed. Update the ready flag. mReadyNotDozing = state || canRunInDoze(); @@ -2110,7 +2111,7 @@ public final class JobStatus { } pw.decreaseIndent(); - if (dozeWhitelisted) { + if (appHasDozeExemption) { pw.println("Doze whitelisted: true"); } if (uidActive) { @@ -2323,7 +2324,7 @@ public final class JobStatus { dumpConstraints(proto, JobStatusDumpProto.SATISFIED_CONSTRAINTS, satisfiedConstraints); dumpConstraints(proto, JobStatusDumpProto.UNSATISFIED_CONSTRAINTS, ((requiredConstraints | CONSTRAINT_WITHIN_QUOTA) & ~satisfiedConstraints)); - proto.write(JobStatusDumpProto.IS_DOZE_WHITELISTED, dozeWhitelisted); + proto.write(JobStatusDumpProto.IS_DOZE_WHITELISTED, appHasDozeExemption); proto.write(JobStatusDumpProto.IS_UID_ACTIVE, uidActive); proto.write(JobStatusDumpProto.IS_EXEMPTED_FROM_APP_STANDBY, job.isExemptedFromAppStandby()); diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java index 65e1d49d1510..dd5246aebbb4 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java @@ -36,6 +36,7 @@ import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.AlarmManager; import android.app.IUidObserver; +import android.app.job.JobInfo; import android.app.usage.UsageEvents; import android.app.usage.UsageStatsManagerInternal; import android.app.usage.UsageStatsManagerInternal.UsageEventListener; @@ -161,6 +162,28 @@ public final class QuotaController extends StateController { public long inQuotaTimeElapsed; /** + * The time after which the app will be under the bucket quota and can start running + * low priority jobs again. This is only valid if + * {@link #executionTimeInWindowMs} >= + * {@link #mAllowedTimePerPeriodMs} * (1 - {@link #mAllowedTimeSurplusPriorityLow}), + * {@link #executionTimeInMaxPeriodMs} >= {@link #mMaxExecutionTimeMs}, + * {@link #bgJobCountInWindow} >= {@link #jobCountLimit}, or + * {@link #sessionCountInWindow} >= {@link #sessionCountLimit}. + */ + public long inQuotaTimeLowElapsed; + + /** + * The time after which the app will be under the bucket quota and can start running + * min priority jobs again. This is only valid if + * {@link #executionTimeInWindowMs} >= + * {@link #mAllowedTimePerPeriodMs} * (1 - {@link #mAllowedTimeSurplusPriorityMin}), + * {@link #executionTimeInMaxPeriodMs} >= {@link #mMaxExecutionTimeMs}, + * {@link #bgJobCountInWindow} >= {@link #jobCountLimit}, or + * {@link #sessionCountInWindow} >= {@link #sessionCountLimit}. + */ + public long inQuotaTimeMinElapsed; + + /** * The time after which {@link #jobCountInRateLimitingWindow} should be considered invalid, * in the elapsed realtime timebase. */ @@ -199,6 +222,8 @@ public final class QuotaController extends StateController { + "bgJobCountInMaxPeriod=" + bgJobCountInMaxPeriod + ", " + "sessionCountInWindow=" + sessionCountInWindow + ", " + "inQuotaTime=" + inQuotaTimeElapsed + ", " + + "inQuotaTimeLow=" + inQuotaTimeLowElapsed + ", " + + "inQuotaTimeMin=" + inQuotaTimeMinElapsed + ", " + "rateLimitJobCountExpirationTime=" + jobRateLimitExpirationTimeElapsed + ", " + "rateLimitJobCountWindow=" + jobCountInRateLimitingWindow + ", " + "rateLimitSessionCountExpirationTime=" @@ -351,6 +376,24 @@ public final class QuotaController extends StateController { */ private long mMaxExecutionTimeIntoQuotaMs = mMaxExecutionTimeMs - mQuotaBufferMs; + /** + * The percentage of {@link #mAllowedTimePerPeriodMs} that should not be used by + * {@link JobInfo#PRIORITY_LOW low priority} jobs. In other words, there must be a minimum + * surplus of this amount of remaining allowed time before we start running low priority + * jobs. + */ + private float mAllowedTimeSurplusPriorityLow = + QcConstants.DEFAULT_ALLOWED_TIME_SURPLUS_PRIORITY_LOW; + + /** + * The percentage of {@link #mAllowedTimePerPeriodMs} that should not be used by + * {@link JobInfo#PRIORITY_MIN min priority} jobs. In other words, there must be a minimum + * surplus of this amount of remaining allowed time before we start running low priority + * jobs. + */ + private float mAllowedTimeSurplusPriorityMin = + QcConstants.DEFAULT_ALLOWED_TIME_SURPLUS_PRIORITY_MIN; + /** The period of time used to rate limit recently run jobs. */ private long mRateLimitingWindowMs = QcConstants.DEFAULT_RATE_LIMITING_WINDOW_MS; @@ -653,10 +696,11 @@ public final class QuotaController extends StateController { boolean forUpdate) { if (jobStatus.clearTrackingController(JobStatus.TRACKING_QUOTA)) { unprepareFromExecutionLocked(jobStatus); - ArraySet<JobStatus> jobs = mTrackedJobs.get(jobStatus.getSourceUserId(), - jobStatus.getSourcePackageName()); - if (jobs != null) { - jobs.remove(jobStatus); + final int userId = jobStatus.getSourceUserId(); + final String pkgName = jobStatus.getSourcePackageName(); + ArraySet<JobStatus> jobs = mTrackedJobs.get(userId, pkgName); + if (jobs != null && jobs.remove(jobStatus) && jobs.size() == 0) { + mInQuotaAlarmQueue.removeAlarmForKey(new Package(userId, pkgName)); } } } @@ -771,7 +815,8 @@ public final class QuotaController extends StateController { return mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS; } return getTimeUntilQuotaConsumedLocked( - jobStatus.getSourceUserId(), jobStatus.getSourcePackageName()); + jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(), + jobStatus.getEffectivePriority()); } // Expedited job. @@ -856,7 +901,8 @@ public final class QuotaController extends StateController { return isTopStartedJobLocked(jobStatus) || isUidInForeground(jobStatus.getSourceUid()) || isWithinQuotaLocked( - jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(), standbyBucket); + jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(), standbyBucket, + jobStatus.getEffectivePriority()); } @GuardedBy("mLock") @@ -873,7 +919,7 @@ public final class QuotaController extends StateController { @VisibleForTesting @GuardedBy("mLock") boolean isWithinQuotaLocked(final int userId, @NonNull final String packageName, - final int standbyBucket) { + final int standbyBucket, final int priority) { if (!mIsEnabled) { return true; } @@ -881,9 +927,16 @@ public final class QuotaController extends StateController { if (isQuotaFreeLocked(standbyBucket)) return true; + final long minSurplus; + if (priority <= JobInfo.PRIORITY_MIN) { + minSurplus = (long) (mAllowedTimePerPeriodMs * mAllowedTimeSurplusPriorityMin); + } else if (priority <= JobInfo.PRIORITY_LOW) { + minSurplus = (long) (mAllowedTimePerPeriodMs * mAllowedTimeSurplusPriorityLow); + } else { + minSurplus = 0; + } ExecutionStats stats = getExecutionStatsLocked(userId, packageName, standbyBucket); - // TODO: use a higher minimum remaining time for jobs with MINIMUM priority - return getRemainingExecutionTimeLocked(stats) > 0 + return getRemainingExecutionTimeLocked(stats) > minSurplus && isUnderJobCountQuotaLocked(stats, standbyBucket) && isUnderSessionCountQuotaLocked(stats, standbyBucket); } @@ -1001,7 +1054,8 @@ public final class QuotaController extends StateController { * job is running. */ @VisibleForTesting - long getTimeUntilQuotaConsumedLocked(final int userId, @NonNull final String packageName) { + long getTimeUntilQuotaConsumedLocked(final int userId, @NonNull final String packageName, + @JobInfo.Priority int jobPriority) { final long nowElapsed = sElapsedRealtimeClock.millis(); final int standbyBucket = JobSchedulerService.standbyBucketForPackage( packageName, userId, nowElapsed); @@ -1022,10 +1076,15 @@ public final class QuotaController extends StateController { final long startWindowElapsed = nowElapsed - stats.windowSizeMs; final long startMaxElapsed = nowElapsed - MAX_PERIOD_MS; - final long allowedTimeRemainingMs = mAllowedTimePerPeriodMs - stats.executionTimeInWindowMs; + final long allowedTimePerPeriodMs = getAllowedTimePerPeriodMs(jobPriority); + final long allowedTimeRemainingMs = allowedTimePerPeriodMs - stats.executionTimeInWindowMs; final long maxExecutionTimeRemainingMs = mMaxExecutionTimeMs - stats.executionTimeInMaxPeriodMs; + if (allowedTimeRemainingMs <= 0 || maxExecutionTimeRemainingMs <= 0) { + return 0; + } + // Regular ACTIVE case. Since the bucket size equals the allowed time, the app jobs can // essentially run until they reach the maximum limit. if (stats.windowSizeMs == mAllowedTimePerPeriodMs) { @@ -1044,6 +1103,16 @@ public final class QuotaController extends StateController { sessions, startWindowElapsed, allowedTimeRemainingMs)); } + private long getAllowedTimePerPeriodMs(@JobInfo.Priority int jobPriority) { + if (jobPriority <= JobInfo.PRIORITY_MIN) { + return (long) (mAllowedTimePerPeriodMs * (1 - mAllowedTimeSurplusPriorityMin)); + } + if (jobPriority <= JobInfo.PRIORITY_LOW) { + return (long) (mAllowedTimePerPeriodMs * (1 - mAllowedTimeSurplusPriorityLow)); + } + return mAllowedTimePerPeriodMs; + } + /** * Calculates how much time it will take, in milliseconds, until the quota is fully consumed. * @@ -1198,10 +1267,13 @@ public final class QuotaController extends StateController { stats.sessionCountInWindow = 0; if (stats.jobCountLimit == 0 || stats.sessionCountLimit == 0) { // App won't be in quota until configuration changes. - stats.inQuotaTimeElapsed = Long.MAX_VALUE; + stats.inQuotaTimeElapsed = stats.inQuotaTimeLowElapsed = stats.inQuotaTimeMinElapsed = + Long.MAX_VALUE; } else { stats.inQuotaTimeElapsed = 0; } + final long allowedTimeMinMs = getAllowedTimePerPeriodMs(JobInfo.PRIORITY_MIN); + final long allowedTimeLowMs = getAllowedTimePerPeriodMs(JobInfo.PRIORITY_LOW); Timer timer = mPkgTimers.get(userId, packageName); final long nowElapsed = sElapsedRealtimeClock.millis(); @@ -1219,13 +1291,25 @@ public final class QuotaController extends StateController { stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed, nowElapsed - mAllowedTimeIntoQuotaMs + stats.windowSizeMs); } + if (stats.executionTimeInWindowMs >= allowedTimeLowMs) { + stats.inQuotaTimeLowElapsed = Math.max(stats.inQuotaTimeLowElapsed, + nowElapsed - allowedTimeLowMs + stats.windowSizeMs); + } + if (stats.executionTimeInWindowMs >= allowedTimeMinMs) { + stats.inQuotaTimeMinElapsed = Math.max(stats.inQuotaTimeMinElapsed, + nowElapsed - allowedTimeMinMs + stats.windowSizeMs); + } if (stats.executionTimeInMaxPeriodMs >= mMaxExecutionTimeIntoQuotaMs) { - stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed, - nowElapsed - mMaxExecutionTimeIntoQuotaMs + MAX_PERIOD_MS); + final long inQuotaTime = nowElapsed - mMaxExecutionTimeIntoQuotaMs + MAX_PERIOD_MS; + stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed, inQuotaTime); + stats.inQuotaTimeLowElapsed = Math.max(stats.inQuotaTimeLowElapsed, inQuotaTime); + stats.inQuotaTimeMinElapsed = Math.max(stats.inQuotaTimeMinElapsed, inQuotaTime); } if (stats.bgJobCountInWindow >= stats.jobCountLimit) { - stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed, - nowElapsed + stats.windowSizeMs); + final long inQuotaTime = nowElapsed + stats.windowSizeMs; + stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed, inQuotaTime); + stats.inQuotaTimeLowElapsed = Math.max(stats.inQuotaTimeLowElapsed, inQuotaTime); + stats.inQuotaTimeMinElapsed = Math.max(stats.inQuotaTimeMinElapsed, inQuotaTime); } } @@ -1267,9 +1351,23 @@ public final class QuotaController extends StateController { start + stats.executionTimeInWindowMs - mAllowedTimeIntoQuotaMs + stats.windowSizeMs); } + if (stats.executionTimeInWindowMs >= allowedTimeLowMs) { + stats.inQuotaTimeLowElapsed = Math.max(stats.inQuotaTimeLowElapsed, + start + stats.executionTimeInWindowMs - allowedTimeLowMs + + stats.windowSizeMs); + } + if (stats.executionTimeInWindowMs >= allowedTimeMinMs) { + stats.inQuotaTimeMinElapsed = Math.max(stats.inQuotaTimeMinElapsed, + start + stats.executionTimeInWindowMs - allowedTimeMinMs + + stats.windowSizeMs); + } if (stats.bgJobCountInWindow >= stats.jobCountLimit) { - stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed, - session.endTimeElapsed + stats.windowSizeMs); + final long inQuotaTime = session.endTimeElapsed + stats.windowSizeMs; + stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed, inQuotaTime); + stats.inQuotaTimeLowElapsed = Math.max(stats.inQuotaTimeLowElapsed, + inQuotaTime); + stats.inQuotaTimeMinElapsed = Math.max(stats.inQuotaTimeMinElapsed, + inQuotaTime); } if (i == loopStart || (sessions.get(i + 1).startTimeElapsed - session.endTimeElapsed) @@ -1278,8 +1376,12 @@ public final class QuotaController extends StateController { sessionCountInWindow++; if (sessionCountInWindow >= stats.sessionCountLimit) { - stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed, - session.endTimeElapsed + stats.windowSizeMs); + final long inQuotaTime = session.endTimeElapsed + stats.windowSizeMs; + stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed, inQuotaTime); + stats.inQuotaTimeLowElapsed = Math.max(stats.inQuotaTimeLowElapsed, + inQuotaTime); + stats.inQuotaTimeMinElapsed = Math.max(stats.inQuotaTimeMinElapsed, + inQuotaTime); } } } @@ -1425,10 +1527,9 @@ public final class QuotaController extends StateController { synchronized (mLock) { final long nowElapsed = sElapsedRealtimeClock.millis(); final ShrinkableDebits quota = getEJDebitsLocked(userId, packageName); - if (transactQuotaLocked(userId, packageName, nowElapsed, quota, credit) - && maybeUpdateConstraintForPkgLocked(nowElapsed, userId, packageName)) { - mStateChangedListener - .onControllerStateChanged(mTrackedJobs.get(userId, packageName)); + if (transactQuotaLocked(userId, packageName, nowElapsed, quota, credit)) { + mStateChangedListener.onControllerStateChanged( + maybeUpdateConstraintForPkgLocked(nowElapsed, userId, packageName)); } } } @@ -1558,9 +1659,8 @@ public final class QuotaController extends StateController { final int userId = mTrackedJobs.keyAt(u); for (int p = 0; p < mTrackedJobs.numElementsForKey(userId); ++p) { final String packageName = mTrackedJobs.keyAt(u, p); - if (maybeUpdateConstraintForPkgLocked(nowElapsed, userId, packageName)) { - changedJobs.addAll(mTrackedJobs.valueAt(u, p)); - } + changedJobs.addAll( + maybeUpdateConstraintForPkgLocked(nowElapsed, userId, packageName)); } } if (changedJobs.size() > 0) { @@ -1573,18 +1673,20 @@ public final class QuotaController extends StateController { * * @return true if at least one job had its bit changed */ - private boolean maybeUpdateConstraintForPkgLocked(final long nowElapsed, final int userId, - @NonNull final String packageName) { + @NonNull + private ArraySet<JobStatus> maybeUpdateConstraintForPkgLocked(final long nowElapsed, + final int userId, @NonNull final String packageName) { ArraySet<JobStatus> jobs = mTrackedJobs.get(userId, packageName); + final ArraySet<JobStatus> changedJobs = new ArraySet<>(); if (jobs == null || jobs.size() == 0) { - return false; + return changedJobs; } // Quota is the same for all jobs within a package. final int realStandbyBucket = jobs.valueAt(0).getStandbyBucket(); - final boolean realInQuota = isWithinQuotaLocked(userId, packageName, realStandbyBucket); + final boolean realInQuota = isWithinQuotaLocked( + userId, packageName, realStandbyBucket, JobInfo.PRIORITY_DEFAULT); boolean outOfEJQuota = false; - boolean changed = false; for (int i = jobs.size() - 1; i >= 0; --i) { final JobStatus js = jobs.valueAt(i); final boolean isWithinEJQuota = @@ -1592,21 +1694,30 @@ public final class QuotaController extends StateController { if (isTopStartedJobLocked(js)) { // Job was started while the app was in the TOP state so we should allow it to // finish. - changed |= js.setQuotaConstraintSatisfied(nowElapsed, true); + if (js.setQuotaConstraintSatisfied(nowElapsed, true)) { + changedJobs.add(js); + } } else if (realStandbyBucket != ACTIVE_INDEX - && realStandbyBucket == js.getEffectiveStandbyBucket()) { + && realStandbyBucket == js.getEffectiveStandbyBucket() + && js.getEffectivePriority() >= JobInfo.PRIORITY_DEFAULT) { // An app in the ACTIVE bucket may be out of quota while the job could be in quota // for some reason. Therefore, avoid setting the real value here and check each job // individually. - changed |= setConstraintSatisfied(js, nowElapsed, isWithinEJQuota || realInQuota); + if (setConstraintSatisfied(js, nowElapsed, isWithinEJQuota || realInQuota)) { + changedJobs.add(js); + } } else { // This job is somehow exempted. Need to determine its own quota status. - changed |= setConstraintSatisfied(js, nowElapsed, - isWithinEJQuota || isWithinQuotaLocked(js)); + if (setConstraintSatisfied(js, nowElapsed, + isWithinEJQuota || isWithinQuotaLocked(js))) { + changedJobs.add(js); + } } if (js.isRequestedExpeditedJob()) { - changed |= setExpeditedQuotaApproved(js, nowElapsed, isWithinEJQuota); + if (setExpeditedQuotaApproved(js, nowElapsed, isWithinEJQuota)) { + changedJobs.add(js); + } outOfEJQuota |= !isWithinEJQuota; } } @@ -1618,7 +1729,7 @@ public final class QuotaController extends StateController { } else { mInQuotaAlarmQueue.removeAlarmForKey(new Package(userId, packageName)); } - return changed; + return changedJobs; } private class UidConstraintUpdater implements Consumer<JobStatus> { @@ -1651,9 +1762,9 @@ public final class QuotaController extends StateController { final int userId = jobStatus.getSourceUserId(); final String packageName = jobStatus.getSourcePackageName(); final int realStandbyBucket = jobStatus.getStandbyBucket(); - if (isWithinQuotaLocked(userId, packageName, realStandbyBucket) && isWithinEJQuota) { - // TODO(141645789): we probably shouldn't cancel the alarm until we've verified - // that all jobs for the userId-package are within quota. + if (isWithinEJQuota + && isWithinQuotaLocked(userId, packageName, realStandbyBucket, + JobInfo.PRIORITY_MIN)) { mInQuotaAlarmQueue.removeAlarmForKey(new Package(userId, packageName)); } else { mToScheduleStartAlarms.add(userId, packageName, realStandbyBucket); @@ -1700,16 +1811,41 @@ public final class QuotaController extends StateController { return; } + ArraySet<JobStatus> jobs = mTrackedJobs.get(userId, packageName); + if (jobs == null || jobs.size() == 0) { + Slog.e(TAG, "maybeScheduleStartAlarmLocked called for " + + packageToString(userId, packageName) + " that has no jobs"); + mInQuotaAlarmQueue.removeAlarmForKey(new Package(userId, packageName)); + return; + } + ExecutionStats stats = getExecutionStatsLocked(userId, packageName, standbyBucket); final boolean isUnderJobCountQuota = isUnderJobCountQuotaLocked(stats, standbyBucket); final boolean isUnderTimingSessionCountQuota = isUnderSessionCountQuotaLocked(stats, standbyBucket); final long remainingEJQuota = getRemainingEJExecutionTimeLocked(userId, packageName); - final boolean inRegularQuota = stats.executionTimeInWindowMs < mAllowedTimePerPeriodMs - && stats.executionTimeInMaxPeriodMs < mMaxExecutionTimeMs - && isUnderJobCountQuota - && isUnderTimingSessionCountQuota; + int minPriority = JobInfo.PRIORITY_MAX; + boolean hasDefPlus = false, hasLow = false, hasMin = false; + for (int i = jobs.size() - 1; i >= 0; --i) { + final int priority = jobs.valueAt(i).getEffectivePriority(); + minPriority = Math.min(minPriority, priority); + if (priority <= JobInfo.PRIORITY_MIN) { + hasMin = true; + } else if (priority <= JobInfo.PRIORITY_LOW) { + hasLow = true; + } else { + hasDefPlus = true; + } + if (hasMin && hasLow && hasDefPlus) { + break; + } + } + final boolean inRegularQuota = + stats.executionTimeInWindowMs < getAllowedTimePerPeriodMs(minPriority) + && stats.executionTimeInMaxPeriodMs < mMaxExecutionTimeMs + && isUnderJobCountQuota + && isUnderTimingSessionCountQuota; if (inRegularQuota && remainingEJQuota > 0) { // Already in quota. Why was this method called? if (DEBUG) { @@ -1728,7 +1864,24 @@ public final class QuotaController extends StateController { long inEJQuotaTimeElapsed = Long.MAX_VALUE; if (!inRegularQuota) { // The time this app will have quota again. - long inQuotaTimeElapsed = stats.inQuotaTimeElapsed; + long executionInQuotaTime = Long.MAX_VALUE; + boolean hasExecutionInQuotaTime = false; + if (hasMin && stats.inQuotaTimeMinElapsed > 0) { + executionInQuotaTime = Math.min(executionInQuotaTime, stats.inQuotaTimeMinElapsed); + hasExecutionInQuotaTime = true; + } + if (hasLow && stats.inQuotaTimeLowElapsed > 0) { + executionInQuotaTime = Math.min(executionInQuotaTime, stats.inQuotaTimeLowElapsed); + hasExecutionInQuotaTime = true; + } + if (hasDefPlus && stats.inQuotaTimeElapsed > 0) { + executionInQuotaTime = Math.min(executionInQuotaTime, stats.inQuotaTimeElapsed); + hasExecutionInQuotaTime = true; + } + long inQuotaTimeElapsed = 0; + if (hasExecutionInQuotaTime) { + inQuotaTimeElapsed = executionInQuotaTime; + } if (!isUnderJobCountQuota && stats.bgJobCountInWindow < stats.jobCountLimit) { // App hit the rate limit. inQuotaTimeElapsed = @@ -1941,6 +2094,7 @@ public final class QuotaController extends StateController { private final ArraySet<JobStatus> mRunningBgJobs = new ArraySet<>(); private long mStartTimeElapsed; private int mBgJobCount; + private int mLowestPriority = JobInfo.PRIORITY_MAX; private long mDebitAdjustment; Timer(int uid, int userId, String packageName, boolean regularJobTimer) { @@ -1963,6 +2117,7 @@ public final class QuotaController extends StateController { Slog.v(TAG, "Starting to track " + jobStatus.toShortString()); } // Always maintain list of running jobs, even when quota is free. + mLowestPriority = Math.min(mLowestPriority, jobStatus.getEffectivePriority()); if (mRunningBgJobs.add(jobStatus) && shouldTrackLocked()) { mBgJobCount++; if (mRegularJobTimer) { @@ -2002,6 +2157,13 @@ public final class QuotaController extends StateController { && !isQuotaFreeLocked(standbyBucket)) { emitSessionLocked(nowElapsed); cancelCutoff(); + mLowestPriority = JobInfo.PRIORITY_MAX; + } else if (mLowestPriority == jobStatus.getEffectivePriority()) { + mLowestPriority = JobInfo.PRIORITY_MAX; + for (int i = mRunningBgJobs.size() - 1; i >= 0; --i) { + mLowestPriority = Math.min(mLowestPriority, + mRunningBgJobs.valueAt(i).getEffectivePriority()); + } } } } @@ -2128,9 +2290,14 @@ public final class QuotaController extends StateController { } Message msg = mHandler.obtainMessage( mRegularJobTimer ? MSG_REACHED_QUOTA : MSG_REACHED_EJ_QUOTA, mPkg); - final long timeRemainingMs = mRegularJobTimer - ? getTimeUntilQuotaConsumedLocked(mPkg.userId, mPkg.packageName) - : getTimeUntilEJQuotaConsumedLocked(mPkg.userId, mPkg.packageName); + final long timeRemainingMs; + if (mRegularJobTimer) { + timeRemainingMs = getTimeUntilQuotaConsumedLocked( + mPkg.userId, mPkg.packageName, mLowestPriority); + } else { + timeRemainingMs = + getTimeUntilEJQuotaConsumedLocked(mPkg.userId, mPkg.packageName); + } if (DEBUG) { Slog.i(TAG, (mRegularJobTimer ? "Regular job" : "EJ") + " for " + mPkg + " has " @@ -2250,11 +2417,10 @@ public final class QuotaController extends StateController { final ShrinkableDebits debits = getEJDebitsLocked(mPkg.userId, mPkg.packageName); if (transactQuotaLocked(mPkg.userId, mPkg.packageName, - nowElapsed, debits, pendingReward) - && maybeUpdateConstraintForPkgLocked(nowElapsed, - mPkg.userId, mPkg.packageName)) { + nowElapsed, debits, pendingReward)) { mStateChangedListener.onControllerStateChanged( - mTrackedJobs.get(mPkg.userId, mPkg.packageName)); + maybeUpdateConstraintForPkgLocked(nowElapsed, + mPkg.userId, mPkg.packageName)); } } break; @@ -2356,11 +2522,9 @@ public final class QuotaController extends StateController { if (timer != null && timer.isActive()) { timer.rescheduleCutoff(); } - if (maybeUpdateConstraintForPkgLocked(sElapsedRealtimeClock.millis(), - userId, packageName)) { - mStateChangedListener - .onControllerStateChanged(mTrackedJobs.get(userId, packageName)); - } + mStateChangedListener.onControllerStateChanged( + maybeUpdateConstraintForPkgLocked( + sElapsedRealtimeClock.millis(), userId, packageName)); } if (restrictedChanges.size() > 0) { mStateChangedListener.onRestrictedBucketChanged(restrictedChanges); @@ -2486,27 +2650,19 @@ public final class QuotaController extends StateController { Slog.d(TAG, "Checking if " + pkg + " has reached its quota."); } - long timeRemainingMs = getRemainingExecutionTimeLocked(pkg.userId, - pkg.packageName); - if (timeRemainingMs <= 50) { - // Less than 50 milliseconds left. Start process of shutting down jobs. + final ArraySet<JobStatus> changedJobs = maybeUpdateConstraintForPkgLocked( + sElapsedRealtimeClock.millis(), pkg.userId, pkg.packageName); + if (changedJobs.size() > 0) { if (DEBUG) Slog.d(TAG, pkg + " has reached its quota."); - if (maybeUpdateConstraintForPkgLocked(sElapsedRealtimeClock.millis(), - pkg.userId, pkg.packageName)) { - mStateChangedListener.onControllerStateChanged( - mTrackedJobs.get(pkg.userId, pkg.packageName)); - } + mStateChangedListener.onControllerStateChanged(changedJobs); } else { // This could potentially happen if an old session phases out while a // job is currently running. // Reschedule message - Message rescheduleMsg = obtainMessage(MSG_REACHED_QUOTA, pkg); - timeRemainingMs = getTimeUntilQuotaConsumedLocked(pkg.userId, - pkg.packageName); if (DEBUG) { - Slog.d(TAG, pkg + " has " + timeRemainingMs + "ms left."); + Slog.d(TAG, pkg + " had early REACHED_QUOTA message"); } - sendMessageDelayed(rescheduleMsg, timeRemainingMs); + mPkgTimers.get(pkg.userId, pkg.packageName).scheduleCutoff(); } break; } @@ -2516,26 +2672,19 @@ public final class QuotaController extends StateController { Slog.d(TAG, "Checking if " + pkg + " has reached its EJ quota."); } - long timeRemainingMs = getRemainingEJExecutionTimeLocked( - pkg.userId, pkg.packageName); - if (timeRemainingMs <= 0) { + final ArraySet<JobStatus> changedJobs = maybeUpdateConstraintForPkgLocked( + sElapsedRealtimeClock.millis(), pkg.userId, pkg.packageName); + if (changedJobs.size() > 0) { if (DEBUG) Slog.d(TAG, pkg + " has reached its EJ quota."); - if (maybeUpdateConstraintForPkgLocked(sElapsedRealtimeClock.millis(), - pkg.userId, pkg.packageName)) { - mStateChangedListener.onControllerStateChanged( - mTrackedJobs.get(pkg.userId, pkg.packageName)); - } + mStateChangedListener.onControllerStateChanged(changedJobs); } else { // This could potentially happen if an old session phases out while a // job is currently running. // Reschedule message - Message rescheduleMsg = obtainMessage(MSG_REACHED_EJ_QUOTA, pkg); - timeRemainingMs = getTimeUntilEJQuotaConsumedLocked( - pkg.userId, pkg.packageName); if (DEBUG) { - Slog.d(TAG, pkg + " has " + timeRemainingMs + "ms left for EJ"); + Slog.d(TAG, pkg + " had early REACHED_EJ_QUOTA message"); } - sendMessageDelayed(rescheduleMsg, timeRemainingMs); + mEJPkgTimers.get(pkg.userId, pkg.packageName).scheduleCutoff(); } break; } @@ -2553,11 +2702,9 @@ public final class QuotaController extends StateController { if (DEBUG) { Slog.d(TAG, "Checking pkg " + packageToString(userId, packageName)); } - if (maybeUpdateConstraintForPkgLocked(sElapsedRealtimeClock.millis(), - userId, packageName)) { - mStateChangedListener.onControllerStateChanged( - mTrackedJobs.get(userId, packageName)); - } + mStateChangedListener.onControllerStateChanged( + maybeUpdateConstraintForPkgLocked(sElapsedRealtimeClock.millis(), + userId, packageName)); break; } case MSG_UID_PROCESS_STATE_CHANGED: { @@ -2781,6 +2928,12 @@ public final class QuotaController extends StateController { static final String KEY_IN_QUOTA_BUFFER_MS = QC_CONSTANT_PREFIX + "in_quota_buffer_ms"; @VisibleForTesting + static final String KEY_ALLOWED_TIME_SURPLUS_PRIORITY_LOW = + QC_CONSTANT_PREFIX + "allowed_time_surplus_priority_low"; + @VisibleForTesting + static final String KEY_ALLOWED_TIME_SURPLUS_PRIORITY_MIN = + QC_CONSTANT_PREFIX + "allowed_time_surplus_priority_min"; + @VisibleForTesting static final String KEY_WINDOW_SIZE_ACTIVE_MS = QC_CONSTANT_PREFIX + "window_size_active_ms"; @VisibleForTesting @@ -2890,6 +3043,8 @@ public final class QuotaController extends StateController { 10 * 60 * 1000L; // 10 minutes private static final long DEFAULT_IN_QUOTA_BUFFER_MS = 30 * 1000L; // 30 seconds + private static final float DEFAULT_ALLOWED_TIME_SURPLUS_PRIORITY_LOW = .25f; + private static final float DEFAULT_ALLOWED_TIME_SURPLUS_PRIORITY_MIN = .5f; private static final long DEFAULT_WINDOW_SIZE_ACTIVE_MS = DEFAULT_ALLOWED_TIME_PER_PERIOD_MS; // ACTIVE apps can run jobs at any time private static final long DEFAULT_WINDOW_SIZE_WORKING_MS = @@ -2951,6 +3106,22 @@ public final class QuotaController extends StateController { public long IN_QUOTA_BUFFER_MS = DEFAULT_IN_QUOTA_BUFFER_MS; /** + * The percentage of {@link #ALLOWED_TIME_PER_PERIOD_MS} that should not be used by + * {@link JobInfo#PRIORITY_LOW low priority} jobs. In other words, there must be a minimum + * surplus of this amount of remaining allowed time before we start running low priority + * jobs. + */ + public float ALLOWED_TIME_SURPLUS_PRIORITY_LOW = DEFAULT_ALLOWED_TIME_SURPLUS_PRIORITY_LOW; + + /** + * The percentage of {@link #ALLOWED_TIME_PER_PERIOD_MS} that should not be used by + * {@link JobInfo#PRIORITY_MIN low priority} jobs. In other words, there must be a minimum + * surplus of this amount of remaining allowed time before we start running min priority + * jobs. + */ + public float ALLOWED_TIME_SURPLUS_PRIORITY_MIN = DEFAULT_ALLOWED_TIME_SURPLUS_PRIORITY_MIN; + + /** * The quota window size of the particular standby bucket. Apps in this standby bucket are * expected to run only {@link #ALLOWED_TIME_PER_PERIOD_MS} within the past * WINDOW_SIZE_MS. @@ -3188,6 +3359,8 @@ public final class QuotaController extends StateController { @NonNull String key) { switch (key) { case KEY_ALLOWED_TIME_PER_PERIOD_MS: + case KEY_ALLOWED_TIME_SURPLUS_PRIORITY_LOW: + case KEY_ALLOWED_TIME_SURPLUS_PRIORITY_MIN: case KEY_IN_QUOTA_BUFFER_MS: case KEY_MAX_EXECUTION_TIME_MS: case KEY_WINDOW_SIZE_ACTIVE_MS: @@ -3407,6 +3580,7 @@ public final class QuotaController extends StateController { final DeviceConfig.Properties properties = DeviceConfig.getProperties( DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_ALLOWED_TIME_PER_PERIOD_MS, KEY_IN_QUOTA_BUFFER_MS, + KEY_ALLOWED_TIME_SURPLUS_PRIORITY_LOW, KEY_ALLOWED_TIME_SURPLUS_PRIORITY_MIN, KEY_MAX_EXECUTION_TIME_MS, KEY_WINDOW_SIZE_ACTIVE_MS, KEY_WINDOW_SIZE_WORKING_MS, KEY_WINDOW_SIZE_FREQUENT_MS, KEY_WINDOW_SIZE_RARE_MS, @@ -3414,6 +3588,12 @@ public final class QuotaController extends StateController { ALLOWED_TIME_PER_PERIOD_MS = properties.getLong(KEY_ALLOWED_TIME_PER_PERIOD_MS, DEFAULT_ALLOWED_TIME_PER_PERIOD_MS); + ALLOWED_TIME_SURPLUS_PRIORITY_LOW = + properties.getFloat(KEY_ALLOWED_TIME_SURPLUS_PRIORITY_LOW, + DEFAULT_ALLOWED_TIME_SURPLUS_PRIORITY_LOW); + ALLOWED_TIME_SURPLUS_PRIORITY_MIN = + properties.getFloat(KEY_ALLOWED_TIME_SURPLUS_PRIORITY_MIN, + DEFAULT_ALLOWED_TIME_SURPLUS_PRIORITY_MIN); IN_QUOTA_BUFFER_MS = properties.getLong(KEY_IN_QUOTA_BUFFER_MS, DEFAULT_IN_QUOTA_BUFFER_MS); MAX_EXECUTION_TIME_MS = properties.getLong(KEY_MAX_EXECUTION_TIME_MS, @@ -3455,6 +3635,23 @@ public final class QuotaController extends StateController { mMaxExecutionTimeIntoQuotaMs = mMaxExecutionTimeMs - mQuotaBufferMs; mShouldReevaluateConstraints = true; } + // Low priority surplus should be in the range [0, .9]. A value of 1 would essentially + // mean never run low priority jobs. + float newAllowedTimeSurplusPriorityLow = + Math.max(0f, Math.min(.9f, ALLOWED_TIME_SURPLUS_PRIORITY_LOW)); + if (Float.compare( + mAllowedTimeSurplusPriorityLow, newAllowedTimeSurplusPriorityLow) != 0) { + mAllowedTimeSurplusPriorityLow = newAllowedTimeSurplusPriorityLow; + mShouldReevaluateConstraints = true; + } + // Min priority surplus should be in the range [0, mAllowedTimeSurplusPriorityLow]. + float newAllowedTimeSurplusPriorityMin = Math.max(0f, + Math.min(mAllowedTimeSurplusPriorityLow, ALLOWED_TIME_SURPLUS_PRIORITY_MIN)); + if (Float.compare( + mAllowedTimeSurplusPriorityMin, newAllowedTimeSurplusPriorityMin) != 0) { + mAllowedTimeSurplusPriorityMin = newAllowedTimeSurplusPriorityMin; + mShouldReevaluateConstraints = true; + } long newActivePeriodMs = Math.max(mAllowedTimePerPeriodMs, Math.min(MAX_PERIOD_MS, WINDOW_SIZE_ACTIVE_MS)); if (mBucketPeriodsMs[ACTIVE_INDEX] != newActivePeriodMs) { @@ -3627,6 +3824,10 @@ public final class QuotaController extends StateController { pw.println("QuotaController:"); pw.increaseIndent(); pw.print(KEY_ALLOWED_TIME_PER_PERIOD_MS, ALLOWED_TIME_PER_PERIOD_MS).println(); + pw.print(KEY_ALLOWED_TIME_SURPLUS_PRIORITY_LOW, ALLOWED_TIME_SURPLUS_PRIORITY_LOW) + .println(); + pw.print(KEY_ALLOWED_TIME_SURPLUS_PRIORITY_MIN, ALLOWED_TIME_SURPLUS_PRIORITY_MIN) + .println(); pw.print(KEY_IN_QUOTA_BUFFER_MS, IN_QUOTA_BUFFER_MS).println(); pw.print(KEY_WINDOW_SIZE_ACTIVE_MS, WINDOW_SIZE_ACTIVE_MS).println(); pw.print(KEY_WINDOW_SIZE_WORKING_MS, WINDOW_SIZE_WORKING_MS).println(); @@ -3750,6 +3951,16 @@ public final class QuotaController extends StateController { } @VisibleForTesting + float getAllowedTimeSurplusPriorityLow() { + return mAllowedTimeSurplusPriorityLow; + } + + @VisibleForTesting + float getAllowedTimeSurplusPriorityMin() { + return mAllowedTimeSurplusPriorityMin; + } + + @VisibleForTesting @NonNull int[] getBucketMaxJobCounts() { return mMaxBucketJobCounts; diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java b/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java index 8b175127373a..dd102bdd726e 100644 --- a/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java +++ b/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java @@ -42,6 +42,7 @@ import android.util.AtomicFile; import android.util.IndentingPrintWriter; import android.util.Slog; import android.util.SparseArray; +import android.util.SparseLongArray; import android.util.TimeUtils; import android.util.Xml; @@ -49,6 +50,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.CollectionUtils; import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.FrameworkStatsLog; +import com.android.internal.util.XmlUtils; import libcore.io.IoUtils; @@ -80,7 +82,7 @@ public class AppIdleHistory { private SparseArray<ArrayMap<String,AppUsageHistory>> mIdleHistory = new SparseArray<>(); private static final long ONE_MINUTE = 60 * 1000; - private static final int STANDBY_BUCKET_UNKNOWN = -1; + static final int STANDBY_BUCKET_UNKNOWN = -1; /** * The bucket beyond which apps are considered idle. Any apps in this bucket or lower are @@ -88,10 +90,35 @@ public class AppIdleHistory { */ static final int IDLE_BUCKET_CUTOFF = STANDBY_BUCKET_RARE; + /** Initial version of the xml containing the app idle stats ({@link #APP_IDLE_FILENAME}). */ + private static final int XML_VERSION_INITIAL = 0; + /** + * Allowed writing expiry times for any standby bucket instead of only active and working set. + * In previous version, we used to specify expiry times for active and working set as + * attributes: + * <pre> + * <package activeTimeoutTime="..." workingSetTimeoutTime="..." /> + * </pre> + * In this version, it is changed to: + * <pre> + * <package> + * <expiryTimes> + * <item bucket="..." expiry="..." /> + * <item bucket="..." expiry="..." /> + * </expiryTimes> + * </package> + * </pre> + */ + private static final int XML_VERSION_ADD_BUCKET_EXPIRY_TIMES = 1; + /** Current version */ + private static final int XML_VERSION_CURRENT = XML_VERSION_ADD_BUCKET_EXPIRY_TIMES; + @VisibleForTesting static final String APP_IDLE_FILENAME = "app_idle_stats.xml"; private static final String TAG_PACKAGES = "packages"; private static final String TAG_PACKAGE = "package"; + private static final String TAG_BUCKET_EXPIRY_TIMES = "expiryTimes"; + private static final String TAG_ITEM = "item"; private static final String ATTR_NAME = "name"; // Screen on timebase time when app was last used private static final String ATTR_SCREEN_IDLE = "screenIdleTime"; @@ -111,6 +138,10 @@ public class AppIdleHistory { private static final String ATTR_BUCKET_ACTIVE_TIMEOUT_TIME = "activeTimeoutTime"; // The time when the forced working_set state can be overridden. private static final String ATTR_BUCKET_WORKING_SET_TIMEOUT_TIME = "workingSetTimeoutTime"; + // The standby bucket value + private static final String ATTR_BUCKET = "bucket"; + // The time when the forced bucket state can be overridde. + private static final String ATTR_EXPIRY_TIME = "expiry"; // Elapsed timebase time when the app was last marked for restriction. private static final String ATTR_LAST_RESTRICTION_ATTEMPT_ELAPSED = "lastRestrictionAttemptElapsedTime"; @@ -119,6 +150,8 @@ public class AppIdleHistory { "lastRestrictionAttemptReason"; // The next estimated launch time of the app, in ms since epoch. private static final String ATTR_NEXT_ESTIMATED_APP_LAUNCH_TIME = "nextEstimatedAppLaunchTime"; + // Version of the xml file. + private static final String ATTR_VERSION = "version"; // device on time = mElapsedDuration + (timeNow - mElapsedSnapshot) private long mElapsedSnapshot; // Elapsed time snapshot when last write of mDeviceOnDuration @@ -158,15 +191,10 @@ public class AppIdleHistory { // The estimated time the app will be launched next, in milliseconds since epoch. @CurrentTimeMillisLong long nextEstimatedLaunchTime; - // When should the bucket active state timeout, in elapsed timebase, if greater than - // lastUsedElapsedTime. - // This is used to keep the app in a high bucket regardless of other timeouts and - // predictions. - long bucketActiveTimeoutTime; - // If there's a forced working_set state, this is when it times out. This can be sitting - // under any active state timeout, so that it becomes applicable after the active state - // timeout expires. - long bucketWorkingSetTimeoutTime; + // Contains standby buckets that apps were forced into and the corresponding expiry times + // (in elapsed timebase) for each bucket state. App will stay in the highest bucket until + // it's expiry time is elapsed and will be moved to the next highest bucket. + SparseLongArray bucketExpiryTimesMs; // The last time an agent attempted to put the app into the RESTRICTED bucket. long lastRestrictAttemptElapsedTime; // The last reason the app was marked to be put into the RESTRICTED bucket. @@ -249,21 +277,24 @@ public class AppIdleHistory { } /** - * Mark the app as used and update the bucket if necessary. If there is a timeout specified + * Mark the app as used and update the bucket if necessary. If there is a expiry time specified * that's in the future, then the usage event is temporary and keeps the app in the specified - * bucket at least until the timeout is reached. This can be used to keep the app in an + * bucket at least until the expiry time is reached. This can be used to keep the app in an * elevated bucket for a while until some important task gets to run. + * * @param appUsageHistory the usage record for the app being updated * @param packageName name of the app being updated, for logging purposes * @param newBucket the bucket to set the app to * @param usageReason the sub-reason for usage, one of REASON_SUB_USAGE_* - * @param elapsedRealtime mark as used time if non-zero - * @param timeout set the timeout of the specified bucket, if non-zero. Can only be used - * with bucket values of ACTIVE and WORKING_SET. + * @param nowElapsedRealtimeMs mark as used time if non-zero (in + * {@link SystemClock#elapsedRealtime()} time base) + * @param expiryElapsedRealtimeMs the expiry time for the specified bucket (in + * {@link SystemClock#elapsedRealtime()} time base) * @return {@code appUsageHistory} */ AppUsageHistory reportUsage(AppUsageHistory appUsageHistory, String packageName, int userId, - int newBucket, int usageReason, long elapsedRealtime, long timeout) { + int newBucket, int usageReason, + long nowElapsedRealtimeMs, long expiryElapsedRealtimeMs) { int bucketingReason = REASON_MAIN_USAGE | usageReason; final boolean isUserUsage = isUserUsage(bucketingReason); @@ -274,30 +305,27 @@ public class AppIdleHistory { newBucket = STANDBY_BUCKET_RESTRICTED; bucketingReason = appUsageHistory.bucketingReason; } else { - // Set the timeout if applicable - if (timeout > elapsedRealtime) { + // Set the expiry time if applicable + if (expiryElapsedRealtimeMs > nowElapsedRealtimeMs) { // Convert to elapsed timebase - final long timeoutTime = mElapsedDuration + (timeout - mElapsedSnapshot); - if (newBucket == STANDBY_BUCKET_ACTIVE) { - appUsageHistory.bucketActiveTimeoutTime = Math.max(timeoutTime, - appUsageHistory.bucketActiveTimeoutTime); - } else if (newBucket == STANDBY_BUCKET_WORKING_SET) { - appUsageHistory.bucketWorkingSetTimeoutTime = Math.max(timeoutTime, - appUsageHistory.bucketWorkingSetTimeoutTime); - } else { - throw new IllegalArgumentException("Cannot set a timeout on bucket=" - + newBucket); + final long expiryTimeMs = getElapsedTime(expiryElapsedRealtimeMs); + if (appUsageHistory.bucketExpiryTimesMs == null) { + appUsageHistory.bucketExpiryTimesMs = new SparseLongArray(); } + final long currentExpiryTimeMs = appUsageHistory.bucketExpiryTimesMs.get(newBucket); + appUsageHistory.bucketExpiryTimesMs.put(newBucket, + Math.max(expiryTimeMs, currentExpiryTimeMs)); + removeElapsedExpiryTimes(appUsageHistory, getElapsedTime(nowElapsedRealtimeMs)); } } - if (elapsedRealtime != 0) { + if (nowElapsedRealtimeMs != 0) { appUsageHistory.lastUsedElapsedTime = mElapsedDuration - + (elapsedRealtime - mElapsedSnapshot); + + (nowElapsedRealtimeMs - mElapsedSnapshot); if (isUserUsage) { appUsageHistory.lastUsedByUserElapsedTime = appUsageHistory.lastUsedElapsedTime; } - appUsageHistory.lastUsedScreenTime = getScreenOnTime(elapsedRealtime); + appUsageHistory.lastUsedScreenTime = getScreenOnTime(nowElapsedRealtimeMs); } if (appUsageHistory.currentBucket > newBucket) { @@ -309,26 +337,41 @@ public class AppIdleHistory { return appUsageHistory; } + private void removeElapsedExpiryTimes(AppUsageHistory appUsageHistory, long elapsedTimeMs) { + if (appUsageHistory.bucketExpiryTimesMs == null) { + return; + } + for (int i = appUsageHistory.bucketExpiryTimesMs.size() - 1; i >= 0; --i) { + if (appUsageHistory.bucketExpiryTimesMs.valueAt(i) < elapsedTimeMs) { + appUsageHistory.bucketExpiryTimesMs.removeAt(i); + } + } + } + /** - * Mark the app as used and update the bucket if necessary. If there is a timeout specified + * Mark the app as used and update the bucket if necessary. If there is a expiry time specified * that's in the future, then the usage event is temporary and keeps the app in the specified - * bucket at least until the timeout is reached. This can be used to keep the app in an + * bucket at least until the expiry time is reached. This can be used to keep the app in an * elevated bucket for a while until some important task gets to run. - * @param packageName - * @param userId + * + * @param packageName package name of the app the usage is reported for + * @param userId user that the app is running in * @param newBucket the bucket to set the app to * @param usageReason sub reason for usage - * @param nowElapsed mark as used time if non-zero - * @param timeout set the timeout of the specified bucket, if non-zero. Can only be used - * with bucket values of ACTIVE and WORKING_SET. - * @return + * @param nowElapsedRealtimeMs mark as used time if non-zero (in + * {@link SystemClock#elapsedRealtime()} time base). + * @param expiryElapsedRealtimeMs the expiry time for the specified bucket (in + * {@link SystemClock#elapsedRealtime()} time base). + * @return the {@link AppUsageHistory} corresponding to the {@code packageName} + * and {@code userId}. */ public AppUsageHistory reportUsage(String packageName, int userId, int newBucket, - int usageReason, long nowElapsed, long timeout) { + int usageReason, long nowElapsedRealtimeMs, long expiryElapsedRealtimeMs) { ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId); - AppUsageHistory history = getPackageHistory(userHistory, packageName, nowElapsed, true); - return reportUsage(history, packageName, userId, newBucket, usageReason, nowElapsed, - timeout); + AppUsageHistory history = getPackageHistory(userHistory, packageName, + nowElapsedRealtimeMs, true); + return reportUsage(history, packageName, userId, newBucket, usageReason, + nowElapsedRealtimeMs, expiryElapsedRealtimeMs); } private ArrayMap<String, AppUsageHistory> getUserHistory(int userId) { @@ -383,7 +426,7 @@ public class AppIdleHistory { } public void setAppStandbyBucket(String packageName, int userId, long elapsedRealtime, - int bucket, int reason, boolean resetTimeout) { + int bucket, int reason, boolean resetExpiryTimes) { ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId); AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName, elapsedRealtime, true); @@ -397,9 +440,8 @@ public class AppIdleHistory { appUsageHistory.lastPredictedTime = elapsed; appUsageHistory.lastPredictedBucket = bucket; } - if (resetTimeout) { - appUsageHistory.bucketActiveTimeoutTime = elapsed; - appUsageHistory.bucketWorkingSetTimeoutTime = elapsed; + if (resetExpiryTimes && appUsageHistory.bucketExpiryTimesMs != null) { + appUsageHistory.bucketExpiryTimesMs.clear(); } if (changed) { logAppStandbyBucketChanged(packageName, userId, bucket, reason); @@ -622,6 +664,17 @@ public class AppIdleHistory { } @VisibleForTesting + long getBucketExpiryTimeMs(String packageName, int userId, int bucket, long elapsedRealtimeMs) { + ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId); + AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName, + elapsedRealtimeMs, true); + if (appUsageHistory.bucketExpiryTimesMs == null) { + return 0; + } + return appUsageHistory.bucketExpiryTimesMs.get(bucket, 0); + } + + @VisibleForTesting File getUserFile(int userId) { return new File(new File(new File(mStorageDir, "users"), Integer.toString(userId)), APP_IDLE_FILENAME); @@ -657,6 +710,7 @@ public class AppIdleHistory { if (!parser.getName().equals(TAG_PACKAGES)) { return; } + final int version = getIntValue(parser, ATTR_VERSION, XML_VERSION_INITIAL); while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) { if (type == XmlPullParser.START_TAG) { final String name = parser.getName(); @@ -681,10 +735,6 @@ public class AppIdleHistory { parser.getAttributeValue(null, ATTR_BUCKETING_REASON); appUsageHistory.lastJobRunTime = getLongValue(parser, ATTR_LAST_RUN_JOB_TIME, Long.MIN_VALUE); - appUsageHistory.bucketActiveTimeoutTime = getLongValue(parser, - ATTR_BUCKET_ACTIVE_TIMEOUT_TIME, 0L); - appUsageHistory.bucketWorkingSetTimeoutTime = getLongValue(parser, - ATTR_BUCKET_WORKING_SET_TIMEOUT_TIME, 0L); appUsageHistory.bucketingReason = REASON_MAIN_DEFAULT; if (bucketingReason != null) { try { @@ -710,6 +760,26 @@ public class AppIdleHistory { ATTR_NEXT_ESTIMATED_APP_LAUNCH_TIME, 0); appUsageHistory.lastInformedBucket = -1; userHistory.put(packageName, appUsageHistory); + + if (version >= XML_VERSION_ADD_BUCKET_EXPIRY_TIMES) { + final int outerDepth = parser.getDepth(); + while (XmlUtils.nextElementWithin(parser, outerDepth)) { + if (TAG_BUCKET_EXPIRY_TIMES.equals(parser.getName())) { + readBucketExpiryTimes(parser, appUsageHistory); + } + } + } else { + final long bucketActiveTimeoutTime = getLongValue(parser, + ATTR_BUCKET_ACTIVE_TIMEOUT_TIME, 0L); + final long bucketWorkingSetTimeoutTime = getLongValue(parser, + ATTR_BUCKET_WORKING_SET_TIMEOUT_TIME, 0L); + if (bucketActiveTimeoutTime != 0 || bucketWorkingSetTimeoutTime != 0) { + insertBucketExpiryTime(appUsageHistory, + STANDBY_BUCKET_ACTIVE, bucketActiveTimeoutTime); + insertBucketExpiryTime(appUsageHistory, + STANDBY_BUCKET_WORKING_SET, bucketWorkingSetTimeoutTime); + } + } } } } @@ -720,21 +790,53 @@ public class AppIdleHistory { } } + private void readBucketExpiryTimes(XmlPullParser parser, AppUsageHistory appUsageHistory) + throws IOException, XmlPullParserException { + final int depth = parser.getDepth(); + while (XmlUtils.nextElementWithin(parser, depth)) { + if (TAG_ITEM.equals(parser.getName())) { + final int bucket = getIntValue(parser, ATTR_BUCKET, STANDBY_BUCKET_UNKNOWN); + if (bucket == STANDBY_BUCKET_UNKNOWN) { + Slog.e(TAG, "Error reading the buckets expiry times"); + continue; + } + final long expiryTimeMs = getLongValue(parser, ATTR_EXPIRY_TIME, 0 /* default */); + insertBucketExpiryTime(appUsageHistory, bucket, expiryTimeMs); + } + } + } + + private void insertBucketExpiryTime(AppUsageHistory appUsageHistory, + int bucket, long expiryTimeMs) { + if (expiryTimeMs == 0) { + return; + } + if (appUsageHistory.bucketExpiryTimesMs == null) { + appUsageHistory.bucketExpiryTimesMs = new SparseLongArray(); + } + appUsageHistory.bucketExpiryTimesMs.put(bucket, expiryTimeMs); + } + private long getLongValue(XmlPullParser parser, String attrName, long defValue) { String value = parser.getAttributeValue(null, attrName); if (value == null) return defValue; return Long.parseLong(value); } + private int getIntValue(XmlPullParser parser, String attrName, int defValue) { + String value = parser.getAttributeValue(null, attrName); + if (value == null) return defValue; + return Integer.parseInt(value); + } - public void writeAppIdleTimes() { + public void writeAppIdleTimes(long elapsedRealtimeMs) { final int size = mIdleHistory.size(); for (int i = 0; i < size; i++) { - writeAppIdleTimes(mIdleHistory.keyAt(i)); + writeAppIdleTimes(mIdleHistory.keyAt(i), elapsedRealtimeMs); } } - public void writeAppIdleTimes(int userId) { + public void writeAppIdleTimes(int userId, long elapsedRealtimeMs) { FileOutputStream fos = null; AtomicFile appIdleFile = new AtomicFile(getUserFile(userId)); try { @@ -747,7 +849,9 @@ public class AppIdleHistory { xml.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); xml.startTag(null, TAG_PACKAGES); + xml.attribute(null, ATTR_VERSION, String.valueOf(XML_VERSION_CURRENT)); + final long elapsedTimeMs = getElapsedTime(elapsedRealtimeMs); ArrayMap<String,AppUsageHistory> userHistory = getUserHistory(userId); final int N = userHistory.size(); for (int i = 0; i < N; i++) { @@ -772,14 +876,6 @@ public class AppIdleHistory { Integer.toString(history.currentBucket)); xml.attribute(null, ATTR_BUCKETING_REASON, Integer.toHexString(history.bucketingReason)); - if (history.bucketActiveTimeoutTime > 0) { - xml.attribute(null, ATTR_BUCKET_ACTIVE_TIMEOUT_TIME, Long.toString(history - .bucketActiveTimeoutTime)); - } - if (history.bucketWorkingSetTimeoutTime > 0) { - xml.attribute(null, ATTR_BUCKET_WORKING_SET_TIMEOUT_TIME, Long.toString(history - .bucketWorkingSetTimeoutTime)); - } if (history.lastJobRunTime != Long.MIN_VALUE) { xml.attribute(null, ATTR_LAST_RUN_JOB_TIME, Long.toString(history .lastJobRunTime)); @@ -794,6 +890,23 @@ public class AppIdleHistory { xml.attribute(null, ATTR_NEXT_ESTIMATED_APP_LAUNCH_TIME, Long.toString(history.nextEstimatedLaunchTime)); } + if (history.bucketExpiryTimesMs != null) { + xml.startTag(null, TAG_BUCKET_EXPIRY_TIMES); + final int size = history.bucketExpiryTimesMs.size(); + for (int j = 0; j < size; ++j) { + final long expiryTimeMs = history.bucketExpiryTimesMs.valueAt(j); + // Skip writing to disk if the expiry time already elapsed. + if (expiryTimeMs < elapsedTimeMs) { + continue; + } + final int bucket = history.bucketExpiryTimesMs.keyAt(j); + xml.startTag(null, TAG_ITEM); + xml.attribute(null, ATTR_BUCKET, String.valueOf(bucket)); + xml.attribute(null, ATTR_EXPIRY_TIME, String.valueOf(expiryTimeMs)); + xml.endTag(null, TAG_ITEM); + } + xml.endTag(null, TAG_BUCKET_EXPIRY_TIMES); + } xml.endTag(null, TAG_PACKAGE); } @@ -846,12 +959,7 @@ public class AppIdleHistory { TimeUtils.formatDuration(screenOnTime - appUsageHistory.lastUsedScreenTime, idpw); idpw.print(" lastPred="); TimeUtils.formatDuration(totalElapsedTime - appUsageHistory.lastPredictedTime, idpw); - idpw.print(" activeLeft="); - TimeUtils.formatDuration(appUsageHistory.bucketActiveTimeoutTime - totalElapsedTime, - idpw); - idpw.print(" wsLeft="); - TimeUtils.formatDuration(appUsageHistory.bucketWorkingSetTimeoutTime - totalElapsedTime, - idpw); + dumpBucketExpiryTimes(idpw, appUsageHistory, totalElapsedTime); idpw.print(" lastJob="); TimeUtils.formatDuration(totalElapsedTime - appUsageHistory.lastJobRunTime, idpw); if (appUsageHistory.lastRestrictAttemptElapsedTime > 0) { @@ -877,4 +985,26 @@ public class AppIdleHistory { idpw.println(); idpw.decreaseIndent(); } + + private void dumpBucketExpiryTimes(IndentingPrintWriter idpw, AppUsageHistory appUsageHistory, + long totalElapsedTimeMs) { + idpw.print(" expiryTimes="); + if (appUsageHistory.bucketExpiryTimesMs == null + || appUsageHistory.bucketExpiryTimesMs.size() == 0) { + idpw.print("<none>"); + return; + } + idpw.print("("); + final int size = appUsageHistory.bucketExpiryTimesMs.size(); + for (int i = 0; i < size; ++i) { + final int bucket = appUsageHistory.bucketExpiryTimesMs.keyAt(i); + final long expiryTimeMs = appUsageHistory.bucketExpiryTimesMs.valueAt(i); + if (i != 0) { + idpw.print(","); + } + idpw.print(bucket + ":"); + TimeUtils.formatDuration(totalElapsedTimeMs - expiryTimeMs, idpw); + } + idpw.print(")"); + } } diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java index abbae4e8e43c..0ad70e40de7f 100644 --- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java +++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java @@ -49,10 +49,12 @@ import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_NEVER; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RARE; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RESTRICTED; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_WORKING_SET; +import static android.app.usage.UsageStatsManager.standbyBucketToString; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static com.android.server.SystemService.PHASE_BOOT_COMPLETED; import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY; +import static com.android.server.usage.AppIdleHistory.STANDBY_BUCKET_UNKNOWN; import android.annotation.CurrentTimeMillisLong; import android.annotation.NonNull; @@ -181,7 +183,7 @@ public class AppStandbyController COMPRESS_TIME ? 1 * ONE_MINUTE : 12 * ONE_HOUR, COMPRESS_TIME ? 4 * ONE_MINUTE : 24 * ONE_HOUR, COMPRESS_TIME ? 16 * ONE_MINUTE : 48 * ONE_HOUR, - COMPRESS_TIME ? 32 * ONE_MINUTE : 45 * ONE_DAY + COMPRESS_TIME ? 32 * ONE_MINUTE : 3 * ONE_DAY }; /** The minimum allowed values for each index in {@link #DEFAULT_ELAPSED_TIME_THRESHOLDS}. */ @@ -298,6 +300,11 @@ public class AppStandbyController long mStrongUsageTimeoutMillis = ConstantsObserver.DEFAULT_STRONG_USAGE_TIMEOUT; /** Minimum time a notification seen event should keep the bucket elevated. */ long mNotificationSeenTimeoutMillis = ConstantsObserver.DEFAULT_NOTIFICATION_TIMEOUT; + /** Minimum time a slice pinned event should keep the bucket elevated. */ + long mSlicePinnedTimeoutMillis = ConstantsObserver.DEFAULT_SLICE_PINNED_TIMEOUT; + /** The standby bucket that an app will be promoted on a notification-seen event */ + int mNotificationSeenPromotedBucket = + ConstantsObserver.DEFAULT_NOTIFICATION_SEEN_PROMOTED_BUCKET; /** Minimum time a system update event should keep the buckets elevated. */ long mSystemUpdateUsageTimeoutMillis = ConstantsObserver.DEFAULT_SYSTEM_UPDATE_TIMEOUT; /** Maximum time to wait for a prediction before using simple timeouts to downgrade buckets. */ @@ -773,7 +780,7 @@ public class AppStandbyController userId); if (DEBUG) { Slog.d(TAG, " Checking idle state for " + packageName - + " minBucket=" + minBucket); + + " minBucket=" + standbyBucketToString(minBucket)); } if (minBucket <= STANDBY_BUCKET_ACTIVE) { // No extra processing needed for ACTIVE or higher since apps can't drop into lower @@ -815,36 +822,34 @@ public class AppStandbyController newBucket = app.lastPredictedBucket; reason = REASON_MAIN_PREDICTED | REASON_SUB_PREDICTED_RESTORED; if (DEBUG) { - Slog.d(TAG, "Restored predicted newBucket = " + newBucket); + Slog.d(TAG, "Restored predicted newBucket = " + + standbyBucketToString(newBucket)); } } else { newBucket = getBucketForLocked(packageName, userId, elapsedRealtime); if (DEBUG) { - Slog.d(TAG, "Evaluated AOSP newBucket = " + newBucket); + Slog.d(TAG, "Evaluated AOSP newBucket = " + + standbyBucketToString(newBucket)); } reason = REASON_MAIN_TIMEOUT; } } - // Check if the app is within one of the timeouts for forced bucket elevation + // Check if the app is within one of the expiry times for forced bucket elevation final long elapsedTimeAdjusted = mAppIdleHistory.getElapsedTime(elapsedRealtime); - if (newBucket >= STANDBY_BUCKET_ACTIVE - && app.bucketActiveTimeoutTime > elapsedTimeAdjusted) { - newBucket = STANDBY_BUCKET_ACTIVE; - reason = app.bucketingReason; - if (DEBUG) { - Slog.d(TAG, " Keeping at ACTIVE due to min timeout"); + final int bucketWithValidExpiryTime = getMinBucketWithValidExpiryTime(app, + newBucket, elapsedTimeAdjusted); + if (bucketWithValidExpiryTime != STANDBY_BUCKET_UNKNOWN) { + newBucket = bucketWithValidExpiryTime; + if (newBucket == STANDBY_BUCKET_ACTIVE || app.currentBucket == newBucket) { + reason = app.bucketingReason; + } else { + reason = REASON_MAIN_USAGE | REASON_SUB_USAGE_ACTIVE_TIMEOUT; } - } else if (newBucket >= STANDBY_BUCKET_WORKING_SET - && app.bucketWorkingSetTimeoutTime > elapsedTimeAdjusted) { - newBucket = STANDBY_BUCKET_WORKING_SET; - // If it was already there, keep the reason, else assume timeout to WS - reason = (newBucket == oldBucket) - ? app.bucketingReason - : REASON_MAIN_USAGE | REASON_SUB_USAGE_ACTIVE_TIMEOUT; if (DEBUG) { - Slog.d(TAG, " Keeping at WORKING_SET due to min timeout"); + Slog.d(TAG, " Keeping at " + standbyBucketToString(newBucket) + + " due to min timeout"); } } @@ -868,13 +873,14 @@ public class AppStandbyController newBucket = minBucket; // Leave the reason alone. if (DEBUG) { - Slog.d(TAG, "Bringing up from " + newBucket + " to " + minBucket + Slog.d(TAG, "Bringing up from " + standbyBucketToString(newBucket) + + " to " + standbyBucketToString(minBucket) + " due to min bucketing"); } } if (DEBUG) { - Slog.d(TAG, " Old bucket=" + oldBucket - + ", newBucket=" + newBucket); + Slog.d(TAG, " Old bucket=" + standbyBucketToString(oldBucket) + + ", newBucket=" + standbyBucketToString(newBucket)); } if (oldBucket != newBucket || predictionLate) { mAppIdleHistory.setAppStandbyBucket(packageName, userId, @@ -967,6 +973,7 @@ public class AppStandbyController } } + @GuardedBy("mAppIdleLock") private void reportEventLocked(String pkg, int eventType, long elapsedRealtime, int userId) { // TODO: Ideally this should call isAppIdleFiltered() to avoid calling back // about apps that are on some kind of whitelist anyway. @@ -980,13 +987,20 @@ public class AppStandbyController final long nextCheckDelay; final int subReason = usageEventToSubReason(eventType); final int reason = REASON_MAIN_USAGE | subReason; - if (eventType == UsageEvents.Event.NOTIFICATION_SEEN - || eventType == UsageEvents.Event.SLICE_PINNED) { - // Mild usage elevates to WORKING_SET but doesn't change usage time. + if (eventType == UsageEvents.Event.NOTIFICATION_SEEN) { + // Notification-seen elevates to a higher bucket (depending on + // {@link ConstantsObserver#KEY_NOTIFICATION_SEEN_PROMOTED_BUCKET}) but doesn't + // change usage time. mAppIdleHistory.reportUsage(appHistory, pkg, userId, - STANDBY_BUCKET_WORKING_SET, subReason, + mNotificationSeenPromotedBucket, subReason, 0, elapsedRealtime + mNotificationSeenTimeoutMillis); nextCheckDelay = mNotificationSeenTimeoutMillis; + } else if (eventType == UsageEvents.Event.SLICE_PINNED) { + // Mild usage elevates to WORKING_SET but doesn't change usage time. + mAppIdleHistory.reportUsage(appHistory, pkg, userId, + STANDBY_BUCKET_WORKING_SET, subReason, + 0, elapsedRealtime + mSlicePinnedTimeoutMillis); + nextCheckDelay = mSlicePinnedTimeoutMillis; } else if (eventType == UsageEvents.Event.SYSTEM_INTERACTION) { mAppIdleHistory.reportUsage(appHistory, pkg, userId, STANDBY_BUCKET_ACTIVE, subReason, @@ -1022,6 +1036,29 @@ public class AppStandbyController } /** + * Returns the lowest standby bucket that is better than {@code targetBucket} and has an + * valid expiry time (i.e. the expiry time is not yet elapsed). + */ + private int getMinBucketWithValidExpiryTime(AppUsageHistory usageHistory, + int targetBucket, long elapsedTimeMs) { + if (usageHistory.bucketExpiryTimesMs == null) { + return STANDBY_BUCKET_UNKNOWN; + } + final int size = usageHistory.bucketExpiryTimesMs.size(); + for (int i = 0; i < size; ++i) { + final int bucket = usageHistory.bucketExpiryTimesMs.keyAt(i); + if (targetBucket <= bucket) { + break; + } + final long expiryTimeMs = usageHistory.bucketExpiryTimesMs.valueAt(i); + if (expiryTimeMs > elapsedTimeMs) { + return bucket; + } + } + return STANDBY_BUCKET_UNKNOWN; + } + + /** * Note: don't call this with the lock held since it makes calls to other system services. */ private @NonNull List<UserHandle> getCrossProfileTargets(String pkg, int userId) { @@ -1564,23 +1601,18 @@ public class AppStandbyController // ACTIVE or WORKING_SET timeout. mAppIdleHistory.updateLastPrediction(app, elapsedTimeAdjusted, newBucket); - if (newBucket > STANDBY_BUCKET_ACTIVE - && app.bucketActiveTimeoutTime > elapsedTimeAdjusted) { - newBucket = STANDBY_BUCKET_ACTIVE; - reason = app.bucketingReason; - if (DEBUG) { - Slog.d(TAG, " Keeping at ACTIVE due to min timeout"); - } - } else if (newBucket > STANDBY_BUCKET_WORKING_SET - && app.bucketWorkingSetTimeoutTime > elapsedTimeAdjusted) { - newBucket = STANDBY_BUCKET_WORKING_SET; - if (app.currentBucket != newBucket) { - reason = REASON_MAIN_USAGE | REASON_SUB_USAGE_ACTIVE_TIMEOUT; - } else { + final int bucketWithValidExpiryTime = getMinBucketWithValidExpiryTime(app, + newBucket, elapsedTimeAdjusted); + if (bucketWithValidExpiryTime != STANDBY_BUCKET_UNKNOWN) { + newBucket = bucketWithValidExpiryTime; + if (newBucket == STANDBY_BUCKET_ACTIVE || app.currentBucket == newBucket) { reason = app.bucketingReason; + } else { + reason = REASON_MAIN_USAGE | REASON_SUB_USAGE_ACTIVE_TIMEOUT; } if (DEBUG) { - Slog.d(TAG, " Keeping at WORKING_SET due to min timeout"); + Slog.d(TAG, " Keeping at " + standbyBucketToString(newBucket) + + " due to min timeout"); } } else if (newBucket == STANDBY_BUCKET_RARE && mAllowRestrictedBucket @@ -1746,7 +1778,7 @@ public class AppStandbyController @Override public void flushToDisk() { synchronized (mAppIdleLock) { - mAppIdleHistory.writeAppIdleTimes(); + mAppIdleHistory.writeAppIdleTimes(mInjector.elapsedRealtime()); mAppIdleHistory.writeAppIdleDurations(); } } @@ -1897,7 +1929,7 @@ public class AppStandbyController } } // Immediately persist defaults to disk - mAppIdleHistory.writeAppIdleTimes(userId); + mAppIdleHistory.writeAppIdleTimes(userId, elapsedRealtime); } } @@ -1964,6 +1996,12 @@ public class AppStandbyController pw.print(" mNotificationSeenTimeoutMillis="); TimeUtils.formatDuration(mNotificationSeenTimeoutMillis, pw); pw.println(); + pw.print(" mNotificationSeenPromotedBucket="); + pw.print(standbyBucketToString(mNotificationSeenPromotedBucket)); + pw.println(); + pw.print(" mSlicePinnedTimeoutMillis="); + TimeUtils.formatDuration(mSlicePinnedTimeoutMillis, pw); + pw.println(); pw.print(" mSyncAdapterTimeoutMillis="); TimeUtils.formatDuration(mSyncAdapterTimeoutMillis, pw); pw.println(); @@ -2386,6 +2424,10 @@ public class AppStandbyController private static final String KEY_STRONG_USAGE_HOLD_DURATION = "strong_usage_duration"; private static final String KEY_NOTIFICATION_SEEN_HOLD_DURATION = "notification_seen_duration"; + private static final String KEY_NOTIFICATION_SEEN_PROMOTED_BUCKET = + "notification_seen_promoted_bucket"; + private static final String KEY_SLICE_PINNED_HOLD_DURATION = + "slice_pinned_duration"; private static final String KEY_SYSTEM_UPDATE_HOLD_DURATION = "system_update_usage_duration"; private static final String KEY_PREDICTION_TIMEOUT = "prediction_timeout"; @@ -2428,6 +2470,10 @@ public class AppStandbyController COMPRESS_TIME ? ONE_MINUTE : 1 * ONE_HOUR; public static final long DEFAULT_NOTIFICATION_TIMEOUT = COMPRESS_TIME ? 12 * ONE_MINUTE : 12 * ONE_HOUR; + public static final long DEFAULT_SLICE_PINNED_TIMEOUT = + COMPRESS_TIME ? 12 * ONE_MINUTE : 12 * ONE_HOUR; + public static final int DEFAULT_NOTIFICATION_SEEN_PROMOTED_BUCKET = + STANDBY_BUCKET_WORKING_SET; public static final long DEFAULT_SYSTEM_UPDATE_TIMEOUT = COMPRESS_TIME ? 2 * ONE_MINUTE : 2 * ONE_HOUR; public static final long DEFAULT_SYSTEM_INTERACTION_TIMEOUT = @@ -2494,7 +2540,7 @@ public class AppStandbyController switch (name) { case KEY_AUTO_RESTRICTED_BUCKET_DELAY_MS: mInjector.mAutoRestrictedBucketDelayMs = Math.max( - COMPRESS_TIME ? ONE_MINUTE : 2 * ONE_HOUR, + COMPRESS_TIME ? ONE_MINUTE : 4 * ONE_HOUR, properties.getLong(KEY_AUTO_RESTRICTED_BUCKET_DELAY_MS, DEFAULT_AUTO_RESTRICTED_BUCKET_DELAY_MS)); break; @@ -2513,6 +2559,16 @@ public class AppStandbyController KEY_NOTIFICATION_SEEN_HOLD_DURATION, DEFAULT_NOTIFICATION_TIMEOUT); break; + case KEY_NOTIFICATION_SEEN_PROMOTED_BUCKET: + mNotificationSeenPromotedBucket = properties.getInt( + KEY_NOTIFICATION_SEEN_PROMOTED_BUCKET, + DEFAULT_NOTIFICATION_SEEN_PROMOTED_BUCKET); + break; + case KEY_SLICE_PINNED_HOLD_DURATION: + mSlicePinnedTimeoutMillis = properties.getLong( + KEY_SLICE_PINNED_HOLD_DURATION, + DEFAULT_SLICE_PINNED_TIMEOUT); + break; case KEY_STRONG_USAGE_HOLD_DURATION: mStrongUsageTimeoutMillis = properties.getLong( KEY_STRONG_USAGE_HOLD_DURATION, DEFAULT_STRONG_USAGE_TIMEOUT); diff --git a/apex/media/framework/java/android/media/MediaSession2Service.java b/apex/media/framework/java/android/media/MediaSession2Service.java index f6fd509fd245..9f80c433d580 100644 --- a/apex/media/framework/java/android/media/MediaSession2Service.java +++ b/apex/media/framework/java/android/media/MediaSession2Service.java @@ -161,19 +161,19 @@ public abstract class MediaSession2Service extends Service { public abstract MediaSession2 onGetSession(@NonNull ControllerInfo controllerInfo); /** - * Called when notification UI needs update. Override this method to show or cancel your own - * notification UI. + * Called to update the media notification when the playback state changes. * <p> - * This would be called on {@link MediaSession2}'s callback executor when playback state is - * changed. + * If playback is active and a notification is returned, the service uses it to become a + * foreground service. If playback is not active then the notification is still posted, but the + * service does not become a foreground service. * <p> - * With the notification returned here, the service becomes foreground service when the playback - * is started. Apps must request the permission - * {@link android.Manifest.permission#FOREGROUND_SERVICE} in order to use this API. It becomes - * background service after the playback is stopped. + * Apps must request the {@link android.Manifest.permission#FOREGROUND_SERVICE} permission + * in order to use this API. For apps targeting {@link android.os.Build.VERSION_CODES#TIRAMISU} + * or later, notifications will only be posted if the app has also been granted the + * {@link android.Manifest.permission#POST_NOTIFICATIONS} permission. * - * @param session a session that needs notification update. - * @return a {@link MediaNotification}. Can be {@code null}. + * @param session the session for which an updated media notification is required. + * @return the {@link MediaNotification}. Can be {@code null}. */ @Nullable public abstract MediaNotification onUpdateNotification(@NonNull MediaSession2 session); diff --git a/api/Android.bp b/api/Android.bp index 362f39f2beaf..9428fcc41b81 100644 --- a/api/Android.bp +++ b/api/Android.bp @@ -128,11 +128,13 @@ combined_apis { "i18n.module.public.api", ], conditional_bootclasspath: [ + "framework-auxiliary", "framework-supplementalapi", ], system_server_classpath: [ "service-media-s", "service-permission", + "service-supplementalprocess", ], } diff --git a/boot/Android.bp b/boot/Android.bp index 3273f2c6ed8e..8958d70f63cf 100644 --- a/boot/Android.bp +++ b/boot/Android.bp @@ -56,6 +56,10 @@ platform_bootclasspath { module: "art-bootclasspath-fragment", }, { + apex: "com.android.auxiliary", + module: "com.android.auxiliary-bootclasspath-fragment", + }, + { apex: "com.android.conscrypt", module: "com.android.conscrypt-bootclasspath-fragment", }, diff --git a/cmds/app_process/Android.bp b/cmds/app_process/Android.bp index a1575173ded6..6a685a79cc33 100644 --- a/cmds/app_process/Android.bp +++ b/cmds/app_process/Android.bp @@ -64,6 +64,8 @@ cc_binary { "libwilhelm", ], + header_libs: ["bionic_libc_platform_headers"], + compile_multilib: "both", cflags: [ diff --git a/cmds/app_process/app_main.cpp b/cmds/app_process/app_main.cpp index 12083b6fe20b..815f9455471c 100644 --- a/cmds/app_process/app_main.cpp +++ b/cmds/app_process/app_main.cpp @@ -15,6 +15,7 @@ #include <android-base/macros.h> #include <binder/IPCThreadState.h> +#include <bionic/pac.h> #include <hwbinder/IPCThreadState.h> #include <utils/Log.h> #include <cutils/memory.h> @@ -182,6 +183,10 @@ int main(int argc, char* const argv[]) ALOGV("app_process main with argv: %s", argv_String.string()); } + // Because of applications that are using PAC instructions incorrectly, PAC + // is disabled in application processes for now. + ScopedDisablePAC x; + AppRuntime runtime(argv[0], computeArgBlockSize(argc, argv)); // Process command line arguments // ignore argv[0] diff --git a/cmds/telecom/src/com/android/commands/telecom/Telecom.java b/cmds/telecom/src/com/android/commands/telecom/Telecom.java index 9c044b5e632e..52f883b5fbb7 100644 --- a/cmds/telecom/src/com/android/commands/telecom/Telecom.java +++ b/cmds/telecom/src/com/android/commands/telecom/Telecom.java @@ -71,6 +71,8 @@ public final class Telecom extends BaseCommand { private static final String COMMAND_GET_DEFAULT_DIALER = "get-default-dialer"; private static final String COMMAND_STOP_BLOCK_SUPPRESSION = "stop-block-suppression"; private static final String COMMAND_CLEANUP_STUCK_CALLS = "cleanup-stuck-calls"; + private static final String COMMAND_CLEANUP_ORPHAN_PHONE_ACCOUNTS = + "cleanup-orphan-phone-accounts"; private static final String COMMAND_RESET_CAR_MODE = "reset-car-mode"; /** @@ -125,6 +127,9 @@ public final class Telecom extends BaseCommand { + " provider after a call to emergency services.\n" + "usage: telecom cleanup-stuck-calls: Clear any disconnected calls that have" + " gotten wedged in Telecom.\n" + + "usage: telecom cleanup-orphan-phone-accounts: remove any phone accounts that" + + " no longer have a valid UserHandle or accounts that no longer belongs to an" + + " installed package.\n" + "usage: telecom set-emer-phone-account-filter <PACKAGE>\n" + "\n" + "telecom set-phone-account-enabled: Enables the given phone account, if it has" @@ -227,6 +232,9 @@ public final class Telecom extends BaseCommand { case COMMAND_CLEANUP_STUCK_CALLS: runCleanupStuckCalls(); break; + case COMMAND_CLEANUP_ORPHAN_PHONE_ACCOUNTS: + runCleanupOrphanPhoneAccounts(); + break; case COMMAND_RESET_CAR_MODE: runResetCarMode(); break; @@ -362,6 +370,11 @@ public final class Telecom extends BaseCommand { mTelecomService.cleanupStuckCalls(); } + private void runCleanupOrphanPhoneAccounts() throws RemoteException { + System.out.println("Success - cleaned up " + mTelecomService.cleanupOrphanPhoneAccounts() + + " phone accounts."); + } + private void runResetCarMode() throws RemoteException { mTelecomService.resetCarMode(); } diff --git a/core/api/current.txt b/core/api/current.txt index 3234e162e5fa..86fbcee7ffad 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -125,11 +125,13 @@ package android { field public static final String POST_NOTIFICATIONS = "android.permission.POST_NOTIFICATIONS"; field @Deprecated public static final String PROCESS_OUTGOING_CALLS = "android.permission.PROCESS_OUTGOING_CALLS"; field public static final String QUERY_ALL_PACKAGES = "android.permission.QUERY_ALL_PACKAGES"; + field public static final String READ_ASSISTANT_APP_SEARCH_DATA = "android.permission.READ_ASSISTANT_APP_SEARCH_DATA"; field public static final String READ_BASIC_PHONE_STATE = "android.permission.READ_BASIC_PHONE_STATE"; field public static final String READ_CALENDAR = "android.permission.READ_CALENDAR"; field public static final String READ_CALL_LOG = "android.permission.READ_CALL_LOG"; field public static final String READ_CONTACTS = "android.permission.READ_CONTACTS"; field public static final String READ_EXTERNAL_STORAGE = "android.permission.READ_EXTERNAL_STORAGE"; + field public static final String READ_HOME_APP_SEARCH_DATA = "android.permission.READ_HOME_APP_SEARCH_DATA"; field @Deprecated public static final String READ_INPUT_STATE = "android.permission.READ_INPUT_STATE"; field public static final String READ_LOGS = "android.permission.READ_LOGS"; field public static final String READ_NEARBY_STREAMING_POLICY = "android.permission.READ_NEARBY_STREAMING_POLICY"; @@ -824,6 +826,7 @@ package android { field public static final int indicatorRight = 16843022; // 0x101010e field public static final int indicatorStart = 16843729; // 0x10103d1 field public static final int inflatedId = 16842995; // 0x10100f3 + field public static final int inheritKeyStoreKeys; field public static final int inheritShowWhenLocked = 16844188; // 0x101059c field public static final int initOrder = 16842778; // 0x101001a field public static final int initialKeyguardLayout = 16843714; // 0x10103c2 @@ -950,6 +953,7 @@ package android { field public static final int letterSpacing = 16843958; // 0x10104b6 field public static final int level = 16844032; // 0x1010500 field public static final int lineBreakStyle = 16844365; // 0x101064d + field public static final int lineBreakWordStyle = 16844366; // 0x101064e field public static final int lineHeight = 16844159; // 0x101057f field public static final int lineSpacingExtra = 16843287; // 0x1010217 field public static final int lineSpacingMultiplier = 16843288; // 0x1010218 @@ -1132,6 +1136,7 @@ package android { field public static final int popupWindowStyle = 16842870; // 0x1010076 field public static final int port = 16842793; // 0x1010029 field public static final int positiveButtonText = 16843253; // 0x10101f5 + field public static final int preferKeepClear; field public static final int preferMinimalPostProcessing = 16844300; // 0x101060c field public static final int preferenceCategoryStyle = 16842892; // 0x101008c field public static final int preferenceFragmentStyle = 16844038; // 0x1010506 @@ -3995,7 +4000,7 @@ package android.app { method @Deprecated public void onTabUnselected(android.app.ActionBar.Tab, android.app.FragmentTransaction); } - @UiContext public class Activity extends android.view.ContextThemeWrapper implements android.content.ComponentCallbacks2 android.view.KeyEvent.Callback android.view.LayoutInflater.Factory2 android.view.View.OnCreateContextMenuListener android.view.Window.Callback { + @UiContext public class Activity extends android.view.ContextThemeWrapper implements android.content.ComponentCallbacks2 android.view.KeyEvent.Callback android.view.LayoutInflater.Factory2 android.view.OnBackInvokedDispatcherOwner android.view.View.OnCreateContextMenuListener android.view.Window.Callback { ctor public Activity(); method public void addContentView(android.view.View, android.view.ViewGroup.LayoutParams); method public void closeContextMenu(); @@ -4038,6 +4043,7 @@ package android.app { method public int getMaxNumPictureInPictureActions(); method public final android.media.session.MediaController getMediaController(); method @NonNull public android.view.MenuInflater getMenuInflater(); + method @Nullable public android.view.OnBackInvokedDispatcher getOnBackInvokedDispatcher(); method public final android.app.Activity getParent(); method @Nullable public android.content.Intent getParentActivityIntent(); method public android.content.SharedPreferences getPreferences(int); @@ -4916,7 +4922,7 @@ package android.app { method public void onDateSet(android.widget.DatePicker, int, int, int); } - public class Dialog implements android.content.DialogInterface android.view.KeyEvent.Callback android.view.View.OnCreateContextMenuListener android.view.Window.Callback { + public class Dialog implements android.content.DialogInterface android.view.KeyEvent.Callback android.view.OnBackInvokedDispatcherOwner android.view.View.OnCreateContextMenuListener android.view.Window.Callback { ctor public Dialog(@NonNull @UiContext android.content.Context); ctor public Dialog(@NonNull @UiContext android.content.Context, @StyleRes int); ctor protected Dialog(@NonNull @UiContext android.content.Context, boolean, @Nullable android.content.DialogInterface.OnCancelListener); @@ -4936,6 +4942,7 @@ package android.app { method @NonNull @UiContext public final android.content.Context getContext(); method @Nullable public android.view.View getCurrentFocus(); method @NonNull public android.view.LayoutInflater getLayoutInflater(); + method @Nullable public android.view.OnBackInvokedDispatcher getOnBackInvokedDispatcher(); method @Nullable public final android.app.Activity getOwnerActivity(); method @Nullable public final android.view.SearchEvent getSearchEvent(); method public final int getVolumeControlStream(); @@ -7289,10 +7296,10 @@ package android.app.admin { method @Nullable public java.util.List<java.lang.String> getDelegatePackages(@NonNull android.content.ComponentName, @NonNull String); method @NonNull public java.util.List<java.lang.String> getDelegatedScopes(@Nullable android.content.ComponentName, @NonNull String); method public CharSequence getDeviceOwnerLockScreenInfo(); - method @Nullable public android.graphics.drawable.Drawable getDrawable(int, int, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>); - method @Nullable public android.graphics.drawable.Drawable getDrawable(int, int, int, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>); - method @Nullable public android.graphics.drawable.Drawable getDrawableForDensity(int, int, int, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>); - method @Nullable public android.graphics.drawable.Drawable getDrawableForDensity(int, int, int, int, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>); + method @NonNull public android.graphics.drawable.Drawable getDrawable(int, int, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>); + method @NonNull public android.graphics.drawable.Drawable getDrawable(int, int, int, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>); + method @NonNull public android.graphics.drawable.Drawable getDrawableForDensity(int, int, int, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>); + method @NonNull public android.graphics.drawable.Drawable getDrawableForDensity(int, int, int, int, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>); method public CharSequence getEndUserSessionMessage(@NonNull android.content.ComponentName); method @NonNull public String getEnrollmentSpecificId(); method @Nullable public android.app.admin.FactoryResetProtectionPolicy getFactoryResetProtectionPolicy(@Nullable android.content.ComponentName); @@ -7309,6 +7316,7 @@ package android.app.admin { method public int getMaximumFailedPasswordsForWipe(@Nullable android.content.ComponentName); method public long getMaximumTimeToLock(@Nullable android.content.ComponentName); method @NonNull public java.util.List<java.lang.String> getMeteredDataDisabledPackages(@NonNull android.content.ComponentName); + method public int getMinimumRequiredWifiSecurityLevel(); method @RequiresPermission(value=android.Manifest.permission.READ_NEARBY_STREAMING_POLICY, conditional=true) public int getNearbyAppStreamingPolicy(); method @RequiresPermission(value=android.Manifest.permission.READ_NEARBY_STREAMING_POLICY, conditional=true) public int getNearbyNotificationStreamingPolicy(); method @Deprecated @ColorInt public int getOrganizationColor(@NonNull android.content.ComponentName); @@ -7349,6 +7357,7 @@ package android.app.admin { method @NonNull public java.util.List<java.lang.String> getUserControlDisabledPackages(@NonNull android.content.ComponentName); method @NonNull public android.os.Bundle getUserRestrictions(@NonNull android.content.ComponentName); method @Nullable public String getWifiMacAddress(@NonNull android.content.ComponentName); + method @Nullable public android.app.admin.WifiSsidPolicy getWifiSsidPolicy(); method public boolean grantKeyPairToApp(@Nullable android.content.ComponentName, @NonNull String, @NonNull String); method public boolean grantKeyPairToWifiAuth(@NonNull String); method public boolean hasCaCertInstalled(@Nullable android.content.ComponentName, byte[]); @@ -7453,6 +7462,7 @@ package android.app.admin { method public void setMaximumFailedPasswordsForWipe(@NonNull android.content.ComponentName, int); method public void setMaximumTimeToLock(@NonNull android.content.ComponentName, long); method @NonNull public java.util.List<java.lang.String> setMeteredDataDisabledPackages(@NonNull android.content.ComponentName, @NonNull java.util.List<java.lang.String>); + method public void setMinimumRequiredWifiSecurityLevel(int); method public void setNearbyAppStreamingPolicy(int); method public void setNearbyNotificationStreamingPolicy(int); method public void setNetworkLoggingEnabled(@Nullable android.content.ComponentName, boolean); @@ -7501,6 +7511,7 @@ package android.app.admin { method public void setUsbDataSignalingEnabled(boolean); method public void setUserControlDisabledPackages(@NonNull android.content.ComponentName, @NonNull java.util.List<java.lang.String>); method public void setUserIcon(@NonNull android.content.ComponentName, android.graphics.Bitmap); + method public void setWifiSsidPolicy(@Nullable android.app.admin.WifiSsidPolicy); method public int startUserInBackground(@NonNull android.content.ComponentName, @NonNull android.os.UserHandle); method public int stopUser(@NonNull android.content.ComponentName, @NonNull android.os.UserHandle); method public boolean switchUser(@NonNull android.content.ComponentName, @Nullable android.os.UserHandle); @@ -7552,6 +7563,7 @@ package android.app.admin { field public static final String EXTRA_PROVISIONING_ACCOUNT_TO_MIGRATE = "android.app.extra.PROVISIONING_ACCOUNT_TO_MIGRATE"; field public static final String EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE = "android.app.extra.PROVISIONING_ADMIN_EXTRAS_BUNDLE"; field public static final String EXTRA_PROVISIONING_ALLOWED_PROVISIONING_MODES = "android.app.extra.PROVISIONING_ALLOWED_PROVISIONING_MODES"; + field public static final String EXTRA_PROVISIONING_ALLOW_OFFLINE = "android.app.extra.PROVISIONING_ALLOW_OFFLINE"; field public static final String EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME = "android.app.extra.PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME"; field public static final String EXTRA_PROVISIONING_DEVICE_ADMIN_MINIMUM_VERSION_CODE = "android.app.extra.PROVISIONING_DEVICE_ADMIN_MINIMUM_VERSION_CODE"; field public static final String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_CHECKSUM = "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_CHECKSUM"; @@ -7595,6 +7607,7 @@ package android.app.admin { field public static final String EXTRA_PROVISIONING_WIFI_USER_CERTIFICATE = "android.app.extra.PROVISIONING_WIFI_USER_CERTIFICATE"; field public static final String EXTRA_RESOURCE_ID = "android.app.extra.RESOURCE_ID"; field public static final String EXTRA_RESOURCE_TYPE_DRAWABLE = "android.app.extra.RESOURCE_TYPE_DRAWABLE"; + field public static final String EXTRA_RESOURCE_TYPE_STRING = "android.app.extra.RESOURCE_TYPE_STRING"; field public static final int FLAG_EVICT_CREDENTIAL_ENCRYPTION_KEY = 1; // 0x1 field public static final int FLAG_MANAGED_CAN_ACCESS_PARENT = 2; // 0x2 field public static final int FLAG_PARENT_CAN_ACCESS_MANAGED = 1; // 0x1 @@ -7669,6 +7682,10 @@ package android.app.admin { field public static final int RESET_PASSWORD_DO_NOT_ASK_CREDENTIALS_ON_BOOT = 2; // 0x2 field public static final int RESET_PASSWORD_REQUIRE_ENTRY = 1; // 0x1 field public static final int SKIP_SETUP_WIZARD = 1; // 0x1 + field public static final int WIFI_SECURITY_ENTERPRISE_192 = 3; // 0x3 + field public static final int WIFI_SECURITY_ENTERPRISE_EAP = 2; // 0x2 + field public static final int WIFI_SECURITY_OPEN = 0; // 0x0 + field public static final int WIFI_SECURITY_PERSONAL = 1; // 0x1 field public static final int WIPE_EUICC = 4; // 0x4 field public static final int WIPE_EXTERNAL_STORAGE = 1; // 0x1 field public static final int WIPE_RESET_PROTECTION_DATA = 2; // 0x2 @@ -7856,6 +7873,18 @@ package android.app.admin { field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.UnsafeStateException> CREATOR; } + public final class WifiSsidPolicy implements android.os.Parcelable { + method @NonNull public static android.app.admin.WifiSsidPolicy createAllowlistPolicy(@NonNull java.util.Set<java.lang.String>); + method @NonNull public static android.app.admin.WifiSsidPolicy createDenylistPolicy(@NonNull java.util.Set<java.lang.String>); + method public int describeContents(); + method public int getPolicyType(); + method @NonNull public java.util.Set<java.lang.String> getSsids(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.WifiSsidPolicy> CREATOR; + field public static final int WIFI_SSID_POLICY_TYPE_ALLOWLIST = 0; // 0x0 + field public static final int WIFI_SSID_POLICY_TYPE_DENYLIST = 1; // 0x1 + } + } package android.app.assist { @@ -8848,11 +8877,12 @@ package android.bluetooth { method @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public boolean isDiscovering(); method public boolean isEnabled(); method public boolean isLe2MPhySupported(); + method public int isLeAudioBroadcastAssistantSupported(); + method public int isLeAudioBroadcastSourceSupported(); method public int isLeAudioSupported(); method public boolean isLeCodedPhySupported(); method public boolean isLeExtendedAdvertisingSupported(); method public boolean isLePeriodicAdvertisingSupported(); - method public int isLePeriodicAdvertisingSyncTransferSenderSupported(); method public boolean isMultipleAdvertisementSupported(); method public boolean isOffloadedFilteringSupported(); method public boolean isOffloadedScanBatchingSupported(); @@ -9261,6 +9291,7 @@ package android.bluetooth { field public static final int SOURCE_CODEC_TYPE_APTX = 2; // 0x2 field public static final int SOURCE_CODEC_TYPE_APTX_HD = 3; // 0x3 field public static final int SOURCE_CODEC_TYPE_INVALID = 1000000; // 0xf4240 + field public static final int SOURCE_CODEC_TYPE_LC3 = 5; // 0x5 field public static final int SOURCE_CODEC_TYPE_LDAC = 4; // 0x4 field public static final int SOURCE_CODEC_TYPE_SBC = 0; // 0x0 } @@ -9715,6 +9746,7 @@ package android.bluetooth { method public void close(); method protected void finalize(); method @NonNull @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices(); + method @Nullable @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public android.bluetooth.BluetoothDevice getConnectedGroupLeadDevice(int); method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public int getConnectionState(@NonNull android.bluetooth.BluetoothDevice); method @NonNull @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(@NonNull int[]); method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public int getGroupId(@NonNull android.bluetooth.BluetoothDevice); @@ -9753,6 +9785,7 @@ package android.bluetooth { field public static final String EXTRA_STATE = "android.bluetooth.profile.extra.STATE"; field public static final int GATT = 7; // 0x7 field public static final int GATT_SERVER = 8; // 0x8 + field public static final int HAP_CLIENT = 28; // 0x1c field public static final int HEADSET = 1; // 0x1 field @Deprecated public static final int HEALTH = 3; // 0x3 field public static final int HEARING_AID = 21; // 0x15 @@ -10928,10 +10961,10 @@ package android.content { method @Nullable public abstract android.content.Intent registerReceiver(android.content.BroadcastReceiver, android.content.IntentFilter, @Nullable String, @Nullable android.os.Handler, int); method @Deprecated @RequiresPermission(android.Manifest.permission.BROADCAST_STICKY) public abstract void removeStickyBroadcast(@RequiresPermission android.content.Intent); method @Deprecated @RequiresPermission(allOf={"android.permission.INTERACT_ACROSS_USERS", android.Manifest.permission.BROADCAST_STICKY}) public abstract void removeStickyBroadcastAsUser(@RequiresPermission android.content.Intent, android.os.UserHandle); + method public void revokeOwnPermissionOnKill(@NonNull String); + method public void revokeOwnPermissionsOnKill(@NonNull java.util.Collection<java.lang.String>); method public abstract void revokeUriPermission(android.net.Uri, int); method public abstract void revokeUriPermission(String, android.net.Uri, int); - method public void selfRevokePermission(@NonNull String); - method public void selfRevokePermissions(@NonNull java.util.Collection<java.lang.String>); method public abstract void sendBroadcast(@RequiresPermission android.content.Intent); method public abstract void sendBroadcast(@RequiresPermission android.content.Intent, @Nullable String); method @RequiresPermission("android.permission.INTERACT_ACROSS_USERS") public abstract void sendBroadcastAsUser(@RequiresPermission android.content.Intent, android.os.UserHandle); @@ -11059,8 +11092,8 @@ package android.content { field public static final String TELEPHONY_SUBSCRIPTION_SERVICE = "telephony_subscription_service"; field public static final String TEXT_CLASSIFICATION_SERVICE = "textclassification"; field public static final String TEXT_SERVICES_MANAGER_SERVICE = "textservices"; - field public static final String TV_IAPP_SERVICE = "tv_iapp"; field public static final String TV_INPUT_SERVICE = "tv_input"; + field public static final String TV_INTERACTIVE_APP_SERVICE = "tv_interactive_app"; field public static final String UI_MODE_SERVICE = "uimode"; field public static final String USAGE_STATS_SERVICE = "usagestats"; field public static final String USB_SERVICE = "usb"; @@ -13179,6 +13212,7 @@ package android.content.pm { field public static final String FEATURE_WIFI_DIRECT = "android.hardware.wifi.direct"; field public static final String FEATURE_WIFI_PASSPOINT = "android.hardware.wifi.passpoint"; field public static final String FEATURE_WIFI_RTT = "android.hardware.wifi.rtt"; + field public static final String FEATURE_WINDOW_MAGNIFICATION = "android.software.window_magnification"; field public static final int FLAG_PERMISSION_WHITELIST_INSTALLER = 2; // 0x2 field public static final int FLAG_PERMISSION_WHITELIST_SYSTEM = 1; // 0x1 field public static final int FLAG_PERMISSION_WHITELIST_UPGRADE = 4; // 0x4 @@ -13471,6 +13505,7 @@ package android.content.pm { public final class ShortcutInfo implements android.os.Parcelable { method public int describeContents(); method @Nullable public android.content.ComponentName getActivity(); + method @NonNull public java.util.List<java.lang.String> getCapabilityParameterValues(@NonNull String, @NonNull String); method @Nullable public java.util.Set<java.lang.String> getCategories(); method @Nullable public CharSequence getDisabledMessage(); method public int getDisabledReason(); @@ -13485,6 +13520,7 @@ package android.content.pm { method public int getRank(); method @Nullable public CharSequence getShortLabel(); method public android.os.UserHandle getUserHandle(); + method public boolean hasCapability(@NonNull String); method public boolean hasKeyFieldsOnly(); method public boolean isCached(); method public boolean isDeclaredInManifest(); @@ -13509,6 +13545,7 @@ package android.content.pm { public static class ShortcutInfo.Builder { ctor public ShortcutInfo.Builder(android.content.Context, String); + method @NonNull public android.content.pm.ShortcutInfo.Builder addCapabilityBinding(@NonNull String, @Nullable String, @Nullable java.util.List<java.lang.String>); method @NonNull public android.content.pm.ShortcutInfo build(); method @NonNull public android.content.pm.ShortcutInfo.Builder setActivity(@NonNull android.content.ComponentName); method @NonNull public android.content.pm.ShortcutInfo.Builder setCategories(java.util.Set<java.lang.String>); @@ -17582,12 +17619,16 @@ package android.graphics.text { public final class LineBreakConfig { ctor public LineBreakConfig(); method public int getLineBreakStyle(); - method public void set(@Nullable android.graphics.text.LineBreakConfig); + method public int getLineBreakWordStyle(); + method public void set(@NonNull android.graphics.text.LineBreakConfig); method public void setLineBreakStyle(int); + method public void setLineBreakWordStyle(int); field public static final int LINE_BREAK_STYLE_LOOSE = 1; // 0x1 field public static final int LINE_BREAK_STYLE_NONE = 0; // 0x0 field public static final int LINE_BREAK_STYLE_NORMAL = 2; // 0x2 field public static final int LINE_BREAK_STYLE_STRICT = 3; // 0x3 + field public static final int LINE_BREAK_WORD_STYLE_NONE = 0; // 0x0 + field public static final int LINE_BREAK_WORD_STYLE_PHRASE = 1; // 0x1 } public class LineBreaker { @@ -18043,6 +18084,7 @@ package android.hardware { field public static final long USAGE_CPU_READ_RARELY = 2L; // 0x2L field public static final long USAGE_CPU_WRITE_OFTEN = 48L; // 0x30L field public static final long USAGE_CPU_WRITE_RARELY = 32L; // 0x20L + field public static final long USAGE_FRONT_BUFFER = 1L; // 0x1L field public static final long USAGE_GPU_COLOR_OUTPUT = 512L; // 0x200L field public static final long USAGE_GPU_CUBE_MAP = 33554432L; // 0x2000000L field public static final long USAGE_GPU_DATA_BUFFER = 16777216L; // 0x1000000L @@ -18536,12 +18578,14 @@ package android.hardware.camera2 { field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> NOISE_REDUCTION_AVAILABLE_NOISE_REDUCTION_MODES; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> REPROCESS_MAX_CAPTURE_STALL; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> REQUEST_AVAILABLE_CAPABILITIES; + field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.DynamicRangeProfiles> REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> REQUEST_MAX_NUM_INPUT_STREAMS; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> REQUEST_MAX_NUM_OUTPUT_PROC; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> REQUEST_MAX_NUM_OUTPUT_PROC_STALLING; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> REQUEST_MAX_NUM_OUTPUT_RAW; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> REQUEST_PARTIAL_RESULT_COUNT; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Byte> REQUEST_PIPELINE_MAX_DEPTH; + field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> REQUEST_RECOMMENDED_TEN_BIT_DYNAMIC_RANGE_PROFILE; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Float> SCALER_AVAILABLE_MAX_DIGITAL_ZOOM; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> SCALER_AVAILABLE_ROTATE_AND_CROP_MODES; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> SCALER_CROPPING_TYPE; @@ -18549,6 +18593,7 @@ package android.hardware.camera2 { field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.MandatoryStreamCombination[]> SCALER_MANDATORY_CONCURRENT_STREAM_COMBINATIONS; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.MandatoryStreamCombination[]> SCALER_MANDATORY_MAXIMUM_RESOLUTION_STREAM_COMBINATIONS; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.MandatoryStreamCombination[]> SCALER_MANDATORY_STREAM_COMBINATIONS; + field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.MandatoryStreamCombination[]> SCALER_MANDATORY_TEN_BIT_OUTPUT_STREAM_COMBINATIONS; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.MultiResolutionStreamConfigurationMap> SCALER_MULTI_RESOLUTION_STREAM_CONFIGURATION_MAP; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.StreamConfigurationMap> SCALER_STREAM_CONFIGURATION_MAP; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.StreamConfigurationMap> SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION; @@ -18865,6 +18910,7 @@ package android.hardware.camera2 { field public static final int REQUEST_AVAILABLE_CAPABILITIES_BURST_CAPTURE = 6; // 0x6 field public static final int REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO = 9; // 0x9 field public static final int REQUEST_AVAILABLE_CAPABILITIES_DEPTH_OUTPUT = 8; // 0x8 + field public static final int REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT = 18; // 0x12 field public static final int REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA = 11; // 0xb field public static final int REQUEST_AVAILABLE_CAPABILITIES_MANUAL_POST_PROCESSING = 2; // 0x2 field public static final int REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR = 1; // 0x1 @@ -19218,6 +19264,25 @@ package android.hardware.camera2.params { field public static final long NORMAL = 0L; // 0x0L } + public final class DynamicRangeProfiles { + ctor public DynamicRangeProfiles(@NonNull int[]); + method @NonNull public java.util.Set<java.lang.Integer> getProfileCaptureRequestConstraints(int); + method @NonNull public java.util.Set<java.lang.Integer> getSupportedProfiles(); + field public static final int DOLBY_VISION_10B_HDR_OEM = 64; // 0x40 + field public static final int DOLBY_VISION_10B_HDR_OEM_PO = 128; // 0x80 + field public static final int DOLBY_VISION_10B_HDR_REF = 16; // 0x10 + field public static final int DOLBY_VISION_10B_HDR_REF_PO = 32; // 0x20 + field public static final int DOLBY_VISION_8B_HDR_OEM = 1024; // 0x400 + field public static final int DOLBY_VISION_8B_HDR_OEM_PO = 2048; // 0x800 + field public static final int DOLBY_VISION_8B_HDR_REF = 256; // 0x100 + field public static final int DOLBY_VISION_8B_HDR_REF_PO = 512; // 0x200 + field public static final int HDR10 = 4; // 0x4 + field public static final int HDR10_PLUS = 8; // 0x8 + field public static final int HLG10 = 2; // 0x2 + field public static final int PUBLIC_MAX = 4096; // 0x1000 + field public static final int STANDARD = 1; // 0x1 + } + public final class ExtensionSessionConfiguration { ctor public ExtensionSessionConfiguration(int, @NonNull java.util.List<android.hardware.camera2.params.OutputConfiguration>, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.CameraExtensionSession.StateCallback); method @NonNull public java.util.concurrent.Executor getExecutor(); @@ -19264,8 +19329,10 @@ package android.hardware.camera2.params { } public static final class MandatoryStreamCombination.MandatoryStreamInformation { + method public int get10BitFormat(); method @NonNull public java.util.List<android.util.Size> getAvailableSizes(); method public int getFormat(); + method public boolean is10BitCapable(); method public boolean isInput(); method public boolean isMaximumSize(); method public boolean isUltraHighResolution(); @@ -19319,12 +19386,14 @@ package android.hardware.camera2.params { method @NonNull public static java.util.Collection<android.hardware.camera2.params.OutputConfiguration> createInstancesForMultiResolutionOutput(@NonNull android.hardware.camera2.MultiResolutionImageReader); method public int describeContents(); method public void enableSurfaceSharing(); + method public int getDynamicRangeProfile(); method public int getMaxSharedSurfaceCount(); method @Nullable public android.view.Surface getSurface(); method public int getSurfaceGroupId(); method @NonNull public java.util.List<android.view.Surface> getSurfaces(); method public void removeSensorPixelModeUsed(int); method public void removeSurface(@NonNull android.view.Surface); + method public void setDynamicRangeProfile(int); method public void setPhysicalCameraId(@Nullable String); method public void writeToParcel(android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.hardware.camera2.params.OutputConfiguration> CREATOR; @@ -19350,6 +19419,7 @@ package android.hardware.camera2.params { method @Nullable public java.util.Set<java.lang.Integer> getValidOutputFormatsForInput(int); method public boolean isOutputSupportedFor(int); method public boolean isOutputSupportedFor(@NonNull android.view.Surface); + field public static final int USECASE_10BIT_OUTPUT = 8; // 0x8 field public static final int USECASE_LOW_LATENCY_SNAPSHOT = 6; // 0x6 field public static final int USECASE_PREVIEW = 0; // 0x0 field public static final int USECASE_RAW = 5; // 0x5 @@ -20891,6 +20961,7 @@ package android.media { method public boolean isSink(); method public boolean isSource(); field public static final int TYPE_AUX_LINE = 19; // 0x13 + field public static final int TYPE_BLE_BROADCAST = 30; // 0x1e field public static final int TYPE_BLE_HEADSET = 26; // 0x1a field public static final int TYPE_BLE_SPEAKER = 27; // 0x1b field public static final int TYPE_BLUETOOTH_A2DP = 8; // 0x8 @@ -22041,14 +22112,30 @@ package android.media { public class ImageWriter implements java.lang.AutoCloseable { method public void close(); method public android.media.Image dequeueInputImage(); + method public long getDataSpace(); method public int getFormat(); + method public int getHardwareBufferFormat(); + method public int getHeight(); method public int getMaxImages(); + method public long getUsage(); + method public int getWidth(); method @NonNull public static android.media.ImageWriter newInstance(@NonNull android.view.Surface, @IntRange(from=1) int); method @NonNull public static android.media.ImageWriter newInstance(@NonNull android.view.Surface, @IntRange(from=1) int, int); method public void queueInputImage(android.media.Image); method public void setOnImageReleasedListener(android.media.ImageWriter.OnImageReleasedListener, android.os.Handler); } + public static final class ImageWriter.Builder { + ctor public ImageWriter.Builder(@NonNull android.view.Surface); + method @NonNull public android.media.ImageWriter build(); + method @NonNull public android.media.ImageWriter.Builder setDataSpace(long); + method @NonNull public android.media.ImageWriter.Builder setHardwareBufferFormat(int); + method @NonNull public android.media.ImageWriter.Builder setImageFormat(int); + method @NonNull public android.media.ImageWriter.Builder setMaxImages(@IntRange(from=1) int); + method @NonNull public android.media.ImageWriter.Builder setUsage(long); + method @NonNull public android.media.ImageWriter.Builder setWidthAndHeight(@IntRange(from=1) int, @IntRange(from=1) int); + } + public static interface ImageWriter.OnImageReleasedListener { method public void onImageReleased(android.media.ImageWriter); } @@ -22460,6 +22547,7 @@ package android.media { field @Deprecated public static final int COLOR_TI_FormatYUV420PackedSemiPlanar = 2130706688; // 0x7f000100 field public static final String FEATURE_AdaptivePlayback = "adaptive-playback"; field public static final String FEATURE_DynamicTimestamp = "dynamic-timestamp"; + field public static final String FEATURE_EncodingStatistics = "encoding-statistics"; field public static final String FEATURE_FrameParsing = "frame-parsing"; field public static final String FEATURE_IntraRefresh = "intra-refresh"; field public static final String FEATURE_LowLatency = "low-latency"; @@ -23234,6 +23322,7 @@ package android.media { field public static final String KEY_OPERATING_RATE = "operating-rate"; field public static final String KEY_OUTPUT_REORDER_DEPTH = "output-reorder-depth"; field public static final String KEY_PCM_ENCODING = "pcm-encoding"; + field public static final String KEY_PICTURE_TYPE = "picture-type"; field public static final String KEY_PIXEL_ASPECT_RATIO_HEIGHT = "sar-height"; field public static final String KEY_PIXEL_ASPECT_RATIO_WIDTH = "sar-width"; field public static final String KEY_PREPEND_HEADER_TO_SYNC_FRAMES = "prepend-sps-pps-to-idr-frames"; @@ -23251,6 +23340,8 @@ package android.media { field public static final String KEY_TILE_HEIGHT = "tile-height"; field public static final String KEY_TILE_WIDTH = "tile-width"; field public static final String KEY_TRACK_ID = "track-id"; + field public static final String KEY_VIDEO_ENCODING_STATISTICS_LEVEL = "video-encoding-statistics-level"; + field public static final String KEY_VIDEO_QP_AVERAGE = "video-qp-average"; field public static final String KEY_VIDEO_QP_B_MAX = "video-qp-b-max"; field public static final String KEY_VIDEO_QP_B_MIN = "video-qp-b-min"; field public static final String KEY_VIDEO_QP_I_MAX = "video-qp-i-max"; @@ -23311,12 +23402,18 @@ package android.media { field public static final String MIMETYPE_VIDEO_SCRAMBLED = "video/scrambled"; field public static final String MIMETYPE_VIDEO_VP8 = "video/x-vnd.on2.vp8"; field public static final String MIMETYPE_VIDEO_VP9 = "video/x-vnd.on2.vp9"; + field public static final int PICTURE_TYPE_B = 3; // 0x3 + field public static final int PICTURE_TYPE_I = 1; // 0x1 + field public static final int PICTURE_TYPE_P = 2; // 0x2 + field public static final int PICTURE_TYPE_UNKNOWN = 0; // 0x0 field public static final int TYPE_BYTE_BUFFER = 5; // 0x5 field public static final int TYPE_FLOAT = 3; // 0x3 field public static final int TYPE_INTEGER = 1; // 0x1 field public static final int TYPE_LONG = 2; // 0x2 field public static final int TYPE_NULL = 0; // 0x0 field public static final int TYPE_STRING = 4; // 0x4 + field public static final int VIDEO_ENCODING_STATISTICS_LEVEL_1 = 1; // 0x1 + field public static final int VIDEO_ENCODING_STATISTICS_LEVEL_NONE = 0; // 0x0 } public final class MediaMetadata implements android.os.Parcelable { @@ -25679,6 +25776,7 @@ package android.media.midi { public final class MidiDeviceInfo implements android.os.Parcelable { method public int describeContents(); + method public int getDefaultProtocol(); method public int getId(); method public int getInputPortCount(); method public int getOutputPortCount(); @@ -25695,6 +25793,14 @@ package android.media.midi { field public static final String PROPERTY_SERIAL_NUMBER = "serial_number"; field public static final String PROPERTY_USB_DEVICE = "usb_device"; field public static final String PROPERTY_VERSION = "version"; + field public static final int PROTOCOL_UMP_MIDI_1_0_UP_TO_128_BITS = 3; // 0x3 + field public static final int PROTOCOL_UMP_MIDI_1_0_UP_TO_128_BITS_AND_JRTS = 4; // 0x4 + field public static final int PROTOCOL_UMP_MIDI_1_0_UP_TO_64_BITS = 1; // 0x1 + field public static final int PROTOCOL_UMP_MIDI_1_0_UP_TO_64_BITS_AND_JRTS = 2; // 0x2 + field public static final int PROTOCOL_UMP_MIDI_2_0 = 17; // 0x11 + field public static final int PROTOCOL_UMP_MIDI_2_0_AND_JRTS = 18; // 0x12 + field public static final int PROTOCOL_UMP_USE_MIDI_CI = 0; // 0x0 + field public static final int PROTOCOL_UNKNOWN = -1; // 0xffffffff field public static final int TYPE_BLUETOOTH = 3; // 0x3 field public static final int TYPE_USB = 1; // 0x1 field public static final int TYPE_VIRTUAL = 2; // 0x2 @@ -25735,11 +25841,15 @@ package android.media.midi { } public final class MidiManager { - method public android.media.midi.MidiDeviceInfo[] getDevices(); + method @Deprecated public android.media.midi.MidiDeviceInfo[] getDevices(); + method @NonNull public java.util.Set<android.media.midi.MidiDeviceInfo> getDevicesForTransport(int); method public void openBluetoothDevice(android.bluetooth.BluetoothDevice, android.media.midi.MidiManager.OnDeviceOpenedListener, android.os.Handler); method public void openDevice(android.media.midi.MidiDeviceInfo, android.media.midi.MidiManager.OnDeviceOpenedListener, android.os.Handler); - method public void registerDeviceCallback(android.media.midi.MidiManager.DeviceCallback, android.os.Handler); + method @Deprecated public void registerDeviceCallback(android.media.midi.MidiManager.DeviceCallback, android.os.Handler); + method public void registerDeviceCallback(int, @NonNull java.util.concurrent.Executor, @NonNull android.media.midi.MidiManager.DeviceCallback); method public void unregisterDeviceCallback(android.media.midi.MidiManager.DeviceCallback); + field public static final int TRANSPORT_MIDI_BYTE_STREAM = 1; // 0x1 + field public static final int TRANSPORT_UNIVERSAL_MIDI_PACKETS = 2; // 0x2 } public static class MidiManager.DeviceCallback { @@ -26800,16 +26910,43 @@ package android.media.tv { package android.media.tv.interactive { - public final class TvIAppManager { + public final class TvInteractiveAppInfo implements android.os.Parcelable { + ctor public TvInteractiveAppInfo(@NonNull android.content.Context, @NonNull android.content.ComponentName); + method public int describeContents(); + method @NonNull public String getId(); + method @Nullable public android.content.pm.ServiceInfo getServiceInfo(); + method @NonNull public int getSupportedTypes(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.media.tv.interactive.TvInteractiveAppInfo> CREATOR; + field public static final int INTERACTIVE_APP_TYPE_ATSC = 2; // 0x2 + field public static final int INTERACTIVE_APP_TYPE_GINGA = 4; // 0x4 + field public static final int INTERACTIVE_APP_TYPE_HBBTV = 1; // 0x1 + } + + public final class TvInteractiveAppManager { + method @NonNull public java.util.List<android.media.tv.interactive.TvInteractiveAppInfo> getTvInteractiveAppServiceList(); } - public abstract class TvIAppService extends android.app.Service { - ctor public TvIAppService(); + public abstract class TvInteractiveAppService extends android.app.Service { + ctor public TvInteractiveAppService(); method public final android.os.IBinder onBind(android.content.Intent); - field public static final String SERVICE_INTERFACE = "android.media.tv.interactive.TvIAppService"; + field public static final String SERVICE_INTERFACE = "android.media.tv.interactive.TvInteractiveAppService"; field public static final String SERVICE_META_DATA = "android.media.tv.interactive.app"; } + public class TvInteractiveAppView extends android.view.ViewGroup { + ctor public TvInteractiveAppView(@NonNull android.content.Context); + ctor public TvInteractiveAppView(@NonNull android.content.Context, @Nullable android.util.AttributeSet); + ctor public TvInteractiveAppView(@NonNull android.content.Context, @Nullable android.util.AttributeSet, int); + method public void clearCallback(); + method public void setCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.tv.interactive.TvInteractiveAppView.TvInteractiveAppCallback); + method public void startInteractiveApp(); + } + + public abstract static class TvInteractiveAppView.TvInteractiveAppCallback { + ctor public TvInteractiveAppView.TvInteractiveAppCallback(); + } + } package android.mtp { @@ -32009,6 +32146,7 @@ package android.os { method @IntRange(from=0xffffffff) public int indexOf(java.util.Locale); method public boolean isEmpty(); method public static boolean isPseudoLocale(@Nullable android.icu.util.ULocale); + method public static boolean matchesLanguageAndScript(@NonNull java.util.Locale, @NonNull java.util.Locale); method public static void setDefault(@NonNull @Size(min=1) android.os.LocaleList); method @IntRange(from=0) public int size(); method @NonNull public String toLanguageTags(); @@ -32186,7 +32324,7 @@ package android.os { method @Deprecated public void readMap(@NonNull java.util.Map, @Nullable ClassLoader); method public <K, V> void readMap(@NonNull java.util.Map<? super K,? super V>, @Nullable ClassLoader, @NonNull Class<K>, @NonNull Class<V>); method @Deprecated @Nullable public <T extends android.os.Parcelable> T readParcelable(@Nullable ClassLoader); - method @Nullable public <T> T readParcelable(@Nullable ClassLoader, @NonNull Class<T>); + method @Nullable public <T extends android.os.Parcelable> T readParcelable(@Nullable ClassLoader, @NonNull Class<? super T>); method @Deprecated @Nullable public android.os.Parcelable[] readParcelableArray(@Nullable ClassLoader); method @Nullable public <T> T[] readParcelableArray(@Nullable ClassLoader, @NonNull Class<T>); method @Deprecated @Nullable public android.os.Parcelable.Creator<?> readParcelableCreator(@Nullable ClassLoader); @@ -32196,7 +32334,7 @@ package android.os { method @Nullable public android.os.PersistableBundle readPersistableBundle(); method @Nullable public android.os.PersistableBundle readPersistableBundle(@Nullable ClassLoader); method @Deprecated @Nullable public java.io.Serializable readSerializable(); - method @Nullable public <T> T readSerializable(@Nullable ClassLoader, @NonNull Class<T>); + method @Nullable public <T extends java.io.Serializable> T readSerializable(@Nullable ClassLoader, @NonNull Class<? super T>); method @NonNull public android.util.Size readSize(); method @NonNull public android.util.SizeF readSizeF(); method @Deprecated @Nullable public <T> android.util.SparseArray<T> readSparseArray(@Nullable ClassLoader); @@ -32466,6 +32604,7 @@ package android.os { method public static final boolean is64Bit(); method public static boolean isApplicationUid(int); method public static final boolean isIsolated(); + method public static final boolean isSupplemental(); method public static final void killProcess(int); method public static final int myPid(); method @NonNull public static String myProcessName(); @@ -32906,9 +33045,13 @@ package android.os { method @NonNull public int[] areEffectsSupported(@NonNull int...); method @NonNull public boolean[] arePrimitivesSupported(@NonNull int...); method @RequiresPermission(android.Manifest.permission.VIBRATE) public abstract void cancel(); + method @Nullable public android.os.vibrator.VibratorFrequencyProfile getFrequencyProfile(); method public int getId(); method @NonNull public int[] getPrimitiveDurations(@NonNull int...); + method public float getQFactor(); + method public float getResonantFrequency(); method public abstract boolean hasAmplitudeControl(); + method public boolean hasFrequencyControl(); method public abstract boolean hasVibrator(); method @Deprecated @RequiresPermission(android.Manifest.permission.VIBRATE) public void vibrate(long); method @Deprecated @RequiresPermission(android.Manifest.permission.VIBRATE) public void vibrate(long, android.media.AudioAttributes); @@ -33234,6 +33377,17 @@ package android.os.strictmode { } +package android.os.vibrator { + + public final class VibratorFrequencyProfile { + method public float getMaxAmplitudeMeasurementInterval(); + method @FloatRange(from=0, to=1) @NonNull public float[] getMaxAmplitudeMeasurements(); + method public float getMaxFrequency(); + method public float getMinFrequency(); + } + +} + package android.preference { @Deprecated public class CheckBoxPreference extends android.preference.TwoStatePreference { @@ -36231,7 +36385,7 @@ package android.provider { field public static final String DTMF_TONE_WHEN_DIALING = "dtmf_tone"; field public static final String END_BUTTON_BEHAVIOR = "end_button_behavior"; field public static final String FONT_SCALE = "font_scale"; - field public static final String HAPTIC_FEEDBACK_ENABLED = "haptic_feedback_enabled"; + field @Deprecated public static final String HAPTIC_FEEDBACK_ENABLED = "haptic_feedback_enabled"; field @Deprecated public static final String HTTP_PROXY = "http_proxy"; field @Deprecated public static final String INSTALL_NON_MARKET_APPS = "install_non_market_apps"; field @Deprecated public static final String LOCATION_PROVIDERS_ALLOWED = "location_providers_allowed"; @@ -36275,7 +36429,7 @@ package android.provider { field public static final String USER_ROTATION = "user_rotation"; field @Deprecated public static final String USE_GOOGLE_MAIL = "use_google_mail"; field public static final String VIBRATE_ON = "vibrate_on"; - field public static final String VIBRATE_WHEN_RINGING = "vibrate_when_ringing"; + field @Deprecated public static final String VIBRATE_WHEN_RINGING = "vibrate_when_ringing"; field @Deprecated public static final String WAIT_FOR_DEBUGGER = "wait_for_debugger"; field @Deprecated public static final String WALLPAPER_ACTIVITY = "wallpaper_activity"; field @Deprecated public static final String WIFI_MAX_DHCP_RETRY_COUNT = "wifi_max_dhcp_retry_count"; @@ -40384,6 +40538,7 @@ package android.telecom { public final class Call { method public void addConferenceParticipants(@NonNull java.util.List<android.net.Uri>); method public void answer(int); + method public void answerCall(@NonNull android.telecom.CallEndpoint, int); method public void conference(android.telecom.Call); method public void deflect(android.net.Uri); method public void disconnect(); @@ -40404,7 +40559,9 @@ package android.telecom { method public void phoneAccountSelected(android.telecom.PhoneAccountHandle, boolean); method public void playDtmfTone(char); method public void postDialContinue(boolean); - method public void pullExternalCall(); + method public void pullCall(); + method @Deprecated public void pullExternalCall(); + method public void pushCall(@NonNull android.telecom.CallEndpoint); method public void putExtras(android.os.Bundle); method public void registerCallback(android.telecom.Call.Callback); method public void registerCallback(android.telecom.Call.Callback, android.os.Handler); @@ -40447,7 +40604,10 @@ package android.telecom { public abstract static class Call.Callback { ctor public Call.Callback(); + method public void onAnswerFailed(@NonNull android.telecom.CallEndpoint, int); method public void onCallDestroyed(android.telecom.Call); + method public void onCallPullFailed(int); + method public void onCallPushFailed(@NonNull android.telecom.CallEndpoint, int); method public void onCannedTextResponsesLoaded(android.telecom.Call, java.util.List<java.lang.String>); method public void onChildrenChanged(android.telecom.Call, java.util.List<android.telecom.Call>); method public void onConferenceableCallsChanged(android.telecom.Call, java.util.List<android.telecom.Call>); @@ -40463,11 +40623,22 @@ package android.telecom { method public void onRttStatusChanged(android.telecom.Call, boolean, android.telecom.Call.RttCall); method public void onStateChanged(android.telecom.Call, int); method public void onVideoCallChanged(android.telecom.Call, android.telecom.InCallService.VideoCall); + field public static final int ANSWER_FAILED_ENDPOINT_REJECTED = 3; // 0x3 + field public static final int ANSWER_FAILED_ENDPOINT_TIMEOUT = 2; // 0x2 + field public static final int ANSWER_FAILED_ENDPOINT_UNAVAILABLE = 1; // 0x1 + field public static final int ANSWER_FAILED_UNKNOWN_REASON = 0; // 0x0 field public static final int HANDOVER_FAILURE_DEST_APP_REJECTED = 1; // 0x1 field public static final int HANDOVER_FAILURE_NOT_SUPPORTED = 2; // 0x2 field public static final int HANDOVER_FAILURE_ONGOING_EMERGENCY_CALL = 4; // 0x4 field public static final int HANDOVER_FAILURE_UNKNOWN = 5; // 0x5 field public static final int HANDOVER_FAILURE_USER_REJECTED = 3; // 0x3 + field public static final int PULL_FAILED_ENDPOINT_REJECTED = 2; // 0x2 + field public static final int PULL_FAILED_ENDPOINT_TIMEOUT = 1; // 0x1 + field public static final int PULL_FAILED_UNKNOWN_REASON = 0; // 0x0 + field public static final int PUSH_FAILED_ENDPOINT_REJECTED = 3; // 0x3 + field public static final int PUSH_FAILED_ENDPOINT_TIMEOUT = 2; // 0x2 + field public static final int PUSH_FAILED_ENDPOINT_UNAVAILABLE = 1; // 0x1 + field public static final int PUSH_FAILED_UNKNOWN_REASON = 0; // 0x0 } public static class Call.Details { @@ -40475,6 +40646,8 @@ package android.telecom { method public boolean can(int); method public static String capabilitiesToString(int); method public android.telecom.PhoneAccountHandle getAccountHandle(); + method @Nullable public android.telecom.CallEndpoint getActiveCallEndpoint(); + method @NonNull public java.util.Set<android.telecom.CallEndpoint> getAvailableCallEndpoints(); method public int getCallCapabilities(); method public int getCallDirection(); method public int getCallProperties(); @@ -40568,6 +40741,34 @@ package android.telecom { field public static final int ROUTE_WIRED_OR_EARPIECE = 5; // 0x5 } + public final class CallEndpoint implements android.os.Parcelable { + ctor public CallEndpoint(@NonNull android.os.ParcelUuid, @NonNull CharSequence, int, @NonNull android.content.ComponentName); + method public int describeContents(); + method @NonNull public CharSequence getDescription(); + method @NonNull public android.os.ParcelUuid getIdentifier(); + method public int getType(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.telecom.CallEndpoint> CREATOR; + field public static final int ENDPOINT_TYPE_TETHERED = 2; // 0x2 + field public static final int ENDPOINT_TYPE_UNTETHERED = 1; // 0x1 + } + + public interface CallEndpointCallback { + method public void onCallEndpointSessionActivationTimeout(); + method public void onCallEndpointSessionDeactivated(); + } + + public class CallEndpointSession { + method public void setCallEndpointSessionActivated(); + method public void setCallEndpointSessionActivationFailed(int); + method public void setCallEndpointSessionDeactivated(); + field public static final int ACTIVATION_FAILURE_REJECTED = 1; // 0x1 + field public static final int ACTIVATION_FAILURE_UNAVAILABLE = 0; // 0x0 + field public static final int ANSWER_REQUEST = 1; // 0x1 + field public static final int PLACE_REQUEST = 3; // 0x3 + field public static final int PUSH_REQUEST = 2; // 0x2 + } + public abstract class CallRedirectionService extends android.app.Service { ctor public CallRedirectionService(); method public final void cancelCall(); @@ -40837,7 +41038,6 @@ package android.telecom { field public static final int PROPERTY_IS_RTT = 256; // 0x100 field public static final int PROPERTY_NETWORK_IDENTIFIED_EMERGENCY_CALL = 1024; // 0x400 field public static final int PROPERTY_SELF_MANAGED = 128; // 0x80 - field public static final int PROPERTY_TETHERED_CALL = 16384; // 0x4000 field public static final int PROPERTY_WIFI = 8; // 0x8 field public static final int STATE_ACTIVE = 4; // 0x4 field public static final int STATE_DIALING = 3; // 0x3 @@ -40971,6 +41171,8 @@ package android.telecom { field public static final int OTHER = 9; // 0x9 field public static final String REASON_EMERGENCY_CALL_PLACED = "REASON_EMERGENCY_CALL_PLACED"; field public static final String REASON_EMULATING_SINGLE_CALL = "EMULATING_SINGLE_CALL"; + field public static final String REASON_ENDPOINT_REJECTED = "REASON_ENDPOINT_REJECTED"; + field public static final String REASON_ENDPOINT_SESSION_DEACTIVATED = "REASON_ENDPOINT_SESSION_DEACTIVATED"; field public static final String REASON_IMS_ACCESS_BLOCKED = "REASON_IMS_ACCESS_BLOCKED"; field public static final String REASON_WIFI_ON_BUT_WFC_OFF = "REASON_WIFI_ON_BUT_WFC_OFF"; field public static final int REJECTED = 6; // 0x6 @@ -40999,6 +41201,7 @@ package android.telecom { method public void onBringToForeground(boolean); method public void onCallAdded(android.telecom.Call); method public void onCallAudioStateChanged(android.telecom.CallAudioState); + method @NonNull public android.telecom.CallEndpointCallback onCallEndpointActivationRequested(@NonNull android.telecom.CallEndpoint, @NonNull android.telecom.CallEndpointSession) throws java.lang.UnsupportedOperationException; method public void onCallRemoved(android.telecom.Call); method public void onCanAddCallChanged(boolean); method public void onConnectionEvent(android.telecom.Call, String, android.os.Bundle); @@ -41261,11 +41464,12 @@ package android.telecom { method @Deprecated @RequiresPermission(android.Manifest.permission.ANSWER_PHONE_CALLS) public boolean endCall(); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.net.Uri getAdnUriForPhoneAccount(android.telecom.PhoneAccountHandle); method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public java.util.List<android.telecom.PhoneAccountHandle> getCallCapablePhoneAccounts(); + method @NonNull public java.util.Set<android.telecom.CallEndpoint> getCallEndpoints(); method public String getDefaultDialerPackage(); method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public android.telecom.PhoneAccountHandle getDefaultOutgoingPhoneAccount(String); method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.READ_SMS, android.Manifest.permission.READ_PHONE_NUMBERS}, conditional=true) public String getLine1Number(android.telecom.PhoneAccountHandle); method public android.telecom.PhoneAccount getPhoneAccount(android.telecom.PhoneAccountHandle); - method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public java.util.List<android.telecom.PhoneAccountHandle> getSelfManagedPhoneAccounts(); + method @RequiresPermission(anyOf={"android.permission.READ_PRIVILEGED_PHONE_STATE", android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.MANAGE_OWN_CALLS}) public java.util.List<android.telecom.PhoneAccountHandle> getSelfManagedPhoneAccounts(); method public android.telecom.PhoneAccountHandle getSimCallManager(); method @Nullable public android.telecom.PhoneAccountHandle getSimCallManagerForSubscription(int); method @Nullable public String getSystemDialerPackage(); @@ -41281,10 +41485,12 @@ package android.telecom { method @RequiresPermission(anyOf={"android.permission.READ_PRIVILEGED_PHONE_STATE", android.Manifest.permission.READ_PHONE_STATE}) public boolean isTtySupported(); method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public boolean isVoiceMailNumber(android.telecom.PhoneAccountHandle, String); method @RequiresPermission(anyOf={android.Manifest.permission.CALL_PHONE, android.Manifest.permission.MANAGE_OWN_CALLS}) public void placeCall(android.net.Uri, android.os.Bundle); + method public void registerCallEndpoints(@NonNull java.util.Set<android.telecom.CallEndpoint>); method public void registerPhoneAccount(android.telecom.PhoneAccount); method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public void showInCallScreen(boolean); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void silenceRinger(); method @RequiresPermission(android.Manifest.permission.CALL_PHONE) public void startConference(@NonNull java.util.List<android.net.Uri>, @NonNull android.os.Bundle); + method public void unregisterCallEndpoints(@NonNull java.util.Set<android.telecom.CallEndpoint>); method public void unregisterPhoneAccount(android.telecom.PhoneAccountHandle); field public static final String ACTION_CHANGE_DEFAULT_DIALER = "android.telecom.action.CHANGE_DEFAULT_DIALER"; field public static final String ACTION_CHANGE_PHONE_ACCOUNTS = "android.telecom.action.CHANGE_PHONE_ACCOUNTS"; @@ -41328,6 +41534,7 @@ package android.telecom { field public static final String EXTRA_PHONE_ACCOUNT_HANDLE = "android.telecom.extra.PHONE_ACCOUNT_HANDLE"; field public static final String EXTRA_PICTURE_URI = "android.telecom.extra.PICTURE_URI"; field public static final String EXTRA_PRIORITY = "android.telecom.extra.PRIORITY"; + field public static final String EXTRA_START_CALL_ON_ENDPOINT = "android.telecom.extra.START_CALL_ON_ENDPOINT"; field public static final String EXTRA_START_CALL_WITH_RTT = "android.telecom.extra.START_CALL_WITH_RTT"; field public static final String EXTRA_START_CALL_WITH_SPEAKERPHONE = "android.telecom.extra.START_CALL_WITH_SPEAKERPHONE"; field public static final String EXTRA_START_CALL_WITH_VIDEO_STATE = "android.telecom.extra.START_CALL_WITH_VIDEO_STATE"; @@ -41339,6 +41546,7 @@ package android.telecom { field public static final String METADATA_IN_CALL_SERVICE_CAR_MODE_UI = "android.telecom.IN_CALL_SERVICE_CAR_MODE_UI"; field public static final String METADATA_IN_CALL_SERVICE_RINGING = "android.telecom.IN_CALL_SERVICE_RINGING"; field public static final String METADATA_IN_CALL_SERVICE_UI = "android.telecom.IN_CALL_SERVICE_UI"; + field public static final String METADATA_STREAMING_TETHERED_CALLS = "android.telecom.STREAMING_TETHERED_CALLS"; field public static final int PRESENTATION_ALLOWED = 1; // 0x1 field public static final int PRESENTATION_PAYPHONE = 4; // 0x4 field public static final int PRESENTATION_RESTRICTED = 2; // 0x2 @@ -41705,11 +41913,11 @@ package android.telephony { field public static final String KEY_CARRIER_SUPPORTS_SS_OVER_UT_BOOL = "carrier_supports_ss_over_ut_bool"; field public static final String KEY_CARRIER_USE_IMS_FIRST_FOR_EMERGENCY_BOOL = "carrier_use_ims_first_for_emergency_bool"; field public static final String KEY_CARRIER_USSD_METHOD_INT = "carrier_ussd_method_int"; - field public static final String KEY_CARRIER_UT_PROVISIONING_REQUIRED_BOOL = "carrier_ut_provisioning_required_bool"; + field @Deprecated public static final String KEY_CARRIER_UT_PROVISIONING_REQUIRED_BOOL = "carrier_ut_provisioning_required_bool"; field public static final String KEY_CARRIER_VOLTE_AVAILABLE_BOOL = "carrier_volte_available_bool"; field public static final String KEY_CARRIER_VOLTE_OVERRIDE_WFC_PROVISIONING_BOOL = "carrier_volte_override_wfc_provisioning_bool"; - field public static final String KEY_CARRIER_VOLTE_PROVISIONED_BOOL = "carrier_volte_provisioned_bool"; - field public static final String KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL = "carrier_volte_provisioning_required_bool"; + field @Deprecated public static final String KEY_CARRIER_VOLTE_PROVISIONED_BOOL = "carrier_volte_provisioned_bool"; + field @Deprecated public static final String KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL = "carrier_volte_provisioning_required_bool"; field public static final String KEY_CARRIER_VOLTE_TTY_SUPPORTED_BOOL = "carrier_volte_tty_supported_bool"; field public static final String KEY_CARRIER_VT_AVAILABLE_BOOL = "carrier_vt_available_bool"; field @Deprecated public static final String KEY_CARRIER_VVM_PACKAGE_NAME_STRING = "carrier_vvm_package_name_string"; @@ -41955,6 +42163,13 @@ package android.telephony { field public static final int IPSEC_ENCRYPTION_ALGORITHM_AES_CBC = 2; // 0x2 field public static final int IPSEC_ENCRYPTION_ALGORITHM_DES_EDE3_CBC = 1; // 0x1 field public static final int IPSEC_ENCRYPTION_ALGORITHM_NULL = 0; // 0x0 + field public static final String KEY_CAPABILITY_CALL_COMPOSER_INT_ARRAY = "ims.key_capability_type_call_composer_int_array"; + field public static final String KEY_CAPABILITY_TYPE_OPTIONS_UCE_INT_ARRAY = "ims.key_capability_type_options_uce_int_array"; + field public static final String KEY_CAPABILITY_TYPE_PRESENCE_UCE_INT_ARRAY = "ims.key_capability_type_presence_uce_int_array"; + field public static final String KEY_CAPABILITY_TYPE_SMS_INT_ARRAY = "ims.key_capability_type_sms_int_array"; + field public static final String KEY_CAPABILITY_TYPE_UT_INT_ARRAY = "ims.key_capability_type_ut_int_array"; + field public static final String KEY_CAPABILITY_TYPE_VIDEO_INT_ARRAY = "ims.key_capability_type_video_int_array"; + field public static final String KEY_CAPABILITY_TYPE_VOICE_INT_ARRAY = "ims.key_capability_type_voice_int_array"; field public static final String KEY_ENABLE_PRESENCE_CAPABILITY_EXCHANGE_BOOL = "ims.enable_presence_capability_exchange_bool"; field public static final String KEY_ENABLE_PRESENCE_GROUP_SUBSCRIBE_BOOL = "ims.enable_presence_group_subscribe_bool"; field public static final String KEY_ENABLE_PRESENCE_PUBLISH_BOOL = "ims.enable_presence_publish_bool"; @@ -41969,11 +42184,13 @@ package android.telephony { field public static final String KEY_IPV4_SIP_MTU_SIZE_CELLULAR_INT = "ims.ipv4_sip_mtu_size_cellular_int"; field public static final String KEY_IPV6_SIP_MTU_SIZE_CELLULAR_INT = "ims.ipv6_sip_mtu_size_cellular_int"; field public static final String KEY_KEEP_PDN_UP_IN_NO_VOPS_BOOL = "ims.keep_pdn_up_in_no_vops_bool"; + field public static final String KEY_MMTEL_REQUIRES_PROVISIONING_BUNDLE = "ims.mmtel_requires_provisioning_bundle"; field public static final String KEY_NON_RCS_CAPABILITIES_CACHE_EXPIRATION_SEC_INT = "ims.non_rcs_capabilities_cache_expiration_sec_int"; field public static final String KEY_PHONE_CONTEXT_DOMAIN_NAME_STRING = "ims.phone_context_domain_name_string"; field public static final String KEY_PREFIX = "ims."; field public static final String KEY_RCS_BULK_CAPABILITY_EXCHANGE_BOOL = "ims.rcs_bulk_capability_exchange_bool"; field public static final String KEY_RCS_FEATURE_TAG_ALLOWED_STRING_ARRAY = "ims.rcs_feature_tag_allowed_string_array"; + field public static final String KEY_RCS_REQUIRES_PROVISIONING_BUNDLE = "ims.rcs_requires_provisioning_bundle"; field public static final String KEY_REGISTRATION_EVENT_PACKAGE_SUPPORTED_BOOL = "ims.registration_event_package_supported_bool"; field public static final String KEY_REGISTRATION_EXPIRY_TIMER_SEC_INT = "ims.registration_expiry_timer_sec_int"; field public static final String KEY_REGISTRATION_RETRY_BASE_TIMER_MILLIS_INT = "ims.registration_retry_base_timer_millis_int"; @@ -42215,7 +42432,6 @@ package android.telephony { field public static final String KEY_CHILD_SESSION_AES_CTR_KEY_SIZE_INT_ARRAY = "iwlan.child_session_aes_ctr_key_size_int_array"; field public static final String KEY_DIFFIE_HELLMAN_GROUPS_INT_ARRAY = "iwlan.diffie_hellman_groups_int_array"; field public static final String KEY_DPD_TIMER_SEC_INT = "iwlan.dpd_timer_sec_int"; - field public static final String KEY_ENABLE_SUPPORT_FOR_EAP_AKA_FAST_REAUTH_BOOL = "iwlan.enable_support_for_eap_aka_fast_reauth_bool"; field public static final String KEY_EPDG_ADDRESS_PRIORITY_INT_ARRAY = "iwlan.epdg_address_priority_int_array"; field public static final String KEY_EPDG_AUTHENTICATION_METHOD_INT = "iwlan.epdg_authentication_method_int"; field public static final String KEY_EPDG_PCO_ID_IPV4_INT = "iwlan.epdg_pco_id_ipv4_int"; @@ -42237,6 +42453,7 @@ package android.telephony { field public static final String KEY_SUPPORTED_IKE_SESSION_ENCRYPTION_ALGORITHMS_INT_ARRAY = "iwlan.supported_ike_session_encryption_algorithms_int_array"; field public static final String KEY_SUPPORTED_INTEGRITY_ALGORITHMS_INT_ARRAY = "iwlan.supported_integrity_algorithms_int_array"; field public static final String KEY_SUPPORTED_PRF_ALGORITHMS_INT_ARRAY = "iwlan.supported_prf_algorithms_int_array"; + field public static final String KEY_SUPPORTS_EAP_AKA_FAST_REAUTH_BOOL = "iwlan.supports_eap_aka_fast_reauth_bool"; } public abstract class CellIdentity implements android.os.Parcelable { @@ -44622,6 +44839,7 @@ package android.telephony.ims { public class ImsManager { method @NonNull public android.telephony.ims.ImsMmTelManager getImsMmTelManager(int); method @NonNull public android.telephony.ims.ImsRcsManager getImsRcsManager(int); + method @NonNull public android.telephony.ims.ProvisioningManager getProvisioningManager(int); field public static final String ACTION_WFC_IMS_REGISTRATION_ERROR = "android.telephony.ims.action.WFC_IMS_REGISTRATION_ERROR"; field public static final String EXTRA_WFC_REGISTRATION_FAILURE_MESSAGE = "android.telephony.ims.extra.WFC_REGISTRATION_FAILURE_MESSAGE"; field public static final String EXTRA_WFC_REGISTRATION_FAILURE_TITLE = "android.telephony.ims.extra.WFC_REGISTRATION_FAILURE_TITLE"; @@ -44872,6 +45090,23 @@ package android.telephony.ims { field public static final int REASON_UNKNOWN_TEMPORARY_ERROR = 1; // 0x1 } + public class ProvisioningManager { + method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) @WorkerThread public boolean getProvisioningStatusForCapability(int, int); + method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) @WorkerThread public boolean getRcsProvisioningStatusForCapability(int, int); + method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public boolean isProvisioningRequiredForCapability(int, int); + method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public boolean isRcsProvisioningRequiredForCapability(int, int); + method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public void registerFeatureProvisioningChangedCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.ProvisioningManager.FeatureProvisioningCallback) throws android.telephony.ims.ImsException; + method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public void setProvisioningStatusForCapability(int, int, boolean); + method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public void setRcsProvisioningStatusForCapability(int, int, boolean); + method public void unregisterFeatureProvisioningChangedCallback(@NonNull android.telephony.ims.ProvisioningManager.FeatureProvisioningCallback); + } + + public static class ProvisioningManager.FeatureProvisioningCallback { + ctor public ProvisioningManager.FeatureProvisioningCallback(); + method public void onFeatureProvisioningChanged(int, int, boolean); + method public void onRcsFeatureProvisioningChanged(int, int, boolean); + } + public class RcsUceAdapter { method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public boolean isUceSettingEnabled() throws android.telephony.ims.ImsException; } @@ -44912,6 +45147,27 @@ package android.telephony.ims.feature { field public static final int CAPABILITY_TYPE_VOICE = 1; // 0x1 } + public class RcsFeature { + } + + public static class RcsFeature.RcsImsCapabilities { + field public static final int CAPABILITY_TYPE_NONE = 0; // 0x0 + field public static final int CAPABILITY_TYPE_OPTIONS_UCE = 1; // 0x1 + field public static final int CAPABILITY_TYPE_PRESENCE_UCE = 2; // 0x2 + } + +} + +package android.telephony.ims.stub { + + public class ImsRegistrationImplBase { + field public static final int REGISTRATION_TECH_CROSS_SIM = 2; // 0x2 + field public static final int REGISTRATION_TECH_IWLAN = 1; // 0x1 + field public static final int REGISTRATION_TECH_LTE = 0; // 0x0 + field public static final int REGISTRATION_TECH_NONE = -1; // 0xffffffff + field public static final int REGISTRATION_TECH_NR = 3; // 0x3 + } + } package android.telephony.mbms { @@ -48473,7 +48729,7 @@ package android.view { field public static final int CLOCK_TICK = 4; // 0x4 field public static final int CONFIRM = 16; // 0x10 field public static final int CONTEXT_CLICK = 6; // 0x6 - field public static final int FLAG_IGNORE_GLOBAL_SETTING = 2; // 0x2 + field @Deprecated public static final int FLAG_IGNORE_GLOBAL_SETTING = 2; // 0x2 field public static final int FLAG_IGNORE_VIEW_SETTING = 1; // 0x1 field public static final int GESTURE_END = 13; // 0xd field public static final int GESTURE_START = 12; // 0xc @@ -48503,6 +48759,7 @@ package android.view { method public static int[] getDeviceIds(); method public int getId(); method public android.view.KeyCharacterMap getKeyCharacterMap(); + method public int getKeyCodeForKeyLocation(int); method public int getKeyboardType(); method @NonNull public android.hardware.lights.LightsManager getLightsManager(); method public android.view.InputDevice.MotionRange getMotionRange(int); @@ -49449,6 +49706,22 @@ package android.view { field public int toolType; } + public interface OnBackInvokedCallback { + method public default void onBackInvoked(); + } + + public abstract class OnBackInvokedDispatcher { + ctor public OnBackInvokedDispatcher(); + method public abstract void registerOnBackInvokedCallback(@NonNull android.view.OnBackInvokedCallback, int); + method public abstract void unregisterOnBackInvokedCallback(@NonNull android.view.OnBackInvokedCallback); + field public static final int PRIORITY_DEFAULT = 0; // 0x0 + field public static final int PRIORITY_OVERLAY = 1000000; // 0xf4240 + } + + public interface OnBackInvokedDispatcherOwner { + method @Nullable public android.view.OnBackInvokedDispatcher getOnBackInvokedDispatcher(); + } + public interface OnReceiveContentListener { method @Nullable public android.view.ContentInfo onReceiveContent(@NonNull android.view.View, @NonNull android.view.ContentInfo); } @@ -49872,7 +50145,7 @@ package android.view { field @NonNull public static final android.os.Parcelable.Creator<android.view.VerifiedMotionEvent> CREATOR; } - @UiThread public class View implements android.view.accessibility.AccessibilityEventSource android.graphics.drawable.Drawable.Callback android.view.KeyEvent.Callback { + @UiThread public class View implements android.view.accessibility.AccessibilityEventSource android.graphics.drawable.Drawable.Callback android.view.KeyEvent.Callback android.view.OnBackInvokedDispatcherOwner { ctor public View(android.content.Context); ctor public View(android.content.Context, @Nullable android.util.AttributeSet); ctor public View(android.content.Context, @Nullable android.util.AttributeSet, int); @@ -50076,6 +50349,7 @@ package android.view { method @IdRes public int getNextFocusLeftId(); method @IdRes public int getNextFocusRightId(); method @IdRes public int getNextFocusUpId(); + method @Nullable public android.view.OnBackInvokedDispatcher getOnBackInvokedDispatcher(); method public android.view.View.OnFocusChangeListener getOnFocusChangeListener(); method @ColorInt public int getOutlineAmbientShadowColor(); method public android.view.ViewOutlineProvider getOutlineProvider(); @@ -50093,6 +50367,7 @@ package android.view { method public float getPivotX(); method public float getPivotY(); method public android.view.PointerIcon getPointerIcon(); + method @NonNull public final java.util.List<android.graphics.Rect> getPreferKeepClearRects(); method @Nullable public String[] getReceiveContentMimeTypes(); method public android.content.res.Resources getResources(); method public final boolean getRevealOnFocusHint(); @@ -50210,6 +50485,7 @@ package android.view { method protected boolean isPaddingOffsetRequired(); method public boolean isPaddingRelative(); method public boolean isPivotSet(); + method public final boolean isPreferKeepClear(); method public boolean isPressed(); method public boolean isSaveEnabled(); method public boolean isSaveFromParentEnabled(); @@ -50452,6 +50728,8 @@ package android.view { method public void setPivotX(float); method public void setPivotY(float); method public void setPointerIcon(android.view.PointerIcon); + method public final void setPreferKeepClear(boolean); + method public final void setPreferKeepClearRects(@NonNull java.util.List<android.graphics.Rect>); method public void setPressed(boolean); method public void setRenderEffect(@Nullable android.graphics.RenderEffect); method public final void setRevealOnFocusHint(boolean); diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt index 4860a72ca67e..d22686c15e9b 100644 --- a/core/api/module-lib-current.txt +++ b/core/api/module-lib-current.txt @@ -9,7 +9,7 @@ package android { package android.app { - @UiContext public class Activity extends android.view.ContextThemeWrapper implements android.content.ComponentCallbacks2 android.view.KeyEvent.Callback android.view.LayoutInflater.Factory2 android.view.View.OnCreateContextMenuListener android.view.Window.Callback { + @UiContext public class Activity extends android.view.ContextThemeWrapper implements android.content.ComponentCallbacks2 android.view.KeyEvent.Callback android.view.LayoutInflater.Factory2 android.view.OnBackInvokedDispatcherOwner android.view.View.OnCreateContextMenuListener android.view.Window.Callback { method public final boolean addDumpable(@NonNull android.util.Dumpable); } @@ -73,7 +73,30 @@ package android.app.admin { package android.app.usage { public class NetworkStatsManager { + method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public void forceUpdate(); method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public void notifyNetworkStatus(@NonNull java.util.List<android.net.Network>, @NonNull java.util.List<android.net.NetworkStateSnapshot>, @Nullable String, @NonNull java.util.List<android.net.UnderlyingNetworkInfo>); + method @NonNull @WorkerThread public android.app.usage.NetworkStats queryDetailsForDevice(@NonNull android.net.NetworkTemplate, long, long); + method @NonNull @WorkerThread public android.app.usage.NetworkStats queryDetailsForUidTagState(@NonNull android.net.NetworkTemplate, long, long, int, int, int) throws java.lang.SecurityException; + method @NonNull @WorkerThread public android.app.usage.NetworkStats querySummary(@NonNull android.net.NetworkTemplate, long, long) throws java.lang.SecurityException; + method @NonNull @WorkerThread public android.app.usage.NetworkStats.Bucket querySummaryForDevice(@NonNull android.net.NetworkTemplate, long, long); + method @NonNull @WorkerThread public android.app.usage.NetworkStats queryTaggedSummary(@NonNull android.net.NetworkTemplate, long, long) throws java.lang.SecurityException; + method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public void setDefaultGlobalAlert(long); + method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public void setPollOnOpen(boolean); + method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public void setStatsProviderWarningAndLimitAsync(@NonNull String, long, long); + method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public void setUidForeground(int, boolean); + } + +} + +package android.bluetooth { + + public class BluetoothFrameworkInitializer { + method public static void registerServiceWrappers(); + method public static void setBluetoothServiceManager(@NonNull android.os.BluetoothServiceManager); + } + + public final class BluetoothPan implements android.bluetooth.BluetoothProfile { + method @Nullable @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED, android.Manifest.permission.TETHER_PRIVILEGED}) public android.net.TetheringManager.TetheredInterfaceRequest requestTetheredInterface(@NonNull java.util.concurrent.Executor, @NonNull android.net.TetheringManager.TetheredInterfaceCallback); } } @@ -131,10 +154,12 @@ package android.hardware.usb { field public static final int USB_DATA_TRANSFER_RATE_LOW_SPEED = 2; // 0x2 field public static final int USB_DATA_TRANSFER_RATE_UNKNOWN = -1; // 0xffffffff field public static final int USB_HAL_NOT_SUPPORTED = -1; // 0xffffffff + field public static final int USB_HAL_RETRY = -2; // 0xfffffffe field public static final int USB_HAL_V1_0 = 10; // 0xa field public static final int USB_HAL_V1_1 = 11; // 0xb field public static final int USB_HAL_V1_2 = 12; // 0xc field public static final int USB_HAL_V1_3 = 13; // 0xd + field public static final int USB_HAL_V2_0 = 20; // 0x14 } } @@ -248,11 +273,39 @@ package android.net { method public int getResourceId(); } + public class NetworkIdentity { + method public int getOemManaged(); + method public int getRatType(); + method @Nullable public String getSubscriberId(); + method public int getType(); + method @Nullable public String getWifiNetworkKey(); + method public boolean isDefaultNetwork(); + method public boolean isMetered(); + method public boolean isRoaming(); + } + + public static final class NetworkIdentity.Builder { + ctor public NetworkIdentity.Builder(); + method @NonNull public android.net.NetworkIdentity build(); + method @NonNull public android.net.NetworkIdentity.Builder clearRatType(); + method @NonNull public android.net.NetworkIdentity.Builder setDefaultNetwork(boolean); + method @NonNull public android.net.NetworkIdentity.Builder setMetered(boolean); + method @NonNull public android.net.NetworkIdentity.Builder setNetworkStateSnapshot(@NonNull android.net.NetworkStateSnapshot); + method @NonNull public android.net.NetworkIdentity.Builder setOemManaged(int); + method @NonNull public android.net.NetworkIdentity.Builder setRatType(int); + method @NonNull public android.net.NetworkIdentity.Builder setRoaming(boolean); + method @NonNull public android.net.NetworkIdentity.Builder setSubscriberId(@Nullable String); + method @NonNull public android.net.NetworkIdentity.Builder setType(int); + method @NonNull public android.net.NetworkIdentity.Builder setWifiNetworkKey(@Nullable String); + } + public class NetworkPolicyManager { method @RequiresPermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) public int getMultipathPreference(@NonNull android.net.Network); method @RequiresPermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) public int getRestrictBackgroundStatus(int); + method @Nullable @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public android.telephony.SubscriptionPlan getSubscriptionPlan(@NonNull android.net.NetworkTemplate); method @RequiresPermission(android.Manifest.permission.OBSERVE_NETWORK_POLICY) public boolean isUidNetworkingBlocked(int, boolean); method @RequiresPermission(android.Manifest.permission.OBSERVE_NETWORK_POLICY) public boolean isUidRestrictedOnMeteredNetworks(int); + method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public void notifyStatsProviderWarningOrLimitReached(); method @RequiresPermission(android.Manifest.permission.OBSERVE_NETWORK_POLICY) public void registerNetworkPolicyCallback(@Nullable java.util.concurrent.Executor, @NonNull android.net.NetworkPolicyManager.NetworkPolicyCallback); method @RequiresPermission(android.Manifest.permission.OBSERVE_NETWORK_POLICY) public void unregisterNetworkPolicyCallback(@NonNull android.net.NetworkPolicyManager.NetworkPolicyCallback); } @@ -273,6 +326,30 @@ package android.net { field @NonNull public static final android.os.Parcelable.Creator<android.net.NetworkStateSnapshot> CREATOR; } + public final class NetworkStatsHistory implements android.os.Parcelable { + method public int describeContents(); + method @NonNull public java.util.List<android.net.NetworkStatsHistory.Entry> getEntries(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.net.NetworkStatsHistory> CREATOR; + } + + public static final class NetworkStatsHistory.Builder { + ctor public NetworkStatsHistory.Builder(long, int); + method @NonNull public android.net.NetworkStatsHistory.Builder addEntry(@NonNull android.net.NetworkStatsHistory.Entry); + method @NonNull public android.net.NetworkStatsHistory build(); + } + + public static final class NetworkStatsHistory.Entry { + ctor public NetworkStatsHistory.Entry(long, long, long, long, long, long, long); + method public long getActiveTime(); + method public long getBucketStart(); + method public long getOperations(); + method public long getRxBytes(); + method public long getRxPackets(); + method public long getTxBytes(); + method public long getTxPackets(); + } + public final class NetworkTemplate implements android.os.Parcelable { method public int describeContents(); method public int getDefaultNetworkStatus(); @@ -328,6 +405,10 @@ package android.net { method public static void setHttpProxyConfiguration(@Nullable android.net.ProxyInfo); } + public class TrafficStats { + method public static void init(@NonNull android.content.Context); + } + public final class UnderlyingNetworkInfo implements android.os.Parcelable { ctor public UnderlyingNetworkInfo(int, @NonNull String, @NonNull java.util.List<java.lang.String>); method public int describeContents(); @@ -358,6 +439,21 @@ package android.os { method public final void markVintfStability(); } + public class BluetoothServiceManager { + method @NonNull public android.os.BluetoothServiceManager.ServiceRegisterer getBluetoothManagerServiceRegisterer(); + } + + public static class BluetoothServiceManager.ServiceNotFoundException extends java.lang.Exception { + ctor public BluetoothServiceManager.ServiceNotFoundException(@NonNull String); + } + + public static final class BluetoothServiceManager.ServiceRegisterer { + method @Nullable public android.os.IBinder get(); + method @NonNull public android.os.IBinder getOrThrow() throws android.os.BluetoothServiceManager.ServiceNotFoundException; + method public void register(@NonNull android.os.IBinder); + method @Nullable public android.os.IBinder tryGet(); + } + public class Build { method public static boolean isDebuggable(); } @@ -371,6 +467,10 @@ package android.os { } public class Process { + method public static final boolean isSupplemental(int); + method public static final int toAppUid(int); + method public static final int toSupplementalUid(int); + field public static final int NFC_UID = 1027; // 0x403 field public static final int VPN_UID = 1016; // 0x3f8 } @@ -402,6 +502,16 @@ package android.os { method @NonNull public java.util.List<android.content.ComponentName> getEnabledComponentOverrides(@NonNull String); } + public final class Trace { + method public static void asyncTraceBegin(long, @NonNull String, int); + method public static void asyncTraceEnd(long, @NonNull String, int); + method public static boolean isTagEnabled(long); + method public static void traceBegin(long, @NonNull String); + method public static void traceCounter(long, @NonNull String, int); + method public static void traceEnd(long); + field public static final long TRACE_TAG_NETWORK = 2097152L; // 0x200000L + } + } package android.os.storage { diff --git a/core/api/removed.txt b/core/api/removed.txt index 07639fbf5378..311b110f1997 100644 --- a/core/api/removed.txt +++ b/core/api/removed.txt @@ -513,7 +513,7 @@ package android.util { package android.view { - @UiThread public class View implements android.view.accessibility.AccessibilityEventSource android.graphics.drawable.Drawable.Callback android.view.KeyEvent.Callback { + @UiThread public class View implements android.view.accessibility.AccessibilityEventSource android.graphics.drawable.Drawable.Callback android.view.KeyEvent.Callback android.view.OnBackInvokedDispatcherOwner { method protected void initializeFadingEdge(android.content.res.TypedArray); method protected void initializeScrollbars(android.content.res.TypedArray); } diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 949dee3d6942..da4147b97550 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -2,12 +2,14 @@ package android { public static final class Manifest.permission { + field public static final String ACCESS_AMBIENT_CONTEXT_EVENT = "android.permission.ACCESS_AMBIENT_CONTEXT_EVENT"; field public static final String ACCESS_AMBIENT_LIGHT_STATS = "android.permission.ACCESS_AMBIENT_LIGHT_STATS"; field public static final String ACCESS_BROADCAST_RADIO = "android.permission.ACCESS_BROADCAST_RADIO"; field public static final String ACCESS_CACHE_FILESYSTEM = "android.permission.ACCESS_CACHE_FILESYSTEM"; field public static final String ACCESS_CONTEXT_HUB = "android.permission.ACCESS_CONTEXT_HUB"; field public static final String ACCESS_DRM_CERTIFICATES = "android.permission.ACCESS_DRM_CERTIFICATES"; field @Deprecated public static final String ACCESS_FM_RADIO = "android.permission.ACCESS_FM_RADIO"; + field public static final String ACCESS_FPS_COUNTER = "android.permission.ACCESS_FPS_COUNTER"; field public static final String ACCESS_INSTANT_APPS = "android.permission.ACCESS_INSTANT_APPS"; field public static final String ACCESS_LOCUS_ID_USAGE_STATS = "android.permission.ACCESS_LOCUS_ID_USAGE_STATS"; field public static final String ACCESS_MOCK_LOCATION = "android.permission.ACCESS_MOCK_LOCATION"; @@ -23,6 +25,7 @@ package android { field public static final String ACCESS_TV_DESCRAMBLER = "android.permission.ACCESS_TV_DESCRAMBLER"; field public static final String ACCESS_TV_SHARED_FILTER = "android.permission.ACCESS_TV_SHARED_FILTER"; field public static final String ACCESS_TV_TUNER = "android.permission.ACCESS_TV_TUNER"; + field public static final String ACCESS_ULTRASOUND = "android.permission.ACCESS_ULTRASOUND"; field public static final String ACCESS_VIBRATOR_STATE = "android.permission.ACCESS_VIBRATOR_STATE"; field public static final String ACTIVITY_EMBEDDING = "android.permission.ACTIVITY_EMBEDDING"; field public static final String ADJUST_RUNTIME_PERMISSIONS_POLICY = "android.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY"; @@ -36,6 +39,7 @@ package android { field public static final String BACKGROUND_CAMERA = "android.permission.BACKGROUND_CAMERA"; field public static final String BACKUP = "android.permission.BACKUP"; field public static final String BATTERY_PREDICTION = "android.permission.BATTERY_PREDICTION"; + field public static final String BIND_AMBIENT_CONTEXT_DETECTION_SERVICE = "android.permission.BIND_AMBIENT_CONTEXT_DETECTION_SERVICE"; field public static final String BIND_ATTENTION_SERVICE = "android.permission.BIND_ATTENTION_SERVICE"; field public static final String BIND_AUGMENTED_AUTOFILL_SERVICE = "android.permission.BIND_AUGMENTED_AUTOFILL_SERVICE"; field public static final String BIND_CALL_DIAGNOSTIC_SERVICE = "android.permission.BIND_CALL_DIAGNOSTIC_SERVICE"; @@ -138,6 +142,7 @@ package android { field public static final String INTERNAL_SYSTEM_WINDOW = "android.permission.INTERNAL_SYSTEM_WINDOW"; field public static final String INVOKE_CARRIER_SETUP = "android.permission.INVOKE_CARRIER_SETUP"; field public static final String KILL_UID = "android.permission.KILL_UID"; + field public static final String LAUNCH_DEVICE_MANAGER_SETUP = "android.permission.LAUNCH_DEVICE_MANAGER_SETUP"; field public static final String LOCAL_MAC_ADDRESS = "android.permission.LOCAL_MAC_ADDRESS"; field public static final String LOCK_DEVICE = "android.permission.LOCK_DEVICE"; field public static final String LOOP_RADIO = "android.permission.LOOP_RADIO"; @@ -157,6 +162,7 @@ package android { field public static final String MANAGE_DEBUGGING = "android.permission.MANAGE_DEBUGGING"; field public static final String MANAGE_DEVICE_ADMINS = "android.permission.MANAGE_DEVICE_ADMINS"; field public static final String MANAGE_FACTORY_RESET_PROTECTION = "android.permission.MANAGE_FACTORY_RESET_PROTECTION"; + field public static final String MANAGE_GAME_MODE = "android.permission.MANAGE_GAME_MODE"; field public static final String MANAGE_HOTWORD_DETECTION = "android.permission.MANAGE_HOTWORD_DETECTION"; field public static final String MANAGE_IPSEC_TUNNELS = "android.permission.MANAGE_IPSEC_TUNNELS"; field public static final String MANAGE_MUSIC_RECOGNITION = "android.permission.MANAGE_MUSIC_RECOGNITION"; @@ -296,6 +302,7 @@ package android { field public static final String SET_SYSTEM_AUDIO_CAPTION = "android.permission.SET_SYSTEM_AUDIO_CAPTION"; field public static final String SET_VOLUME_KEY_LONG_PRESS_LISTENER = "android.permission.SET_VOLUME_KEY_LONG_PRESS_LISTENER"; field public static final String SET_WALLPAPER_COMPONENT = "android.permission.SET_WALLPAPER_COMPONENT"; + field public static final String SET_WALLPAPER_DIM_AMOUNT = "android.permission.SET_WALLPAPER_DIM_AMOUNT"; field public static final String SHOW_KEYGUARD_MESSAGE = "android.permission.SHOW_KEYGUARD_MESSAGE"; field public static final String SHUTDOWN = "android.permission.SHUTDOWN"; field public static final String SIGNAL_REBOOT_READINESS = "android.permission.SIGNAL_REBOOT_READINESS"; @@ -364,6 +371,7 @@ package android { } public static final class R.bool { + field public static final int config_enableQrCodeScannerOnLockScreen; field public static final int config_sendPackageName = 17891328; // 0x1110000 field public static final int config_showDefaultAssistant = 17891329; // 0x1110001 field public static final int config_showDefaultEmergency = 17891330; // 0x1110002 @@ -391,6 +399,7 @@ package android { field public static final int config_customMediaKeyDispatcher = 17039404; // 0x104002c field public static final int config_customMediaSessionPolicyProvider = 17039405; // 0x104002d field public static final int config_defaultAssistant = 17039393; // 0x1040021 + field public static final int config_defaultAutomotiveNavigation; field public static final int config_defaultBrowser = 17039394; // 0x1040022 field public static final int config_defaultCallRedirection = 17039397; // 0x1040025 field public static final int config_defaultCallScreening = 17039398; // 0x1040026 @@ -443,7 +452,7 @@ package android.accounts { package android.app { - @UiContext public class Activity extends android.view.ContextThemeWrapper implements android.content.ComponentCallbacks2 android.view.KeyEvent.Callback android.view.LayoutInflater.Factory2 android.view.View.OnCreateContextMenuListener android.view.Window.Callback { + @UiContext public class Activity extends android.view.ContextThemeWrapper implements android.content.ComponentCallbacks2 android.view.KeyEvent.Callback android.view.LayoutInflater.Factory2 android.view.OnBackInvokedDispatcherOwner android.view.View.OnCreateContextMenuListener android.view.Window.Callback { method public void convertFromTranslucent(); method public boolean convertToTranslucent(android.app.Activity.TranslucentConversionListener, android.app.ActivityOptions); method @Deprecated public boolean isBackgroundVisibleBehind(); @@ -750,7 +759,17 @@ package android.app { } public final class GameManager { - method @RequiresPermission("android.permission.MANAGE_GAME_MODE") public void setGameMode(@NonNull String, int); + method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_GAME_MODE) public android.app.GameModeInfo getGameModeInfo(@NonNull String); + method @RequiresPermission(android.Manifest.permission.MANAGE_GAME_MODE) public void setGameMode(@NonNull String, int); + } + + public final class GameModeInfo implements android.os.Parcelable { + ctor public GameModeInfo(int, @NonNull int[]); + method public int describeContents(); + method public int getActiveGameMode(); + method @NonNull public int[] getAvailableGameModes(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.app.GameModeInfo> CREATOR; } public abstract class InstantAppResolverService extends android.app.Service { @@ -919,15 +938,21 @@ package android.app { method @RequiresPermission(android.Manifest.permission.READ_PROJECTION_STATE) public void addOnProjectionStateChangedListener(int, @NonNull java.util.concurrent.Executor, @NonNull android.app.UiModeManager.OnProjectionStateChangedListener); method @RequiresPermission(android.Manifest.permission.ENTER_CAR_MODE_PRIORITIZED) public void enableCarMode(@IntRange(from=0) int, int); method @RequiresPermission(android.Manifest.permission.READ_PROJECTION_STATE) public int getActiveProjectionTypes(); + method @RequiresPermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE) public int getNightModeCustomType(); method @NonNull @RequiresPermission(android.Manifest.permission.READ_PROJECTION_STATE) public java.util.Set<java.lang.String> getProjectingPackages(int); method @RequiresPermission(value=android.Manifest.permission.TOGGLE_AUTOMOTIVE_PROJECTION, conditional=true) public boolean releaseProjection(int); method @RequiresPermission(android.Manifest.permission.READ_PROJECTION_STATE) public void removeOnProjectionStateChangedListener(@NonNull android.app.UiModeManager.OnProjectionStateChangedListener); method @RequiresPermission(value=android.Manifest.permission.TOGGLE_AUTOMOTIVE_PROJECTION, conditional=true) public boolean requestProjection(int); + method @RequiresPermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE) public boolean setNightModeActivatedForCustomMode(int, boolean); + method @RequiresPermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE) public void setNightModeCustomType(int); field public static final String ACTION_ENTER_CAR_MODE_PRIORITIZED = "android.app.action.ENTER_CAR_MODE_PRIORITIZED"; field public static final String ACTION_EXIT_CAR_MODE_PRIORITIZED = "android.app.action.EXIT_CAR_MODE_PRIORITIZED"; field public static final int DEFAULT_PRIORITY = 0; // 0x0 field public static final String EXTRA_CALLING_PACKAGE = "android.app.extra.CALLING_PACKAGE"; field public static final String EXTRA_PRIORITY = "android.app.extra.PRIORITY"; + field public static final int MODE_NIGHT_CUSTOM_TYPE_BEDTIME = 1; // 0x1 + field public static final int MODE_NIGHT_CUSTOM_TYPE_SCHEDULE = 0; // 0x0 + field public static final int MODE_NIGHT_CUSTOM_TYPE_UNKNOWN = -1; // 0xffffffff field public static final int PROJECTION_TYPE_ALL = -1; // 0xffffffff field public static final int PROJECTION_TYPE_AUTOMOTIVE = 1; // 0x1 field public static final int PROJECTION_TYPE_NONE = 0; // 0x0 @@ -985,8 +1010,10 @@ package android.app { public class WallpaperManager { method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public void clearWallpaper(int, int); + method @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_DIM_AMOUNT) public float getWallpaperDimAmount(); method public void setDisplayOffset(android.os.IBinder, int, int); method @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT) public boolean setWallpaperComponent(android.content.ComponentName); + method @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_DIM_AMOUNT) public void setWallpaperDimAmount(@FloatRange(from=0.0f, to=1.0f) float); } } @@ -1026,6 +1053,8 @@ package android.app.admin { method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.QUERY_ADMIN_POLICY}) public java.util.List<java.lang.String> getPermittedInputMethodsForCurrentUser(); method @Nullable public android.content.ComponentName getProfileOwner() throws java.lang.IllegalArgumentException; method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS}) public String getProfileOwnerNameAsUser(int) throws java.lang.IllegalArgumentException; + method @NonNull public String getString(@NonNull String, @NonNull java.util.concurrent.Callable<java.lang.String>); + method @NonNull public String getString(@NonNull String, @NonNull java.util.concurrent.Callable<java.lang.String>, @NonNull java.lang.Object...); method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS}) public int getUserProvisioningState(); method public boolean isDeviceManaged(); method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isDeviceProvisioned(); @@ -1038,25 +1067,28 @@ package android.app.admin { method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public boolean packageHasActiveAdmins(String); method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void provisionFullyManagedDevice(@NonNull android.app.admin.FullyManagedDeviceProvisioningParams) throws android.app.admin.ProvisioningException; method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES) public void resetDrawables(@NonNull int[]); + method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES) public void resetStrings(@NonNull String[]); method @Deprecated @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_ADMINS) public boolean setActiveProfileOwner(@NonNull android.content.ComponentName, String) throws java.lang.IllegalArgumentException; method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void setDeviceProvisioningConfigApplied(); method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES) public void setDrawables(@NonNull java.util.Set<android.app.admin.DevicePolicyDrawableResource>); method @Deprecated @RequiresPermission(value=android.Manifest.permission.GRANT_PROFILE_OWNER_DEVICE_IDS_ACCESS, conditional=true) public void setProfileOwnerCanAccessDeviceIds(@NonNull android.content.ComponentName); method public void setSecondaryLockscreenEnabled(@NonNull android.content.ComponentName, boolean); + method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES) public void setStrings(@NonNull java.util.Set<android.app.admin.DevicePolicyStringResource>); method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void setUserProvisioningState(int, @NonNull android.os.UserHandle); field public static final String ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_ALLOWED = "android.account.DEVICE_OR_PROFILE_OWNER_ALLOWED"; field public static final String ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_DISALLOWED = "android.account.DEVICE_OR_PROFILE_OWNER_DISALLOWED"; field public static final String ACTION_BIND_SECONDARY_LOCKSCREEN_SERVICE = "android.app.action.BIND_SECONDARY_LOCKSCREEN_SERVICE"; + field @RequiresPermission(android.Manifest.permission.DISPATCH_PROVISIONING_MESSAGE) public static final String ACTION_ESTABLISH_NETWORK_CONNECTION = "android.app.action.ESTABLISH_NETWORK_CONNECTION"; field public static final String ACTION_PROVISION_FINALIZATION = "android.app.action.PROVISION_FINALIZATION"; field public static final String ACTION_PROVISION_FINANCED_DEVICE = "android.app.action.PROVISION_FINANCED_DEVICE"; field public static final String ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE = "android.app.action.PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE"; field @RequiresPermission(android.Manifest.permission.MANAGE_FACTORY_RESET_PROTECTION) public static final String ACTION_RESET_PROTECTION_POLICY_CHANGED = "android.app.action.RESET_PROTECTION_POLICY_CHANGED"; - field @RequiresPermission("android.permission.LAUNCH_DEVICE_MANAGER_SETUP") public static final String ACTION_ROLE_HOLDER_PROVISION_FINALIZATION = "android.app.action.ROLE_HOLDER_PROVISION_FINALIZATION"; - field @RequiresPermission("android.permission.LAUNCH_DEVICE_MANAGER_SETUP") public static final String ACTION_ROLE_HOLDER_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE = "android.app.action.ROLE_HOLDER_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE"; - field @RequiresPermission("android.permission.LAUNCH_DEVICE_MANAGER_SETUP") public static final String ACTION_ROLE_HOLDER_PROVISION_MANAGED_PROFILE = "android.app.action.ROLE_HOLDER_PROVISION_MANAGED_PROFILE"; + field @RequiresPermission(android.Manifest.permission.LAUNCH_DEVICE_MANAGER_SETUP) public static final String ACTION_ROLE_HOLDER_PROVISION_FINALIZATION = "android.app.action.ROLE_HOLDER_PROVISION_FINALIZATION"; + field @RequiresPermission(android.Manifest.permission.LAUNCH_DEVICE_MANAGER_SETUP) public static final String ACTION_ROLE_HOLDER_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE = "android.app.action.ROLE_HOLDER_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE"; + field @RequiresPermission(android.Manifest.permission.LAUNCH_DEVICE_MANAGER_SETUP) public static final String ACTION_ROLE_HOLDER_PROVISION_MANAGED_PROFILE = "android.app.action.ROLE_HOLDER_PROVISION_MANAGED_PROFILE"; field public static final String ACTION_SET_PROFILE_OWNER = "android.app.action.SET_PROFILE_OWNER"; field @Deprecated public static final String ACTION_STATE_USER_SETUP_COMPLETE = "android.app.action.STATE_USER_SETUP_COMPLETE"; - field @RequiresPermission("android.permission.LAUNCH_DEVICE_MANAGER_SETUP") public static final String ACTION_UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER = "android.app.action.UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER"; + field @RequiresPermission(android.Manifest.permission.LAUNCH_DEVICE_MANAGER_SETUP) public static final String ACTION_UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER = "android.app.action.UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER"; field public static final int CODE_ACCOUNTS_NOT_EMPTY = 6; // 0x6 field public static final int CODE_CANNOT_ADD_MANAGED_PROFILE = 11; // 0xb field public static final int CODE_DEVICE_ADMIN_NOT_SUPPORTED = 13; // 0xd @@ -1110,6 +1142,48 @@ package android.app.admin { field public static final int STATE_USER_UNMANAGED = 0; // 0x0 } + public static final class DevicePolicyResources.Strings { + field public static final String UNDEFINED = "UNDEFINED"; + } + + public static final class DevicePolicyResources.Strings.DocumentsUi { + field public static final String CANT_SAVE_TO_PERSONAL_MESSAGE = "DocumentsUi.CANT_SAVE_TO_PERSONAL_MESSAGE"; + field public static final String CANT_SAVE_TO_PERSONAL_TITLE = "DocumentsUi.CANT_SAVE_TO_PERSONAL_TITLE"; + field public static final String CANT_SAVE_TO_WORK_MESSAGE = "DocumentsUi.CANT_SAVE_TO_WORK_MESSAGE"; + field public static final String CANT_SAVE_TO_WORK_TITLE = "DocumentsUi.CANT_SAVE_TO_WORK_TITLE"; + field public static final String CANT_SELECT_PERSONAL_FILES_MESSAGE = "DocumentsUi.CANT_SELECT_PERSONAL_FILES_MESSAGE"; + field public static final String CANT_SELECT_PERSONAL_FILES_TITLE = "DocumentsUi.CANT_SELECT_PERSONAL_FILES_TITLE"; + field public static final String CANT_SELECT_WORK_FILES_MESSAGE = "DocumentsUi.CANT_SELECT_WORK_FILES_MESSAGE"; + field public static final String CANT_SELECT_WORK_FILES_TITLE = "DocumentsUi.CANT_SELECT_WORK_FILES_TITLE"; + field public static final String CROSS_PROFILE_NOT_ALLOWED_MESSAGE = "DocumentsUi.CROSS_PROFILE_NOT_ALLOWED_MESSAGE"; + field public static final String CROSS_PROFILE_NOT_ALLOWED_TITLE = "DocumentsUi.CROSS_PROFILE_NOT_ALLOWED_TITLE"; + field public static final String PERSONAL_TAB = "DocumentsUi.PERSONAL_TAB"; + field public static final String PREVIEW_WORK_FILE_ACCESSIBILITY = "DocumentsUi.PREVIEW_WORK_FILE_ACCESSIBILITY"; + field public static final String WORK_ACCESSIBILITY = "DocumentsUi.WORK_ACCESSIBILITY"; + field public static final String WORK_PROFILE_OFF_ENABLE_BUTTON = "DocumentsUi.WORK_PROFILE_OFF_ENABLE_BUTTON"; + field public static final String WORK_PROFILE_OFF_ERROR_TITLE = "DocumentsUi.WORK_PROFILE_OFF_ERROR_TITLE"; + field public static final String WORK_TAB = "DocumentsUi.WORK_TAB"; + } + + public static final class DevicePolicyResources.Strings.MediaProvider { + field public static final String BLOCKED_BY_ADMIN_TITLE = "MediaProvider.BLOCKED_BY_ADMIN_TITLE"; + field public static final String BLOCKED_FROM_PERSONAL_MESSAGE = "MediaProvider.BLOCKED_FROM_PERSONAL_MESSAGE"; + field public static final String BLOCKED_FROM_WORK_MESSAGE = "MediaProvider.BLOCKED_FROM_WORK_MESSAGE"; + field public static final String SWITCH_TO_PERSONAL_MESSAGE = "MediaProvider.SWITCH_TO_PERSONAL_MESSAGE"; + field public static final String SWITCH_TO_WORK_MESSAGE = "MediaProvider.SWITCH_TO_WORK_MESSAGE"; + field public static final String WORK_PROFILE_PAUSED_MESSAGE = "MediaProvider.WORK_PROFILE_PAUSED_MESSAGE"; + field public static final String WORK_PROFILE_PAUSED_TITLE = "MediaProvider.WORK_PROFILE_PAUSED_TITLE"; + } + + public final class DevicePolicyStringResource implements android.os.Parcelable { + ctor public DevicePolicyStringResource(@NonNull android.content.Context, @NonNull String, @StringRes int); + method public int describeContents(); + method public int getCallingPackageResourceId(); + method @NonNull public String getStringId(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.DevicePolicyStringResource> CREATOR; + } + public final class FullyManagedDeviceProvisioningParams implements android.os.Parcelable { method public boolean canDeviceOwnerGrantSensorsPermissions(); method public int describeContents(); @@ -1181,6 +1255,87 @@ package android.app.admin { } +package android.app.ambientcontext { + + public final class AmbientContextEvent implements android.os.Parcelable { + method public int describeContents(); + method public int getConfidenceLevel(); + method public int getDensityLevel(); + method @NonNull public java.time.Instant getEndTime(); + method public int getEventType(); + method @NonNull public java.time.Instant getStartTime(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.app.ambientcontext.AmbientContextEvent> CREATOR; + field public static final int EVENT_COUGH = 1; // 0x1 + field public static final int EVENT_SNORE = 2; // 0x2 + field public static final int EVENT_UNKNOWN = 0; // 0x0 + field public static final int LEVEL_HIGH = 5; // 0x5 + field public static final int LEVEL_LOW = 1; // 0x1 + field public static final int LEVEL_MEDIUM = 3; // 0x3 + field public static final int LEVEL_MEDIUM_HIGH = 4; // 0x4 + field public static final int LEVEL_MEDIUM_LOW = 2; // 0x2 + field public static final int LEVEL_UNKNOWN = 0; // 0x0 + } + + public static final class AmbientContextEvent.Builder { + ctor public AmbientContextEvent.Builder(); + method @NonNull public android.app.ambientcontext.AmbientContextEvent build(); + method @NonNull public android.app.ambientcontext.AmbientContextEvent.Builder setConfidenceLevel(int); + method @NonNull public android.app.ambientcontext.AmbientContextEvent.Builder setDensityLevel(int); + method @NonNull public android.app.ambientcontext.AmbientContextEvent.Builder setEndTime(@NonNull java.time.Instant); + method @NonNull public android.app.ambientcontext.AmbientContextEvent.Builder setEventType(int); + method @NonNull public android.app.ambientcontext.AmbientContextEvent.Builder setStartTime(@NonNull java.time.Instant); + } + + public final class AmbientContextEventRequest implements android.os.Parcelable { + method public int describeContents(); + method @NonNull public java.util.Set<java.lang.Integer> getEventTypes(); + method @NonNull public android.os.PersistableBundle getOptions(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.app.ambientcontext.AmbientContextEventRequest> CREATOR; + } + + public static final class AmbientContextEventRequest.Builder { + ctor public AmbientContextEventRequest.Builder(); + method @NonNull public android.app.ambientcontext.AmbientContextEventRequest.Builder addEventType(int); + method @NonNull public android.app.ambientcontext.AmbientContextEventRequest build(); + method @NonNull public android.app.ambientcontext.AmbientContextEventRequest.Builder setOptions(@NonNull android.os.PersistableBundle); + } + + public final class AmbientContextEventResponse implements android.os.Parcelable { + method public int describeContents(); + method @Nullable public android.app.PendingIntent getActionPendingIntent(); + method @NonNull public java.util.List<android.app.ambientcontext.AmbientContextEvent> getEvents(); + method @NonNull public String getPackageName(); + method public int getStatusCode(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.app.ambientcontext.AmbientContextEventResponse> CREATOR; + field public static final int STATUS_ACCESS_DENIED = 5; // 0x5 + field public static final int STATUS_MICROPHONE_DISABLED = 4; // 0x4 + field public static final int STATUS_NOT_SUPPORTED = 2; // 0x2 + field public static final int STATUS_SERVICE_UNAVAILABLE = 3; // 0x3 + field public static final int STATUS_SUCCESS = 1; // 0x1 + field public static final int STATUS_UNKNOWN = 0; // 0x0 + } + + public static final class AmbientContextEventResponse.Builder { + ctor public AmbientContextEventResponse.Builder(); + method @NonNull public android.app.ambientcontext.AmbientContextEventResponse.Builder addEvent(@NonNull android.app.ambientcontext.AmbientContextEvent); + method @NonNull public android.app.ambientcontext.AmbientContextEventResponse build(); + method @NonNull public android.app.ambientcontext.AmbientContextEventResponse.Builder setActionPendingIntent(@NonNull android.app.PendingIntent); + method @NonNull public android.app.ambientcontext.AmbientContextEventResponse.Builder setPackageName(@NonNull String); + method @NonNull public android.app.ambientcontext.AmbientContextEventResponse.Builder setStatusCode(int); + } + + public final class AmbientContextManager { + method @Nullable public static android.app.ambientcontext.AmbientContextEventResponse getResponseFromIntent(@NonNull android.content.Intent); + method @RequiresPermission(android.Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT) public void registerObserver(@NonNull android.app.ambientcontext.AmbientContextEventRequest, @NonNull android.app.PendingIntent); + method @RequiresPermission(android.Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT) public void unregisterObserver(); + field public static final String EXTRA_AMBIENT_CONTEXT_EVENT_RESPONSE = "android.app.ambientcontext.extra.AMBIENT_CONTEXT_EVENT_RESPONSE"; + } + +} + package android.app.assist { public class ActivityId { @@ -2030,6 +2185,8 @@ package android.app.usage { } public class NetworkStatsManager { + method @NonNull @RequiresPermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) public android.net.NetworkStats getMobileUidStats(); + method @NonNull @RequiresPermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) public android.net.NetworkStats getWifiUidStats(); method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_STATS_PROVIDER, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void registerNetworkStatsProvider(@NonNull String, @NonNull android.net.netstats.provider.NetworkStatsProvider); method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_STATS_PROVIDER, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void unregisterNetworkStatsProvider(@NonNull android.net.netstats.provider.NetworkStatsProvider); } @@ -2206,6 +2363,7 @@ package android.bluetooth { method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean isEncrypted(); method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public boolean isInSilenceMode(); method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean removeBond(); + method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public boolean setLowLatencyAudioAllowed(boolean); method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public boolean setMessageAccessPermission(int); method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public boolean setMetadata(int, @NonNull byte[]); method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public boolean setPhonebookAccessPermission(int); @@ -2258,6 +2416,15 @@ package android.bluetooth { method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.MODIFY_PHONE_STATE}) public boolean stopScoUsingVirtualVoiceCall(); } + public final class BluetoothHeadsetClient implements java.lang.AutoCloseable android.bluetooth.BluetoothProfile { + method @NonNull @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices(); + method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int getConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice); + method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int getConnectionState(@NonNull android.bluetooth.BluetoothDevice); + method @NonNull @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(@NonNull int[]); + method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int); + field @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.headsetprofile.action.CONNECTION_STATE_CHANGED"; + } + public final class BluetoothHearingAid implements android.bluetooth.BluetoothProfile { method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int getConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice); method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public long getHiSyncId(@NonNull android.bluetooth.BluetoothDevice); @@ -2276,6 +2443,10 @@ package android.bluetooth { field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.input.profile.action.CONNECTION_STATE_CHANGED"; } + public final class BluetoothLeAudio implements java.lang.AutoCloseable android.bluetooth.BluetoothProfile { + method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int getAudioLocation(@NonNull android.bluetooth.BluetoothDevice); + } + public final class BluetoothMap implements java.lang.AutoCloseable android.bluetooth.BluetoothProfile { method public void close(); method protected void finalize(); @@ -2285,15 +2456,21 @@ package android.bluetooth { field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.map.profile.action.CONNECTION_STATE_CHANGED"; } - public final class BluetoothMapClient implements android.bluetooth.BluetoothProfile { + public final class BluetoothMapClient implements java.lang.AutoCloseable android.bluetooth.BluetoothProfile { + method @NonNull @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices(); + method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int getConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice); + method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int getConnectionState(@NonNull android.bluetooth.BluetoothDevice); + method @NonNull @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(@NonNull int[]); method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.SEND_SMS}) public boolean sendMessage(@NonNull android.bluetooth.BluetoothDevice, @NonNull java.util.Collection<android.net.Uri>, @NonNull String, @Nullable android.app.PendingIntent, @Nullable android.app.PendingIntent); + method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int); + field @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.mapmce.profile.action.CONNECTION_STATE_CHANGED"; } public final class BluetoothPan implements android.bluetooth.BluetoothProfile { method @NonNull @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices(); method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int getConnectionState(@NonNull android.bluetooth.BluetoothDevice); method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean isTetheringOn(); - method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED, android.Manifest.permission.TETHER_PRIVILEGED}) public void setBluetoothTethering(boolean); + method @Deprecated @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED, android.Manifest.permission.TETHER_PRIVILEGED}) public void setBluetoothTethering(boolean); method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int); field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.pan.profile.action.CONNECTION_STATE_CHANGED"; field public static final String ACTION_TETHERING_STATE_CHANGED = "android.bluetooth.action.TETHERING_STATE_CHANGED"; @@ -2314,6 +2491,15 @@ package android.bluetooth { field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.pbap.profile.action.CONNECTION_STATE_CHANGED"; } + public final class BluetoothPbapClient implements java.lang.AutoCloseable android.bluetooth.BluetoothProfile { + method @NonNull @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices(); + method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int getConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice); + method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int getConnectionState(@NonNull android.bluetooth.BluetoothDevice); + method @NonNull @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(@NonNull int[]); + method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int); + field @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.pbapclient.profile.action.CONNECTION_STATE_CHANGED"; + } + public interface BluetoothProfile { field public static final int A2DP_SINK = 11; // 0xb field public static final int AVRCP_CONTROLLER = 12; // 0xc @@ -2355,6 +2541,7 @@ package android.bluetooth { field @NonNull public static final android.os.ParcelUuid COORDINATED_SET; field @NonNull public static final android.os.ParcelUuid DIP; field @NonNull public static final android.os.ParcelUuid GENERIC_MEDIA_CONTROL; + field @NonNull public static final android.os.ParcelUuid HAS; field @NonNull public static final android.os.ParcelUuid HEARING_AID; field @NonNull public static final android.os.ParcelUuid HFP; field @NonNull public static final android.os.ParcelUuid HFP_AG; @@ -2571,10 +2758,32 @@ package android.companion { package android.companion.virtual { public final class VirtualDeviceManager { + method @Nullable @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.companion.virtual.VirtualDeviceManager.VirtualDevice createVirtualDevice(int, @NonNull android.companion.virtual.VirtualDeviceParams); } public static class VirtualDeviceManager.VirtualDevice implements java.lang.AutoCloseable { method public void close(); + method @Nullable public android.hardware.display.VirtualDisplay createVirtualDisplay(int, int, int, @Nullable android.view.Surface, int, @Nullable android.os.Handler, @Nullable android.hardware.display.VirtualDisplay.Callback); + method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualKeyboard createVirtualKeyboard(@NonNull android.hardware.display.VirtualDisplay, @NonNull String, int, int); + method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualMouse createVirtualMouse(@NonNull android.hardware.display.VirtualDisplay, @NonNull String, int, int); + method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualTouchscreen createVirtualTouchscreen(@NonNull android.hardware.display.VirtualDisplay, @NonNull String, int, int); + } + + public final class VirtualDeviceParams implements android.os.Parcelable { + method public int describeContents(); + method public int getLockState(); + method @NonNull public java.util.Set<android.os.UserHandle> getUsersWithMatchingAccounts(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.companion.virtual.VirtualDeviceParams> CREATOR; + field public static final int LOCK_STATE_ALWAYS_LOCKED = 0; // 0x0 + field public static final int LOCK_STATE_ALWAYS_UNLOCKED = 1; // 0x1 + } + + public static final class VirtualDeviceParams.Builder { + ctor public VirtualDeviceParams.Builder(); + method @NonNull public android.companion.virtual.VirtualDeviceParams build(); + method @NonNull @RequiresPermission(value="android.permission.ADD_ALWAYS_UNLOCKED_DISPLAY", conditional=true) public android.companion.virtual.VirtualDeviceParams.Builder setLockState(int); + method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setUsersWithMatchingAccounts(@NonNull java.util.Set<android.os.UserHandle>); } } @@ -2632,6 +2841,7 @@ package android.content { method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public abstract void sendBroadcastAsUser(@RequiresPermission android.content.Intent, android.os.UserHandle, @Nullable String, @Nullable android.os.Bundle); method public abstract void sendOrderedBroadcast(@NonNull android.content.Intent, @Nullable String, @Nullable android.os.Bundle, @Nullable android.content.BroadcastReceiver, @Nullable android.os.Handler, int, @Nullable String, @Nullable android.os.Bundle); method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public void startActivityAsUser(@NonNull @RequiresPermission android.content.Intent, @NonNull android.os.UserHandle); + field public static final String AMBIENT_CONTEXT_SERVICE = "ambient_context"; field public static final String APP_HIBERNATION_SERVICE = "app_hibernation"; field public static final String APP_INTEGRITY_SERVICE = "app_integrity"; field public static final String APP_PREDICTION_SERVICE = "app_prediction"; @@ -3628,6 +3838,7 @@ package android.hardware.display { method @RequiresPermission(android.Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS) public void setBrightnessConfiguration(android.hardware.display.BrightnessConfiguration); method @RequiresPermission(android.Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS) public void setBrightnessConfigurationForDisplay(@NonNull android.hardware.display.BrightnessConfiguration, @NonNull String); method @Deprecated @RequiresPermission(android.Manifest.permission.CONTROL_DISPLAY_SATURATION) public void setSaturationLevel(float); + field public static final int VIRTUAL_DISPLAY_FLAG_TRUSTED = 1024; // 0x400 } } @@ -4063,6 +4274,7 @@ package android.hardware.input { public class VirtualMouse implements java.io.Closeable { method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void close(); + method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.graphics.PointF getCursorPosition(); method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void sendButtonEvent(@NonNull android.hardware.input.VirtualMouseButtonEvent); method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void sendRelativeEvent(@NonNull android.hardware.input.VirtualMouseRelativeEvent); method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void sendScrollEvent(@NonNull android.hardware.input.VirtualMouseScrollEvent); @@ -5022,8 +5234,27 @@ package android.hardware.usb { } public final class UsbPort { + method @CheckResult @RequiresPermission(android.Manifest.permission.MANAGE_USB) public int enableLimitPowerTransfer(boolean); + method @CheckResult @RequiresPermission(android.Manifest.permission.MANAGE_USB) public int enableUsbData(boolean); + method @CheckResult @RequiresPermission(android.Manifest.permission.MANAGE_USB) public int enableUsbDataWhileDocked(); method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_USB) public android.hardware.usb.UsbPortStatus getStatus(); method @RequiresPermission(android.Manifest.permission.MANAGE_USB) public void setRoles(int, int); + field public static final int ENABLE_LIMIT_POWER_TRANSFER_ERROR_INTERNAL = 1; // 0x1 + field public static final int ENABLE_LIMIT_POWER_TRANSFER_ERROR_NOT_SUPPORTED = 2; // 0x2 + field public static final int ENABLE_LIMIT_POWER_TRANSFER_ERROR_OTHER = 4; // 0x4 + field public static final int ENABLE_LIMIT_POWER_TRANSFER_ERROR_PORT_MISMATCH = 3; // 0x3 + field public static final int ENABLE_LIMIT_POWER_TRANSFER_SUCCESS = 0; // 0x0 + field public static final int ENABLE_USB_DATA_ERROR_INTERNAL = 1; // 0x1 + field public static final int ENABLE_USB_DATA_ERROR_NOT_SUPPORTED = 2; // 0x2 + field public static final int ENABLE_USB_DATA_ERROR_OTHER = 4; // 0x4 + field public static final int ENABLE_USB_DATA_ERROR_PORT_MISMATCH = 3; // 0x3 + field public static final int ENABLE_USB_DATA_SUCCESS = 0; // 0x0 + field public static final int ENABLE_USB_DATA_WHILE_DOCKED_ERROR_DATA_ENABLED = 4; // 0x4 + field public static final int ENABLE_USB_DATA_WHILE_DOCKED_ERROR_INTERNAL = 1; // 0x1 + field public static final int ENABLE_USB_DATA_WHILE_DOCKED_ERROR_NOT_SUPPORTED = 2; // 0x2 + field public static final int ENABLE_USB_DATA_WHILE_DOCKED_ERROR_OTHER = 5; // 0x5 + field public static final int ENABLE_USB_DATA_WHILE_DOCKED_ERROR_PORT_MISMATCH = 3; // 0x3 + field public static final int ENABLE_USB_DATA_WHILE_DOCKED_SUCCESS = 0; // 0x0 } public final class UsbPortStatus implements android.os.Parcelable { @@ -5031,8 +5262,11 @@ package android.hardware.usb { method public int getCurrentDataRole(); method public int getCurrentMode(); method public int getCurrentPowerRole(); + method public int getPowerBrickStatus(); method public int getSupportedRoleCombinations(); + method @Nullable public int[] getUsbDataStatus(); method public boolean isConnected(); + method public boolean isPowerTransferLimited(); method public boolean isRoleCombinationSupported(int, int); method public void writeToParcel(android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.hardware.usb.UsbPortStatus> CREATOR; @@ -5044,9 +5278,19 @@ package android.hardware.usb { field public static final int MODE_DFP = 2; // 0x2 field public static final int MODE_NONE = 0; // 0x0 field public static final int MODE_UFP = 1; // 0x1 + field public static final int POWER_BRICK_STATUS_CONNECTED = 1; // 0x1 + field public static final int POWER_BRICK_STATUS_DISCONNECTED = 2; // 0x2 + field public static final int POWER_BRICK_STATUS_UNKNOWN = 0; // 0x0 field public static final int POWER_ROLE_NONE = 0; // 0x0 field public static final int POWER_ROLE_SINK = 2; // 0x2 field public static final int POWER_ROLE_SOURCE = 1; // 0x1 + field public static final int USB_DATA_STATUS_DISABLED_CONTAMINANT = 3; // 0x3 + field public static final int USB_DATA_STATUS_DISABLED_DEBUG = 6; // 0x6 + field public static final int USB_DATA_STATUS_DISABLED_DOCK = 4; // 0x4 + field public static final int USB_DATA_STATUS_DISABLED_FORCE = 5; // 0x5 + field public static final int USB_DATA_STATUS_DISABLED_OVERHEAT = 2; // 0x2 + field public static final int USB_DATA_STATUS_ENABLED = 1; // 0x1 + field public static final int USB_DATA_STATUS_UNKNOWN = 0; // 0x0 } } @@ -5663,6 +5907,7 @@ package android.media { method public int getCapturePreset(); method public int getSystemUsage(); method public static boolean isSystemUsage(int); + field @RequiresPermission(android.Manifest.permission.ACCESS_ULTRASOUND) public static final int CONTENT_TYPE_ULTRASOUND = 1997; // 0x7cd field public static final int FLAG_BEACON = 8; // 0x8 field public static final int FLAG_BYPASS_INTERRUPTION_POLICY = 64; // 0x40 field public static final int FLAG_BYPASS_MUTE = 128; // 0x80 @@ -5679,6 +5924,7 @@ package android.media { method public android.media.AudioAttributes.Builder setCapturePreset(int); method @NonNull @RequiresPermission(android.Manifest.permission.CAPTURE_AUDIO_HOTWORD) public android.media.AudioAttributes.Builder setHotwordModeEnabled(boolean); method public android.media.AudioAttributes.Builder setInternalCapturePreset(int); + method @NonNull public android.media.AudioAttributes.Builder setInternalContentType(int); method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public android.media.AudioAttributes.Builder setSystemUsage(int); } @@ -5894,6 +6140,7 @@ package android.media { field @RequiresPermission(android.Manifest.permission.CAPTURE_AUDIO_OUTPUT) public static final int ECHO_REFERENCE = 1997; // 0x7cd field @RequiresPermission(android.Manifest.permission.CAPTURE_AUDIO_HOTWORD) public static final int HOTWORD = 1999; // 0x7cf field @RequiresPermission(android.Manifest.permission.CAPTURE_AUDIO_OUTPUT) public static final int RADIO_TUNER = 1998; // 0x7ce + field @RequiresPermission(android.Manifest.permission.ACCESS_ULTRASOUND) public static final int ULTRASOUND = 2000; // 0x7d0 } public final class MediaRouter2 { @@ -6559,6 +6806,7 @@ package android.media.tv.tuner { method @Nullable public android.media.tv.tuner.Lnb openLnbByName(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.media.tv.tuner.LnbCallback); method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_TV_SHARED_FILTER) public static android.media.tv.tuner.filter.SharedFilter openSharedFilter(@NonNull android.content.Context, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.media.tv.tuner.filter.SharedFilterCallback); method @Nullable public android.media.tv.tuner.filter.TimeFilter openTimeFilter(); + method public int removeOutputPid(@IntRange(from=0) int); method public int scan(@NonNull android.media.tv.tuner.frontend.FrontendSettings, int, @NonNull java.util.concurrent.Executor, @NonNull android.media.tv.tuner.frontend.ScanCallback); method public int setLnaEnabled(boolean); method public int setMaxNumberOfFrontends(int, @IntRange(from=0) int); @@ -6999,7 +7247,7 @@ package android.media.tv.tuner.filter { } public abstract class SectionSettings extends android.media.tv.tuner.filter.Settings { - method public int getBitWidthOfLengthField(); + method public int getLengthFieldBitWidth(); method public boolean isCrcEnabled(); method public boolean isRaw(); method public boolean isRepeat(); @@ -7699,6 +7947,7 @@ package android.media.tv.tuner.frontend { public class FrontendStatus { method public int getAgc(); + method @NonNull public android.media.tv.tuner.frontend.Atsc3PlpInfo[] getAllAtsc3PlpInfo(); method @NonNull public android.media.tv.tuner.frontend.FrontendStatus.Atsc3PlpTuningInfo[] getAtsc3PlpTuningInfo(); method public int getBandwidth(); method public int getBer(); @@ -7741,6 +7990,7 @@ package android.media.tv.tuner.frontend { method public boolean isRfLocked(); method public boolean isShortFramesEnabled(); field public static final int FRONTEND_STATUS_TYPE_AGC = 14; // 0xe + field public static final int FRONTEND_STATUS_TYPE_ATSC3_ALL_PLP_INFO = 41; // 0x29 field public static final int FRONTEND_STATUS_TYPE_ATSC3_PLP_INFO = 21; // 0x15 field public static final int FRONTEND_STATUS_TYPE_BANDWIDTH = 25; // 0x19 field public static final int FRONTEND_STATUS_TYPE_BER = 2; // 0x2 @@ -8149,11 +8399,12 @@ package android.net { field public static final String PERMISSION_MAINLINE_NETWORK_STACK = "android.permission.MAINLINE_NETWORK_STACK"; } - public final class NetworkStats implements android.os.Parcelable { + public final class NetworkStats implements java.lang.Iterable<android.net.NetworkStats.Entry> android.os.Parcelable { ctor public NetworkStats(long, int); method @NonNull public android.net.NetworkStats add(@NonNull android.net.NetworkStats); method @NonNull public android.net.NetworkStats addEntry(@NonNull android.net.NetworkStats.Entry); method public int describeContents(); + method @NonNull public java.util.Iterator<android.net.NetworkStats.Entry> iterator(); method @NonNull public android.net.NetworkStats subtract(@NonNull android.net.NetworkStats); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.net.NetworkStats> CREATOR; @@ -8631,6 +8882,7 @@ package android.net.wifi.nl80211 { method @Nullable public android.net.wifi.nl80211.DeviceWiphyCapabilities getDeviceWiphyCapabilities(@NonNull String); method @NonNull public java.util.List<android.net.wifi.nl80211.NativeScanResult> getScanResults(@NonNull String, int); method @Nullable public android.net.wifi.nl80211.WifiNl80211Manager.TxPacketCounters getTxPacketCounters(@NonNull String); + method public boolean notifyCountryCodeChanged(); method @Nullable public static android.net.wifi.nl80211.WifiNl80211Manager.OemSecurityType parseOemSecurityTypeElement(int, int, @NonNull byte[]); method @Deprecated public boolean registerApCallback(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.nl80211.WifiNl80211Manager.SoftApCallback); method public boolean registerCountryCodeChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.nl80211.WifiNl80211Manager.CountryCodeChangedListener); @@ -9628,12 +9880,17 @@ package android.permission { public final class PermissionControllerManager { method @RequiresPermission(anyOf={android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS, android.Manifest.permission.RESTORE_RUNTIME_PERMISSIONS}) public void applyStagedRuntimePermissionBackup(@NonNull String, @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>); + method @RequiresPermission(android.Manifest.permission.MANAGE_APP_HIBERNATION) public void getHibernationEligibility(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer); method @RequiresPermission(android.Manifest.permission.GET_RUNTIME_PERMISSIONS) public void getRuntimePermissionBackup(@NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<byte[]>); method public void getUnusedAppCount(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer); method @RequiresPermission(android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS) public void revokeRuntimePermissions(@NonNull java.util.Map<java.lang.String,java.util.List<java.lang.String>>, boolean, int, @NonNull java.util.concurrent.Executor, @NonNull android.permission.PermissionControllerManager.OnRevokeRuntimePermissionsCallback); method @RequiresPermission(anyOf={android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS, android.Manifest.permission.RESTORE_RUNTIME_PERMISSIONS}) public void stageAndApplyRuntimePermissionsBackup(@NonNull byte[], @NonNull android.os.UserHandle); field public static final int COUNT_ONLY_WHEN_GRANTED = 1; // 0x1 field public static final int COUNT_WHEN_SYSTEM = 2; // 0x2 + field public static final int HIBERNATION_ELIGIBILITY_ELIGIBLE = 0; // 0x0 + field public static final int HIBERNATION_ELIGIBILITY_EXEMPT_BY_SYSTEM = 1; // 0x1 + field public static final int HIBERNATION_ELIGIBILITY_EXEMPT_BY_USER = 2; // 0x2 + field public static final int HIBERNATION_ELIGIBILITY_UNKNOWN = -1; // 0xffffffff field public static final int REASON_INSTALLER_POLICY_VIOLATION = 2; // 0x2 field public static final int REASON_MALWARE = 1; // 0x1 } @@ -9651,6 +9908,7 @@ package android.permission { method @BinderThread public abstract void onCountPermissionApps(@NonNull java.util.List<java.lang.String>, int, @NonNull java.util.function.IntConsumer); method @BinderThread public abstract void onGetAppPermissions(@NonNull String, @NonNull java.util.function.Consumer<java.util.List<android.permission.RuntimePermissionPresentationInfo>>); method @BinderThread public void onGetGroupOfPlatformPermission(@NonNull String, @NonNull java.util.function.Consumer<java.lang.String>); + method @RequiresPermission(android.Manifest.permission.MANAGE_APP_HIBERNATION) public void onGetHibernationEligibility(@NonNull String, @NonNull java.util.function.IntConsumer); method @BinderThread public abstract void onGetPermissionUsages(boolean, long, @NonNull java.util.function.Consumer<java.util.List<android.permission.RuntimePermissionUsageInfo>>); method @BinderThread public void onGetPlatformPermissionsForGroup(@NonNull String, @NonNull java.util.function.Consumer<java.util.List<java.lang.String>>); method @BinderThread public abstract void onGetRuntimePermissionsBackup(@NonNull android.os.UserHandle, @NonNull java.io.OutputStream, @NonNull Runnable); @@ -9659,9 +9917,9 @@ package android.permission { method @BinderThread public void onOneTimePermissionSessionTimeout(@NonNull String); method @Deprecated @BinderThread public void onRestoreDelayedRuntimePermissionsBackup(@NonNull String, @NonNull android.os.UserHandle, @NonNull java.util.function.Consumer<java.lang.Boolean>); method @Deprecated @BinderThread public void onRestoreRuntimePermissionsBackup(@NonNull android.os.UserHandle, @NonNull java.io.InputStream, @NonNull Runnable); + method @BinderThread public void onRevokeOwnPermissionsOnKill(@NonNull String, @NonNull java.util.List<java.lang.String>, @NonNull Runnable); method @BinderThread public abstract void onRevokeRuntimePermission(@NonNull String, @NonNull String, @NonNull Runnable); method @BinderThread public abstract void onRevokeRuntimePermissions(@NonNull java.util.Map<java.lang.String,java.util.List<java.lang.String>>, boolean, int, @NonNull String, @NonNull java.util.function.Consumer<java.util.Map<java.lang.String,java.util.List<java.lang.String>>>); - method @BinderThread public void onSelfRevokePermissions(@NonNull String, @NonNull java.util.List<java.lang.String>, @NonNull Runnable); method @Deprecated @BinderThread public abstract void onSetRuntimePermissionGrantStateByDeviceAdmin(@NonNull String, @NonNull String, @NonNull String, int, @NonNull java.util.function.Consumer<java.lang.Boolean>); method @BinderThread public void onSetRuntimePermissionGrantStateByDeviceAdmin(@NonNull String, @NonNull android.permission.AdminPermissionControlParams, @NonNull java.util.function.Consumer<java.lang.Boolean>); method @BinderThread public void onStageAndApplyRuntimePermissionsBackup(@NonNull android.os.UserHandle, @NonNull java.io.InputStream, @NonNull Runnable); @@ -9869,6 +10127,7 @@ package android.provider { method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static boolean setProperty(@NonNull String, @NonNull String, @Nullable String, boolean); field public static final String NAMESPACE_ACTIVITY_MANAGER = "activity_manager"; field public static final String NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT = "activity_manager_native_boot"; + field public static final String NAMESPACE_AMBIENT_CONTEXT_MANAGER_SERVICE = "ambient_context_manager_service"; field public static final String NAMESPACE_APPSEARCH = "appsearch"; field public static final String NAMESPACE_APP_COMPAT = "app_compat"; field public static final String NAMESPACE_APP_HIBERNATION = "app_hibernation"; @@ -9911,6 +10170,7 @@ package android.provider { field public static final String NAMESPACE_STATSD_NATIVE_BOOT = "statsd_native_boot"; field @Deprecated public static final String NAMESPACE_STORAGE = "storage"; field public static final String NAMESPACE_STORAGE_NATIVE_BOOT = "storage_native_boot"; + field public static final String NAMESPACE_SUPPLEMENTAL_API = "supplemental_api"; field public static final String NAMESPACE_SURFACE_FLINGER_NATIVE_BOOT = "surface_flinger_native_boot"; field public static final String NAMESPACE_SWCODEC_NATIVE = "swcodec_native"; field public static final String NAMESPACE_SYSTEMUI = "systemui"; @@ -10139,6 +10399,7 @@ package android.provider { field public static final String AUTO_REVOKE_DISABLED = "auto_revoke_disabled"; field public static final String COMPLETED_CATEGORY_PREFIX = "suggested.completed_category."; field public static final String DOZE_ALWAYS_ON = "doze_always_on"; + field public static final String FAST_PAIR_SCAN_ENABLED = "fast_pair_scan_enabled"; field public static final String HUSH_GESTURE_USED = "hush_gesture_used"; field public static final String INSTANT_APPS_ENABLED = "instant_apps_enabled"; field public static final String LAST_SETUP_SHOWN = "last_setup_shown"; @@ -10439,6 +10700,18 @@ package android.security.keystore.recovery { } +package android.service.ambientcontext { + + public abstract class AmbientContextDetectionService extends android.app.Service { + ctor public AmbientContextDetectionService(); + method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent); + method public abstract void onStartDetection(@NonNull android.app.ambientcontext.AmbientContextEventRequest, @NonNull String, @NonNull java.util.function.Consumer<android.app.ambientcontext.AmbientContextEventResponse>); + method public abstract void onStopDetection(@NonNull String); + field public static final String SERVICE_INTERFACE = "android.service.ambientcontext.AmbientContextDetectionService"; + } + +} + package android.service.appprediction { public abstract class AppPredictionService extends android.app.Service { @@ -10890,6 +11163,15 @@ package android.service.games { ctor public GameSession(); method public void onCreate(); method public void onDestroy(); + method public void onGameTaskFocusChanged(boolean); + method public void setTaskOverlayView(@NonNull android.view.View, @NonNull android.view.ViewGroup.LayoutParams); + method public void takeScreenshot(@NonNull java.util.concurrent.Executor, @NonNull android.service.games.GameSession.ScreenshotCallback); + } + + public static interface GameSession.ScreenshotCallback { + method public void onFailure(int); + method public void onSuccess(@NonNull android.graphics.Bitmap); + field public static final int ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR = 0; // 0x0 } public abstract class GameSessionService extends android.app.Service { @@ -11030,6 +11312,7 @@ package android.service.persistentdata { method @android.service.persistentdata.PersistentDataBlockManager.FlashLockState @RequiresPermission(anyOf={android.Manifest.permission.READ_OEM_UNLOCK_STATE, "android.permission.OEM_UNLOCK_STATE"}) public int getFlashLockState(); method public long getMaximumDataBlockSize(); method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_OEM_UNLOCK_STATE, "android.permission.OEM_UNLOCK_STATE"}) public boolean getOemUnlockEnabled(); + method @NonNull public String getPersistentDataPackageName(); method public byte[] read(); method @Deprecated @RequiresPermission("android.permission.OEM_UNLOCK_STATE") public void setOemUnlockEnabled(boolean); method @RequiresPermission("android.permission.OEM_UNLOCK_STATE") public void wipe(); @@ -12987,6 +13270,7 @@ package android.telephony { field public static final int ALLOWED_NETWORK_TYPES_REASON_USER = 0; // 0x0 field public static final int CALL_WAITING_STATUS_DISABLED = 2; // 0x2 field public static final int CALL_WAITING_STATUS_ENABLED = 1; // 0x1 + field public static final int CALL_WAITING_STATUS_FDN_CHECK_FAILURE = 5; // 0x5 field public static final int CALL_WAITING_STATUS_NOT_SUPPORTED = 4; // 0x4 field public static final int CALL_WAITING_STATUS_UNKNOWN_ERROR = 3; // 0x3 field public static final String CAPABILITY_NR_DUAL_CONNECTIVITY_CONFIGURATION_AVAILABLE = "CAPABILITY_NR_DUAL_CONNECTIVITY_CONFIGURATION_AVAILABLE"; @@ -14312,18 +14596,16 @@ package android.telephony.ims { public class ProvisioningManager { method @NonNull public static android.telephony.ims.ProvisioningManager createForSubscriptionId(int); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) @WorkerThread public int getProvisioningIntValue(int); - method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) @WorkerThread public boolean getProvisioningStatusForCapability(int, int); method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) @WorkerThread public String getProvisioningStringValue(int); - method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) @WorkerThread public boolean getRcsProvisioningStatusForCapability(int); + method @Deprecated @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) @WorkerThread public boolean getRcsProvisioningStatusForCapability(int); method @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.PERFORM_IMS_SINGLE_REGISTRATION}) public boolean isRcsVolteSingleRegistrationCapable() throws android.telephony.ims.ImsException; method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void notifyRcsAutoConfigurationReceived(@NonNull byte[], boolean); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerProvisioningChangedCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.ProvisioningManager.Callback) throws android.telephony.ims.ImsException; method @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.PERFORM_IMS_SINGLE_REGISTRATION}) public void registerRcsProvisioningCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.ProvisioningManager.RcsProvisioningCallback) throws android.telephony.ims.ImsException; method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public int setProvisioningIntValue(int, int); - method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public void setProvisioningStatusForCapability(int, int, boolean); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public int setProvisioningStringValue(int, @NonNull String); method @RequiresPermission(android.Manifest.permission.PERFORM_IMS_SINGLE_REGISTRATION) public void setRcsClientConfiguration(@NonNull android.telephony.ims.RcsClientConfiguration) throws android.telephony.ims.ImsException; - method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public void setRcsProvisioningStatusForCapability(int, boolean); + method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public void setRcsProvisioningStatusForCapability(int, boolean); method @RequiresPermission(android.Manifest.permission.PERFORM_IMS_SINGLE_REGISTRATION) public void triggerRcsReconfiguration(); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void unregisterProvisioningChangedCallback(@NonNull android.telephony.ims.ProvisioningManager.Callback); method @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.PERFORM_IMS_SINGLE_REGISTRATION}) public void unregisterRcsProvisioningCallback(@NonNull android.telephony.ims.ProvisioningManager.RcsProvisioningCallback); @@ -14756,9 +15038,6 @@ package android.telephony.ims.feature { method public void addCapabilities(int); method public boolean isCapable(int); method public void removeCapabilities(int); - field public static final int CAPABILITY_TYPE_NONE = 0; // 0x0 - field public static final int CAPABILITY_TYPE_OPTIONS_UCE = 1; // 0x1 - field public static final int CAPABILITY_TYPE_PRESENCE_UCE = 2; // 0x2 } } @@ -14908,11 +15187,6 @@ package android.telephony.ims.stub { method public void triggerFullNetworkRegistration(@IntRange(from=100, to=699) int, @Nullable String); method public void triggerSipDelegateDeregistration(); method public void updateSipDelegateRegistration(); - field public static final int REGISTRATION_TECH_CROSS_SIM = 2; // 0x2 - field public static final int REGISTRATION_TECH_IWLAN = 1; // 0x1 - field public static final int REGISTRATION_TECH_LTE = 0; // 0x0 - field public static final int REGISTRATION_TECH_NONE = -1; // 0xffffffff - field public static final int REGISTRATION_TECH_NR = 3; // 0x3 } public class ImsSmsImplBase { @@ -15190,6 +15464,8 @@ package android.view { public interface WindowManager extends android.view.ViewManager { method @RequiresPermission(android.Manifest.permission.RESTRICTED_VR_ACCESS) public android.graphics.Region getCurrentImeTouchRegion(); + method public default void registerTaskFpsCallback(@IntRange(from=0) int, @NonNull android.window.TaskFpsCallback); + method public default void unregisterTaskFpsCallback(@NonNull android.window.TaskFpsCallback); } public static class WindowManager.LayoutParams extends android.view.ViewGroup.LayoutParams implements android.os.Parcelable { @@ -15777,3 +16053,15 @@ package android.webkit { } +package android.window { + + public final class TaskFpsCallback { + ctor public TaskFpsCallback(@NonNull java.util.concurrent.Executor, @NonNull android.window.TaskFpsCallback.OnFpsCallbackListener); + } + + public static interface TaskFpsCallback.OnFpsCallbackListener { + method public void onFpsReported(float); + } + +} + diff --git a/core/api/system-lint-baseline.txt b/core/api/system-lint-baseline.txt index 716f43e1d68a..3d756bafa292 100644 --- a/core/api/system-lint-baseline.txt +++ b/core/api/system-lint-baseline.txt @@ -91,6 +91,10 @@ MissingNullability: android.telephony.mbms.DownloadRequest.Builder#setServiceId( +NoSettingsProvider: android.provider.Settings.Secure#FAST_PAIR_SCAN_ENABLED: + New setting keys are not allowed (Field: FAST_PAIR_SCAN_ENABLED); use getters/setters in relevant manager class + + OnNameExpected: android.service.smartspace.SmartspaceService#notifySmartspaceEvent(android.app.smartspace.SmartspaceSessionId, android.app.smartspace.SmartspaceTargetEvent): Methods implemented by developers should follow the on<Something> style, was `notifySmartspaceEvent` @@ -103,6 +107,16 @@ ProtectedMember: android.service.notification.NotificationAssistantService#attac +RethrowRemoteException: android.app.WallpaperManager#getWallpaperDimAmount(): + Methods calling system APIs should rethrow `RemoteException` as `RuntimeException` (but do not list it in the throws clause) +RethrowRemoteException: android.app.WallpaperManager#getWallpaperDimmingAmount(): + Methods calling system APIs should rethrow `RemoteException` as `RuntimeException` (but do not list it in the throws clause) +RethrowRemoteException: android.app.WallpaperManager#setWallpaperDimAmount(float): + Methods calling system APIs should rethrow `RemoteException` as `RuntimeException` (but do not list it in the throws clause) +RethrowRemoteException: android.app.WallpaperManager#setWallpaperDimmingAmount(float): + Methods calling system APIs should rethrow `RemoteException` as `RuntimeException` (but do not list it in the throws clause) + + SamShouldBeLast: android.accounts.AccountManager#addAccount(String, String, String[], android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler): SamShouldBeLast: android.accounts.AccountManager#addOnAccountsUpdatedListener(android.accounts.OnAccountsUpdateListener, android.os.Handler, boolean): diff --git a/core/api/test-current.txt b/core/api/test-current.txt index d6a9f7faea65..ff26ae8d09fc 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -38,6 +38,7 @@ package android { field public static final String RESET_APP_ERRORS = "android.permission.RESET_APP_ERRORS"; field public static final String REVOKE_POST_NOTIFICATIONS_WITHOUT_KILL = "android.permission.REVOKE_POST_NOTIFICATIONS_WITHOUT_KILL"; field public static final String SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS = "android.permission.SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS"; + field public static final String SET_KEYBOARD_LAYOUT = "android.permission.SET_KEYBOARD_LAYOUT"; field public static final String START_TASKS_FROM_RECENTS = "android.permission.START_TASKS_FROM_RECENTS"; field public static final String SUSPEND_APPS = "android.permission.SUSPEND_APPS"; field public static final String TEST_BIOMETRIC = "android.permission.TEST_BIOMETRIC"; @@ -101,7 +102,7 @@ package android.animation { package android.app { - @UiContext public class Activity extends android.view.ContextThemeWrapper implements android.content.ComponentCallbacks2 android.view.KeyEvent.Callback android.view.LayoutInflater.Factory2 android.view.View.OnCreateContextMenuListener android.view.Window.Callback { + @UiContext public class Activity extends android.view.ContextThemeWrapper implements android.content.ComponentCallbacks2 android.view.KeyEvent.Callback android.view.LayoutInflater.Factory2 android.view.OnBackInvokedDispatcherOwner android.view.View.OnCreateContextMenuListener android.view.Window.Callback { method public void onMovedToDisplay(int, android.content.res.Configuration); } @@ -1180,9 +1181,23 @@ package android.hardware.hdmi { package android.hardware.input { + public final class InputDeviceIdentifier implements android.os.Parcelable { + ctor public InputDeviceIdentifier(@NonNull String, int, int); + method public int describeContents(); + method @NonNull public String getDescriptor(); + method public int getProductId(); + method public int getVendorId(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.hardware.input.InputDeviceIdentifier> CREATOR; + } + public final class InputManager { method public int getBlockUntrustedTouchesMode(@NonNull android.content.Context); + method @Nullable public String getCurrentKeyboardLayoutForInputDevice(@NonNull android.hardware.input.InputDeviceIdentifier); + method @NonNull public java.util.List<java.lang.String> getKeyboardLayoutDescriptorsForInputDevice(@NonNull android.view.InputDevice); + method @RequiresPermission(android.Manifest.permission.SET_KEYBOARD_LAYOUT) public void removeKeyboardLayoutForInputDevice(@NonNull android.hardware.input.InputDeviceIdentifier, @NonNull String); method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setBlockUntrustedTouchesMode(@NonNull android.content.Context, int); + method @RequiresPermission(android.Manifest.permission.SET_KEYBOARD_LAYOUT) public void setCurrentKeyboardLayoutForInputDevice(@NonNull android.hardware.input.InputDeviceIdentifier, @NonNull String); method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setMaximumObscuringOpacityForTouch(@FloatRange(from=0, to=1) float); field public static final long BLOCK_UNTRUSTED_TOUCHES = 158002302L; // 0x96aec7eL } @@ -1928,6 +1943,9 @@ package android.os.storage { method @NonNull public static String convert(@NonNull java.util.UUID); method public boolean isAppIoBlocked(@NonNull java.util.UUID, int, int, int); method public static boolean isUserKeyUnlocked(int); + field public static final String CACHE_RESERVE_PERCENT_HIGH_KEY = "cache_reserve_percent_high"; + field public static final String CACHE_RESERVE_PERCENT_LOW_KEY = "cache_reserve_percent_low"; + field public static final String STORAGE_THRESHOLD_PERCENT_HIGH_KEY = "storage_threshold_percent_high"; } public final class StorageVolume implements android.os.Parcelable { @@ -2737,6 +2755,7 @@ package android.view { public final class InputDevice implements android.os.Parcelable { method @RequiresPermission("android.permission.DISABLE_INPUT_DEVICE") public void disable(); method @RequiresPermission("android.permission.DISABLE_INPUT_DEVICE") public void enable(); + method @NonNull public android.hardware.input.InputDeviceIdentifier getIdentifier(); } public class KeyEvent extends android.view.InputEvent implements android.os.Parcelable { @@ -2779,7 +2798,7 @@ package android.view { method public void setView(@NonNull android.view.View, @NonNull android.view.WindowManager.LayoutParams); } - @UiThread public class View implements android.view.accessibility.AccessibilityEventSource android.graphics.drawable.Drawable.Callback android.view.KeyEvent.Callback { + @UiThread public class View implements android.view.accessibility.AccessibilityEventSource android.graphics.drawable.Drawable.Callback android.view.KeyEvent.Callback android.view.OnBackInvokedDispatcherOwner { method public android.view.View getTooltipView(); method public boolean isAutofilled(); method public static boolean isDefaultFocusHighlightEnabled(); diff --git a/core/java/Android.bp b/core/java/Android.bp index c9cbef2c9e9a..897bab429a87 100644 --- a/core/java/Android.bp +++ b/core/java/Android.bp @@ -121,6 +121,11 @@ filegroup { ], } +filegroup { + name: "ILogcatManagerService_aidl", + srcs: ["android/os/logcat/ILogcatManagerService.aidl"], +} + genrule { name: "statslog-framework-java-gen", tools: ["stats-log-api-gen"], diff --git a/core/java/android/accessibilityservice/AccessibilityGestureEvent.java b/core/java/android/accessibilityservice/AccessibilityGestureEvent.java index 3c9b23251191..8e01779c6fac 100644 --- a/core/java/android/accessibilityservice/AccessibilityGestureEvent.java +++ b/core/java/android/accessibilityservice/AccessibilityGestureEvent.java @@ -172,7 +172,7 @@ public final class AccessibilityGestureEvent implements Parcelable { private AccessibilityGestureEvent(@NonNull Parcel parcel) { mGestureId = parcel.readInt(); mDisplayId = parcel.readInt(); - ParceledListSlice<MotionEvent> slice = parcel.readParcelable(getClass().getClassLoader()); + ParceledListSlice<MotionEvent> slice = parcel.readParcelable(getClass().getClassLoader(), android.content.pm.ParceledListSlice.class); mMotionEvents = slice.getList(); } diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java index 04c784ea1c17..1167d0b1034f 100644 --- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java +++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java @@ -1094,8 +1094,8 @@ public class AccessibilityServiceInfo implements Parcelable { mInteractiveUiTimeout = parcel.readInt(); flags = parcel.readInt(); crashed = parcel.readInt() != 0; - mComponentName = parcel.readParcelable(this.getClass().getClassLoader()); - mResolveInfo = parcel.readParcelable(null); + mComponentName = parcel.readParcelable(this.getClass().getClassLoader(), android.content.ComponentName.class); + mResolveInfo = parcel.readParcelable(null, android.content.pm.ResolveInfo.class); mSettingsActivityName = parcel.readString(); mCapabilities = parcel.readInt(); mSummaryResId = parcel.readInt(); diff --git a/core/java/android/accessibilityservice/TouchInteractionController.java b/core/java/android/accessibilityservice/TouchInteractionController.java index a8ba1d33f3b4..bb2b8d45fd61 100644 --- a/core/java/android/accessibilityservice/TouchInteractionController.java +++ b/core/java/android/accessibilityservice/TouchInteractionController.java @@ -24,6 +24,8 @@ import android.util.ArrayMap; import android.view.MotionEvent; import android.view.accessibility.AccessibilityInteractionClient; +import java.util.LinkedList; +import java.util.Queue; import java.util.concurrent.Executor; /** @@ -102,6 +104,11 @@ public final class TouchInteractionController { private boolean mServiceDetectsGestures; /** Map of callbacks to executors. Lazily created when adding the first callback. */ private ArrayMap<Callback, Executor> mCallbacks; + // A list of motion events that should be queued until a pending transition has taken place. + private Queue<MotionEvent> mQueuedMotionEvents = new LinkedList<>(); + // Whether this controller is waiting for a state transition. + // Motion events will be queued and sent to listeners after the transition has taken place. + private boolean mStateChangeRequested = false; // The current state of the display. private int mState = STATE_CLEAR; @@ -169,6 +176,14 @@ public final class TouchInteractionController { * main thread. */ void onMotionEvent(MotionEvent event) { + if (mStateChangeRequested) { + mQueuedMotionEvents.add(event); + } else { + sendEventToAllListeners(event); + } + } + + private void sendEventToAllListeners(MotionEvent event) { final ArrayMap<Callback, Executor> entries; synchronized (mLock) { // callbacks may remove themselves. Perform a shallow copy to avoid concurrent @@ -209,6 +224,10 @@ public final class TouchInteractionController { callback.onStateChanged(state); } } + mStateChangeRequested = false; + while (mQueuedMotionEvents.size() > 0) { + sendEventToAllListeners(mQueuedMotionEvents.poll()); + } } /** @@ -253,6 +272,7 @@ public final class TouchInteractionController { } catch (RemoteException re) { throw new RuntimeException(re); } + mStateChangeRequested = true; } } @@ -281,6 +301,7 @@ public final class TouchInteractionController { } catch (RemoteException re) { throw new RuntimeException(re); } + mStateChangeRequested = true; } } @@ -302,6 +323,7 @@ public final class TouchInteractionController { } catch (RemoteException re) { throw new RuntimeException(re); } + mStateChangeRequested = true; } } diff --git a/core/java/android/accounts/CantAddAccountActivity.java b/core/java/android/accounts/CantAddAccountActivity.java index f7f232e5c422..107efc3cce95 100644 --- a/core/java/android/accounts/CantAddAccountActivity.java +++ b/core/java/android/accounts/CantAddAccountActivity.java @@ -16,9 +16,13 @@ package android.accounts; +import static android.app.admin.DevicePolicyResources.Strings.Core.CANT_ADD_ACCOUNT_MESSAGE; + import android.app.Activity; +import android.app.admin.DevicePolicyManager; import android.os.Bundle; import android.view.View; +import android.widget.TextView; import com.android.internal.R; @@ -33,6 +37,12 @@ public class CantAddAccountActivity extends Activity { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.app_not_authorized); + + TextView view = findViewById(R.id.description); + String text = getSystemService(DevicePolicyManager.class).getString( + CANT_ADD_ACCOUNT_MESSAGE, + () -> getString(R.string.error_message_change_not_allowed)); + view.setText(text); } public void onCancelButtonClicked(View view) { diff --git a/core/java/android/accounts/ChooseTypeAndAccountActivity.java b/core/java/android/accounts/ChooseTypeAndAccountActivity.java index 2e9f73ca388e..0d82ac942148 100644 --- a/core/java/android/accounts/ChooseTypeAndAccountActivity.java +++ b/core/java/android/accounts/ChooseTypeAndAccountActivity.java @@ -15,7 +15,10 @@ */ package android.accounts; +import static android.app.admin.DevicePolicyResources.Strings.Core.CANT_ADD_ACCOUNT_MESSAGE; + import android.app.Activity; +import android.app.admin.DevicePolicyManager; import android.content.Intent; import android.os.Bundle; import android.os.Parcelable; @@ -199,7 +202,14 @@ public class ChooseTypeAndAccountActivity extends Activity if (mPossiblyVisibleAccounts.isEmpty() && mDisallowAddAccounts) { requestWindowFeature(Window.FEATURE_NO_TITLE); + setContentView(R.layout.app_not_authorized); + TextView view = findViewById(R.id.description); + String text = getSystemService(DevicePolicyManager.class).getString( + CANT_ADD_ACCOUNT_MESSAGE, + () -> getString(R.string.error_message_change_not_allowed)); + view.setText(text); + mDontShowPicker = true; } diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 283345f07337..a7b96a6f136d 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -111,6 +111,8 @@ import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.MotionEvent; +import android.view.OnBackInvokedDispatcher; +import android.view.OnBackInvokedDispatcherOwner; import android.view.RemoteAnimationDefinition; import android.view.SearchEvent; import android.view.View; @@ -736,7 +738,8 @@ public class Activity extends ContextThemeWrapper Window.Callback, KeyEvent.Callback, OnCreateContextMenuListener, ComponentCallbacks2, Window.OnWindowDismissedCallback, - ContentCaptureManager.ContentCaptureClient { + ContentCaptureManager.ContentCaptureClient, + OnBackInvokedDispatcherOwner { private static final String TAG = "Activity"; private static final boolean DEBUG_LIFECYCLE = false; @@ -1521,7 +1524,10 @@ public class Activity extends ContextThemeWrapper } private void dispatchActivityConfigurationChanged() { - getApplication().dispatchActivityConfigurationChanged(this); + // In case the new config comes before mApplication is assigned. + if (getApplication() != null) { + getApplication().dispatchActivityConfigurationChanged(this); + } Object[] callbacks = collectActivityLifecycleCallbacks(); if (callbacks != null) { for (int i = 0; i < callbacks.length; i++) { @@ -8672,4 +8678,22 @@ public class Activity extends ContextThemeWrapper return (w != null && w.peekDecorView() != null); } } + + /** + * Returns the {@link OnBackInvokedDispatcher} instance associated with the window that this + * activity is attached to. + * + * Returns null if the activity is not attached to a window with a decor. + */ + @Nullable + @Override + public OnBackInvokedDispatcher getOnBackInvokedDispatcher() { + if (mWindow != null) { + View decorView = mWindow.getDecorView(); + if (decorView != null) { + return decorView.getOnBackInvokedDispatcher(); + } + } + return null; + } } diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 9f8d24662c8d..a1409839ff63 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -1903,7 +1903,7 @@ public class ActivityManager { public void readFromParcel(Parcel source) { id = source.readInt(); persistentId = source.readInt(); - childrenTaskInfos = source.readArrayList(RecentTaskInfo.class.getClassLoader()); + childrenTaskInfos = source.readArrayList(RecentTaskInfo.class.getClassLoader(), android.app.ActivityManager.RecentTaskInfo.class); lastSnapshotData.taskSize = source.readTypedObject(Point.CREATOR); lastSnapshotData.contentInsets = source.readTypedObject(Rect.CREATOR); lastSnapshotData.bufferSize = source.readTypedObject(Point.CREATOR); diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 686ca3bad087..ea6271412289 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -61,6 +61,7 @@ import android.app.servertransaction.PendingTransactionActions.StopInfo; import android.app.servertransaction.ResumeActivityItem; import android.app.servertransaction.TransactionExecutor; import android.app.servertransaction.TransactionExecutorHelper; +import android.bluetooth.BluetoothFrameworkInitializer; import android.compat.annotation.UnsupportedAppUsage; import android.content.AttributionSource; import android.content.AutofillOptions; @@ -105,9 +106,11 @@ import android.media.MediaFrameworkPlatformInitializer; import android.media.MediaServiceManager; import android.net.ConnectivityManager; import android.net.Proxy; +import android.net.TrafficStats; import android.net.Uri; import android.os.AsyncTask; import android.os.Binder; +import android.os.BluetoothServiceManager; import android.os.Build; import android.os.Bundle; import android.os.CancellationSignal; @@ -6725,6 +6728,13 @@ public final class ActivityThread extends ClientTransactionHandler NetworkSecurityConfigProvider.install(appContext); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + // For backward compatibility, TrafficStats needs static access to the application context. + // But for isolated apps which cannot access network related services, service discovery + // is restricted. Hence, calling this would result in NPE. + if (!Process.isIsolated()) { + TrafficStats.init(appContext); + } + // Continue loading instrumentation. if (ii != null) { initInstrumentation(ii, data, appContext); @@ -7911,6 +7921,7 @@ public final class ActivityThread extends ClientTransactionHandler StatsFrameworkInitializer.setStatsServiceManager(new StatsServiceManager()); MediaFrameworkPlatformInitializer.setMediaServiceManager(new MediaServiceManager()); MediaFrameworkInitializer.setMediaServiceManager(new MediaServiceManager()); + BluetoothFrameworkInitializer.setBluetoothServiceManager(new BluetoothServiceManager()); } private void purgePendingResources() { diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 565f69090c6b..68c69e555bda 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -4058,7 +4058,7 @@ public class AppOpsManager { LongSparseArray<NoteOpEvent> array = new LongSparseArray<>(numEntries); for (int i = 0; i < numEntries; i++) { - array.put(source.readLong(), source.readParcelable(null)); + array.put(source.readLong(), source.readParcelable(null, android.app.AppOpsManager.NoteOpEvent.class)); } return array; @@ -5178,7 +5178,7 @@ public class AppOpsManager { final int[] uids = parcel.createIntArray(); if (!ArrayUtils.isEmpty(uids)) { final ParceledListSlice<HistoricalUidOps> listSlice = parcel.readParcelable( - HistoricalOps.class.getClassLoader()); + HistoricalOps.class.getClassLoader(), android.content.pm.ParceledListSlice.class); final List<HistoricalUidOps> uidOps = (listSlice != null) ? listSlice.getList() : null; if (uidOps == null) { @@ -10000,7 +10000,7 @@ public class AppOpsManager { private static @Nullable List<AttributedOpEntry> readDiscreteAccessArrayFromParcel( @NonNull Parcel parcel) { - final ParceledListSlice<AttributedOpEntry> listSlice = parcel.readParcelable(null); + final ParceledListSlice<AttributedOpEntry> listSlice = parcel.readParcelable(null, android.content.pm.ParceledListSlice.class); return listSlice == null ? null : listSlice.getList(); } diff --git a/core/java/android/app/Application.java b/core/java/android/app/Application.java index 7f849ef00d7d..9eb3e8fb0160 100644 --- a/core/java/android/app/Application.java +++ b/core/java/android/app/Application.java @@ -29,9 +29,13 @@ import android.content.Intent; import android.content.res.Configuration; import android.os.Build; import android.os.Bundle; +import android.util.ArrayMap; import android.util.Log; +import android.util.Slog; import android.view.autofill.AutofillManager; +import com.android.internal.annotations.GuardedBy; + import java.util.ArrayList; /** @@ -53,6 +57,10 @@ import java.util.ArrayList; */ public class Application extends ContextWrapper implements ComponentCallbacks2 { private static final String TAG = "Application"; + + /** Whether to enable the check to detect "duplicate application instances". */ + private static final boolean DEBUG_DUP_APP_INSTANCES = true; + @UnsupportedAppUsage private ArrayList<ActivityLifecycleCallbacks> mActivityLifecycleCallbacks = new ArrayList<ActivityLifecycleCallbacks>(); @@ -66,6 +74,13 @@ public class Application extends ContextWrapper implements ComponentCallbacks2 { @UnsupportedAppUsage public LoadedApk mLoadedApk; + @GuardedBy("sInstances") + private static final ArrayMap<Class<?>, Application> sInstances = + DEBUG_DUP_APP_INSTANCES ? new ArrayMap<>(1) : null; + + // Only set when DEBUG_DUP_APP_INSTANCES is true. + private StackTrace mConstructorStackTrace; + public interface ActivityLifecycleCallbacks { /** @@ -231,6 +246,41 @@ public class Application extends ContextWrapper implements ComponentCallbacks2 { public Application() { super(null); + if (DEBUG_DUP_APP_INSTANCES) { + checkDuplicateInstances(); + } + } + + private void checkDuplicateInstances() { + final Class<?> myClass = this.getClass(); + + // We only activate this check for custom application classes. + // Otherwise, it'd misfire if multiple apps share the same process, if all of them use + // the same Application class (on the same classloader). + if (myClass == Application.class) { + return; + } + synchronized (sInstances) { + final Application firstInstance = sInstances.get(myClass); + if (firstInstance == null) { + this.mConstructorStackTrace = new StackTrace("First ctor was called here"); + sInstances.put(myClass, this); + return; + } + final StackTrace currentStackTrace = new StackTrace("Current ctor was called here", + firstInstance.mConstructorStackTrace); + this.mConstructorStackTrace = currentStackTrace; + Slog.wtf(TAG, "Application ctor called twice for " + myClass + + " first LoadedApk=" + firstInstance.getLoadedApkInfo(), + currentStackTrace); + } + } + + private String getLoadedApkInfo() { + if (mLoadedApk == null) { + return "null"; + } + return mLoadedApk + "/pkg=" + mLoadedApk.mPackageName; } /** diff --git a/core/java/android/app/AutomaticZenRule.java b/core/java/android/app/AutomaticZenRule.java index 7a806bdf473d..c0aebeed596a 100644 --- a/core/java/android/app/AutomaticZenRule.java +++ b/core/java/android/app/AutomaticZenRule.java @@ -118,11 +118,11 @@ public final class AutomaticZenRule implements Parcelable { name = source.readString(); } interruptionFilter = source.readInt(); - conditionId = source.readParcelable(null); - owner = source.readParcelable(null); - configurationActivity = source.readParcelable(null); + conditionId = source.readParcelable(null, android.net.Uri.class); + owner = source.readParcelable(null, android.content.ComponentName.class); + configurationActivity = source.readParcelable(null, android.content.ComponentName.class); creationTime = source.readLong(); - mZenPolicy = source.readParcelable(null); + mZenPolicy = source.readParcelable(null, android.service.notification.ZenPolicy.class); mModified = source.readInt() == ENABLED; mPkg = source.readString(); } diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index fa48730d4950..f3315a8dc089 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -2179,8 +2179,8 @@ class ContextImpl extends Context { } @Override - public void selfRevokePermissions(@NonNull Collection<String> permissions) { - getSystemService(PermissionManager.class).selfRevokePermissions(permissions); + public void revokeOwnPermissionsOnKill(@NonNull Collection<String> permissions) { + getSystemService(PermissionManager.class).revokeOwnPermissionsOnKill(permissions); } @Override diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java index 306035341ea3..a7fb83bfcf5e 100644 --- a/core/java/android/app/Dialog.java +++ b/core/java/android/app/Dialog.java @@ -52,6 +52,8 @@ import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.MotionEvent; +import android.view.OnBackInvokedDispatcher; +import android.view.OnBackInvokedDispatcherOwner; import android.view.SearchEvent; import android.view.View; import android.view.View.OnCreateContextMenuListener; @@ -94,7 +96,8 @@ import java.lang.ref.WeakReference; * </div> */ public class Dialog implements DialogInterface, Window.Callback, - KeyEvent.Callback, OnCreateContextMenuListener, Window.OnWindowDismissedCallback { + KeyEvent.Callback, OnCreateContextMenuListener, Window.OnWindowDismissedCallback, + OnBackInvokedDispatcherOwner { private static final String TAG = "Dialog"; @UnsupportedAppUsage private Activity mOwnerActivity; @@ -1439,4 +1442,22 @@ public class Dialog implements DialogInterface, Window.Callback, } } } + + /** + * Returns the {@link OnBackInvokedDispatcher} instance associated with the window that this + * dialog is attached to. + * + * Returns null if the dialog is not attached to a window with a decor. + */ + @Nullable + @Override + public OnBackInvokedDispatcher getOnBackInvokedDispatcher() { + if (mWindow != null) { + View decorView = mWindow.getDecorView(); + if (decorView != null) { + return decorView.getOnBackInvokedDispatcher(); + } + } + return null; + } } diff --git a/core/java/android/app/GameManager.java b/core/java/android/app/GameManager.java index 29e1b70097f2..76471d30eaf9 100644 --- a/core/java/android/app/GameManager.java +++ b/core/java/android/app/GameManager.java @@ -119,6 +119,31 @@ public final class GameManager { } /** + * Returns the {@link GameModeInfo} associated with the game associated with + * the given {@code packageName}. If the given package is not a game, {@code null} is + * always returned. + * <p> + * An application can use <code>android:isGame="true"</code> or + * <code>android:appCategory="game"</code> to indicate that the application is a game. + * If the manifest doesn't define a category, the category can also be + * provided by the installer via + * {@link android.content.pm.PackageManager#setApplicationCategoryHint(String, int)}. + * <p> + * + * @hide + */ + @SystemApi + @UserHandleAware + @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE) + public @Nullable GameModeInfo getGameModeInfo(@NonNull String packageName) { + try { + return mService.getGameModeInfo(packageName, mContext.getUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Sets the game mode for the given package. * <p> * The caller must have {@link android.Manifest.permission#MANAGE_GAME_MODE}. diff --git a/core/java/android/app/GameModeInfo.aidl b/core/java/android/app/GameModeInfo.aidl new file mode 100644 index 000000000000..3b13201c5d1b --- /dev/null +++ b/core/java/android/app/GameModeInfo.aidl @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app; + +/** + * @hide + */ +parcelable GameModeInfo;
\ No newline at end of file diff --git a/core/java/android/app/GameModeInfo.java b/core/java/android/app/GameModeInfo.java new file mode 100644 index 000000000000..fe0ac352404d --- /dev/null +++ b/core/java/android/app/GameModeInfo.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app; + +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * GameModeInfo returned from {@link GameManager#getGameModeInfo(String)}. + * @hide + */ +@SystemApi +public final class GameModeInfo implements Parcelable { + + public static final @NonNull Creator<GameModeInfo> CREATOR = new Creator<GameModeInfo>() { + @Override + public GameModeInfo createFromParcel(Parcel in) { + return new GameModeInfo(in); + } + + @Override + public GameModeInfo[] newArray(int size) { + return new GameModeInfo[size]; + } + }; + + public GameModeInfo(@GameManager.GameMode int activeGameMode, + @NonNull @GameManager.GameMode int[] availableGameModes) { + mActiveGameMode = activeGameMode; + mAvailableGameModes = availableGameModes; + } + + GameModeInfo(Parcel in) { + mActiveGameMode = in.readInt(); + final int availableGameModesCount = in.readInt(); + mAvailableGameModes = new int[availableGameModesCount]; + in.readIntArray(mAvailableGameModes); + } + + /** + * Returns the {@link GameManager.GameMode} the application is currently using. + * Developers can enable game modes by adding + * <code> + * <meta-data android:name="android.game_mode_intervention" + * android:resource="@xml/GAME_MODE_CONFIG_FILE" /> + * </code> + * to the {@link <application> tag}, where the GAME_MODE_CONFIG_FILE is an XML file that + * specifies the game mode enablement and configuration: + * <code> + * <game-mode-config xmlns:android="http://schemas.android.com/apk/res/android" + * android:gameModePerformance="true" + * android:gameModeBattery="false" + * /> + * </code> + */ + public @GameManager.GameMode int getActiveGameMode() { + return mActiveGameMode; + } + + /** + * The collection of {@link GameManager.GameMode GameModes} that can be applied to the game. + */ + @NonNull + public @GameManager.GameMode int[] getAvailableGameModes() { + return mAvailableGameModes; + } + + // Ideally there should be callback that the caller can register to know when the available + // GameMode and/or the active GameMode is changed, however, there's no concrete use case + // at the moment so there's no callback mechanism introduced . + private final @GameManager.GameMode int[] mAvailableGameModes; + private final @GameManager.GameMode int mActiveGameMode; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mActiveGameMode); + dest.writeInt(mAvailableGameModes.length); + dest.writeIntArray(mAvailableGameModes); + } +} diff --git a/core/java/android/app/GrantedUriPermission.java b/core/java/android/app/GrantedUriPermission.java index 48d5b8cc126b..a71cb4a11af8 100644 --- a/core/java/android/app/GrantedUriPermission.java +++ b/core/java/android/app/GrantedUriPermission.java @@ -68,7 +68,7 @@ public class GrantedUriPermission implements Parcelable { }; private GrantedUriPermission(Parcel in) { - uri = in.readParcelable(null); + uri = in.readParcelable(null, android.net.Uri.class); packageName = in.readString(); } } diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl index a9ec11edd680..0801b2481f0c 100644 --- a/core/java/android/app/IActivityTaskManager.aidl +++ b/core/java/android/app/IActivityTaskManager.aidl @@ -72,6 +72,7 @@ import android.view.IRemoteAnimationRunner; import android.view.RemoteAnimationDefinition; import android.view.RemoteAnimationAdapter; import android.window.IWindowOrganizerController; +import android.window.BackNavigationInfo; import android.window.SplashScreenView; import com.android.internal.app.IVoiceInteractor; import com.android.internal.os.IResultReceiver; @@ -346,7 +347,8 @@ interface IActivityTaskManager { void setRunningRemoteTransitionDelegate(in IApplicationThread caller); /** - * Prepare the back preview in the server + * Prepare the back navigation in the server. This setups the leashed for sysui to animate + * the back gesture and returns the data needed for the animation. */ - void startBackPreview(IRemoteAnimationRunner runner); + android.window.BackNavigationInfo startBackNavigation(); } diff --git a/core/java/android/app/IGameManagerService.aidl b/core/java/android/app/IGameManagerService.aidl index d9aa586c6bbb..57de8c70e742 100644 --- a/core/java/android/app/IGameManagerService.aidl +++ b/core/java/android/app/IGameManagerService.aidl @@ -16,6 +16,7 @@ package android.app; +import android.app.GameModeInfo; import android.app.GameState; /** @@ -27,4 +28,5 @@ interface IGameManagerService { int[] getAvailableGameModes(String packageName); boolean getAngleEnabled(String packageName, int userId); void setGameState(String packageName, in GameState gameState, int userId); -}
\ No newline at end of file + GameModeInfo getGameModeInfo(String packageName, int userId); +} diff --git a/core/java/android/app/IUiModeManager.aidl b/core/java/android/app/IUiModeManager.aidl index 440dd629c114..55afed20c826 100644 --- a/core/java/android/app/IUiModeManager.aidl +++ b/core/java/android/app/IUiModeManager.aidl @@ -48,20 +48,43 @@ interface IUiModeManager { /** * Sets the night mode. + * <p> * The mode can be one of: - * 1 - notnight mode - * 2 - night mode - * 3 - automatic mode switching + * <ol>notnight mode</ol> + * <ol>night mode</ol> + * <ol>custom schedule mode switching</ol> */ void setNightMode(int mode); /** - * Gets the currently configured night mode. Return 1 for notnight, - * 2 for night, and 3 for automatic mode switching. + * Gets the currently configured night mode. + * <p> + * Returns + * <ol>notnight mode</ol> + * <ol>night mode</ol> + * <ol>custom schedule mode switching</ol> */ int getNightMode(); /** + * Sets the current night mode to {@link #MODE_NIGHT_CUSTOM} with the custom night mode type + * {@code nightModeCustomType}. + * + * @param nightModeCustomType + * @hide + */ + void setNightModeCustomType(int nightModeCustomType); + + /** + * Returns the custom night mode type. + * <p> + * If the current night mode is not {@link #MODE_NIGHT_CUSTOM}, returns + * {@link #MODE_NIGHT_CUSTOM_TYPE_UNKNOWN}. + * @hide + */ + int getNightModeCustomType(); + + /** * Sets the dark mode for the given application. This setting is persisted and will override the * system configuration for this application. * 1 - notnight mode @@ -81,8 +104,21 @@ interface IUiModeManager { boolean isNightModeLocked(); /** - * [De]Activates night mode - */ + * [De]activating night mode for the current user if the current night mode is custom and the + * custom type matches {@code nightModeCustomType}. + * + * @param nightModeCustomType the specify type of custom mode + * @param active {@code true} to activate night mode. Otherwise, deactivate night mode + * @return {@code true} if night mode has successfully activated for the requested + * {@code nightModeCustomType}. + * @hide + */ + boolean setNightModeActivatedForCustomMode(int nightModeCustom, boolean active); + + /** + * [De]Activates night mode. + * @hide + */ boolean setNightModeActivated(boolean active); /** diff --git a/core/java/android/app/IWallpaperManager.aidl b/core/java/android/app/IWallpaperManager.aidl index 4f7c6841d6bb..28c273ec50a6 100644 --- a/core/java/android/app/IWallpaperManager.aidl +++ b/core/java/android/app/IWallpaperManager.aidl @@ -204,4 +204,27 @@ interface IWallpaperManager { * @hide */ void notifyGoingToSleep(int x, int y, in Bundle extras); + + /** + * Sets the wallpaper dim amount between [0f, 1f] which would be blended with the system default + * dimming. 0f doesn't add any additional dimming and 1f makes the wallpaper fully black. + * + * @hide + */ + oneway void setWallpaperDimAmount(float dimAmount); + + /** + * Gets the current additional dim amount set on the wallpaper. 0f means no application has + * added any dimming on top of the system default dim amount. + * + * @hide + */ + float getWallpaperDimAmount(); + + /** + * Whether the lock screen wallpaper is different from the system wallpaper. + * + * @hide + */ + boolean lockScreenWallpaperExists(); } diff --git a/core/java/android/app/NotificationChannelGroup.java b/core/java/android/app/NotificationChannelGroup.java index cd6df0b231d9..f97415ca20c8 100644 --- a/core/java/android/app/NotificationChannelGroup.java +++ b/core/java/android/app/NotificationChannelGroup.java @@ -100,7 +100,7 @@ public final class NotificationChannelGroup implements Parcelable { } else { mDescription = null; } - in.readParcelableList(mChannels, NotificationChannel.class.getClassLoader()); + in.readParcelableList(mChannels, NotificationChannel.class.getClassLoader(), android.app.NotificationChannel.class); mBlocked = in.readBoolean(); mUserLockedFields = in.readInt(); } diff --git a/core/java/android/app/RemoteInputHistoryItem.java b/core/java/android/app/RemoteInputHistoryItem.java index 091db3f142ae..32f89819fb1f 100644 --- a/core/java/android/app/RemoteInputHistoryItem.java +++ b/core/java/android/app/RemoteInputHistoryItem.java @@ -48,7 +48,7 @@ public class RemoteInputHistoryItem implements Parcelable { protected RemoteInputHistoryItem(Parcel in) { mText = in.readCharSequence(); mMimeType = in.readStringNoHelper(); - mUri = in.readParcelable(Uri.class.getClassLoader()); + mUri = in.readParcelable(Uri.class.getClassLoader(), android.net.Uri.class); } public static final Creator<RemoteInputHistoryItem> CREATOR = diff --git a/core/java/android/app/ServiceStartNotAllowedException.java b/core/java/android/app/ServiceStartNotAllowedException.java index 33285b2190eb..b1f47eee4bfd 100644 --- a/core/java/android/app/ServiceStartNotAllowedException.java +++ b/core/java/android/app/ServiceStartNotAllowedException.java @@ -40,4 +40,11 @@ public abstract class ServiceStartNotAllowedException extends IllegalStateExcept return new BackgroundServiceStartNotAllowedException(message); } } + + @Override + public synchronized Throwable getCause() { + // "Cause" is often used for clustering exceptions, and developers don't want to have it + // for this exception. b/210890426 + return null; + } } diff --git a/core/java/android/app/StackTrace.java b/core/java/android/app/StackTrace.java index ec058f88118b..6a77abdefea9 100644 --- a/core/java/android/app/StackTrace.java +++ b/core/java/android/app/StackTrace.java @@ -24,4 +24,8 @@ public class StackTrace extends Exception { public StackTrace(String message) { super(message); } + + public StackTrace(String message, Throwable innerStackTrace) { + super(message, innerStackTrace); + } } diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index 67c42f69d079..7f8e46edf594 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -24,6 +24,8 @@ import android.annotation.SystemApi; import android.app.ContextImpl.ServiceInitializationState; import android.app.admin.DevicePolicyManager; import android.app.admin.IDevicePolicyManager; +import android.app.ambientcontext.AmbientContextManager; +import android.app.ambientcontext.IAmbientContextEventObserver; import android.app.appsearch.AppSearchManagerFrameworkInitializer; import android.app.blob.BlobStoreManagerFrameworkInitializer; import android.app.contentsuggestions.ContentSuggestionsManager; @@ -49,7 +51,7 @@ import android.app.usage.StorageStatsManager; import android.app.usage.UsageStatsManager; import android.apphibernation.AppHibernationManager; import android.appwidget.AppWidgetManager; -import android.bluetooth.BluetoothManager; +import android.bluetooth.BluetoothFrameworkInitializer; import android.companion.CompanionDeviceManager; import android.companion.ICompanionDeviceManager; import android.companion.virtual.IVirtualDeviceManager; @@ -124,8 +126,8 @@ import android.media.projection.MediaProjectionManager; import android.media.soundtrigger.SoundTriggerManager; import android.media.tv.ITvInputManager; import android.media.tv.TvInputManager; -import android.media.tv.interactive.ITvIAppManager; -import android.media.tv.interactive.TvIAppManager; +import android.media.tv.interactive.ITvInteractiveAppManager; +import android.media.tv.interactive.TvInteractiveAppManager; import android.media.tv.tunerresourcemanager.ITunerResourceManager; import android.media.tv.tunerresourcemanager.TunerResourceManager; import android.nearby.NearbyFrameworkInitializer; @@ -346,13 +348,6 @@ public final class SystemServiceRegistry { return new MediaRouter(ctx); }}); - registerService(Context.BLUETOOTH_SERVICE, BluetoothManager.class, - new CachedServiceFetcher<BluetoothManager>() { - @Override - public BluetoothManager createService(ContextImpl ctx) { - return new BluetoothManager(ctx); - }}); - registerService(Context.HDMI_CONTROL_SERVICE, HdmiControlManager.class, new StaticServiceFetcher<HdmiControlManager>() { @Override @@ -964,13 +959,16 @@ public final class SystemServiceRegistry { } }); - registerService(Context.TV_IAPP_SERVICE, TvIAppManager.class, - new CachedServiceFetcher<TvIAppManager>() { + registerService(Context.TV_INTERACTIVE_APP_SERVICE, TvInteractiveAppManager.class, + new CachedServiceFetcher<TvInteractiveAppManager>() { @Override - public TvIAppManager createService(ContextImpl ctx) throws ServiceNotFoundException { - IBinder iBinder = ServiceManager.getServiceOrThrow(Context.TV_IAPP_SERVICE); - ITvIAppManager service = ITvIAppManager.Stub.asInterface(iBinder); - return new TvIAppManager(service, ctx.getUserId()); + public TvInteractiveAppManager createService(ContextImpl ctx) + throws ServiceNotFoundException { + IBinder iBinder = + ServiceManager.getServiceOrThrow(Context.TV_INTERACTIVE_APP_SERVICE); + ITvInteractiveAppManager service = + ITvInteractiveAppManager.Stub.asInterface(iBinder); + return new TvInteractiveAppManager(service, ctx.getUserId()); }}); registerService(Context.TV_INPUT_SERVICE, TvInputManager.class, @@ -1021,19 +1019,21 @@ public final class SystemServiceRegistry { }}); registerService(Context.PERSISTENT_DATA_BLOCK_SERVICE, PersistentDataBlockManager.class, - new StaticServiceFetcher<PersistentDataBlockManager>() { + new CachedServiceFetcher<PersistentDataBlockManager>() { @Override - public PersistentDataBlockManager createService() throws ServiceNotFoundException { + public PersistentDataBlockManager createService(ContextImpl ctx) + throws ServiceNotFoundException { IBinder b = ServiceManager.getServiceOrThrow(Context.PERSISTENT_DATA_BLOCK_SERVICE); IPersistentDataBlockService persistentDataBlockService = IPersistentDataBlockService.Stub.asInterface(b); if (persistentDataBlockService != null) { - return new PersistentDataBlockManager(persistentDataBlockService); + return new PersistentDataBlockManager(ctx, persistentDataBlockService); } else { // not supported return null; } - }}); + } + }); registerService(Context.OEM_LOCK_SERVICE, OemLockManager.class, new StaticServiceFetcher<OemLockManager>() { @@ -1518,6 +1518,18 @@ public final class SystemServiceRegistry { } }); + registerService(Context.AMBIENT_CONTEXT_SERVICE, AmbientContextManager.class, + new CachedServiceFetcher<AmbientContextManager>() { + @Override + public AmbientContextManager createService(ContextImpl ctx) + throws ServiceNotFoundException { + IBinder iBinder = ServiceManager.getServiceOrThrow( + Context.AMBIENT_CONTEXT_SERVICE); + IAmbientContextEventObserver manager = + IAmbientContextEventObserver.Stub.asInterface(iBinder); + return new AmbientContextManager(ctx.getOuterContext(), manager); + }}); + sInitializing = true; try { // Note: the following functions need to be @SystemApis, once they become mainline @@ -1525,6 +1537,7 @@ public final class SystemServiceRegistry { ConnectivityFrameworkInitializer.registerServiceWrappers(); JobSchedulerFrameworkInitializer.registerServiceWrappers(); BlobStoreManagerFrameworkInitializer.initialize(); + BluetoothFrameworkInitializer.registerServiceWrappers(); TelephonyFrameworkInitializer.registerServiceWrappers(); AppSearchManagerFrameworkInitializer.initialize(); WifiFrameworkInitializer.registerServiceWrappers(); diff --git a/core/java/android/app/UiModeManager.java b/core/java/android/app/UiModeManager.java index 973a8fb068d1..73a9e5a221c7 100644 --- a/core/java/android/app/UiModeManager.java +++ b/core/java/android/app/UiModeManager.java @@ -243,6 +243,45 @@ public class UiModeManager { */ public static final int MODE_NIGHT_YES = 2; + /** + * Granular types for {@link MODE_NIGHT_CUSTOM_TYPE_BEDTIME} + * @hide + */ + @IntDef(prefix = { "MODE_NIGHT_CUSTOM_TYPE_" }, value = { + MODE_NIGHT_CUSTOM_TYPE_UNKNOWN, + MODE_NIGHT_CUSTOM_TYPE_SCHEDULE, + MODE_NIGHT_CUSTOM_TYPE_BEDTIME, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface NightModeCustomType {} + + /** + * A granular type for {@link #MODE_NIGHT_CUSTOM} which is unknown. + * <p> + * This is the default value when the night mode is set to value other than + * {@link #MODE_NIGHT_CUSTOM}. + * @hide + */ + @SystemApi + public static final int MODE_NIGHT_CUSTOM_TYPE_UNKNOWN = -1; + + /** + * A granular type for {@link #MODE_NIGHT_CUSTOM} which is based on a custom schedule. + * <p> + * This is the default value when night mode is set to {@link #MODE_NIGHT_CUSTOM} unless the + * the night mode custom type is specified by calling {@link #setNightModeCustomType(int)}. + * @hide + */ + @SystemApi + public static final int MODE_NIGHT_CUSTOM_TYPE_SCHEDULE = 0; + + /** + * A granular type for {@link #MODE_NIGHT_CUSTOM} which is based on the bedtime schedule. + * @hide + */ + @SystemApi + public static final int MODE_NIGHT_CUSTOM_TYPE_BEDTIME = 1; + private IUiModeManager mService; /** @@ -496,6 +535,45 @@ public class UiModeManager { } /** + * Sets the current night mode to {@link #MODE_NIGHT_CUSTOM} with the custom night mode type + * {@code nightModeCustomType}. + * + * @param nightModeCustomType + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE) + public void setNightModeCustomType(@NightModeCustomType int nightModeCustomType) { + if (mService != null) { + try { + mService.setNightModeCustomType(nightModeCustomType); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + + /** + * Returns the custom night mode type. + * <p> + * If the current night mode is not {@link #MODE_NIGHT_CUSTOM}, returns + * {@link #MODE_NIGHT_CUSTOM_TYPE_UNKNOWN}. + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE) + public int getNightModeCustomType() { + if (mService != null) { + try { + return mService.getNightModeCustomType(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + return MODE_NIGHT_CUSTOM_TYPE_UNKNOWN; + } + + /** * Sets and persist the night mode for this application. * <p> * The mode can be one of: @@ -599,11 +677,36 @@ public class UiModeManager { } /** + * [De]activating night mode for the current user if the current night mode is custom and the + * custom type matches {@code nightModeCustomType}. + * + * @param nightModeCustomType the specify type of custom mode + * @param active {@code true} to activate night mode. Otherwise, deactivate night mode + * @return {@code true} if night mode has successfully activated for the requested + * {@code nightModeCustomType}. + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE) + public boolean setNightModeActivatedForCustomMode(@NightModeCustomType int nightModeCustomType, + boolean active) { + if (mService != null) { + try { + return mService.setNightModeActivatedForCustomMode(nightModeCustomType, active); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + return false; + } + + /** * Activating night mode for the current user * * @return {@code true} if the change is successful * @hide */ + @RequiresPermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE) public boolean setNightModeActivated(boolean active) { if (mService != null) { try { diff --git a/core/java/android/app/WallpaperColors.java b/core/java/android/app/WallpaperColors.java index 7ef0a19ec44c..067a4c3c047e 100644 --- a/core/java/android/app/WallpaperColors.java +++ b/core/java/android/app/WallpaperColors.java @@ -16,6 +16,7 @@ package android.app; +import android.annotation.FloatRange; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -27,6 +28,7 @@ import android.graphics.drawable.Drawable; import android.os.Parcel; import android.os.Parcelable; import android.util.Log; +import android.util.MathUtils; import android.util.Size; import com.android.internal.graphics.ColorUtils; @@ -44,6 +46,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; /** @@ -173,6 +176,22 @@ public final class WallpaperColors implements Parcelable { if (bitmap == null) { throw new IllegalArgumentException("Bitmap can't be null"); } + return fromBitmap(bitmap, 0f /* dimAmount */); + } + + /** + * Constructs {@link WallpaperColors} from a bitmap with dimming applied. + * <p> + * Main colors will be extracted from the bitmap with dimming taken into account when + * calculating dark hints. + * + * @param bitmap Source where to extract from. + * @param dimAmount Wallpaper dim amount + * @hide + */ + public static WallpaperColors fromBitmap(@NonNull Bitmap bitmap, + @FloatRange (from = 0f, to = 1f) float dimAmount) { + Objects.requireNonNull(bitmap, "Bitmap can't be null"); final int bitmapArea = bitmap.getWidth() * bitmap.getHeight(); boolean shouldRecycle = false; @@ -211,7 +230,7 @@ public final class WallpaperColors implements Parcelable { } - int hints = calculateDarkHints(bitmap); + int hints = calculateDarkHints(bitmap, dimAmount); if (shouldRecycle) { bitmap.recycle(); @@ -507,13 +526,15 @@ public final class WallpaperColors implements Parcelable { * Checks if image is bright and clean enough to support light text. * * @param source What to read. + * @param dimAmount How much wallpaper dim amount was applied. * @return Whether image supports dark text or not. */ - private static int calculateDarkHints(Bitmap source) { + private static int calculateDarkHints(Bitmap source, float dimAmount) { if (source == null) { return 0; } + dimAmount = MathUtils.saturate(dimAmount); int[] pixels = new int[source.getWidth() * source.getHeight()]; double totalLuminance = 0; final int maxDarkPixels = (int) (pixels.length * MAX_DARK_AREA); @@ -521,24 +542,37 @@ public final class WallpaperColors implements Parcelable { source.getPixels(pixels, 0 /* offset */, source.getWidth(), 0 /* x */, 0 /* y */, source.getWidth(), source.getHeight()); + // Create a new black layer with dimAmount as the alpha to be accounted for when computing + // the luminance. + int dimmingLayerAlpha = (int) (255 * dimAmount); + int blackTransparent = ColorUtils.setAlphaComponent(Color.BLACK, dimmingLayerAlpha); + // This bitmap was already resized to fit the maximum allowed area. // Let's just loop through the pixels, no sweat! float[] tmpHsl = new float[3]; for (int i = 0; i < pixels.length; i++) { - ColorUtils.colorToHSL(pixels[i], tmpHsl); - final float luminance = tmpHsl[2]; - final int alpha = Color.alpha(pixels[i]); + int pixelColor = pixels[i]; + ColorUtils.colorToHSL(pixelColor, tmpHsl); + final int alpha = Color.alpha(pixelColor); + + // Apply composite colors where the foreground is a black layer with an alpha value of + // the dim amount and the background is the wallpaper pixel color. + int compositeColors = ColorUtils.compositeColors(blackTransparent, pixelColor); + + // Calculate the adjusted luminance of the dimmed wallpaper pixel color. + double adjustedLuminance = ColorUtils.calculateLuminance(compositeColors); + // Make sure we don't have a dark pixel mass that will // make text illegible. final boolean satisfiesTextContrast = ContrastColorUtil - .calculateContrast(pixels[i], Color.BLACK) > DARK_PIXEL_CONTRAST; + .calculateContrast(pixelColor, Color.BLACK) > DARK_PIXEL_CONTRAST; if (!satisfiesTextContrast && alpha != 0) { darkPixels++; if (DEBUG_DARK_PIXELS) { pixels[i] = Color.RED; } } - totalLuminance += luminance; + totalLuminance += adjustedLuminance; } int hints = 0; diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java index 772492dbfd5e..0a18588e0131 100644 --- a/core/java/android/app/WallpaperManager.java +++ b/core/java/android/app/WallpaperManager.java @@ -16,6 +16,7 @@ package android.app; +import android.annotation.FloatRange; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -71,6 +72,7 @@ import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; +import android.util.MathUtils; import android.util.Pair; import android.view.Display; import android.view.WindowManagerGlobal; @@ -1489,27 +1491,18 @@ public class WallpaperManager { mContext.getUserId()); if (fd != null) { FileOutputStream fos = null; - final Bitmap tmp = BitmapFactory.decodeStream(resources.openRawResource(resid)); + boolean ok = false; try { - // If the stream can't be decoded, treat it as an invalid input. - if (tmp != null) { - fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd); - tmp.compress(Bitmap.CompressFormat.PNG, 100, fos); - // The 'close()' is the trigger for any server-side image manipulation, - // so we must do that before waiting for completion. - fos.close(); - completion.waitForCompletion(); - } else { - throw new IllegalArgumentException( - "Resource 0x" + Integer.toHexString(resid) + " is invalid"); - } + fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd); + copyStreamToWallpaperFile(resources.openRawResource(resid), fos); + // The 'close()' is the trigger for any server-side image manipulation, + // so we must do that before waiting for completion. + fos.close(); + completion.waitForCompletion(); } finally { // Might be redundant but completion shouldn't wait unless the write // succeeded; this is a fallback if it threw past the close+wait. IoUtils.closeQuietly(fos); - if (tmp != null) { - tmp.recycle(); - } } } } catch (RemoteException e) { @@ -1751,22 +1744,13 @@ public class WallpaperManager { result, which, completion, mContext.getUserId()); if (fd != null) { FileOutputStream fos = null; - final Bitmap tmp = BitmapFactory.decodeStream(bitmapData); try { - // If the stream can't be decoded, treat it as an invalid input. - if (tmp != null) { - fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd); - tmp.compress(Bitmap.CompressFormat.PNG, 100, fos); - fos.close(); - completion.waitForCompletion(); - } else { - throw new IllegalArgumentException("InputStream is invalid"); - } + fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd); + copyStreamToWallpaperFile(bitmapData, fos); + fos.close(); + completion.waitForCompletion(); } finally { IoUtils.closeQuietly(fos); - if (tmp != null) { - tmp.recycle(); - } } } } catch (RemoteException e) { @@ -2012,6 +1996,63 @@ public class WallpaperManager { } /** + * Sets the wallpaper dim amount between [0f, 1f] which would be blended with the system default + * dimming. 0f doesn't add any additional dimming and 1f makes the wallpaper fully black. + * + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_DIM_AMOUNT) + public void setWallpaperDimAmount(@FloatRange (from = 0f, to = 1f) float dimAmount) { + if (sGlobals.mService == null) { + Log.w(TAG, "WallpaperService not running"); + throw new RuntimeException(new DeadSystemException()); + } + try { + sGlobals.mService.setWallpaperDimAmount(MathUtils.saturate(dimAmount)); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Gets the current additional dim amount set on the wallpaper. 0f means no application has + * added any dimming on top of the system default dim amount. + * + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_DIM_AMOUNT) + public float getWallpaperDimAmount() { + if (sGlobals.mService == null) { + Log.w(TAG, "WallpaperService not running"); + throw new RuntimeException(new DeadSystemException()); + } + try { + return sGlobals.mService.getWallpaperDimAmount(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Whether the lock screen wallpaper is different from the system wallpaper. + * + * @hide + */ + public boolean lockScreenWallpaperExists() { + if (sGlobals.mService == null) { + Log.w(TAG, "WallpaperService not running"); + throw new RuntimeException(new DeadSystemException()); + } + try { + return sGlobals.mService.lockScreenWallpaperExists(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Set the live wallpaper. * * This can only be called by packages with android.permission.SET_WALLPAPER_COMPONENT diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 1de5114dfe1b..96d037c905aa 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -622,6 +622,32 @@ public class DevicePolicyManager { "android.app.extra.FORCE_UPDATE_ROLE_HOLDER"; /** + * A boolean extra indicating whether offline provisioning is allowed. + * + * <p>For the online provisioning flow, there will be an attempt to download and install + * the latest version of the device management role holder. The platform will then delegate + * provisioning to the device management role holder via role holder-specific provisioning + * actions. + * + * <p>For the offline provisioning flow, the provisioning flow will always be handled by + * the platform. + * + * <p>If this extra is set to {@code false}, the provisioning flow will enforce that an + * internet connection is established, which will start the online provisioning flow. If an + * internet connection cannot be established, provisioning will fail. + * + * <p>If this extra is set to {@code true}, the provisioning flow will still try to connect to + * the internet, but if it fails it will start the offline provisioning flow. + * + * <p>The default value is {@code false}. + * + * <p>This extra is respected when provided via the provisioning intent actions such as {@link + * #ACTION_PROVISION_MANAGED_PROFILE}. + */ + public static final String EXTRA_PROVISIONING_ALLOW_OFFLINE = + "android.app.extra.PROVISIONING_ALLOW_OFFLINE"; + + /** * Action: Bugreport sharing with device owner has been accepted by the user. * * @hide @@ -1552,6 +1578,78 @@ public class DevicePolicyManager { public static final int FLAG_SUPPORTED_MODES_DEVICE_OWNER = 1 << 2; /** + * Constant for {@link #getMinimumRequiredWifiSecurityLevel()} and + * {@link #setMinimumRequiredWifiSecurityLevel(int)}: no minimum security level. + * + * <p> When returned from {@link #getMinimumRequiredWifiSecurityLevel()}, the constant + * represents the current minimum security level required. + * When passed to {@link #setMinimumRequiredWifiSecurityLevel(int)}, it sets the + * minimum security level a Wi-Fi network must meet. + * + * @see #WIFI_SECURITY_PERSONAL + * @see #WIFI_SECURITY_ENTERPRISE_EAP + * @see #WIFI_SECURITY_ENTERPRISE_192 + */ + public static final int WIFI_SECURITY_OPEN = 0; + + /** + * Constant for {@link #getMinimumRequiredWifiSecurityLevel()} and + * {@link #setMinimumRequiredWifiSecurityLevel(int)}: personal network such as WEP, WPA2-PSK. + * + * <p> When returned from {@link #getMinimumRequiredWifiSecurityLevel()}, the constant + * represents the current minimum security level required. + * When passed to {@link #setMinimumRequiredWifiSecurityLevel(int)}, it sets the + * minimum security level a Wi-Fi network must meet. + * + * @see #WIFI_SECURITY_OPEN + * @see #WIFI_SECURITY_ENTERPRISE_EAP + * @see #WIFI_SECURITY_ENTERPRISE_192 + */ + public static final int WIFI_SECURITY_PERSONAL = 1; + + /** + * Constant for {@link #getMinimumRequiredWifiSecurityLevel()} and + * {@link #setMinimumRequiredWifiSecurityLevel(int)}: enterprise EAP network. + * + * <p> When returned from {@link #getMinimumRequiredWifiSecurityLevel()}, the constant + * represents the current minimum security level required. + * When passed to {@link #setMinimumRequiredWifiSecurityLevel(int)}, it sets the + * minimum security level a Wi-Fi network must meet. + * + * @see #WIFI_SECURITY_OPEN + * @see #WIFI_SECURITY_PERSONAL + * @see #WIFI_SECURITY_ENTERPRISE_192 + */ + public static final int WIFI_SECURITY_ENTERPRISE_EAP = 2; + + /** + * Constant for {@link #getMinimumRequiredWifiSecurityLevel()} and + * {@link #setMinimumRequiredWifiSecurityLevel(int)}: enterprise 192 bit network. + * + * <p> When returned from {@link #getMinimumRequiredWifiSecurityLevel()}, the constant + * represents the current minimum security level required. + * When passed to {@link #setMinimumRequiredWifiSecurityLevel(int)}, it sets the + * minimum security level a Wi-Fi network must meet. + * + * @see #WIFI_SECURITY_OPEN + * @see #WIFI_SECURITY_PERSONAL + * @see #WIFI_SECURITY_ENTERPRISE_EAP + */ + public static final int WIFI_SECURITY_ENTERPRISE_192 = 3; + + /** + * Possible Wi-Fi minimum security levels + * + * @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = {"WIFI_SECURITY_"}, value = { + WIFI_SECURITY_OPEN, + WIFI_SECURITY_PERSONAL, + WIFI_SECURITY_ENTERPRISE_EAP, + WIFI_SECURITY_ENTERPRISE_192}) + public @interface WifiSecurity {} + + /** * This MIME type is used for starting the device owner provisioning. * * <p>During device owner provisioning a device admin app is set as the owner of the device. @@ -2986,6 +3084,54 @@ public class DevicePolicyManager { "android.app.extra.PROVISIONING_ROLE_HOLDER_CUSTOM_USER_CONSENT_INTENT"; /** + * Activity action: attempts to establish network connection + * + * <p>This intent can be accompanied by any of the relevant provisioning extras related to + * network connectivity, such as: + * <ul> + * <li>{@link #EXTRA_PROVISIONING_WIFI_SSID}</li> + * <li>{@link #EXTRA_PROVISIONING_WIFI_HIDDEN}</li> + * <li>{@link #EXTRA_PROVISIONING_WIFI_SECURITY_TYPE}</li> + * <li>{@link #EXTRA_PROVISIONING_WIFI_PASSWORD}</li> + * <li>{@link #EXTRA_PROVISIONING_WIFI_PROXY_HOST}</li> + * <li>{@link #EXTRA_PROVISIONING_WIFI_PROXY_PORT}</li> + * <li>{@link #EXTRA_PROVISIONING_WIFI_PROXY_BYPASS}</li> + * <li>{@link #EXTRA_PROVISIONING_WIFI_PAC_URL}</li> + * <li>{@code #EXTRA_PROVISIONING_WIFI_EAP_METHOD}</li> + * <li>{@code #EXTRA_PROVISIONING_WIFI_PHASE2_AUTH}</li> + * <li>{@code #EXTRA_PROVISIONING_WIFI_CA_CERTIFICATE}</li> + * <li>{@code #EXTRA_PROVISIONING_WIFI_USER_CERTIFICATE}</li> + * <li>{@code #EXTRA_PROVISIONING_WIFI_IDENTITY}</li> + * <li>{@code #EXTRA_PROVISIONING_WIFI_ANONYMOUS_IDENTITY}</li> + * <li>{@code #EXTRA_PROVISIONING_WIFI_DOMAIN}</li> + * </ul> + * + * <p>If there are provisioning extras related to network connectivity, this activity + * attempts to connect to the specified network. Otherwise it prompts the end-user to connect. + * + * <p>This activity is meant to be started by the provisioning initiator prior to starting + * {@link #ACTION_PROVISION_MANAGED_PROFILE} or {@link + * #ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE}. + * + * <p>Note that network connectivity is still also handled when provisioning via {@link + * #ACTION_PROVISION_MANAGED_PROFILE} or {@link + * #ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE}. {@link + * #ACTION_ESTABLISH_NETWORK_CONNECTION} should only be used in cases when the provisioning + * initiator would like to do some additional logic after the network connectivity step and + * before the start of provisioning. + * + * If network connection is established, {@link Activity#RESULT_OK} will be returned. Otherwise + * the result will be {@link Activity#RESULT_CANCELED}. + * + * @hide + */ + @RequiresPermission(android.Manifest.permission.DISPATCH_PROVISIONING_MESSAGE) + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + @SystemApi + public static final String ACTION_ESTABLISH_NETWORK_CONNECTION = + "android.app.action.ESTABLISH_NETWORK_CONNECTION"; + + /** * Maximum supported password length. Kind-of arbitrary. * @hide */ @@ -3256,14 +3402,15 @@ public class DevicePolicyManager { /** * Broadcast action: notify system apps (e.g. settings, SysUI, etc) that the device management - * resources with IDs {@link #EXTRA_RESOURCE_ID} has been updated using, the updated resources - * can be retrieved using {@link #getDrawable}. + * resources with IDs {@link #EXTRA_RESOURCE_ID} has been updated, the updated resources can be + * retrieved using {@link #getDrawable} and {@code #getString}. * * <p>This broadcast is sent to registered receivers only. * * <p> The following extras will be included to identify the type of resource being updated: * <ul> * <li>{@link #EXTRA_RESOURCE_TYPE_DRAWABLE} for drawable resources</li> + * <li>{@link #EXTRA_RESOURCE_TYPE_STRING} for string resources</li> * </ul> */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) @@ -3278,8 +3425,16 @@ public class DevicePolicyManager { "android.app.extra.RESOURCE_TYPE_DRAWABLE"; /** + * A boolean extra for {@link #ACTION_DEVICE_POLICY_RESOURCE_UPDATED} to indicate that a + * resource of type {@link String} is being updated. + */ + public static final String EXTRA_RESOURCE_TYPE_STRING = + "android.app.extra.RESOURCE_TYPE_STRING"; + + /** * An integer array extra for {@link #ACTION_DEVICE_POLICY_RESOURCE_UPDATED} to indicate which - * drawable IDs (see {@link DevicePolicyResources.UpdatableDrawableId}) have been updated. + * resource IDs (see {@link DevicePolicyResources.UpdatableDrawableId} and + * {@link DevicePolicyResources.UpdatableStringId}) have been updated. */ public static final String EXTRA_RESOURCE_ID = "android.app.extra.RESOURCE_ID"; @@ -14482,6 +14637,105 @@ public class DevicePolicyManager { } /** + * Called by device owner or profile owner of an organization-owned managed profile to + * specify the minimum security level required for Wi-Fi networks. + * The device may not connect to networks that do not meet the minimum security level. + * If the current network does not meet the minimum security level set, it will be disconnected. + * + * + * @param level minimum security level + * @throws SecurityException if the caller is not a device owner or a profile owner on + * an organization-owned managed profile. + */ + public void setMinimumRequiredWifiSecurityLevel(@WifiSecurity int level) { + throwIfParentInstance("setMinimumRequiredWifiSecurityLevel"); + if (mService != null) { + try { + mService.setMinimumRequiredWifiSecurityLevel(level); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + + /** + * Returns the current Wi-Fi minimum security level. + * + * @see #setMinimumRequiredWifiSecurityLevel(int) + */ + public @WifiSecurity int getMinimumRequiredWifiSecurityLevel() { + throwIfParentInstance("getMinimumRequiredWifiSecurityLevel"); + if (mService == null) { + return WIFI_SECURITY_OPEN; + } + try { + return mService.getMinimumRequiredWifiSecurityLevel(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Called by device owner or profile owner of an organization-owned managed profile to + * specify the Wi-Fi SSID policy ({@link WifiSsidPolicy}). + * Wi-Fi SSID policy specifies the SSID restriction the network must satisfy + * in order to be eligible for a connection. Providing a null policy results in the + * deactivation of the SSID restriction + * + * @param policy Wi-Fi SSID policy + * @throws SecurityException if the caller is not a device owner or a profile owner on + * an organization-owned managed profile. + */ + public void setWifiSsidPolicy(@Nullable WifiSsidPolicy policy) { + throwIfParentInstance("setWifiSsidPolicy"); + if (mService != null) { + try { + if (policy == null) { + mService.setSsidAllowlist(new ArrayList<>()); + } else { + int policyType = policy.getPolicyType(); + if (policyType == WifiSsidPolicy.WIFI_SSID_POLICY_TYPE_ALLOWLIST) { + mService.setSsidAllowlist(new ArrayList<>(policy.getSsids())); + } else { + mService.setSsidDenylist(new ArrayList<>(policy.getSsids())); + } + } + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + + /** + * Returns the current Wi-Fi SSID policy. + * If the policy has not been set, it will return NULL. + * + * @see #setWifiSsidPolicy(WifiSsidPolicy) + * @throws SecurityException if the caller is not a device owner or a profile owner on + * an organization-owned managed profile or a system app. + */ + @Nullable + public WifiSsidPolicy getWifiSsidPolicy() { + throwIfParentInstance("getWifiSsidPolicy"); + if (mService == null) { + return null; + } + try { + List<String> allowlist = mService.getSsidAllowlist(); + if (!allowlist.isEmpty()) { + return WifiSsidPolicy.createAllowlistPolicy(new ArraySet<>(allowlist)); + } + List<String> denylist = mService.getSsidDenylist(); + if (!denylist.isEmpty()) { + return WifiSsidPolicy.createDenylistPolicy(new ArraySet<>(denylist)); + } + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + return null; + } + + /** * For each {@link DevicePolicyDrawableResource} item in {@code drawables}, if * {@link DevicePolicyDrawableResource#getDrawableSource()} is not set or is set to * {@link DevicePolicyResources.Drawable.Source#UNDEFINED}, it updates the drawable resource for @@ -14514,11 +14768,6 @@ public class DevicePolicyManager { * * @param drawables The list of {@link DevicePolicyDrawableResource} to update. * - * @throws IllegalArgumentException if {@link DevicePolicyDrawableResource#getDrawableId()}, - * {@link DevicePolicyDrawableResource#getDrawableStyle()}, or - * {@link DevicePolicyDrawableResource#getDrawableSource()} aren't defined in - * {@link DevicePolicyResources.Drawable}. - * * @hide */ @SystemApi @@ -14546,9 +14795,6 @@ public class DevicePolicyManager { * * @param drawableIds The list of IDs to remove. * - * @throws IllegalArgumentException if IDs are not defined in - * {@link DevicePolicyResources.Drawable} - * * @hide */ @SystemApi @@ -14572,6 +14818,9 @@ public class DevicePolicyManager { * <p>Also returns the drawable from {@code defaultDrawableLoader} if * {@link DevicePolicyResources.Drawable#INVALID_ID} was passed. * + * <p>{@code defaultDrawableLoader} must return a non {@code null} {@link Drawable}, otherwise a + * {@link NullPointerException} is thrown. + * * <p>This API uses the screen density returned from {@link Resources#getConfiguration()}, to * set a different value use * {@link #getDrawableForDensity(int, int, int, Callable)}. @@ -14587,7 +14836,7 @@ public class DevicePolicyManager { * @param defaultDrawableLoader To get the default drawable if no updated drawable was set for * the provided params. */ - @Nullable + @NonNull public Drawable getDrawable( @DevicePolicyResources.UpdatableDrawableId int drawableId, @DevicePolicyResources.UpdatableDrawableStyle int drawableStyle, @@ -14601,6 +14850,9 @@ public class DevicePolicyManager { * could result in returning a different drawable than {@link #getDrawable(int, int, Callable)} * if an override was set for that specific source. * + * <p>{@code defaultDrawableLoader} must return a non {@code null} {@link Drawable}, otherwise a + * {@link NullPointerException} is thrown. + * * <p>Callers should register for {@link #ACTION_DEVICE_POLICY_RESOURCE_UPDATED} to get * notified when a resource has been updated. * @@ -14610,7 +14862,7 @@ public class DevicePolicyManager { * @param defaultDrawableLoader To get the default drawable if no updated drawable was set for * the provided params. */ - @Nullable + @NonNull public Drawable getDrawable( @DevicePolicyResources.UpdatableDrawableId int drawableId, @DevicePolicyResources.UpdatableDrawableStyle int drawableStyle, @@ -14648,6 +14900,9 @@ public class DevicePolicyManager { * Similar to {@link #getDrawable(int, int, Callable)}, but also accepts * {@code density}. See {@link Resources#getDrawableForDensity(int, int, Resources.Theme)}. * + * <p>{@code defaultDrawableLoader} must return a non {@code null} {@link Drawable}, otherwise a + * {@link NullPointerException} is thrown. + * * <p>Callers should register for {@link #ACTION_DEVICE_POLICY_RESOURCE_UPDATED} to get * notified when a resource has been updated. * @@ -14659,7 +14914,7 @@ public class DevicePolicyManager { * @param defaultDrawableLoader To get the default drawable if no updated drawable was set for * the provided params. */ - @Nullable + @NonNull public Drawable getDrawableForDensity( @DevicePolicyResources.UpdatableDrawableId int drawableId, @DevicePolicyResources.UpdatableDrawableStyle int drawableStyle, @@ -14677,6 +14932,9 @@ public class DevicePolicyManager { * Similar to {@link #getDrawable(int, int, int, Callable)}, but also accepts * {@code density}. See {@link Resources#getDrawableForDensity(int, int, Resources.Theme)}. * + * <p>{@code defaultDrawableLoader} must return a non {@code null} {@link Drawable}, otherwise a + * {@link NullPointerException} is thrown. + * * <p>Callers should register for {@link #ACTION_DEVICE_POLICY_RESOURCE_UPDATED} to get * notified when a resource has been updated. * @@ -14689,7 +14947,7 @@ public class DevicePolicyManager { * @param defaultDrawableLoader To get the default drawable if no updated drawable was set for * the provided params. */ - @Nullable + @NonNull public Drawable getDrawableForDensity( @DevicePolicyResources.UpdatableDrawableId int drawableId, @DevicePolicyResources.UpdatableDrawableStyle int drawableStyle, @@ -14719,4 +14977,167 @@ public class DevicePolicyManager { } return ParcelableResource.loadDefaultDrawable(defaultDrawableLoader); } + + /** + * For each {@link DevicePolicyStringResource} item in {@code strings}, it updates the string + * resource for {@link DevicePolicyStringResource#getStringId()} to the string with ID + * {@code callingPackageResourceId} (see {@link DevicePolicyResources.String}), meaning any + * system UI surface calling {@link #getString} with {@code stringId} will get + * the new resource after this API is called. + * + * <p>Sends a broadcast with action {@link #ACTION_DEVICE_POLICY_RESOURCE_UPDATED} to + * registered receivers when a resource has been updated successfully. + * + * <p>Important notes to consider when using this API: + * <ul> + * <li> {@link #getString} references the resource + * {@code callingPackageResourceId} in the calling package each time it gets called. You have to + * ensure that the resource is always available in the calling package as long as it is used as + * an updated resource. + * <li> You still have to re-call {@code setStrings} even if you only make changes to the + * content of the resource with ID {@code callingPackageResourceId} as the content might be + * cached and would need updating. + * </ul> + * + * @param strings The list of {@link DevicePolicyStringResource} to update. + * + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES) + public void setStrings(@NonNull Set<DevicePolicyStringResource> strings) { + if (mService != null) { + try { + mService.setStrings(new ArrayList<>(strings)); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + + /** + * Removes the updated strings for the list of {@code stringIds} (see + * {@link DevicePolicyResources.String}) that was previously set by calling {@link #setStrings}, + * meaning any subsequent calls to {@link #getString} for the provided IDs will + * return the default string from {@code defaultStringLoader}. + * + * <p>Sends a broadcast with action {@link #ACTION_DEVICE_POLICY_RESOURCE_UPDATED} to + * registered receivers when a resource has been reset successfully. + * + * @param stringIds The list of IDs to remove the updated resources for. + * + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES) + public void resetStrings(@NonNull String[] stringIds) { + if (mService != null) { + try { + mService.resetStrings(stringIds); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + + /** + * Returns the appropriate updated string for the {@code stringId} (see + * {@link DevicePolicyResources.String}) if one was set using + * {@link #setStrings}, otherwise returns the string from {@code defaultStringLoader}. + * + * <p>Also returns the string from {@code defaultStringLoader} if + * {@link DevicePolicyResources.String#INVALID_ID} was passed. + * + * <p>{@code defaultStringLoader} must return a non {@code null} {@link String}, otherwise a + * {@link NullPointerException} is thrown. + * + * <p>Callers should register for {@link #ACTION_DEVICE_POLICY_RESOURCE_UPDATED} to get + * notified when a resource has been updated. + * + * <p>Note that each call to this API loads the resource from the package that called + * {@link #setStrings} to set the updated resource. + * + * @param stringId The IDs to get the updated resource for. + * @param defaultStringLoader To get the default string if no updated string was set for + * {@code stringId}. + * + * @hide + */ + @SystemApi + @NonNull + public String getString( + @NonNull @DevicePolicyResources.UpdatableStringId String stringId, + @NonNull Callable<String> defaultStringLoader) { + + Objects.requireNonNull(stringId, "stringId can't be null"); + Objects.requireNonNull(defaultStringLoader, "defaultStringLoader can't be null"); + + if (stringId.equals(DevicePolicyResources.Strings.UNDEFINED)) { + return ParcelableResource.loadDefaultString(defaultStringLoader); + } + if (mService != null) { + try { + ParcelableResource resource = mService.getString(stringId); + if (resource == null) { + return ParcelableResource.loadDefaultString(defaultStringLoader); + } + return resource.getString(mContext, defaultStringLoader); + } catch (RemoteException e) { + Log.e( + TAG, + "Error getting the updated string from DevicePolicyManagerService.", + e); + return ParcelableResource.loadDefaultString(defaultStringLoader); + } + } + return ParcelableResource.loadDefaultString(defaultStringLoader); + } + + /** + * Similar to {@link #getString(String, Callable)} but accepts {@code formatArgs} and returns a + * localized formatted string, substituting the format arguments as defined in + * {@link java.util.Formatter} and {@link java.lang.String#format}, (see + * {@link Resources#getString(int, Object...)}). + * + * <p>{@code defaultStringLoader} must return a non {@code null} {@link String}, otherwise a + * {@link NullPointerException} is thrown. + * + * @param stringId The IDs to get the updated resource for. + * @param defaultStringLoader To get the default string if no updated string was set for + * {@code stringId}. + * @param formatArgs The format arguments that will be used for substitution. + * + * @hide + */ + @SystemApi + @NonNull + @SuppressLint("SamShouldBeLast") + public String getString( + @NonNull @DevicePolicyResources.UpdatableStringId String stringId, + @NonNull Callable<String> defaultStringLoader, + @NonNull Object... formatArgs) { + + Objects.requireNonNull(stringId, "stringId can't be null"); + Objects.requireNonNull(defaultStringLoader, "defaultStringLoader can't be null"); + + if (stringId.equals(DevicePolicyResources.Strings.UNDEFINED)) { + return ParcelableResource.loadDefaultString(defaultStringLoader); + } + if (mService != null) { + try { + ParcelableResource resource = mService.getString(stringId); + if (resource == null) { + return ParcelableResource.loadDefaultString(defaultStringLoader); + } + return resource.getString(mContext, defaultStringLoader, formatArgs); + } catch (RemoteException e) { + Log.e( + TAG, + "Error getting the updated string from DevicePolicyManagerService.", + e); + return ParcelableResource.loadDefaultString(defaultStringLoader); + } + } + return ParcelableResource.loadDefaultString(defaultStringLoader); + } } diff --git a/core/java/android/app/admin/DevicePolicyResources.java b/core/java/android/app/admin/DevicePolicyResources.java index 5133f26f8111..46e2cceefaf7 100644 --- a/core/java/android/app/admin/DevicePolicyResources.java +++ b/core/java/android/app/admin/DevicePolicyResources.java @@ -16,8 +16,123 @@ package android.app.admin; +import static android.app.admin.DevicePolicyResources.Strings.Core.CANT_ADD_ACCOUNT_MESSAGE; +import static android.app.admin.DevicePolicyResources.Strings.Core.FORWARD_INTENT_TO_PERSONAL; +import static android.app.admin.DevicePolicyResources.Strings.Core.FORWARD_INTENT_TO_WORK; +import static android.app.admin.DevicePolicyResources.Strings.Core.LOCATION_CHANGED_MESSAGE; +import static android.app.admin.DevicePolicyResources.Strings.Core.LOCATION_CHANGED_TITLE; +import static android.app.admin.DevicePolicyResources.Strings.Core.NETWORK_LOGGING_MESSAGE; +import static android.app.admin.DevicePolicyResources.Strings.Core.NETWORK_LOGGING_TITLE; +import static android.app.admin.DevicePolicyResources.Strings.Core.NOTIFICATION_CHANNEL_DEVICE_ADMIN; +import static android.app.admin.DevicePolicyResources.Strings.Core.NOTIFICATION_WORK_PROFILE_CONTENT_DESCRIPTION; +import static android.app.admin.DevicePolicyResources.Strings.Core.PACKAGE_DELETED_BY_DO; +import static android.app.admin.DevicePolicyResources.Strings.Core.PACKAGE_INSTALLED_BY_DO; +import static android.app.admin.DevicePolicyResources.Strings.Core.PACKAGE_UPDATED_BY_DO; +import static android.app.admin.DevicePolicyResources.Strings.Core.PERSONAL_APP_SUSPENSION_MESSAGE; +import static android.app.admin.DevicePolicyResources.Strings.Core.PERSONAL_APP_SUSPENSION_SOON_MESSAGE; +import static android.app.admin.DevicePolicyResources.Strings.Core.PERSONAL_APP_SUSPENSION_TITLE; +import static android.app.admin.DevicePolicyResources.Strings.Core.PERSONAL_APP_SUSPENSION_TURN_ON_PROFILE; +import static android.app.admin.DevicePolicyResources.Strings.Core.PRINTING_DISABLED_NAMED_ADMIN; +import static android.app.admin.DevicePolicyResources.Strings.Core.PROFILE_ENCRYPTED_DETAIL; +import static android.app.admin.DevicePolicyResources.Strings.Core.PROFILE_ENCRYPTED_MESSAGE; +import static android.app.admin.DevicePolicyResources.Strings.Core.PROFILE_ENCRYPTED_TITLE; +import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_ACCESS_PERSONAL; +import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_ACCESS_WORK; +import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_SHARE_WITH_PERSONAL; +import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_SHARE_WITH_WORK; +import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CROSS_PROFILE_BLOCKED_TITLE; +import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_NO_PERSONAL_APPS; +import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_NO_WORK_APPS; +import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_PERSONAL_TAB; +import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_PERSONAL_TAB_ACCESSIBILITY; +import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_WORK_PAUSED_TITLE; +import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_WORK_PROFILE_NOT_SUPPORTED; +import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_WORK_TAB; +import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_WORK_TAB_ACCESSIBILITY; +import static android.app.admin.DevicePolicyResources.Strings.Core.SWITCH_TO_PERSONAL_LABEL; +import static android.app.admin.DevicePolicyResources.Strings.Core.SWITCH_TO_WORK_LABEL; +import static android.app.admin.DevicePolicyResources.Strings.Core.UNLAUNCHABLE_APP_WORK_PAUSED_MESSAGE; +import static android.app.admin.DevicePolicyResources.Strings.Core.UNLAUNCHABLE_APP_WORK_PAUSED_TITLE; +import static android.app.admin.DevicePolicyResources.Strings.Core.WORK_PROFILE_BADGED_LABEL; +import static android.app.admin.DevicePolicyResources.Strings.Core.WORK_PROFILE_DELETED_FAILED_PASSWORD_ATTEMPTS_MESSAGE; +import static android.app.admin.DevicePolicyResources.Strings.Core.WORK_PROFILE_DELETED_GENERIC_MESSAGE; +import static android.app.admin.DevicePolicyResources.Strings.Core.WORK_PROFILE_DELETED_ORG_OWNED_MESSAGE; +import static android.app.admin.DevicePolicyResources.Strings.Core.WORK_PROFILE_DELETED_TITLE; +import static android.app.admin.DevicePolicyResources.Strings.DocumentsUi.CANT_SAVE_TO_PERSONAL_MESSAGE; +import static android.app.admin.DevicePolicyResources.Strings.DocumentsUi.CANT_SAVE_TO_PERSONAL_TITLE; +import static android.app.admin.DevicePolicyResources.Strings.DocumentsUi.CANT_SAVE_TO_WORK_MESSAGE; +import static android.app.admin.DevicePolicyResources.Strings.DocumentsUi.CANT_SAVE_TO_WORK_TITLE; +import static android.app.admin.DevicePolicyResources.Strings.DocumentsUi.CANT_SELECT_PERSONAL_FILES_MESSAGE; +import static android.app.admin.DevicePolicyResources.Strings.DocumentsUi.CANT_SELECT_PERSONAL_FILES_TITLE; +import static android.app.admin.DevicePolicyResources.Strings.DocumentsUi.CANT_SELECT_WORK_FILES_MESSAGE; +import static android.app.admin.DevicePolicyResources.Strings.DocumentsUi.CANT_SELECT_WORK_FILES_TITLE; +import static android.app.admin.DevicePolicyResources.Strings.DocumentsUi.CROSS_PROFILE_NOT_ALLOWED_MESSAGE; +import static android.app.admin.DevicePolicyResources.Strings.DocumentsUi.CROSS_PROFILE_NOT_ALLOWED_TITLE; +import static android.app.admin.DevicePolicyResources.Strings.DocumentsUi.PERSONAL_TAB; +import static android.app.admin.DevicePolicyResources.Strings.DocumentsUi.PREVIEW_WORK_FILE_ACCESSIBILITY; +import static android.app.admin.DevicePolicyResources.Strings.DocumentsUi.WORK_ACCESSIBILITY; +import static android.app.admin.DevicePolicyResources.Strings.DocumentsUi.WORK_PROFILE_OFF_ENABLE_BUTTON; +import static android.app.admin.DevicePolicyResources.Strings.DocumentsUi.WORK_PROFILE_OFF_ERROR_TITLE; +import static android.app.admin.DevicePolicyResources.Strings.DocumentsUi.WORK_TAB; +import static android.app.admin.DevicePolicyResources.Strings.Launcher.ALL_APPS_PERSONAL_TAB; +import static android.app.admin.DevicePolicyResources.Strings.Launcher.ALL_APPS_PERSONAL_TAB_ACCESSIBILITY; +import static android.app.admin.DevicePolicyResources.Strings.Launcher.ALL_APPS_WORK_TAB; +import static android.app.admin.DevicePolicyResources.Strings.Launcher.ALL_APPS_WORK_TAB_ACCESSIBILITY; +import static android.app.admin.DevicePolicyResources.Strings.Launcher.DISABLED_BY_ADMIN_MESSAGE; +import static android.app.admin.DevicePolicyResources.Strings.Launcher.WIDGETS_PERSONAL_TAB; +import static android.app.admin.DevicePolicyResources.Strings.Launcher.WIDGETS_WORK_TAB; +import static android.app.admin.DevicePolicyResources.Strings.Launcher.WORK_FOLDER_NAME; +import static android.app.admin.DevicePolicyResources.Strings.Launcher.WORK_PROFILE_EDU; +import static android.app.admin.DevicePolicyResources.Strings.Launcher.WORK_PROFILE_EDU_ACCEPT; +import static android.app.admin.DevicePolicyResources.Strings.Launcher.WORK_PROFILE_ENABLE_BUTTON; +import static android.app.admin.DevicePolicyResources.Strings.Launcher.WORK_PROFILE_PAUSED_DESCRIPTION; +import static android.app.admin.DevicePolicyResources.Strings.Launcher.WORK_PROFILE_PAUSE_BUTTON; +import static android.app.admin.DevicePolicyResources.Strings.MediaProvider.BLOCKED_BY_ADMIN_TITLE; +import static android.app.admin.DevicePolicyResources.Strings.MediaProvider.BLOCKED_FROM_PERSONAL_MESSAGE; +import static android.app.admin.DevicePolicyResources.Strings.MediaProvider.BLOCKED_FROM_WORK_MESSAGE; +import static android.app.admin.DevicePolicyResources.Strings.MediaProvider.SWITCH_TO_PERSONAL_MESSAGE; +import static android.app.admin.DevicePolicyResources.Strings.MediaProvider.SWITCH_TO_WORK_MESSAGE; +import static android.app.admin.DevicePolicyResources.Strings.MediaProvider.WORK_PROFILE_PAUSED_MESSAGE; +import static android.app.admin.DevicePolicyResources.Strings.SystemUi.BIOMETRIC_DIALOG_WORK_LOCK_FAILED_ATTEMPTS; +import static android.app.admin.DevicePolicyResources.Strings.SystemUi.BIOMETRIC_DIALOG_WORK_PASSWORD_LAST_ATTEMPT; +import static android.app.admin.DevicePolicyResources.Strings.SystemUi.BIOMETRIC_DIALOG_WORK_PATTERN_LAST_ATTEMPT; +import static android.app.admin.DevicePolicyResources.Strings.SystemUi.BIOMETRIC_DIALOG_WORK_PIN_LAST_ATTEMPT; +import static android.app.admin.DevicePolicyResources.Strings.SystemUi.KEYGUARD_MANAGEMENT_DISCLOSURE; +import static android.app.admin.DevicePolicyResources.Strings.SystemUi.KEYGUARD_NAMED_MANAGEMENT_DISCLOSURE; +import static android.app.admin.DevicePolicyResources.Strings.SystemUi.ONGOING_PRIVACY_DIALOG_WORK; +import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_MANAGEMENT; +import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_MANAGEMENT_CA_CERT; +import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_MANAGEMENT_NAMED_VPN; +import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_MANAGEMENT_NETWORK; +import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_MANAGEMENT_TITLE; +import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_MANAGEMENT_TWO_NAMED_VPN; +import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_NAMED_MANAGEMENT; +import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_PERSONAL_PROFILE_NAMED_VPN; +import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_VIEW_POLICIES; +import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_WORK_PROFILE_CA_CERT; +import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_WORK_PROFILE_NAMED_VPN; +import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_WORK_PROFILE_NETWORK; +import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_MANAGEMENT; +import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_MANAGEMENT_MONITORING; +import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_MANAGEMENT_MULTIPLE_VPNS; +import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_MANAGEMENT_NAMED_VPN; +import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_NAMED_MANAGEMENT; +import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_NAMED_MANAGEMENT_MONITORING; +import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_NAMED_MANAGEMENT_MULTIPLE_VPNS; +import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_NAMED_MANAGEMENT_NAMED_VPN; +import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_NAMED_WORK_PROFILE_MONITORING; +import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_PERSONAL_PROFILE_NAMED_VPN; +import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_WORK_PROFILE_MONITORING; +import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_WORK_PROFILE_NAMED_VPN; +import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_WORK_PROFILE_NETWORK; +import static android.app.admin.DevicePolicyResources.Strings.SystemUi.STATUS_BAR_WORK_ICON_ACCESSIBILITY; +import static android.app.admin.DevicePolicyResources.Strings.SystemUi.WORK_LOCK_ACCESSIBILITY; + import android.annotation.IntDef; +import android.annotation.StringDef; import android.annotation.SuppressLint; +import android.annotation.SystemApi; +import android.os.UserHandle; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -27,8 +142,8 @@ import java.util.Set; /** * Class containing the required identifiers to update device management resources. * - * <p>See {@link DevicePolicyManager#getDrawable}. - * + * <p>See {@link DevicePolicyManager#getDrawable} and + * {@code DevicePolicyManager#getString}. */ public final class DevicePolicyResources { @@ -78,6 +193,74 @@ public final class DevicePolicyResources { }) public @interface UpdatableDrawableSource {} + /** + * Resource identifiers used to update device management-related string resources. + * + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @StringDef({ + // Launcher Strings + WORK_PROFILE_EDU, WORK_PROFILE_EDU_ACCEPT, Strings.Launcher.WORK_PROFILE_PAUSED_TITLE, + WORK_PROFILE_PAUSED_DESCRIPTION, WORK_PROFILE_PAUSE_BUTTON, WORK_PROFILE_ENABLE_BUTTON, + ALL_APPS_WORK_TAB, ALL_APPS_PERSONAL_TAB, ALL_APPS_WORK_TAB_ACCESSIBILITY, + ALL_APPS_PERSONAL_TAB_ACCESSIBILITY, WORK_FOLDER_NAME, WIDGETS_WORK_TAB, + WIDGETS_PERSONAL_TAB, DISABLED_BY_ADMIN_MESSAGE, + + // SysUI Strings + QS_MSG_MANAGEMENT, QS_MSG_NAMED_MANAGEMENT, QS_MSG_MANAGEMENT_MONITORING, + QS_MSG_NAMED_MANAGEMENT_MONITORING, QS_MSG_MANAGEMENT_NAMED_VPN, + QS_MSG_NAMED_MANAGEMENT_NAMED_VPN, QS_MSG_MANAGEMENT_MULTIPLE_VPNS, + QS_MSG_NAMED_MANAGEMENT_MULTIPLE_VPNS, QS_MSG_WORK_PROFILE_MONITORING, + QS_MSG_NAMED_WORK_PROFILE_MONITORING, QS_MSG_WORK_PROFILE_NETWORK, + QS_MSG_WORK_PROFILE_NAMED_VPN, QS_MSG_PERSONAL_PROFILE_NAMED_VPN, + QS_DIALOG_MANAGEMENT_TITLE, QS_DIALOG_VIEW_POLICIES, QS_DIALOG_MANAGEMENT, + QS_DIALOG_NAMED_MANAGEMENT, QS_DIALOG_MANAGEMENT_CA_CERT, + QS_DIALOG_WORK_PROFILE_CA_CERT, QS_DIALOG_MANAGEMENT_NETWORK, + QS_DIALOG_WORK_PROFILE_NETWORK, QS_DIALOG_MANAGEMENT_NAMED_VPN, + QS_DIALOG_MANAGEMENT_TWO_NAMED_VPN, QS_DIALOG_WORK_PROFILE_NAMED_VPN, + QS_DIALOG_PERSONAL_PROFILE_NAMED_VPN, BIOMETRIC_DIALOG_WORK_PIN_LAST_ATTEMPT, + BIOMETRIC_DIALOG_WORK_PATTERN_LAST_ATTEMPT, BIOMETRIC_DIALOG_WORK_PASSWORD_LAST_ATTEMPT, + BIOMETRIC_DIALOG_WORK_LOCK_FAILED_ATTEMPTS, STATUS_BAR_WORK_ICON_ACCESSIBILITY, + ONGOING_PRIVACY_DIALOG_WORK, KEYGUARD_MANAGEMENT_DISCLOSURE, + KEYGUARD_NAMED_MANAGEMENT_DISCLOSURE, WORK_LOCK_ACCESSIBILITY, + + // Core Strings + WORK_PROFILE_DELETED_TITLE, WORK_PROFILE_DELETED_FAILED_PASSWORD_ATTEMPTS_MESSAGE, + WORK_PROFILE_DELETED_GENERIC_MESSAGE, WORK_PROFILE_DELETED_ORG_OWNED_MESSAGE, + PERSONAL_APP_SUSPENSION_TITLE, PERSONAL_APP_SUSPENSION_MESSAGE, + PERSONAL_APP_SUSPENSION_SOON_MESSAGE, PERSONAL_APP_SUSPENSION_TURN_ON_PROFILE, + PRINTING_DISABLED_NAMED_ADMIN, LOCATION_CHANGED_TITLE, LOCATION_CHANGED_MESSAGE, + NETWORK_LOGGING_TITLE, NETWORK_LOGGING_MESSAGE, + NOTIFICATION_WORK_PROFILE_CONTENT_DESCRIPTION, NOTIFICATION_CHANNEL_DEVICE_ADMIN, + SWITCH_TO_WORK_LABEL, SWITCH_TO_PERSONAL_LABEL, FORWARD_INTENT_TO_WORK, + FORWARD_INTENT_TO_PERSONAL, RESOLVER_WORK_PROFILE_NOT_SUPPORTED, RESOLVER_PERSONAL_TAB, + RESOLVER_WORK_TAB, RESOLVER_PERSONAL_TAB_ACCESSIBILITY, RESOLVER_WORK_TAB_ACCESSIBILITY, + RESOLVER_CROSS_PROFILE_BLOCKED_TITLE, RESOLVER_CANT_SHARE_WITH_PERSONAL, + RESOLVER_CANT_SHARE_WITH_WORK, RESOLVER_CANT_ACCESS_PERSONAL, RESOLVER_CANT_ACCESS_WORK, + RESOLVER_WORK_PAUSED_TITLE, RESOLVER_NO_WORK_APPS, RESOLVER_NO_PERSONAL_APPS, + CANT_ADD_ACCOUNT_MESSAGE, PACKAGE_INSTALLED_BY_DO, PACKAGE_UPDATED_BY_DO, + PACKAGE_DELETED_BY_DO, UNLAUNCHABLE_APP_WORK_PAUSED_TITLE, + UNLAUNCHABLE_APP_WORK_PAUSED_MESSAGE, PROFILE_ENCRYPTED_TITLE, PROFILE_ENCRYPTED_DETAIL, + PROFILE_ENCRYPTED_MESSAGE, WORK_PROFILE_BADGED_LABEL, + + // DocsUi Strings + WORK_PROFILE_OFF_ERROR_TITLE, WORK_PROFILE_OFF_ENABLE_BUTTON, + CANT_SELECT_WORK_FILES_TITLE, CANT_SELECT_WORK_FILES_MESSAGE, + CANT_SELECT_PERSONAL_FILES_TITLE, CANT_SELECT_PERSONAL_FILES_MESSAGE, + CANT_SAVE_TO_WORK_TITLE, CANT_SAVE_TO_WORK_MESSAGE, CANT_SAVE_TO_PERSONAL_TITLE, + CANT_SAVE_TO_PERSONAL_MESSAGE, CROSS_PROFILE_NOT_ALLOWED_TITLE, + CROSS_PROFILE_NOT_ALLOWED_MESSAGE, PREVIEW_WORK_FILE_ACCESSIBILITY, PERSONAL_TAB, + WORK_TAB, WORK_ACCESSIBILITY, + + // MediaProvider Strings + SWITCH_TO_WORK_MESSAGE, SWITCH_TO_PERSONAL_MESSAGE, BLOCKED_BY_ADMIN_TITLE, + BLOCKED_FROM_PERSONAL_MESSAGE, BLOCKED_FROM_PERSONAL_MESSAGE, + BLOCKED_FROM_WORK_MESSAGE, Strings.MediaProvider.WORK_PROFILE_PAUSED_TITLE, + WORK_PROFILE_PAUSED_MESSAGE + }) + public @interface UpdatableStringId { + } /** * Class containing the identifiers used to update device management-related system drawable. @@ -240,4 +423,995 @@ public final class DevicePolicyResources { } } } + + /** + * Class containing the identifiers used to update device management-related system strings. + * + * @hide + */ + @SystemApi + public static final class Strings { + + private Strings() {} + + /** + * An ID for any string that can't be updated. + */ + public static final String UNDEFINED = "UNDEFINED"; + + /** + * @hide + */ + public static final Set<String> UPDATABLE_STRING_IDS = buildStringsSet(); + + private static Set<String> buildStringsSet() { + Set<String> strings = new HashSet<>(); + strings.addAll(Launcher.buildStringsSet()); + strings.addAll(SystemUi.buildStringsSet()); + strings.addAll(Core.buildStringsSet()); + strings.addAll(DocumentsUi.buildStringsSet()); + strings.addAll(MediaProvider.buildStringsSet()); + return strings; + } + + /** + * Class containing the identifiers used to update device management-related system strings + * in the Launcher package. + * + * @hide + */ + public static final class Launcher { + + private Launcher(){} + + private static final String PREFIX = "Launcher."; + + /** + * User on-boarding title for work profile apps. + */ + public static final String WORK_PROFILE_EDU = PREFIX + "WORK_PROFILE_EDU"; + + /** + * Action label to finish work profile edu. + */ + public static final String WORK_PROFILE_EDU_ACCEPT = PREFIX + "WORK_PROFILE_EDU_ACCEPT"; + + /** + * Title shown when user opens work apps tab while work profile is paused. + */ + public static final String WORK_PROFILE_PAUSED_TITLE = + PREFIX + "WORK_PROFILE_PAUSED_TITLE"; + + /** + * Description shown when user opens work apps tab while work profile is paused. + */ + public static final String WORK_PROFILE_PAUSED_DESCRIPTION = + PREFIX + "WORK_PROFILE_PAUSED_DESCRIPTION"; + + /** + * Shown on the button to pause work profile. + */ + public static final String WORK_PROFILE_PAUSE_BUTTON = + PREFIX + "WORK_PROFILE_PAUSE_BUTTON"; + + /** + * Shown on the button to enable work profile. + */ + public static final String WORK_PROFILE_ENABLE_BUTTON = + PREFIX + "WORK_PROFILE_ENABLE_BUTTON"; + + /** + * Label on launcher tab to indicate work apps. + */ + public static final String ALL_APPS_WORK_TAB = PREFIX + "ALL_APPS_WORK_TAB"; + + /** + * Label on launcher tab to indicate personal apps. + */ + public static final String ALL_APPS_PERSONAL_TAB = PREFIX + "ALL_APPS_PERSONAL_TAB"; + + /** + * Accessibility description for launcher tab to indicate work apps. + */ + public static final String ALL_APPS_WORK_TAB_ACCESSIBILITY = + PREFIX + "ALL_APPS_WORK_TAB_ACCESSIBILITY"; + + /** + * Accessibility description for launcher tab to indicate personal apps. + */ + public static final String ALL_APPS_PERSONAL_TAB_ACCESSIBILITY = + PREFIX + "ALL_APPS_PERSONAL_TAB_ACCESSIBILITY"; + + /** + * Work folder name. + */ + public static final String WORK_FOLDER_NAME = PREFIX + "WORK_FOLDER_NAME"; + + /** + * Label on widget tab to indicate work app widgets. + */ + public static final String WIDGETS_WORK_TAB = PREFIX + "WIDGETS_WORK_TAB"; + + /** + * Label on widget tab to indicate personal app widgets. + */ + public static final String WIDGETS_PERSONAL_TAB = PREFIX + "WIDGETS_PERSONAL_TAB"; + + /** + * Message shown when a feature is disabled by the admin (e.g. changing wallpaper). + */ + public static final String DISABLED_BY_ADMIN_MESSAGE = + PREFIX + "DISABLED_BY_ADMIN_MESSAGE"; + + /** + * @hide + */ + static Set<String> buildStringsSet() { + Set<String> strings = new HashSet<>(); + strings.add(WORK_PROFILE_EDU); + strings.add(WORK_PROFILE_EDU_ACCEPT); + strings.add(WORK_PROFILE_PAUSED_TITLE); + strings.add(WORK_PROFILE_PAUSED_DESCRIPTION); + strings.add(WORK_PROFILE_PAUSE_BUTTON); + strings.add(WORK_PROFILE_ENABLE_BUTTON); + strings.add(ALL_APPS_WORK_TAB); + strings.add(ALL_APPS_PERSONAL_TAB); + strings.add(ALL_APPS_PERSONAL_TAB_ACCESSIBILITY); + strings.add(ALL_APPS_WORK_TAB_ACCESSIBILITY); + strings.add(WORK_FOLDER_NAME); + strings.add(WIDGETS_WORK_TAB); + strings.add(WIDGETS_PERSONAL_TAB); + strings.add(DISABLED_BY_ADMIN_MESSAGE); + return strings; + } + } + + /** + * Class containing the identifiers used to update device management-related system strings + * in the SystemUi package. + * + * @hide + */ + public static final class SystemUi { + + private SystemUi() { + } + private static final String PREFIX = "SystemUi."; + + /** + * Label in quick settings for toggling work profile on/off. + */ + public static final String QS_WORK_PROFILE_LABEL = PREFIX + "QS_WORK_PROFILE_LABEL"; + + /** + * Disclosure at the bottom of Quick Settings to indicate device management. + */ + public static final String QS_MSG_MANAGEMENT = PREFIX + "QS_MSG_MANAGEMENT"; + + /** + * Similar to {@link #QS_MSG_MANAGEMENT} but accepts the organization name as a + * param. + */ + public static final String QS_MSG_NAMED_MANAGEMENT = PREFIX + "QS_MSG_NAMED_MANAGEMENT"; + + /** + * Disclosure at the bottom of Quick Settings to indicate device management monitoring. + */ + public static final String QS_MSG_MANAGEMENT_MONITORING = + PREFIX + "QS_MSG_MANAGEMENT_MONITORING"; + + /** + * Similar to {@link #QS_MSG_MANAGEMENT_MONITORING} but accepts the + * organization name as a param. + */ + public static final String QS_MSG_NAMED_MANAGEMENT_MONITORING = + PREFIX + "QS_MSG_NAMED_MANAGEMENT_MONITORING"; + + /** + * Disclosure at the bottom of Quick Settings to indicate device management and the + * device is connected to a VPN, accepts VPN name as a param. + */ + public static final String QS_MSG_MANAGEMENT_NAMED_VPN = + PREFIX + "QS_MSG_MANAGEMENT_NAMED_VPN"; + + /** + * Similar to {@link #QS_MSG_MANAGEMENT_NAMED_VPN} but also accepts the + * organization name as a param. + */ + public static final String QS_MSG_NAMED_MANAGEMENT_NAMED_VPN = + PREFIX + "QS_MSG_NAMED_MANAGEMENT_NAMED_VPN"; + + /** + * Disclosure at the bottom of Quick Settings to indicate device management and the + * device is connected to multiple VPNs. + */ + public static final String QS_MSG_MANAGEMENT_MULTIPLE_VPNS = + PREFIX + "QS_MSG_MANAGEMENT_MULTIPLE_VPNS"; + + /** + * Similar to {@link #QS_MSG_MANAGEMENT_MULTIPLE_VPNS} but also accepts the + * organization name as a param. + */ + public static final String QS_MSG_NAMED_MANAGEMENT_MULTIPLE_VPNS = + PREFIX + "QS_MSG_NAMED_MANAGEMENT_MULTIPLE_VPNS"; + + /** + * Disclosure at the bottom of Quick Settings to indicate work profile monitoring. + */ + public static final String QS_MSG_WORK_PROFILE_MONITORING = + PREFIX + "QS_MSG_WORK_PROFILE_MONITORING"; + + /** + * Similar to {@link #QS_MSG_WORK_PROFILE_MONITORING} but accepts the + * organization name as a param. + */ + public static final String QS_MSG_NAMED_WORK_PROFILE_MONITORING = + PREFIX + "QS_MSG_NAMED_WORK_PROFILE_MONITORING"; + + /** + * Disclosure at the bottom of Quick Settings to indicate network activity is visible to + * admin. + */ + public static final String QS_MSG_WORK_PROFILE_NETWORK = + PREFIX + "QS_MSG_WORK_PROFILE_NETWORK"; + + /** + * Disclosure at the bottom of Quick Settings to indicate work profile is connected to a + * VPN, accepts VPN name as a param. + */ + public static final String QS_MSG_WORK_PROFILE_NAMED_VPN = + PREFIX + "QS_MSG_WORK_PROFILE_NAMED_VPN"; + + /** + * Disclosure at the bottom of Quick Settings to indicate personal profile is connected + * to a VPN, accepts VPN name as a param. + */ + public static final String QS_MSG_PERSONAL_PROFILE_NAMED_VPN = + PREFIX + "QS_MSG_PERSONAL_PROFILE_NAMED_VPN"; + + /** + * Title for dialog to indicate device management. + */ + public static final String QS_DIALOG_MANAGEMENT_TITLE = + PREFIX + "QS_DIALOG_MANAGEMENT_TITLE"; + + /** + * Label for button in the device management dialog to open a page with more information + * on the admin's abilities. + */ + public static final String QS_DIALOG_VIEW_POLICIES = + PREFIX + "QS_DIALOG_VIEW_POLICIES"; + + /** + * Description for device management dialog to indicate admin abilities. + */ + public static final String QS_DIALOG_MANAGEMENT = PREFIX + "QS_DIALOG_MANAGEMENT"; + + /** + * Similar to {@link #QS_DIALOG_MANAGEMENT} but accepts the organization name as a + * param. + */ + public static final String QS_DIALOG_NAMED_MANAGEMENT = + PREFIX + "QS_DIALOG_NAMED_MANAGEMENT"; + + /** + * Description for the managed device certificate authorities in the device management + * dialog. + */ + public static final String QS_DIALOG_MANAGEMENT_CA_CERT = + PREFIX + "QS_DIALOG_MANAGEMENT_CA_CERT"; + + /** + * Description for the work profile certificate authorities in the device management + * dialog. + */ + public static final String QS_DIALOG_WORK_PROFILE_CA_CERT = + PREFIX + "QS_DIALOG_WORK_PROFILE_CA_CERT"; + + /** + * Description for the managed device network logging in the device management dialog. + */ + public static final String QS_DIALOG_MANAGEMENT_NETWORK = + PREFIX + "QS_DIALOG_MANAGEMENT_NETWORK"; + + /** + * Description for the work profile network logging in the device management dialog. + */ + public static final String QS_DIALOG_WORK_PROFILE_NETWORK = + PREFIX + "QS_DIALOG_WORK_PROFILE_NETWORK"; + + /** + * Description for an active VPN in the device management dialog, accepts VPN name as a + * param. + */ + public static final String QS_DIALOG_MANAGEMENT_NAMED_VPN = + PREFIX + "QS_DIALOG_MANAGEMENT_NAMED_VPN"; + + /** + * Description for two active VPN in the device management dialog, accepts two VPN names + * as params. + */ + public static final String QS_DIALOG_MANAGEMENT_TWO_NAMED_VPN = + PREFIX + "QS_DIALOG_MANAGEMENT_TWO_NAMED_VPN"; + + /** + * Description for an active work profile VPN in the device management dialog, accepts + * VPN name as a param. + */ + public static final String QS_DIALOG_WORK_PROFILE_NAMED_VPN = + PREFIX + "QS_DIALOG_WORK_PROFILE_NAMED_VPN"; + + /** + * Description for an active personal profile VPN in the device management dialog, + * accepts VPN name as a param. + */ + public static final String QS_DIALOG_PERSONAL_PROFILE_NAMED_VPN = + PREFIX + "QS_DIALOG_PERSONAL_PROFILE_NAMED_VPN"; + + /** + * Content of a dialog shown when the user only has one attempt left to provide the + * correct pin before the work profile is removed. + */ + public static final String BIOMETRIC_DIALOG_WORK_PIN_LAST_ATTEMPT = + PREFIX + "BIOMETRIC_DIALOG_WORK_PIN_LAST_ATTEMPT"; + + /** + * Content of a dialog shown when the user only has one attempt left to provide the + * correct pattern before the work profile is removed. + */ + public static final String BIOMETRIC_DIALOG_WORK_PATTERN_LAST_ATTEMPT = + PREFIX + "BIOMETRIC_DIALOG_WORK_PATTERN_LAST_ATTEMPT"; + + /** + * Content of a dialog shown when the user only has one attempt left to provide the + * correct password before the work profile is removed. + */ + public static final String BIOMETRIC_DIALOG_WORK_PASSWORD_LAST_ATTEMPT = + PREFIX + "BIOMETRIC_DIALOG_WORK_PASSWORD_LAST_ATTEMPT"; + + /** + * Content of a dialog shown when the user has failed to provide the work lock too many + * times and the work profile is removed. + */ + public static final String BIOMETRIC_DIALOG_WORK_LOCK_FAILED_ATTEMPTS = + PREFIX + "BIOMETRIC_DIALOG_WORK_LOCK_FAILED_ATTEMPTS"; + + /** + * Accessibility label for managed profile icon in the status bar + */ + public static final String STATUS_BAR_WORK_ICON_ACCESSIBILITY = + PREFIX + "STATUS_BAR_WORK_ICON_ACCESSIBILITY"; + + /** + * Text appended to privacy dialog, indicating that the application is in the work + * profile. + */ + public static final String ONGOING_PRIVACY_DIALOG_WORK = + PREFIX + "ONGOING_PRIVACY_DIALOG_WORK"; + + /** + * Text on keyguard screen indicating device management. + */ + public static final String KEYGUARD_MANAGEMENT_DISCLOSURE = + PREFIX + "KEYGUARD_MANAGEMENT_DISCLOSURE"; + + /** + * Similar to {@link #KEYGUARD_MANAGEMENT_DISCLOSURE} but also accepts organization name + * as a param. + */ + public static final String KEYGUARD_NAMED_MANAGEMENT_DISCLOSURE = + PREFIX + "KEYGUARD_NAMED_MANAGEMENT_DISCLOSURE"; + + /** + * Content description for the work profile lock screen. + */ + public static final String WORK_LOCK_ACCESSIBILITY = PREFIX + "WORK_LOCK_ACCESSIBILITY"; + + /** + * @hide + */ + static Set<String> buildStringsSet() { + Set<String> strings = new HashSet<>(); + strings.add(QS_WORK_PROFILE_LABEL); + strings.add(QS_MSG_MANAGEMENT); + strings.add(QS_MSG_NAMED_MANAGEMENT); + strings.add(QS_MSG_MANAGEMENT_MONITORING); + strings.add(QS_MSG_NAMED_MANAGEMENT_MONITORING); + strings.add(QS_MSG_MANAGEMENT_NAMED_VPN); + strings.add(QS_MSG_NAMED_MANAGEMENT_NAMED_VPN); + strings.add(QS_MSG_MANAGEMENT_MULTIPLE_VPNS); + strings.add(QS_MSG_NAMED_MANAGEMENT_MULTIPLE_VPNS); + strings.add(QS_MSG_WORK_PROFILE_MONITORING); + strings.add(QS_MSG_NAMED_WORK_PROFILE_MONITORING); + strings.add(QS_MSG_WORK_PROFILE_NETWORK); + strings.add(QS_MSG_WORK_PROFILE_NAMED_VPN); + strings.add(QS_MSG_PERSONAL_PROFILE_NAMED_VPN); + strings.add(QS_DIALOG_MANAGEMENT_TITLE); + strings.add(QS_DIALOG_VIEW_POLICIES); + strings.add(QS_DIALOG_MANAGEMENT); + strings.add(QS_DIALOG_NAMED_MANAGEMENT); + strings.add(QS_DIALOG_MANAGEMENT_CA_CERT); + strings.add(QS_DIALOG_WORK_PROFILE_CA_CERT); + strings.add(QS_DIALOG_MANAGEMENT_NETWORK); + strings.add(QS_DIALOG_WORK_PROFILE_NETWORK); + strings.add(QS_DIALOG_MANAGEMENT_NAMED_VPN); + strings.add(QS_DIALOG_MANAGEMENT_TWO_NAMED_VPN); + strings.add(QS_DIALOG_WORK_PROFILE_NAMED_VPN); + strings.add(QS_DIALOG_PERSONAL_PROFILE_NAMED_VPN); + strings.add(BIOMETRIC_DIALOG_WORK_PIN_LAST_ATTEMPT); + strings.add(BIOMETRIC_DIALOG_WORK_PATTERN_LAST_ATTEMPT); + strings.add(BIOMETRIC_DIALOG_WORK_PASSWORD_LAST_ATTEMPT); + strings.add(BIOMETRIC_DIALOG_WORK_LOCK_FAILED_ATTEMPTS); + strings.add(STATUS_BAR_WORK_ICON_ACCESSIBILITY); + strings.add(ONGOING_PRIVACY_DIALOG_WORK); + strings.add(KEYGUARD_MANAGEMENT_DISCLOSURE); + strings.add(KEYGUARD_NAMED_MANAGEMENT_DISCLOSURE); + strings.add(WORK_LOCK_ACCESSIBILITY); + return strings; + } + } + + /** + * Class containing the identifiers used to update device management-related system strings + * in the android core package. + * + * @hide + */ + public static final class Core { + + private Core() { + } + + private static final String PREFIX = "Core."; + /** + * Notification title when the system deletes the work profile. + */ + public static final String WORK_PROFILE_DELETED_TITLE = + PREFIX + "WORK_PROFILE_DELETED_TITLE"; + + /** + * Content text for the "Work profile deleted" notification to indicates that a work + * profile has been deleted because the maximum failed password attempts as been + * reached. + */ + public static final String WORK_PROFILE_DELETED_FAILED_PASSWORD_ATTEMPTS_MESSAGE = + PREFIX + "WORK_PROFILE_DELETED_FAILED_PASSWORD_ATTEMPTS_MESSAGE"; + + /** + * Content text for the "Work profile deleted" notification to indicate that a work + * profile has been deleted. + */ + public static final String WORK_PROFILE_DELETED_GENERIC_MESSAGE = + PREFIX + "WORK_PROFILE_DELETED_GENERIC_MESSAGE"; + + /** + * Content text for the "Work profile deleted" notification to indicates that a work + * profile has been deleted because the admin of an organization-owned device has + * relinquishes it. + */ + public static final String WORK_PROFILE_DELETED_ORG_OWNED_MESSAGE = + PREFIX + "WORK_PROFILE_DELETED_ORG_OWNED_MESSAGE"; + + /** + * Notification title for when personal apps are either blocked or will be blocked + * soon due to a work policy from their admin. + */ + public static final String PERSONAL_APP_SUSPENSION_TITLE = + PREFIX + "PERSONAL_APP_SUSPENSION_TITLE"; + + /** + * Content text for the personal app suspension notification to indicate that personal + * apps are blocked due to a work policy from the admin. + */ + public static final String PERSONAL_APP_SUSPENSION_MESSAGE = + PREFIX + "PERSONAL_APP_SUSPENSION_MESSAGE"; + + /** + * Content text for the personal app suspension notification to indicate that personal + * apps will be blocked at a particular time due to a work policy from their admin. + * It also explains for how many days the profile is allowed to be off. + * <ul>Takes in the following as params: + * <li> The date that the personal apps will get suspended at</li> + * <li> The time that the personal apps will get suspended at</li> + * <li> The max allowed days for the work profile stay switched off</li> + * </ul> + */ + public static final String PERSONAL_APP_SUSPENSION_SOON_MESSAGE = + PREFIX + "PERSONAL_APP_SUSPENSION_SOON_MESSAGE"; + + /** + * Title for the button that turns work profile in the personal app suspension + * notification. + */ + public static final String PERSONAL_APP_SUSPENSION_TURN_ON_PROFILE = + PREFIX + "PERSONAL_APP_SUSPENSION_TURN_ON_PROFILE"; + + /** + * A toast message displayed when printing is attempted but disabled by policy, accepts + * admin name as a param. + */ + public static final String PRINTING_DISABLED_NAMED_ADMIN = + PREFIX + "PRINTING_DISABLED_NAMED_ADMIN"; + + /** + * Notification title to indicate that the device owner has changed the location + * settings. + */ + public static final String LOCATION_CHANGED_TITLE = PREFIX + "LOCATION_CHANGED_TITLE"; + + /** + * Content text for the location changed notification to indicate that the device owner + * has changed the location settings. + */ + public static final String LOCATION_CHANGED_MESSAGE = + PREFIX + "LOCATION_CHANGED_MESSAGE"; + + /** + * Notification title to indicate that the device is managed and network logging was + * activated by a device owner. + */ + public static final String NETWORK_LOGGING_TITLE = PREFIX + "NETWORK_LOGGING_TITLE"; + + /** + * Content text for the network logging notification to indicate that the device is + * managed and network logging was activated by a device owner. + */ + public static final String NETWORK_LOGGING_MESSAGE = PREFIX + "NETWORK_LOGGING_MESSAGE"; + + /** + * Content description of the work profile icon in the notifications. + */ + public static final String NOTIFICATION_WORK_PROFILE_CONTENT_DESCRIPTION = + PREFIX + "NOTIFICATION_WORK_PROFILE_CONTENT_DESCRIPTION"; + + /** + * Notification channel name for high-priority alerts from the user's IT admin for key + * updates about the device. + */ + public static final String NOTIFICATION_CHANNEL_DEVICE_ADMIN = + PREFIX + "NOTIFICATION_CHANNEL_DEVICE_ADMIN"; + + /** + * Label returned from + * {@link android.content.pm.CrossProfileApps#getProfileSwitchingLabel(UserHandle)} + * that calling app can show to user for the semantic of switching to work profile. + */ + public static final String SWITCH_TO_WORK_LABEL = PREFIX + "SWITCH_TO_WORK_LABEL"; + + /** + * Label returned from + * {@link android.content.pm.CrossProfileApps#getProfileSwitchingLabel(UserHandle)} + * that calling app can show to user for the semantic of switching to personal profile. + */ + public static final String SWITCH_TO_PERSONAL_LABEL = + PREFIX + "SWITCH_TO_PERSONAL_LABEL"; + + /** + * Message to show when an intent automatically switches users into the work profile. + */ + public static final String FORWARD_INTENT_TO_WORK = PREFIX + "FORWARD_INTENT_TO_WORK"; + + /** + * Message to show when an intent automatically switches users into the personal + * profile. + */ + public static final String FORWARD_INTENT_TO_PERSONAL = + PREFIX + "FORWARD_INTENT_TO_PERSONAL"; + + /** + * Text for the toast that is shown when the user clicks on a launcher that doesn't + * support the work profile, takes in the launcher name as a param. + */ + public static final String RESOLVER_WORK_PROFILE_NOT_SUPPORTED = + PREFIX + "RESOLVER_WORK_PROFILE_NOT_SUPPORTED"; + + /** + * Label for the personal tab in the {@link com.android.internal.app.ResolverActivity). + */ + public static final String RESOLVER_PERSONAL_TAB = PREFIX + "RESOLVER_PERSONAL_TAB"; + + /** + * Label for the work tab in the {@link com.android.internal.app.ResolverActivity). + */ + public static final String RESOLVER_WORK_TAB = PREFIX + "RESOLVER_WORK_TAB"; + + /** + * Accessibility Label for the personal tab in the + * {@link com.android.internal.app.ResolverActivity). + */ + public static final String RESOLVER_PERSONAL_TAB_ACCESSIBILITY = + PREFIX + "RESOLVER_PERSONAL_TAB_ACCESSIBILITY"; + + /** + * Accessibility Label for the work tab in the + * {@link com.android.internal.app.ResolverActivity). + */ + public static final String RESOLVER_WORK_TAB_ACCESSIBILITY = + PREFIX + "RESOLVER_WORK_TAB_ACCESSIBILITY"; + + /** + * Title for resolver screen to let the user know that their IT admin doesn't allow + * them to share this content across profiles. + */ + public static final String RESOLVER_CROSS_PROFILE_BLOCKED_TITLE = + PREFIX + "RESOLVER_CROSS_PROFILE_BLOCKED_TITLE"; + + /** + * Description for resolver screen to let the user know that their IT admin doesn't + * allow them to share this content with apps in their personal profile. + */ + public static final String RESOLVER_CANT_SHARE_WITH_PERSONAL = + PREFIX + "RESOLVER_CANT_SHARE_WITH_PERSONAL"; + + /** + * Description for resolver screen to let the user know that their IT admin doesn't + * allow them to share this content with apps in their work profile. + */ + public static final String RESOLVER_CANT_SHARE_WITH_WORK = + PREFIX + "RESOLVER_CANT_SHARE_WITH_WORK"; + + /** + * Description for resolver screen to let the user know that their IT admin doesn't + * allow them to open this specific content with an app in their personal profile. + */ + public static final String RESOLVER_CANT_ACCESS_PERSONAL = + PREFIX + "RESOLVER_CANT_ACCESS_PERSONAL"; + + /** + * Description for resolver screen to let the user know that their IT admin doesn't + * allow them to open this specific content with an app in their work profile. + */ + public static final String RESOLVER_CANT_ACCESS_WORK = + PREFIX + "RESOLVER_CANT_ACCESS_WORK"; + + /** + * Title for resolver screen to let the user know that they need to turn on work apps + * in order to share or open content + */ + public static final String RESOLVER_WORK_PAUSED_TITLE = + PREFIX + "RESOLVER_WORK_PAUSED_TITLE"; + + /** + * Text on resolver screen to let the user know that their current work apps don't + * support the specific content. + */ + public static final String RESOLVER_NO_WORK_APPS = PREFIX + "RESOLVER_NO_WORK_APPS"; + + /** + * Text on resolver screen to let the user know that their current personal apps don't + * support the specific content. + */ + public static final String RESOLVER_NO_PERSONAL_APPS = + PREFIX + "RESOLVER_NO_PERSONAL_APPS"; + + /** + * Message informing user that the adding the account is disallowed by an administrator. + */ + public static final String CANT_ADD_ACCOUNT_MESSAGE = + PREFIX + "CANT_ADD_ACCOUNT_MESSAGE"; + + /** + * Notification shown when device owner silently installs a package. + */ + public static final String PACKAGE_INSTALLED_BY_DO = PREFIX + "PACKAGE_INSTALLED_BY_DO"; + + /** + * Notification shown when device owner silently updates a package. + */ + public static final String PACKAGE_UPDATED_BY_DO = PREFIX + "PACKAGE_UPDATED_BY_DO"; + + /** + * Notification shown when device owner silently deleted a package. + */ + public static final String PACKAGE_DELETED_BY_DO = PREFIX + "PACKAGE_DELETED_BY_DO"; + + /** + * Title for dialog shown when user tries to open a work app when the work profile is + * turned off, confirming that the user wants to turn on access to their + * work apps. + */ + public static final String UNLAUNCHABLE_APP_WORK_PAUSED_TITLE = + PREFIX + "UNLAUNCHABLE_APP_WORK_PAUSED_TITLE"; + + /** + * Text for dialog shown when user tries to open a work app when the work profile is + * turned off, confirming that the user wants to turn on access to their + * work apps. + */ + public static final String UNLAUNCHABLE_APP_WORK_PAUSED_MESSAGE = + PREFIX + "UNLAUNCHABLE_APP_WORK_PAUSED_MESSAGE"; + + /** + * Notification title shown when work profile is credential encrypted and requires + * the user to unlock before it's usable. + */ + public static final String PROFILE_ENCRYPTED_TITLE = PREFIX + "PROFILE_ENCRYPTED_TITLE"; + + /** + * Notification detail shown when work profile is credential encrypted and requires + * the user to unlock before it's usable. + */ + public static final String PROFILE_ENCRYPTED_DETAIL = + PREFIX + "PROFILE_ENCRYPTED_DETAIL"; + + /** + * Notification message shown when work profile is credential encrypted and requires + * the user to unlock before it's usable. + */ + public static final String PROFILE_ENCRYPTED_MESSAGE = + PREFIX + "PROFILE_ENCRYPTED_MESSAGE"; + + /** + * Used to badge a string with "Work" for work profile content, e.g. "Work Email". + * Accepts the string to badge as an argument. + * <p>See {@link android.content.pm.PackageManager#getUserBadgedLabel}</p> + */ + public static final String WORK_PROFILE_BADGED_LABEL = + PREFIX + "WORK_PROFILE_BADGED_LABEL"; + + /** + * @hide + */ + static Set<String> buildStringsSet() { + Set<String> strings = new HashSet<>(); + strings.add(WORK_PROFILE_DELETED_TITLE); + strings.add(WORK_PROFILE_DELETED_FAILED_PASSWORD_ATTEMPTS_MESSAGE); + strings.add(WORK_PROFILE_DELETED_GENERIC_MESSAGE); + strings.add(WORK_PROFILE_DELETED_ORG_OWNED_MESSAGE); + strings.add(PERSONAL_APP_SUSPENSION_TITLE); + strings.add(PERSONAL_APP_SUSPENSION_MESSAGE); + strings.add(PERSONAL_APP_SUSPENSION_SOON_MESSAGE); + strings.add(PERSONAL_APP_SUSPENSION_TURN_ON_PROFILE); + strings.add(PRINTING_DISABLED_NAMED_ADMIN); + strings.add(LOCATION_CHANGED_TITLE); + strings.add(LOCATION_CHANGED_MESSAGE); + strings.add(NETWORK_LOGGING_TITLE); + strings.add(NETWORK_LOGGING_MESSAGE); + strings.add(NOTIFICATION_WORK_PROFILE_CONTENT_DESCRIPTION); + strings.add(NOTIFICATION_CHANNEL_DEVICE_ADMIN); + strings.add(SWITCH_TO_WORK_LABEL); + strings.add(SWITCH_TO_PERSONAL_LABEL); + strings.add(FORWARD_INTENT_TO_WORK); + strings.add(FORWARD_INTENT_TO_PERSONAL); + strings.add(RESOLVER_WORK_PROFILE_NOT_SUPPORTED); + strings.add(RESOLVER_PERSONAL_TAB); + strings.add(RESOLVER_WORK_TAB); + strings.add(RESOLVER_PERSONAL_TAB_ACCESSIBILITY); + strings.add(RESOLVER_WORK_TAB_ACCESSIBILITY); + strings.add(RESOLVER_CROSS_PROFILE_BLOCKED_TITLE); + strings.add(RESOLVER_CANT_SHARE_WITH_PERSONAL); + strings.add(RESOLVER_CANT_SHARE_WITH_WORK); + strings.add(RESOLVER_CANT_ACCESS_PERSONAL); + strings.add(RESOLVER_CANT_ACCESS_WORK); + strings.add(RESOLVER_WORK_PAUSED_TITLE); + strings.add(RESOLVER_NO_WORK_APPS); + strings.add(RESOLVER_NO_PERSONAL_APPS); + strings.add(CANT_ADD_ACCOUNT_MESSAGE); + strings.add(PACKAGE_INSTALLED_BY_DO); + strings.add(PACKAGE_UPDATED_BY_DO); + strings.add(PACKAGE_DELETED_BY_DO); + strings.add(UNLAUNCHABLE_APP_WORK_PAUSED_TITLE); + strings.add(UNLAUNCHABLE_APP_WORK_PAUSED_MESSAGE); + strings.add(PROFILE_ENCRYPTED_TITLE); + strings.add(PROFILE_ENCRYPTED_DETAIL); + strings.add(PROFILE_ENCRYPTED_MESSAGE); + strings.add(WORK_PROFILE_BADGED_LABEL); + return strings; + } + } + + /** + * Class containing the identifiers used to update device management-related system strings + * in the DocumentsUi package. + */ + public static final class DocumentsUi { + + private DocumentsUi() { + } + + private static final String PREFIX = "DocumentsUi."; + + /** + * Title for error message shown when work profile is turned off. + */ + public static final String WORK_PROFILE_OFF_ERROR_TITLE = + PREFIX + "WORK_PROFILE_OFF_ERROR_TITLE"; + + /** + * Button text shown when work profile is turned off. + */ + public static final String WORK_PROFILE_OFF_ENABLE_BUTTON = + PREFIX + "WORK_PROFILE_OFF_ENABLE_BUTTON"; + + /** + * Title for error message shown when a user's IT admin does not allow the user to + * select work files from a personal app. + */ + public static final String CANT_SELECT_WORK_FILES_TITLE = + PREFIX + "CANT_SELECT_WORK_FILES_TITLE"; + + /** + * Message shown when a user's IT admin does not allow the user to select work files + * from a personal app. + */ + public static final String CANT_SELECT_WORK_FILES_MESSAGE = + PREFIX + "CANT_SELECT_WORK_FILES_MESSAGE"; + + /** + * Title for error message shown when a user's IT admin does not allow the user to + * select personal files from a work app. + */ + public static final String CANT_SELECT_PERSONAL_FILES_TITLE = + PREFIX + "CANT_SELECT_PERSONAL_FILES_TITLE"; + + /** + * Message shown when a user's IT admin does not allow the user to select personal files + * from a work app. + */ + public static final String CANT_SELECT_PERSONAL_FILES_MESSAGE = + PREFIX + "CANT_SELECT_PERSONAL_FILES_MESSAGE"; + + /** + * Title for error message shown when a user's IT admin does not allow the user to save + * files from their personal profile to their work profile. + */ + public static final String CANT_SAVE_TO_WORK_TITLE = + PREFIX + "CANT_SAVE_TO_WORK_TITLE"; + + /** + * Message shown when a user's IT admin does not allow the user to save files from their + * personal profile to their work profile. + */ + public static final String CANT_SAVE_TO_WORK_MESSAGE = + PREFIX + "CANT_SAVE_TO_WORK_MESSAGE"; + + /** + * Title for error message shown when a user's IT admin does not allow the user to save + * files from their work profile to their personal profile. + */ + public static final String CANT_SAVE_TO_PERSONAL_TITLE = + PREFIX + "CANT_SAVE_TO_PERSONAL_TITLE"; + + /** + * Message shown when a user's IT admin does not allow the user to save files from their + * work profile to their personal profile. + */ + public static final String CANT_SAVE_TO_PERSONAL_MESSAGE = + PREFIX + "CANT_SAVE_TO_PERSONAL_MESSAGE"; + + /** + * Title for error message shown when a user tries to do something on their work + * device, but that action isn't allowed by their IT admin. + */ + public static final String CROSS_PROFILE_NOT_ALLOWED_TITLE = + PREFIX + "CROSS_PROFILE_NOT_ALLOWED_TITLE"; + + /** + * Message shown when a user tries to do something on their work device, but that action + * isn't allowed by their IT admin. + */ + public static final String CROSS_PROFILE_NOT_ALLOWED_MESSAGE = + PREFIX + "CROSS_PROFILE_NOT_ALLOWED_MESSAGE"; + + /** + * Content description text that's spoken by a screen reader for previewing a work file + * before opening it. Accepts file name as a param. + */ + public static final String PREVIEW_WORK_FILE_ACCESSIBILITY = + PREFIX + "PREVIEW_WORK_FILE_ACCESSIBILITY"; + + /** + * Label for tab and sidebar to indicate personal content. + */ + public static final String PERSONAL_TAB = PREFIX + "PERSONAL_TAB"; + + /** + * Label for tab and sidebar tab to indicate work content + */ + public static final String WORK_TAB = PREFIX + "WORK_TAB"; + + /** + * Accessibility label to indicate the subject(e.g. file/folder) is from work profile. + */ + public static final String WORK_ACCESSIBILITY = PREFIX + "WORK_ACCESSIBILITY"; + + /** + * @hide + */ + static Set<String> buildStringsSet() { + Set<String> strings = new HashSet<>(); + strings.add(WORK_PROFILE_OFF_ERROR_TITLE); + strings.add(WORK_PROFILE_OFF_ENABLE_BUTTON); + strings.add(CANT_SELECT_WORK_FILES_TITLE); + strings.add(CANT_SELECT_WORK_FILES_MESSAGE); + strings.add(CANT_SELECT_PERSONAL_FILES_TITLE); + strings.add(CANT_SELECT_PERSONAL_FILES_MESSAGE); + strings.add(CANT_SAVE_TO_WORK_TITLE); + strings.add(CANT_SAVE_TO_WORK_MESSAGE); + strings.add(CANT_SAVE_TO_PERSONAL_TITLE); + strings.add(CANT_SAVE_TO_PERSONAL_MESSAGE); + strings.add(CROSS_PROFILE_NOT_ALLOWED_TITLE); + strings.add(CROSS_PROFILE_NOT_ALLOWED_MESSAGE); + strings.add(PREVIEW_WORK_FILE_ACCESSIBILITY); + strings.add(PERSONAL_TAB); + strings.add(WORK_TAB); + strings.add(WORK_ACCESSIBILITY); + return strings; + } + } + + /** + * Class containing the identifiers used to update device management-related system strings + * in the MediaProvider module. + */ + public static final class MediaProvider { + + private MediaProvider() { + } + + private static final String PREFIX = "MediaProvider."; + + /** + * The text shown to switch to the work profile in PhotoPicker. + */ + public static final String SWITCH_TO_WORK_MESSAGE = + PREFIX + "SWITCH_TO_WORK_MESSAGE"; + + /** + * The text shown to switch to the personal profile in PhotoPicker. + */ + public static final String SWITCH_TO_PERSONAL_MESSAGE = + PREFIX + "SWITCH_TO_PERSONAL_MESSAGE"; + + /** + * The title for error dialog in PhotoPicker when the admin blocks cross user + * interaction for the intent. + */ + public static final String BLOCKED_BY_ADMIN_TITLE = + PREFIX + "BLOCKED_BY_ADMIN_TITLE"; + + /** + * The message for error dialog in PhotoPicker when the admin blocks cross user + * interaction from the personal profile. + */ + public static final String BLOCKED_FROM_PERSONAL_MESSAGE = + PREFIX + "BLOCKED_FROM_PERSONAL_MESSAGE"; + + /** + * The message for error dialog in PhotoPicker when the admin blocks cross user + * interaction from the work profile. + */ + public static final String BLOCKED_FROM_WORK_MESSAGE = + PREFIX + "BLOCKED_FROM_WORK_MESSAGE"; + + /** + * The title of the error dialog in PhotoPicker when the user tries to switch to work + * content, but work profile is off. + */ + public static final String WORK_PROFILE_PAUSED_TITLE = + PREFIX + "WORK_PROFILE_PAUSED_TITLE"; + + /** + * The message of the error dialog in PhotoPicker when the user tries to switch to work + * content, but work profile is off. + */ + public static final String WORK_PROFILE_PAUSED_MESSAGE = + PREFIX + "WORK_PROFILE_PAUSED_MESSAGE"; + + /** + * @hide + */ + static Set<String> buildStringsSet() { + Set<String> strings = new HashSet<>(); + strings.add(SWITCH_TO_WORK_MESSAGE); + strings.add(SWITCH_TO_PERSONAL_MESSAGE); + strings.add(BLOCKED_BY_ADMIN_TITLE); + strings.add(BLOCKED_FROM_PERSONAL_MESSAGE); + strings.add(BLOCKED_FROM_WORK_MESSAGE); + strings.add(WORK_PROFILE_PAUSED_TITLE); + strings.add(WORK_PROFILE_PAUSED_MESSAGE); + return strings; + } + } + } } diff --git a/core/java/android/app/admin/DevicePolicyStringResource.aidl b/core/java/android/app/admin/DevicePolicyStringResource.aidl new file mode 100644 index 000000000000..13b0b958027b --- /dev/null +++ b/core/java/android/app/admin/DevicePolicyStringResource.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.admin; + +parcelable DevicePolicyStringResource; diff --git a/core/java/android/app/admin/DevicePolicyStringResource.java b/core/java/android/app/admin/DevicePolicyStringResource.java new file mode 100644 index 000000000000..5f09bfdcf331 --- /dev/null +++ b/core/java/android/app/admin/DevicePolicyStringResource.java @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.admin; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.StringRes; +import android.annotation.SystemApi; +import android.content.Context; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Objects; + +/** + * Used to pass in the required information for updating an enterprise string resource using + * {@link DevicePolicyManager#setStrings}. + * + * @hide + */ +@SystemApi +public final class DevicePolicyStringResource implements Parcelable { + @NonNull private final @DevicePolicyResources.UpdatableStringId String mStringId; + private final @StringRes int mCallingPackageResourceId; + @NonNull private ParcelableResource mResource; + + /** + * Creates an object containing the required information for updating an enterprise string + * resource using {@link DevicePolicyManager#setStrings}. + * + * <p>It will be used to update the string defined by {@code stringId} to the string with ID + * {@code callingPackageResourceId} in the calling package</p> + * + * @param stringId The ID of the string to update. + * @param callingPackageResourceId The ID of the {@link StringRes} in the calling package to + * use as an updated resource. + * + * @throws IllegalStateException if the resource with ID {@code callingPackageResourceId} + * doesn't exist in the {@code context} package. + */ + public DevicePolicyStringResource( + @NonNull Context context, + @NonNull @DevicePolicyResources.UpdatableStringId String stringId, + @StringRes int callingPackageResourceId) { + this(stringId, callingPackageResourceId, new ParcelableResource( + context, callingPackageResourceId, ParcelableResource.RESOURCE_TYPE_STRING)); + } + + private DevicePolicyStringResource( + @NonNull @DevicePolicyResources.UpdatableStringId String stringId, + @StringRes int callingPackageResourceId, + @NonNull ParcelableResource resource) { + Objects.requireNonNull(stringId, "stringId must be provided."); + Objects.requireNonNull(resource, "ParcelableResource must be provided."); + + this.mStringId = stringId; + this.mCallingPackageResourceId = callingPackageResourceId; + this.mResource = resource; + } + + /** + * Returns the ID of the string to update. + */ + @DevicePolicyResources.UpdatableStringId + @NonNull + public String getStringId() { + return mStringId; + } + + /** + * Returns the ID of the {@link StringRes} in the calling package to use as an updated + * resource. + */ + public int getCallingPackageResourceId() { + return mCallingPackageResourceId; + } + + /** + * Returns the {@link ParcelableResource} of the string. + * + * @hide + */ + @NonNull + public ParcelableResource getResource() { + return mResource; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + DevicePolicyStringResource other = (DevicePolicyStringResource) o; + return mStringId == other.mStringId + && mCallingPackageResourceId == other.mCallingPackageResourceId + && mResource.equals(other.mResource); + } + + @Override + public int hashCode() { + return Objects.hash(mStringId, mCallingPackageResourceId, mResource); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeString(mStringId); + dest.writeInt(mCallingPackageResourceId); + dest.writeTypedObject(mResource, flags); + } + + public static final @NonNull Creator<DevicePolicyStringResource> CREATOR = + new Creator<DevicePolicyStringResource>() { + @Override + public DevicePolicyStringResource createFromParcel(Parcel in) { + String stringId = in.readString(); + int callingPackageResourceId = in.readInt(); + ParcelableResource resource = in.readTypedObject(ParcelableResource.CREATOR); + + return new DevicePolicyStringResource(stringId, callingPackageResourceId, resource); + } + + @Override + public DevicePolicyStringResource[] newArray(int size) { + return new DevicePolicyStringResource[size]; + } + }; +} diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index 832008755451..f663c17c7884 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -18,6 +18,7 @@ package android.app.admin; import android.app.admin.DevicePolicyDrawableResource; +import android.app.admin.DevicePolicyStringResource; import android.app.admin.ParcelableResource; import android.app.admin.NetworkEvent; import android.app.IApplicationThread; @@ -532,8 +533,20 @@ interface IDevicePolicyManager { boolean isUsbDataSignalingEnabledForUser(int userId); boolean canUsbDataSignalingBeDisabled(); + void setMinimumRequiredWifiSecurityLevel(int level); + int getMinimumRequiredWifiSecurityLevel(); + + void setSsidAllowlist(in List<String> ssids); + List<String> getSsidAllowlist(); + void setSsidDenylist(in List<String> ssids); + List<String> getSsidDenylist(); + List<UserHandle> listForegroundAffiliatedUsers(); - void setDrawables(in List<DevicePolicyDrawableResource> resource); + void setDrawables(in List<DevicePolicyDrawableResource> drawables); void resetDrawables(in int[] drawableIds); ParcelableResource getDrawable(int drawableId, int drawableStyle, int drawableSource); + + void setStrings(in List<DevicePolicyStringResource> strings); + void resetStrings(in String[] stringIds); + ParcelableResource getString(String stringId); } diff --git a/core/java/android/app/admin/ParcelableResource.java b/core/java/android/app/admin/ParcelableResource.java index e5171622be4a..dba362820b1d 100644 --- a/core/java/android/app/admin/ParcelableResource.java +++ b/core/java/android/app/admin/ParcelableResource.java @@ -43,7 +43,7 @@ import java.util.concurrent.Callable; /** * Used to store the required information to load a resource that was updated using - * {@link DevicePolicyManager#setDrawables}. + * {@link DevicePolicyManager#setDrawables} and {@link DevicePolicyManager#setStrings}. * * @hide */ @@ -57,10 +57,13 @@ public final class ParcelableResource implements Parcelable { private static final String ATTR_RESOURCE_TYPE = "resource-type"; public static final int RESOURCE_TYPE_DRAWABLE = 1; + public static final int RESOURCE_TYPE_STRING = 2; + @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = { "RESOURCE_TYPE_" }, value = { - RESOURCE_TYPE_DRAWABLE + RESOURCE_TYPE_DRAWABLE, + RESOURCE_TYPE_STRING }) public @interface ResourceType {} @@ -78,13 +81,11 @@ public final class ParcelableResource implements Parcelable { * resource * @param resourceId of the resource to use as an updated resource * @param resourceType see {@link ResourceType} - * @throws IllegalArgumentException if the given {@code resourceId} doesn't exist in the - * {@link Context#getResources()} of the given {@code context} */ - public ParcelableResource(@NonNull Context context, @AnyRes int resourceId, - @ResourceType int resourceType) throws IllegalArgumentException { + public ParcelableResource( + @NonNull Context context, @AnyRes int resourceId, @ResourceType int resourceType) + throws IllegalStateException, IllegalArgumentException { Objects.requireNonNull(context, "context must be provided"); - verifyResourceExistsInCallingPackage(context, resourceId, resourceType); this.mResourceId = resourceId; @@ -108,25 +109,41 @@ public final class ParcelableResource implements Parcelable { private static void verifyResourceExistsInCallingPackage( Context context, @AnyRes int resourceId, @ResourceType int resourceType) - throws IllegalArgumentException { + throws IllegalStateException, IllegalArgumentException { switch (resourceType) { case RESOURCE_TYPE_DRAWABLE: if (!hasDrawableInCallingPackage(context, resourceId)) { - throw new IllegalArgumentException(String.format( + throw new IllegalStateException(String.format( "Drawable with id %d doesn't exist in the calling package %s", resourceId, context.getPackageName())); } break; + case RESOURCE_TYPE_STRING: + if (!hasStringInCallingPackage(context, resourceId)) { + throw new IllegalStateException(String.format( + "String with id %d doesn't exist in the calling package %s", + resourceId, + context.getPackageName())); + } + break; default: throw new IllegalArgumentException( - "Unknown ParcelableDevicePolicyResourceType: " + resourceType); + "Unknown ResourceType: " + resourceType); } } private static boolean hasDrawableInCallingPackage(Context context, @AnyRes int resourceId) { try { - return context.getDrawable(resourceId) != null; + return "drawable".equals(context.getResources().getResourceTypeName(resourceId)); + } catch (Resources.NotFoundException e) { + return false; + } + } + + private static boolean hasStringInCallingPackage(Context context, @AnyRes int resourceId) { + try { + return "string".equals(context.getResources().getResourceTypeName(resourceId)); } catch (Resources.NotFoundException e) { return false; } @@ -158,7 +175,7 @@ public final class ParcelableResource implements Parcelable { * <p>Returns the default drawable by calling the {@code defaultDrawableLoader} if the updated * drawable was not found or could not be loaded.</p> */ - @Nullable + @NonNull public Drawable getDrawable( Context context, int density, @@ -175,6 +192,59 @@ public final class ParcelableResource implements Parcelable { } } + /** + * Loads the string with id {@code mResourceId} from {@code mPackageName} using the + * configuration returned from {@link Resources#getConfiguration} of the provided + * {@code context}. + * + * <p>Returns the default string by calling {@code defaultStringLoader} if the updated + * string was not found or could not be loaded.</p> + */ + @NonNull + public String getString( + Context context, + @NonNull Callable<String> defaultStringLoader) { + // TODO(b/203548565): properly handle edge case when the device manager role holder is + // unavailable because it's being updated. + try { + Resources resources = getAppResourcesWithCallersConfiguration(context); + verifyResourceName(resources); + return resources.getString(mResourceId); + } catch (PackageManager.NameNotFoundException | RuntimeException e) { + Slog.e(TAG, "Unable to load string resource " + mResourceName, e); + return loadDefaultString(defaultStringLoader); + } + } + + /** + * Loads the string with id {@code mResourceId} from {@code mPackageName} using the + * configuration returned from {@link Resources#getConfiguration} of the provided + * {@code context}. + * + * <p>Returns the default string by calling {@code defaultStringLoader} if the updated + * string was not found or could not be loaded.</p> + */ + @Nullable + public String getString( + Context context, + @NonNull Callable<String> defaultStringLoader, + @NonNull Object... formatArgs) { + // TODO(b/203548565): properly handle edge case when the device manager role holder is + // unavailable because it's being updated. + try { + Resources resources = getAppResourcesWithCallersConfiguration(context); + verifyResourceName(resources); + String rawString = resources.getString(mResourceId); + return String.format( + context.getResources().getConfiguration().getLocales().get(0), + rawString, + formatArgs); + } catch (PackageManager.NameNotFoundException | RuntimeException e) { + Slog.e(TAG, "Unable to load string resource " + mResourceName, e); + return loadDefaultString(defaultStringLoader); + } + } + private Resources getAppResourcesWithCallersConfiguration(Context context) throws PackageManager.NameNotFoundException { PackageManager pm = context.getPackageManager(); @@ -195,15 +265,40 @@ public final class ParcelableResource implements Parcelable { } /** - * returns the {@link Drawable} loaded from calling - * {@code defaultDrawableLoader}. + * returns the {@link Drawable} loaded from calling {@code defaultDrawableLoader}. */ - public static Drawable loadDefaultDrawable( - @NonNull Callable<Drawable> defaultDrawableLoader) { + @NonNull + public static Drawable loadDefaultDrawable(@NonNull Callable<Drawable> defaultDrawableLoader) { + try { + Objects.requireNonNull(defaultDrawableLoader, "defaultDrawableLoader can't be null"); + + Drawable drawable = defaultDrawableLoader.call(); + Objects.requireNonNull(drawable, "defaultDrawable can't be null"); + + return drawable; + } catch (NullPointerException rethrown) { + throw rethrown; + } catch (Exception e) { + throw new RuntimeException("Couldn't load default drawable: ", e); + } + } + + /** + * returns the {@link String} loaded from calling {@code defaultStringLoader}. + */ + @NonNull + public static String loadDefaultString(@NonNull Callable<String> defaultStringLoader) { try { - return defaultDrawableLoader.call(); + Objects.requireNonNull(defaultStringLoader, "defaultStringLoader can't be null"); + + String string = defaultStringLoader.call(); + Objects.requireNonNull(string, "defaultString can't be null"); + + return string; + } catch (NullPointerException rethrown) { + throw rethrown; } catch (Exception e) { - throw new RuntimeException("Couldn't load default drawable", e); + throw new RuntimeException("Couldn't load default string: ", e); } } diff --git a/core/java/android/app/admin/WifiSsidPolicy.java b/core/java/android/app/admin/WifiSsidPolicy.java new file mode 100644 index 000000000000..37150179cc68 --- /dev/null +++ b/core/java/android/app/admin/WifiSsidPolicy.java @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.admin; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.ArraySet; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Set; + +/** + * Used to indicate the Wi-Fi SSID restriction policy the network must satisfy + * in order to be eligible for a connection. + * + * If the policy type is a denylist, the device may not connect to networks on the denylist. + * If the policy type is an allowlist, the device may only connect to networks on the allowlist. + * Admin configured networks are not exempt from this restriction. + * This policy only prohibits connecting to a restricted network and + * does not affect adding a restricted network. + * If the current network is present in the denylist or not present in the allowlist, + * it will be disconnected. + */ +public final class WifiSsidPolicy implements Parcelable { + /** + * SSID policy type indicator for {@link WifiSsidPolicy}. + * + * <p> When returned from {@link WifiSsidPolicy#getPolicyType()}, the constant + * indicates that the SSID policy type is an allowlist. + * + * @see #WIFI_SSID_POLICY_TYPE_DENYLIST + */ + public static final int WIFI_SSID_POLICY_TYPE_ALLOWLIST = 0; + + /** + * SSID policy type indicator for {@link WifiSsidPolicy}. + * + * <p> When returned from {@link WifiSsidPolicy#getPolicyType()}, the constant + * indicates that the SSID policy type is a denylist. + * + * @see #WIFI_SSID_POLICY_TYPE_ALLOWLIST + */ + public static final int WIFI_SSID_POLICY_TYPE_DENYLIST = 1; + + /** + * Possible SSID policy types + * + * @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = {"WIFI_SSID_POLICY_TYPE_"}, value = { + WIFI_SSID_POLICY_TYPE_ALLOWLIST, + WIFI_SSID_POLICY_TYPE_DENYLIST}) + public @interface WifiSsidPolicyType {} + + private @WifiSsidPolicyType int mPolicyType; + private ArraySet<String> mSsids; + + private WifiSsidPolicy(@WifiSsidPolicyType int policyType, @NonNull Set<String> ssids) { + mPolicyType = policyType; + mSsids = new ArraySet<>(ssids); + } + + private WifiSsidPolicy(Parcel in) { + mPolicyType = in.readInt(); + mSsids = (ArraySet<String>) in.readArraySet(null); + } + /** + * Create the allowlist Wi-Fi SSID Policy. + * + * @param ssids allowlist of SSIDs in UTF-8 without double quotes format + * @throws IllegalArgumentException if the input ssids list is empty + */ + @NonNull + public static WifiSsidPolicy createAllowlistPolicy(@NonNull Set<String> ssids) { + if (ssids.isEmpty()) { + throw new IllegalArgumentException("SSID list cannot be empty"); + } + return new WifiSsidPolicy(WIFI_SSID_POLICY_TYPE_ALLOWLIST, ssids); + } + + /** + * Create the denylist Wi-Fi SSID Policy. + * + * @param ssids denylist of SSIDs in UTF-8 without double quotes format + * @throws IllegalArgumentException if the input ssids list is empty + */ + @NonNull + public static WifiSsidPolicy createDenylistPolicy(@NonNull Set<String> ssids) { + if (ssids.isEmpty()) { + throw new IllegalArgumentException("SSID list cannot be empty"); + } + return new WifiSsidPolicy(WIFI_SSID_POLICY_TYPE_DENYLIST, ssids); + } + + /** + * Returns the set of SSIDs in UTF-8 without double quotes format. + */ + @NonNull + public Set<String> getSsids() { + return mSsids; + } + + /** + * Returns the policy type. + */ + public @WifiSsidPolicyType int getPolicyType() { + return mPolicyType; + } + + /** + * @see Parcelable.Creator + */ + @NonNull + public static final Creator<WifiSsidPolicy> CREATOR = new Creator<WifiSsidPolicy>() { + @Override + public WifiSsidPolicy createFromParcel(Parcel source) { + return new WifiSsidPolicy(source); + } + + @Override + public WifiSsidPolicy[] newArray(int size) { + return new WifiSsidPolicy[size]; + } + }; + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mPolicyType); + dest.writeArraySet(mSsids); + } + + @Override + public int describeContents() { + return 0; + } +} diff --git a/core/java/android/app/ambientcontext/AmbientContextEvent.aidl b/core/java/android/app/ambientcontext/AmbientContextEvent.aidl new file mode 100644 index 000000000000..0965b1a3f013 --- /dev/null +++ b/core/java/android/app/ambientcontext/AmbientContextEvent.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.ambientcontext; + +parcelable AmbientContextEvent; diff --git a/core/java/android/app/ambientcontext/AmbientContextEvent.java b/core/java/android/app/ambientcontext/AmbientContextEvent.java new file mode 100644 index 000000000000..11e695ad7fad --- /dev/null +++ b/core/java/android/app/ambientcontext/AmbientContextEvent.java @@ -0,0 +1,492 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.ambientcontext; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.os.Parcelable; + +import com.android.internal.util.DataClass; +import com.android.internal.util.Parcelling; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.time.Instant; + + +/** + * Represents a detected ambient event. Each event has a type, start time, end time, + * plus some optional data. + * + * @hide + */ +@SystemApi +@DataClass( + genBuilder = true, + genConstructor = false, + genHiddenConstDefs = true, + genParcelable = true, + genToString = true +) +public final class AmbientContextEvent implements Parcelable { + /** + * The integer indicating an unknown event was detected. + */ + public static final int EVENT_UNKNOWN = 0; + + /** + * The integer indicating a cough event was detected. + */ + public static final int EVENT_COUGH = 1; + + /** + * The integer indicating a snore event was detected. + */ + public static final int EVENT_SNORE = 2; + + /** @hide */ + @IntDef(prefix = { "EVENT_" }, value = { + EVENT_UNKNOWN, + EVENT_COUGH, + EVENT_SNORE, + }) public @interface EventCode {} + + /** The integer indicating an unknown level. */ + public static final int LEVEL_UNKNOWN = 0; + + /** The integer indicating a low level. */ + public static final int LEVEL_LOW = 1; + + /** The integer indicating a medium low level. */ + public static final int LEVEL_MEDIUM_LOW = 2; + + /** The integer indicating a medium Level. */ + public static final int LEVEL_MEDIUM = 3; + + /** The integer indicating a medium high level. */ + public static final int LEVEL_MEDIUM_HIGH = 4; + + /** The integer indicating a high level. */ + public static final int LEVEL_HIGH = 5; + + /** @hide */ + @IntDef(prefix = {"LEVEL_"}, value = { + LEVEL_UNKNOWN, + LEVEL_LOW, + LEVEL_MEDIUM_LOW, + LEVEL_MEDIUM, + LEVEL_MEDIUM_HIGH, + LEVEL_HIGH + }) public @interface LevelValue {} + + @EventCode private final int mEventType; + private static int defaultEventType() { + return EVENT_UNKNOWN; + } + + /** Event start time */ + @DataClass.ParcelWith(Parcelling.BuiltIn.ForInstant.class) + @NonNull private final Instant mStartTime; + @NonNull private static Instant defaultStartTime() { + return Instant.MIN; + } + + /** Event end time */ + @DataClass.ParcelWith(Parcelling.BuiltIn.ForInstant.class) + @NonNull private final Instant mEndTime; + @NonNull private static Instant defaultEndTime() { + return Instant.MAX; + } + + /** + * Confidence level from LEVEL_LOW to LEVEL_HIGH, or LEVEL_NONE if not available. + * Apps can add post-processing filter using this value if needed. + */ + @LevelValue private final int mConfidenceLevel; + private static int defaultConfidenceLevel() { + return LEVEL_UNKNOWN; + } + + /** + * Density level from LEVEL_LOW to LEVEL_HIGH, or LEVEL_NONE if not available. + * Apps can add post-processing filter using this value if needed. + */ + @LevelValue private final int mDensityLevel; + private static int defaultDensityLevel() { + return LEVEL_UNKNOWN; + } + + + + // 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/app/ambientcontext/AmbientContextEvent.java + // + // To exclude the generated code from IntelliJ auto-formatting enable (one-time): + // Settings > Editor > Code Style > Formatter Control + //@formatter:off + + + /** @hide */ + @IntDef(prefix = "EVENT_", value = { + EVENT_UNKNOWN, + EVENT_COUGH, + EVENT_SNORE + }) + @Retention(RetentionPolicy.SOURCE) + @DataClass.Generated.Member + public @interface Event {} + + /** @hide */ + @DataClass.Generated.Member + public static String eventToString(@Event int value) { + switch (value) { + case EVENT_UNKNOWN: + return "EVENT_UNKNOWN"; + case EVENT_COUGH: + return "EVENT_COUGH"; + case EVENT_SNORE: + return "EVENT_SNORE"; + default: return Integer.toHexString(value); + } + } + + /** @hide */ + @IntDef(prefix = "LEVEL_", value = { + LEVEL_UNKNOWN, + LEVEL_LOW, + LEVEL_MEDIUM_LOW, + LEVEL_MEDIUM, + LEVEL_MEDIUM_HIGH, + LEVEL_HIGH + }) + @Retention(RetentionPolicy.SOURCE) + @DataClass.Generated.Member + public @interface Level {} + + /** @hide */ + @DataClass.Generated.Member + public static String levelToString(@Level int value) { + switch (value) { + case LEVEL_UNKNOWN: + return "LEVEL_UNKNOWN"; + case LEVEL_LOW: + return "LEVEL_LOW"; + case LEVEL_MEDIUM_LOW: + return "LEVEL_MEDIUM_LOW"; + case LEVEL_MEDIUM: + return "LEVEL_MEDIUM"; + case LEVEL_MEDIUM_HIGH: + return "LEVEL_MEDIUM_HIGH"; + case LEVEL_HIGH: + return "LEVEL_HIGH"; + default: return Integer.toHexString(value); + } + } + + @DataClass.Generated.Member + /* package-private */ AmbientContextEvent( + @EventCode int eventType, + @NonNull Instant startTime, + @NonNull Instant endTime, + @LevelValue int confidenceLevel, + @LevelValue int densityLevel) { + this.mEventType = eventType; + com.android.internal.util.AnnotationValidations.validate( + EventCode.class, null, mEventType); + this.mStartTime = startTime; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mStartTime); + this.mEndTime = endTime; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mEndTime); + this.mConfidenceLevel = confidenceLevel; + com.android.internal.util.AnnotationValidations.validate( + LevelValue.class, null, mConfidenceLevel); + this.mDensityLevel = densityLevel; + com.android.internal.util.AnnotationValidations.validate( + LevelValue.class, null, mDensityLevel); + + // onConstructed(); // You can define this method to get a callback + } + + @DataClass.Generated.Member + public @EventCode int getEventType() { + return mEventType; + } + + /** + * Event start time + */ + @DataClass.Generated.Member + public @NonNull Instant getStartTime() { + return mStartTime; + } + + /** + * Event end time + */ + @DataClass.Generated.Member + public @NonNull Instant getEndTime() { + return mEndTime; + } + + /** + * Confidence level from LEVEL_LOW to LEVEL_HIGH, or LEVEL_NONE if not available. + * Apps can add post-processing filter using this value if needed. + */ + @DataClass.Generated.Member + public @LevelValue int getConfidenceLevel() { + return mConfidenceLevel; + } + + /** + * Density level from LEVEL_LOW to LEVEL_HIGH, or LEVEL_NONE if not available. + * Apps can add post-processing filter using this value if needed. + */ + @DataClass.Generated.Member + public @LevelValue int getDensityLevel() { + return mDensityLevel; + } + + @Override + @DataClass.Generated.Member + public String toString() { + // You can override field toString logic by defining methods like: + // String fieldNameToString() { ... } + + return "AmbientContextEvent { " + + "eventType = " + mEventType + ", " + + "startTime = " + mStartTime + ", " + + "endTime = " + mEndTime + ", " + + "confidenceLevel = " + mConfidenceLevel + ", " + + "densityLevel = " + mDensityLevel + + " }"; + } + + @DataClass.Generated.Member + static Parcelling<Instant> sParcellingForStartTime = + Parcelling.Cache.get( + Parcelling.BuiltIn.ForInstant.class); + static { + if (sParcellingForStartTime == null) { + sParcellingForStartTime = Parcelling.Cache.put( + new Parcelling.BuiltIn.ForInstant()); + } + } + + @DataClass.Generated.Member + static Parcelling<Instant> sParcellingForEndTime = + Parcelling.Cache.get( + Parcelling.BuiltIn.ForInstant.class); + static { + if (sParcellingForEndTime == null) { + sParcellingForEndTime = Parcelling.Cache.put( + new Parcelling.BuiltIn.ForInstant()); + } + } + + @Override + @DataClass.Generated.Member + public void writeToParcel(@NonNull android.os.Parcel dest, int flags) { + // You can override field parcelling by defining methods like: + // void parcelFieldName(Parcel dest, int flags) { ... } + + dest.writeInt(mEventType); + sParcellingForStartTime.parcel(mStartTime, dest, flags); + sParcellingForEndTime.parcel(mEndTime, dest, flags); + dest.writeInt(mConfidenceLevel); + dest.writeInt(mDensityLevel); + } + + @Override + @DataClass.Generated.Member + public int describeContents() { return 0; } + + /** @hide */ + @SuppressWarnings({"unchecked", "RedundantCast"}) + @DataClass.Generated.Member + /* package-private */ AmbientContextEvent(@NonNull android.os.Parcel in) { + // You can override field unparcelling by defining methods like: + // static FieldType unparcelFieldName(Parcel in) { ... } + + int eventType = in.readInt(); + Instant startTime = sParcellingForStartTime.unparcel(in); + Instant endTime = sParcellingForEndTime.unparcel(in); + int confidenceLevel = in.readInt(); + int densityLevel = in.readInt(); + + this.mEventType = eventType; + com.android.internal.util.AnnotationValidations.validate( + EventCode.class, null, mEventType); + this.mStartTime = startTime; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mStartTime); + this.mEndTime = endTime; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mEndTime); + this.mConfidenceLevel = confidenceLevel; + com.android.internal.util.AnnotationValidations.validate( + LevelValue.class, null, mConfidenceLevel); + this.mDensityLevel = densityLevel; + com.android.internal.util.AnnotationValidations.validate( + LevelValue.class, null, mDensityLevel); + + // onConstructed(); // You can define this method to get a callback + } + + @DataClass.Generated.Member + public static final @NonNull Parcelable.Creator<AmbientContextEvent> CREATOR + = new Parcelable.Creator<AmbientContextEvent>() { + @Override + public AmbientContextEvent[] newArray(int size) { + return new AmbientContextEvent[size]; + } + + @Override + public AmbientContextEvent createFromParcel(@NonNull android.os.Parcel in) { + return new AmbientContextEvent(in); + } + }; + + /** + * A builder for {@link AmbientContextEvent} + */ + @SuppressWarnings("WeakerAccess") + @DataClass.Generated.Member + public static final class Builder { + + private @EventCode int mEventType; + private @NonNull Instant mStartTime; + private @NonNull Instant mEndTime; + private @LevelValue int mConfidenceLevel; + private @LevelValue int mDensityLevel; + + private long mBuilderFieldsSet = 0L; + + public Builder() { + } + + @DataClass.Generated.Member + public @NonNull Builder setEventType(@EventCode int value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x1; + mEventType = value; + return this; + } + + /** + * Event start time + */ + @DataClass.Generated.Member + public @NonNull Builder setStartTime(@NonNull Instant value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x2; + mStartTime = value; + return this; + } + + /** + * Event end time + */ + @DataClass.Generated.Member + public @NonNull Builder setEndTime(@NonNull Instant value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x4; + mEndTime = value; + return this; + } + + /** + * Confidence level from LEVEL_LOW to LEVEL_HIGH, or LEVEL_NONE if not available. + * Apps can add post-processing filter using this value if needed. + */ + @DataClass.Generated.Member + public @NonNull Builder setConfidenceLevel(@LevelValue int value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x8; + mConfidenceLevel = value; + return this; + } + + /** + * Density level from LEVEL_LOW to LEVEL_HIGH, or LEVEL_NONE if not available. + * Apps can add post-processing filter using this value if needed. + */ + @DataClass.Generated.Member + public @NonNull Builder setDensityLevel(@LevelValue int value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x10; + mDensityLevel = value; + return this; + } + + /** Builds the instance. This builder should not be touched after calling this! */ + public @NonNull AmbientContextEvent build() { + checkNotUsed(); + mBuilderFieldsSet |= 0x20; // Mark builder used + + if ((mBuilderFieldsSet & 0x1) == 0) { + mEventType = defaultEventType(); + } + if ((mBuilderFieldsSet & 0x2) == 0) { + mStartTime = defaultStartTime(); + } + if ((mBuilderFieldsSet & 0x4) == 0) { + mEndTime = defaultEndTime(); + } + if ((mBuilderFieldsSet & 0x8) == 0) { + mConfidenceLevel = defaultConfidenceLevel(); + } + if ((mBuilderFieldsSet & 0x10) == 0) { + mDensityLevel = defaultDensityLevel(); + } + AmbientContextEvent o = new AmbientContextEvent( + mEventType, + mStartTime, + mEndTime, + mConfidenceLevel, + mDensityLevel); + return o; + } + + private void checkNotUsed() { + if ((mBuilderFieldsSet & 0x20) != 0) { + throw new IllegalStateException( + "This Builder should not be reused. Use a new Builder instance instead"); + } + } + } + + @DataClass.Generated( + time = 1642040319323L, + codegenVersion = "1.0.23", + sourceFile = "frameworks/base/core/java/android/app/ambientcontext/AmbientContextEvent.java", + inputSignatures = "public static final int EVENT_UNKNOWN\npublic static final int EVENT_COUGH\npublic static final int EVENT_SNORE\npublic static final int LEVEL_UNKNOWN\npublic static final int LEVEL_LOW\npublic static final int LEVEL_MEDIUM_LOW\npublic static final int LEVEL_MEDIUM\npublic static final int LEVEL_MEDIUM_HIGH\npublic static final int LEVEL_HIGH\nprivate final @android.app.ambientcontext.AmbientContextEvent.EventCode int mEventType\nprivate final @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInstant.class) @android.annotation.NonNull java.time.Instant mStartTime\nprivate final @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInstant.class) @android.annotation.NonNull java.time.Instant mEndTime\nprivate final @android.app.ambientcontext.AmbientContextEvent.LevelValue int mConfidenceLevel\nprivate final @android.app.ambientcontext.AmbientContextEvent.LevelValue int mDensityLevel\nprivate static int defaultEventType()\nprivate static @android.annotation.NonNull java.time.Instant defaultStartTime()\nprivate static @android.annotation.NonNull java.time.Instant defaultEndTime()\nprivate static int defaultConfidenceLevel()\nprivate static int defaultDensityLevel()\nclass AmbientContextEvent extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genBuilder=true, genConstructor=false, genHiddenConstDefs=true, genParcelable=true, genToString=true)") + @Deprecated + private void __metadata() {} + + + //@formatter:on + // End of generated code + +} diff --git a/core/java/android/app/ambientcontext/AmbientContextEventRequest.aidl b/core/java/android/app/ambientcontext/AmbientContextEventRequest.aidl new file mode 100644 index 000000000000..e24c6ad1bcea --- /dev/null +++ b/core/java/android/app/ambientcontext/AmbientContextEventRequest.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.ambientcontext; + +parcelable AmbientContextEventRequest; diff --git a/core/java/android/app/ambientcontext/AmbientContextEventRequest.java b/core/java/android/app/ambientcontext/AmbientContextEventRequest.java new file mode 100644 index 000000000000..82b16a2db0ce --- /dev/null +++ b/core/java/android/app/ambientcontext/AmbientContextEventRequest.java @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.ambientcontext; + +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.PersistableBundle; +import android.util.ArraySet; + +import java.util.HashSet; +import java.util.Set; + +/** + * Represents the request for ambient event detection. + * + * @hide + */ +@SystemApi +public final class AmbientContextEventRequest implements Parcelable { + @NonNull private final Set<Integer> mEventTypes; + @NonNull private final PersistableBundle mOptions; + + AmbientContextEventRequest( + @NonNull Set<Integer> eventTypes, + @NonNull PersistableBundle options) { + this.mEventTypes = eventTypes; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mEventTypes); + this.mOptions = options; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mOptions); + } + + /** + * The event types to detect. + */ + public @NonNull Set<Integer> getEventTypes() { + return mEventTypes; + } + + /** + * Optional detection options. + */ + public @NonNull PersistableBundle getOptions() { + return mOptions; + } + + @Override + public String toString() { + return "AmbientContextEventRequest { " + "eventTypes = " + mEventTypes + ", " + + "options = " + mOptions + " }"; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeArraySet(new ArraySet<>(mEventTypes)); + dest.writeTypedObject(mOptions, flags); + } + + @Override + public int describeContents() { + return 0; + } + + /** @hide */ + @SuppressWarnings({"unchecked", "RedundantCast"}) + AmbientContextEventRequest(@NonNull Parcel in) { + Set<Integer> eventTypes = (Set<Integer>) in.readArraySet(Integer.class.getClassLoader()); + PersistableBundle options = (PersistableBundle) in.readTypedObject( + PersistableBundle.CREATOR); + + this.mEventTypes = eventTypes; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mEventTypes); + this.mOptions = options; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mOptions); + } + + public static final @NonNull Parcelable.Creator<AmbientContextEventRequest> CREATOR = + new Parcelable.Creator<AmbientContextEventRequest>() { + @Override + public AmbientContextEventRequest[] newArray(int size) { + return new AmbientContextEventRequest[size]; + } + + @Override + public AmbientContextEventRequest createFromParcel(@NonNull Parcel in) { + return new AmbientContextEventRequest(in); + } + }; + + /** + * A builder for {@link AmbientContextEventRequest} + */ + @SuppressWarnings("WeakerAccess") + public static final class Builder { + private @NonNull Set<Integer> mEventTypes; + private @NonNull PersistableBundle mOptions; + + private long mBuilderFieldsSet = 0L; + + public Builder() { + } + + /** + * Add an event type to detect. + */ + public @NonNull Builder addEventType(@AmbientContextEvent.EventCode int value) { + checkNotUsed(); + if (mEventTypes == null) { + mBuilderFieldsSet |= 0x1; + mEventTypes = new HashSet<>(); + } + mEventTypes.add(value); + return this; + } + + /** + * Optional detection options. + */ + public @NonNull Builder setOptions(@NonNull PersistableBundle value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x2; + mOptions = value; + return this; + } + + /** Builds the instance. This builder should not be touched after calling this! */ + public @NonNull AmbientContextEventRequest build() { + checkNotUsed(); + mBuilderFieldsSet |= 0x4; // Mark builder used + + if ((mBuilderFieldsSet & 0x1) == 0) { + mEventTypes = new HashSet<>(); + } + if ((mBuilderFieldsSet & 0x2) == 0) { + mOptions = new PersistableBundle(); + } + AmbientContextEventRequest o = new AmbientContextEventRequest( + mEventTypes, + mOptions); + return o; + } + + private void checkNotUsed() { + if ((mBuilderFieldsSet & 0x4) != 0) { + throw new IllegalStateException( + "This Builder should not be reused. Use a new Builder instance instead"); + } + } + } +} diff --git a/core/java/android/app/ambientcontext/AmbientContextEventResponse.aidl b/core/java/android/app/ambientcontext/AmbientContextEventResponse.aidl new file mode 100644 index 000000000000..4dc6466c7365 --- /dev/null +++ b/core/java/android/app/ambientcontext/AmbientContextEventResponse.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.ambientcontext; + +parcelable AmbientContextEventResponse;
\ No newline at end of file diff --git a/core/java/android/app/ambientcontext/AmbientContextEventResponse.java b/core/java/android/app/ambientcontext/AmbientContextEventResponse.java new file mode 100644 index 000000000000..472a78b177c9 --- /dev/null +++ b/core/java/android/app/ambientcontext/AmbientContextEventResponse.java @@ -0,0 +1,293 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.ambientcontext; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.app.PendingIntent; +import android.os.Parcelable; + +import com.android.internal.util.AnnotationValidations; + +import java.util.ArrayList; +import java.util.List; + +/** + * Represents a response from the {@code AmbientContextEvent} service. + * + * @hide + */ +@SystemApi +public final class AmbientContextEventResponse implements Parcelable { + /** + * An unknown status. + */ + public static final int STATUS_UNKNOWN = 0; + /** + * The value of the status code that indicates success. + */ + public static final int STATUS_SUCCESS = 1; + /** + * The value of the status code that indicates one or more of the + * requested events are not supported. + */ + public static final int STATUS_NOT_SUPPORTED = 2; + /** + * The value of the status code that indicates service not available. + */ + public static final int STATUS_SERVICE_UNAVAILABLE = 3; + /** + * The value of the status code that microphone is disabled. + */ + public static final int STATUS_MICROPHONE_DISABLED = 4; + /** + * The value of the status code that the app is not granted access. + */ + public static final int STATUS_ACCESS_DENIED = 5; + + /** @hide */ + @IntDef(prefix = { "STATUS_" }, value = { + STATUS_UNKNOWN, + STATUS_SUCCESS, + STATUS_NOT_SUPPORTED, + STATUS_SERVICE_UNAVAILABLE, + STATUS_MICROPHONE_DISABLED, + STATUS_ACCESS_DENIED + }) public @interface StatusCode {} + + @StatusCode private final int mStatusCode; + @NonNull private final List<AmbientContextEvent> mEvents; + @NonNull private final String mPackageName; + @Nullable private final PendingIntent mActionPendingIntent; + + /** @hide */ + public static String statusToString(@StatusCode int value) { + switch (value) { + case STATUS_UNKNOWN: + return "STATUS_UNKNOWN"; + case STATUS_SUCCESS: + return "STATUS_SUCCESS"; + case STATUS_NOT_SUPPORTED: + return "STATUS_NOT_SUPPORTED"; + case STATUS_SERVICE_UNAVAILABLE: + return "STATUS_SERVICE_UNAVAILABLE"; + case STATUS_MICROPHONE_DISABLED: + return "STATUS_MICROPHONE_DISABLED"; + case STATUS_ACCESS_DENIED: + return "STATUS_ACCESS_DENIED"; + default: return Integer.toHexString(value); + } + } + + AmbientContextEventResponse( + @StatusCode int statusCode, + @NonNull List<AmbientContextEvent> events, + @NonNull String packageName, + @Nullable PendingIntent actionPendingIntent) { + this.mStatusCode = statusCode; + AnnotationValidations.validate(StatusCode.class, null, mStatusCode); + this.mEvents = events; + AnnotationValidations.validate(NonNull.class, null, mEvents); + this.mPackageName = packageName; + AnnotationValidations.validate(NonNull.class, null, mPackageName); + this.mActionPendingIntent = actionPendingIntent; + } + + /** + * The status of the response. + */ + public @StatusCode int getStatusCode() { + return mStatusCode; + } + + /** + * The detected event. + */ + public @NonNull List<AmbientContextEvent> getEvents() { + return mEvents; + } + + /** + * The package to deliver the response to. + */ + public @NonNull String getPackageName() { + return mPackageName; + } + + /** + * A {@link PendingIntent} that the client should call to allow further actions by user. + * For example, with {@link STATUS_ACCESS_DENIED}, the PendingIntent can redirect users to the + * grant access activity. + */ + public @Nullable PendingIntent getActionPendingIntent() { + return mActionPendingIntent; + } + + @Override + public String toString() { + return "AmbientContextEventResponse { " + "statusCode = " + mStatusCode + ", " + + "events = " + mEvents + ", " + "packageName = " + mPackageName + ", " + + "callbackPendingIntent = " + mActionPendingIntent + " }"; + } + + @Override + public void writeToParcel(@NonNull android.os.Parcel dest, int flags) { + byte flg = 0; + if (mActionPendingIntent != null) flg |= 0x8; + dest.writeByte(flg); + dest.writeInt(mStatusCode); + dest.writeParcelableList(mEvents, flags); + dest.writeString(mPackageName); + if (mActionPendingIntent != null) dest.writeTypedObject(mActionPendingIntent, flags); + } + + @Override + public int describeContents() { + return 0; + } + + /** @hide */ + @SuppressWarnings({"unchecked", "RedundantCast"}) + AmbientContextEventResponse(@NonNull android.os.Parcel in) { + byte flg = in.readByte(); + int statusCode = in.readInt(); + List<AmbientContextEvent> events = new ArrayList<>(); + in.readParcelableList(events, AmbientContextEvent.class.getClassLoader(), + AmbientContextEvent.class); + String packageName = in.readString(); + PendingIntent callbackPendingIntent = (flg & 0x8) == 0 ? null + : (PendingIntent) in.readTypedObject(PendingIntent.CREATOR); + + this.mStatusCode = statusCode; + AnnotationValidations.validate( + StatusCode.class, null, mStatusCode); + this.mEvents = events; + AnnotationValidations.validate( + NonNull.class, null, mEvents); + this.mPackageName = packageName; + AnnotationValidations.validate( + NonNull.class, null, mPackageName); + this.mActionPendingIntent = callbackPendingIntent; + } + + public static final @NonNull Parcelable.Creator<AmbientContextEventResponse> CREATOR = + new Parcelable.Creator<AmbientContextEventResponse>() { + @Override + public AmbientContextEventResponse[] newArray(int size) { + return new AmbientContextEventResponse[size]; + } + + @Override + public AmbientContextEventResponse createFromParcel(@NonNull android.os.Parcel in) { + return new AmbientContextEventResponse(in); + } + }; + + /** + * A builder for {@link AmbientContextEventResponse} + */ + @SuppressWarnings("WeakerAccess") + public static final class Builder { + private @StatusCode int mStatusCode; + private @NonNull List<AmbientContextEvent> mEvents; + private @NonNull String mPackageName; + private @Nullable PendingIntent mCallbackPendingIntent; + private long mBuilderFieldsSet = 0L; + + public Builder() { + } + + /** + * The status of the response. + */ + public @NonNull Builder setStatusCode(@StatusCode int value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x1; + mStatusCode = value; + return this; + } + + /** + * Adds an event to the builder. + */ + public @NonNull Builder addEvent(@NonNull AmbientContextEvent value) { + checkNotUsed(); + if (mEvents == null) { + mBuilderFieldsSet |= 0x2; + mEvents = new ArrayList<>(); + } + mEvents.add(value); + return this; + } + + /** + * The package to deliver the response to. + */ + public @NonNull Builder setPackageName(@NonNull String value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x4; + mPackageName = value; + return this; + } + + /** + * A {@link PendingIntent} that the client should call to allow further actions by user. + * For example, with {@link STATUS_ACCESS_DENIED}, the PendingIntent can redirect users to + * the grant access activity. + */ + public @NonNull Builder setActionPendingIntent(@NonNull PendingIntent value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x8; + mCallbackPendingIntent = value; + return this; + } + + /** Builds the instance. This builder should not be touched after calling this! */ + public @NonNull AmbientContextEventResponse build() { + checkNotUsed(); + mBuilderFieldsSet |= 0x10; // Mark builder used + + if ((mBuilderFieldsSet & 0x1) == 0) { + mStatusCode = STATUS_UNKNOWN; + } + if ((mBuilderFieldsSet & 0x2) == 0) { + mEvents = new ArrayList<>(); + } + if ((mBuilderFieldsSet & 0x4) == 0) { + mPackageName = ""; + } + if ((mBuilderFieldsSet & 0x8) == 0) { + mCallbackPendingIntent = null; + } + AmbientContextEventResponse o = new AmbientContextEventResponse( + mStatusCode, + mEvents, + mPackageName, + mCallbackPendingIntent); + return o; + } + + private void checkNotUsed() { + if ((mBuilderFieldsSet & 0x10) != 0) { + throw new IllegalStateException( + "This Builder should not be reused. Use a new Builder instance instead"); + } + } + } +} diff --git a/core/java/android/app/ambientcontext/AmbientContextManager.java b/core/java/android/app/ambientcontext/AmbientContextManager.java new file mode 100644 index 000000000000..6841d1bbfc1f --- /dev/null +++ b/core/java/android/app/ambientcontext/AmbientContextManager.java @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.ambientcontext; + +import android.Manifest; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.RequiresPermission; +import android.annotation.SystemApi; +import android.annotation.SystemService; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.os.RemoteException; + +import com.android.internal.util.Preconditions; + +/** + * Allows granted apps to register for particular pre-defined {@link AmbientContextEvent}s. + * After successful registration, the app receives a callback on the provided {@link PendingIntent} + * when the requested event is detected. + * <p /> + * + * Example: + * + * <pre><code> + * // Create request + * AmbientContextEventRequest request = new AmbientContextEventRequest.Builder() + * .addEventType(AmbientContextEvent.EVENT_COUGH) + * .addEventTYpe(AmbientContextEvent.EVENT_SNORE) + * .build(); + * // Create PendingIntent + * Intent intent = new Intent(actionString, null, context, MyBroadcastReceiver.class) + * .addFlags(Intent.FLAG_RECEIVER_FOREGROUND); + * PendingIntent pendingIntent = PendingIntents.getBroadcastMutable(context, 0, intent, 0); + * // Register for events + * AmbientContextManager ambientContextManager = + * context.getSystemService(AmbientContextManager.class); + * ambientContextManager.registerObserver(request, pendingIntent); + * + * // Handle the callback intent in your receiver + * {@literal @}Override + * protected void onReceive(Context context, Intent intent) { + * AmbientContextEventResponse response = + * AmbientContextManager.getResponseFromIntent(intent); + * if (response != null) { + * if (response.getStatusCode() == AmbientContextEventResponse.STATUS_SUCCESS) { + * // Do something useful with response.getEvent() + * } else if (response.getStatusCode() == AmbientContextEventResponse.STATUS_ACCESS_DENIED) { + * // Redirect users to grant access + * PendingIntent callbackPendingIntent = response.getCallbackPendingIntent(); + * if (callbackPendingIntent != null) { + * callbackPendingIntent.send(); + * } + * } else ... + * } + * } + * </code></pre> + * + * @hide + */ +@SystemApi +@SystemService(Context.AMBIENT_CONTEXT_SERVICE) +public final class AmbientContextManager { + + /** + * The key of an Intent extra indicating the response. + */ + public static final String EXTRA_AMBIENT_CONTEXT_EVENT_RESPONSE = + "android.app.ambientcontext.extra.AMBIENT_CONTEXT_EVENT_RESPONSE"; + + /** + * Allows clients to retrieve the response from the intent. + * @param intent received from the PendingIntent callback + * + * @return the AmbientContextEventResponse, or null if not present + */ + @Nullable + public static AmbientContextEventResponse getResponseFromIntent( + @NonNull Intent intent) { + if (intent.hasExtra(AmbientContextManager.EXTRA_AMBIENT_CONTEXT_EVENT_RESPONSE)) { + return intent.getParcelableExtra(EXTRA_AMBIENT_CONTEXT_EVENT_RESPONSE); + } else { + return null; + } + } + + private final Context mContext; + private final IAmbientContextEventObserver mService; + + /** + * {@hide} + */ + public AmbientContextManager(Context context, IAmbientContextEventObserver service) { + mContext = context; + mService = service; + } + + /** + * Allows app to register as a {@link AmbientContextEvent} observer. The + * observer receives a callback on the provided {@link PendingIntent} when the requested + * event is detected. Registering another observer from the same package that has already been + * registered will override the previous observer. + * + * @param request The request with events to observe. + * @param pendingIntent A mutable {@link PendingIntent} that will be dispatched when any + * requested event is detected. + */ + @RequiresPermission(Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT) + public void registerObserver( + @NonNull AmbientContextEventRequest request, + @NonNull PendingIntent pendingIntent) { + Preconditions.checkArgument(!pendingIntent.isImmutable()); + try { + mService.registerObserver(request, pendingIntent); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Unregisters the requesting app as an {@code AmbientContextEvent} observer. Unregistering an + * observer that was already unregistered or never registered will have no effect. + */ + @RequiresPermission(Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT) + public void unregisterObserver() { + try { + mService.unregisterObserver(mContext.getOpPackageName()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } +} diff --git a/core/java/android/app/ambientcontext/IAmbientContextEventObserver.aidl b/core/java/android/app/ambientcontext/IAmbientContextEventObserver.aidl new file mode 100644 index 000000000000..9032fe1ee045 --- /dev/null +++ b/core/java/android/app/ambientcontext/IAmbientContextEventObserver.aidl @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.ambientcontext; + +import android.app.PendingIntent; +import android.app.ambientcontext.AmbientContextEventRequest; + +/** + * Interface for an AmbientContextEventManager that provides access to AmbientContextEvents. + * + * @hide + */ +oneway interface IAmbientContextEventObserver { + void registerObserver(in AmbientContextEventRequest request, in PendingIntent pendingIntent); + void unregisterObserver(in String callingPackage); +}
\ No newline at end of file diff --git a/core/java/android/app/ambientcontext/OWNERS b/core/java/android/app/ambientcontext/OWNERS new file mode 100644 index 000000000000..a863297b84e8 --- /dev/null +++ b/core/java/android/app/ambientcontext/OWNERS @@ -0,0 +1,3 @@ +enxun@google.com +kxchen@google.com +tgadh@google.com diff --git a/core/java/android/app/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java index e1f6af0cc128..6e49e956fe7e 100644 --- a/core/java/android/app/assist/AssistStructure.java +++ b/core/java/android/app/assist/AssistStructure.java @@ -815,13 +815,13 @@ public class AssistStructure implements Parcelable { mAutofillHints = in.readStringArray(); } if ((autofillFlags & AUTOFILL_FLAGS_HAS_AUTOFILL_VALUE) != 0) { - mAutofillValue = in.readParcelable(null); + mAutofillValue = in.readParcelable(null, android.view.autofill.AutofillValue.class); } if ((autofillFlags & AUTOFILL_FLAGS_HAS_AUTOFILL_OPTIONS) != 0) { mAutofillOptions = in.readCharSequenceArray(); } if ((autofillFlags & AUTOFILL_FLAGS_HAS_HTML_INFO) != 0) { - mHtmlInfo = in.readParcelable(null); + mHtmlInfo = in.readParcelable(null, android.view.ViewStructure.HtmlInfo.class); } if ((autofillFlags & AUTOFILL_FLAGS_HAS_MIN_TEXT_EMS) != 0) { mMinEms = in.readInt(); @@ -886,7 +886,7 @@ public class AssistStructure implements Parcelable { mWebDomain = in.readString(); } if ((flags&FLAGS_HAS_LOCALE_LIST) != 0) { - mLocaleList = in.readParcelable(null); + mLocaleList = in.readParcelable(null, android.os.LocaleList.class); } if ((flags & FLAGS_HAS_MIME_TYPES) != 0) { mReceiveContentMimeTypes = in.readStringArray(); diff --git a/core/java/android/app/people/ConversationChannel.java b/core/java/android/app/people/ConversationChannel.java index 2bf71b0183c6..ab350f225e52 100644 --- a/core/java/android/app/people/ConversationChannel.java +++ b/core/java/android/app/people/ConversationChannel.java @@ -83,16 +83,16 @@ public final class ConversationChannel implements Parcelable { } public ConversationChannel(Parcel in) { - mShortcutInfo = in.readParcelable(ShortcutInfo.class.getClassLoader()); + mShortcutInfo = in.readParcelable(ShortcutInfo.class.getClassLoader(), android.content.pm.ShortcutInfo.class); mUid = in.readInt(); - mNotificationChannel = in.readParcelable(NotificationChannel.class.getClassLoader()); + mNotificationChannel = in.readParcelable(NotificationChannel.class.getClassLoader(), android.app.NotificationChannel.class); mNotificationChannelGroup = - in.readParcelable(NotificationChannelGroup.class.getClassLoader()); + in.readParcelable(NotificationChannelGroup.class.getClassLoader(), android.app.NotificationChannelGroup.class); mLastEventTimestamp = in.readLong(); mHasActiveNotifications = in.readBoolean(); mHasBirthdayToday = in.readBoolean(); mStatuses = new ArrayList<>(); - in.readParcelableList(mStatuses, ConversationStatus.class.getClassLoader()); + in.readParcelableList(mStatuses, ConversationStatus.class.getClassLoader(), android.app.people.ConversationStatus.class); } @Override diff --git a/core/java/android/app/people/ConversationStatus.java b/core/java/android/app/people/ConversationStatus.java index 8038158b1f97..a7b61b37d14e 100644 --- a/core/java/android/app/people/ConversationStatus.java +++ b/core/java/android/app/people/ConversationStatus.java @@ -126,7 +126,7 @@ public final class ConversationStatus implements Parcelable { mActivity = p.readInt(); mAvailability = p.readInt(); mDescription = p.readCharSequence(); - mIcon = p.readParcelable(Icon.class.getClassLoader()); + mIcon = p.readParcelable(Icon.class.getClassLoader(), android.graphics.drawable.Icon.class); mStartTimeMs = p.readLong(); mEndTimeMs = p.readLong(); } diff --git a/core/java/android/app/people/PeopleSpaceTile.java b/core/java/android/app/people/PeopleSpaceTile.java index e11861f49be8..4337111636a0 100644 --- a/core/java/android/app/people/PeopleSpaceTile.java +++ b/core/java/android/app/people/PeopleSpaceTile.java @@ -472,9 +472,9 @@ public class PeopleSpaceTile implements Parcelable { public PeopleSpaceTile(Parcel in) { mId = in.readString(); mUserName = in.readCharSequence(); - mUserIcon = in.readParcelable(Icon.class.getClassLoader()); - mContactUri = in.readParcelable(Uri.class.getClassLoader()); - mUserHandle = in.readParcelable(UserHandle.class.getClassLoader()); + mUserIcon = in.readParcelable(Icon.class.getClassLoader(), android.graphics.drawable.Icon.class); + mContactUri = in.readParcelable(Uri.class.getClassLoader(), android.net.Uri.class); + mUserHandle = in.readParcelable(UserHandle.class.getClassLoader(), android.os.UserHandle.class); mPackageName = in.readString(); mBirthdayText = in.readString(); mLastInteractionTimestamp = in.readLong(); @@ -483,12 +483,12 @@ public class PeopleSpaceTile implements Parcelable { mNotificationContent = in.readCharSequence(); mNotificationSender = in.readCharSequence(); mNotificationCategory = in.readString(); - mNotificationDataUri = in.readParcelable(Uri.class.getClassLoader()); + mNotificationDataUri = in.readParcelable(Uri.class.getClassLoader(), android.net.Uri.class); mMessagesCount = in.readInt(); - mIntent = in.readParcelable(Intent.class.getClassLoader()); + mIntent = in.readParcelable(Intent.class.getClassLoader(), android.content.Intent.class); mNotificationTimestamp = in.readLong(); mStatuses = new ArrayList<>(); - in.readParcelableList(mStatuses, ConversationStatus.class.getClassLoader()); + in.readParcelableList(mStatuses, ConversationStatus.class.getClassLoader(), android.app.people.ConversationStatus.class); mCanBypassDnd = in.readBoolean(); mIsPackageSuspended = in.readBoolean(); mIsUserQuieted = in.readBoolean(); diff --git a/core/java/android/app/prediction/AppTargetEvent.java b/core/java/android/app/prediction/AppTargetEvent.java index 963e750e4fd1..51e3953ead4f 100644 --- a/core/java/android/app/prediction/AppTargetEvent.java +++ b/core/java/android/app/prediction/AppTargetEvent.java @@ -72,7 +72,7 @@ public final class AppTargetEvent implements Parcelable { } private AppTargetEvent(Parcel parcel) { - mTarget = parcel.readParcelable(null); + mTarget = parcel.readParcelable(null, android.app.prediction.AppTarget.class); mLocation = parcel.readString(); mAction = parcel.readInt(); } diff --git a/core/java/android/app/servertransaction/ClientTransaction.java b/core/java/android/app/servertransaction/ClientTransaction.java index fbb37db52014..30a6c311bd1e 100644 --- a/core/java/android/app/servertransaction/ClientTransaction.java +++ b/core/java/android/app/servertransaction/ClientTransaction.java @@ -197,11 +197,11 @@ public class ClientTransaction implements Parcelable, ObjectPoolItem { if (readActivityToken) { mActivityToken = in.readStrongBinder(); } - mLifecycleStateRequest = in.readParcelable(getClass().getClassLoader()); + mLifecycleStateRequest = in.readParcelable(getClass().getClassLoader(), android.app.servertransaction.ActivityLifecycleItem.class); final boolean readActivityCallbacks = in.readBoolean(); if (readActivityCallbacks) { mActivityCallbacks = new ArrayList<>(); - in.readParcelableList(mActivityCallbacks, getClass().getClassLoader()); + in.readParcelableList(mActivityCallbacks, getClass().getClassLoader(), android.app.servertransaction.ClientTransactionItem.class); } } diff --git a/core/java/android/app/smartspace/SmartspaceTargetEvent.java b/core/java/android/app/smartspace/SmartspaceTargetEvent.java index 61f8723ca393..89caab764591 100644 --- a/core/java/android/app/smartspace/SmartspaceTargetEvent.java +++ b/core/java/android/app/smartspace/SmartspaceTargetEvent.java @@ -96,7 +96,7 @@ public final class SmartspaceTargetEvent implements Parcelable { } private SmartspaceTargetEvent(Parcel parcel) { - mSmartspaceTarget = parcel.readParcelable(null); + mSmartspaceTarget = parcel.readParcelable(null, android.app.smartspace.SmartspaceTarget.class); mSmartspaceActionId = parcel.readString(); mEventType = parcel.readInt(); } diff --git a/core/java/android/app/time/ExternalTimeSuggestion.java b/core/java/android/app/time/ExternalTimeSuggestion.java index 8e281c07c45d..0f98b4451983 100644 --- a/core/java/android/app/time/ExternalTimeSuggestion.java +++ b/core/java/android/app/time/ExternalTimeSuggestion.java @@ -101,11 +101,11 @@ public final class ExternalTimeSuggestion implements Parcelable { } private static ExternalTimeSuggestion createFromParcel(Parcel in) { - TimestampedValue<Long> utcTime = in.readParcelable(null /* classLoader */); + TimestampedValue<Long> utcTime = in.readParcelable(null /* classLoader */, android.os.TimestampedValue.class); ExternalTimeSuggestion suggestion = new ExternalTimeSuggestion(utcTime.getReferenceTimeMillis(), utcTime.getValue()); @SuppressWarnings("unchecked") - ArrayList<String> debugInfo = (ArrayList<String>) in.readArrayList(null /* classLoader */); + ArrayList<String> debugInfo = (ArrayList<String>) in.readArrayList(null /* classLoader */, java.lang.String.class); suggestion.mDebugInfo = debugInfo; return suggestion; } diff --git a/core/java/android/app/time/TimeCapabilitiesAndConfig.java b/core/java/android/app/time/TimeCapabilitiesAndConfig.java index 4a1044760064..71fce14a80b1 100644 --- a/core/java/android/app/time/TimeCapabilitiesAndConfig.java +++ b/core/java/android/app/time/TimeCapabilitiesAndConfig.java @@ -59,8 +59,8 @@ public final class TimeCapabilitiesAndConfig implements Parcelable { @NonNull private static TimeCapabilitiesAndConfig readFromParcel(Parcel in) { - TimeCapabilities capabilities = in.readParcelable(null); - TimeConfiguration configuration = in.readParcelable(null); + TimeCapabilities capabilities = in.readParcelable(null, android.app.time.TimeCapabilities.class); + TimeConfiguration configuration = in.readParcelable(null, android.app.time.TimeConfiguration.class); return new TimeCapabilitiesAndConfig(capabilities, configuration); } diff --git a/core/java/android/app/time/TimeZoneCapabilitiesAndConfig.java b/core/java/android/app/time/TimeZoneCapabilitiesAndConfig.java index a9ea76f77958..cd91b0431b28 100644 --- a/core/java/android/app/time/TimeZoneCapabilitiesAndConfig.java +++ b/core/java/android/app/time/TimeZoneCapabilitiesAndConfig.java @@ -61,8 +61,8 @@ public final class TimeZoneCapabilitiesAndConfig implements Parcelable { @NonNull private static TimeZoneCapabilitiesAndConfig createFromParcel(Parcel in) { - TimeZoneCapabilities capabilities = in.readParcelable(null); - TimeZoneConfiguration configuration = in.readParcelable(null); + TimeZoneCapabilities capabilities = in.readParcelable(null, android.app.time.TimeZoneCapabilities.class); + TimeZoneConfiguration configuration = in.readParcelable(null, android.app.time.TimeZoneConfiguration.class); return new TimeZoneCapabilitiesAndConfig(capabilities, configuration); } diff --git a/core/java/android/app/timedetector/GnssTimeSuggestion.java b/core/java/android/app/timedetector/GnssTimeSuggestion.java index 6478a2dd2aa9..8ccff6227c79 100644 --- a/core/java/android/app/timedetector/GnssTimeSuggestion.java +++ b/core/java/android/app/timedetector/GnssTimeSuggestion.java @@ -66,10 +66,10 @@ public final class GnssTimeSuggestion implements Parcelable { } private static GnssTimeSuggestion createFromParcel(Parcel in) { - TimestampedValue<Long> utcTime = in.readParcelable(null /* classLoader */); + TimestampedValue<Long> utcTime = in.readParcelable(null /* classLoader */, android.os.TimestampedValue.class); GnssTimeSuggestion suggestion = new GnssTimeSuggestion(utcTime); @SuppressWarnings("unchecked") - ArrayList<String> debugInfo = (ArrayList<String>) in.readArrayList(null /* classLoader */); + ArrayList<String> debugInfo = (ArrayList<String>) in.readArrayList(null /* classLoader */, java.lang.String.class); suggestion.mDebugInfo = debugInfo; return suggestion; } diff --git a/core/java/android/app/timedetector/ManualTimeSuggestion.java b/core/java/android/app/timedetector/ManualTimeSuggestion.java index 299e9518e329..1699a5f8c8ae 100644 --- a/core/java/android/app/timedetector/ManualTimeSuggestion.java +++ b/core/java/android/app/timedetector/ManualTimeSuggestion.java @@ -66,10 +66,10 @@ public final class ManualTimeSuggestion implements Parcelable { } private static ManualTimeSuggestion createFromParcel(Parcel in) { - TimestampedValue<Long> utcTime = in.readParcelable(null /* classLoader */); + TimestampedValue<Long> utcTime = in.readParcelable(null /* classLoader */, android.os.TimestampedValue.class); ManualTimeSuggestion suggestion = new ManualTimeSuggestion(utcTime); @SuppressWarnings("unchecked") - ArrayList<String> debugInfo = (ArrayList<String>) in.readArrayList(null /* classLoader */); + ArrayList<String> debugInfo = (ArrayList<String>) in.readArrayList(null /* classLoader */, java.lang.String.class); suggestion.mDebugInfo = debugInfo; return suggestion; } diff --git a/core/java/android/app/timedetector/NetworkTimeSuggestion.java b/core/java/android/app/timedetector/NetworkTimeSuggestion.java index a5259c27ec42..20300832d2fc 100644 --- a/core/java/android/app/timedetector/NetworkTimeSuggestion.java +++ b/core/java/android/app/timedetector/NetworkTimeSuggestion.java @@ -66,10 +66,10 @@ public final class NetworkTimeSuggestion implements Parcelable { } private static NetworkTimeSuggestion createFromParcel(Parcel in) { - TimestampedValue<Long> utcTime = in.readParcelable(null /* classLoader */); + TimestampedValue<Long> utcTime = in.readParcelable(null /* classLoader */, android.os.TimestampedValue.class); NetworkTimeSuggestion suggestion = new NetworkTimeSuggestion(utcTime); @SuppressWarnings("unchecked") - ArrayList<String> debugInfo = (ArrayList<String>) in.readArrayList(null /* classLoader */); + ArrayList<String> debugInfo = (ArrayList<String>) in.readArrayList(null /* classLoader */, java.lang.String.class); suggestion.mDebugInfo = debugInfo; return suggestion; } diff --git a/core/java/android/app/timedetector/TelephonyTimeSuggestion.java b/core/java/android/app/timedetector/TelephonyTimeSuggestion.java index 6c3a304ed3a7..52d0bbea701e 100644 --- a/core/java/android/app/timedetector/TelephonyTimeSuggestion.java +++ b/core/java/android/app/timedetector/TelephonyTimeSuggestion.java @@ -77,10 +77,10 @@ public final class TelephonyTimeSuggestion implements Parcelable { private static TelephonyTimeSuggestion createFromParcel(Parcel in) { int slotIndex = in.readInt(); TelephonyTimeSuggestion suggestion = new TelephonyTimeSuggestion.Builder(slotIndex) - .setUtcTime(in.readParcelable(null /* classLoader */)) + .setUtcTime(in.readParcelable(null /* classLoader */, android.os.TimestampedValue.class)) .build(); @SuppressWarnings("unchecked") - ArrayList<String> debugInfo = (ArrayList<String>) in.readArrayList(null /* classLoader */); + ArrayList<String> debugInfo = (ArrayList<String>) in.readArrayList(null /* classLoader */, java.lang.String.class); if (debugInfo != null) { suggestion.addDebugInfo(debugInfo); } diff --git a/core/java/android/app/timezone/RulesState.java b/core/java/android/app/timezone/RulesState.java index ee88ec54d08f..516ad033a936 100644 --- a/core/java/android/app/timezone/RulesState.java +++ b/core/java/android/app/timezone/RulesState.java @@ -195,12 +195,12 @@ public final class RulesState implements Parcelable { private static RulesState createFromParcel(Parcel in) { String baseRulesVersion = in.readString(); - DistroFormatVersion distroFormatVersionSupported = in.readParcelable(null); + DistroFormatVersion distroFormatVersionSupported = in.readParcelable(null, android.app.timezone.DistroFormatVersion.class); boolean operationInProgress = in.readByte() == BYTE_TRUE; int distroStagedState = in.readByte(); - DistroRulesVersion stagedDistroRulesVersion = in.readParcelable(null); + DistroRulesVersion stagedDistroRulesVersion = in.readParcelable(null, android.app.timezone.DistroRulesVersion.class); int installedDistroStatus = in.readByte(); - DistroRulesVersion installedDistroRulesVersion = in.readParcelable(null); + DistroRulesVersion installedDistroRulesVersion = in.readParcelable(null, android.app.timezone.DistroRulesVersion.class); return new RulesState(baseRulesVersion, distroFormatVersionSupported, operationInProgress, distroStagedState, stagedDistroRulesVersion, installedDistroStatus, installedDistroRulesVersion); diff --git a/core/java/android/app/timezonedetector/ManualTimeZoneSuggestion.java b/core/java/android/app/timezonedetector/ManualTimeZoneSuggestion.java index 01a60b1fa025..387319edc5e7 100644 --- a/core/java/android/app/timezonedetector/ManualTimeZoneSuggestion.java +++ b/core/java/android/app/timezonedetector/ManualTimeZoneSuggestion.java @@ -65,7 +65,7 @@ public final class ManualTimeZoneSuggestion implements Parcelable { String zoneId = in.readString(); ManualTimeZoneSuggestion suggestion = new ManualTimeZoneSuggestion(zoneId); @SuppressWarnings("unchecked") - ArrayList<String> debugInfo = (ArrayList<String>) in.readArrayList(null /* classLoader */); + ArrayList<String> debugInfo = (ArrayList<String>) in.readArrayList(null /* classLoader */, java.lang.String.class); suggestion.mDebugInfo = debugInfo; return suggestion; } diff --git a/core/java/android/app/timezonedetector/TelephonyTimeZoneSuggestion.java b/core/java/android/app/timezonedetector/TelephonyTimeZoneSuggestion.java index eb6750f06d25..e5b4e46ba285 100644 --- a/core/java/android/app/timezonedetector/TelephonyTimeZoneSuggestion.java +++ b/core/java/android/app/timezonedetector/TelephonyTimeZoneSuggestion.java @@ -165,7 +165,7 @@ public final class TelephonyTimeZoneSuggestion implements Parcelable { .setQuality(in.readInt()) .build(); List<String> debugInfo = - in.readArrayList(TelephonyTimeZoneSuggestion.class.getClassLoader()); + in.readArrayList(TelephonyTimeZoneSuggestion.class.getClassLoader(), java.lang.String.class); if (debugInfo != null) { suggestion.addDebugInfo(debugInfo); } diff --git a/core/java/android/app/trust/ITrustListener.aidl b/core/java/android/app/trust/ITrustListener.aidl index 65b024999a5b..6b9d2c73450e 100644 --- a/core/java/android/app/trust/ITrustListener.aidl +++ b/core/java/android/app/trust/ITrustListener.aidl @@ -16,13 +16,16 @@ */ package android.app.trust; +import java.util.List; + /** * Private API to be notified about trust changes. * * {@hide} */ oneway interface ITrustListener { - void onTrustChanged(boolean enabled, int userId, int flags); + void onTrustChanged(boolean enabled, int userId, int flags, + in List<String> trustGrantedMessages); void onTrustManagedChanged(boolean managed, int userId); void onTrustError(in CharSequence message); }
\ No newline at end of file diff --git a/core/java/android/app/trust/ITrustManager.aidl b/core/java/android/app/trust/ITrustManager.aidl index 1291766c00d8..edabccf23c2c 100644 --- a/core/java/android/app/trust/ITrustManager.aidl +++ b/core/java/android/app/trust/ITrustManager.aidl @@ -26,6 +26,7 @@ import android.hardware.biometrics.BiometricSourceType; */ interface ITrustManager { void reportUnlockAttempt(boolean successful, int userId); + void reportUserRequestedUnlock(int userId); void reportUnlockLockout(int timeoutMs, int userId); void reportEnabledTrustAgentsChanged(int userId); void registerTrustListener(in ITrustListener trustListener); diff --git a/core/java/android/app/trust/OWNERS b/core/java/android/app/trust/OWNERS new file mode 100644 index 000000000000..e2c6ce15b51e --- /dev/null +++ b/core/java/android/app/trust/OWNERS @@ -0,0 +1 @@ +include /core/java/android/service/trust/OWNERS diff --git a/core/java/android/app/trust/TrustManager.java b/core/java/android/app/trust/TrustManager.java index e214007c1850..70b7de0767e4 100644 --- a/core/java/android/app/trust/TrustManager.java +++ b/core/java/android/app/trust/TrustManager.java @@ -29,6 +29,9 @@ import android.os.Message; import android.os.RemoteException; import android.util.ArrayMap; +import java.util.ArrayList; +import java.util.List; + /** * See {@link com.android.server.trust.TrustManagerService} * @hide @@ -43,6 +46,7 @@ public class TrustManager { private static final String TAG = "TrustManager"; private static final String DATA_FLAGS = "initiatedByUser"; private static final String DATA_MESSAGE = "message"; + private static final String DATA_GRANTED_MESSAGES = "grantedMessages"; private final ITrustManager mService; private final ArrayMap<TrustListener, ITrustListener> mTrustListeners; @@ -85,6 +89,19 @@ public class TrustManager { } /** + * Reports that the user {@code userId} is likely interested in unlocking the device. + * + * Requires the {@link android.Manifest.permission#ACCESS_KEYGUARD_SECURE_STORAGE} permission. + */ + public void reportUserRequestedUnlock(int userId) { + try { + mService.reportUserRequestedUnlock(userId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Reports that user {@param userId} has entered a temporary device lockout. * * This generally occurs when the user has unsuccessfully tried to unlock the device too many @@ -139,12 +156,15 @@ public class TrustManager { try { ITrustListener.Stub iTrustListener = new ITrustListener.Stub() { @Override - public void onTrustChanged(boolean enabled, int userId, int flags) { + public void onTrustChanged(boolean enabled, int userId, int flags, + List<String> trustGrantedMessages) { Message m = mHandler.obtainMessage(MSG_TRUST_CHANGED, (enabled ? 1 : 0), userId, trustListener); if (flags != 0) { m.getData().putInt(DATA_FLAGS, flags); } + m.getData().putCharSequenceArrayList( + DATA_GRANTED_MESSAGES, (ArrayList) trustGrantedMessages); m.sendToTarget(); } @@ -231,14 +251,15 @@ public class TrustManager { switch(msg.what) { case MSG_TRUST_CHANGED: int flags = msg.peekData() != null ? msg.peekData().getInt(DATA_FLAGS) : 0; - ((TrustListener)msg.obj).onTrustChanged(msg.arg1 != 0, msg.arg2, flags); + ((TrustListener) msg.obj).onTrustChanged(msg.arg1 != 0, msg.arg2, flags, + msg.getData().getStringArrayList(DATA_GRANTED_MESSAGES)); break; case MSG_TRUST_MANAGED_CHANGED: ((TrustListener)msg.obj).onTrustManagedChanged(msg.arg1 != 0, msg.arg2); break; case MSG_TRUST_ERROR: final CharSequence message = msg.peekData().getCharSequence(DATA_MESSAGE); - ((TrustListener)msg.obj).onTrustError(message); + ((TrustListener) msg.obj).onTrustError(message); } } }; @@ -252,8 +273,11 @@ public class TrustManager { * @param flags Flags specified by the trust agent when granting trust. See * {@link android.service.trust.TrustAgentService#grantTrust(CharSequence, long, int) * TrustAgentService.grantTrust(CharSequence, long, int)}. + * @param trustGrantedMessages Messages to display to the user when trust has been granted + * by one or more trust agents. */ - void onTrustChanged(boolean enabled, int userId, int flags); + void onTrustChanged(boolean enabled, int userId, int flags, + List<String> trustGrantedMessages); /** * Reports that whether trust is managed has changed diff --git a/core/java/android/app/usage/CacheQuotaHint.java b/core/java/android/app/usage/CacheQuotaHint.java index 0ccb058d11cf..ba6bcdc936ba 100644 --- a/core/java/android/app/usage/CacheQuotaHint.java +++ b/core/java/android/app/usage/CacheQuotaHint.java @@ -148,7 +148,7 @@ public final class CacheQuotaHint implements Parcelable { return builder.setVolumeUuid(in.readString()) .setUid(in.readInt()) .setQuota(in.readLong()) - .setUsageStats(in.readParcelable(UsageStats.class.getClassLoader())) + .setUsageStats(in.readParcelable(UsageStats.class.getClassLoader(), android.app.usage.UsageStats.class)) .build(); } diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java index 630b8d26f52e..d7e197ee5ed1 100644 --- a/core/java/android/app/usage/UsageStatsManager.java +++ b/core/java/android/app/usage/UsageStatsManager.java @@ -1277,6 +1277,28 @@ public final class UsageStatsManager { } } + /** @hide */ + public static String standbyBucketToString(int standbyBucket) { + switch (standbyBucket) { + case STANDBY_BUCKET_EXEMPTED: + return "EXEMPTED"; + case STANDBY_BUCKET_ACTIVE: + return "ACTIVE"; + case STANDBY_BUCKET_WORKING_SET: + return "WORKING_SET"; + case STANDBY_BUCKET_FREQUENT: + return "FREQUENT"; + case STANDBY_BUCKET_RARE: + return "RARE"; + case STANDBY_BUCKET_RESTRICTED: + return "RESTRICTED"; + case STANDBY_BUCKET_NEVER: + return "NEVER"; + default: + return String.valueOf(standbyBucket); + } + } + /** * {@hide} * Temporarily allowlist the specified app for a short duration. This is to allow an app diff --git a/core/java/android/app/wallpapereffectsgeneration/OWNERS b/core/java/android/app/wallpapereffectsgeneration/OWNERS new file mode 100644 index 000000000000..2bc01541a939 --- /dev/null +++ b/core/java/android/app/wallpapereffectsgeneration/OWNERS @@ -0,0 +1,5 @@ +susharon@google.com +shanh@google.com +huiwu@google.com +srazdan@google.com + diff --git a/core/java/android/companion/AssociationRequest.java b/core/java/android/companion/AssociationRequest.java index 18a59d863c46..1d2f06d34c8c 100644 --- a/core/java/android/companion/AssociationRequest.java +++ b/core/java/android/companion/AssociationRequest.java @@ -595,7 +595,7 @@ public final class AssociationRequest implements Parcelable { boolean forceConfirmation = (flg & 0x20) != 0; boolean skipPrompt = (flg & 0x400) != 0; List<DeviceFilter<?>> deviceFilters = new ArrayList<>(); - in.readParcelableList(deviceFilters, DeviceFilter.class.getClassLoader()); + in.readParcelableList(deviceFilters, DeviceFilter.class.getClassLoader(), (Class<android.companion.DeviceFilter<?>>) (Class<?>) android.companion.DeviceFilter.class); String deviceProfile = (flg & 0x4) == 0 ? null : in.readString(); CharSequence displayName = (flg & 0x8) == 0 ? null : (CharSequence) in.readCharSequence(); String packageName = (flg & 0x40) == 0 ? null : in.readString(); diff --git a/core/java/android/companion/BluetoothDeviceFilter.java b/core/java/android/companion/BluetoothDeviceFilter.java index be663f7bdc1d..e0018f4bad42 100644 --- a/core/java/android/companion/BluetoothDeviceFilter.java +++ b/core/java/android/companion/BluetoothDeviceFilter.java @@ -70,7 +70,7 @@ public final class BluetoothDeviceFilter implements DeviceFilter<BluetoothDevice } private static List<ParcelUuid> readUuids(Parcel in) { - return in.readParcelableList(new ArrayList<>(), ParcelUuid.class.getClassLoader()); + return in.readParcelableList(new ArrayList<>(), ParcelUuid.class.getClassLoader(), android.os.ParcelUuid.class); } /** @hide */ diff --git a/core/java/android/companion/BluetoothLeDeviceFilter.java b/core/java/android/companion/BluetoothLeDeviceFilter.java index 58898cc095be..e6091f04a72a 100644 --- a/core/java/android/companion/BluetoothLeDeviceFilter.java +++ b/core/java/android/companion/BluetoothLeDeviceFilter.java @@ -252,7 +252,7 @@ public final class BluetoothLeDeviceFilter implements DeviceFilter<ScanResult> { public BluetoothLeDeviceFilter createFromParcel(Parcel in) { Builder builder = new Builder() .setNamePattern(patternFromString(in.readString())) - .setScanFilter(in.readParcelable(null)); + .setScanFilter(in.readParcelable(null, android.bluetooth.le.ScanFilter.class)); byte[] rawDataFilter = in.createByteArray(); byte[] rawDataFilterMask = in.createByteArray(); if (rawDataFilter != null) { diff --git a/core/java/android/companion/virtual/IVirtualDevice.aidl b/core/java/android/companion/virtual/IVirtualDevice.aidl index 85855bedfbeb..339e9a2ff1bc 100644 --- a/core/java/android/companion/virtual/IVirtualDevice.aidl +++ b/core/java/android/companion/virtual/IVirtualDevice.aidl @@ -18,6 +18,7 @@ package android.companion.virtual; import android.app.PendingIntent; import android.graphics.Point; +import android.graphics.PointF; import android.hardware.input.VirtualKeyEvent; import android.hardware.input.VirtualMouseButtonEvent; import android.hardware.input.VirtualMouseRelativeEvent; @@ -75,4 +76,5 @@ interface IVirtualDevice { */ void launchPendingIntent( int displayId, in PendingIntent pendingIntent, in ResultReceiver resultReceiver); + PointF getCursorPosition(IBinder token); } diff --git a/core/java/android/companion/virtual/IVirtualDeviceActivityListener.aidl b/core/java/android/companion/virtual/IVirtualDeviceActivityListener.aidl new file mode 100644 index 000000000000..53af4c5da761 --- /dev/null +++ b/core/java/android/companion/virtual/IVirtualDeviceActivityListener.aidl @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.companion.virtual; + +import android.content.ComponentName; + +/** + * Interface to listen for activity changes in a virtual device. + * + * @hide + */ +interface IVirtualDeviceActivityListener { + + /** + * Called when the top activity is changed. + * + * @param displayId The display ID on which the activity change happened. + * @param topActivity The component name of the top activity. + */ + void onTopActivityChanged(int displayId, in ComponentName topActivity); + + /** + * Called when the display becomes empty (e.g. if the user hits back on the last + * activity of the root task). + * + * @param displayId The display ID that became empty. + */ + void onDisplayEmpty(int displayId); +} diff --git a/core/java/android/companion/virtual/IVirtualDeviceManager.aidl b/core/java/android/companion/virtual/IVirtualDeviceManager.aidl index d80bee668f18..b7f826a940a8 100644 --- a/core/java/android/companion/virtual/IVirtualDeviceManager.aidl +++ b/core/java/android/companion/virtual/IVirtualDeviceManager.aidl @@ -17,6 +17,7 @@ package android.companion.virtual; import android.companion.virtual.IVirtualDevice; +import android.companion.virtual.IVirtualDeviceActivityListener; import android.companion.virtual.VirtualDeviceParams; /** @@ -39,5 +40,5 @@ interface IVirtualDeviceManager { */ IVirtualDevice createVirtualDevice( in IBinder token, String packageName, int associationId, - in VirtualDeviceParams params); + in VirtualDeviceParams params, in IVirtualDeviceActivityListener activityListener); } diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java index 8ab668873f33..64f16ac3e1b3 100644 --- a/core/java/android/companion/virtual/VirtualDeviceManager.java +++ b/core/java/android/companion/virtual/VirtualDeviceManager.java @@ -16,7 +16,6 @@ package android.companion.virtual; -import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; @@ -26,6 +25,7 @@ import android.annotation.SystemService; import android.app.Activity; import android.app.PendingIntent; import android.companion.AssociationInfo; +import android.content.ComponentName; import android.content.Context; import android.graphics.Point; import android.hardware.display.DisplayManager; @@ -41,12 +41,9 @@ import android.os.IBinder; import android.os.Looper; import android.os.RemoteException; import android.os.ResultReceiver; +import android.util.ArrayMap; import android.view.Surface; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; import java.util.concurrent.Executor; /** @@ -61,23 +58,6 @@ public final class VirtualDeviceManager { private static final boolean DEBUG = false; private static final String LOG_TAG = "VirtualDeviceManager"; - /** @hide */ - @IntDef(prefix = "DISPLAY_FLAG_", - flag = true, - value = {DISPLAY_FLAG_TRUSTED}) - @Retention(RetentionPolicy.SOURCE) - @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE}) - public @interface DisplayFlags {} - - /** - * Indicates that the display is trusted to show system decorations and receive inputs without - * users' touch. - * - * @see DisplayManager#VIRTUAL_DISPLAY_FLAG_TRUSTED - * @hide // TODO(b/194949534): Unhide this API - */ - public static final int DISPLAY_FLAG_TRUSTED = 1; - private static final int DEFAULT_VIRTUAL_DISPLAY_FLAGS = DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC | DisplayManager.VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT @@ -102,16 +82,14 @@ public final class VirtualDeviceManager { * @param associationId The association ID as returned by {@link AssociationInfo#getId()} from * Companion Device Manager. Virtual devices must have a corresponding association with CDM in * order to be created. - * @hide */ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) @Nullable - public VirtualDevice createVirtualDevice(int associationId, VirtualDeviceParams params) { - // TODO(b/194949534): Unhide this API + public VirtualDevice createVirtualDevice( + int associationId, + @NonNull VirtualDeviceParams params) { try { - IVirtualDevice virtualDevice = mService.createVirtualDevice( - new Binder(), mContext.getPackageName(), associationId, params); - return new VirtualDevice(mContext, virtualDevice); + return new VirtualDevice(mService, mContext, associationId, params); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -128,10 +106,49 @@ public final class VirtualDeviceManager { private final Context mContext; private final IVirtualDevice mVirtualDevice; + private final ArrayMap<ActivityListener, ActivityListenerDelegate> mActivityListeners = + new ArrayMap<>(); + private final IVirtualDeviceActivityListener mActivityListenerBinder = + new IVirtualDeviceActivityListener.Stub() { - private VirtualDevice(Context context, IVirtualDevice virtualDevice) { + @Override + public void onTopActivityChanged(int displayId, ComponentName topActivity) { + final long token = Binder.clearCallingIdentity(); + try { + for (int i = 0; i < mActivityListeners.size(); i++) { + mActivityListeners.valueAt(i) + .onTopActivityChanged(displayId, topActivity); + } + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public void onDisplayEmpty(int displayId) { + final long token = Binder.clearCallingIdentity(); + try { + for (int i = 0; i < mActivityListeners.size(); i++) { + mActivityListeners.valueAt(i).onDisplayEmpty(displayId); + } + } finally { + Binder.restoreCallingIdentity(token); + } + } + }; + + private VirtualDevice( + IVirtualDeviceManager service, + Context context, + int associationId, + VirtualDeviceParams params) throws RemoteException { mContext = context.getApplicationContext(); - mVirtualDevice = virtualDevice; + mVirtualDevice = service.createVirtualDevice( + new Binder(), + mContext.getPackageName(), + associationId, + params, + mActivityListenerBinder); } /** @@ -159,7 +176,7 @@ public final class VirtualDeviceManager { mVirtualDevice.launchPendingIntent( displayId, pendingIntent, - new ResultReceiver(new Handler(Looper.myLooper())) { + new ResultReceiver(new Handler(Looper.getMainLooper())) { @Override protected void onReceiveResult(int resultCode, Bundle resultData) { super.onReceiveResult(resultCode, resultData); @@ -179,7 +196,9 @@ public final class VirtualDeviceManager { /** * Creates a virtual display for this virtual device. All displays created on the same - * device belongs to the same display group. + * device belongs to the same display group. Requires the ADD_TRUSTED_DISPLAY permission + * to create a virtual display which is not in the default DisplayGroup, and to create + * trusted displays. * * @param width The width of the virtual display in pixels, must be greater than 0. * @param height The height of the virtual display in pixels, must be greater than 0. @@ -187,7 +206,12 @@ public final class VirtualDeviceManager { * @param surface The surface to which the content of the virtual display should * be rendered, or null if there is none initially. The surface can also be set later using * {@link VirtualDisplay#setSurface(Surface)}. - * @param flags Either 0, or {@link #DISPLAY_FLAG_TRUSTED}. + * @param flags A combination of virtual display flags accepted by + * {@link DisplayManager#createVirtualDisplay}. In addition, the following flags are + * automatically set for all virtual devices: + * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_PUBLIC VIRTUAL_DISPLAY_FLAG_PUBLIC} and + * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY + * VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY}. * @param callback Callback to call when the state of the {@link VirtualDisplay} changes * @param handler The handler on which the listener should be invoked, or null * if the listener should be invoked on the calling thread's looper. @@ -195,9 +219,7 @@ public final class VirtualDeviceManager { * not create the virtual display. * * @see DisplayManager#createVirtualDisplay - * @hide */ - // TODO(b/194949534): Unhide this API // Suppress "ExecutorRegistration" because DisplayManager.createVirtualDisplay takes a // handler @SuppressLint("ExecutorRegistration") @@ -207,7 +229,7 @@ public final class VirtualDeviceManager { int height, int densityDpi, @Nullable Surface surface, - @DisplayFlags int flags, + int flags, @Nullable Handler handler, @Nullable VirtualDisplay.Callback callback) { // TODO(b/205343547): Handle display groups properly instead of creating a new display @@ -246,7 +268,6 @@ public final class VirtualDeviceManager { * @param inputDeviceName the name to call this input device * @param vendorId the vendor id * @param productId the product id - * @hide */ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) @NonNull @@ -273,7 +294,6 @@ public final class VirtualDeviceManager { * @param inputDeviceName the name to call this input device * @param vendorId the vendor id * @param productId the product id - * @hide */ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) @NonNull @@ -300,7 +320,6 @@ public final class VirtualDeviceManager { * @param inputDeviceName the name to call this input device * @param vendorId the vendor id * @param productId the product id - * @hide */ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) @NonNull @@ -328,12 +347,8 @@ public final class VirtualDeviceManager { * com.android.server.companion.virtual.VirtualDeviceImpl#getBaseVirtualDisplayFlags()} will * be added by DisplayManagerService. */ - private int getVirtualDisplayFlags(@DisplayFlags int flags) { - int virtualDisplayFlags = DEFAULT_VIRTUAL_DISPLAY_FLAGS; - if ((flags & DISPLAY_FLAG_TRUSTED) != 0) { - virtualDisplayFlags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED; - } - return virtualDisplayFlags; + private int getVirtualDisplayFlags(int flags) { + return DEFAULT_VIRTUAL_DISPLAY_FLAGS | flags; } private String getVirtualDisplayName() { @@ -347,6 +362,47 @@ public final class VirtualDeviceManager { throw e.rethrowFromSystemServer(); } } + + /** + * Adds an activity listener to listen for events such as top activity change or virtual + * display task stack became empty. + * + * @param listener The listener to add. + * @see #removeActivityListener(ActivityListener) + * @hide + */ + // TODO(b/194949534): Unhide this API + public void addActivityListener(@NonNull ActivityListener listener) { + addActivityListener(listener, mContext.getMainExecutor()); + } + + /** + * Adds an activity listener to listen for events such as top activity change or virtual + * display task stack became empty. + * + * @param listener The listener to add. + * @param executor The executor where the callback is executed on. + * @see #removeActivityListener(ActivityListener) + * @hide + */ + // TODO(b/194949534): Unhide this API + public void addActivityListener( + @NonNull ActivityListener listener, @NonNull Executor executor) { + mActivityListeners.put(listener, new ActivityListenerDelegate(listener, executor)); + } + + /** + * Removes an activity listener previously added with + * {@link #addActivityListener}. + * + * @param listener The listener to remove. + * @see #addActivityListener(ActivityListener, Executor) + * @hide + */ + // TODO(b/194949534): Unhide this API + public void removeActivityListener(@NonNull ActivityListener listener) { + mActivityListeners.remove(listener); + } } /** @@ -366,4 +422,50 @@ public final class VirtualDeviceManager { */ void onLaunchFailed(); } + + /** + * Listener for activity changes in this virtual device. + * + * @hide + */ + // TODO(b/194949534): Unhide this API + public interface ActivityListener { + + /** + * Called when the top activity is changed. + * + * @param displayId The display ID on which the activity change happened. + * @param topActivity The component name of the top activity. + */ + void onTopActivityChanged(int displayId, @NonNull ComponentName topActivity); + + /** + * Called when the display becomes empty (e.g. if the user hits back on the last + * activity of the root task). + * + * @param displayId The display ID that became empty. + */ + void onDisplayEmpty(int displayId); + } + + /** + * A wrapper for {@link ActivityListener} that executes callbacks on the given executor. + */ + private static class ActivityListenerDelegate { + @NonNull private final ActivityListener mActivityListener; + @NonNull private final Executor mExecutor; + + ActivityListenerDelegate(@NonNull ActivityListener listener, @NonNull Executor executor) { + mActivityListener = listener; + mExecutor = executor; + } + + public void onTopActivityChanged(int displayId, ComponentName topActivity) { + mExecutor.execute(() -> mActivityListener.onTopActivityChanged(displayId, topActivity)); + } + + public void onDisplayEmpty(int displayId) { + mExecutor.execute(() -> mActivityListener.onDisplayEmpty(displayId)); + } + } } diff --git a/core/java/android/companion/virtual/VirtualDeviceParams.java b/core/java/android/companion/virtual/VirtualDeviceParams.java index d61d4741637a..2ddfeb4c8ab5 100644 --- a/core/java/android/companion/virtual/VirtualDeviceParams.java +++ b/core/java/android/companion/virtual/VirtualDeviceParams.java @@ -20,7 +20,10 @@ import static android.Manifest.permission.ADD_ALWAYS_UNLOCKED_DISPLAY; import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.RequiresPermission; +import android.annotation.SystemApi; +import android.content.ComponentName; import android.os.Parcel; import android.os.Parcelable; import android.os.UserHandle; @@ -39,7 +42,7 @@ import java.util.Set; * * @hide */ -// TODO(b/194949534): Unhide this API +@SystemApi public final class VirtualDeviceParams implements Parcelable { /** @hide */ @@ -51,32 +54,36 @@ public final class VirtualDeviceParams implements Parcelable { /** * Indicates that the lock state of the virtual device should be always locked. - * - * @hide // TODO(b/194949534): Unhide this API */ public static final int LOCK_STATE_ALWAYS_LOCKED = 0; /** * Indicates that the lock state of the virtual device should be always unlocked. - * - * @hide // TODO(b/194949534): Unhide this API */ public static final int LOCK_STATE_ALWAYS_UNLOCKED = 1; private final int mLockState; private final ArraySet<UserHandle> mUsersWithMatchingAccounts; + @Nullable private final ArraySet<ComponentName> mAllowedActivities; + @Nullable private final ArraySet<ComponentName> mBlockedActivities; private VirtualDeviceParams( @LockState int lockState, - @NonNull Set<UserHandle> usersWithMatchingAccounts) { + @NonNull Set<UserHandle> usersWithMatchingAccounts, + @Nullable Set<ComponentName> allowedActivities, + @Nullable Set<ComponentName> blockedActivities) { mLockState = lockState; mUsersWithMatchingAccounts = new ArraySet<>(usersWithMatchingAccounts); + mAllowedActivities = allowedActivities == null ? null : new ArraySet<>(allowedActivities); + mBlockedActivities = blockedActivities == null ? null : new ArraySet<>(blockedActivities); } @SuppressWarnings("unchecked") private VirtualDeviceParams(Parcel parcel) { mLockState = parcel.readInt(); mUsersWithMatchingAccounts = (ArraySet<UserHandle>) parcel.readArraySet(null); + mAllowedActivities = (ArraySet<ComponentName>) parcel.readArraySet(null); + mBlockedActivities = (ArraySet<ComponentName>) parcel.readArraySet(null); } /** @@ -98,6 +105,35 @@ public final class VirtualDeviceParams implements Parcelable { return Collections.unmodifiableSet(mUsersWithMatchingAccounts); } + /** + * Returns the set of activities allowed to be streamed, or {@code null} if this is not set. + * + * @see Builder#setAllowedActivities(Set) + * @hide // TODO(b/194949534): Unhide this API + */ + @Nullable + public Set<ComponentName> getAllowedActivities() { + if (mAllowedActivities == null) { + return null; + } + return Collections.unmodifiableSet(mAllowedActivities); + } + + /** + * Returns the set of activities that are blocked from streaming, or {@code null} if this is not + * set. + * + * @see Builder#setBlockedActivities(Set) + * @hide // TODO(b/194949534): Unhide this API + */ + @Nullable + public Set<ComponentName> getBlockedActivities() { + if (mBlockedActivities == null) { + return null; + } + return Collections.unmodifiableSet(mBlockedActivities); + } + @Override public int describeContents() { return 0; @@ -107,6 +143,8 @@ public final class VirtualDeviceParams implements Parcelable { public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeInt(mLockState); dest.writeArraySet(mUsersWithMatchingAccounts); + dest.writeArraySet(mAllowedActivities); + dest.writeArraySet(mBlockedActivities); } @Override @@ -118,8 +156,10 @@ public final class VirtualDeviceParams implements Parcelable { return false; } VirtualDeviceParams that = (VirtualDeviceParams) o; - return mLockState == that.mLockState && mUsersWithMatchingAccounts.equals( - that.mUsersWithMatchingAccounts); + return mLockState == that.mLockState + && mUsersWithMatchingAccounts.equals(that.mUsersWithMatchingAccounts) + && Objects.equals(mAllowedActivities, that.mAllowedActivities) + && Objects.equals(mBlockedActivities, that.mBlockedActivities); } @Override @@ -128,13 +168,17 @@ public final class VirtualDeviceParams implements Parcelable { } @Override + @NonNull public String toString() { return "VirtualDeviceParams(" + " mLockState=" + mLockState + " mUsersWithMatchingAccounts=" + mUsersWithMatchingAccounts + + " mAllowedActivities=" + mAllowedActivities + + " mBlockedActivities=" + mBlockedActivities + ")"; } + @NonNull public static final Parcelable.Creator<VirtualDeviceParams> CREATOR = new Parcelable.Creator<VirtualDeviceParams>() { public VirtualDeviceParams createFromParcel(Parcel in) { @@ -153,6 +197,8 @@ public final class VirtualDeviceParams implements Parcelable { private @LockState int mLockState = LOCK_STATE_ALWAYS_LOCKED; private Set<UserHandle> mUsersWithMatchingAccounts; + @Nullable private Set<ComponentName> mBlockedActivities; + @Nullable private Set<ComponentName> mAllowedActivities; /** * Sets the lock state of the device. The permission {@code ADD_ALWAYS_UNLOCKED_DISPLAY} @@ -171,12 +217,25 @@ public final class VirtualDeviceParams implements Parcelable { /** * Sets the user handles with matching managed accounts on the remote device to which - * this virtual device is streaming. + * this virtual device is streaming. The caller is responsible for verifying the presence + * and legitimacy of a matching managed account on the remote device. + * + * <p>If the app streaming policy is + * {@link android.app.admin.DevicePolicyManager#NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY + * NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY}, activities not in + * {@code usersWithMatchingAccounts} will be blocked from starting. + * + * <p> If {@code usersWithMatchingAccounts} is empty (the default), streaming is allowed + * only if there is no device policy, or if the nearby streaming policy is + * {@link android.app.admin.DevicePolicyManager#NEARBY_STREAMING_ENABLED + * NEARBY_STREAMING_ENABLED}. * * @param usersWithMatchingAccounts A set of user handles with matching managed * accounts on the remote device this is streaming to. + * * @see android.app.admin.DevicePolicyManager#NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY */ + @NonNull public Builder setUsersWithMatchingAccounts( @NonNull Set<UserHandle> usersWithMatchingAccounts) { mUsersWithMatchingAccounts = usersWithMatchingAccounts; @@ -184,6 +243,54 @@ public final class VirtualDeviceParams implements Parcelable { } /** + * Sets the activities allowed to be launched in the virtual device. If + * {@code allowedActivities} is non-null, all activities other than the ones in the set will + * be blocked from launching. + * + * <p>{@code allowedActivities} and the set in {@link #setBlockedActivities(Set)} cannot + * both be non-null at the same time. + * + * @throws IllegalArgumentException if {@link #setBlockedActivities(Set)} has been set to a + * non-null value. + * + * @param allowedActivities A set of activity {@link ComponentName} allowed to be launched + * in the virtual device. + * @hide // TODO(b/194949534): Unhide this API + */ + public Builder setAllowedActivities(@Nullable Set<ComponentName> allowedActivities) { + if (mBlockedActivities != null && allowedActivities != null) { + throw new IllegalArgumentException( + "Allowed activities and Blocked activities cannot both be set."); + } + mAllowedActivities = allowedActivities; + return this; + } + + /** + * Sets the activities blocked from launching in the virtual device. If the {@code + * blockedActivities} is non-null, activities in the set are blocked from launching in the + * virtual device. + * + * {@code blockedActivities} and the set in {@link #setAllowedActivities(Set)} cannot both + * be non-null at the same time. + * + * @throws IllegalArgumentException if {@link #setAllowedActivities(Set)} has been set to a + * non-null value. + * + * @param blockedActivities A set of {@link ComponentName} to be blocked launching from + * virtual device. + * @hide // TODO(b/194949534): Unhide this API + */ + public Builder setBlockedActivities(@Nullable Set<ComponentName> blockedActivities) { + if (mAllowedActivities != null && blockedActivities != null) { + throw new IllegalArgumentException( + "Allowed activities and Blocked activities cannot both be set."); + } + mBlockedActivities = blockedActivities; + return this; + } + + /** * Builds the {@link VirtualDeviceParams} instance. */ @NonNull @@ -191,7 +298,13 @@ public final class VirtualDeviceParams implements Parcelable { if (mUsersWithMatchingAccounts == null) { mUsersWithMatchingAccounts = Collections.emptySet(); } - return new VirtualDeviceParams(mLockState, mUsersWithMatchingAccounts); + if (mAllowedActivities != null && mBlockedActivities != null) { + // Should never reach here because the setters block this as well. + throw new IllegalStateException( + "Allowed activities and Blocked activities cannot both be set."); + } + return new VirtualDeviceParams(mLockState, mUsersWithMatchingAccounts, + mAllowedActivities, mBlockedActivities); } } } diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java index c714f507242e..b4f23028325b 100644 --- a/core/java/android/content/ContentProvider.java +++ b/core/java/android/content/ContentProvider.java @@ -48,10 +48,13 @@ import android.os.RemoteCallback; import android.os.RemoteException; import android.os.Trace; import android.os.UserHandle; +import android.os.UserManager; import android.os.storage.StorageManager; import android.permission.PermissionCheckerManager; +import android.provider.MediaStore; import android.text.TextUtils; import android.util.Log; +import android.util.SparseBooleanArray; import com.android.internal.annotations.VisibleForTesting; @@ -134,9 +137,18 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall private boolean mExported; private boolean mNoPerms; private boolean mSingleUser; + private SparseBooleanArray mUsersRedirectedToOwner = new SparseBooleanArray(); private ThreadLocal<AttributionSource> mCallingAttributionSource; + /** + * @hide + */ + public static boolean isAuthorityRedirectedForCloneProfile(String authority) { + // For now, only MediaProvider gets redirected. + return MediaStore.AUTHORITY.equals(authority); + } + private Transport mTransport = new Transport(); /** @@ -726,13 +738,47 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall } boolean checkUser(int pid, int uid, Context context) { - if (UserHandle.getUserId(uid) == context.getUserId() || mSingleUser) { + int callingUserId = UserHandle.getUserId(uid); + + if (callingUserId == context.getUserId() || mSingleUser) { return true; } - return context.checkPermission(INTERACT_ACROSS_USERS, pid, uid) - == PackageManager.PERMISSION_GRANTED + if (context.checkPermission(INTERACT_ACROSS_USERS, pid, uid) + == PackageManager.PERMISSION_GRANTED || context.checkPermission(INTERACT_ACROSS_USERS_FULL, pid, uid) - == PackageManager.PERMISSION_GRANTED; + == PackageManager.PERMISSION_GRANTED) { + return true; + } + + if (isAuthorityRedirectedForCloneProfile(mAuthority)) { + if (mUsersRedirectedToOwner.indexOfKey(callingUserId) >= 0) { + return mUsersRedirectedToOwner.get(callingUserId); + } + + // Haven't seen this user yet, look it up + try { + UserHandle callingUser = UserHandle.getUserHandleForUid(uid); + Context callingUserContext = mContext.createPackageContextAsUser("system", + 0, callingUser); + UserManager um = callingUserContext.getSystemService(UserManager.class); + + if (um != null && um.isCloneProfile()) { + UserHandle parent = um.getProfileParent(callingUser); + + if (parent != null && parent.equals(context.getUser())) { + mUsersRedirectedToOwner.put(callingUser.getIdentifier(), true); + return true; + } + } + } catch (PackageManager.NameNotFoundException e) { + // ignore + } + + mUsersRedirectedToOwner.put(UserHandle.getUserId(uid), false); + return false; + } + + return false; } /** diff --git a/core/java/android/content/ContentProviderOperation.java b/core/java/android/content/ContentProviderOperation.java index 30775b19ab00..0c065d9bd402 100644 --- a/core/java/android/content/ContentProviderOperation.java +++ b/core/java/android/content/ContentProviderOperation.java @@ -108,7 +108,7 @@ public class ContentProviderOperation implements Parcelable { mExtras = null; } mSelection = source.readInt() != 0 ? source.readString8() : null; - mSelectionArgs = source.readSparseArray(null); + mSelectionArgs = source.readSparseArray(null, java.lang.Object.class); mExpectedCount = source.readInt() != 0 ? source.readInt() : null; mYieldAllowed = source.readInt() != 0; mExceptionAllowed = source.readInt() != 0; diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 2309fb636d44..ce2efcf4ac7f 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -41,6 +41,7 @@ import android.app.GameManager; import android.app.IApplicationThread; import android.app.IServiceConnection; import android.app.VrManager; +import android.app.ambientcontext.AmbientContextManager; import android.app.people.PeopleManager; import android.app.time.TimeManager; import android.compat.annotation.UnsupportedAppUsage; @@ -3829,7 +3830,7 @@ public abstract class Context { PRINT_SERVICE, CONSUMER_IR_SERVICE, //@hide: TRUST_SERVICE, - TV_IAPP_SERVICE, + TV_INTERACTIVE_APP_SERVICE, TV_INPUT_SERVICE, //@hide: TV_TUNER_RESOURCE_MGR_SERVICE, //@hide: NETWORK_SCORE_SERVICE, @@ -5356,13 +5357,13 @@ public abstract class Context { /** * Use with {@link #getSystemService(String)} to retrieve a - * {@link android.media.tv.interactive.TvIAppManager} for interacting with TV interactive - * applications (TV iApp) on the device. + * {@link android.media.tv.interactive.TvInteractiveAppManager} for interacting with TV + * interactive applications on the device. * * @see #getSystemService(String) - * @see android.media.tv.interactive.TvIAppManager + * @see android.media.tv.interactive.TvInteractiveAppManager */ - public static final String TV_IAPP_SERVICE = "tv_iapp"; + public static final String TV_INTERACTIVE_APP_SERVICE = "tv_interactive_app"; /** * Use with {@link #getSystemService(String)} to retrieve a @@ -5931,6 +5932,17 @@ public abstract class Context { public static final String NEARBY_SERVICE = "nearby"; /** + * Use with {@link #getSystemService(String)} to retrieve a + * {@link android.app.ambientcontext.AmbientContextManager}. + * + * @see #getSystemService(String) + * @see AmbientContextManager + * @hide + */ + @SystemApi + public static final String AMBIENT_CONTEXT_SERVICE = "ambient_context"; + + /** * Determine whether the given permission is allowed for a particular * process and user ID running in the system. * @@ -6417,10 +6429,10 @@ public abstract class Context { * Triggers the asynchronous revocation of a permission. * * @param permName The name of the permission to be revoked. - * @see #selfRevokePermissions(Collection) + * @see #revokeOwnPermissionsOnKill(Collection) */ - public void selfRevokePermission(@NonNull String permName) { - selfRevokePermissions(Collections.singletonList(permName)); + public void revokeOwnPermissionOnKill(@NonNull String permName) { + revokeOwnPermissionsOnKill(Collections.singletonList(permName)); } /** @@ -6445,7 +6457,7 @@ public abstract class Context { * @see PackageManager#getGroupOfPlatformPermission(String, Executor, Consumer) * @see PackageManager#getPlatformPermissionsForGroup(String, Executor, Consumer) */ - public void selfRevokePermissions(@NonNull Collection<String> permissions) { + public void revokeOwnPermissionsOnKill(@NonNull Collection<String> permissions) { throw new AbstractMethodError("Must be overridden in implementing class"); } diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java index 805e499bba46..6ae768a44078 100644 --- a/core/java/android/content/ContextWrapper.java +++ b/core/java/android/content/ContextWrapper.java @@ -1016,8 +1016,8 @@ public class ContextWrapper extends Context { } @Override - public void selfRevokePermissions(@NonNull Collection<String> permissions) { - mBase.selfRevokePermissions(permissions); + public void revokeOwnPermissionsOnKill(@NonNull Collection<String> permissions) { + mBase.revokeOwnPermissionsOnKill(permissions); } @Override diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 58a7d8796ffb..7f00bcb1dccb 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -8190,6 +8190,37 @@ public class Intent implements Parcelable, Cloneable { hasIntentInfo = true; } break; + case "--ed": { + String key = cmd.getNextArgRequired(); + String value = cmd.getNextArgRequired(); + intent.putExtra(key, Double.valueOf(value)); + hasIntentInfo = true; + } + break; + case "--eda": { + String key = cmd.getNextArgRequired(); + String value = cmd.getNextArgRequired(); + String[] strings = value.split(","); + double[] list = new double[strings.length]; + for (int i = 0; i < strings.length; i++) { + list[i] = Double.valueOf(strings[i]); + } + intent.putExtra(key, list); + hasIntentInfo = true; + } + break; + case "--edal": { + String key = cmd.getNextArgRequired(); + String value = cmd.getNextArgRequired(); + String[] strings = value.split(","); + ArrayList<Double> list = new ArrayList<>(strings.length); + for (int i = 0; i < strings.length; i++) { + list.add(Double.valueOf(strings[i])); + } + intent.putExtra(key, list); + hasIntentInfo = true; + } + break; case "--esa": { String key = cmd.getNextArgRequired(); String value = cmd.getNextArgRequired(); @@ -8435,25 +8466,30 @@ public class Intent implements Parcelable, Cloneable { " [--ei <EXTRA_KEY> <EXTRA_INT_VALUE> ...]", " [--el <EXTRA_KEY> <EXTRA_LONG_VALUE> ...]", " [--ef <EXTRA_KEY> <EXTRA_FLOAT_VALUE> ...]", + " [--ed <EXTRA_KEY> <EXTRA_DOUBLE_VALUE> ...]", " [--eu <EXTRA_KEY> <EXTRA_URI_VALUE> ...]", " [--ecn <EXTRA_KEY> <EXTRA_COMPONENT_NAME_VALUE>]", " [--eia <EXTRA_KEY> <EXTRA_INT_VALUE>[,<EXTRA_INT_VALUE...]]", - " (mutiple extras passed as Integer[])", + " (multiple extras passed as Integer[])", " [--eial <EXTRA_KEY> <EXTRA_INT_VALUE>[,<EXTRA_INT_VALUE...]]", - " (mutiple extras passed as List<Integer>)", + " (multiple extras passed as List<Integer>)", " [--ela <EXTRA_KEY> <EXTRA_LONG_VALUE>[,<EXTRA_LONG_VALUE...]]", - " (mutiple extras passed as Long[])", + " (multiple extras passed as Long[])", " [--elal <EXTRA_KEY> <EXTRA_LONG_VALUE>[,<EXTRA_LONG_VALUE...]]", - " (mutiple extras passed as List<Long>)", + " (multiple extras passed as List<Long>)", " [--efa <EXTRA_KEY> <EXTRA_FLOAT_VALUE>[,<EXTRA_FLOAT_VALUE...]]", - " (mutiple extras passed as Float[])", + " (multiple extras passed as Float[])", " [--efal <EXTRA_KEY> <EXTRA_FLOAT_VALUE>[,<EXTRA_FLOAT_VALUE...]]", - " (mutiple extras passed as List<Float>)", + " (multiple extras passed as List<Float>)", + " [--eda <EXTRA_KEY> <EXTRA_DOUBLE_VALUE>[,<EXTRA_DOUBLE_VALUE...]]", + " (multiple extras passed as Double[])", + " [--edal <EXTRA_KEY> <EXTRA_DOUBLE_VALUE>[,<EXTRA_DOUBLE_VALUE...]]", + " (multiple extras passed as List<Double>)", " [--esa <EXTRA_KEY> <EXTRA_STRING_VALUE>[,<EXTRA_STRING_VALUE...]]", - " (mutiple extras passed as String[]; to embed a comma into a string,", + " (multiple extras passed as String[]; to embed a comma into a string,", " escape it using \"\\,\")", " [--esal <EXTRA_KEY> <EXTRA_STRING_VALUE>[,<EXTRA_STRING_VALUE...]]", - " (mutiple extras passed as List<String>; to embed a comma into a string,", + " (multiple extras passed as List<String>; to embed a comma into a string,", " escape it using \"\\,\")", " [-f <FLAG>]", " [--grant-read-uri-permission] [--grant-write-uri-permission]", diff --git a/core/java/android/content/PeriodicSync.java b/core/java/android/content/PeriodicSync.java index 432e81bad019..6830f5f34e75 100644 --- a/core/java/android/content/PeriodicSync.java +++ b/core/java/android/content/PeriodicSync.java @@ -84,7 +84,7 @@ public class PeriodicSync implements Parcelable { } private PeriodicSync(Parcel in) { - this.account = in.readParcelable(null); + this.account = in.readParcelable(null, android.accounts.Account.class); this.authority = in.readString(); this.extras = in.readBundle(); this.period = in.readLong(); diff --git a/core/java/android/content/SyncInfo.java b/core/java/android/content/SyncInfo.java index 017a92b1e8bb..57101be6507e 100644 --- a/core/java/android/content/SyncInfo.java +++ b/core/java/android/content/SyncInfo.java @@ -99,7 +99,7 @@ public class SyncInfo implements Parcelable { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) SyncInfo(Parcel parcel) { authorityId = parcel.readInt(); - account = parcel.readParcelable(Account.class.getClassLoader()); + account = parcel.readParcelable(Account.class.getClassLoader(), android.accounts.Account.class); authority = parcel.readString(); startTime = parcel.readLong(); } diff --git a/core/java/android/content/SyncRequest.java b/core/java/android/content/SyncRequest.java index e1e6f75d152f..83ce84e7a5cb 100644 --- a/core/java/android/content/SyncRequest.java +++ b/core/java/android/content/SyncRequest.java @@ -174,7 +174,7 @@ public class SyncRequest implements Parcelable { mIsAuthority = (in.readInt() != 0); mIsExpedited = (in.readInt() != 0); mIsScheduledAsExpeditedJob = (in.readInt() != 0); - mAccountToSync = in.readParcelable(null); + mAccountToSync = in.readParcelable(null, android.accounts.Account.class); mAuthority = in.readString(); } diff --git a/core/java/android/content/UndoManager.java b/core/java/android/content/UndoManager.java index 87afbf874b37..b2979f36e01b 100644 --- a/core/java/android/content/UndoManager.java +++ b/core/java/android/content/UndoManager.java @@ -777,7 +777,7 @@ public class UndoManager { final int N = p.readInt(); for (int i=0; i<N; i++) { UndoOwner owner = mManager.restoreOwner(p); - UndoOperation op = (UndoOperation)p.readParcelable(loader); + UndoOperation op = (UndoOperation)p.readParcelable(loader, android.content.UndoOperation.class); op.mOwner = owner; mOperations.add(op); } diff --git a/core/java/android/content/UriPermission.java b/core/java/android/content/UriPermission.java index d3a9cb812539..73477612985c 100644 --- a/core/java/android/content/UriPermission.java +++ b/core/java/android/content/UriPermission.java @@ -47,7 +47,7 @@ public final class UriPermission implements Parcelable { /** {@hide} */ public UriPermission(Parcel in) { - mUri = in.readParcelable(null); + mUri = in.readParcelable(null, android.net.Uri.class); mModeFlags = in.readInt(); mPersistedTime = in.readLong(); } diff --git a/core/java/android/content/om/OverlayManagerTransaction.java b/core/java/android/content/om/OverlayManagerTransaction.java index 73be0ffbf467..868dab298108 100644 --- a/core/java/android/content/om/OverlayManagerTransaction.java +++ b/core/java/android/content/om/OverlayManagerTransaction.java @@ -67,7 +67,7 @@ public class OverlayManagerTransaction mRequests = new ArrayList<>(size); for (int i = 0; i < size; i++) { final int request = source.readInt(); - final OverlayIdentifier overlay = source.readParcelable(null); + final OverlayIdentifier overlay = source.readParcelable(null, android.content.om.OverlayIdentifier.class); final int userId = source.readInt(); final Bundle extras = source.readBundle(null); mRequests.add(new Request(request, overlay, userId, extras)); diff --git a/core/java/android/content/pm/AppSearchShortcutInfo.java b/core/java/android/content/pm/AppSearchShortcutInfo.java index 806091e2158d..8d9ef8530bfc 100644 --- a/core/java/android/content/pm/AppSearchShortcutInfo.java +++ b/core/java/android/content/pm/AppSearchShortcutInfo.java @@ -423,7 +423,7 @@ public class AppSearchShortcutInfo extends GenericDocument { shortLabelResName, longLabel, longLabelResId, longLabelResName, disabledMessage, disabledMessageResId, disabledMessageResName, categoriesSet, intents, rank, extras, getCreationTimestampMillis(), flags, iconResId, iconResName, bitmapPath, iconUri, - disabledReason, persons, locusId, null); + disabledReason, persons, locusId, null, null); si.setImplicitRank(implicitRank); if ((implicitRank & ShortcutInfo.RANK_CHANGED_BIT) != 0) { si.setRankChanged(); diff --git a/core/java/android/content/pm/CrossProfileApps.java b/core/java/android/content/pm/CrossProfileApps.java index 11b2ea1f6523..1e887582e5c3 100644 --- a/core/java/android/content/pm/CrossProfileApps.java +++ b/core/java/android/content/pm/CrossProfileApps.java @@ -15,6 +15,9 @@ */ package android.content.pm; +import static android.app.admin.DevicePolicyResources.Strings.Core.SWITCH_TO_PERSONAL_LABEL; +import static android.app.admin.DevicePolicyResources.Strings.Core.SWITCH_TO_WORK_LABEL; + import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; @@ -22,6 +25,7 @@ import android.annotation.SystemApi; import android.annotation.TestApi; import android.app.Activity; import android.app.AppOpsManager.Mode; +import android.app.admin.DevicePolicyManager; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -241,12 +245,24 @@ public class CrossProfileApps { public @NonNull CharSequence getProfileSwitchingLabel(@NonNull UserHandle userHandle) { verifyCanAccessUser(userHandle); - final int stringRes = mUserManager.isManagedProfile(userHandle.getIdentifier()) - ? R.string.managed_profile_label - : R.string.user_owner_label; + final boolean isManagedProfile = mUserManager.isManagedProfile(userHandle.getIdentifier()); + final DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class); + return dpm.getString( + getUpdatableProfileSwitchingLabelId(isManagedProfile), + () -> getDefaultProfileSwitchingLabel(isManagedProfile)); + } + + private String getUpdatableProfileSwitchingLabelId(boolean isManagedProfile) { + return isManagedProfile ? SWITCH_TO_WORK_LABEL : SWITCH_TO_PERSONAL_LABEL; + } + + private String getDefaultProfileSwitchingLabel(boolean isManagedProfile) { + final int stringRes = isManagedProfile + ? R.string.managed_profile_label : R.string.user_owner_label; return mResources.getString(stringRes); } + /** * Return a drawable that calling app can show to user for the semantic of profile switching -- * launching its own activity in specified user profile. For example, it may return a briefcase diff --git a/core/java/android/content/pm/InstallSourceInfo.java b/core/java/android/content/pm/InstallSourceInfo.java index a45bf7930509..84d2ca389611 100644 --- a/core/java/android/content/pm/InstallSourceInfo.java +++ b/core/java/android/content/pm/InstallSourceInfo.java @@ -61,7 +61,7 @@ public final class InstallSourceInfo implements Parcelable { private InstallSourceInfo(Parcel source) { mInitiatingPackageName = source.readString(); - mInitiatingPackageSigningInfo = source.readParcelable(SigningInfo.class.getClassLoader()); + mInitiatingPackageSigningInfo = source.readParcelable(SigningInfo.class.getClassLoader(), android.content.pm.SigningInfo.class); mOriginatingPackageName = source.readString(); mInstallingPackageName = source.readString(); } diff --git a/core/java/android/content/pm/InstantAppInfo.java b/core/java/android/content/pm/InstantAppInfo.java index 24d6a07ec4e8..d6cfb0e70693 100644 --- a/core/java/android/content/pm/InstantAppInfo.java +++ b/core/java/android/content/pm/InstantAppInfo.java @@ -65,7 +65,7 @@ public final class InstantAppInfo implements Parcelable { mLabelText = parcel.readCharSequence(); mRequestedPermissions = parcel.readStringArray(); mGrantedPermissions = parcel.createStringArray(); - mApplicationInfo = parcel.readParcelable(null); + mApplicationInfo = parcel.readParcelable(null, android.content.pm.ApplicationInfo.class); } /** diff --git a/core/java/android/content/pm/InstantAppIntentFilter.java b/core/java/android/content/pm/InstantAppIntentFilter.java index 123d2ba5aa8d..721b2616fbfd 100644 --- a/core/java/android/content/pm/InstantAppIntentFilter.java +++ b/core/java/android/content/pm/InstantAppIntentFilter.java @@ -46,7 +46,7 @@ public final class InstantAppIntentFilter implements Parcelable { InstantAppIntentFilter(Parcel in) { mSplitName = in.readString(); - in.readList(mFilters, getClass().getClassLoader()); + in.readList(mFilters, getClass().getClassLoader(), android.content.IntentFilter.class); } public String getSplitName() { diff --git a/core/java/android/content/pm/InstantAppResolveInfo.java b/core/java/android/content/pm/InstantAppResolveInfo.java index 98815647f0c3..6124638ccbcb 100644 --- a/core/java/android/content/pm/InstantAppResolveInfo.java +++ b/core/java/android/content/pm/InstantAppResolveInfo.java @@ -140,7 +140,7 @@ public final class InstantAppResolveInfo implements Parcelable { mFilters = Collections.emptyList(); mVersionCode = -1; } else { - mDigest = in.readParcelable(null /*loader*/); + mDigest = in.readParcelable(null /*loader*/, android.content.pm.InstantAppResolveInfo.InstantAppDigest.class); mPackageName = in.readString(); mFilters = new ArrayList<>(); in.readTypedList(mFilters, InstantAppIntentFilter.CREATOR); diff --git a/core/java/android/content/pm/LauncherActivityInfoInternal.java b/core/java/android/content/pm/LauncherActivityInfoInternal.java index 417f168940b6..46c415df7525 100644 --- a/core/java/android/content/pm/LauncherActivityInfoInternal.java +++ b/core/java/android/content/pm/LauncherActivityInfoInternal.java @@ -43,10 +43,10 @@ public class LauncherActivityInfoInternal implements Parcelable { } public LauncherActivityInfoInternal(Parcel source) { - mActivityInfo = source.readParcelable(ActivityInfo.class.getClassLoader()); + mActivityInfo = source.readParcelable(ActivityInfo.class.getClassLoader(), android.content.pm.ActivityInfo.class); mComponentName = new ComponentName(mActivityInfo.packageName, mActivityInfo.name); mIncrementalStatesInfo = source.readParcelable( - IncrementalStatesInfo.class.getClassLoader()); + IncrementalStatesInfo.class.getClassLoader(), android.content.pm.IncrementalStatesInfo.class); } public ComponentName getComponentName() { diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index d76dc782c367..08b07a73d4af 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -1754,11 +1754,11 @@ public class PackageInstaller { installScenario = source.readInt(); sizeBytes = source.readLong(); appPackageName = source.readString(); - appIcon = source.readParcelable(null); + appIcon = source.readParcelable(null, android.graphics.Bitmap.class); appLabel = source.readString(); - originatingUri = source.readParcelable(null); + originatingUri = source.readParcelable(null, android.net.Uri.class); originatingUid = source.readInt(); - referrerUri = source.readParcelable(null); + referrerUri = source.readParcelable(null, android.net.Uri.class); abiOverride = source.readString(); volumeUuid = source.readString(); grantedRuntimePermissions = source.readStringArray(); @@ -1770,7 +1770,7 @@ public class PackageInstaller { forceQueryableOverride = source.readBoolean(); requiredInstalledVersionCode = source.readLong(); DataLoaderParamsParcel dataLoaderParamsParcel = source.readParcelable( - DataLoaderParamsParcel.class.getClassLoader()); + DataLoaderParamsParcel.class.getClassLoader(), android.content.pm.DataLoaderParamsParcel.class); if (dataLoaderParamsParcel != null) { dataLoaderParams = new DataLoaderParams(dataLoaderParamsParcel); } @@ -2563,13 +2563,13 @@ public class PackageInstaller { installScenario = source.readInt(); sizeBytes = source.readLong(); appPackageName = source.readString(); - appIcon = source.readParcelable(null); + appIcon = source.readParcelable(null, android.graphics.Bitmap.class); appLabel = source.readString(); installLocation = source.readInt(); - originatingUri = source.readParcelable(null); + originatingUri = source.readParcelable(null, android.net.Uri.class); originatingUid = source.readInt(); - referrerUri = source.readParcelable(null); + referrerUri = source.readParcelable(null, android.net.Uri.class); grantedRuntimePermissions = source.readStringArray(); whitelistedRestrictedPermissions = source.createStringArrayList(); autoRevokePermissionsMode = source.readInt(); diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index c8f88f2edc62..1021d3e8e3fb 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -4077,6 +4077,14 @@ public abstract class PackageManager { @SdkConstant(SdkConstantType.FEATURE) public static final String FEATURE_DREAM_OVERLAY = "android.software.dream_overlay"; + /** + * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device + * supports window magnification. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_WINDOW_MAGNIFICATION = + "android.software.window_magnification"; + /** @hide */ public static final boolean APP_ENUMERATION_ENABLED_BY_DEFAULT = true; diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 701c5461362a..98cc8f6b0670 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -7300,7 +7300,7 @@ public class PackageParser { splitFlags = dest.createIntArray(); splitPrivateFlags = dest.createIntArray(); baseHardwareAccelerated = (dest.readInt() == 1); - applicationInfo = dest.readParcelable(boot); + applicationInfo = dest.readParcelable(boot, android.content.pm.ApplicationInfo.class); if (applicationInfo.permission != null) { applicationInfo.permission = applicationInfo.permission.intern(); } @@ -7308,19 +7308,19 @@ public class PackageParser { // We don't serialize the "owner" package and the application info object for each of // these components, in order to save space and to avoid circular dependencies while // serialization. We need to fix them all up here. - dest.readParcelableList(permissions, boot); + dest.readParcelableList(permissions, boot, android.content.pm.PackageParser.Permission.class); fixupOwner(permissions); - dest.readParcelableList(permissionGroups, boot); + dest.readParcelableList(permissionGroups, boot, android.content.pm.PackageParser.PermissionGroup.class); fixupOwner(permissionGroups); - dest.readParcelableList(activities, boot); + dest.readParcelableList(activities, boot, android.content.pm.PackageParser.Activity.class); fixupOwner(activities); - dest.readParcelableList(receivers, boot); + dest.readParcelableList(receivers, boot, android.content.pm.PackageParser.Activity.class); fixupOwner(receivers); - dest.readParcelableList(providers, boot); + dest.readParcelableList(providers, boot, android.content.pm.PackageParser.Provider.class); fixupOwner(providers); - dest.readParcelableList(services, boot); + dest.readParcelableList(services, boot, android.content.pm.PackageParser.Service.class); fixupOwner(services); - dest.readParcelableList(instrumentation, boot); + dest.readParcelableList(instrumentation, boot, android.content.pm.PackageParser.Instrumentation.class); fixupOwner(instrumentation); dest.readStringList(requestedPermissions); @@ -7330,10 +7330,10 @@ public class PackageParser { protectedBroadcasts = dest.createStringArrayList(); internStringArrayList(protectedBroadcasts); - parentPackage = dest.readParcelable(boot); + parentPackage = dest.readParcelable(boot, android.content.pm.PackageParser.Package.class); childPackages = new ArrayList<>(); - dest.readParcelableList(childPackages, boot); + dest.readParcelableList(childPackages, boot, android.content.pm.PackageParser.Package.class); if (childPackages.size() == 0) { childPackages = null; } @@ -7367,7 +7367,7 @@ public class PackageParser { } preferredActivityFilters = new ArrayList<>(); - dest.readParcelableList(preferredActivityFilters, boot); + dest.readParcelableList(preferredActivityFilters, boot, android.content.pm.PackageParser.ActivityIntentInfo.class); if (preferredActivityFilters.size() == 0) { preferredActivityFilters = null; } @@ -7388,7 +7388,7 @@ public class PackageParser { } mSharedUserLabel = dest.readInt(); - mSigningDetails = dest.readParcelable(boot); + mSigningDetails = dest.readParcelable(boot, android.content.pm.PackageParser.SigningDetails.class); mPreferredOrder = dest.readInt(); @@ -7400,19 +7400,19 @@ public class PackageParser { configPreferences = new ArrayList<>(); - dest.readParcelableList(configPreferences, boot); + dest.readParcelableList(configPreferences, boot, android.content.pm.ConfigurationInfo.class); if (configPreferences.size() == 0) { configPreferences = null; } reqFeatures = new ArrayList<>(); - dest.readParcelableList(reqFeatures, boot); + dest.readParcelableList(reqFeatures, boot, android.content.pm.FeatureInfo.class); if (reqFeatures.size() == 0) { reqFeatures = null; } featureGroups = new ArrayList<>(); - dest.readParcelableList(featureGroups, boot); + dest.readParcelableList(featureGroups, boot, android.content.pm.FeatureGroupInfo.class); if (featureGroups.size() == 0) { featureGroups = null; } @@ -7809,13 +7809,13 @@ public class PackageParser { private Permission(Parcel in) { super(in); final ClassLoader boot = Object.class.getClassLoader(); - info = in.readParcelable(boot); + info = in.readParcelable(boot, android.content.pm.PermissionInfo.class); if (info.group != null) { info.group = info.group.intern(); } tree = (in.readInt() == 1); - group = in.readParcelable(boot); + group = in.readParcelable(boot, android.content.pm.PackageParser.PermissionGroup.class); } public static final Parcelable.Creator CREATOR = new Parcelable.Creator<Permission>() { @@ -7870,7 +7870,7 @@ public class PackageParser { private PermissionGroup(Parcel in) { super(in); - info = in.readParcelable(Object.class.getClassLoader()); + info = in.readParcelable(Object.class.getClassLoader(), android.content.pm.PermissionGroupInfo.class); } public static final Parcelable.Creator CREATOR = new Parcelable.Creator<PermissionGroup>() { @@ -8163,7 +8163,7 @@ public class PackageParser { private Activity(Parcel in) { super(in); - info = in.readParcelable(Object.class.getClassLoader()); + info = in.readParcelable(Object.class.getClassLoader(), android.content.pm.ActivityInfo.class); mHasMaxAspectRatio = in.readBoolean(); mHasMinAspectRatio = in.readBoolean(); @@ -8257,7 +8257,7 @@ public class PackageParser { private Service(Parcel in) { super(in); - info = in.readParcelable(Object.class.getClassLoader()); + info = in.readParcelable(Object.class.getClassLoader(), android.content.pm.ServiceInfo.class); for (ServiceIntentInfo aii : intents) { aii.service = this; @@ -8347,7 +8347,7 @@ public class PackageParser { private Provider(Parcel in) { super(in); - info = in.readParcelable(Object.class.getClassLoader()); + info = in.readParcelable(Object.class.getClassLoader(), android.content.pm.ProviderInfo.class); syncable = (in.readInt() == 1); for (ProviderIntentInfo aii : intents) { @@ -8439,7 +8439,7 @@ public class PackageParser { private Instrumentation(Parcel in) { super(in); - info = in.readParcelable(Object.class.getClassLoader()); + info = in.readParcelable(Object.class.getClassLoader(), android.content.pm.InstrumentationInfo.class); if (info.targetPackage != null) { info.targetPackage = info.targetPackage.intern(); diff --git a/core/java/android/content/pm/SharedLibraryInfo.java b/core/java/android/content/pm/SharedLibraryInfo.java index f153566bf61a..43a4b17e5172 100644 --- a/core/java/android/content/pm/SharedLibraryInfo.java +++ b/core/java/android/content/pm/SharedLibraryInfo.java @@ -136,8 +136,8 @@ public final class SharedLibraryInfo implements Parcelable { mName = parcel.readString8(); mVersion = parcel.readLong(); mType = parcel.readInt(); - mDeclaringPackage = parcel.readParcelable(null); - mDependentPackages = parcel.readArrayList(null); + mDeclaringPackage = parcel.readParcelable(null, android.content.pm.VersionedPackage.class); + mDependentPackages = parcel.readArrayList(null, android.content.pm.VersionedPackage.class); mDependencies = parcel.createTypedArrayList(SharedLibraryInfo.CREATOR); mIsNative = parcel.readBoolean(); } diff --git a/core/java/android/content/pm/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java index 613fb84812f8..ab827aacbdc1 100644 --- a/core/java/android/content/pm/ShortcutInfo.java +++ b/core/java/android/content/pm/ShortcutInfo.java @@ -41,6 +41,7 @@ import android.os.Parcelable; import android.os.PersistableBundle; import android.os.UserHandle; import android.text.TextUtils; +import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; import android.view.contentcapture.ContentCaptureContext; @@ -50,9 +51,15 @@ import com.android.internal.util.Preconditions; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; /** * Represents a shortcut that can be published via {@link ShortcutManager}. @@ -463,6 +470,9 @@ public final class ShortcutInfo implements Parcelable { private int mExcludedSurfaces; + @Nullable + private Map<String, Map<String, List<String>>> mCapabilityBindings; + private ShortcutInfo(Builder b) { mUserId = b.mContext.getUserId(); @@ -490,7 +500,7 @@ public final class ShortcutInfo implements Parcelable { mRank = b.mRank; mExtras = b.mExtras; mLocusId = b.mLocusId; - + mCapabilityBindings = b.mCapabilityBindings; mStartingThemeResName = b.mStartingThemeResId != 0 ? b.mContext.getResources().getResourceName(b.mStartingThemeResId) : null; updateTimestamp(); @@ -602,7 +612,7 @@ public final class ShortcutInfo implements Parcelable { mLocusId = source.mLocusId; mExcludedSurfaces = source.mExcludedSurfaces; - // Just always keep it since it's cheep. + // Just always keep it since it's cheap. mIconResId = source.mIconResId; if ((cloneFlags & CLONE_REMOVE_NON_KEY_INFO) == 0) { @@ -641,6 +651,7 @@ public final class ShortcutInfo implements Parcelable { // Set this bit. mFlags |= FLAG_KEY_FIELDS_ONLY; } + mCapabilityBindings = source.mCapabilityBindings; mStartingThemeResName = source.mStartingThemeResName; } @@ -968,6 +979,9 @@ public final class ShortcutInfo implements Parcelable { if (source.mStartingThemeResName != null && !source.mStartingThemeResName.isEmpty()) { mStartingThemeResName = source.mStartingThemeResName; } + if (source.mCapabilityBindings != null) { + mCapabilityBindings = source.mCapabilityBindings; + } } /** @@ -1039,6 +1053,9 @@ public final class ShortcutInfo implements Parcelable { private int mStartingThemeResId; + @Nullable + private Map<String, Map<String, List<String>>> mCapabilityBindings; + private int mExcludedSurfaces; /** @@ -1401,6 +1418,53 @@ public final class ShortcutInfo implements Parcelable { } /** + * Associates a shortcut with a capability, and a parameter of that capability. Used when + * the shortcut is an instance of a capability. + * + * <P>This method can be called multiple times to add multiple parameters to the same + * capability. + * + * @param capability capability associated with the shortcut. e.g. actions.intent + * .START_EXERCISE. + * @param parameterName name of the parameter associated with given capability. + * e.g. exercise.name. + * @param parameterValues a list of values for that parameters. The first value will be + * the primary name, while the rest will be alternative names. If + * the values are empty, then the parameter will not be saved in + * the shortcut. + */ + @NonNull + public Builder addCapabilityBinding(@NonNull String capability, + @Nullable String parameterName, @Nullable List<String> parameterValues) { + Objects.requireNonNull(capability); + if (capability.contains("/")) { + throw new IllegalArgumentException("Illegal character '/' is found in capability"); + } + if (mCapabilityBindings == null) { + mCapabilityBindings = new ArrayMap<>(1); + } + if (!mCapabilityBindings.containsKey(capability)) { + mCapabilityBindings.put(capability, new ArrayMap<>(0)); + } + if (parameterName == null || parameterValues == null || parameterValues.isEmpty()) { + return this; + } + if (parameterName.contains("/")) { + throw new IllegalArgumentException( + "Illegal character '/' is found in parameter name"); + } + final Map<String, List<String>> params = mCapabilityBindings.get(capability); + if (!params.containsKey(parameterName)) { + params.put(parameterName, parameterValues); + return this; + } + params.put(parameterName, + Stream.of(params.get(parameterName), parameterValues) + .flatMap(Collection::stream).collect(Collectors.toList())); + return this; + } + + /** * Sets which surfaces a shortcut will be excluded from. * * If the shortcut is set to be excluded from {@link #SURFACE_LAUNCHER}, shortcuts will be @@ -2176,13 +2240,51 @@ public final class ShortcutInfo implements Parcelable { return (mExcludedSurfaces & surface) == 0; } + /** + * @hide + */ + public Map<String, Map<String, List<String>>> getCapabilityBindings() { + return mCapabilityBindings; + } + + /** + * Return true if the shortcut is or can be used in specified capability. + */ + public boolean hasCapability(@NonNull String capability) { + Objects.requireNonNull(capability); + return mCapabilityBindings != null && mCapabilityBindings.containsKey(capability); + } + + /** + * Returns the values of specified parameter in associated with given capability. + * + * @param capability capability associated with the shortcut. e.g. actions.intent + * .START_EXERCISE. + * @param parameterName name of the parameter associated with given capability. + * e.g. exercise.name. + */ + @NonNull + public List<String> getCapabilityParameterValues( + @NonNull String capability, @NonNull String parameterName) { + Objects.requireNonNull(capability); + Objects.requireNonNull(parameterName); + if (mCapabilityBindings == null) { + return Collections.emptyList(); + } + final Map<String, List<String>> param = mCapabilityBindings.get(capability); + if (param == null || !param.containsKey(parameterName)) { + return Collections.emptyList(); + } + return param.get(parameterName); + } + private ShortcutInfo(Parcel source) { final ClassLoader cl = getClass().getClassLoader(); mUserId = source.readInt(); mId = source.readString8(); mPackageName = source.readString8(); - mActivity = source.readParcelable(cl); + mActivity = source.readParcelable(cl, android.content.ComponentName.class); mFlags = source.readInt(); mIconResId = source.readInt(); mLastChangedTimestamp = source.readLong(); @@ -2192,7 +2294,7 @@ public final class ShortcutInfo implements Parcelable { return; // key information only. } - mIcon = source.readParcelable(cl); + mIcon = source.readParcelable(cl, android.graphics.drawable.Icon.class); mTitle = source.readCharSequence(); mTitleResId = source.readInt(); mText = source.readCharSequence(); @@ -2202,7 +2304,7 @@ public final class ShortcutInfo implements Parcelable { mIntents = source.readParcelableArray(cl, Intent.class); mIntentPersistableExtrases = source.readParcelableArray(cl, PersistableBundle.class); mRank = source.readInt(); - mExtras = source.readParcelable(cl); + mExtras = source.readParcelable(cl, android.os.PersistableBundle.class); mBitmapPath = source.readString8(); mIconResName = source.readString8(); @@ -2221,10 +2323,19 @@ public final class ShortcutInfo implements Parcelable { } mPersons = source.readParcelableArray(cl, Person.class); - mLocusId = source.readParcelable(cl); + mLocusId = source.readParcelable(cl, android.content.LocusId.class); mIconUri = source.readString8(); mStartingThemeResName = source.readString8(); mExcludedSurfaces = source.readInt(); + + final Map<String, Map> rawCapabilityBindings = source.readHashMap( + /*loader*/ null, /*clazzKey*/ String.class, /*clazzValue*/ HashMap.class); + if (rawCapabilityBindings != null && !rawCapabilityBindings.isEmpty()) { + final Map<String, Map<String, List<String>>> capabilityBindings = + new ArrayMap<>(rawCapabilityBindings.size()); + rawCapabilityBindings.forEach(capabilityBindings::put); + mCapabilityBindings = capabilityBindings; + } } @Override @@ -2278,6 +2389,7 @@ public final class ShortcutInfo implements Parcelable { dest.writeString8(mIconUri); dest.writeString8(mStartingThemeResName); dest.writeInt(mExcludedSurfaces); + dest.writeMap(mCapabilityBindings); } public static final @NonNull Creator<ShortcutInfo> CREATOR = @@ -2529,7 +2641,8 @@ public final class ShortcutInfo implements Parcelable { long lastChangedTimestamp, int flags, int iconResId, String iconResName, String bitmapPath, String iconUri, int disabledReason, Person[] persons, LocusId locusId, - @Nullable String startingThemeResName) { + @Nullable String startingThemeResName, + @Nullable Map<String, Map<String, List<String>>> capabilityBindings) { mUserId = userId; mId = id; mPackageName = packageName; @@ -2559,5 +2672,6 @@ public final class ShortcutInfo implements Parcelable { mPersons = persons; mLocusId = locusId; mStartingThemeResName = startingThemeResName; + mCapabilityBindings = capabilityBindings; } } diff --git a/core/java/android/content/pm/ShortcutManager.java b/core/java/android/content/pm/ShortcutManager.java index be0d934f5133..7dbfd08310be 100644 --- a/core/java/android/content/pm/ShortcutManager.java +++ b/core/java/android/content/pm/ShortcutManager.java @@ -704,8 +704,8 @@ public class ShortcutManager { } private ShareShortcutInfo(@NonNull Parcel in) { - mShortcutInfo = in.readParcelable(ShortcutInfo.class.getClassLoader()); - mTargetComponent = in.readParcelable(ComponentName.class.getClassLoader()); + mShortcutInfo = in.readParcelable(ShortcutInfo.class.getClassLoader(), android.content.pm.ShortcutInfo.class); + mTargetComponent = in.readParcelable(ComponentName.class.getClassLoader(), android.content.ComponentName.class); } @NonNull diff --git a/core/java/android/content/pm/ShortcutQueryWrapper.java b/core/java/android/content/pm/ShortcutQueryWrapper.java index c6134416adbc..64337d86f7ec 100644 --- a/core/java/android/content/pm/ShortcutQueryWrapper.java +++ b/core/java/android/content/pm/ShortcutQueryWrapper.java @@ -143,7 +143,7 @@ public final class ShortcutQueryWrapper extends LauncherApps.ShortcutQuery imple List<LocusId> locusIds = null; if ((flg & 0x8) != 0) { locusIds = new ArrayList<>(); - in.readParcelableList(locusIds, LocusId.class.getClassLoader()); + in.readParcelableList(locusIds, LocusId.class.getClassLoader(), android.content.LocusId.class); } ComponentName activity = (flg & 0x10) == 0 ? null : (ComponentName) in.readTypedObject(ComponentName.CREATOR); diff --git a/core/java/android/content/pm/VerifierInfo.java b/core/java/android/content/pm/VerifierInfo.java index 3e69ff555946..868bb9cb995c 100644 --- a/core/java/android/content/pm/VerifierInfo.java +++ b/core/java/android/content/pm/VerifierInfo.java @@ -59,7 +59,7 @@ public class VerifierInfo implements Parcelable { private VerifierInfo(Parcel source) { packageName = source.readString(); - publicKey = (PublicKey) source.readSerializable(); + publicKey = (PublicKey) source.readSerializable(java.security.PublicKey.class.getClassLoader(), java.security.PublicKey.class); } @Override diff --git a/core/java/android/graphics/fonts/FontUpdateRequest.java b/core/java/android/graphics/fonts/FontUpdateRequest.java index cda1638a24dc..dae09f0977a4 100644 --- a/core/java/android/graphics/fonts/FontUpdateRequest.java +++ b/core/java/android/graphics/fonts/FontUpdateRequest.java @@ -235,7 +235,7 @@ public final class FontUpdateRequest implements Parcelable { public Family createFromParcel(Parcel source) { String familyName = source.readString8(); List<Font> fonts = source.readParcelableList( - new ArrayList<>(), Font.class.getClassLoader()); + new ArrayList<>(), Font.class.getClassLoader(), android.graphics.fonts.FontUpdateRequest.Font.class); return new Family(familyName, fonts); } @@ -379,9 +379,9 @@ public final class FontUpdateRequest implements Parcelable { protected FontUpdateRequest(Parcel in) { mType = in.readInt(); - mFd = in.readParcelable(ParcelFileDescriptor.class.getClassLoader()); + mFd = in.readParcelable(ParcelFileDescriptor.class.getClassLoader(), android.os.ParcelFileDescriptor.class); mSignature = in.readBlob(); - mFontFamily = in.readParcelable(FontConfig.FontFamily.class.getClassLoader()); + mFontFamily = in.readParcelable(FontConfig.FontFamily.class.getClassLoader(), android.graphics.fonts.FontUpdateRequest.Family.class); } public @Type int getType() { diff --git a/core/java/android/hardware/CameraStreamStats.java b/core/java/android/hardware/CameraStreamStats.java index 41d1e2523a9b..ed22de8dd594 100644 --- a/core/java/android/hardware/CameraStreamStats.java +++ b/core/java/android/hardware/CameraStreamStats.java @@ -15,6 +15,7 @@ */ package android.hardware; +import android.hardware.camera2.params.DynamicRangeProfiles; import android.os.Parcel; import android.os.Parcelable; import android.util.Log; @@ -45,6 +46,7 @@ public class CameraStreamStats implements Parcelable { private int mHistogramType; private float[] mHistogramBins; private long[] mHistogramCounts; + private int mDynamicRangeProfile; private static final String TAG = "CameraStreamStats"; @@ -60,11 +62,12 @@ public class CameraStreamStats implements Parcelable { mMaxHalBuffers = 0; mMaxAppBuffers = 0; mHistogramType = HISTOGRAM_TYPE_UNKNOWN; + mDynamicRangeProfile = DynamicRangeProfiles.STANDARD; } public CameraStreamStats(int width, int height, int format, int dataSpace, long usage, long requestCount, long errorCount, - int startLatencyMs, int maxHalBuffers, int maxAppBuffers) { + int startLatencyMs, int maxHalBuffers, int maxAppBuffers, int dynamicRangeProfile) { mWidth = width; mHeight = height; mFormat = format; @@ -76,6 +79,7 @@ public class CameraStreamStats implements Parcelable { mMaxHalBuffers = maxHalBuffers; mMaxAppBuffers = maxAppBuffers; mHistogramType = HISTOGRAM_TYPE_UNKNOWN; + mDynamicRangeProfile = dynamicRangeProfile; } public static final @android.annotation.NonNull Parcelable.Creator<CameraStreamStats> CREATOR = @@ -121,6 +125,7 @@ public class CameraStreamStats implements Parcelable { dest.writeInt(mHistogramType); dest.writeFloatArray(mHistogramBins); dest.writeLongArray(mHistogramCounts); + dest.writeInt(mDynamicRangeProfile); } public void readFromParcel(Parcel in) { @@ -137,6 +142,7 @@ public class CameraStreamStats implements Parcelable { mHistogramType = in.readInt(); mHistogramBins = in.createFloatArray(); mHistogramCounts = in.createLongArray(); + mDynamicRangeProfile = in.readInt(); } public int getWidth() { @@ -190,4 +196,8 @@ public class CameraStreamStats implements Parcelable { public long[] getHistogramCounts() { return mHistogramCounts; } + + public int getDynamicRangeProfile() { + return mDynamicRangeProfile; + } } diff --git a/core/java/android/hardware/HardwareBuffer.java b/core/java/android/hardware/HardwareBuffer.java index 4683d252b68a..acceb654a959 100644 --- a/core/java/android/hardware/HardwareBuffer.java +++ b/core/java/android/hardware/HardwareBuffer.java @@ -110,9 +110,9 @@ public final class HardwareBuffer implements Parcelable, AutoCloseable { @Retention(RetentionPolicy.SOURCE) @LongDef(flag = true, value = {USAGE_CPU_READ_RARELY, USAGE_CPU_READ_OFTEN, USAGE_CPU_WRITE_RARELY, USAGE_CPU_WRITE_OFTEN, USAGE_GPU_SAMPLED_IMAGE, - USAGE_GPU_COLOR_OUTPUT, USAGE_PROTECTED_CONTENT, USAGE_VIDEO_ENCODE, - USAGE_GPU_DATA_BUFFER, USAGE_SENSOR_DIRECT_DATA, USAGE_GPU_CUBE_MAP, - USAGE_GPU_MIPMAP_COMPLETE}) + USAGE_GPU_COLOR_OUTPUT, USAGE_COMPOSER_OVERLAY, USAGE_PROTECTED_CONTENT, + USAGE_VIDEO_ENCODE, USAGE_GPU_DATA_BUFFER, USAGE_SENSOR_DIRECT_DATA, + USAGE_GPU_CUBE_MAP, USAGE_GPU_MIPMAP_COMPLETE, USAGE_FRONT_BUFFER}) public @interface Usage {}; @Usage @@ -151,6 +151,12 @@ public final class HardwareBuffer implements Parcelable, AutoCloseable { public static final long USAGE_GPU_CUBE_MAP = 1 << 25; /** Usage: The buffer contains a complete mipmap hierarchy */ public static final long USAGE_GPU_MIPMAP_COMPLETE = 1 << 26; + /** Usage: The buffer is used for front-buffer rendering. When front-buffering rendering is + * specified, different usages may adjust their behavior as a result. For example, when + * used as USAGE_GPU_COLOR_OUTPUT the buffer will behave similar to a single-buffered window. + * When used with USAGE_COMPOSER_OVERLAY, the system will try to prioritize the buffer + * receiving an overlay plane & avoid caching it in intermediate composition buffers. */ + public static final long USAGE_FRONT_BUFFER = 1 << 32; /** * Creates a new <code>HardwareBuffer</code> instance. diff --git a/core/java/android/hardware/biometrics/PromptInfo.java b/core/java/android/hardware/biometrics/PromptInfo.java index e6b762a64384..0c03948e5368 100644 --- a/core/java/android/hardware/biometrics/PromptInfo.java +++ b/core/java/android/hardware/biometrics/PromptInfo.java @@ -65,7 +65,7 @@ public class PromptInfo implements Parcelable { mAuthenticators = in.readInt(); mDisallowBiometricsIfPolicyExists = in.readBoolean(); mReceiveSystemEvents = in.readBoolean(); - mAllowedSensorIds = in.readArrayList(Integer.class.getClassLoader()); + mAllowedSensorIds = in.readArrayList(Integer.class.getClassLoader(), java.lang.Integer.class); mAllowBackgroundAuthentication = in.readBoolean(); mIgnoreEnrollmentState = in.readBoolean(); } diff --git a/core/java/android/hardware/biometrics/SensorPropertiesInternal.java b/core/java/android/hardware/biometrics/SensorPropertiesInternal.java index f365ee6066d0..1490ea1592a5 100644 --- a/core/java/android/hardware/biometrics/SensorPropertiesInternal.java +++ b/core/java/android/hardware/biometrics/SensorPropertiesInternal.java @@ -60,7 +60,7 @@ public class SensorPropertiesInternal implements Parcelable { sensorStrength = in.readInt(); maxEnrollmentsPerUser = in.readInt(); componentInfo = new ArrayList<>(); - in.readList(componentInfo, ComponentInfoInternal.class.getClassLoader()); + in.readList(componentInfo, ComponentInfoInternal.class.getClassLoader(), android.hardware.biometrics.ComponentInfoInternal.class); resetLockoutRequiresHardwareAuthToken = in.readBoolean(); resetLockoutRequiresChallenge = in.readBoolean(); } diff --git a/core/java/android/hardware/camera2/CameraCaptureSession.java b/core/java/android/hardware/camera2/CameraCaptureSession.java index bd4746369811..691690c09e0e 100644 --- a/core/java/android/hardware/camera2/CameraCaptureSession.java +++ b/core/java/android/hardware/camera2/CameraCaptureSession.java @@ -321,6 +321,9 @@ public abstract class CameraCaptureSession implements AutoCloseable { * can submit reprocess capture requests. Submitting a reprocess request to a regular capture * session will result in an {@link IllegalArgumentException}.</p> * + * <p>Submitting a request that targets Surfaces with an unsupported dynamic range combination + * will result in an {@link IllegalArgumentException}.</p> + * * @param request the settings for this capture * @param listener The callback object to notify once this request has been * processed. If null, no metadata will be produced for this capture, @@ -347,13 +350,15 @@ public abstract class CameraCaptureSession implements AutoCloseable { * a different session; or the capture targets a Surface in * the middle of being {@link #prepare prepared}; or the * handler is null, the listener is not null, and the calling - * thread has no looper. + * thread has no looper; or the request targets Surfaces with + * an unsupported dynamic range combination * * @see #captureBurst * @see #setRepeatingRequest * @see #setRepeatingBurst * @see #abortCaptures * @see CameraDevice#createReprocessableCaptureSession + * @see android.hardware.camera2.params.DynamicRangeProfiles#getProfileCaptureRequestConstraints */ public abstract int capture(@NonNull CaptureRequest request, @Nullable CaptureCallback listener, @Nullable Handler handler) @@ -389,13 +394,16 @@ public abstract class CameraCaptureSession implements AutoCloseable { * request was created with a {@link TotalCaptureResult} from * a different session; or the capture targets a Surface in * the middle of being {@link #prepare prepared}; or the - * executor is null, or the listener is not null. + * executor is null, or the listener is not null; + * or the request targets Surfaces with an unsupported dynamic + * range combination; * * @see #captureBurst * @see #setRepeatingRequest * @see #setRepeatingBurst * @see #abortCaptures * @see CameraDevice#createReprocessableCaptureSession + * @see android.hardware.camera2.params.DynamicRangeProfiles#getProfileCaptureRequestConstraints */ public int captureSingleRequest(@NonNull CaptureRequest request, @NonNull @CallbackExecutor Executor executor, @NonNull CaptureCallback listener) @@ -427,6 +435,9 @@ public abstract class CameraCaptureSession implements AutoCloseable { * can submit reprocess capture requests. Submitting a reprocess request to a regular * capture session will result in an {@link IllegalArgumentException}.</p> * + * <p>Submitting a request that targets Surfaces with an unsupported dynamic range combination + * will result in an {@link IllegalArgumentException}.</p> + * * @param requests the list of settings for this burst capture * @param listener The callback object to notify each time one of the * requests in the burst has been processed. If null, no metadata will be @@ -454,12 +465,15 @@ public abstract class CameraCaptureSession implements AutoCloseable { * {@link TotalCaptureResult} from a different session; or one * of the captures targets a Surface in the middle of being * {@link #prepare prepared}; or if the handler is null, the - * listener is not null, and the calling thread has no looper. + * listener is not null, and the calling thread has no looper; + * or the request targets Surfaces with an unsupported dynamic + * range combination. * * @see #capture * @see #setRepeatingRequest * @see #setRepeatingBurst * @see #abortCaptures + * @see android.hardware.camera2.params.DynamicRangeProfiles#getProfileCaptureRequestConstraints */ public abstract int captureBurst(@NonNull List<CaptureRequest> requests, @Nullable CaptureCallback listener, @Nullable Handler handler) @@ -499,12 +513,15 @@ public abstract class CameraCaptureSession implements AutoCloseable { * {@link TotalCaptureResult} from a different session; or one * of the captures targets a Surface in the middle of being * {@link #prepare prepared}; or if the executor is null; or if - * the listener is null. + * the listener is null; + * or the request targets Surfaces with an unsupported dynamic + * range combination. * * @see #capture * @see #setRepeatingRequest * @see #setRepeatingBurst * @see #abortCaptures + * @see android.hardware.camera2.params.DynamicRangeProfiles#getProfileCaptureRequestConstraints */ public int captureBurstRequests(@NonNull List<CaptureRequest> requests, @NonNull @CallbackExecutor Executor executor, @NonNull CaptureCallback listener) @@ -545,6 +562,9 @@ public abstract class CameraCaptureSession implements AutoCloseable { * single reprocess input image. The request must be capturing images from the camera. If a * reprocess capture request is submitted, this method will throw IllegalArgumentException.</p> * + * <p>Submitting a request that targets Surfaces with an unsupported dynamic range combination + * will result in an {@link IllegalArgumentException}.</p> + * * @param request the request to repeat indefinitely * @param listener The callback object to notify every time the * request finishes processing. If null, no metadata will be @@ -567,13 +587,16 @@ public abstract class CameraCaptureSession implements AutoCloseable { * is a reprocess capture request; or the capture targets a * Surface in the middle of being {@link #prepare prepared}; or * the handler is null, the listener is not null, and the - * calling thread has no looper; or no requests were passed in. + * calling thread has no looper; or no requests were passed in; + * or the request targets Surfaces with an unsupported dynamic + * range combination. * * @see #capture * @see #captureBurst * @see #setRepeatingBurst * @see #stopRepeating * @see #abortCaptures + * @see android.hardware.camera2.params.DynamicRangeProfiles#getProfileCaptureRequestConstraints */ public abstract int setRepeatingRequest(@NonNull CaptureRequest request, @Nullable CaptureCallback listener, @Nullable Handler handler) @@ -604,13 +627,16 @@ public abstract class CameraCaptureSession implements AutoCloseable { * that are not currently configured as outputs; or the request * is a reprocess capture request; or the capture targets a * Surface in the middle of being {@link #prepare prepared}; or - * the executor is null; or the listener is null. + * the executor is null; or the listener is null; + * or the request targets Surfaces with an unsupported dynamic + * range combination. * * @see #capture * @see #captureBurst * @see #setRepeatingBurst * @see #stopRepeating * @see #abortCaptures + * @see android.hardware.camera2.params.DynamicRangeProfiles#getProfileCaptureRequestConstraints */ public int setSingleRepeatingRequest(@NonNull CaptureRequest request, @NonNull @CallbackExecutor Executor executor, @NonNull CaptureCallback listener) @@ -655,6 +681,9 @@ public abstract class CameraCaptureSession implements AutoCloseable { * single reprocess input image. The request must be capturing images from the camera. If a * reprocess capture request is submitted, this method will throw IllegalArgumentException.</p> * + * <p>Submitting a request that targets Surfaces with an unsupported dynamic range combination + * will result in an {@link IllegalArgumentException}.</p> + * * @param requests the list of requests to cycle through indefinitely * @param listener The callback object to notify each time one of the * requests in the repeating bursts has finished processing. If null, no @@ -678,13 +707,16 @@ public abstract class CameraCaptureSession implements AutoCloseable { * targets a Surface in the middle of being * {@link #prepare prepared}; or the handler is null, the * listener is not null, and the calling thread has no looper; - * or no requests were passed in. + * or no requests were passed in; + * or the request targets Surfaces with an unsupported dynamic + * range combination. * * @see #capture * @see #captureBurst * @see #setRepeatingRequest * @see #stopRepeating * @see #abortCaptures + * @see android.hardware.camera2.params.DynamicRangeProfiles#getProfileCaptureRequestConstraints */ public abstract int setRepeatingBurst(@NonNull List<CaptureRequest> requests, @Nullable CaptureCallback listener, @Nullable Handler handler) @@ -717,13 +749,16 @@ public abstract class CameraCaptureSession implements AutoCloseable { * is a reprocess capture request; or one of the captures * targets a Surface in the middle of being * {@link #prepare prepared}; or the executor is null; or the - * listener is null. + * listener is null; + * or the request targets Surfaces with an unsupported dynamic + * range combination. * * @see #capture * @see #captureBurst * @see #setRepeatingRequest * @see #stopRepeating * @see #abortCaptures + * @see android.hardware.camera2.params.DynamicRangeProfiles#getProfileCaptureRequestConstraints */ public int setRepeatingBurstRequests(@NonNull List<CaptureRequest> requests, @NonNull @CallbackExecutor Executor executor, @NonNull CaptureCallback listener) diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java index 48a9121ef7f2..d2dc314585d6 100644 --- a/core/java/android/hardware/camera2/CameraCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraCharacteristics.java @@ -461,7 +461,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri public @Nullable RecommendedStreamConfigurationMap getRecommendedStreamConfigurationMap( @RecommendedStreamConfigurationMap.RecommendedUsecase int usecase) { if (((usecase >= RecommendedStreamConfigurationMap.USECASE_PREVIEW) && - (usecase <= RecommendedStreamConfigurationMap.USECASE_LOW_LATENCY_SNAPSHOT)) || + (usecase <= RecommendedStreamConfigurationMap.USECASE_10BIT_OUTPUT)) || ((usecase >= RecommendedStreamConfigurationMap.USECASE_VENDOR_START) && (usecase < RecommendedStreamConfigurationMap.MAX_USECASE_COUNT))) { if (mRecommendedConfigurations == null) { @@ -2213,6 +2213,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_OFFLINE_PROCESSING OFFLINE_PROCESSING}</li> * <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR ULTRA_HIGH_RESOLUTION_SENSOR}</li> * <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_REMOSAIC_REPROCESSING REMOSAIC_REPROCESSING}</li> + * <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT DYNAMIC_RANGE_TEN_BIT}</li> * </ul> * * <p>This key is available on all devices.</p> @@ -2236,6 +2237,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * @see #REQUEST_AVAILABLE_CAPABILITIES_OFFLINE_PROCESSING * @see #REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR * @see #REQUEST_AVAILABLE_CAPABILITIES_REMOSAIC_REPROCESSING + * @see #REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT */ @PublicKey @NonNull @@ -2379,6 +2381,86 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri new Key<int[]>("android.request.characteristicKeysNeedingPermission", int[].class); /** + * <p>Devices supporting the 10-bit output capability + * {@link android.hardware.camera2.CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT } + * must list their supported dynamic range profiles along with capture request + * constraints for specific profile combinations.</p> + * <p>Camera clients can retrieve the list of supported 10-bit dynamic range profiles by calling + * {@link android.hardware.camera2.params.DynamicRangeProfiles#getSupportedProfiles }. + * Any of them can be configured by setting OutputConfiguration dynamic range profile in + * {@link android.hardware.camera2.params.OutputConfiguration#setDynamicRangeProfile }. + * Clients can also check if there are any constraints that limit the combination + * of supported profiles that can be referenced within a single capture request by calling + * {@link android.hardware.camera2.params.DynamicRangeProfiles#getProfileCaptureRequestConstraints }.</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + */ + @PublicKey + @NonNull + @SyntheticKey + public static final Key<android.hardware.camera2.params.DynamicRangeProfiles> REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES = + new Key<android.hardware.camera2.params.DynamicRangeProfiles>("android.request.availableDynamicRangeProfiles", android.hardware.camera2.params.DynamicRangeProfiles.class); + + /** + * <p>A map of all available 10-bit dynamic range profiles along with their + * capture request constraints.</p> + * <p>Devices supporting the 10-bit output capability + * {@link android.hardware.camera2.CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT } + * must list their supported dynamic range profiles. In case the camera is not able to + * support every possible profile combination within a single capture request, then the + * constraints must be listed here as well.</p> + * <p><b>Possible values:</b></p> + * <ul> + * <li>{@link #REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_STANDARD STANDARD}</li> + * <li>{@link #REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_HLG10 HLG10}</li> + * <li>{@link #REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_HDR10 HDR10}</li> + * <li>{@link #REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_HDR10_PLUS HDR10_PLUS}</li> + * <li>{@link #REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_DOLBY_VISION_10B_HDR_REF DOLBY_VISION_10B_HDR_REF}</li> + * <li>{@link #REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_DOLBY_VISION_10B_HDR_REF_PO DOLBY_VISION_10B_HDR_REF_PO}</li> + * <li>{@link #REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_DOLBY_VISION_10B_HDR_OEM DOLBY_VISION_10B_HDR_OEM}</li> + * <li>{@link #REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_DOLBY_VISION_10B_HDR_OEM_PO DOLBY_VISION_10B_HDR_OEM_PO}</li> + * <li>{@link #REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_DOLBY_VISION_8B_HDR_REF DOLBY_VISION_8B_HDR_REF}</li> + * <li>{@link #REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_DOLBY_VISION_8B_HDR_REF_PO DOLBY_VISION_8B_HDR_REF_PO}</li> + * <li>{@link #REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_DOLBY_VISION_8B_HDR_OEM DOLBY_VISION_8B_HDR_OEM}</li> + * <li>{@link #REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_DOLBY_VISION_8B_HDR_OEM_PO DOLBY_VISION_8B_HDR_OEM_PO}</li> + * <li>{@link #REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_MAX MAX}</li> + * </ul> + * + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * @see #REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_STANDARD + * @see #REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_HLG10 + * @see #REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_HDR10 + * @see #REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_HDR10_PLUS + * @see #REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_DOLBY_VISION_10B_HDR_REF + * @see #REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_DOLBY_VISION_10B_HDR_REF_PO + * @see #REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_DOLBY_VISION_10B_HDR_OEM + * @see #REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_DOLBY_VISION_10B_HDR_OEM_PO + * @see #REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_DOLBY_VISION_8B_HDR_REF + * @see #REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_DOLBY_VISION_8B_HDR_REF_PO + * @see #REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_DOLBY_VISION_8B_HDR_OEM + * @see #REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_DOLBY_VISION_8B_HDR_OEM_PO + * @see #REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_MAX + * @hide + */ + public static final Key<int[]> REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP = + new Key<int[]>("android.request.availableDynamicRangeProfilesMap", int[].class); + + /** + * <p>Recommended 10-bit dynamic range profile.</p> + * <p>Devices supporting the 10-bit output capability + * {@link android.hardware.camera2.CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT } + * must list a 10-bit supported dynamic range profile that is expected to perform + * optimally in terms of image quality, power and performance. + * The value advertised can be used as a hint by camera clients when configuring the dynamic + * range profile when calling + * {@link android.hardware.camera2.params.OutputConfiguration#setDynamicRangeProfile }.</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + */ + @PublicKey + @NonNull + public static final Key<Integer> REQUEST_RECOMMENDED_TEN_BIT_DYNAMIC_RANGE_PROFILE = + new Key<Integer>("android.request.recommendedTenBitDynamicRangeProfile", int.class); + + /** * <p>The list of image formats that are supported by this * camera device for output streams.</p> * <p>All camera devices will support JPEG and YUV_420_888 formats.</p> @@ -3340,6 +3422,32 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri new Key<android.hardware.camera2.params.MandatoryStreamCombination[]>("android.scaler.mandatoryMaximumResolutionStreamCombinations", android.hardware.camera2.params.MandatoryStreamCombination[].class); /** + * <p>An array of mandatory stream combinations which are applicable when device support the + * 10-bit output capability + * {@link android.hardware.camera2.CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT } + * This is an app-readable conversion of the maximum resolution mandatory stream combination + * {@link android.hardware.camera2.CameraDevice#createCaptureSession tables}.</p> + * <p>The array of + * {@link android.hardware.camera2.params.MandatoryStreamCombination combinations} is + * generated according to the documented + * {@link android.hardware.camera2.CameraDevice#createCaptureSession guideline} for each + * device which has the + * {@link android.hardware.camera2.CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT } + * capability. + * Clients can use the array as a quick reference to find an appropriate camera stream + * combination. + * The mandatory stream combination array will be {@code null} in case the device is not an + * {@link android.hardware.camera2.CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT } + * device.</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + */ + @PublicKey + @NonNull + @SyntheticKey + public static final Key<android.hardware.camera2.params.MandatoryStreamCombination[]> SCALER_MANDATORY_TEN_BIT_OUTPUT_STREAM_COMBINATIONS = + new Key<android.hardware.camera2.params.MandatoryStreamCombination[]>("android.scaler.mandatoryTenBitOutputStreamCombinations", android.hardware.camera2.params.MandatoryStreamCombination[].class); + + /** * <p>Whether the camera device supports multi-resolution input or output streams</p> * <p>A logical multi-camera or an ultra high resolution camera may support multi-resolution * input or output streams. With multi-resolution output streams, the camera device is able diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java index 3c1ec3e629a9..47eb79d07469 100644 --- a/core/java/android/hardware/camera2/CameraDevice.java +++ b/core/java/android/hardware/camera2/CameraDevice.java @@ -402,7 +402,9 @@ public abstract class CameraDevice implements AutoCloseable { * registered surfaces do not meet the device-specific * extension requirements such as dimensions and/or * (output format)/(surface type), or if the extension is not - * supported. + * supported, or if any of the output configurations select + * a dynamic range different from + * {@link android.hardware.camera2.params.DynamicRangeProfiles#STANDARD} * @see CameraExtensionCharacteristics#getSupportedExtensions * @see CameraExtensionCharacteristics#getExtensionSupportedSizes */ @@ -822,7 +824,36 @@ public abstract class CameraDevice implements AutoCloseable { * be chosen from. {@code DEFAULT} refers to the default sensor pixel mode {@link * StreamConfigurationMap} and {@code MAX_RES} refers to the maximum resolution {@link * StreamConfigurationMap}. The same capture request must not mix targets from - * {@link StreamConfigurationMap}s corresponding to different sensor pixel modes. + * {@link StreamConfigurationMap}s corresponding to different sensor pixel modes. </p> + * + * <p> 10-bit output capable + * {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT} + * devices support at least the following stream combinations: </p> + * <table> + * <tr><th colspan="7">10-bit output additional guaranteed configurations</th></tr> + * <tr><th colspan="2" id="rb">Target 1</th><th colspan="2" id="rb">Target 2</th><th colspan="2" id="rb">Target 3</th> <th rowspan="2">Sample use case(s)</th> </tr> + * <tr><th>Type</th><th id="rb">Max size</th><th>Type</th><th id="rb">Max size</th><th>Type</th><th id="rb">Max size</th></tr> + * <tr> <td>{@code PRIV}</td><td id="rb">{@code MAXIMUM}</td> }</td> <td colspan="4" id="rb"></td> <td>Simple preview, GPU video processing, or no-preview video recording.</td> </tr> + * <tr> <td>{@code YUV}</td><td id="rb">{@code MAXIMUM}</td> }</td> <td colspan="4" id="rb"></td> <td>In-application video/image processing.</td> </tr> + * <tr> <td>{@code PRIV}</td><td id="rb">{@code PREVIEW}</td> <td>{@code JPEG}</td><td id="rb">{@code MAXIMUM }</td> <td colspan="2" id="rb"></td> <td>Standard still imaging.</td> </tr> + * <tr> <td>{@code PRIV}</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV }</td><td id="rb">{@code MAXIMUM }</td> <td colspan="2" id="rb"></td> <td>Maximum-resolution in-app processing with preview.</td> </tr> + * <tr> <td>{@code YUV}</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV}</td><td id="rb">{@code MAXIMUM }</td> <td colspan="2" id="rb"></td> <td>Maximum-resolution two-input in-app processing.</td> </tr> + * <tr> <td>{@code PRIV}</td><td id="rb">{@code PREVIEW}</td> <td>{@code PRIV}</td><td id="rb">{@code RECORD }</td> <td colspan="2" id="rb"></td> <td>High-resolution video recording with preview.</td> </tr> + * <tr> <td>{@code PRIV}</td><td id="rb">{@code PREVIEW}</td> <td>{@code PRIV}</td><td id="rb">{@code RECORD }</td> <td>{@code YUV}</td><td id="rb">{@code RECORD }</td> <td>High-resolution recording with in-app snapshot.</td> </tr> + * <tr> <td>{@code PRIV}</td><td id="rb">{@code PREVIEW}</td> <td>{@code PRIV }</td><td id="rb">{@code RECORD }</td> <td>{@code JPEG}</td><td id="rb">{@code RECORD }</td> <td>High-resolution recording with video snapshot.</td> </tr> + * </table><br> + * <p>Here PRIV can be either 8 or 10-bit {@link android.graphics.ImageFormat#PRIVATE} pixel + * format. YUV can be either {@link android.graphics.ImageFormat#YUV_420_888} or + * {@link android.graphics.ImageFormat#YCBCR_P010}. + * For the maximum size column, PREVIEW refers to the best size match to the device's screen + * resolution, or to 1080p (1920x1080), whichever is smaller. RECORD refers to the camera + * device's maximum supported recording resolution, as determined by + * {@link android.media.CamcorderProfile}. MAXIMUM refers to the camera device's maximum output + * resolution for that format or target from {@link StreamConfigurationMap#getOutputSizes(int)}. + * Do note that invalid combinations such as having a camera surface configured to use pixel + * format {@link android.graphics.ImageFormat#YUV_420_888} with a 10-bit profile + * will cause a capture session initialization failure. + * </p> * * <p>Since the capabilities of camera devices vary greatly, a given camera device may support * target combinations with sizes outside of these guarantees, but this can only be tested for @@ -907,6 +938,13 @@ public abstract class CameraDevice implements AutoCloseable { * guaranteed output targets that can be submitted in a regular or reprocess * {@link CaptureRequest} simultaneously.</p> * + * <p>Reprocessing with 10-bit output targets on 10-bit capable + * {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT} devices is + * not supported. Trying to initialize a repreocessable capture session with one ore more + * output configurations set {@link OutputConfiguration#setDynamicRangeProfile(int)} to use + * a 10-bit dynamic range profile {@link android.hardware.camera2.params.DynamicRangeProfiles} + * will trigger {@link IllegalArgumentException}.</p> + * * <style scoped> * #rb { border-right-width: thick; } * </style> @@ -1083,13 +1121,17 @@ public abstract class CameraDevice implements AutoCloseable { * * @throws IllegalArgumentException In case the session configuration is invalid; or the output * configurations are empty; or the session configuration - * executor is invalid. + * executor is invalid; + * or the output dynamic range combination is + * invalid/unsupported. * @throws CameraAccessException In case the camera device is no longer connected or has * encountered a fatal error. * @see #createCaptureSession(List, CameraCaptureSession.StateCallback, Handler) * @see #createCaptureSessionByOutputConfigurations * @see #createReprocessableCaptureSession * @see #createConstrainedHighSpeedCaptureSession + * @see OutputConfiguration#setDynamicRangeProfile(int) + * @see android.hardware.camera2.params.DynamicRangeProfiles */ public void createCaptureSession( SessionConfiguration config) throws CameraAccessException { diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java index 639abe9d1abf..803684da6ddb 100644 --- a/core/java/android/hardware/camera2/CameraMetadata.java +++ b/core/java/android/hardware/camera2/CameraMetadata.java @@ -1190,6 +1190,135 @@ public abstract class CameraMetadata<TKey> { */ public static final int REQUEST_AVAILABLE_CAPABILITIES_REMOSAIC_REPROCESSING = 17; + /** + * <p>The device supports one or more 10-bit camera outputs according to the dynamic range + * profiles specified in + * {@link android.hardware.camera2.params.DynamicRangeProfiles#getSupportedProfiles }. + * They can be configured as part of the capture session initialization via + * {@link android.hardware.camera2.params.OutputConfiguration#setDynamicRangeProfile }. + * Cameras that enable this capability must also support the following: + * * Profile {@link android.hardware.camera2.params.DynamicRangeProfiles#HLG10 } + * * All mandatory stream combinations for this specific capability as per + * documentation {@link android.hardware.camera2.CameraDevice#createCaptureSession } + * * In case the device is not able to capture some combination of supported + * standard 8-bit and/or 10-bit dynamic range profiles within the same capture request, + * then those constraints must be listed in + * {@link android.hardware.camera2.params.DynamicRangeProfiles#getProfileCaptureRequestConstraints } + * * Recommended dynamic range profile listed in + * {@link android.hardware.camera2.CameraCharacteristics#REQUEST_RECOMMENDED_TEN_BIT_DYNAMIC_RANGE_PROFILE }.</p> + * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES + */ + public static final int REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT = 18; + + // + // Enumeration values for CameraCharacteristics#REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP + // + + /** + * <p>8-bit SDR profile which is the default for all non 10-bit output capable devices.</p> + * @see CameraCharacteristics#REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP + * @hide + */ + public static final int REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_STANDARD = 0x1; + + /** + * <p>10-bit pixel samples encoded using the Hybrid log-gamma transfer function.</p> + * @see CameraCharacteristics#REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP + * @hide + */ + public static final int REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_HLG10 = 0x2; + + /** + * <p>10-bit pixel samples encoded using the SMPTE ST 2084 transfer function. + * This profile utilizes internal static metadata to increase the quality + * of the capture.</p> + * @see CameraCharacteristics#REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP + * @hide + */ + public static final int REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_HDR10 = 0x4; + + /** + * <p>10-bit pixel samples encoded using the SMPTE ST 2084 transfer function. + * In contrast to HDR10, this profile uses internal per-frame metadata + * to further enhance the quality of the capture.</p> + * @see CameraCharacteristics#REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP + * @hide + */ + public static final int REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_HDR10_PLUS = 0x8; + + /** + * <p>This is a camera mode for Dolby Vision capture optimized for a more scene + * accurate capture. This would typically differ from what a specific device + * might want to tune for a consumer optimized Dolby Vision general capture.</p> + * @see CameraCharacteristics#REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP + * @hide + */ + public static final int REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_DOLBY_VISION_10B_HDR_REF = 0x10; + + /** + * <p>This is the power optimized mode for 10-bit Dolby Vision HDR Reference Mode.</p> + * @see CameraCharacteristics#REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP + * @hide + */ + public static final int REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_DOLBY_VISION_10B_HDR_REF_PO = 0x20; + + /** + * <p>This is the camera mode for the default Dolby Vision capture mode for the + * specific device. This would be tuned by each specific device for consumer + * pleasing results that resonate with their particular audience. We expect + * that each specific device would have a different look for their default + * Dolby Vision capture.</p> + * @see CameraCharacteristics#REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP + * @hide + */ + public static final int REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_DOLBY_VISION_10B_HDR_OEM = 0x40; + + /** + * <p>This is the power optimized mode for 10-bit Dolby Vision HDR device specific + * capture Mode.</p> + * @see CameraCharacteristics#REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP + * @hide + */ + public static final int REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_DOLBY_VISION_10B_HDR_OEM_PO = 0x80; + + /** + * <p>This is the 8-bit version of the Dolby Vision reference capture mode optimized + * for scene accuracy.</p> + * @see CameraCharacteristics#REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP + * @hide + */ + public static final int REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_DOLBY_VISION_8B_HDR_REF = 0x100; + + /** + * <p>This is the power optimized mode for 8-bit Dolby Vision HDR Reference Mode.</p> + * @see CameraCharacteristics#REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP + * @hide + */ + public static final int REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_DOLBY_VISION_8B_HDR_REF_PO = 0x200; + + /** + * <p>This is the 8-bit version of device specific tuned and optimized Dolby Vision + * capture mode.</p> + * @see CameraCharacteristics#REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP + * @hide + */ + public static final int REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_DOLBY_VISION_8B_HDR_OEM = 0x400; + + /** + * <p>This is the power optimized mode for 8-bit Dolby Vision HDR device specific + * capture Mode.</p> + * @see CameraCharacteristics#REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP + * @hide + */ + public static final int REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_DOLBY_VISION_8B_HDR_OEM_PO = 0x800; + + /** + * + * @see CameraCharacteristics#REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP + * @hide + */ + public static final int REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_MAX = 0x1000; + // // Enumeration values for CameraCharacteristics#SCALER_CROPPING_TYPE // diff --git a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java index b8443fb6d14b..9d2c901ed049 100644 --- a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java @@ -35,7 +35,6 @@ import android.hardware.camera2.CaptureResult; import android.hardware.camera2.TotalCaptureResult; import android.hardware.camera2.extension.CameraOutputConfig; import android.hardware.camera2.extension.CameraSessionConfig; -import android.hardware.camera2.extension.CaptureStageImpl; import android.hardware.camera2.extension.IAdvancedExtenderImpl; import android.hardware.camera2.extension.ICaptureCallback; import android.hardware.camera2.extension.IImageProcessorImpl; @@ -49,6 +48,7 @@ import android.hardware.camera2.extension.ParcelCaptureResult; import android.hardware.camera2.extension.ParcelImage; import android.hardware.camera2.extension.ParcelTotalCaptureResult; import android.hardware.camera2.extension.Request; +import android.hardware.camera2.params.DynamicRangeProfiles; import android.hardware.camera2.params.ExtensionSessionConfiguration; import android.hardware.camera2.params.OutputConfiguration; import android.hardware.camera2.params.SessionConfiguration; @@ -130,6 +130,13 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes config.getOutputConfigurations().size() + " expected <= 2"); } + for (OutputConfiguration c : config.getOutputConfigurations()) { + if (c.getDynamicRangeProfile() != DynamicRangeProfiles.STANDARD) { + throw new IllegalArgumentException("Unsupported dynamic range profile: " + + c.getDynamicRangeProfile()); + } + } + int suitableSurfaceCount = 0; List<Size> supportedPreviewSizes = extensionChars.getExtensionSupportedSizes( config.getExtension(), SurfaceTexture.class); diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java index 71047af69b87..c8ecfd0bdea9 100644 --- a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java @@ -39,6 +39,7 @@ import android.hardware.camera2.extension.IPreviewExtenderImpl; import android.hardware.camera2.extension.IRequestUpdateProcessorImpl; import android.hardware.camera2.extension.ParcelImage; import android.hardware.camera2.TotalCaptureResult; +import android.hardware.camera2.params.DynamicRangeProfiles; import android.hardware.camera2.params.ExtensionSessionConfiguration; import android.hardware.camera2.params.OutputConfiguration; import android.hardware.camera2.params.SessionConfiguration; @@ -145,6 +146,13 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { config.getOutputConfigurations().size() + " expected <= 2"); } + for (OutputConfiguration c : config.getOutputConfigurations()) { + if (c.getDynamicRangeProfile() != DynamicRangeProfiles.STANDARD) { + throw new IllegalArgumentException("Unsupported dynamic range profile: " + + c.getDynamicRangeProfile()); + } + } + Pair<IPreviewExtenderImpl, IImageCaptureExtenderImpl> extenders = CameraExtensionCharacteristics.initializeExtension(config.getExtension()); diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java index e393a66eb733..0f8bdf64e132 100644 --- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java +++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java @@ -51,6 +51,7 @@ import android.hardware.camera2.marshal.impl.MarshalQueryableStreamConfiguration import android.hardware.camera2.marshal.impl.MarshalQueryableString; import android.hardware.camera2.params.Capability; import android.hardware.camera2.params.DeviceStateSensorOrientationMap; +import android.hardware.camera2.params.DynamicRangeProfiles; import android.hardware.camera2.params.Face; import android.hardware.camera2.params.HighSpeedVideoConfiguration; import android.hardware.camera2.params.LensShadingMap; @@ -331,6 +332,7 @@ public class CameraMetadataNative implements Parcelable { private static final int MANDATORY_STREAM_CONFIGURATIONS_DEFAULT = 0; private static final int MANDATORY_STREAM_CONFIGURATIONS_MAX_RESOLUTION = 1; private static final int MANDATORY_STREAM_CONFIGURATIONS_CONCURRENT = 2; + private static final int MANDATORY_STREAM_CONFIGURATIONS_10BIT = 3; private static String translateLocationProviderToProcess(final String provider) { if (provider == null) { @@ -678,6 +680,16 @@ public class CameraMetadataNative implements Parcelable { }); sGetCommandMap.put( + CameraCharacteristics.SCALER_MANDATORY_TEN_BIT_OUTPUT_STREAM_COMBINATIONS.getNativeKey(), + new GetCommand() { + @Override + @SuppressWarnings("unchecked") + public <T> T getValue(CameraMetadataNative metadata, Key<T> key) { + return (T) metadata.getMandatory10BitStreamCombinations(); + } + }); + + sGetCommandMap.put( CameraCharacteristics.SCALER_MANDATORY_MAXIMUM_RESOLUTION_STREAM_COMBINATIONS.getNativeKey(), new GetCommand() { @Override @@ -771,6 +783,15 @@ public class CameraMetadataNative implements Parcelable { } }); sGetCommandMap.put( + CameraCharacteristics.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES.getNativeKey(), + new GetCommand() { + @Override + @SuppressWarnings("unchecked") + public <T> T getValue(CameraMetadataNative metadata, Key<T> key) { + return (T) metadata.getDynamicRangeProfiles(); + } + }); + sGetCommandMap.put( CaptureResult.STATISTICS_OIS_SAMPLES.getNativeKey(), new GetCommand() { @Override @@ -1015,6 +1036,17 @@ public class CameraMetadataNative implements Parcelable { return map; } + private DynamicRangeProfiles getDynamicRangeProfiles() { + int[] profileArray = getBase( + CameraCharacteristics.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP); + + if (profileArray == null) { + return null; + } + + return new DynamicRangeProfiles(profileArray); + } + private Location getGpsLocation() { String processingMethod = get(CaptureResult.JPEG_GPS_PROCESSING_METHOD); double[] coords = get(CaptureResult.JPEG_GPS_COORDINATES); @@ -1378,6 +1410,9 @@ public class CameraMetadataNative implements Parcelable { case MANDATORY_STREAM_CONFIGURATIONS_MAX_RESOLUTION: combs = build.getAvailableMandatoryMaximumResolutionStreamCombinations(); break; + case MANDATORY_STREAM_CONFIGURATIONS_10BIT: + combs = build.getAvailableMandatory10BitStreamCombinations(); + break; default: combs = build.getAvailableMandatoryStreamCombinations(); } @@ -1389,6 +1424,10 @@ public class CameraMetadataNative implements Parcelable { return null; } + private MandatoryStreamCombination[] getMandatory10BitStreamCombinations() { + return getMandatoryStreamCombinationsHelper(MANDATORY_STREAM_CONFIGURATIONS_10BIT); + } + private MandatoryStreamCombination[] getMandatoryConcurrentStreamCombinations() { if (!mHasMandatoryConcurrentStreams) { return null; diff --git a/core/java/android/hardware/camera2/params/DynamicRangeProfiles.java b/core/java/android/hardware/camera2/params/DynamicRangeProfiles.java new file mode 100644 index 000000000000..5c1a4aa120ea --- /dev/null +++ b/core/java/android/hardware/camera2/params/DynamicRangeProfiles.java @@ -0,0 +1,301 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.camera2.params; + +import android.annotation.IntDef; +import android.annotation.NonNull; + +import android.hardware.camera2.CameraMetadata; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +/** + * Immutable class with information about supported 10-bit dynamic range profiles. + * + * <p>An instance of this class can be queried by retrieving the value of + * {@link android.hardware.camera2.CameraCharacteristics#REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES}. + * </p> + * + * <p>All camera devices supporting the + * {@link android.hardware.camera2.CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT} + * capability must advertise the supported 10-bit dynamic range profiles in + * {@link #getSupportedProfiles}</p> + * + * <p>Some devices may not be able to support 8-bit and/or 10-bit output with different dynamic + * range profiles within the same capture request. Such device specific constraints can be queried + * by calling {@link #getProfileCaptureRequestConstraints(int)}. Do note that unsupported + * combinations will result in {@link IllegalArgumentException} when trying to submit a capture + * request. Capture requests that only reference outputs configured using the same dynamic range + * profile value will never fail due to such constraints.</p> + * + * @see OutputConfiguration#setDynamicRangeProfile(int) + */ +public final class DynamicRangeProfiles { + /** + * This the default 8-bit standard profile that will be used in case where camera clients do not + * explicitly configure a supported dynamic range profile by calling + * {@link OutputConfiguration#setDynamicRangeProfile(int)}. + */ + public static final int STANDARD = + CameraMetadata.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_STANDARD; + + /** + * 10-bit pixel samples encoded using the Hybrid log-gamma transfer function + * + * <p>All 10-bit output capable devices are required to support this profile.</p> + */ + public static final int HLG10 = + CameraMetadata.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_HLG10; + + /** + * 10-bit pixel samples encoded using the SMPTE ST 2084 transfer function. + * + * <p>This profile utilizes internal static metadata to increase the quality + * of the capture.</p> + */ + public static final int HDR10 = + CameraMetadata.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_HDR10; + + /** + * 10-bit pixel samples encoded using the SMPTE ST 2084 transfer function. + * + * <p>In contrast to HDR10, this profile uses internal per-frame metadata + * to further enhance the quality of the capture.</p> + */ + public static final int HDR10_PLUS = + CameraMetadata.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_HDR10_PLUS; + + /** + * <p>This is a camera mode for Dolby Vision capture optimized for a more scene + * accurate capture. This would typically differ from what a specific device + * might want to tune for a consumer optimized Dolby Vision general capture.</p> + */ + public static final int DOLBY_VISION_10B_HDR_REF = + CameraMetadata.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_DOLBY_VISION_10B_HDR_REF; + + /** + * <p>This is the power optimized mode for 10-bit Dolby Vision HDR Reference Mode.</p> + */ + public static final int DOLBY_VISION_10B_HDR_REF_PO = + CameraMetadata.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_DOLBY_VISION_10B_HDR_REF_PO; + + /** + * <p>This is the camera mode for the default Dolby Vision capture mode for the + * specific device. This would be tuned by each specific device for consumer + * pleasing results that resonate with their particular audience. We expect + * that each specific device would have a different look for their default + * Dolby Vision capture.</p> + */ + public static final int DOLBY_VISION_10B_HDR_OEM = + CameraMetadata.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_DOLBY_VISION_10B_HDR_OEM; + + /** + * <p>This is the power optimized mode for 10-bit Dolby Vision HDR device specific capture + * Mode.</p> + */ + public static final int DOLBY_VISION_10B_HDR_OEM_PO = + CameraMetadata.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_DOLBY_VISION_10B_HDR_OEM_PO; + + /** + * <p>This is the 8-bit version of the Dolby Vision reference capture mode optimized + * for scene accuracy.</p> + */ + public static final int DOLBY_VISION_8B_HDR_REF = + CameraMetadata.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_DOLBY_VISION_8B_HDR_REF; + + /** + * <p>This is the power optimized mode for 8-bit Dolby Vision HDR Reference Mode.</p> + */ + public static final int DOLBY_VISION_8B_HDR_REF_PO = + CameraMetadata.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_DOLBY_VISION_8B_HDR_REF_PO; + + /** + * <p>This is the 8-bit version of device specific tuned and optimized Dolby Vision + * capture mode.</p> + */ + public static final int DOLBY_VISION_8B_HDR_OEM = + CameraMetadata.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_DOLBY_VISION_8B_HDR_OEM; + + /** + * <p>This is the power optimized mode for 8-bit Dolby Vision HDR device specific + * capture Mode.</p> + */ + public static final int DOLBY_VISION_8B_HDR_OEM_PO = + CameraMetadata.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_DOLBY_VISION_8B_HDR_OEM_PO; + + /* + * @hide + */ + public static final int PUBLIC_MAX = + CameraMetadata.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_MAX; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = {"PROFILE_"}, value = + {STANDARD, + HLG10, + HDR10, + HDR10_PLUS, + DOLBY_VISION_10B_HDR_REF, + DOLBY_VISION_10B_HDR_REF_PO, + DOLBY_VISION_10B_HDR_OEM, + DOLBY_VISION_10B_HDR_OEM_PO, + DOLBY_VISION_8B_HDR_REF, + DOLBY_VISION_8B_HDR_REF_PO, + DOLBY_VISION_8B_HDR_OEM, + DOLBY_VISION_8B_HDR_OEM_PO}) + public @interface Profile { + } + + private final HashMap<Integer, Set<Integer>> mProfileMap = new HashMap<>(); + + /** + * Create a new immutable DynamicRangeProfiles instance. + * + * <p>This constructor takes over the array; do not write to the array afterwards.</p> + * + * <p>Do note that the constructor is available for testing purposes only! + * Camera clients must always retrieve the value of + * {@link android.hardware.camera2.CameraCharacteristics#REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES}. + * for a given camera id in order to retrieve the device capabilities.</p> + * + * @param elements + * An array of elements describing the map. It contains two elements per entry which + * describe the supported dynamic range profile value in the first element and in the + * second element a bitmap of concurrently supported dynamic range profiles within the + * same capture request. Bitmap values of 0 indicate that there are no constraints. + * + * @throws IllegalArgumentException + * if the {@code elements} array length is invalid, not divisible by 2 or contains + * invalid element values + * @throws NullPointerException + * if {@code elements} is {@code null} + * + */ + public DynamicRangeProfiles(@NonNull final int[] elements) { + if ((elements.length % 2) != 0) { + throw new IllegalArgumentException("Dynamic range profile map length " + + elements.length + " is not even!"); + } + + for (int i = 0; i < elements.length; i += 2) { + checkProfileValue(elements[i]); + // STANDARD is not expected to be included + if (elements[i] == STANDARD) { + throw new IllegalArgumentException("Dynamic range profile map must not include a" + + " STANDARD profile entry!"); + } + HashSet<Integer> profiles = new HashSet<>(); + + if (elements[i+1] != 0) { + for (int profile = STANDARD; profile < PUBLIC_MAX; profile <<= 1) { + if ((elements[i+1] & profile) != 0) { + profiles.add(profile); + } + } + } + + mProfileMap.put(elements[i], profiles); + } + + // Build the STANDARD constraints depending on the advertised 10-bit limitations + HashSet<Integer> standardConstraints = new HashSet<>(); + standardConstraints.add(STANDARD); + for(Integer profile : mProfileMap.keySet()) { + if (mProfileMap.get(profile).isEmpty() || mProfileMap.get(profile).contains(STANDARD)) { + standardConstraints.add(profile); + } + } + + mProfileMap.put(STANDARD, standardConstraints); + } + + + /** + * @hide + */ + public static void checkProfileValue(int profile) { + switch (profile) { + case STANDARD: + case HLG10: + case HDR10: + case HDR10_PLUS: + case DOLBY_VISION_10B_HDR_REF: + case DOLBY_VISION_10B_HDR_REF_PO: + case DOLBY_VISION_10B_HDR_OEM: + case DOLBY_VISION_10B_HDR_OEM_PO: + case DOLBY_VISION_8B_HDR_REF: + case DOLBY_VISION_8B_HDR_REF_PO: + case DOLBY_VISION_8B_HDR_OEM: + case DOLBY_VISION_8B_HDR_OEM_PO: + //No-op + break; + default: + throw new IllegalArgumentException("Unknown profile " + profile); + } + } + + /** + * Return a set of supported dynamic range profiles. + * + * @return non-modifiable set of dynamic range profiles + */ + public @NonNull Set<Integer> getSupportedProfiles() { + return Collections.unmodifiableSet(mProfileMap.keySet()); + } + + /** + * Return a list of supported dynamic range profiles that + * can be referenced in a single capture request along with a given + * profile. + * + * <p>For example if assume that a particular 10-bit output capable device + * returns ({@link #STANDARD}, {@link #HLG10}, {@link #HDR10}) as result from calling + * {@link #getSupportedProfiles()} and {@link #getProfileCaptureRequestConstraints(int)} + * returns ({@link #STANDARD}, {@link #HLG10}) when given an argument of {@link #STANDARD}. + * This means that the corresponding camera device will only accept and process capture requests + * that reference outputs configured using {@link #HDR10} dynamic profile or alternatively + * some combination of {@link #STANDARD} and {@link #HLG10}. However trying to + * queue capture requests to outputs that reference both {@link #HDR10} and + * {@link #STANDARD}/{@link #HLG10} will result in {@link IllegalArgumentException}.</p> + * + * <p>The list will be empty in case there are no constraints for the given + * profile.</p> + * + * @return non-modifiable set of dynamic range profiles + * @throws IllegalArgumentException - If the profile argument is not + * within the list returned by + * getSupportedProfiles() + * + * @see OutputConfiguration#setDynamicRangeProfile(int) + */ + public @NonNull Set<Integer> getProfileCaptureRequestConstraints(@Profile int profile) { + Set<Integer> ret = mProfileMap.get(profile); + if (ret == null) { + throw new IllegalArgumentException("Unsupported profile!"); + } + + return Collections.unmodifiableSet(ret); + } +} diff --git a/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java b/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java index a6789213b50b..32c15da4a909 100644 --- a/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java +++ b/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java @@ -18,8 +18,6 @@ package android.hardware.camera2.params; import static android.hardware.camera2.params.StreamConfigurationMap.checkArgumentFormat; -import static com.android.internal.util.Preconditions.*; - import android.annotation.NonNull; import android.annotation.Nullable; import android.graphics.ImageFormat; @@ -28,7 +26,6 @@ import android.hardware.camera2.CameraCharacteristics; import android.hardware.camera2.CameraDevice; import android.hardware.camera2.CameraManager; import android.hardware.camera2.CameraMetadata; -import android.hardware.camera2.params.StreamConfigurationMap; import android.hardware.camera2.utils.HashCodeHelpers; import android.media.CamcorderProfile; import android.util.Log; @@ -40,6 +37,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; +import java.util.HashSet; import java.util.List; /** @@ -64,6 +62,7 @@ public final class MandatoryStreamCombination { private final boolean mIsInput; private final boolean mIsUltraHighResolution; private final boolean mIsMaximumSize; + private final boolean mIs10BitCapable; /** * Create a new {@link MandatoryStreamInformation}. @@ -119,6 +118,29 @@ public final class MandatoryStreamCombination { */ public MandatoryStreamInformation(@NonNull List<Size> availableSizes, @Format int format, boolean isMaximumSize, boolean isInput, boolean isUltraHighResolution) { + this(availableSizes, format, isMaximumSize, isInput, isUltraHighResolution, + /*is10bitCapable*/ false); + } + + /** + * Create a new {@link MandatoryStreamInformation}. + * + * @param availableSizes List of possible stream sizes. + * @param format Image format. + * @param isMaximumSize Whether this is a maximum size stream. + * @param isInput Flag indicating whether this stream is input. + * @param isUltraHighResolution Flag indicating whether this is a ultra-high resolution + * stream. + * @param is10BitCapable Flag indicating whether this stream is able to support 10-bit + * + * @throws IllegalArgumentException + * if sizes is empty or if the format was not user-defined in + * ImageFormat/PixelFormat. + * @hide + */ + public MandatoryStreamInformation(@NonNull List<Size> availableSizes, @Format int format, + boolean isMaximumSize, boolean isInput, boolean isUltraHighResolution, + boolean is10BitCapable) { if (availableSizes.isEmpty()) { throw new IllegalArgumentException("No available sizes"); } @@ -127,6 +149,7 @@ public final class MandatoryStreamCombination { mIsMaximumSize = isMaximumSize; mIsInput = isInput; mIsUltraHighResolution = isUltraHighResolution; + mIs10BitCapable = is10BitCapable; } /** @@ -180,6 +203,27 @@ public final class MandatoryStreamCombination { } /** + * Indicates whether this stream is able to support 10-bit output. + * + * <p>10-bit capable streams can be configured to output 10-bit sample data via calls to + * {@link android.hardware.camera2.params.OutputConfiguration#setDynamicRangeProfile} and + * selecting the appropriate output Surface pixel format which can be queried via + * {@link #get10BitFormat()} and will be either + * {@link ImageFormat#PRIVATE} (the default for Surfaces initialized by + * {@link android.view.SurfaceView}, {@link android.view.TextureView}, + * {@link android.media.MediaRecorder}, {@link android.media.MediaCodec} etc.) or + * {@link ImageFormat#YCBCR_P010}.</p> + * + * @return true if stream is able to output 10-bit pixels + * + * @see android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT + * @see OutputConfiguration#setDynamicRangeProfile(int) + */ + public boolean is10BitCapable() { + return mIs10BitCapable; + } + + /** * Return the list of available sizes for this mandatory stream. * * <p>Per documented {@link CameraDevice#createCaptureSession guideline} the largest @@ -201,6 +245,29 @@ public final class MandatoryStreamCombination { * @return integer format. */ public @Format int getFormat() { + // P010 YUV streams must be supported along with SDR 8-bit YUV streams + if ((mIs10BitCapable) && (mFormat == ImageFormat.YCBCR_P010)) { + return ImageFormat.YUV_420_888; + } + return mFormat; + } + + /** + * Retrieve the mandatory stream 10-bit {@code format} for 10-bit capable streams. + * + * <p>In case {@link #is10BitCapable()} returns {@code true}, then this method + * will return the corresponding 10-bit output Surface pixel format. Depending on + * the stream type it will be either {@link ImageFormat#PRIVATE} or + * {@link ImageFormat#YCBCR_P010}.</p> + * + * @return integer format. + * @throws UnsupportedOperationException in case the stream is not capable of 10-bit output + * @see #is10BitCapable() + */ + public @Format int get10BitFormat() { + if (!mIs10BitCapable) { + throw new UnsupportedOperationException("10-bit output is not supported!"); + } return mFormat; } @@ -932,6 +999,41 @@ public final class MandatoryStreamCombination { /*reprocessType*/ ReprocessType.PRIVATE), }; + private static StreamCombinationTemplate s10BitOutputStreamCombinations[] = { + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.MAXIMUM)}, + "Simple preview, GPU video processing, or no-preview video recording"), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.YCBCR_P010, SizeThreshold.MAXIMUM)}, + "In-application video/image processing"), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM), + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW)}, + "Standard still imaging"), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.YCBCR_P010, SizeThreshold.MAXIMUM), + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW)}, + "Maximum-resolution in-app processing with preview"), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.YCBCR_P010, SizeThreshold.MAXIMUM), + new StreamTemplate(ImageFormat.YCBCR_P010, SizeThreshold.PREVIEW)}, + "Maximum-resolution two-input in-app processing"), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.RECORD), + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW)}, + "High-resolution video recording with preview"), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.YCBCR_P010, SizeThreshold.RECORD), + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.RECORD), + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW)}, + "High-resolution recording with in-app snapshot"), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.JPEG, SizeThreshold.RECORD), + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.RECORD), + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW)}, + "High-resolution recording with video snapshot"), + }; + /** * Helper builder class to generate a list of available mandatory stream combinations. * @hide @@ -971,6 +1073,86 @@ public final class MandatoryStreamCombination { } /** + * Retrieve a list of all available mandatory 10-bit output capable stream combinations. + * + * @return a non-modifiable list of supported mandatory 10-bit capable stream combinations, + * null in case device is not 10-bit output capable. + */ + public @NonNull List<MandatoryStreamCombination> + getAvailableMandatory10BitStreamCombinations() { + // Since 10-bit streaming support is optional, we mandate these stream + // combinations regardless of camera device capabilities. + + StreamCombinationTemplate []chosenStreamCombinations = s10BitOutputStreamCombinations; + if (!is10BitOutputSupported()) { + Log.v(TAG, "Device is not able to output 10-bit!"); + return null; + } + + HashMap<Pair<SizeThreshold, Integer>, List<Size>> availableSizes = + enumerateAvailableSizes(); + if (availableSizes == null) { + Log.e(TAG, "Available size enumeration failed!"); + return null; + } + + ArrayList<MandatoryStreamCombination> availableStreamCombinations = new ArrayList<>(); + availableStreamCombinations.ensureCapacity(chosenStreamCombinations.length); + for (StreamCombinationTemplate combTemplate : chosenStreamCombinations) { + ArrayList<MandatoryStreamInformation> streamsInfo = new ArrayList<>(); + streamsInfo.ensureCapacity(combTemplate.mStreamTemplates.length); + for (StreamTemplate template : combTemplate.mStreamTemplates) { + List<Size> sizes = null; + Pair<SizeThreshold, Integer> pair; + pair = new Pair<>(template.mSizeThreshold, new Integer(template.mFormat)); + sizes = availableSizes.get(pair); + if (template.mFormat == ImageFormat.YCBCR_P010) { + // Make sure that exactly the same 10 and 8-bit YUV streams sizes are + // supported + pair = new Pair<>(template.mSizeThreshold, + new Integer(ImageFormat.YUV_420_888)); + HashSet<Size> sdrYuvSizes = new HashSet<>(availableSizes.get(pair)); + if (!sdrYuvSizes.equals(new HashSet<>(sizes))) { + Log.e(TAG, "The supported 10-bit YUV sizes are different from the" + + " supported 8-bit YUV sizes!"); + return null; + } + } + + MandatoryStreamInformation streamInfo; + boolean isMaximumSize = + (template.mSizeThreshold == SizeThreshold.MAXIMUM); + try { + streamInfo = new MandatoryStreamInformation(sizes, template.mFormat, + isMaximumSize, /*isInput*/ false, + /*isUltraHighResolution*/ false, + /*is10BitCapable*/ template.mFormat != ImageFormat.JPEG); + } catch (IllegalArgumentException e) { + Log.e(TAG, "No available sizes found for format: " + template.mFormat + + " size threshold: " + template.mSizeThreshold + " combination: " + + combTemplate.mDescription); + return null; + } + streamsInfo.add(streamInfo); + } + + MandatoryStreamCombination streamCombination; + try { + streamCombination = new MandatoryStreamCombination(streamsInfo, + combTemplate.mDescription, /*isReprocessable*/ false); + } catch (IllegalArgumentException e) { + Log.e(TAG, "No stream information for mandatory combination: " + + combTemplate.mDescription); + return null; + } + + availableStreamCombinations.add(streamCombination); + } + + return Collections.unmodifiableList(availableStreamCombinations); + } + + /** * Retrieve a list of all available mandatory concurrent stream combinations. * This method should only be called for devices which are listed in combinations returned * by CameraManager.getConcurrentCameraIds. @@ -1444,7 +1626,8 @@ public final class MandatoryStreamCombination { final int[] formats = { ImageFormat.PRIVATE, ImageFormat.YUV_420_888, - ImageFormat.JPEG + ImageFormat.JPEG, + ImageFormat.YCBCR_P010 }; Size recordingMaxSize = new Size(0, 0); Size previewMaxSize = new Size(0, 0); @@ -1464,7 +1647,11 @@ public final class MandatoryStreamCombination { HashMap<Integer, Size[]> allSizes = new HashMap<Integer, Size[]>(); for (int format : formats) { Integer intFormat = new Integer(format); - allSizes.put(intFormat, mStreamConfigMap.getOutputSizes(format)); + Size[] sizes = mStreamConfigMap.getOutputSizes(format); + if (sizes == null) { + sizes = new Size[0]; + } + allSizes.put(intFormat, sizes); } List<Size> previewSizes = getSizesWithinBound( @@ -1646,6 +1833,14 @@ public final class MandatoryStreamCombination { } /** + * Check whether the current device supports 10-bit output. + */ + private boolean is10BitOutputSupported() { + return isCapabilitySupported( + CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT); + } + + /** * Check whether the current device supports private reprocessing. */ private boolean isPrivateReprocessingSupported() { diff --git a/core/java/android/hardware/camera2/params/OutputConfiguration.java b/core/java/android/hardware/camera2/params/OutputConfiguration.java index 5bb7201eff65..f2b881ba7758 100644 --- a/core/java/android/hardware/camera2/params/OutputConfiguration.java +++ b/core/java/android/hardware/camera2/params/OutputConfiguration.java @@ -29,6 +29,8 @@ import android.hardware.camera2.CameraCharacteristics; import android.hardware.camera2.CameraDevice; import android.hardware.camera2.CameraMetadata; import android.hardware.camera2.MultiResolutionImageReader; +import android.hardware.camera2.params.DynamicRangeProfiles; +import android.hardware.camera2.params.DynamicRangeProfiles.Profile; import android.hardware.camera2.params.MultiResolutionStreamInfo; import android.hardware.camera2.utils.HashCodeHelpers; import android.hardware.camera2.utils.SurfaceUtils; @@ -258,6 +260,39 @@ public final class OutputConfiguration implements Parcelable { } /** + * Set a specific device supported dynamic range profile. + * + * <p>Clients can choose from any profile advertised as supported in + * CameraCharacteristics.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES + * queried using {@link DynamicRangeProfiles#getSupportedProfiles()}. + * If this is not explicitly set, then the default profile will be + * {@link DynamicRangeProfiles#STANDARD}.</p> + * + * <p>Do note that invalid combinations between the registered output + * surface pixel format and the configured dynamic range profile will + * cause capture session initialization failure. Invalid combinations + * include any 10-bit dynamic range profile advertised in + * {@link DynamicRangeProfiles#getSupportedProfiles()} combined with + * an output Surface pixel format different from {@link ImageFormat#PRIVATE} + * (the default for Surfaces initialized by {@link android.view.SurfaceView}, + * {@link android.view.TextureView}, {@link android.media.MediaRecorder}, + * {@link android.media.MediaCodec} etc.) + * or {@link ImageFormat#YCBCR_P010}.</p> + */ + public void setDynamicRangeProfile(@Profile int profile) { + mDynamicRangeProfile = profile; + } + + /** + * Return current dynamic range profile. + * + * @return the currently set dynamic range profile + */ + public @Profile int getDynamicRangeProfile() { + return mDynamicRangeProfile; + } + + /** * Create a new {@link OutputConfiguration} instance. * * <p>This constructor takes an argument for desired camera rotation</p> @@ -319,6 +354,7 @@ public final class OutputConfiguration implements Parcelable { mPhysicalCameraId = null; mIsMultiResolution = false; mSensorPixelModesUsed = new ArrayList<Integer>(); + mDynamicRangeProfile = DynamicRangeProfiles.STANDARD; } /** @@ -416,6 +452,7 @@ public final class OutputConfiguration implements Parcelable { mPhysicalCameraId = null; mIsMultiResolution = false; mSensorPixelModesUsed = new ArrayList<Integer>(); + mDynamicRangeProfile = DynamicRangeProfiles.STANDARD; } /** @@ -718,6 +755,7 @@ public final class OutputConfiguration implements Parcelable { this.mPhysicalCameraId = other.mPhysicalCameraId; this.mIsMultiResolution = other.mIsMultiResolution; this.mSensorPixelModesUsed = other.mSensorPixelModesUsed; + this.mDynamicRangeProfile = other.mDynamicRangeProfile; } /** @@ -737,6 +775,8 @@ public final class OutputConfiguration implements Parcelable { boolean isMultiResolutionOutput = source.readInt() == 1; int[] sensorPixelModesUsed = source.createIntArray(); checkArgumentInRange(rotation, ROTATION_0, ROTATION_270, "Rotation constant"); + int dynamicRangeProfile = source.readInt(); + DynamicRangeProfiles.checkProfileValue(dynamicRangeProfile); mSurfaceGroupId = surfaceSetId; mRotation = rotation; @@ -760,6 +800,7 @@ public final class OutputConfiguration implements Parcelable { mPhysicalCameraId = physicalCameraId; mIsMultiResolution = isMultiResolutionOutput; mSensorPixelModesUsed = convertIntArrayToIntegerList(sensorPixelModesUsed); + mDynamicRangeProfile = dynamicRangeProfile; } /** @@ -875,6 +916,7 @@ public final class OutputConfiguration implements Parcelable { dest.writeInt(mIsMultiResolution ? 1 : 0); // writeList doesn't seem to work well with Integer list. dest.writeIntArray(convertIntegerToIntList(mSensorPixelModesUsed)); + dest.writeInt(mDynamicRangeProfile); } /** @@ -920,6 +962,9 @@ public final class OutputConfiguration implements Parcelable { if (mSurfaces.get(i) != other.mSurfaces.get(i)) return false; } + if (mDynamicRangeProfile != other.mDynamicRangeProfile) { + return false; + } return true; } @@ -939,7 +984,8 @@ public final class OutputConfiguration implements Parcelable { mRotation, mConfiguredSize.hashCode(), mConfiguredFormat, mConfiguredDataspace, mSurfaceGroupId, mSurfaceType, mIsShared ? 1 : 0, mPhysicalCameraId == null ? 0 : mPhysicalCameraId.hashCode(), - mIsMultiResolution ? 1 : 0, mSensorPixelModesUsed.hashCode()); + mIsMultiResolution ? 1 : 0, mSensorPixelModesUsed.hashCode(), + mDynamicRangeProfile); } return HashCodeHelpers.hashCode( @@ -947,7 +993,8 @@ public final class OutputConfiguration implements Parcelable { mConfiguredSize.hashCode(), mConfiguredFormat, mConfiguredDataspace, mSurfaceGroupId, mIsShared ? 1 : 0, mPhysicalCameraId == null ? 0 : mPhysicalCameraId.hashCode(), - mIsMultiResolution ? 1 : 0, mSensorPixelModesUsed.hashCode()); + mIsMultiResolution ? 1 : 0, mSensorPixelModesUsed.hashCode(), + mDynamicRangeProfile); } private static final String TAG = "OutputConfiguration"; @@ -979,4 +1026,6 @@ public final class OutputConfiguration implements Parcelable { private boolean mIsMultiResolution; // The sensor pixel modes that this OutputConfiguration will use private ArrayList<Integer> mSensorPixelModesUsed; + // Dynamic range profile + private int mDynamicRangeProfile; } diff --git a/core/java/android/hardware/camera2/params/RecommendedStreamConfigurationMap.java b/core/java/android/hardware/camera2/params/RecommendedStreamConfigurationMap.java index 2d725989af17..80db38fc9d8f 100644 --- a/core/java/android/hardware/camera2/params/RecommendedStreamConfigurationMap.java +++ b/core/java/android/hardware/camera2/params/RecommendedStreamConfigurationMap.java @@ -16,6 +16,8 @@ package android.hardware.camera2.params; +import static com.android.internal.R.string.hardware; + import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; @@ -149,6 +151,16 @@ public final class RecommendedStreamConfigurationMap { public static final int USECASE_LOW_LATENCY_SNAPSHOT = 0x6; /** + * If supported, the recommended 10-bit output stream configurations must include + * a subset of the advertised {@link android.graphics.ImageFormat#YCBCR_P010} and + * {@link android.graphics.ImageFormat#PRIVATE} outputs that are optimized for power + * and performance when registered along with a supported 10-bit dynamic range profile. + * {@see android.hardware.camera2.params.OutputConfiguration#setDynamicRangeProfile} for + * details. + */ + public static final int USECASE_10BIT_OUTPUT = 0x8; + + /** * Device specific use cases. * @hide */ @@ -163,7 +175,8 @@ public final class RecommendedStreamConfigurationMap { USECASE_SNAPSHOT, USECASE_ZSL, USECASE_RAW, - USECASE_LOW_LATENCY_SNAPSHOT}) + USECASE_LOW_LATENCY_SNAPSHOT, + USECASE_10BIT_OUTPUT}) public @interface RecommendedUsecase {}; /** diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java index 89ac8bf2d7bc..eefa1d3279e3 100644 --- a/core/java/android/hardware/display/DisplayManager.java +++ b/core/java/android/hardware/display/DisplayManager.java @@ -334,6 +334,7 @@ public final class DisplayManager { * @hide */ @TestApi + @SystemApi public static final int VIRTUAL_DISPLAY_FLAG_TRUSTED = 1 << 10; /** diff --git a/core/java/android/hardware/face/FaceAuthenticationFrame.java b/core/java/android/hardware/face/FaceAuthenticationFrame.java index f39d63411825..a53aad74d4e0 100644 --- a/core/java/android/hardware/face/FaceAuthenticationFrame.java +++ b/core/java/android/hardware/face/FaceAuthenticationFrame.java @@ -46,7 +46,7 @@ public final class FaceAuthenticationFrame implements Parcelable { } private FaceAuthenticationFrame(@NonNull Parcel source) { - mData = source.readParcelable(FaceDataFrame.class.getClassLoader()); + mData = source.readParcelable(FaceDataFrame.class.getClassLoader(), android.hardware.face.FaceDataFrame.class); } @Override diff --git a/core/java/android/hardware/face/FaceEnrollFrame.java b/core/java/android/hardware/face/FaceEnrollFrame.java index 822a57944449..bbccee2e2c3d 100644 --- a/core/java/android/hardware/face/FaceEnrollFrame.java +++ b/core/java/android/hardware/face/FaceEnrollFrame.java @@ -73,9 +73,9 @@ public final class FaceEnrollFrame implements Parcelable { } private FaceEnrollFrame(@NonNull Parcel source) { - mCell = source.readParcelable(FaceEnrollCell.class.getClassLoader()); + mCell = source.readParcelable(FaceEnrollCell.class.getClassLoader(), android.hardware.face.FaceEnrollCell.class); mStage = source.readInt(); - mData = source.readParcelable(FaceDataFrame.class.getClassLoader()); + mData = source.readParcelable(FaceDataFrame.class.getClassLoader(), android.hardware.face.FaceDataFrame.class); } @Override diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl index 2ac194b67192..0304815ef8fe 100644 --- a/core/java/android/hardware/input/IInputManager.aidl +++ b/core/java/android/hardware/input/IInputManager.aidl @@ -50,6 +50,10 @@ interface IInputManager { // Reports whether the hardware supports the given keys; returns true if successful boolean hasKeys(int deviceId, int sourceMask, in int[] keyCodes, out boolean[] keyExists); + // Returns the keyCode produced when pressing the key at the specified location, given the + // active keyboard layout. + int getKeyCodeForKeyLocation(int deviceId, in int locationKeyCode); + // Temporarily changes the pointer speed. void tryPointerSpeed(int speed); diff --git a/core/java/android/hardware/input/InputDeviceIdentifier.java b/core/java/android/hardware/input/InputDeviceIdentifier.java index c673e7ab7c53..a5b9a2a4da76 100644 --- a/core/java/android/hardware/input/InputDeviceIdentifier.java +++ b/core/java/android/hardware/input/InputDeviceIdentifier.java @@ -16,7 +16,9 @@ package android.hardware.input; +import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.TestApi; import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; @@ -28,12 +30,13 @@ import java.util.Objects; * * @hide */ +@TestApi public final class InputDeviceIdentifier implements Parcelable { private final String mDescriptor; private final int mVendorId; private final int mProductId; - public InputDeviceIdentifier(String descriptor, int vendorId, int productId) { + public InputDeviceIdentifier(@NonNull String descriptor, int vendorId, int productId) { this.mDescriptor = descriptor; this.mVendorId = vendorId; this.mProductId = productId; @@ -51,12 +54,13 @@ public final class InputDeviceIdentifier implements Parcelable { } @Override - public void writeToParcel(Parcel dest, int flags) { + public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeString(mDescriptor); dest.writeInt(mVendorId); dest.writeInt(mProductId); } + @NonNull public String getDescriptor() { return mDescriptor; } diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java index ef349a96ee17..cbc837393b6b 100644 --- a/core/java/android/hardware/input/InputManager.java +++ b/core/java/android/hardware/input/InputManager.java @@ -58,6 +58,7 @@ import android.util.SparseArray; import android.view.InputDevice; import android.view.InputEvent; import android.view.InputMonitor; +import android.view.KeyEvent; import android.view.MotionEvent; import android.view.PointerIcon; import android.view.VerifiedInputEvent; @@ -637,6 +638,30 @@ public final class InputManager { } /** + * Returns the descriptors of all supported keyboard layouts appropriate for the specified + * input device. + * <p> + * The input manager consults the built-in keyboard layouts as well as all keyboard layouts + * advertised by applications using a {@link #ACTION_QUERY_KEYBOARD_LAYOUTS} broadcast receiver. + * </p> + * + * @param device The input device to query. + * @return The ids of all keyboard layouts which are supported by the specified input device. + * + * @hide + */ + @TestApi + @NonNull + public List<String> getKeyboardLayoutDescriptorsForInputDevice(@NonNull InputDevice device) { + KeyboardLayout[] layouts = getKeyboardLayoutsForInputDevice(device.getIdentifier()); + List<String> res = new ArrayList<>(); + for (KeyboardLayout kl : layouts) { + res.add(kl.getDescriptor()); + } + return res; + } + + /** * Gets information about all supported keyboard layouts appropriate * for a specific input device. * <p> @@ -650,7 +675,9 @@ public final class InputManager { * * @hide */ - public KeyboardLayout[] getKeyboardLayoutsForInputDevice(InputDeviceIdentifier identifier) { + @NonNull + public KeyboardLayout[] getKeyboardLayoutsForInputDevice( + @NonNull InputDeviceIdentifier identifier) { try { return mIm.getKeyboardLayoutsForInputDevice(identifier); } catch (RemoteException ex) { @@ -680,15 +707,17 @@ public final class InputManager { } /** - * Gets the current keyboard layout descriptor for the specified input - * device. + * Gets the current keyboard layout descriptor for the specified input device. * * @param identifier Identifier for the input device - * @return The keyboard layout descriptor, or null if no keyboard layout has - * been set. + * @return The keyboard layout descriptor, or null if no keyboard layout has been set. + * * @hide */ - public String getCurrentKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier) { + @TestApi + @Nullable + public String getCurrentKeyboardLayoutForInputDevice( + @NonNull InputDeviceIdentifier identifier) { try { return mIm.getCurrentKeyboardLayoutForInputDevice(identifier); } catch (RemoteException ex) { @@ -697,20 +726,21 @@ public final class InputManager { } /** - * Sets the current keyboard layout descriptor for the specified input - * device. + * Sets the current keyboard layout descriptor for the specified input device. * <p> - * This method may have the side-effect of causing the input device in - * question to be reconfigured. + * This method may have the side-effect of causing the input device in question to be + * reconfigured. * </p> * * @param identifier The identifier for the input device. - * @param keyboardLayoutDescriptor The keyboard layout descriptor to use, - * must not be null. + * @param keyboardLayoutDescriptor The keyboard layout descriptor to use, must not be null. + * * @hide */ - public void setCurrentKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier, - String keyboardLayoutDescriptor) { + @TestApi + @RequiresPermission(Manifest.permission.SET_KEYBOARD_LAYOUT) + public void setCurrentKeyboardLayoutForInputDevice(@NonNull InputDeviceIdentifier identifier, + @NonNull String keyboardLayoutDescriptor) { if (identifier == null) { throw new IllegalArgumentException("identifier must not be null"); } @@ -727,11 +757,11 @@ public final class InputManager { } /** - * Gets all keyboard layout descriptors that are enabled for the specified - * input device. + * Gets all keyboard layout descriptors that are enabled for the specified input device. * * @param identifier The identifier for the input device. * @return The keyboard layout descriptors. + * * @hide */ public String[] getEnabledKeyboardLayoutsForInputDevice(InputDeviceIdentifier identifier) { @@ -749,15 +779,16 @@ public final class InputManager { /** * Adds the keyboard layout descriptor for the specified input device. * <p> - * This method may have the side-effect of causing the input device in - * question to be reconfigured. + * This method may have the side-effect of causing the input device in question to be + * reconfigured. * </p> * * @param identifier The identifier for the input device. - * @param keyboardLayoutDescriptor The descriptor of the keyboard layout to - * add. + * @param keyboardLayoutDescriptor The descriptor of the keyboard layout to add. + * * @hide */ + @RequiresPermission(Manifest.permission.SET_KEYBOARD_LAYOUT) public void addKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier, String keyboardLayoutDescriptor) { if (identifier == null) { @@ -777,17 +808,19 @@ public final class InputManager { /** * Removes the keyboard layout descriptor for the specified input device. * <p> - * This method may have the side-effect of causing the input device in - * question to be reconfigured. + * This method may have the side-effect of causing the input device in question to be + * reconfigured. * </p> * * @param identifier The identifier for the input device. - * @param keyboardLayoutDescriptor The descriptor of the keyboard layout to - * remove. + * @param keyboardLayoutDescriptor The descriptor of the keyboard layout to remove. + * * @hide */ - public void removeKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier, - String keyboardLayoutDescriptor) { + @TestApi + @RequiresPermission(Manifest.permission.SET_KEYBOARD_LAYOUT) + public void removeKeyboardLayoutForInputDevice(@NonNull InputDeviceIdentifier identifier, + @NonNull String keyboardLayoutDescriptor) { if (identifier == null) { throw new IllegalArgumentException("inputDeviceDescriptor must not be null"); } @@ -1044,6 +1077,27 @@ public final class InputManager { return ret; } + /** + * Gets the key code produced by the specified location on a US keyboard layout. + * Key code as defined in {@link android.view.KeyEvent}. + * This API is only functional for devices with {@link InputDevice#SOURCE_KEYBOARD} available + * which can alter their key mapping using country specific keyboard layouts. + * + * @param deviceId The input device id. + * @param locationKeyCode The location of a key on a US keyboard layout. + * @return The key code produced when pressing the key at the specified location, given the + * active keyboard layout. Returns {@link KeyEvent#KEYCODE_UNKNOWN} if the requested + * mapping could not be determined, or if an error occurred. + * @hide + */ + public int getKeyCodeForKeyLocation(int deviceId, int locationKeyCode) { + try { + return mIm.getKeyCodeForKeyLocation(deviceId, locationKeyCode); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** * Injects an input event into the event system on behalf of an application. diff --git a/core/java/android/hardware/input/InputManagerInternal.java b/core/java/android/hardware/input/InputManagerInternal.java index 1173c311bd26..f866a2e1c691 100644 --- a/core/java/android/hardware/input/InputManagerInternal.java +++ b/core/java/android/hardware/input/InputManagerInternal.java @@ -17,6 +17,7 @@ package android.hardware.input; import android.annotation.NonNull; +import android.graphics.PointF; import android.hardware.display.DisplayViewport; import android.os.IBinder; import android.view.InputEvent; @@ -79,6 +80,22 @@ public abstract class InputManagerInternal { public abstract boolean transferTouchFocus(@NonNull IBinder fromChannelToken, @NonNull IBinder toChannelToken); + /** + * Sets the display id that the MouseCursorController will be forced to target. Pass + * {@link android.view.Display#INVALID_DISPLAY} to clear the override. + */ + public abstract void setVirtualMousePointerDisplayId(int pointerDisplayId); + + /** Gets the current position of the mouse cursor. */ + public abstract PointF getCursorPosition(); + + /** + * Sets the eligibility of windows on a given display for pointer capture. If a display is + * marked ineligible, requests to enable pointer capture for windows on that display will be + * ignored. + */ + public abstract void setDisplayEligibilityForPointerCapture(int displayId, boolean isEligible); + /** Registers the {@link LidSwitchCallback} to begin receiving notifications. */ public abstract void registerLidSwitchCallback(@NonNull LidSwitchCallback callbacks); diff --git a/core/java/android/hardware/input/VirtualMouse.java b/core/java/android/hardware/input/VirtualMouse.java index 6599dd2e28eb..6e2b56a2b5bc 100644 --- a/core/java/android/hardware/input/VirtualMouse.java +++ b/core/java/android/hardware/input/VirtualMouse.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.companion.virtual.IVirtualDevice; +import android.graphics.PointF; import android.os.IBinder; import android.os.RemoteException; import android.view.MotionEvent; @@ -61,6 +62,8 @@ public class VirtualMouse implements Closeable { * Send a mouse button event to the system. * * @param event the event + * @throws IllegalStateException if the display this mouse is associated with is not currently + * targeted */ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void sendButtonEvent(@NonNull VirtualMouseButtonEvent event) { @@ -76,6 +79,8 @@ public class VirtualMouse implements Closeable { * {@link MotionEvent#AXIS_SCROLL}. * * @param event the event + * @throws IllegalStateException if the display this mouse is associated with is not currently + * targeted */ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void sendScrollEvent(@NonNull VirtualMouseScrollEvent event) { @@ -90,6 +95,8 @@ public class VirtualMouse implements Closeable { * Sends a relative movement event to the system. * * @param event the event + * @throws IllegalStateException if the display this mouse is associated with is not currently + * targeted */ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void sendRelativeEvent(@NonNull VirtualMouseRelativeEvent event) { @@ -99,4 +106,20 @@ public class VirtualMouse implements Closeable { throw e.rethrowFromSystemServer(); } } + + /** + * Gets the current cursor position. + * + * @return the position, expressed as x and y coordinates + * @throws IllegalStateException if the display this mouse is associated with is not currently + * targeted + */ + @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) + public @NonNull PointF getCursorPosition() { + try { + return mVirtualDevice.getCursorPosition(mToken); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } diff --git a/core/java/android/hardware/location/GeofenceHardwareMonitorEvent.java b/core/java/android/hardware/location/GeofenceHardwareMonitorEvent.java index 78cca9601a2d..310ebe9ac093 100644 --- a/core/java/android/hardware/location/GeofenceHardwareMonitorEvent.java +++ b/core/java/android/hardware/location/GeofenceHardwareMonitorEvent.java @@ -81,7 +81,7 @@ public class GeofenceHardwareMonitorEvent implements Parcelable { int monitoringType = source.readInt(); int monitoringStatus = source.readInt(); int sourceTechnologies = source.readInt(); - Location location = source.readParcelable(classLoader); + Location location = source.readParcelable(classLoader, android.location.Location.class); return new GeofenceHardwareMonitorEvent( monitoringType, diff --git a/core/java/android/hardware/usb/IUsbManager.aidl b/core/java/android/hardware/usb/IUsbManager.aidl index 7f07af79ab69..459dab1bffe1 100644 --- a/core/java/android/hardware/usb/IUsbManager.aidl +++ b/core/java/android/hardware/usb/IUsbManager.aidl @@ -18,6 +18,7 @@ package android.hardware.usb; import android.app.PendingIntent; import android.content.ComponentName; +import android.hardware.usb.IUsbOperationInternal; import android.hardware.usb.UsbAccessory; import android.hardware.usb.UsbDevice; import android.hardware.usb.ParcelableUsbPort; @@ -136,7 +137,10 @@ interface IUsbManager void resetUsbGadget(); /* Set USB data on or off */ - boolean enableUsbDataSignal(boolean enable); + boolean enableUsbData(in String portId, boolean enable, int operationId, in IUsbOperationInternal callback); + + /* Enable USB data when disabled due to docking event */ + void enableUsbDataWhileDocked(in String portId, int operationId, in IUsbOperationInternal callback); /* Gets the USB Hal Version. */ int getUsbHalVersion(); @@ -156,9 +160,14 @@ interface IUsbManager /* Sets the port's current role. */ void setPortRoles(in String portId, int powerRole, int dataRole); + /* Limit power transfer in & out of the port within the allowed limit by the USB + * specification. + */ + void enableLimitPowerTransfer(in String portId, boolean limit, int operationId, in IUsbOperationInternal callback); + /* Enable/disable contaminant detection */ void enableContaminantDetection(in String portId, boolean enable); - /* Sets USB device connection handler. */ - void setUsbDeviceConnectionHandler(in ComponentName usbDeviceConnectionHandler); + /* Sets USB device connection handler. */ + void setUsbDeviceConnectionHandler(in ComponentName usbDeviceConnectionHandler); } diff --git a/core/java/android/hardware/usb/IUsbOperationInternal.aidl b/core/java/android/hardware/usb/IUsbOperationInternal.aidl new file mode 100644 index 000000000000..3f3bbf63ed8b --- /dev/null +++ b/core/java/android/hardware/usb/IUsbOperationInternal.aidl @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.usb; + +/** + * @hide + */ +oneway interface IUsbOperationInternal { +void onOperationComplete(in int status); +} diff --git a/core/java/android/hardware/usb/UsbManager.java b/core/java/android/hardware/usb/UsbManager.java index c29a948c2a26..f0e040ed4686 100644 --- a/core/java/android/hardware/usb/UsbManager.java +++ b/core/java/android/hardware/usb/UsbManager.java @@ -36,6 +36,8 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.hardware.usb.gadget.V1_0.GadgetFunction; import android.hardware.usb.gadget.V1_2.UsbSpeed; +import android.hardware.usb.IUsbOperationInternal; +import android.hardware.usb.UsbPort; import android.os.Build; import android.os.Bundle; import android.os.ParcelFileDescriptor; @@ -48,6 +50,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.StringJoiner; /** @@ -517,6 +520,14 @@ public class UsbManager { public static final int USB_DATA_TRANSFER_RATE_40G = 40 * 1024; /** + * Returned when the client has to retry querying the version. + * + * @hide + */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public static final int USB_HAL_RETRY = -2; + + /** * The Value for USB hal is not presented. * * {@hide} @@ -557,6 +568,14 @@ public class UsbManager { public static final int USB_HAL_V1_3 = 13; /** + * Value for USB Hal Version v2.0. + * + * @hide + */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public static final int USB_HAL_V2_0 = 20; + + /** * Code for the charging usb function. Passed into {@link #setCurrentFunctions(long)} * {@hide} */ @@ -665,6 +684,7 @@ public class UsbManager { USB_HAL_V1_1, USB_HAL_V1_2, USB_HAL_V1_3, + USB_HAL_V2_0, }) public @interface UsbHalVersion {} @@ -1169,8 +1189,9 @@ public class UsbManager { /** * Enable/Disable the USB data signaling. * <p> - * Enables/Disables USB data path in all the USB ports. + * Enables/Disables USB data path of the first port.. * It will force to stop or restore USB data signaling. + * Call UsbPort API if the device has more than one UsbPort. * </p> * * @param enable enable or disable USB data signaling @@ -1181,11 +1202,11 @@ public class UsbManager { */ @RequiresPermission(Manifest.permission.MANAGE_USB) public boolean enableUsbDataSignal(boolean enable) { - try { - return mService.enableUsbDataSignal(enable); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); + List<UsbPort> usbPorts = getPorts(); + if (usbPorts.size() == 1) { + return usbPorts.get(0).enableUsbData(enable) == UsbPort.ENABLE_USB_DATA_SUCCESS; } + return false; } /** @@ -1271,6 +1292,102 @@ public class UsbManager { } /** + * Should only be called by {@link UsbPort#enableLimitPowerTransfer}. + * <p> + * limits or restores power transfer in and out of USB port. + * + * @param port USB port for which power transfer has to be limited or restored. + * @param limit limit power transfer when true. + * relax power transfer restrictions when false. + * @param operationId operationId for the request. + * @param callback callback object to be invoked when the operation is complete. + * + * @hide + */ + @RequiresPermission(Manifest.permission.MANAGE_USB) + void enableLimitPowerTransfer(@NonNull UsbPort port, boolean limit, int operationId, + IUsbOperationInternal callback) { + Objects.requireNonNull(port, "enableLimitPowerTransfer:port must not be null. opId:" + + operationId); + try { + mService.enableLimitPowerTransfer(port.getId(), limit, operationId, callback); + } catch (RemoteException e) { + Log.e(TAG, "enableLimitPowerTransfer failed. opId:" + operationId, e); + try { + callback.onOperationComplete(UsbOperationInternal.USB_OPERATION_ERROR_INTERNAL); + } catch (RemoteException r) { + Log.e(TAG, "enableLimitPowerTransfer failed to call onOperationComplete. opId:" + + operationId, r); + } + throw e.rethrowFromSystemServer(); + } + } + + /** + * Should only be called by {@link UsbPort#enableUsbData}. + * <p> + * Enables or disables USB data on the specific port. + * + * @param port USB port for which USB data needs to be enabled or disabled. + * @param enable Enable USB data when true. + * Disable USB data when false. + * @param operationId operationId for the request. + * @param callback callback object to be invoked when the operation is complete. + * @return True when the operation is asynchronous. The caller must therefore call + * {@link UsbOperationInternal#waitForOperationComplete} for processing + * the result. + * False when the operation is synchronous. Caller can proceed reading the result + * through {@link UsbOperationInternal#getStatus} + * @hide + */ + @RequiresPermission(Manifest.permission.MANAGE_USB) + boolean enableUsbData(@NonNull UsbPort port, boolean enable, int operationId, + IUsbOperationInternal callback) { + Objects.requireNonNull(port, "enableUsbData: port must not be null. opId:" + operationId); + try { + return mService.enableUsbData(port.getId(), enable, operationId, callback); + } catch (RemoteException e) { + Log.e(TAG, "enableUsbData: failed. opId:" + operationId, e); + try { + callback.onOperationComplete(UsbOperationInternal.USB_OPERATION_ERROR_INTERNAL); + } catch (RemoteException r) { + Log.e(TAG, "enableUsbData: failed to call onOperationComplete. opId:" + + operationId, r); + } + throw e.rethrowFromSystemServer(); + } + } + + /** + * Should only be called by {@link UsbPort#enableUsbDataWhileDocked}. + * <p> + * Enables or disables USB data when disabled due to docking event. + * + * @param port USB port for which USB data needs to be enabled. + * @param operationId operationId for the request. + * @param callback callback object to be invoked when the operation is complete. + * @hide + */ + @RequiresPermission(Manifest.permission.MANAGE_USB) + void enableUsbDataWhileDocked(@NonNull UsbPort port, int operationId, + IUsbOperationInternal callback) { + Objects.requireNonNull(port, "enableUsbDataWhileDocked: port must not be null. opId:" + + operationId); + try { + mService.enableUsbDataWhileDocked(port.getId(), operationId, callback); + } catch (RemoteException e) { + Log.e(TAG, "enableUsbDataWhileDocked: failed. opId:" + operationId, e); + try { + callback.onOperationComplete(UsbOperationInternal.USB_OPERATION_ERROR_INTERNAL); + } catch (RemoteException r) { + Log.e(TAG, "enableUsbDataWhileDocked: failed to call onOperationComplete. opId:" + + operationId, r); + } + throw e.rethrowFromSystemServer(); + } + } + + /** * Sets the component that will handle USB device connection. * <p> * Setting component allows to specify external USB host manager to handle use cases, where diff --git a/core/java/android/hardware/usb/UsbOperationInternal.java b/core/java/android/hardware/usb/UsbOperationInternal.java new file mode 100644 index 000000000000..9bc2b3892a1e --- /dev/null +++ b/core/java/android/hardware/usb/UsbOperationInternal.java @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.hardware.usb; + +import android.annotation.IntDef; +import android.hardware.usb.IUsbOperationInternal; +import android.hardware.usb.UsbPort; +import android.util.Log; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.ReentrantLock; +import java.util.concurrent.TimeUnit; +/** + * UsbOperationInternal allows UsbPort to support both synchronous and + * asynchronous function irrespective of whether the underlying hal + * method is synchronous or asynchronous. + * + * @hide + */ +public final class UsbOperationInternal extends IUsbOperationInternal.Stub { + private static final String TAG = "UsbPortStatus"; + private final int mOperationID; + // Cached portId. + private final String mId; + // True implies operation did not timeout. + private boolean mOperationComplete; + private @UsbOperationStatus int mStatus; + final ReentrantLock mLock = new ReentrantLock(); + final Condition mOperationWait = mLock.newCondition(); + // Maximum time the caller has to wait for onOperationComplete to be called. + private static final int USB_OPERATION_TIMEOUT_MSECS = 5000; + + /** + * The requested operation was successfully completed. + * Returned in {@link onOperationComplete} and {@link getStatus}. + */ + public static final int USB_OPERATION_SUCCESS = 0; + + /** + * The requested operation failed due to internal error. + * Returned in {@link onOperationComplete} and {@link getStatus}. + */ + public static final int USB_OPERATION_ERROR_INTERNAL = 1; + + /** + * The requested operation failed as it's not supported. + * Returned in {@link onOperationComplete} and {@link getStatus}. + */ + public static final int USB_OPERATION_ERROR_NOT_SUPPORTED = 2; + + /** + * The requested operation failed as it's not supported. + * Returned in {@link onOperationComplete} and {@link getStatus}. + */ + public static final int USB_OPERATION_ERROR_PORT_MISMATCH = 3; + + @IntDef(prefix = { "USB_OPERATION_" }, value = { + USB_OPERATION_SUCCESS, + USB_OPERATION_ERROR_INTERNAL, + USB_OPERATION_ERROR_NOT_SUPPORTED, + USB_OPERATION_ERROR_PORT_MISMATCH + }) + @Retention(RetentionPolicy.SOURCE) + @interface UsbOperationStatus{} + + UsbOperationInternal(int operationID, String id) { + this.mOperationID = operationID; + this.mId = id; + } + + /** + * Hal glue layer would directly call this function when the requested + * operation is complete. + */ + @Override + public void onOperationComplete(@UsbOperationStatus int status) { + mLock.lock(); + try { + mOperationComplete = true; + mStatus = status; + Log.i(TAG, "Port:" + mId + " opID:" + mOperationID + " status:" + mStatus); + mOperationWait.signal(); + } finally { + mLock.unlock(); + } + } + + /** + * Caller invokes this function to wait for the operation to be complete. + */ + public void waitForOperationComplete() { + mLock.lock(); + try { + long now = System.currentTimeMillis(); + long deadline = now + USB_OPERATION_TIMEOUT_MSECS; + // Wait in loop to overcome spurious wakeups. + do { + mOperationWait.await(deadline - System.currentTimeMillis(), + TimeUnit.MILLISECONDS); + } while (!mOperationComplete && System.currentTimeMillis() < deadline); + if (!mOperationComplete) { + Log.e(TAG, "Port:" + mId + " opID:" + mOperationID + + " operationComplete not received in " + USB_OPERATION_TIMEOUT_MSECS + + "msecs"); + } + } catch (InterruptedException e) { + Log.e(TAG, "Port:" + mId + " opID:" + mOperationID + " operationComplete interrupted"); + } finally { + mLock.unlock(); + } + } + + public @UsbOperationStatus int getStatus() { + return mOperationComplete ? mStatus : USB_OPERATION_ERROR_INTERNAL; + } +} diff --git a/core/java/android/hardware/usb/UsbPort.java b/core/java/android/hardware/usb/UsbPort.java index 274e23fff292..bef4dea019a2 100644 --- a/core/java/android/hardware/usb/UsbPort.java +++ b/core/java/android/hardware/usb/UsbPort.java @@ -16,6 +16,10 @@ package android.hardware.usb; +import static android.hardware.usb.UsbOperationInternal.USB_OPERATION_ERROR_INTERNAL; +import static android.hardware.usb.UsbOperationInternal.USB_OPERATION_ERROR_NOT_SUPPORTED; +import static android.hardware.usb.UsbOperationInternal.USB_OPERATION_ERROR_PORT_MISMATCH; +import static android.hardware.usb.UsbOperationInternal.USB_OPERATION_SUCCESS; import static android.hardware.usb.UsbPortStatus.CONTAMINANT_DETECTION_DETECTED; import static android.hardware.usb.UsbPortStatus.CONTAMINANT_DETECTION_DISABLED; import static android.hardware.usb.UsbPortStatus.CONTAMINANT_DETECTION_NOT_DETECTED; @@ -29,20 +33,38 @@ import static android.hardware.usb.UsbPortStatus.MODE_DFP; import static android.hardware.usb.UsbPortStatus.MODE_DUAL; import static android.hardware.usb.UsbPortStatus.MODE_NONE; import static android.hardware.usb.UsbPortStatus.MODE_UFP; +import static android.hardware.usb.UsbPortStatus.POWER_BRICK_STATUS_DISCONNECTED; +import static android.hardware.usb.UsbPortStatus.POWER_BRICK_STATUS_UNKNOWN; +import static android.hardware.usb.UsbPortStatus.POWER_BRICK_STATUS_CONNECTED; import static android.hardware.usb.UsbPortStatus.POWER_ROLE_NONE; import static android.hardware.usb.UsbPortStatus.POWER_ROLE_SINK; import static android.hardware.usb.UsbPortStatus.POWER_ROLE_SOURCE; +import static android.hardware.usb.UsbPortStatus.USB_DATA_STATUS_UNKNOWN; +import static android.hardware.usb.UsbPortStatus.USB_DATA_STATUS_ENABLED; +import static android.hardware.usb.UsbPortStatus.USB_DATA_STATUS_DISABLED_OVERHEAT; +import static android.hardware.usb.UsbPortStatus.USB_DATA_STATUS_DISABLED_CONTAMINANT; +import static android.hardware.usb.UsbPortStatus.USB_DATA_STATUS_DISABLED_DOCK; +import static android.hardware.usb.UsbPortStatus.USB_DATA_STATUS_DISABLED_FORCE; +import static android.hardware.usb.UsbPortStatus.USB_DATA_STATUS_DISABLED_DEBUG; import android.Manifest; +import android.annotation.CheckResult; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemApi; +import android.hardware.usb.UsbOperationInternal; import android.hardware.usb.V1_0.Constants; +import android.os.Binder; +import android.util.Log; import com.android.internal.util.Preconditions; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Objects; +import java.util.concurrent.atomic.AtomicInteger; /** * Represents a physical USB port and describes its characteristics. @@ -51,6 +73,7 @@ import java.util.Objects; */ @SystemApi public final class UsbPort { + private static final String TAG = "UsbPort"; private final String mId; private final int mSupportedModes; private final UsbManager mUsbManager; @@ -64,6 +87,125 @@ public final class UsbPort { */ private static final int POWER_ROLE_OFFSET = Constants.PortPowerRole.NONE; + /** + * Counter for tracking UsbOperation operations. + */ + private static final AtomicInteger sUsbOperationCount = new AtomicInteger(); + + /** + * The {@link #enableUsbData} request was successfully completed. + */ + public static final int ENABLE_USB_DATA_SUCCESS = 0; + + /** + * The {@link #enableUsbData} request failed due to internal error. + */ + public static final int ENABLE_USB_DATA_ERROR_INTERNAL = 1; + + /** + * The {@link #enableUsbData} request failed as it's not supported. + */ + public static final int ENABLE_USB_DATA_ERROR_NOT_SUPPORTED = 2; + + /** + * The {@link #enableUsbData} request failed as port id mismatched. + */ + public static final int ENABLE_USB_DATA_ERROR_PORT_MISMATCH = 3; + + /** + * The {@link #enableUsbData} request failed due to other reasons. + */ + public static final int ENABLE_USB_DATA_ERROR_OTHER = 4; + + /** @hide */ + @IntDef(prefix = { "ENABLE_USB_DATA_" }, value = { + ENABLE_USB_DATA_SUCCESS, + ENABLE_USB_DATA_ERROR_INTERNAL, + ENABLE_USB_DATA_ERROR_NOT_SUPPORTED, + ENABLE_USB_DATA_ERROR_PORT_MISMATCH, + ENABLE_USB_DATA_ERROR_OTHER + }) + @Retention(RetentionPolicy.SOURCE) + @interface EnableUsbDataStatus{} + + /** + * The {@link #enableLimitPowerTransfer} request was successfully completed. + */ + public static final int ENABLE_LIMIT_POWER_TRANSFER_SUCCESS = 0; + + /** + * The {@link #enableLimitPowerTransfer} request failed due to internal error. + */ + public static final int ENABLE_LIMIT_POWER_TRANSFER_ERROR_INTERNAL = 1; + + /** + * The {@link #enableLimitPowerTransfer} request failed as it's not supported. + */ + public static final int ENABLE_LIMIT_POWER_TRANSFER_ERROR_NOT_SUPPORTED = 2; + + /** + * The {@link #enableLimitPowerTransfer} request failed as port id mismatched. + */ + public static final int ENABLE_LIMIT_POWER_TRANSFER_ERROR_PORT_MISMATCH = 3; + + /** + * The {@link #enableLimitPowerTransfer} request failed due to other reasons. + */ + public static final int ENABLE_LIMIT_POWER_TRANSFER_ERROR_OTHER = 4; + + /** @hide */ + @IntDef(prefix = { "ENABLE_LIMIT_POWER_TRANSFER_" }, value = { + ENABLE_LIMIT_POWER_TRANSFER_SUCCESS, + ENABLE_LIMIT_POWER_TRANSFER_ERROR_INTERNAL, + ENABLE_LIMIT_POWER_TRANSFER_ERROR_NOT_SUPPORTED, + ENABLE_LIMIT_POWER_TRANSFER_ERROR_PORT_MISMATCH, + ENABLE_LIMIT_POWER_TRANSFER_ERROR_OTHER + }) + @Retention(RetentionPolicy.SOURCE) + @interface EnableLimitPowerTransferStatus{} + + /** + * The {@link #enableUsbDataWhileDocked} request was successfully completed. + */ + public static final int ENABLE_USB_DATA_WHILE_DOCKED_SUCCESS = 0; + + /** + * The {@link #enableUsbDataWhileDocked} request failed due to internal error. + */ + public static final int ENABLE_USB_DATA_WHILE_DOCKED_ERROR_INTERNAL = 1; + + /** + * The {@link #enableUsbDataWhileDocked} request failed as it's not supported. + */ + public static final int ENABLE_USB_DATA_WHILE_DOCKED_ERROR_NOT_SUPPORTED = 2; + + /** + * The {@link #enableUsbDataWhileDocked} request failed as port id mismatched. + */ + public static final int ENABLE_USB_DATA_WHILE_DOCKED_ERROR_PORT_MISMATCH = 3; + + /** + * The {@link #enableUsbDataWhileDocked} request failed as data is still enabled. + */ + public static final int ENABLE_USB_DATA_WHILE_DOCKED_ERROR_DATA_ENABLED = 4; + + /** + * The {@link #enableUsbDataWhileDocked} request failed due to other reasons. + */ + public static final int ENABLE_USB_DATA_WHILE_DOCKED_ERROR_OTHER = 5; + + /** @hide */ + @IntDef(prefix = { "ENABLE_USB_DATA_WHILE_DOCKED_" }, value = { + ENABLE_USB_DATA_WHILE_DOCKED_SUCCESS, + ENABLE_USB_DATA_WHILE_DOCKED_ERROR_INTERNAL, + ENABLE_USB_DATA_WHILE_DOCKED_ERROR_NOT_SUPPORTED, + ENABLE_USB_DATA_WHILE_DOCKED_ERROR_PORT_MISMATCH, + ENABLE_USB_DATA_WHILE_DOCKED_ERROR_DATA_ENABLED, + ENABLE_USB_DATA_WHILE_DOCKED_ERROR_OTHER + }) + @Retention(RetentionPolicy.SOURCE) + @interface EnableUsbDataWhileDockedStatus{} + /** @hide */ public UsbPort(@NonNull UsbManager usbManager, @NonNull String id, int supportedModes, int supportedContaminantProtectionModes, @@ -157,7 +299,7 @@ public final class UsbPort { * {@link UsbPortStatus#isRoleCombinationSupported UsbPortStatus.isRoleCombinationSupported}. * </p><p> * Note: This function is asynchronous and may fail silently without applying - * the requested changes. If this function does cause a status change to occur then + * the operationed changes. If this function does cause a status change to occur then * a {@link UsbManager#ACTION_USB_PORT_CHANGED} broadcast will be sent. * </p> * @@ -177,6 +319,133 @@ public final class UsbPort { } /** + * Enables/Disables Usb data on the port. + * + * @param enable When true enables USB data if disabled. + * When false disables USB data if enabled. + * @return {@link #ENABLE_USB_DATA_SUCCESS} when request completes successfully or + * {@link #ENABLE_USB_DATA_ERROR_INTERNAL} when request fails due to internal + * error or + * {@link ENABLE_USB_DATA_ERROR_NOT_SUPPORTED} when not supported or + * {@link ENABLE_USB_DATA_ERROR_PORT_MISMATCH} when request fails due to port id + * mismatch or + * {@link ENABLE_USB_DATA_ERROR_OTHER} when fails due to other reasons. + */ + @CheckResult + @RequiresPermission(Manifest.permission.MANAGE_USB) + public @EnableUsbDataStatus int enableUsbData(boolean enable) { + // UID is added To minimize operationID overlap between two different packages. + int operationId = sUsbOperationCount.incrementAndGet() + Binder.getCallingUid(); + Log.i(TAG, "enableUsbData opId:" + operationId + + " callingUid:" + Binder.getCallingUid()); + UsbOperationInternal opCallback = + new UsbOperationInternal(operationId, mId); + if (mUsbManager.enableUsbData(this, enable, operationId, opCallback) == true) { + opCallback.waitForOperationComplete(); + } + + int result = opCallback.getStatus(); + switch (result) { + case USB_OPERATION_SUCCESS: + return ENABLE_USB_DATA_SUCCESS; + case USB_OPERATION_ERROR_INTERNAL: + return ENABLE_USB_DATA_ERROR_INTERNAL; + case USB_OPERATION_ERROR_NOT_SUPPORTED: + return ENABLE_USB_DATA_ERROR_NOT_SUPPORTED; + case USB_OPERATION_ERROR_PORT_MISMATCH: + return ENABLE_USB_DATA_ERROR_PORT_MISMATCH; + default: + return ENABLE_USB_DATA_ERROR_OTHER; + } + } + + /** + * Enables Usb data when disabled due to {@link UsbPort#USB_DATA_STATUS_DISABLED_DOCK} + * + * @return {@link #ENABLE_USB_DATA_WHILE_DOCKED_SUCCESS} when request completes successfully or + * {@link #ENABLE_USB_DATA_WHILE_DOCKED_ERROR_INTERNAL} when request fails due to + * internal error or + * {@link ENABLE_USB_DATA_WHILE_DOCKED_ERROR_NOT_SUPPORTED} when not supported or + * {@link ENABLE_USB_DATA_WHILE_DOCKED_ERROR_PORT_MISMATCH} when request fails due to + * port id mismatch or + * {@link ENABLE_USB_DATA_WHILE_DOCKED_ERROR_DATA_ENABLED} when request fails as data + * is still enabled or + * {@link ENABLE_USB_DATA_WHILE_DOCKED_ERROR_OTHER} when fails due to other reasons. + */ + @CheckResult + @RequiresPermission(Manifest.permission.MANAGE_USB) + public @EnableUsbDataWhileDockedStatus int enableUsbDataWhileDocked() { + // UID is added To minimize operationID overlap between two different packages. + int operationId = sUsbOperationCount.incrementAndGet() + Binder.getCallingUid(); + Log.i(TAG, "enableUsbData opId:" + operationId + + " callingUid:" + Binder.getCallingUid()); + UsbPortStatus portStatus = getStatus(); + if (portStatus != null && + !usbDataStatusToString(portStatus.getUsbDataStatus()).contains("disabled-dock")) { + return ENABLE_USB_DATA_WHILE_DOCKED_ERROR_DATA_ENABLED; + } + + UsbOperationInternal opCallback = + new UsbOperationInternal(operationId, mId); + mUsbManager.enableUsbDataWhileDocked(this, operationId, opCallback); + opCallback.waitForOperationComplete(); + int result = opCallback.getStatus(); + switch (result) { + case USB_OPERATION_SUCCESS: + return ENABLE_USB_DATA_WHILE_DOCKED_SUCCESS; + case USB_OPERATION_ERROR_INTERNAL: + return ENABLE_USB_DATA_WHILE_DOCKED_ERROR_INTERNAL; + case USB_OPERATION_ERROR_NOT_SUPPORTED: + return ENABLE_USB_DATA_WHILE_DOCKED_ERROR_NOT_SUPPORTED; + case USB_OPERATION_ERROR_PORT_MISMATCH: + return ENABLE_USB_DATA_WHILE_DOCKED_ERROR_PORT_MISMATCH; + default: + return ENABLE_USB_DATA_WHILE_DOCKED_ERROR_OTHER; + } + } + + /** + * Limits power transfer In and out of the port. + * <p> + * Disables charging and limits sourcing power(when permitted by the USB spec) until + * port disconnect event. + * </p> + * @param enable limits power transfer when true. + * @return {@link #ENABLE_LIMIT_POWER_TRANSFER_SUCCESS} when request completes successfully or + * {@link #ENABLE_LIMIT_POWER_TRANSFER_ERROR_INTERNAL} when request fails due to + * internal error or + * {@link ENABLE_LIMIT_POWER_TRANSFER_ERROR_NOT_SUPPORTED} when not supported or + * {@link ENABLE_LIMIT_POWER_TRANSFER_ERROR_PORT_MISMATCH} when request fails due to + * port id mismatch or + * {@link ENABLE_LIMIT_POWER_TRANSFER_ERROR_OTHER} when fails due to other reasons. + */ + @CheckResult + @RequiresPermission(Manifest.permission.MANAGE_USB) + public @EnableLimitPowerTransferStatus int enableLimitPowerTransfer(boolean enable) { + // UID is added To minimize operationID overlap between two different packages. + int operationId = sUsbOperationCount.incrementAndGet() + Binder.getCallingUid(); + Log.i(TAG, "enableLimitPowerTransfer opId:" + operationId + + " callingUid:" + Binder.getCallingUid()); + UsbOperationInternal opCallback = + new UsbOperationInternal(operationId, mId); + mUsbManager.enableLimitPowerTransfer(this, enable, operationId, opCallback); + opCallback.waitForOperationComplete(); + int result = opCallback.getStatus(); + switch (result) { + case USB_OPERATION_SUCCESS: + return ENABLE_LIMIT_POWER_TRANSFER_SUCCESS; + case USB_OPERATION_ERROR_INTERNAL: + return ENABLE_LIMIT_POWER_TRANSFER_ERROR_INTERNAL; + case USB_OPERATION_ERROR_NOT_SUPPORTED: + return ENABLE_LIMIT_POWER_TRANSFER_ERROR_NOT_SUPPORTED; + case USB_OPERATION_ERROR_PORT_MISMATCH: + return ENABLE_LIMIT_POWER_TRANSFER_ERROR_PORT_MISMATCH; + default: + return ENABLE_LIMIT_POWER_TRANSFER_ERROR_OTHER; + } + } + + /** * @hide **/ public void enableContaminantDetection(boolean enable) { @@ -274,6 +543,57 @@ public final class UsbPort { } /** @hide */ + public static String usbDataStatusToString(int usbDataStatus) { + switch (usbDataStatus) { + case USB_DATA_STATUS_UNKNOWN: + return "unknown"; + case USB_DATA_STATUS_ENABLED: + return "enabled"; + case USB_DATA_STATUS_DISABLED_OVERHEAT: + return "disabled-overheat"; + case USB_DATA_STATUS_DISABLED_CONTAMINANT: + return "disabled-contaminant"; + case USB_DATA_STATUS_DISABLED_DOCK: + return "disabled-dock"; + case USB_DATA_STATUS_DISABLED_FORCE: + return "disabled-force"; + case USB_DATA_STATUS_DISABLED_DEBUG: + return "disabled-debug"; + default: + return Integer.toString(usbDataStatus); + } + } + + /** @hide */ + public static String usbDataStatusToString(int[] usbDataStatus) { + StringBuilder modeString = new StringBuilder(); + if (usbDataStatus == null) { + return "unknown"; + } + for (int i = 0; i < usbDataStatus.length; i++) { + modeString.append(usbDataStatusToString(usbDataStatus[i])); + if (i < usbDataStatus.length - 1) { + modeString.append(", "); + } + } + return modeString.toString(); + } + + /** @hide */ + public static String powerBrickStatusToString(int powerBrickStatus) { + switch (powerBrickStatus) { + case POWER_BRICK_STATUS_UNKNOWN: + return "unknown"; + case POWER_BRICK_STATUS_CONNECTED: + return "connected"; + case POWER_BRICK_STATUS_DISCONNECTED: + return "disconnected"; + default: + return Integer.toString(powerBrickStatus); + } + } + + /** @hide */ public static String roleCombinationsToString(int combo) { StringBuilder result = new StringBuilder(); result.append("["); diff --git a/core/java/android/hardware/usb/UsbPortStatus.java b/core/java/android/hardware/usb/UsbPortStatus.java index bb7aff651b3d..d1f424667d73 100644 --- a/core/java/android/hardware/usb/UsbPortStatus.java +++ b/core/java/android/hardware/usb/UsbPortStatus.java @@ -18,8 +18,8 @@ package android.hardware.usb; import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.SystemApi; -import android.hardware.usb.V1_0.Constants; import android.os.Parcel; import android.os.Parcelable; @@ -36,27 +36,31 @@ import java.lang.annotation.RetentionPolicy; @Immutable @SystemApi public final class UsbPortStatus implements Parcelable { + private static final String TAG = "UsbPortStatus"; private final int mCurrentMode; private final @UsbPowerRole int mCurrentPowerRole; private final @UsbDataRole int mCurrentDataRole; private final int mSupportedRoleCombinations; private final @ContaminantProtectionStatus int mContaminantProtectionStatus; private final @ContaminantDetectionStatus int mContaminantDetectionStatus; + private final boolean mPowerTransferLimited; + private final @UsbDataStatus int[] mUsbDataStatus; + private final @PowerBrickStatus int mPowerBrickStatus; /** * Power role: This USB port does not have a power role. */ - public static final int POWER_ROLE_NONE = Constants.PortPowerRole.NONE; + public static final int POWER_ROLE_NONE = 0; /** * Power role: This USB port can act as a source (provide power). */ - public static final int POWER_ROLE_SOURCE = Constants.PortPowerRole.SOURCE; + public static final int POWER_ROLE_SOURCE = 1; /** * Power role: This USB port can act as a sink (receive power). */ - public static final int POWER_ROLE_SINK = Constants.PortPowerRole.SINK; + public static final int POWER_ROLE_SINK = 2; @IntDef(prefix = { "POWER_ROLE_" }, value = { POWER_ROLE_NONE, @@ -69,17 +73,17 @@ public final class UsbPortStatus implements Parcelable { /** * Power role: This USB port does not have a data role. */ - public static final int DATA_ROLE_NONE = Constants.PortDataRole.NONE; + public static final int DATA_ROLE_NONE = 0; /** * Data role: This USB port can act as a host (access data services). */ - public static final int DATA_ROLE_HOST = Constants.PortDataRole.HOST; + public static final int DATA_ROLE_HOST = 1; /** * Data role: This USB port can act as a device (offer data services). */ - public static final int DATA_ROLE_DEVICE = Constants.PortDataRole.DEVICE; + public static final int DATA_ROLE_DEVICE = 2; @IntDef(prefix = { "DATA_ROLE_" }, value = { DATA_ROLE_NONE, @@ -92,23 +96,23 @@ public final class UsbPortStatus implements Parcelable { /** * There is currently nothing connected to this USB port. */ - public static final int MODE_NONE = Constants.PortMode.NONE; + public static final int MODE_NONE = 0; /** - * This USB port can act as a downstream facing port (host). + * This USB port can act as an upstream facing port (device). * - * <p> Implies that the port supports the {@link #POWER_ROLE_SOURCE} and - * {@link #DATA_ROLE_HOST} combination of roles (and possibly others as well). + * <p> Implies that the port supports the {@link #POWER_ROLE_SINK} and + * {@link #DATA_ROLE_DEVICE} combination of roles (and possibly others as well). */ - public static final int MODE_DFP = Constants.PortMode.DFP; + public static final int MODE_UFP = 1 << 0; /** - * This USB port can act as an upstream facing port (device). + * This USB port can act as a downstream facing port (host). * - * <p> Implies that the port supports the {@link #POWER_ROLE_SINK} and - * {@link #DATA_ROLE_DEVICE} combination of roles (and possibly others as well). + * <p> Implies that the port supports the {@link #POWER_ROLE_SOURCE} and + * {@link #DATA_ROLE_HOST} combination of roles (and possibly others as well). */ - public static final int MODE_UFP = Constants.PortMode.UFP; + public static final int MODE_DFP = 1 << 1; /** * This USB port can act either as an downstream facing port (host) or as @@ -120,87 +124,127 @@ public final class UsbPortStatus implements Parcelable { * * @hide */ - public static final int MODE_DUAL = Constants.PortMode.DRP; + public static final int MODE_DUAL = MODE_UFP | MODE_DFP; /** * This USB port can support USB Type-C Audio accessory. */ - public static final int MODE_AUDIO_ACCESSORY = - android.hardware.usb.V1_1.Constants.PortMode_1_1.AUDIO_ACCESSORY; + public static final int MODE_AUDIO_ACCESSORY = 1 << 2; /** * This USB port can support USB Type-C debug accessory. */ - public static final int MODE_DEBUG_ACCESSORY = - android.hardware.usb.V1_1.Constants.PortMode_1_1.DEBUG_ACCESSORY; + public static final int MODE_DEBUG_ACCESSORY = 1 << 3; /** * Contaminant presence detection not supported by the device. * @hide */ - public static final int CONTAMINANT_DETECTION_NOT_SUPPORTED = - android.hardware.usb.V1_2.Constants.ContaminantDetectionStatus.NOT_SUPPORTED; + public static final int CONTAMINANT_DETECTION_NOT_SUPPORTED = 0; /** * Contaminant presence detection supported but disabled. * @hide */ - public static final int CONTAMINANT_DETECTION_DISABLED = - android.hardware.usb.V1_2.Constants.ContaminantDetectionStatus.DISABLED; + public static final int CONTAMINANT_DETECTION_DISABLED = 1; /** * Contaminant presence enabled but not detected. * @hide */ - public static final int CONTAMINANT_DETECTION_NOT_DETECTED = - android.hardware.usb.V1_2.Constants.ContaminantDetectionStatus.NOT_DETECTED; + public static final int CONTAMINANT_DETECTION_NOT_DETECTED = 2; /** * Contaminant presence enabled and detected. * @hide */ - public static final int CONTAMINANT_DETECTION_DETECTED = - android.hardware.usb.V1_2.Constants.ContaminantDetectionStatus.DETECTED; + public static final int CONTAMINANT_DETECTION_DETECTED = 3; /** * Contaminant protection - No action performed upon detection of * contaminant presence. * @hide */ - public static final int CONTAMINANT_PROTECTION_NONE = - android.hardware.usb.V1_2.Constants.ContaminantProtectionStatus.NONE; + public static final int CONTAMINANT_PROTECTION_NONE = 0; /** * Contaminant protection - Port is forced to sink upon detection of * contaminant presence. * @hide */ - public static final int CONTAMINANT_PROTECTION_SINK = - android.hardware.usb.V1_2.Constants.ContaminantProtectionStatus.FORCE_SINK; + public static final int CONTAMINANT_PROTECTION_SINK = 1 << 0; /** * Contaminant protection - Port is forced to source upon detection of * contaminant presence. * @hide */ - public static final int CONTAMINANT_PROTECTION_SOURCE = - android.hardware.usb.V1_2.Constants.ContaminantProtectionStatus.FORCE_SOURCE; + public static final int CONTAMINANT_PROTECTION_SOURCE = 1 << 1; /** * Contaminant protection - Port is disabled upon detection of * contaminant presence. * @hide */ - public static final int CONTAMINANT_PROTECTION_FORCE_DISABLE = - android.hardware.usb.V1_2.Constants.ContaminantProtectionStatus.FORCE_DISABLE; + public static final int CONTAMINANT_PROTECTION_FORCE_DISABLE = 1 << 2; /** * Contaminant protection - Port is disabled upon detection of * contaminant presence. * @hide */ - public static final int CONTAMINANT_PROTECTION_DISABLED = - android.hardware.usb.V1_2.Constants.ContaminantProtectionStatus.DISABLED; + public static final int CONTAMINANT_PROTECTION_DISABLED = 1 << 3; + + /** + * USB data status is not known. + */ + public static final int USB_DATA_STATUS_UNKNOWN = 0; + + /** + * USB data is enabled. + */ + public static final int USB_DATA_STATUS_ENABLED = 1; + + /** + * USB data is disabled as the port is too hot. + */ + public static final int USB_DATA_STATUS_DISABLED_OVERHEAT = 2; + + /** + * USB data is disabled due to contaminated port. + */ + public static final int USB_DATA_STATUS_DISABLED_CONTAMINANT = 3; + + /** + * USB data is disabled due to docking event. + */ + public static final int USB_DATA_STATUS_DISABLED_DOCK = 4; + + /** + * USB data is disabled by + * {@link UsbPort#enableUsbData UsbPort.enableUsbData}. + */ + public static final int USB_DATA_STATUS_DISABLED_FORCE = 5; + + /** + * USB data is disabled for debug. + */ + public static final int USB_DATA_STATUS_DISABLED_DEBUG = 6; + + /** + * Unknown whether a power brick is connected. + */ + public static final int POWER_BRICK_STATUS_UNKNOWN = 0; + + /** + * The connected device is a power brick. + */ + public static final int POWER_BRICK_STATUS_CONNECTED = 1; + + /** + * The connected device is not power brick. + */ + public static final int POWER_BRICK_STATUS_DISCONNECTED = 2; @IntDef(prefix = { "CONTAMINANT_DETECTION_" }, value = { CONTAMINANT_DETECTION_NOT_SUPPORTED, @@ -232,6 +276,44 @@ public final class UsbPortStatus implements Parcelable { @interface UsbPortMode{} /** @hide */ + @IntDef(prefix = { "USB_DATA_STATUS_" }, value = { + USB_DATA_STATUS_UNKNOWN, + USB_DATA_STATUS_ENABLED, + USB_DATA_STATUS_DISABLED_OVERHEAT, + USB_DATA_STATUS_DISABLED_CONTAMINANT, + USB_DATA_STATUS_DISABLED_DOCK, + USB_DATA_STATUS_DISABLED_FORCE, + USB_DATA_STATUS_DISABLED_DEBUG + }) + @Retention(RetentionPolicy.SOURCE) + @interface UsbDataStatus{} + + /** @hide */ + @IntDef(prefix = { "POWER_BRICK_STATUS_" }, value = { + POWER_BRICK_STATUS_UNKNOWN, + POWER_BRICK_STATUS_DISCONNECTED, + POWER_BRICK_STATUS_CONNECTED, + }) + @Retention(RetentionPolicy.SOURCE) + @interface PowerBrickStatus{} + + /** @hide */ + public UsbPortStatus(int currentMode, int currentPowerRole, int currentDataRole, + int supportedRoleCombinations, int contaminantProtectionStatus, + int contaminantDetectionStatus, @UsbDataStatus int[] usbDataStatus, + boolean powerTransferLimited, @PowerBrickStatus int powerBrickStatus) { + mCurrentMode = currentMode; + mCurrentPowerRole = currentPowerRole; + mCurrentDataRole = currentDataRole; + mSupportedRoleCombinations = supportedRoleCombinations; + mContaminantProtectionStatus = contaminantProtectionStatus; + mContaminantDetectionStatus = contaminantDetectionStatus; + mUsbDataStatus = usbDataStatus; + mPowerTransferLimited = powerTransferLimited; + mPowerBrickStatus = powerBrickStatus; + } + + /** @hide */ public UsbPortStatus(int currentMode, int currentPowerRole, int currentDataRole, int supportedRoleCombinations, int contaminantProtectionStatus, int contaminantDetectionStatus) { @@ -241,6 +323,9 @@ public final class UsbPortStatus implements Parcelable { mSupportedRoleCombinations = supportedRoleCombinations; mContaminantProtectionStatus = contaminantProtectionStatus; mContaminantDetectionStatus = contaminantDetectionStatus; + mUsbDataStatus = new int[]{USB_DATA_STATUS_UNKNOWN}; + mPowerBrickStatus = POWER_BRICK_STATUS_UNKNOWN; + mPowerTransferLimited = false; } /** @@ -323,6 +408,40 @@ public final class UsbPortStatus implements Parcelable { return mContaminantProtectionStatus; } + /** + * Returns UsbData status. + * + * @return Current USB data status of the port: {@link #USB_DATA_STATUS_UNKNOWN} + * or {@link #USB_DATA_STATUS_ENABLED} or {@link #USB_DATA_STATUS_DIASBLED_OVERHEAT} + * or {@link #USB_DATA_STATUS_DISABLED_CONTAMINANT} + * or {@link #USB_DATA_STATUS_DISABLED_DOCK} or {@link #USB_DATA_STATUS_DISABLED_FORCE} + * or {@link #USB_DATA_STATUS_DISABLED_DEBUG} + */ + public @UsbDataStatus @Nullable int[] getUsbDataStatus() { + return mUsbDataStatus; + } + + /** + * Returns whether power transfer is limited. + * + * @return true when power transfer is limited. + * false otherwise. + */ + public boolean isPowerTransferLimited() { + return mPowerTransferLimited; + } + + /** + * Let's the caller know if a power brick is connected to the USB port. + * + * @return {@link #POWER_BRICK_STATUS_UNKNOWN} + * or {@link #POWER_BRICK_STATUS_CONNECTED} + * or {@link #POWER_BRICK_STATUS_DISCONNECTED} + */ + public @PowerBrickStatus int getPowerBrickStatus() { + return mPowerBrickStatus; + } + @NonNull @Override public String toString() { @@ -336,6 +455,12 @@ public final class UsbPortStatus implements Parcelable { + getContaminantDetectionStatus() + ", contaminantProtectionStatus=" + getContaminantProtectionStatus() + + ", usbDataStatus=" + + UsbPort.usbDataStatusToString(getUsbDataStatus()) + + ", isPowerTransferLimited=" + + isPowerTransferLimited() + +", powerBrickStatus=" + + UsbPort.powerBrickStatusToString(getPowerBrickStatus()) + "}"; } @@ -352,6 +477,10 @@ public final class UsbPortStatus implements Parcelable { dest.writeInt(mSupportedRoleCombinations); dest.writeInt(mContaminantProtectionStatus); dest.writeInt(mContaminantDetectionStatus); + dest.writeInt(mUsbDataStatus.length); + dest.writeIntArray(mUsbDataStatus); + dest.writeBoolean(mPowerTransferLimited); + dest.writeInt(mPowerBrickStatus); } public static final @NonNull Parcelable.Creator<UsbPortStatus> CREATOR = @@ -364,9 +493,14 @@ public final class UsbPortStatus implements Parcelable { int supportedRoleCombinations = in.readInt(); int contaminantProtectionStatus = in.readInt(); int contaminantDetectionStatus = in.readInt(); + int[] usbDataStatus = new int[in.readInt()]; + in.readIntArray(usbDataStatus); + boolean powerTransferLimited = in.readBoolean(); + int powerBrickStatus = in.readInt(); return new UsbPortStatus(currentMode, currentPowerRole, currentDataRole, supportedRoleCombinations, contaminantProtectionStatus, - contaminantDetectionStatus); + contaminantDetectionStatus, usbDataStatus, powerTransferLimited, + powerBrickStatus); } @Override diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java index 6f15588c0724..cc325cde1f41 100644 --- a/core/java/android/inputmethodservice/IInputMethodWrapper.java +++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java @@ -72,7 +72,6 @@ class IInputMethodWrapper extends IInputMethod.Stub private static final int DO_START_INPUT = 32; private static final int DO_CREATE_SESSION = 40; private static final int DO_SET_SESSION_ENABLED = 45; - private static final int DO_REVOKE_SESSION = 50; private static final int DO_SHOW_SOFT_INPUT = 60; private static final int DO_HIDE_SOFT_INPUT = 70; private static final int DO_CHANGE_INPUTMETHOD_SUBTYPE = 80; @@ -215,9 +214,6 @@ class IInputMethodWrapper extends IInputMethod.Stub inputMethod.setSessionEnabled((InputMethodSession)msg.obj, msg.arg1 != 0); return; - case DO_REVOKE_SESSION: - inputMethod.revokeSession((InputMethodSession)msg.obj); - return; case DO_SHOW_SOFT_INPUT: { final SomeArgs args = (SomeArgs)msg.obj; inputMethod.showSoftInputWithToken( @@ -368,22 +364,6 @@ class IInputMethodWrapper extends IInputMethod.Stub @BinderThread @Override - public void revokeSession(IInputMethodSession session) { - try { - InputMethodSession ls = ((IInputMethodSessionWrapper) - session).getInternalInputMethodSession(); - if (ls == null) { - Log.w(TAG, "Session is already finished: " + session); - return; - } - mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_REVOKE_SESSION, ls)); - } catch (ClassCastException e) { - Log.w(TAG, "Incoming session not of correct type: " + session, e); - } - } - - @BinderThread - @Override public void showSoftInput(IBinder showInputToken, int flags, ResultReceiver resultReceiver) { mCaller.executeOrSendMessage(mCaller.obtainMessageIOO(DO_SHOW_SOFT_INPUT, flags, showInputToken, resultReceiver)); diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index 09d50850788b..5d2d8eafb3a8 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -469,6 +469,10 @@ public class InputMethodService extends AbstractInputMethodService { InputMethodManager mImm; private InputMethodPrivilegedOperations mPrivOps = new InputMethodPrivilegedOperations(); + @NonNull + private final NavigationBarController mNavigationBarController = + new NavigationBarController(this); + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) int mTheme = 0; @@ -611,6 +615,7 @@ public class InputMethodService extends AbstractInputMethodService { info.touchableRegion.set(mTmpInsets.touchableRegion); info.setTouchableInsets(mTmpInsets.touchableInsets); } + mNavigationBarController.updateTouchableInsets(mTmpInsets, info); if (mInputFrame != null) { setImeExclusionRect(mTmpInsets.visibleTopInsets); @@ -1534,6 +1539,7 @@ public class InputMethodService extends AbstractInputMethodService { mCandidatesVisibility = getCandidatesHiddenVisibility(); mCandidatesFrame.setVisibility(mCandidatesVisibility); mInputFrame.setVisibility(View.GONE); + mNavigationBarController.onViewInitialized(); Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); } @@ -1543,6 +1549,7 @@ public class InputMethodService extends AbstractInputMethodService { mRootView.getViewTreeObserver().removeOnComputeInternalInsetsListener( mInsetsComputer); doFinishInput(); + mNavigationBarController.onDestroy(); mWindow.dismissForDestroyIfNecessary(); if (mSettingsObserver != null) { mSettingsObserver.unregister(); @@ -2451,6 +2458,7 @@ public class InputMethodService extends AbstractInputMethodService { setImeWindowStatus(nextImeWindowStatus, mBackDisposition); } + mNavigationBarController.onWindowShown(); // compute visibility onWindowShown(); mWindowVisible = true; @@ -3656,6 +3664,7 @@ public class InputMethodService extends AbstractInputMethodService { + " touchableInsets=" + mTmpInsets.touchableInsets + " touchableRegion=" + mTmpInsets.touchableRegion); p.println(" mSettingsObserver=" + mSettingsObserver); + p.println(" mNavigationBarController=" + mNavigationBarController.toDebugString()); } private final ImeTracing.ServiceDumper mDumper = new ImeTracing.ServiceDumper() { diff --git a/core/java/android/inputmethodservice/NavigationBarController.java b/core/java/android/inputmethodservice/NavigationBarController.java new file mode 100644 index 000000000000..7295b72c276b --- /dev/null +++ b/core/java/android/inputmethodservice/NavigationBarController.java @@ -0,0 +1,328 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.inputmethodservice; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.StatusBarManager; +import android.content.res.Resources; +import android.graphics.Insets; +import android.graphics.Rect; +import android.graphics.Region; +import android.inputmethodservice.navigationbar.NavigationBarFrame; +import android.inputmethodservice.navigationbar.NavigationBarView; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewTreeObserver; +import android.view.Window; +import android.view.WindowInsets; +import android.view.WindowManagerPolicyConstants; +import android.widget.FrameLayout; + +import java.util.Objects; + +/** + * This class hides details behind {@link InputMethodService#canImeRenderGesturalNavButtons()} from + * {@link InputMethodService}. + * + * <p>All the package-private methods are no-op when + * {@link InputMethodService#canImeRenderGesturalNavButtons()} returns {@code false}.</p> + */ +final class NavigationBarController { + + private interface Callback { + default void updateTouchableInsets(@NonNull InputMethodService.Insets originalInsets, + @NonNull ViewTreeObserver.InternalInsetsInfo dest) { + } + + default void onViewInitialized() { + } + + default void onWindowShown() { + } + + default void onDestroy() { + } + + default String toDebugString() { + return "No-op implementation"; + } + + Callback NOOP = new Callback() { + }; + } + + private final Callback mImpl; + + NavigationBarController(@NonNull InputMethodService inputMethodService) { + mImpl = InputMethodService.canImeRenderGesturalNavButtons() + ? new Impl(inputMethodService) : Callback.NOOP; + } + + void updateTouchableInsets(@NonNull InputMethodService.Insets originalInsets, + @NonNull ViewTreeObserver.InternalInsetsInfo dest) { + mImpl.updateTouchableInsets(originalInsets, dest); + } + + void onViewInitialized() { + mImpl.onViewInitialized(); + } + + void onWindowShown() { + mImpl.onWindowShown(); + } + + void onDestroy() { + mImpl.onDestroy(); + } + + String toDebugString() { + return mImpl.toDebugString(); + } + + private static final class Impl implements Callback { + @NonNull + private final InputMethodService mService; + + private boolean mDestroyed = false; + + private boolean mRenderGesturalNavButtons; + + @Nullable + private NavigationBarFrame mNavigationBarFrame; + @Nullable + Insets mLastInsets; + + Impl(@NonNull InputMethodService inputMethodService) { + mService = inputMethodService; + } + + @Nullable + private Insets getSystemInsets() { + if (mService.mWindow == null) { + return null; + } + final View decorView = mService.mWindow.getWindow().getDecorView(); + if (decorView == null) { + return null; + } + final WindowInsets windowInsets = decorView.getRootWindowInsets(); + if (windowInsets == null) { + return null; + } + final Insets stableBarInsets = + windowInsets.getInsetsIgnoringVisibility(WindowInsets.Type.systemBars()); + return Insets.min(windowInsets.getInsets(WindowInsets.Type.systemBars() + | WindowInsets.Type.displayCutout()), stableBarInsets); + } + + private void installNavigationBarFrameIfNecessary() { + if (!mRenderGesturalNavButtons) { + return; + } + final View rawDecorView = mService.mWindow.getWindow().getDecorView(); + if (!(rawDecorView instanceof ViewGroup)) { + return; + } + final ViewGroup decorView = (ViewGroup) rawDecorView; + mNavigationBarFrame = decorView.findViewByPredicate( + NavigationBarFrame.class::isInstance); + final Insets systemInsets = getSystemInsets(); + if (mNavigationBarFrame == null) { + mNavigationBarFrame = new NavigationBarFrame(mService); + LayoutInflater.from(mService).inflate( + com.android.internal.R.layout.input_method_navigation_bar, + mNavigationBarFrame); + if (systemInsets != null) { + decorView.addView(mNavigationBarFrame, new FrameLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + systemInsets.bottom, Gravity.BOTTOM)); + mLastInsets = systemInsets; + } else { + decorView.addView(mNavigationBarFrame); + } + final NavigationBarView navigationBarView = mNavigationBarFrame.findViewByPredicate( + NavigationBarView.class::isInstance); + if (navigationBarView != null) { + // TODO(b/213337792): Support InputMethodService#setBackDisposition(). + // TODO(b/213337792): Set NAVIGATION_HINT_IME_SHOWN only when necessary. + final int hints = StatusBarManager.NAVIGATION_HINT_BACK_ALT + | StatusBarManager.NAVIGATION_HINT_IME_SHOWN; + navigationBarView.setNavigationIconHints(hints); + } + } else { + mNavigationBarFrame.setLayoutParams(new FrameLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, systemInsets.bottom, Gravity.BOTTOM)); + mLastInsets = systemInsets; + } + + mNavigationBarFrame.setBackground(null); + } + + @Override + public void updateTouchableInsets(@NonNull InputMethodService.Insets originalInsets, + @NonNull ViewTreeObserver.InternalInsetsInfo dest) { + if (!mRenderGesturalNavButtons || mNavigationBarFrame == null + || mService.isExtractViewShown()) { + return; + } + + final Insets systemInsets = getSystemInsets(); + if (systemInsets != null) { + final Window window = mService.mWindow.getWindow(); + final View decor = window.getDecorView(); + Region touchableRegion = null; + final View inputFrame = mService.mInputFrame; + switch (originalInsets.touchableInsets) { + case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME: + if (inputFrame.getVisibility() == View.VISIBLE) { + touchableRegion = new Region(inputFrame.getLeft(), + inputFrame.getTop(), inputFrame.getRight(), + inputFrame.getBottom()); + } + break; + case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT: + if (inputFrame.getVisibility() == View.VISIBLE) { + touchableRegion = new Region(inputFrame.getLeft(), + originalInsets.contentTopInsets, inputFrame.getRight(), + inputFrame.getBottom()); + } + break; + case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_VISIBLE: + if (inputFrame.getVisibility() == View.VISIBLE) { + touchableRegion = new Region(inputFrame.getLeft(), + originalInsets.visibleTopInsets, inputFrame.getRight(), + inputFrame.getBottom()); + } + break; + case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION: + touchableRegion = new Region(); + touchableRegion.set(originalInsets.touchableRegion); + break; + } + final Rect navBarRect = new Rect(decor.getLeft(), + decor.getBottom() - systemInsets.bottom, + decor.getRight(), decor.getBottom()); + if (touchableRegion == null) { + touchableRegion = new Region(navBarRect); + } else { + touchableRegion.union(navBarRect); + } + + dest.touchableRegion.set(touchableRegion); + dest.setTouchableInsets( + ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION); + + // TODO(b/205803355): See if we can use View#OnLayoutChangeListener(). + // TODO(b/205803355): See if we can replace DecorView#mNavigationColorViewState.view + boolean zOrderChanged = false; + if (decor instanceof ViewGroup) { + ViewGroup decorGroup = (ViewGroup) decor; + final View navbarBackgroundView = window.getNavigationBarBackgroundView(); + zOrderChanged = navbarBackgroundView != null + && decorGroup.indexOfChild(navbarBackgroundView) + > decorGroup.indexOfChild(mNavigationBarFrame); + } + final boolean insetChanged = !Objects.equals(systemInsets, mLastInsets); + if (zOrderChanged || insetChanged) { + final NavigationBarFrame that = mNavigationBarFrame; + that.post(() -> { + if (!that.isAttachedToWindow()) { + return; + } + final Insets currentSystemInsets = getSystemInsets(); + if (!Objects.equals(currentSystemInsets, mLastInsets)) { + that.setLayoutParams( + new FrameLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + currentSystemInsets.bottom, Gravity.BOTTOM)); + mLastInsets = currentSystemInsets; + } + if (decor instanceof ViewGroup) { + ViewGroup decorGroup = (ViewGroup) decor; + final View navbarBackgroundView = + window.getNavigationBarBackgroundView(); + if (navbarBackgroundView != null + && decorGroup.indexOfChild(navbarBackgroundView) + > decorGroup.indexOfChild(that)) { + decorGroup.bringChildToFront(that); + } + } + }); + } + } + } + + private boolean isGesturalNavigationEnabled() { + final Resources resources = mService.getResources(); + if (resources == null) { + return false; + } + return resources.getInteger(com.android.internal.R.integer.config_navBarInteractionMode) + == WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL; + } + + @Override + public void onViewInitialized() { + if (mDestroyed) { + return; + } + mRenderGesturalNavButtons = isGesturalNavigationEnabled(); + installNavigationBarFrameIfNecessary(); + } + + @Override + public void onDestroy() { + mDestroyed = true; + } + + @Override + public void onWindowShown() { + if (mDestroyed || !mRenderGesturalNavButtons || mNavigationBarFrame == null) { + return; + } + final Insets systemInsets = getSystemInsets(); + if (systemInsets != null) { + if (!Objects.equals(systemInsets, mLastInsets)) { + mNavigationBarFrame.setLayoutParams(new NavigationBarFrame.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + systemInsets.bottom, Gravity.BOTTOM)); + mLastInsets = systemInsets; + } + final Window window = mService.mWindow.getWindow(); + View rawDecorView = window.getDecorView(); + if (rawDecorView instanceof ViewGroup) { + final ViewGroup decor = (ViewGroup) rawDecorView; + final View navbarBackgroundView = window.getNavigationBarBackgroundView(); + if (navbarBackgroundView != null + && decor.indexOfChild(navbarBackgroundView) + > decor.indexOfChild(mNavigationBarFrame)) { + decor.bringChildToFront(mNavigationBarFrame); + } + } + mNavigationBarFrame.setVisibility(View.VISIBLE); + } + } + + @Override + public String toDebugString() { + return "{mRenderGesturalNavButtons=" + mRenderGesturalNavButtons + "}"; + } + } +} diff --git a/core/java/android/inputmethodservice/navigationbar/ButtonDispatcher.java b/core/java/android/inputmethodservice/navigationbar/ButtonDispatcher.java new file mode 100644 index 000000000000..3f26fa461097 --- /dev/null +++ b/core/java/android/inputmethodservice/navigationbar/ButtonDispatcher.java @@ -0,0 +1,302 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.inputmethodservice.navigationbar; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; +import android.view.View; +import android.view.View.AccessibilityDelegate; +import android.view.animation.Interpolator; +import android.view.animation.LinearInterpolator; + +import java.util.ArrayList; + +/** + * Dispatches common view calls to multiple views. This is used to handle + * multiples of the same nav bar icon appearing. + */ +final class ButtonDispatcher { + private static final int FADE_DURATION_IN = 150; + private static final int FADE_DURATION_OUT = 250; + public static final Interpolator LINEAR = new LinearInterpolator(); + + private final ArrayList<View> mViews = new ArrayList<>(); + + private final int mId; + + private View.OnClickListener mClickListener; + private View.OnTouchListener mTouchListener; + private View.OnLongClickListener mLongClickListener; + private View.OnHoverListener mOnHoverListener; + private Boolean mLongClickable; + private float mAlpha = 1.0f; + private Float mDarkIntensity; + private int mVisibility = View.VISIBLE; + private Boolean mDelayTouchFeedback; + private KeyButtonDrawable mImageDrawable; + private View mCurrentView; + private ValueAnimator mFadeAnimator; + private AccessibilityDelegate mAccessibilityDelegate; + + private final ValueAnimator.AnimatorUpdateListener mAlphaListener = animation -> + setAlpha( + (float) animation.getAnimatedValue(), + false /* animate */, + false /* cancelAnimator */); + + private final AnimatorListenerAdapter mFadeListener = new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mFadeAnimator = null; + setVisibility(getAlpha() == 1 ? View.VISIBLE : View.INVISIBLE); + } + }; + + public ButtonDispatcher(int id) { + mId = id; + } + + public void clear() { + mViews.clear(); + } + + public void addView(View view) { + mViews.add(view); + view.setOnClickListener(mClickListener); + view.setOnTouchListener(mTouchListener); + view.setOnLongClickListener(mLongClickListener); + view.setOnHoverListener(mOnHoverListener); + if (mLongClickable != null) { + view.setLongClickable(mLongClickable); + } + view.setAlpha(mAlpha); + view.setVisibility(mVisibility); + if (mAccessibilityDelegate != null) { + view.setAccessibilityDelegate(mAccessibilityDelegate); + } + if (view instanceof ButtonInterface) { + final ButtonInterface button = (ButtonInterface) view; + if (mDarkIntensity != null) { + button.setDarkIntensity(mDarkIntensity); + } + if (mImageDrawable != null) { + button.setImageDrawable(mImageDrawable); + } + if (mDelayTouchFeedback != null) { + button.setDelayTouchFeedback(mDelayTouchFeedback); + } + } + } + + public int getId() { + return mId; + } + + public int getVisibility() { + return mVisibility; + } + + public boolean isVisible() { + return getVisibility() == View.VISIBLE; + } + + public float getAlpha() { + return mAlpha; + } + + public KeyButtonDrawable getImageDrawable() { + return mImageDrawable; + } + + public void setImageDrawable(KeyButtonDrawable drawable) { + mImageDrawable = drawable; + final int N = mViews.size(); + for (int i = 0; i < N; i++) { + if (mViews.get(i) instanceof ButtonInterface) { + ((ButtonInterface) mViews.get(i)).setImageDrawable(mImageDrawable); + } + } + if (mImageDrawable != null) { + mImageDrawable.setCallback(mCurrentView); + } + } + + public void setVisibility(int visibility) { + if (mVisibility == visibility) return; + if (mFadeAnimator != null) { + mFadeAnimator.cancel(); + } + + mVisibility = visibility; + final int N = mViews.size(); + for (int i = 0; i < N; i++) { + mViews.get(i).setVisibility(mVisibility); + } + } + + public void setAlpha(float alpha) { + setAlpha(alpha, false /* animate */); + } + + public void setAlpha(float alpha, boolean animate) { + setAlpha(alpha, animate, true /* cancelAnimator */); + } + + public void setAlpha(float alpha, boolean animate, long duration) { + setAlpha(alpha, animate, duration, true /* cancelAnimator */); + } + + public void setAlpha(float alpha, boolean animate, boolean cancelAnimator) { + setAlpha( + alpha, + animate, + (getAlpha() < alpha) ? FADE_DURATION_IN : FADE_DURATION_OUT, + cancelAnimator); + } + + public void setAlpha(float alpha, boolean animate, long duration, boolean cancelAnimator) { + if (mFadeAnimator != null && (cancelAnimator || animate)) { + mFadeAnimator.cancel(); + } + if (animate) { + setVisibility(View.VISIBLE); + mFadeAnimator = ValueAnimator.ofFloat(getAlpha(), alpha); + mFadeAnimator.setDuration(duration); + mFadeAnimator.setInterpolator(LINEAR); + mFadeAnimator.addListener(mFadeListener); + mFadeAnimator.addUpdateListener(mAlphaListener); + mFadeAnimator.start(); + } else { + // Discretize the alpha updates to prevent too frequent updates when there is a long + // alpha animation + int prevAlpha = (int) (getAlpha() * 255); + int nextAlpha = (int) (alpha * 255); + if (prevAlpha != nextAlpha) { + mAlpha = nextAlpha / 255f; + final int N = mViews.size(); + for (int i = 0; i < N; i++) { + mViews.get(i).setAlpha(mAlpha); + } + } + } + } + + public void setDarkIntensity(float darkIntensity) { + mDarkIntensity = darkIntensity; + final int N = mViews.size(); + for (int i = 0; i < N; i++) { + if (mViews.get(i) instanceof ButtonInterface) { + ((ButtonInterface) mViews.get(i)).setDarkIntensity(darkIntensity); + } + } + } + + public void setDelayTouchFeedback(boolean delay) { + mDelayTouchFeedback = delay; + final int N = mViews.size(); + for (int i = 0; i < N; i++) { + if (mViews.get(i) instanceof ButtonInterface) { + ((ButtonInterface) mViews.get(i)).setDelayTouchFeedback(delay); + } + } + } + + public void setOnClickListener(View.OnClickListener clickListener) { + mClickListener = clickListener; + final int N = mViews.size(); + for (int i = 0; i < N; i++) { + mViews.get(i).setOnClickListener(mClickListener); + } + } + + public void setOnTouchListener(View.OnTouchListener touchListener) { + mTouchListener = touchListener; + final int N = mViews.size(); + for (int i = 0; i < N; i++) { + mViews.get(i).setOnTouchListener(mTouchListener); + } + } + + public void setLongClickable(boolean isLongClickable) { + mLongClickable = isLongClickable; + final int N = mViews.size(); + for (int i = 0; i < N; i++) { + mViews.get(i).setLongClickable(mLongClickable); + } + } + + public void setOnLongClickListener(View.OnLongClickListener longClickListener) { + mLongClickListener = longClickListener; + final int N = mViews.size(); + for (int i = 0; i < N; i++) { + mViews.get(i).setOnLongClickListener(mLongClickListener); + } + } + + public void setOnHoverListener(View.OnHoverListener hoverListener) { + mOnHoverListener = hoverListener; + final int N = mViews.size(); + for (int i = 0; i < N; i++) { + mViews.get(i).setOnHoverListener(mOnHoverListener); + } + } + + public void setAccessibilityDelegate(AccessibilityDelegate delegate) { + mAccessibilityDelegate = delegate; + final int N = mViews.size(); + for (int i = 0; i < N; i++) { + mViews.get(i).setAccessibilityDelegate(delegate); + } + } + + public void setTranslation(int x, int y, int z) { + final int N = mViews.size(); + for (int i = 0; i < N; i++) { + final View view = mViews.get(i); + view.setTranslationX(x); + view.setTranslationY(y); + view.setTranslationZ(z); + } + } + + public ArrayList<View> getViews() { + return mViews; + } + + public View getCurrentView() { + return mCurrentView; + } + + public void setCurrentView(View currentView) { + mCurrentView = currentView.findViewById(mId); + if (mImageDrawable != null) { + mImageDrawable.setCallback(mCurrentView); + } + if (mCurrentView != null) { + mCurrentView.setTranslationX(0); + mCurrentView.setTranslationY(0); + mCurrentView.setTranslationZ(0); + } + } + + /** + * Executes when button is detached from window. + */ + public void onDestroy() { + } +} diff --git a/core/java/android/inputmethodservice/navigationbar/ButtonInterface.java b/core/java/android/inputmethodservice/navigationbar/ButtonInterface.java new file mode 100644 index 000000000000..1c9c86d2a7e5 --- /dev/null +++ b/core/java/android/inputmethodservice/navigationbar/ButtonInterface.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.inputmethodservice.navigationbar; + +import android.annotation.Nullable; +import android.graphics.drawable.Drawable; + +interface ButtonInterface { + + void setImageDrawable(@Nullable Drawable drawable); + + void setDarkIntensity(float intensity); + + void setDelayTouchFeedback(boolean shouldDelay); +} diff --git a/core/java/android/inputmethodservice/navigationbar/DeadZone.java b/core/java/android/inputmethodservice/navigationbar/DeadZone.java new file mode 100644 index 000000000000..cd857369bc5a --- /dev/null +++ b/core/java/android/inputmethodservice/navigationbar/DeadZone.java @@ -0,0 +1,203 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.inputmethodservice.navigationbar; + +import static android.inputmethodservice.navigationbar.NavigationBarConstants.NAVIGATION_BAR_DEADZONE_DECAY; +import static android.inputmethodservice.navigationbar.NavigationBarConstants.NAVIGATION_BAR_DEADZONE_HOLD; +import static android.inputmethodservice.navigationbar.NavigationBarConstants.NAVIGATION_BAR_DEADZONE_SIZE; +import static android.inputmethodservice.navigationbar.NavigationBarConstants.NAVIGATION_BAR_DEADZONE_SIZE_MAX; +import static android.inputmethodservice.navigationbar.NavigationBarUtils.dpToPx; + +import android.animation.ObjectAnimator; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.graphics.Canvas; +import android.os.SystemClock; +import android.util.Log; +import android.view.MotionEvent; +import android.view.Surface; + +/** + * The "dead zone" consumes unintentional taps along the top edge of the navigation bar. + * When users are typing quickly on an IME they may attempt to hit the space bar, overshoot, and + * accidentally hit the home button. The DeadZone expands temporarily after each tap in the UI + * outside the navigation bar (since this is when accidental taps are more likely), then contracts + * back over time (since a later tap might be intended for the top of the bar). + */ +final class DeadZone { + public static final String TAG = "DeadZone"; + + public static final boolean DEBUG = false; + public static final int HORIZONTAL = 0; // Consume taps along the top edge. + public static final int VERTICAL = 1; // Consume taps along the left edge. + + private static final boolean CHATTY = true; // print to logcat when we eat a click + private final NavigationBarView mNavigationBarView; + + private boolean mShouldFlash; + private float mFlashFrac = 0f; + + private int mSizeMax; + private int mSizeMin; + // Upon activity elsewhere in the UI, the dead zone will hold steady for + // mHold ms, then move back over the course of mDecay ms + private int mHold, mDecay; + private boolean mVertical; + private long mLastPokeTime; + private int mDisplayRotation; + + private final Runnable mDebugFlash = new Runnable() { + @Override + public void run() { + ObjectAnimator.ofFloat(DeadZone.this, "flash", 1f, 0f).setDuration(150).start(); + } + }; + + public DeadZone(NavigationBarView view) { + mNavigationBarView = view; + onConfigurationChanged(Surface.ROTATION_0); + } + + static float lerp(float a, float b, float f) { + return (b - a) * f + a; + } + + private float getSize(long now) { + if (mSizeMax == 0) + return 0; + long dt = (now - mLastPokeTime); + if (dt > mHold + mDecay) + return mSizeMin; + if (dt < mHold) + return mSizeMax; + return (int) lerp(mSizeMax, mSizeMin, (float) (dt - mHold) / mDecay); + } + + public void setFlashOnTouchCapture(boolean dbg) { + mShouldFlash = dbg; + mFlashFrac = 0f; + mNavigationBarView.postInvalidate(); + } + + public void onConfigurationChanged(int rotation) { + mDisplayRotation = rotation; + + final Resources res = mNavigationBarView.getResources(); + mHold = NAVIGATION_BAR_DEADZONE_HOLD; + mDecay = NAVIGATION_BAR_DEADZONE_DECAY; + + mSizeMin = dpToPx(NAVIGATION_BAR_DEADZONE_SIZE, res); + mSizeMax = dpToPx(NAVIGATION_BAR_DEADZONE_SIZE_MAX, res); + mVertical = (res.getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE); + + if (DEBUG) { + Log.v(TAG, this + " size=[" + mSizeMin + "-" + mSizeMax + "] hold=" + mHold + + (mVertical ? " vertical" : " horizontal")); + } + setFlashOnTouchCapture(false); // hard-coded from "bool/config_dead_zone_flash" + } + + // I made you a touch event... + public boolean onTouchEvent(MotionEvent event) { + if (DEBUG) { + Log.v(TAG, this + " onTouch: " + MotionEvent.actionToString(event.getAction())); + } + + // Don't consume events for high precision pointing devices. For this purpose a stylus is + // considered low precision (like a finger), so its events may be consumed. + if (event.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE) { + return false; + } + + final int action = event.getAction(); + if (action == MotionEvent.ACTION_OUTSIDE) { + poke(event); + return true; + } else if (action == MotionEvent.ACTION_DOWN) { + if (DEBUG) { + Log.v(TAG, this + " ACTION_DOWN: " + event.getX() + "," + event.getY()); + } + //TODO(b/205803355): call mNavBarController.touchAutoDim(mDisplayId); here + int size = (int) getSize(event.getEventTime()); + // In the vertical orientation consume taps along the left edge. + // In horizontal orientation consume taps along the top edge. + final boolean consumeEvent; + if (mVertical) { + if (mDisplayRotation == Surface.ROTATION_270) { + consumeEvent = event.getX() > mNavigationBarView.getWidth() - size; + } else { + consumeEvent = event.getX() < size; + } + } else { + consumeEvent = event.getY() < size; + } + if (consumeEvent) { + if (CHATTY) { + Log.v(TAG, "consuming errant click: (" + event.getX() + "," + + event.getY() + ")"); + } + if (mShouldFlash) { + mNavigationBarView.post(mDebugFlash); + mNavigationBarView.postInvalidate(); + } + return true; // ...but I eated it + } + } + return false; + } + + private void poke(MotionEvent event) { + mLastPokeTime = event.getEventTime(); + if (DEBUG) + Log.v(TAG, "poked! size=" + getSize(mLastPokeTime)); + if (mShouldFlash) mNavigationBarView.postInvalidate(); + } + + public void setFlash(float f) { + mFlashFrac = f; + mNavigationBarView.postInvalidate(); + } + + public float getFlash() { + return mFlashFrac; + } + + public void onDraw(Canvas can) { + if (!mShouldFlash || mFlashFrac <= 0f) { + return; + } + + final int size = (int) getSize(SystemClock.uptimeMillis()); + if (mVertical) { + if (mDisplayRotation == Surface.ROTATION_270) { + can.clipRect(can.getWidth() - size, 0, can.getWidth(), can.getHeight()); + } else { + can.clipRect(0, 0, size, can.getHeight()); + } + } else { + can.clipRect(0, 0, can.getWidth(), size); + } + + final float frac = DEBUG ? (mFlashFrac - 0.5f) + 0.5f : mFlashFrac; + can.drawARGB((int) (frac * 0xFF), 0xDD, 0xEE, 0xAA); + + if (DEBUG && size > mSizeMin) { + // Very aggressive redrawing here, for debugging only + mNavigationBarView.postInvalidateDelayed(100); + } + } +} diff --git a/core/java/android/inputmethodservice/navigationbar/KeyButtonDrawable.java b/core/java/android/inputmethodservice/navigationbar/KeyButtonDrawable.java new file mode 100644 index 000000000000..25a443de916b --- /dev/null +++ b/core/java/android/inputmethodservice/navigationbar/KeyButtonDrawable.java @@ -0,0 +1,483 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.inputmethodservice.navigationbar; + +import static android.inputmethodservice.navigationbar.NavigationBarConstants.NAV_KEY_BUTTON_SHADOW_COLOR; +import static android.inputmethodservice.navigationbar.NavigationBarConstants.NAV_KEY_BUTTON_SHADOW_OFFSET_X; +import static android.inputmethodservice.navigationbar.NavigationBarConstants.NAV_KEY_BUTTON_SHADOW_OFFSET_Y; +import static android.inputmethodservice.navigationbar.NavigationBarConstants.NAV_KEY_BUTTON_SHADOW_RADIUS; +import static android.inputmethodservice.navigationbar.NavigationBarUtils.dpToPx; + +import android.animation.ArgbEvaluator; +import android.annotation.ColorInt; +import android.annotation.DrawableRes; +import android.annotation.NonNull; +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.BlurMaskFilter; +import android.graphics.BlurMaskFilter.Blur; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.ColorFilter; +import android.graphics.Paint; +import android.graphics.PixelFormat; +import android.graphics.PorterDuff; +import android.graphics.PorterDuff.Mode; +import android.graphics.PorterDuffColorFilter; +import android.graphics.Rect; +import android.graphics.drawable.AnimatedVectorDrawable; +import android.graphics.drawable.Drawable; +import android.util.FloatProperty; +import android.view.View; + + +/** + * Drawable for {@link KeyButtonView}s that supports tinting between two colors, rotation and shows + * a shadow. AnimatedVectorDrawable will only support tinting from intensities but has no support + * for shadows nor rotations. + */ +final class KeyButtonDrawable extends Drawable { + + public static final FloatProperty<KeyButtonDrawable> KEY_DRAWABLE_ROTATE = + new FloatProperty<KeyButtonDrawable>("KeyButtonRotation") { + @Override + public void setValue(KeyButtonDrawable drawable, float degree) { + drawable.setRotation(degree); + } + + @Override + public Float get(KeyButtonDrawable drawable) { + return drawable.getRotation(); + } + }; + + public static final FloatProperty<KeyButtonDrawable> KEY_DRAWABLE_TRANSLATE_Y = + new FloatProperty<KeyButtonDrawable>("KeyButtonTranslateY") { + @Override + public void setValue(KeyButtonDrawable drawable, float y) { + drawable.setTranslationY(y); + } + + @Override + public Float get(KeyButtonDrawable drawable) { + return drawable.getTranslationY(); + } + }; + + private final Paint mIconPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); + private final Paint mShadowPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); + private final ShadowDrawableState mState; + private AnimatedVectorDrawable mAnimatedDrawable; + private final Callback mAnimatedDrawableCallback = new Callback() { + @Override + public void invalidateDrawable(@NonNull Drawable who) { + invalidateSelf(); + } + + @Override + public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when) { + scheduleSelf(what, when); + } + + @Override + public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) { + unscheduleSelf(what); + } + }; + + public KeyButtonDrawable(Drawable d, @ColorInt int lightColor, @ColorInt int darkColor, + boolean horizontalFlip, Color ovalBackgroundColor) { + this(d, new ShadowDrawableState(lightColor, darkColor, + d instanceof AnimatedVectorDrawable, horizontalFlip, ovalBackgroundColor)); + } + + private KeyButtonDrawable(Drawable d, ShadowDrawableState state) { + mState = state; + if (d != null) { + mState.mBaseHeight = d.getIntrinsicHeight(); + mState.mBaseWidth = d.getIntrinsicWidth(); + mState.mChangingConfigurations = d.getChangingConfigurations(); + mState.mChildState = d.getConstantState(); + } + if (canAnimate()) { + mAnimatedDrawable = (AnimatedVectorDrawable) mState.mChildState.newDrawable().mutate(); + mAnimatedDrawable.setCallback(mAnimatedDrawableCallback); + setDrawableBounds(mAnimatedDrawable); + } + } + + public void setDarkIntensity(float intensity) { + mState.mDarkIntensity = intensity; + final int color = (int) ArgbEvaluator.getInstance() + .evaluate(intensity, mState.mLightColor, mState.mDarkColor); + updateShadowAlpha(); + setColorFilter(new PorterDuffColorFilter(color, Mode.SRC_ATOP)); + } + + public void setRotation(float degrees) { + if (canAnimate()) { + // AnimatedVectorDrawables will not support rotation + return; + } + if (mState.mRotateDegrees != degrees) { + mState.mRotateDegrees = degrees; + invalidateSelf(); + } + } + + public void setTranslationX(float x) { + setTranslation(x, mState.mTranslationY); + } + + public void setTranslationY(float y) { + setTranslation(mState.mTranslationX, y); + } + + public void setTranslation(float x, float y) { + if (mState.mTranslationX != x || mState.mTranslationY != y) { + mState.mTranslationX = x; + mState.mTranslationY = y; + invalidateSelf(); + } + } + + public void setShadowProperties(int x, int y, int size, int color) { + if (canAnimate()) { + // AnimatedVectorDrawables will not support shadows + return; + } + if (mState.mShadowOffsetX != x || mState.mShadowOffsetY != y + || mState.mShadowSize != size || mState.mShadowColor != color) { + mState.mShadowOffsetX = x; + mState.mShadowOffsetY = y; + mState.mShadowSize = size; + mState.mShadowColor = color; + mShadowPaint.setColorFilter( + new PorterDuffColorFilter(mState.mShadowColor, Mode.SRC_ATOP)); + updateShadowAlpha(); + invalidateSelf(); + } + } + + @Override + public boolean setVisible(boolean visible, boolean restart) { + boolean changed = super.setVisible(visible, restart); + if (changed) { + // End any existing animations when the visibility changes + jumpToCurrentState(); + } + return changed; + } + + @Override + public void jumpToCurrentState() { + super.jumpToCurrentState(); + if (mAnimatedDrawable != null) { + mAnimatedDrawable.jumpToCurrentState(); + } + } + + @Override + public void setAlpha(int alpha) { + mState.mAlpha = alpha; + mIconPaint.setAlpha(alpha); + updateShadowAlpha(); + invalidateSelf(); + } + + @Override + public void setColorFilter(ColorFilter colorFilter) { + mIconPaint.setColorFilter(colorFilter); + if (mAnimatedDrawable != null) { + if (hasOvalBg()) { + mAnimatedDrawable.setColorFilter( + new PorterDuffColorFilter(mState.mLightColor, PorterDuff.Mode.SRC_IN)); + } else { + mAnimatedDrawable.setColorFilter(colorFilter); + } + } + invalidateSelf(); + } + + public float getDarkIntensity() { + return mState.mDarkIntensity; + } + + public float getRotation() { + return mState.mRotateDegrees; + } + + public float getTranslationX() { + return mState.mTranslationX; + } + + public float getTranslationY() { + return mState.mTranslationY; + } + + @Override + public ConstantState getConstantState() { + return mState; + } + + @Override + public int getOpacity() { + return PixelFormat.TRANSLUCENT; + } + + @Override + public int getIntrinsicHeight() { + return mState.mBaseHeight + (mState.mShadowSize + Math.abs(mState.mShadowOffsetY)) * 2; + } + + @Override + public int getIntrinsicWidth() { + return mState.mBaseWidth + (mState.mShadowSize + Math.abs(mState.mShadowOffsetX)) * 2; + } + + public boolean canAnimate() { + return mState.mSupportsAnimation; + } + + public void startAnimation() { + if (mAnimatedDrawable != null) { + mAnimatedDrawable.start(); + } + } + + public void resetAnimation() { + if (mAnimatedDrawable != null) { + mAnimatedDrawable.reset(); + } + } + + public void clearAnimationCallbacks() { + if (mAnimatedDrawable != null) { + mAnimatedDrawable.clearAnimationCallbacks(); + } + } + + @Override + public void draw(Canvas canvas) { + Rect bounds = getBounds(); + if (bounds.isEmpty()) { + return; + } + + if (mAnimatedDrawable != null) { + mAnimatedDrawable.draw(canvas); + } else { + // If no cache or previous cached bitmap is hardware/software acceleration does not + // match the current canvas on draw then regenerate + boolean hwBitmapChanged = mState.mIsHardwareBitmap != canvas.isHardwareAccelerated(); + if (hwBitmapChanged) { + mState.mIsHardwareBitmap = canvas.isHardwareAccelerated(); + } + if (mState.mLastDrawnIcon == null || hwBitmapChanged) { + regenerateBitmapIconCache(); + } + canvas.save(); + canvas.translate(mState.mTranslationX, mState.mTranslationY); + canvas.rotate(mState.mRotateDegrees, getIntrinsicWidth() / 2, getIntrinsicHeight() / 2); + + if (mState.mShadowSize > 0) { + if (mState.mLastDrawnShadow == null || hwBitmapChanged) { + regenerateBitmapShadowCache(); + } + + // Translate (with rotation offset) before drawing the shadow + final float radians = (float) (mState.mRotateDegrees * Math.PI / 180); + final float shadowOffsetX = (float) (Math.sin(radians) * mState.mShadowOffsetY + + Math.cos(radians) * mState.mShadowOffsetX) - mState.mTranslationX; + final float shadowOffsetY = (float) (Math.cos(radians) * mState.mShadowOffsetY + - Math.sin(radians) * mState.mShadowOffsetX) - mState.mTranslationY; + canvas.drawBitmap(mState.mLastDrawnShadow, shadowOffsetX, shadowOffsetY, + mShadowPaint); + } + canvas.drawBitmap(mState.mLastDrawnIcon, null, bounds, mIconPaint); + canvas.restore(); + } + } + + @Override + public boolean canApplyTheme() { + return mState.canApplyTheme(); + } + + @ColorInt int getDrawableBackgroundColor() { + return mState.mOvalBackgroundColor.toArgb(); + } + + boolean hasOvalBg() { + return mState.mOvalBackgroundColor != null; + } + + private void regenerateBitmapIconCache() { + final int width = getIntrinsicWidth(); + final int height = getIntrinsicHeight(); + Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + final Canvas canvas = new Canvas(bitmap); + + // Call mutate, so that the pixel allocation by the underlying vector drawable is cleared. + final Drawable d = mState.mChildState.newDrawable().mutate(); + setDrawableBounds(d); + canvas.save(); + if (mState.mHorizontalFlip) { + canvas.scale(-1f, 1f, width * 0.5f, height * 0.5f); + } + d.draw(canvas); + canvas.restore(); + + if (mState.mIsHardwareBitmap) { + bitmap = bitmap.copy(Bitmap.Config.HARDWARE, false); + } + mState.mLastDrawnIcon = bitmap; + } + + private void regenerateBitmapShadowCache() { + if (mState.mShadowSize == 0) { + // No shadow + mState.mLastDrawnIcon = null; + return; + } + + final int width = getIntrinsicWidth(); + final int height = getIntrinsicHeight(); + Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + + // Call mutate, so that the pixel allocation by the underlying vector drawable is cleared. + final Drawable d = mState.mChildState.newDrawable().mutate(); + setDrawableBounds(d); + canvas.save(); + if (mState.mHorizontalFlip) { + canvas.scale(-1f, 1f, width * 0.5f, height * 0.5f); + } + d.draw(canvas); + canvas.restore(); + + // Draws the shadow from original drawable + Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); + paint.setMaskFilter(new BlurMaskFilter(mState.mShadowSize, Blur.NORMAL)); + int[] offset = new int[2]; + final Bitmap shadow = bitmap.extractAlpha(paint, offset); + paint.setMaskFilter(null); + bitmap.eraseColor(Color.TRANSPARENT); + canvas.drawBitmap(shadow, offset[0], offset[1], paint); + + if (mState.mIsHardwareBitmap) { + bitmap = bitmap.copy(Bitmap.Config.HARDWARE, false); + } + mState.mLastDrawnShadow = bitmap; + } + + /** + * Set the alpha of the shadow. As dark intensity increases, drop the alpha of the shadow since + * dark color and shadow should not be visible at the same time. + */ + private void updateShadowAlpha() { + // Update the color from the original color's alpha as the max + int alpha = Color.alpha(mState.mShadowColor); + mShadowPaint.setAlpha( + Math.round(alpha * (mState.mAlpha / 255f) * (1 - mState.mDarkIntensity))); + } + + /** + * Prevent shadow clipping by offsetting the drawable bounds by the shadow and its offset + * @param d the drawable to set the bounds + */ + private void setDrawableBounds(Drawable d) { + final int offsetX = mState.mShadowSize + Math.abs(mState.mShadowOffsetX); + final int offsetY = mState.mShadowSize + Math.abs(mState.mShadowOffsetY); + d.setBounds(offsetX, offsetY, getIntrinsicWidth() - offsetX, + getIntrinsicHeight() - offsetY); + } + + private static class ShadowDrawableState extends ConstantState { + int mChangingConfigurations; + int mBaseWidth; + int mBaseHeight; + float mRotateDegrees; + float mTranslationX; + float mTranslationY; + int mShadowOffsetX; + int mShadowOffsetY; + int mShadowSize; + int mShadowColor; + float mDarkIntensity; + int mAlpha; + boolean mHorizontalFlip; + + boolean mIsHardwareBitmap; + Bitmap mLastDrawnIcon; + Bitmap mLastDrawnShadow; + ConstantState mChildState; + + final int mLightColor; + final int mDarkColor; + final boolean mSupportsAnimation; + final Color mOvalBackgroundColor; + + public ShadowDrawableState(@ColorInt int lightColor, @ColorInt int darkColor, + boolean animated, boolean horizontalFlip, Color ovalBackgroundColor) { + mLightColor = lightColor; + mDarkColor = darkColor; + mSupportsAnimation = animated; + mAlpha = 255; + mHorizontalFlip = horizontalFlip; + mOvalBackgroundColor = ovalBackgroundColor; + } + + @Override + public Drawable newDrawable() { + return new KeyButtonDrawable(null, this); + } + + @Override + public int getChangingConfigurations() { + return mChangingConfigurations; + } + + @Override + public boolean canApplyTheme() { + return true; + } + } + + /** + * Creates a KeyButtonDrawable with a shadow given its icon. For more information, see + * {@link #create(Context, int, boolean, boolean)}. + */ + public static KeyButtonDrawable create(Context context, @ColorInt int lightColor, + @ColorInt int darkColor, @DrawableRes int iconResId, boolean hasShadow, + Color ovalBackgroundColor) { + final Resources res = context.getResources(); + boolean isRtl = res.getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL; + Drawable d = context.getDrawable(iconResId); + final KeyButtonDrawable drawable = new KeyButtonDrawable(d, lightColor, darkColor, + isRtl && d.isAutoMirrored(), ovalBackgroundColor); + if (hasShadow) { + int offsetX = dpToPx(NAV_KEY_BUTTON_SHADOW_OFFSET_X, res); + int offsetY = dpToPx(NAV_KEY_BUTTON_SHADOW_OFFSET_Y, res); + int radius = dpToPx(NAV_KEY_BUTTON_SHADOW_RADIUS, res); + int color = NAV_KEY_BUTTON_SHADOW_COLOR; + drawable.setShadowProperties(offsetX, offsetY, radius, color); + } + return drawable; + } +} diff --git a/core/java/android/inputmethodservice/navigationbar/KeyButtonRipple.java b/core/java/android/inputmethodservice/navigationbar/KeyButtonRipple.java new file mode 100644 index 000000000000..38a63b661ac0 --- /dev/null +++ b/core/java/android/inputmethodservice/navigationbar/KeyButtonRipple.java @@ -0,0 +1,525 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.inputmethodservice.navigationbar; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ObjectAnimator; +import android.annotation.DimenRes; +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.CanvasProperty; +import android.graphics.ColorFilter; +import android.graphics.Paint; +import android.graphics.PixelFormat; +import android.graphics.RecordingCanvas; +import android.graphics.drawable.Drawable; +import android.os.Handler; +import android.os.Trace; +import android.view.RenderNodeAnimator; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.animation.Interpolator; +import android.view.animation.PathInterpolator; + +import java.util.ArrayList; +import java.util.HashSet; + +final class KeyButtonRipple extends Drawable { + + private static final float GLOW_MAX_SCALE_FACTOR = 1.35f; + private static final float GLOW_MAX_ALPHA = 0.2f; + private static final float GLOW_MAX_ALPHA_DARK = 0.1f; + private static final int ANIMATION_DURATION_SCALE = 350; + private static final int ANIMATION_DURATION_FADE = 450; + private static final Interpolator ALPHA_OUT_INTERPOLATOR = + new PathInterpolator(0f, 0f, 0.8f, 1f); + + @DimenRes + private final int mMaxWidthResource; + + private Paint mRipplePaint; + private CanvasProperty<Float> mLeftProp; + private CanvasProperty<Float> mTopProp; + private CanvasProperty<Float> mRightProp; + private CanvasProperty<Float> mBottomProp; + private CanvasProperty<Float> mRxProp; + private CanvasProperty<Float> mRyProp; + private CanvasProperty<Paint> mPaintProp; + private float mGlowAlpha = 0f; + private float mGlowScale = 1f; + private boolean mPressed; + private boolean mVisible; + private boolean mDrawingHardwareGlow; + private int mMaxWidth; + private boolean mLastDark; + private boolean mDark; + private boolean mDelayTouchFeedback; + + private final Interpolator mInterpolator = new LogInterpolator(); + private boolean mSupportHardware; + private final View mTargetView; + private final Handler mHandler = new Handler(); + + private final HashSet<Animator> mRunningAnimations = new HashSet<>(); + private final ArrayList<Animator> mTmpArray = new ArrayList<>(); + + private final TraceAnimatorListener mExitHwTraceAnimator = + new TraceAnimatorListener("exitHardware"); + private final TraceAnimatorListener mEnterHwTraceAnimator = + new TraceAnimatorListener("enterHardware"); + + public enum Type { + OVAL, + ROUNDED_RECT + } + + private Type mType = Type.ROUNDED_RECT; + + public KeyButtonRipple(Context ctx, View targetView, @DimenRes int maxWidthResource) { + mMaxWidthResource = maxWidthResource; + mMaxWidth = ctx.getResources().getDimensionPixelSize(maxWidthResource); + mTargetView = targetView; + } + + public void updateResources() { + mMaxWidth = mTargetView.getContext().getResources() + .getDimensionPixelSize(mMaxWidthResource); + invalidateSelf(); + } + + public void setDarkIntensity(float darkIntensity) { + mDark = darkIntensity >= 0.5f; + } + + public void setDelayTouchFeedback(boolean delay) { + mDelayTouchFeedback = delay; + } + + public void setType(Type type) { + mType = type; + } + + private Paint getRipplePaint() { + if (mRipplePaint == null) { + mRipplePaint = new Paint(); + mRipplePaint.setAntiAlias(true); + mRipplePaint.setColor(mLastDark ? 0xff000000 : 0xffffffff); + } + return mRipplePaint; + } + + private void drawSoftware(Canvas canvas) { + if (mGlowAlpha > 0f) { + final Paint p = getRipplePaint(); + p.setAlpha((int)(mGlowAlpha * 255f)); + + final float w = getBounds().width(); + final float h = getBounds().height(); + final boolean horizontal = w > h; + final float diameter = getRippleSize() * mGlowScale; + final float radius = diameter * .5f; + final float cx = w * .5f; + final float cy = h * .5f; + final float rx = horizontal ? radius : cx; + final float ry = horizontal ? cy : radius; + final float corner = horizontal ? cy : cx; + + if (mType == Type.ROUNDED_RECT) { + canvas.drawRoundRect(cx - rx, cy - ry, cx + rx, cy + ry, corner, corner, p); + } else { + canvas.save(); + canvas.translate(cx, cy); + float r = Math.min(rx, ry); + canvas.drawOval(-r, -r, r, r, p); + canvas.restore(); + } + } + } + + @Override + public void draw(Canvas canvas) { + mSupportHardware = canvas.isHardwareAccelerated(); + if (mSupportHardware) { + drawHardware((RecordingCanvas) canvas); + } else { + drawSoftware(canvas); + } + } + + @Override + public void setAlpha(int alpha) { + // Not supported. + } + + @Override + public void setColorFilter(ColorFilter colorFilter) { + // Not supported. + } + + @Override + public int getOpacity() { + return PixelFormat.TRANSLUCENT; + } + + private boolean isHorizontal() { + return getBounds().width() > getBounds().height(); + } + + private void drawHardware(RecordingCanvas c) { + if (mDrawingHardwareGlow) { + if (mType == Type.ROUNDED_RECT) { + c.drawRoundRect(mLeftProp, mTopProp, mRightProp, mBottomProp, mRxProp, mRyProp, + mPaintProp); + } else { + CanvasProperty<Float> cx = CanvasProperty.createFloat(getBounds().width() / 2); + CanvasProperty<Float> cy = CanvasProperty.createFloat(getBounds().height() / 2); + int d = Math.min(getBounds().width(), getBounds().height()); + CanvasProperty<Float> r = CanvasProperty.createFloat(1.0f * d / 2); + c.drawCircle(cx, cy, r, mPaintProp); + } + } + } + + /** Gets the glow alpha, used by {@link android.animation.ObjectAnimator} via reflection. */ + public float getGlowAlpha() { + return mGlowAlpha; + } + + /** Sets the glow alpha, used by {@link android.animation.ObjectAnimator} via reflection. */ + public void setGlowAlpha(float x) { + mGlowAlpha = x; + invalidateSelf(); + } + + /** Gets the glow scale, used by {@link android.animation.ObjectAnimator} via reflection. */ + public float getGlowScale() { + return mGlowScale; + } + + /** Sets the glow scale, used by {@link android.animation.ObjectAnimator} via reflection. */ + public void setGlowScale(float x) { + mGlowScale = x; + invalidateSelf(); + } + + private float getMaxGlowAlpha() { + return mLastDark ? GLOW_MAX_ALPHA_DARK : GLOW_MAX_ALPHA; + } + + @Override + protected boolean onStateChange(int[] state) { + boolean pressed = false; + for (int i = 0; i < state.length; i++) { + if (state[i] == android.R.attr.state_pressed) { + pressed = true; + break; + } + } + if (pressed != mPressed) { + setPressed(pressed); + mPressed = pressed; + return true; + } else { + return false; + } + } + + @Override + public boolean setVisible(boolean visible, boolean restart) { + boolean changed = super.setVisible(visible, restart); + if (changed) { + // End any existing animations when the visibility changes + jumpToCurrentState(); + } + return changed; + } + + @Override + public void jumpToCurrentState() { + endAnimations("jumpToCurrentState", false /* cancel */); + } + + @Override + public boolean isStateful() { + return true; + } + + @Override + public boolean hasFocusStateSpecified() { + return true; + } + + public void setPressed(boolean pressed) { + if (mDark != mLastDark && pressed) { + mRipplePaint = null; + mLastDark = mDark; + } + if (mSupportHardware) { + setPressedHardware(pressed); + } else { + setPressedSoftware(pressed); + } + } + + /** + * Abort the ripple while it is delayed and before shown used only when setShouldDelayStartTouch + * is enabled. + */ + public void abortDelayedRipple() { + mHandler.removeCallbacksAndMessages(null); + } + + private void endAnimations(String reason, boolean cancel) { + Trace.beginSection("KeyButtonRipple.endAnim: reason=" + reason + " cancel=" + cancel); + Trace.endSection(); + mVisible = false; + mTmpArray.addAll(mRunningAnimations); + int size = mTmpArray.size(); + for (int i = 0; i < size; i++) { + Animator a = mTmpArray.get(i); + if (cancel) { + a.cancel(); + } else { + a.end(); + } + } + mTmpArray.clear(); + mRunningAnimations.clear(); + mHandler.removeCallbacksAndMessages(null); + } + + private void setPressedSoftware(boolean pressed) { + if (pressed) { + if (mDelayTouchFeedback) { + if (mRunningAnimations.isEmpty()) { + mHandler.removeCallbacksAndMessages(null); + mHandler.postDelayed(this::enterSoftware, ViewConfiguration.getTapTimeout()); + } else if (mVisible) { + enterSoftware(); + } + } else { + enterSoftware(); + } + } else { + exitSoftware(); + } + } + + private void enterSoftware() { + endAnimations("enterSoftware", true /* cancel */); + mVisible = true; + mGlowAlpha = getMaxGlowAlpha(); + ObjectAnimator scaleAnimator = ObjectAnimator.ofFloat(this, "glowScale", + 0f, GLOW_MAX_SCALE_FACTOR); + scaleAnimator.setInterpolator(mInterpolator); + scaleAnimator.setDuration(ANIMATION_DURATION_SCALE); + scaleAnimator.addListener(mAnimatorListener); + scaleAnimator.start(); + mRunningAnimations.add(scaleAnimator); + + // With the delay, it could eventually animate the enter animation with no pressed state, + // then immediately show the exit animation. If this is skipped there will be no ripple. + if (mDelayTouchFeedback && !mPressed) { + exitSoftware(); + } + } + + private void exitSoftware() { + ObjectAnimator alphaAnimator = ObjectAnimator.ofFloat(this, "glowAlpha", mGlowAlpha, 0f); + alphaAnimator.setInterpolator(ALPHA_OUT_INTERPOLATOR); + alphaAnimator.setDuration(ANIMATION_DURATION_FADE); + alphaAnimator.addListener(mAnimatorListener); + alphaAnimator.start(); + mRunningAnimations.add(alphaAnimator); + } + + private void setPressedHardware(boolean pressed) { + if (pressed) { + if (mDelayTouchFeedback) { + if (mRunningAnimations.isEmpty()) { + mHandler.removeCallbacksAndMessages(null); + mHandler.postDelayed(this::enterHardware, ViewConfiguration.getTapTimeout()); + } else if (mVisible) { + enterHardware(); + } + } else { + enterHardware(); + } + } else { + exitHardware(); + } + } + + /** + * Sets the left/top property for the round rect to {@code prop} depending on whether we are + * horizontal or vertical mode. + */ + private void setExtendStart(CanvasProperty<Float> prop) { + if (isHorizontal()) { + mLeftProp = prop; + } else { + mTopProp = prop; + } + } + + private CanvasProperty<Float> getExtendStart() { + return isHorizontal() ? mLeftProp : mTopProp; + } + + /** + * Sets the right/bottom property for the round rect to {@code prop} depending on whether we are + * horizontal or vertical mode. + */ + private void setExtendEnd(CanvasProperty<Float> prop) { + if (isHorizontal()) { + mRightProp = prop; + } else { + mBottomProp = prop; + } + } + + private CanvasProperty<Float> getExtendEnd() { + return isHorizontal() ? mRightProp : mBottomProp; + } + + private int getExtendSize() { + return isHorizontal() ? getBounds().width() : getBounds().height(); + } + + private int getRippleSize() { + int size = isHorizontal() ? getBounds().width() : getBounds().height(); + return Math.min(size, mMaxWidth); + } + + private void enterHardware() { + endAnimations("enterHardware", true /* cancel */); + mVisible = true; + mDrawingHardwareGlow = true; + setExtendStart(CanvasProperty.createFloat(getExtendSize() / 2)); + final RenderNodeAnimator startAnim = new RenderNodeAnimator(getExtendStart(), + getExtendSize()/2 - GLOW_MAX_SCALE_FACTOR * getRippleSize()/2); + startAnim.setDuration(ANIMATION_DURATION_SCALE); + startAnim.setInterpolator(mInterpolator); + startAnim.addListener(mAnimatorListener); + startAnim.setTarget(mTargetView); + + setExtendEnd(CanvasProperty.createFloat(getExtendSize() / 2)); + final RenderNodeAnimator endAnim = new RenderNodeAnimator(getExtendEnd(), + getExtendSize()/2 + GLOW_MAX_SCALE_FACTOR * getRippleSize()/2); + endAnim.setDuration(ANIMATION_DURATION_SCALE); + endAnim.setInterpolator(mInterpolator); + endAnim.addListener(mAnimatorListener); + endAnim.addListener(mEnterHwTraceAnimator); + endAnim.setTarget(mTargetView); + + if (isHorizontal()) { + mTopProp = CanvasProperty.createFloat(0f); + mBottomProp = CanvasProperty.createFloat(getBounds().height()); + mRxProp = CanvasProperty.createFloat(getBounds().height()/2); + mRyProp = CanvasProperty.createFloat(getBounds().height()/2); + } else { + mLeftProp = CanvasProperty.createFloat(0f); + mRightProp = CanvasProperty.createFloat(getBounds().width()); + mRxProp = CanvasProperty.createFloat(getBounds().width()/2); + mRyProp = CanvasProperty.createFloat(getBounds().width()/2); + } + + mGlowScale = GLOW_MAX_SCALE_FACTOR; + mGlowAlpha = getMaxGlowAlpha(); + mRipplePaint = getRipplePaint(); + mRipplePaint.setAlpha((int) (mGlowAlpha * 255)); + mPaintProp = CanvasProperty.createPaint(mRipplePaint); + + startAnim.start(); + endAnim.start(); + mRunningAnimations.add(startAnim); + mRunningAnimations.add(endAnim); + + invalidateSelf(); + + // With the delay, it could eventually animate the enter animation with no pressed state, + // then immediately show the exit animation. If this is skipped there will be no ripple. + if (mDelayTouchFeedback && !mPressed) { + exitHardware(); + } + } + + private void exitHardware() { + mPaintProp = CanvasProperty.createPaint(getRipplePaint()); + final RenderNodeAnimator opacityAnim = new RenderNodeAnimator(mPaintProp, + RenderNodeAnimator.PAINT_ALPHA, 0); + opacityAnim.setDuration(ANIMATION_DURATION_FADE); + opacityAnim.setInterpolator(ALPHA_OUT_INTERPOLATOR); + opacityAnim.addListener(mAnimatorListener); + opacityAnim.addListener(mExitHwTraceAnimator); + opacityAnim.setTarget(mTargetView); + + opacityAnim.start(); + mRunningAnimations.add(opacityAnim); + + invalidateSelf(); + } + + private final AnimatorListenerAdapter mAnimatorListener = + new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mRunningAnimations.remove(animation); + if (mRunningAnimations.isEmpty() && !mPressed) { + mVisible = false; + mDrawingHardwareGlow = false; + invalidateSelf(); + } + } + }; + + private static final class TraceAnimatorListener extends AnimatorListenerAdapter { + private final String mName; + TraceAnimatorListener(String name) { + mName = name; + } + + @Override + public void onAnimationStart(Animator animation) { + Trace.beginSection("KeyButtonRipple.start." + mName); + Trace.endSection(); + } + + @Override + public void onAnimationCancel(Animator animation) { + Trace.beginSection("KeyButtonRipple.cancel." + mName); + Trace.endSection(); + } + + @Override + public void onAnimationEnd(Animator animation) { + Trace.beginSection("KeyButtonRipple.end." + mName); + Trace.endSection(); + } + } + + /** + * Interpolator with a smooth log deceleration + */ + private static final class LogInterpolator implements Interpolator { + @Override + public float getInterpolation(float input) { + return 1 - (float) Math.pow(400, -input * 1.4); + } + } +} diff --git a/core/java/android/inputmethodservice/navigationbar/KeyButtonView.java b/core/java/android/inputmethodservice/navigationbar/KeyButtonView.java new file mode 100644 index 000000000000..74d30f8f8806 --- /dev/null +++ b/core/java/android/inputmethodservice/navigationbar/KeyButtonView.java @@ -0,0 +1,370 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.inputmethodservice.navigationbar; + +import static android.view.Display.INVALID_DISPLAY; +import static android.view.KeyEvent.KEYCODE_BACK; +import static android.view.KeyEvent.KEYCODE_UNKNOWN; +import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK; +import static android.view.accessibility.AccessibilityNodeInfo.ACTION_LONG_CLICK; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.drawable.Drawable; +import android.inputmethodservice.InputMethodService; +import android.media.AudioManager; +import android.os.Bundle; +import android.os.SystemClock; +import android.util.AttributeSet; +import android.view.HapticFeedbackConstants; +import android.view.InputDevice; +import android.view.KeyCharacterMap; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.SoundEffectConstants; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; +import android.view.inputmethod.InputConnection; +import android.widget.ImageView; + +import com.android.internal.annotations.VisibleForTesting; + +/** + * @hide + */ +public class KeyButtonView extends ImageView implements ButtonInterface { + private static final String TAG = KeyButtonView.class.getSimpleName(); + + private final boolean mPlaySounds; + private long mDownTime; + private boolean mTracking; + private int mCode; + private int mTouchDownX; + private int mTouchDownY; + private AudioManager mAudioManager; + private boolean mGestureAborted; + @VisibleForTesting boolean mLongClicked; + private OnClickListener mOnClickListener; + private final KeyButtonRipple mRipple; + private final Paint mOvalBgPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); + private float mDarkIntensity; + private boolean mHasOvalBg = false; + + private final Runnable mCheckLongPress = new Runnable() { + public void run() { + if (isPressed()) { + // Log.d("KeyButtonView", "longpressed: " + this); + if (isLongClickable()) { + // Just an old-fashioned ImageView + performLongClick(); + mLongClicked = true; + } else { + if (mCode != KEYCODE_UNKNOWN) { + sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.FLAG_LONG_PRESS); + sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED); + } + mLongClicked = true; + } + } + } + }; + + public KeyButtonView(Context context, AttributeSet attrs) { + super(context, attrs); + + // TODO(b/205803355): Figure out better place to set this. + switch (getId()) { + case com.android.internal.R.id.input_method_nav_back: + mCode = KEYCODE_BACK; + break; + default: + mCode = KEYCODE_UNKNOWN; + break; + } + + mPlaySounds = true; + + setClickable(true); + mAudioManager = context.getSystemService(AudioManager.class); + + mRipple = new KeyButtonRipple(context, this, + com.android.internal.R.dimen.input_method_nav_key_button_ripple_max_width); + setBackground(mRipple); + setWillNotDraw(false); + forceHasOverlappingRendering(false); + } + + @Override + public boolean isClickable() { + return mCode != KEYCODE_UNKNOWN || super.isClickable(); + } + + public void setCode(int code) { + mCode = code; + } + + @Override + public void setOnClickListener(OnClickListener onClickListener) { + super.setOnClickListener(onClickListener); + mOnClickListener = onClickListener; + } + + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + if (mCode != KEYCODE_UNKNOWN) { + info.addAction(new AccessibilityNodeInfo.AccessibilityAction(ACTION_CLICK, null)); + if (isLongClickable()) { + info.addAction( + new AccessibilityNodeInfo.AccessibilityAction(ACTION_LONG_CLICK, null)); + } + } + } + + @Override + protected void onWindowVisibilityChanged(int visibility) { + super.onWindowVisibilityChanged(visibility); + if (visibility != View.VISIBLE) { + jumpDrawablesToCurrentState(); + } + } + + @Override + public boolean performAccessibilityActionInternal(int action, Bundle arguments) { + if (action == ACTION_CLICK && mCode != KEYCODE_UNKNOWN) { + sendEvent(KeyEvent.ACTION_DOWN, 0, SystemClock.uptimeMillis()); + sendEvent(KeyEvent.ACTION_UP, mTracking ? KeyEvent.FLAG_TRACKING : 0); + mTracking = false; + sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); + playSoundEffect(SoundEffectConstants.CLICK); + return true; + } else if (action == ACTION_LONG_CLICK && mCode != KEYCODE_UNKNOWN) { + sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.FLAG_LONG_PRESS); + sendEvent(KeyEvent.ACTION_UP, mTracking ? KeyEvent.FLAG_TRACKING : 0); + mTracking = false; + sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED); + return true; + } + return super.performAccessibilityActionInternal(action, arguments); + } + + @Override + public boolean onTouchEvent(MotionEvent ev) { + final boolean showSwipeUI = false; // mOverviewProxyService.shouldShowSwipeUpUI(); + final int action = ev.getAction(); + int x, y; + if (action == MotionEvent.ACTION_DOWN) { + mGestureAborted = false; + } + if (mGestureAborted) { + setPressed(false); + return false; + } + + switch (action) { + case MotionEvent.ACTION_DOWN: + mDownTime = SystemClock.uptimeMillis(); + mLongClicked = false; + setPressed(true); + + // Use raw X and Y to detect gestures in case a parent changes the x and y values + mTouchDownX = (int) ev.getRawX(); + mTouchDownY = (int) ev.getRawY(); + if (mCode != KEYCODE_UNKNOWN) { + sendEvent(KeyEvent.ACTION_DOWN, 0, mDownTime); + } else { + // Provide the same haptic feedback that the system offers for virtual keys. + performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY); + } + if (!showSwipeUI) { + playSoundEffect(SoundEffectConstants.CLICK); + } + removeCallbacks(mCheckLongPress); + postDelayed(mCheckLongPress, ViewConfiguration.getLongPressTimeout()); + break; + case MotionEvent.ACTION_MOVE: + x = (int)ev.getRawX(); + y = (int)ev.getRawY(); + + float slop = getQuickStepTouchSlopPx(getContext()); + if (Math.abs(x - mTouchDownX) > slop || Math.abs(y - mTouchDownY) > slop) { + // When quick step is enabled, prevent animating the ripple triggered by + // setPressed and decide to run it on touch up + setPressed(false); + removeCallbacks(mCheckLongPress); + } + break; + case MotionEvent.ACTION_CANCEL: + setPressed(false); + if (mCode != KEYCODE_UNKNOWN) { + sendEvent(KeyEvent.ACTION_UP, KeyEvent.FLAG_CANCELED); + } + removeCallbacks(mCheckLongPress); + break; + case MotionEvent.ACTION_UP: + final boolean doIt = isPressed() && !mLongClicked; + setPressed(false); + final boolean doHapticFeedback = (SystemClock.uptimeMillis() - mDownTime) > 150; + if (showSwipeUI) { + if (doIt) { + // Apply haptic feedback on touch up since there is none on touch down + performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY); + playSoundEffect(SoundEffectConstants.CLICK); + } + } else if (doHapticFeedback && !mLongClicked) { + // Always send a release ourselves because it doesn't seem to be sent elsewhere + // and it feels weird to sometimes get a release haptic and other times not. + performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY_RELEASE); + } + if (mCode != KEYCODE_UNKNOWN) { + if (doIt) { + sendEvent(KeyEvent.ACTION_UP, mTracking ? KeyEvent.FLAG_TRACKING : 0); + mTracking = false; + sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); + } else { + sendEvent(KeyEvent.ACTION_UP, KeyEvent.FLAG_CANCELED); + } + } else { + // no key code, just a regular ImageView + if (doIt && mOnClickListener != null) { + mOnClickListener.onClick(this); + sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); + } + } + removeCallbacks(mCheckLongPress); + break; + } + + return true; + } + + @Override + public void setImageDrawable(Drawable drawable) { + super.setImageDrawable(drawable); + + if (drawable == null) { + return; + } + KeyButtonDrawable keyButtonDrawable = (KeyButtonDrawable) drawable; + keyButtonDrawable.setDarkIntensity(mDarkIntensity); + mHasOvalBg = keyButtonDrawable.hasOvalBg(); + if (mHasOvalBg) { + mOvalBgPaint.setColor(keyButtonDrawable.getDrawableBackgroundColor()); + } + mRipple.setType(keyButtonDrawable.hasOvalBg() ? KeyButtonRipple.Type.OVAL + : KeyButtonRipple.Type.ROUNDED_RECT); + } + + public void playSoundEffect(int soundConstant) { + if (!mPlaySounds) return; + mAudioManager.playSoundEffect(soundConstant); + } + + public void sendEvent(int action, int flags) { + sendEvent(action, flags, SystemClock.uptimeMillis()); + } + + private void sendEvent(int action, int flags, long when) { + if (mCode == KeyEvent.KEYCODE_BACK && flags != KeyEvent.FLAG_LONG_PRESS) { + if (action == MotionEvent.ACTION_UP) { + // TODO(b/205803355): Implement notifyBackAction(); + } + } + + // TODO(b/205803355): Consolidate this logic to somewhere else. + if (mContext instanceof InputMethodService) { + final int repeatCount = (flags & KeyEvent.FLAG_LONG_PRESS) != 0 ? 1 : 0; + final KeyEvent ev = new KeyEvent(mDownTime, when, action, mCode, repeatCount, + 0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, + flags | KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_VIRTUAL_HARD_KEY, + InputDevice.SOURCE_KEYBOARD); + int displayId = INVALID_DISPLAY; + + // Make KeyEvent work on multi-display environment + if (getDisplay() != null) { + displayId = getDisplay().getDisplayId(); + } + if (displayId != INVALID_DISPLAY) { + ev.setDisplayId(displayId); + } + final InputMethodService ims = (InputMethodService) mContext; + final boolean handled; + switch (action) { + case KeyEvent.ACTION_DOWN: + handled = ims.onKeyDown(ev.getKeyCode(), ev); + mTracking = handled && ev.getRepeatCount() == 0 && + (ev.getFlags() & KeyEvent.FLAG_START_TRACKING) != 0; + break; + case KeyEvent.ACTION_UP: + handled = ims.onKeyUp(ev.getKeyCode(), ev); + break; + default: + handled = false; + break; + } + if (!handled) { + final InputConnection ic = ims.getCurrentInputConnection(); + if (ic != null) { + ic.sendKeyEvent(ev); + } + } + } + } + + @Override + public void setDarkIntensity(float darkIntensity) { + mDarkIntensity = darkIntensity; + + Drawable drawable = getDrawable(); + if (drawable != null) { + ((KeyButtonDrawable) drawable).setDarkIntensity(darkIntensity); + // Since we reuse the same drawable for multiple views, we need to invalidate the view + // manually. + invalidate(); + } + mRipple.setDarkIntensity(darkIntensity); + } + + @Override + public void setDelayTouchFeedback(boolean shouldDelay) { + mRipple.setDelayTouchFeedback(shouldDelay); + } + + @Override + public void draw(Canvas canvas) { + if (mHasOvalBg) { + int d = Math.min(getWidth(), getHeight()); + canvas.drawOval(0, 0, d, d, mOvalBgPaint); + } + super.draw(canvas); + } + + /** + * Ratio of quickstep touch slop (when system takes over the touch) to view touch slop + */ + public static final float QUICKSTEP_TOUCH_SLOP_RATIO = 3; + + /** + * Touch slop for quickstep gesture + */ + private static float getQuickStepTouchSlopPx(Context context) { + return QUICKSTEP_TOUCH_SLOP_RATIO * ViewConfiguration.get(context).getScaledTouchSlop(); + } +} diff --git a/core/java/android/inputmethodservice/navigationbar/NavigationBarConstants.java b/core/java/android/inputmethodservice/navigationbar/NavigationBarConstants.java new file mode 100644 index 000000000000..93c54395f972 --- /dev/null +++ b/core/java/android/inputmethodservice/navigationbar/NavigationBarConstants.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.inputmethodservice.navigationbar; + +import android.annotation.ColorInt; + +final class NavigationBarConstants { + private NavigationBarConstants() { + // Not intended to be instantiated. + } + + // Copied from "navbar_back_button_ime_offset" + // TODO(b/215443343): Handle this in the drawable then remove this constant. + static final float NAVBAR_BACK_BUTTON_IME_OFFSET = 2.0f; + + // Copied from "light_mode_icon_color_single_tone" at packages/SettingsLib/res/values/colors.xml + @ColorInt + static final int LIGHT_MODE_ICON_COLOR_SINGLE_TONE = 0xffffffff; + + // Copied from "dark_mode_icon_color_single_tone" at packages/SettingsLib/res/values/colors.xml + @ColorInt + static final int DARK_MODE_ICON_COLOR_SINGLE_TONE = 0x99000000; + + // Copied from "navigation_bar_deadzone_hold" + static final int NAVIGATION_BAR_DEADZONE_HOLD = 333; + + // Copied from "navigation_bar_deadzone_hold" + static final int NAVIGATION_BAR_DEADZONE_DECAY = 333; + + // Copied from "navigation_bar_deadzone_size" + static final float NAVIGATION_BAR_DEADZONE_SIZE = 12.0f; + + // Copied from "navigation_bar_deadzone_size_max" + static final float NAVIGATION_BAR_DEADZONE_SIZE_MAX = 32.0f; + + // Copied from "nav_key_button_shadow_offset_x" + static final float NAV_KEY_BUTTON_SHADOW_OFFSET_X = 0.0f; + + // Copied from "nav_key_button_shadow_offset_y" + static final float NAV_KEY_BUTTON_SHADOW_OFFSET_Y = 1.0f; + + // Copied from "nav_key_button_shadow_radius" + static final float NAV_KEY_BUTTON_SHADOW_RADIUS = 0.5f; + + // Copied from "nav_key_button_shadow_color" + @ColorInt + static final int NAV_KEY_BUTTON_SHADOW_COLOR = 0x30000000; +} diff --git a/core/java/android/inputmethodservice/navigationbar/NavigationBarFrame.java b/core/java/android/inputmethodservice/navigationbar/NavigationBarFrame.java new file mode 100644 index 000000000000..f01173e0fdae --- /dev/null +++ b/core/java/android/inputmethodservice/navigationbar/NavigationBarFrame.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.inputmethodservice.navigationbar; + +import static android.view.MotionEvent.ACTION_OUTSIDE; + +import android.annotation.AttrRes; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.widget.FrameLayout; + +/** + * @hide + */ +public final class NavigationBarFrame extends FrameLayout { + + private DeadZone mDeadZone = null; + + public NavigationBarFrame(@NonNull Context context) { + super(context); + } + + public NavigationBarFrame(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public NavigationBarFrame(@NonNull Context context, @Nullable AttributeSet attrs, + @AttrRes int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public void setDeadZone(@NonNull DeadZone deadZone) { + mDeadZone = deadZone; + } + + @Override + public boolean dispatchTouchEvent(MotionEvent event) { + if (event.getAction() == ACTION_OUTSIDE) { + if (mDeadZone != null) { + return mDeadZone.onTouchEvent(event); + } + } + return super.dispatchTouchEvent(event); + } +}
\ No newline at end of file diff --git a/core/java/android/inputmethodservice/navigationbar/NavigationBarInflaterView.java b/core/java/android/inputmethodservice/navigationbar/NavigationBarInflaterView.java new file mode 100644 index 000000000000..d488890b27d4 --- /dev/null +++ b/core/java/android/inputmethodservice/navigationbar/NavigationBarInflaterView.java @@ -0,0 +1,429 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.inputmethodservice.navigationbar; + +import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; + +import android.annotation.Nullable; +import android.content.Context; +import android.content.res.Configuration; +import android.inputmethodservice.navigationbar.ReverseLinearLayout.ReverseRelativeLayout; +import android.util.AttributeSet; +import android.util.Log; +import android.util.SparseArray; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.LinearLayout; +import android.widget.Space; + +/** + * @hide + */ +public final class NavigationBarInflaterView extends FrameLayout { + + private static final String TAG = "NavBarInflater"; + + public static final String NAV_BAR_VIEWS = "sysui_nav_bar"; + public static final String NAV_BAR_LEFT = "sysui_nav_bar_left"; + public static final String NAV_BAR_RIGHT = "sysui_nav_bar_right"; + + public static final String MENU_IME_ROTATE = "menu_ime"; + public static final String BACK = "back"; + public static final String HOME = "home"; + public static final String RECENT = "recent"; + public static final String NAVSPACE = "space"; + public static final String CLIPBOARD = "clipboard"; + public static final String HOME_HANDLE = "home_handle"; + public static final String KEY = "key"; + public static final String LEFT = "left"; + public static final String RIGHT = "right"; + public static final String CONTEXTUAL = "contextual"; + public static final String IME_SWITCHER = "ime_switcher"; + + public static final String GRAVITY_SEPARATOR = ";"; + public static final String BUTTON_SEPARATOR = ","; + + public static final String SIZE_MOD_START = "["; + public static final String SIZE_MOD_END = "]"; + + public static final String KEY_CODE_START = "("; + public static final String KEY_IMAGE_DELIM = ":"; + public static final String KEY_CODE_END = ")"; + private static final String WEIGHT_SUFFIX = "W"; + private static final String WEIGHT_CENTERED_SUFFIX = "WC"; + private static final String ABSOLUTE_SUFFIX = "A"; + private static final String ABSOLUTE_VERTICAL_CENTERED_SUFFIX = "C"; + + // Copied from "config_navBarLayoutHandle: + private static final String CONFIG_NAV_BAR_LAYOUT_HANDLE = + "back[70AC];home_handle;ime_switcher[70AC]"; + + protected LayoutInflater mLayoutInflater; + protected LayoutInflater mLandscapeInflater; + + protected FrameLayout mHorizontal; + + SparseArray<ButtonDispatcher> mButtonDispatchers; + + private View mLastPortrait; + private View mLastLandscape; + + private boolean mAlternativeOrder; + + public NavigationBarInflaterView(Context context, AttributeSet attrs) { + super(context, attrs); + createInflaters(); + } + + void createInflaters() { + mLayoutInflater = LayoutInflater.from(mContext); + Configuration landscape = new Configuration(); + landscape.setTo(mContext.getResources().getConfiguration()); + landscape.orientation = Configuration.ORIENTATION_LANDSCAPE; + mLandscapeInflater = LayoutInflater.from(mContext.createConfigurationContext(landscape)); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + inflateChildren(); + clearViews(); + inflateLayout(getDefaultLayout()); + } + + private void inflateChildren() { + removeAllViews(); + mHorizontal = (FrameLayout) mLayoutInflater.inflate( + com.android.internal.R.layout.input_method_navigation_layout, + this /* root */, false /* attachToRoot */); + addView(mHorizontal); + updateAlternativeOrder(); + } + + String getDefaultLayout() { + return CONFIG_NAV_BAR_LAYOUT_HANDLE; + } + + public void setButtonDispatchers(SparseArray<ButtonDispatcher> buttonDispatchers) { + mButtonDispatchers = buttonDispatchers; + for (int i = 0; i < buttonDispatchers.size(); i++) { + initiallyFill(buttonDispatchers.valueAt(i)); + } + } + + void updateButtonDispatchersCurrentView() { + if (mButtonDispatchers != null) { + View view = mHorizontal; + for (int i = 0; i < mButtonDispatchers.size(); i++) { + final ButtonDispatcher dispatcher = mButtonDispatchers.valueAt(i); + dispatcher.setCurrentView(view); + } + } + } + + void setAlternativeOrder(boolean alternativeOrder) { + if (alternativeOrder != mAlternativeOrder) { + mAlternativeOrder = alternativeOrder; + updateAlternativeOrder(); + } + } + + private void updateAlternativeOrder() { + updateAlternativeOrder(mHorizontal.findViewById( + com.android.internal.R.id.input_method_nav_ends_group)); + updateAlternativeOrder(mHorizontal.findViewById( + com.android.internal.R.id.input_method_nav_center_group)); + } + + private void updateAlternativeOrder(View v) { + if (v instanceof ReverseLinearLayout) { + ((ReverseLinearLayout) v).setAlternativeOrder(mAlternativeOrder); + } + } + + private void initiallyFill( + ButtonDispatcher buttonDispatcher) { + addAll(buttonDispatcher, mHorizontal.findViewById( + com.android.internal.R.id.input_method_nav_ends_group)); + addAll(buttonDispatcher, mHorizontal.findViewById( + com.android.internal.R.id.input_method_nav_center_group)); + } + + private void addAll(ButtonDispatcher buttonDispatcher, ViewGroup parent) { + for (int i = 0; i < parent.getChildCount(); i++) { + // Need to manually search for each id, just in case each group has more than one + // of a single id. It probably mostly a waste of time, but shouldn't take long + // and will only happen once. + if (parent.getChildAt(i).getId() == buttonDispatcher.getId()) { + buttonDispatcher.addView(parent.getChildAt(i)); + } + if (parent.getChildAt(i) instanceof ViewGroup) { + addAll(buttonDispatcher, (ViewGroup) parent.getChildAt(i)); + } + } + } + + protected void inflateLayout(String newLayout) { + if (newLayout == null) { + newLayout = getDefaultLayout(); + } + String[] sets = newLayout.split(GRAVITY_SEPARATOR, 3); + if (sets.length != 3) { + Log.d(TAG, "Invalid layout."); + newLayout = getDefaultLayout(); + sets = newLayout.split(GRAVITY_SEPARATOR, 3); + } + String[] start = sets[0].split(BUTTON_SEPARATOR); + String[] center = sets[1].split(BUTTON_SEPARATOR); + String[] end = sets[2].split(BUTTON_SEPARATOR); + // Inflate these in start to end order or accessibility traversal will be messed up. + inflateButtons(start, mHorizontal.findViewById( + com.android.internal.R.id.input_method_nav_ends_group), + false /* landscape */, true /* start */); + + inflateButtons(center, mHorizontal.findViewById( + com.android.internal.R.id.input_method_nav_center_group), + false /* landscape */, false /* start */); + + addGravitySpacer(mHorizontal.findViewById( + com.android.internal.R.id.input_method_nav_ends_group)); + + inflateButtons(end, mHorizontal.findViewById( + com.android.internal.R.id.input_method_nav_ends_group), + false /* landscape */, false /* start */); + + updateButtonDispatchersCurrentView(); + } + + private void addGravitySpacer(LinearLayout layout) { + layout.addView(new Space(mContext), new LinearLayout.LayoutParams(0, 0, 1)); + } + + private void inflateButtons(String[] buttons, ViewGroup parent, boolean landscape, + boolean start) { + for (int i = 0; i < buttons.length; i++) { + inflateButton(buttons[i], parent, landscape, start); + } + } + + private ViewGroup.LayoutParams copy(ViewGroup.LayoutParams layoutParams) { + if (layoutParams instanceof LinearLayout.LayoutParams) { + return new LinearLayout.LayoutParams(layoutParams.width, layoutParams.height, + ((LinearLayout.LayoutParams) layoutParams).weight); + } + return new LayoutParams(layoutParams.width, layoutParams.height); + } + + @Nullable + protected View inflateButton(String buttonSpec, ViewGroup parent, boolean landscape, + boolean start) { + LayoutInflater inflater = landscape ? mLandscapeInflater : mLayoutInflater; + View v = createView(buttonSpec, parent, inflater); + if (v == null) return null; + + v = applySize(v, buttonSpec, landscape, start); + parent.addView(v); + addToDispatchers(v); + View lastView = landscape ? mLastLandscape : mLastPortrait; + View accessibilityView = v; + if (v instanceof ReverseRelativeLayout) { + accessibilityView = ((ReverseRelativeLayout) v).getChildAt(0); + } + if (lastView != null) { + accessibilityView.setAccessibilityTraversalAfter(lastView.getId()); + } + if (landscape) { + mLastLandscape = accessibilityView; + } else { + mLastPortrait = accessibilityView; + } + return v; + } + + private View applySize(View v, String buttonSpec, boolean landscape, boolean start) { + String sizeStr = extractSize(buttonSpec); + if (sizeStr == null) return v; + + if (sizeStr.contains(WEIGHT_SUFFIX) || sizeStr.contains(ABSOLUTE_SUFFIX)) { + // To support gravity, wrap in RelativeLayout and apply gravity to it. + // Children wanting to use gravity must be smaller than the frame. + ReverseRelativeLayout frame = new ReverseRelativeLayout(mContext); + LayoutParams childParams = new LayoutParams(v.getLayoutParams()); + + // Compute gravity to apply + int gravity = (landscape) ? (start ? Gravity.TOP : Gravity.BOTTOM) + : (start ? Gravity.START : Gravity.END); + if (sizeStr.endsWith(WEIGHT_CENTERED_SUFFIX)) { + gravity = Gravity.CENTER; + } else if (sizeStr.endsWith(ABSOLUTE_VERTICAL_CENTERED_SUFFIX)) { + gravity = Gravity.CENTER_VERTICAL; + } + + // Set default gravity, flipped if needed in reversed layouts (270 RTL and 90 LTR) + frame.setDefaultGravity(gravity); + frame.setGravity(gravity); // Apply gravity to root + + frame.addView(v, childParams); + + if (sizeStr.contains(WEIGHT_SUFFIX)) { + // Use weighting to set the width of the frame + float weight = Float.parseFloat( + sizeStr.substring(0, sizeStr.indexOf(WEIGHT_SUFFIX))); + frame.setLayoutParams(new LinearLayout.LayoutParams(0, MATCH_PARENT, weight)); + } else { + int width = (int) convertDpToPx(mContext, + Float.parseFloat(sizeStr.substring(0, sizeStr.indexOf(ABSOLUTE_SUFFIX)))); + frame.setLayoutParams(new LinearLayout.LayoutParams(width, MATCH_PARENT)); + } + + // Ensure ripples can be drawn outside bounds + frame.setClipChildren(false); + frame.setClipToPadding(false); + + return frame; + } + + float size = Float.parseFloat(sizeStr); + ViewGroup.LayoutParams params = v.getLayoutParams(); + params.width = (int) (params.width * size); + return v; + } + + View createView(String buttonSpec, ViewGroup parent, LayoutInflater inflater) { + View v = null; + String button = extractButton(buttonSpec); + if (LEFT.equals(button)) { + button = extractButton(NAVSPACE); + } else if (RIGHT.equals(button)) { + button = extractButton(MENU_IME_ROTATE); + } + if (HOME.equals(button)) { + //v = inflater.inflate(R.layout.home, parent, false); + } else if (BACK.equals(button)) { + v = inflater.inflate(com.android.internal.R.layout.input_method_nav_back, parent, + false); + } else if (RECENT.equals(button)) { + //v = inflater.inflate(R.layout.recent_apps, parent, false); + } else if (MENU_IME_ROTATE.equals(button)) { + //v = inflater.inflate(R.layout.menu_ime, parent, false); + } else if (NAVSPACE.equals(button)) { + //v = inflater.inflate(R.layout.nav_key_space, parent, false); + } else if (CLIPBOARD.equals(button)) { + //v = inflater.inflate(R.layout.clipboard, parent, false); + } else if (CONTEXTUAL.equals(button)) { + //v = inflater.inflate(R.layout.contextual, parent, false); + } else if (HOME_HANDLE.equals(button)) { + v = inflater.inflate(com.android.internal.R.layout.input_method_nav_home_handle, + parent, false); + } else if (IME_SWITCHER.equals(button)) { + v = inflater.inflate(com.android.internal.R.layout.input_method_nav_ime_switcher, + parent, false); + } else if (button.startsWith(KEY)) { + /* + String uri = extractImage(button); + int code = extractKeycode(button); + v = inflater.inflate(R.layout.custom_key, parent, false); + ((KeyButtonView) v).setCode(code); + if (uri != null) { + if (uri.contains(":")) { + ((KeyButtonView) v).loadAsync(Icon.createWithContentUri(uri)); + } else if (uri.contains("/")) { + int index = uri.indexOf('/'); + String pkg = uri.substring(0, index); + int id = Integer.parseInt(uri.substring(index + 1)); + ((KeyButtonView) v).loadAsync(Icon.createWithResource(pkg, id)); + } + } + */ + } + return v; + } + + /* + public static String extractImage(String buttonSpec) { + if (!buttonSpec.contains(KEY_IMAGE_DELIM)) { + return null; + } + final int start = buttonSpec.indexOf(KEY_IMAGE_DELIM); + String subStr = buttonSpec.substring(start + 1, buttonSpec.indexOf(KEY_CODE_END)); + return subStr; + } + + public static int extractKeycode(String buttonSpec) { + if (!buttonSpec.contains(KEY_CODE_START)) { + return 1; + } + final int start = buttonSpec.indexOf(KEY_CODE_START); + String subStr = buttonSpec.substring(start + 1, buttonSpec.indexOf(KEY_IMAGE_DELIM)); + return Integer.parseInt(subStr); + } + */ + + public static String extractSize(String buttonSpec) { + if (!buttonSpec.contains(SIZE_MOD_START)) { + return null; + } + final int sizeStart = buttonSpec.indexOf(SIZE_MOD_START); + return buttonSpec.substring(sizeStart + 1, buttonSpec.indexOf(SIZE_MOD_END)); + } + + public static String extractButton(String buttonSpec) { + if (!buttonSpec.contains(SIZE_MOD_START)) { + return buttonSpec; + } + return buttonSpec.substring(0, buttonSpec.indexOf(SIZE_MOD_START)); + } + + private void addToDispatchers(View v) { + if (mButtonDispatchers != null) { + final int indexOfKey = mButtonDispatchers.indexOfKey(v.getId()); + if (indexOfKey >= 0) { + mButtonDispatchers.valueAt(indexOfKey).addView(v); + } + if (v instanceof ViewGroup) { + final ViewGroup viewGroup = (ViewGroup)v; + final int N = viewGroup.getChildCount(); + for (int i = 0; i < N; i++) { + addToDispatchers(viewGroup.getChildAt(i)); + } + } + } + } + + private void clearViews() { + if (mButtonDispatchers != null) { + for (int i = 0; i < mButtonDispatchers.size(); i++) { + mButtonDispatchers.valueAt(i).clear(); + } + } + clearAllChildren(mHorizontal.findViewById( + com.android.internal.R.id.input_method_nav_buttons)); + } + + private void clearAllChildren(ViewGroup group) { + for (int i = 0; i < group.getChildCount(); i++) { + ((ViewGroup) group.getChildAt(i)).removeAllViews(); + } + } + + private static float convertDpToPx(Context context, float dp) { + return dp * context.getResources().getDisplayMetrics().density; + } +} diff --git a/core/java/android/inputmethodservice/navigationbar/NavigationBarUtils.java b/core/java/android/inputmethodservice/navigationbar/NavigationBarUtils.java new file mode 100644 index 000000000000..c6096d7ba0a1 --- /dev/null +++ b/core/java/android/inputmethodservice/navigationbar/NavigationBarUtils.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.inputmethodservice.navigationbar; + +import static android.util.TypedValue.COMPLEX_UNIT_DIP; + +import android.content.res.Resources; +import android.util.TypedValue; + +final class NavigationBarUtils { + private NavigationBarUtils() { + // Not intended to be instantiated. + } + + /** + * A utility method to convert "dp" to "pixel". + * + * <p>TODO(b/215443343): Remove this method by migrating DP values from + * {@link NavigationBarConstants} to resource files.</p> + * + * @param dpValue "dp" value to be converted to "pixel" + * @param res {@link Resources} to be used when dealing with "dp". + * @return the pixels for a given dp value. + */ + static int dpToPx(float dpValue, Resources res) { + return (int) TypedValue.applyDimension(COMPLEX_UNIT_DIP, dpValue, res.getDisplayMetrics()); + } +} diff --git a/core/java/android/inputmethodservice/navigationbar/NavigationBarView.java b/core/java/android/inputmethodservice/navigationbar/NavigationBarView.java new file mode 100644 index 000000000000..42847784dd2b --- /dev/null +++ b/core/java/android/inputmethodservice/navigationbar/NavigationBarView.java @@ -0,0 +1,380 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.inputmethodservice.navigationbar; + +import static android.inputmethodservice.navigationbar.NavigationBarConstants.DARK_MODE_ICON_COLOR_SINGLE_TONE; +import static android.inputmethodservice.navigationbar.NavigationBarConstants.LIGHT_MODE_ICON_COLOR_SINGLE_TONE; +import static android.inputmethodservice.navigationbar.NavigationBarConstants.NAVBAR_BACK_BUTTON_IME_OFFSET; +import static android.inputmethodservice.navigationbar.NavigationBarUtils.dpToPx; +import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL; + +import android.animation.ObjectAnimator; +import android.animation.PropertyValuesHolder; +import android.annotation.DrawableRes; +import android.annotation.FloatRange; +import android.app.StatusBarManager; +import android.content.Context; +import android.content.res.Configuration; +import android.graphics.Canvas; +import android.util.AttributeSet; +import android.util.Log; +import android.util.SparseArray; +import android.view.Display; +import android.view.MotionEvent; +import android.view.Surface; +import android.view.View; +import android.view.animation.Interpolator; +import android.view.animation.PathInterpolator; +import android.view.inputmethod.InputMethodManager; +import android.widget.FrameLayout; + +import java.util.function.Consumer; + +/** + * @hide + */ +public final class NavigationBarView extends FrameLayout { + final static boolean DEBUG = false; + final static String TAG = "NavBarView"; + + // Copied from com.android.systemui.animation.Interpolators#FAST_OUT_SLOW_IN + private static final Interpolator FAST_OUT_SLOW_IN = new PathInterpolator(0.4f, 0f, 0.2f, 1f); + + // The current view is always mHorizontal. + View mCurrentView = null; + private View mHorizontal; + + private int mCurrentRotation = -1; + + int mDisabledFlags = 0; + int mNavigationIconHints = StatusBarManager.NAVIGATION_HINT_BACK_ALT; + private final int mNavBarMode = NAV_BAR_MODE_GESTURAL; + + private KeyButtonDrawable mBackIcon; + private KeyButtonDrawable mImeSwitcherIcon; + private Context mLightContext; + private final int mLightIconColor; + private final int mDarkIconColor; + + private final android.inputmethodservice.navigationbar.DeadZone mDeadZone; + private boolean mDeadZoneConsuming = false; + + private final SparseArray<ButtonDispatcher> mButtonDispatchers = new SparseArray<>(); + private Configuration mConfiguration; + private Configuration mTmpLastConfiguration; + + private NavigationBarInflaterView mNavigationInflaterView; + + public NavigationBarView(Context context, AttributeSet attrs) { + super(context, attrs); + + mLightContext = context; + mLightIconColor = LIGHT_MODE_ICON_COLOR_SINGLE_TONE; + mDarkIconColor = DARK_MODE_ICON_COLOR_SINGLE_TONE; + + mConfiguration = new Configuration(); + mTmpLastConfiguration = new Configuration(); + mConfiguration.updateFrom(context.getResources().getConfiguration()); + + mButtonDispatchers.put(com.android.internal.R.id.input_method_nav_back, + new ButtonDispatcher(com.android.internal.R.id.input_method_nav_back)); + mButtonDispatchers.put(com.android.internal.R.id.input_method_nav_ime_switcher, + new ButtonDispatcher(com.android.internal.R.id.input_method_nav_ime_switcher)); + mButtonDispatchers.put(com.android.internal.R.id.input_method_nav_home_handle, + new ButtonDispatcher(com.android.internal.R.id.input_method_nav_home_handle)); + + mDeadZone = new android.inputmethodservice.navigationbar.DeadZone(this); + + getImeSwitchButton().setOnClickListener(view -> view.getContext() + .getSystemService(InputMethodManager.class).showInputMethodPicker()); + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent event) { + return shouldDeadZoneConsumeTouchEvents(event) || super.onInterceptTouchEvent(event); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + shouldDeadZoneConsumeTouchEvents(event); + return super.onTouchEvent(event); + } + + private boolean shouldDeadZoneConsumeTouchEvents(MotionEvent event) { + int action = event.getActionMasked(); + if (action == MotionEvent.ACTION_DOWN) { + mDeadZoneConsuming = false; + } + if (mDeadZone.onTouchEvent(event) || mDeadZoneConsuming) { + switch (action) { + case MotionEvent.ACTION_DOWN: + mDeadZoneConsuming = true; + break; + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + mDeadZoneConsuming = false; + break; + } + return true; + } + return false; + } + + public View getCurrentView() { + return mCurrentView; + } + + /** + * Applies {@param consumer} to each of the nav bar views. + */ + public void forEachView(Consumer<View> consumer) { + if (mHorizontal != null) { + consumer.accept(mHorizontal); + } + } + + public ButtonDispatcher getBackButton() { + return mButtonDispatchers.get(com.android.internal.R.id.input_method_nav_back); + } + + public ButtonDispatcher getImeSwitchButton() { + return mButtonDispatchers.get(com.android.internal.R.id.input_method_nav_ime_switcher); + } + + public ButtonDispatcher getHomeHandle() { + return mButtonDispatchers.get(com.android.internal.R.id.input_method_nav_home_handle); + } + + public SparseArray<ButtonDispatcher> getButtonDispatchers() { + return mButtonDispatchers; + } + + private void reloadNavIcons() { + updateIcons(Configuration.EMPTY); + } + + private void updateIcons(Configuration oldConfig) { + final boolean orientationChange = oldConfig.orientation != mConfiguration.orientation; + final boolean densityChange = oldConfig.densityDpi != mConfiguration.densityDpi; + final boolean dirChange = + oldConfig.getLayoutDirection() != mConfiguration.getLayoutDirection(); + + if (densityChange || dirChange) { + mImeSwitcherIcon = getDrawable(com.android.internal.R.drawable.ic_ime_switcher); + } + if (orientationChange || densityChange || dirChange) { + mBackIcon = getBackDrawable(); + } + } + + public KeyButtonDrawable getBackDrawable() { + KeyButtonDrawable drawable = getDrawable(com.android.internal.R.drawable.ic_ime_nav_back); + orientBackButton(drawable); + return drawable; + } + + /** + * @return whether this nav bar mode is edge to edge + */ + public static boolean isGesturalMode(int mode) { + return mode == NAV_BAR_MODE_GESTURAL; + } + + private void orientBackButton(KeyButtonDrawable drawable) { + final boolean useAltBack = + (mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0; + final boolean isRtl = mConfiguration.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL; + float degrees = useAltBack ? (isRtl ? 90 : -90) : 0; + if (drawable.getRotation() == degrees) { + return; + } + + if (isGesturalMode(mNavBarMode)) { + drawable.setRotation(degrees); + return; + } + + // Animate the back button's rotation to the new degrees and only in portrait move up the + // back button to line up with the other buttons + float targetY = useAltBack + ? - dpToPx(NAVBAR_BACK_BUTTON_IME_OFFSET, getResources()) + : 0; + ObjectAnimator navBarAnimator = ObjectAnimator.ofPropertyValuesHolder(drawable, + PropertyValuesHolder.ofFloat(KeyButtonDrawable.KEY_DRAWABLE_ROTATE, degrees), + PropertyValuesHolder.ofFloat(KeyButtonDrawable.KEY_DRAWABLE_TRANSLATE_Y, targetY)); + navBarAnimator.setInterpolator(FAST_OUT_SLOW_IN); + navBarAnimator.setDuration(200); + navBarAnimator.start(); + } + + private KeyButtonDrawable getDrawable(@DrawableRes int icon) { + return KeyButtonDrawable.create(mLightContext, mLightIconColor, mDarkIconColor, icon, + true /* hasShadow */, null /* ovalBackgroundColor */); + } + + @Override + public void setLayoutDirection(int layoutDirection) { + reloadNavIcons(); + + super.setLayoutDirection(layoutDirection); + } + + public void setNavigationIconHints(int hints) { + if (hints == mNavigationIconHints) return; + final boolean newBackAlt = (hints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0; + final boolean oldBackAlt = + (mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0; + if (newBackAlt != oldBackAlt) { + //onImeVisibilityChanged(newBackAlt); + } + + if (DEBUG) { + android.widget.Toast.makeText(getContext(), "Navigation icon hints = " + hints, 500) + .show(); + } + mNavigationIconHints = hints; + updateNavButtonIcons(); + } + + public void setDisabledFlags(int disabledFlags) { + if (mDisabledFlags == disabledFlags) return; + + mDisabledFlags = disabledFlags; + + updateNavButtonIcons(); + } + + public void updateNavButtonIcons() { + // We have to replace or restore the back and home button icons when exiting or entering + // carmode, respectively. Recents are not available in CarMode in nav bar so change + // to recent icon is not required. + KeyButtonDrawable backIcon = mBackIcon; + orientBackButton(backIcon); + getBackButton().setImageDrawable(backIcon); + + getImeSwitchButton().setImageDrawable(mImeSwitcherIcon); + + // Update IME button visibility, a11y and rotate button always overrides the appearance + final boolean imeSwitcherVisible = + (mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_IME_SHOWN) != 0; + getImeSwitchButton().setVisibility(imeSwitcherVisible ? View.VISIBLE : View.INVISIBLE); + + getBackButton().setVisibility(View.VISIBLE); + getHomeHandle().setVisibility(View.INVISIBLE); + + // We used to be reporting the touch regions via notifyActiveTouchRegions() here. + // TODO(b/215593010): Consider taking care of this in the Launcher side. + } + + private Display getContextDisplay() { + return getContext().getDisplay(); + } + + @Override + public void onFinishInflate() { + super.onFinishInflate(); + mNavigationInflaterView = findViewById(com.android.internal.R.id.input_method_nav_inflater); + mNavigationInflaterView.setButtonDispatchers(mButtonDispatchers); + + updateOrientationViews(); + reloadNavIcons(); + } + + @Override + protected void onDraw(Canvas canvas) { + mDeadZone.onDraw(canvas); + super.onDraw(canvas); + } + + private void updateOrientationViews() { + mHorizontal = findViewById(com.android.internal.R.id.input_method_nav_horizontal); + + updateCurrentView(); + } + + private void updateCurrentView() { + resetViews(); + mCurrentView = mHorizontal; + mCurrentView.setVisibility(View.VISIBLE); + mCurrentRotation = getContextDisplay().getRotation(); + mNavigationInflaterView.setAlternativeOrder(mCurrentRotation == Surface.ROTATION_90); + mNavigationInflaterView.updateButtonDispatchersCurrentView(); + } + + private void resetViews() { + mHorizontal.setVisibility(View.GONE); + } + + public void reorient() { + updateCurrentView(); + + final android.inputmethodservice.navigationbar.NavigationBarFrame frame = + getRootView().findViewByPredicate(view -> view instanceof NavigationBarFrame); + frame.setDeadZone(mDeadZone); + mDeadZone.onConfigurationChanged(mCurrentRotation); + + if (DEBUG) { + Log.d(TAG, "reorient(): rot=" + mCurrentRotation); + } + + // Resolve layout direction if not resolved since components changing layout direction such + // as changing languages will recreate this view and the direction will be resolved later + if (!isLayoutDirectionResolved()) { + resolveLayoutDirection(); + } + updateNavButtonIcons(); + } + + @Override + protected void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + mTmpLastConfiguration.updateFrom(mConfiguration); + final int changes = mConfiguration.updateFrom(newConfig); + + updateIcons(mTmpLastConfiguration); + if (mTmpLastConfiguration.densityDpi != mConfiguration.densityDpi + || mTmpLastConfiguration.getLayoutDirection() + != mConfiguration.getLayoutDirection()) { + // If car mode or density changes, we need to reset the icons. + updateNavButtonIcons(); + } + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + // This needs to happen first as it can changed the enabled state which can affect whether + // the back button is visible + requestApplyInsets(); + reorient(); + updateNavButtonIcons(); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + for (int i = 0; i < mButtonDispatchers.size(); ++i) { + mButtonDispatchers.valueAt(i).onDestroy(); + } + } + + public void setDarkIntensity(@FloatRange(from = 0.0f, to = 1.0f) float intensity) { + for (int i = 0; i < mButtonDispatchers.size(); ++i) { + mButtonDispatchers.valueAt(i).setDarkIntensity(intensity); + } + } +} diff --git a/core/java/android/inputmethodservice/navigationbar/NavigationHandle.java b/core/java/android/inputmethodservice/navigationbar/NavigationHandle.java new file mode 100644 index 000000000000..273cafb7fca7 --- /dev/null +++ b/core/java/android/inputmethodservice/navigationbar/NavigationHandle.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.inputmethodservice.navigationbar; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; + +/** + * TODO(b/215443343): Remove this file, as IME actually doesn't use this. + * + * @hide + */ +public class NavigationHandle extends View implements ButtonInterface { + + public NavigationHandle(Context context) { + this(context, null); + } + + public NavigationHandle(Context context, AttributeSet attr) { + super(context, attr); + setFocusable(false); + } + + @Override + public boolean dispatchTouchEvent(MotionEvent event) { + return false; + } + + @Override + public void setImageDrawable(Drawable drawable) { + } + + @Override + public void setDarkIntensity(float intensity) { + } + + @Override + public void setDelayTouchFeedback(boolean shouldDelay) { + } +} diff --git a/core/java/android/inputmethodservice/navigationbar/ReverseLinearLayout.java b/core/java/android/inputmethodservice/navigationbar/ReverseLinearLayout.java new file mode 100644 index 000000000000..68163c35f784 --- /dev/null +++ b/core/java/android/inputmethodservice/navigationbar/ReverseLinearLayout.java @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.inputmethodservice.navigationbar; + +import android.annotation.Nullable; +import android.content.Context; +import android.util.AttributeSet; +import android.view.Gravity; +import android.view.View; +import android.view.ViewGroup; +import android.widget.LinearLayout; +import android.widget.RelativeLayout; + +import java.util.ArrayList; + +/** + * Automatically reverses the order of children as they are added. + * Also reverse the width and height values of layout params + * @hide + */ +public class ReverseLinearLayout extends LinearLayout { + + /** If true, the layout is reversed vs. a regular linear layout */ + private boolean mIsLayoutReverse; + + /** If true, the layout is opposite to it's natural reversity from the layout direction */ + private boolean mIsAlternativeOrder; + + public ReverseLinearLayout(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + updateOrder(); + } + + @Override + public void addView(View child) { + reverseParams(child.getLayoutParams(), child, mIsLayoutReverse); + if (mIsLayoutReverse) { + super.addView(child, 0); + } else { + super.addView(child); + } + } + + @Override + public void addView(View child, ViewGroup.LayoutParams params) { + reverseParams(params, child, mIsLayoutReverse); + if (mIsLayoutReverse) { + super.addView(child, 0, params); + } else { + super.addView(child, params); + } + } + + @Override + public void onRtlPropertiesChanged(int layoutDirection) { + super.onRtlPropertiesChanged(layoutDirection); + updateOrder(); + } + + public void setAlternativeOrder(boolean alternative) { + mIsAlternativeOrder = alternative; + updateOrder(); + } + + /** + * In landscape, the LinearLayout is not auto mirrored since it is vertical. Therefore we + * have to do it manually + */ + private void updateOrder() { + boolean isLayoutRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL; + boolean isLayoutReverse = isLayoutRtl ^ mIsAlternativeOrder; + + if (mIsLayoutReverse != isLayoutReverse) { + // reversity changed, swap the order of all views. + int childCount = getChildCount(); + ArrayList<View> childList = new ArrayList<>(childCount); + for (int i = 0; i < childCount; i++) { + childList.add(getChildAt(i)); + } + removeAllViews(); + for (int i = childCount - 1; i >= 0; i--) { + final View child = childList.get(i); + super.addView(child); + } + mIsLayoutReverse = isLayoutReverse; + } + } + + private static void reverseParams(ViewGroup.LayoutParams params, View child, + boolean isLayoutReverse) { + if (child instanceof Reversible) { + ((Reversible) child).reverse(isLayoutReverse); + } + if (child.getPaddingLeft() == child.getPaddingRight() + && child.getPaddingTop() == child.getPaddingBottom()) { + child.setPadding(child.getPaddingTop(), child.getPaddingLeft(), + child.getPaddingTop(), child.getPaddingLeft()); + } + if (params == null) { + return; + } + int width = params.width; + params.width = params.height; + params.height = width; + } + + interface Reversible { + void reverse(boolean isLayoutReverse); + } + + public static class ReverseRelativeLayout extends RelativeLayout implements Reversible { + + public ReverseRelativeLayout(Context context) { + super(context); + } + + @Override + public void reverse(boolean isLayoutReverse) { + updateGravity(isLayoutReverse); + reverseGroup(this, isLayoutReverse); + } + + private int mDefaultGravity = Gravity.NO_GRAVITY; + public void setDefaultGravity(int gravity) { + mDefaultGravity = gravity; + } + + public void updateGravity(boolean isLayoutReverse) { + // Flip gravity if top of bottom is used + if (mDefaultGravity != Gravity.TOP && mDefaultGravity != Gravity.BOTTOM) return; + + // Use the default (intended for 270 LTR and 90 RTL) unless layout is otherwise + int gravityToApply = mDefaultGravity; + if (isLayoutReverse) { + gravityToApply = mDefaultGravity == Gravity.TOP ? Gravity.BOTTOM : Gravity.TOP; + } + + if (getGravity() != gravityToApply) setGravity(gravityToApply); + } + } + + private static void reverseGroup(ViewGroup group, boolean isLayoutReverse) { + for (int i = 0; i < group.getChildCount(); i++) { + final View child = group.getChildAt(i); + reverseParams(child.getLayoutParams(), child, isLayoutReverse); + + // Recursively reverse all children + if (child instanceof ViewGroup) { + reverseGroup((ViewGroup) child, isLayoutReverse); + } + } + } +} diff --git a/core/java/android/net/INetworkPolicyManager.aidl b/core/java/android/net/INetworkPolicyManager.aidl index 147138e6712a..6284f56c8258 100644 --- a/core/java/android/net/INetworkPolicyManager.aidl +++ b/core/java/android/net/INetworkPolicyManager.aidl @@ -70,7 +70,7 @@ interface INetworkPolicyManager { int getMultipathPreference(in Network network); SubscriptionPlan getSubscriptionPlan(in NetworkTemplate template); - void onStatsProviderWarningOrLimitReached(); + void notifyStatsProviderWarningOrLimitReached(); SubscriptionPlan[] getSubscriptionPlans(int subId, String callingPackage); void setSubscriptionPlans(int subId, in SubscriptionPlan[] plans, String callingPackage); String getSubscriptionPlansOwner(int subId); diff --git a/core/java/android/net/InterfaceConfiguration.java b/core/java/android/net/InterfaceConfiguration.java index 37425ffc18aa..1c4089c0f366 100644 --- a/core/java/android/net/InterfaceConfiguration.java +++ b/core/java/android/net/InterfaceConfiguration.java @@ -160,6 +160,7 @@ public class InterfaceConfiguration implements Parcelable { } } + @SuppressWarnings("UnsafeParcelApi") public static final @android.annotation.NonNull Creator<InterfaceConfiguration> CREATOR = new Creator< InterfaceConfiguration>() { public InterfaceConfiguration createFromParcel(Parcel in) { diff --git a/core/java/android/net/NetworkPolicy.java b/core/java/android/net/NetworkPolicy.java index ab1f5420fb3f..596f4317dce3 100644 --- a/core/java/android/net/NetworkPolicy.java +++ b/core/java/android/net/NetworkPolicy.java @@ -142,8 +142,8 @@ public class NetworkPolicy implements Parcelable, Comparable<NetworkPolicy> { } private NetworkPolicy(Parcel source) { - template = source.readParcelable(null); - cycleRule = source.readParcelable(null); + template = source.readParcelable(null, android.net.NetworkTemplate.class); + cycleRule = source.readParcelable(null, android.util.RecurrenceRule.class); warningBytes = source.readLong(); limitBytes = source.readLong(); lastWarningSnooze = source.readLong(); diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java index 426fc617d5fb..c936bfa05118 100644 --- a/core/java/android/net/NetworkPolicyManager.java +++ b/core/java/android/net/NetworkPolicyManager.java @@ -546,7 +546,7 @@ public class NetworkPolicyManager { @RequiresPermission(anyOf = { NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) - // @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public SubscriptionPlan getSubscriptionPlan(@NonNull NetworkTemplate template) { try { return mService.getSubscriptionPlan(template); @@ -565,10 +565,10 @@ public class NetworkPolicyManager { @RequiresPermission(anyOf = { NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) - // @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) - public void onStatsProviderWarningOrLimitReached() { + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public void notifyStatsProviderWarningOrLimitReached() { try { - mService.onStatsProviderWarningOrLimitReached(); + mService.notifyStatsProviderWarningOrLimitReached(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/net/vcn/VcnConfig.java b/core/java/android/net/vcn/VcnConfig.java index caab15251f58..fd3fe3731b74 100644 --- a/core/java/android/net/vcn/VcnConfig.java +++ b/core/java/android/net/vcn/VcnConfig.java @@ -173,7 +173,7 @@ public final class VcnConfig implements Parcelable { new Parcelable.Creator<VcnConfig>() { @NonNull public VcnConfig createFromParcel(Parcel in) { - return new VcnConfig((PersistableBundle) in.readParcelable(null)); + return new VcnConfig((PersistableBundle) in.readParcelable(null, android.os.PersistableBundle.class)); } @NonNull diff --git a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java index a6830b708c31..2339656979b5 100644 --- a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java +++ b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java @@ -549,8 +549,8 @@ public final class VcnGatewayConnectionConfig { * networks, a network will be chosen arbitrarily amongst the networks matching the highest * priority rule. * - * <p>If all networks fail to match the rules provided, an underlying network will still be - * selected (at random if necessary). + * <p>If all networks fail to match the rules provided, a carrier-owned underlying network + * will still be selected (if available, at random if necessary). * * @param underlyingNetworkTemplates a list of unique VcnUnderlyingNetworkTemplates that are * ordered from most to least preferred, or an empty list to use the default diff --git a/core/java/android/net/vcn/VcnNetworkPolicyResult.java b/core/java/android/net/vcn/VcnNetworkPolicyResult.java index 14e70cfeb18a..fca084a00a79 100644 --- a/core/java/android/net/vcn/VcnNetworkPolicyResult.java +++ b/core/java/android/net/vcn/VcnNetworkPolicyResult.java @@ -114,7 +114,7 @@ public final class VcnNetworkPolicyResult implements Parcelable { public static final @NonNull Creator<VcnNetworkPolicyResult> CREATOR = new Creator<VcnNetworkPolicyResult>() { public VcnNetworkPolicyResult createFromParcel(Parcel in) { - return new VcnNetworkPolicyResult(in.readBoolean(), in.readParcelable(null)); + return new VcnNetworkPolicyResult(in.readBoolean(), in.readParcelable(null, android.net.NetworkCapabilities.class)); } public VcnNetworkPolicyResult[] newArray(int size) { diff --git a/core/java/android/net/vcn/VcnTransportInfo.java b/core/java/android/net/vcn/VcnTransportInfo.java index 25a257423ce2..5c47b28a7c74 100644 --- a/core/java/android/net/vcn/VcnTransportInfo.java +++ b/core/java/android/net/vcn/VcnTransportInfo.java @@ -146,7 +146,7 @@ public class VcnTransportInfo implements TransportInfo, Parcelable { new Creator<VcnTransportInfo>() { public VcnTransportInfo createFromParcel(Parcel in) { final int subId = in.readInt(); - final WifiInfo wifiInfo = in.readParcelable(null); + final WifiInfo wifiInfo = in.readParcelable(null, android.net.wifi.WifiInfo.class); // If all fields are their null values, return null TransportInfo to avoid // leaking information about this being a VCN Network (instead of macro diff --git a/core/java/android/net/vcn/VcnUnderlyingNetworkPolicy.java b/core/java/android/net/vcn/VcnUnderlyingNetworkPolicy.java index b0d4f3be248f..2b5305d05dcd 100644 --- a/core/java/android/net/vcn/VcnUnderlyingNetworkPolicy.java +++ b/core/java/android/net/vcn/VcnUnderlyingNetworkPolicy.java @@ -106,7 +106,7 @@ public final class VcnUnderlyingNetworkPolicy implements Parcelable { public static final @NonNull Creator<VcnUnderlyingNetworkPolicy> CREATOR = new Creator<VcnUnderlyingNetworkPolicy>() { public VcnUnderlyingNetworkPolicy createFromParcel(Parcel in) { - return new VcnUnderlyingNetworkPolicy(in.readParcelable(null)); + return new VcnUnderlyingNetworkPolicy(in.readParcelable(null, android.net.vcn.VcnNetworkPolicyResult.class)); } public VcnUnderlyingNetworkPolicy[] newArray(int size) { diff --git a/core/java/android/nfc/BeamShareData.java b/core/java/android/nfc/BeamShareData.java index ed3b74ab6308..6a40f98fe21c 100644 --- a/core/java/android/nfc/BeamShareData.java +++ b/core/java/android/nfc/BeamShareData.java @@ -47,13 +47,13 @@ public final class BeamShareData implements Parcelable { @Override public BeamShareData createFromParcel(Parcel source) { Uri[] uris = null; - NdefMessage msg = source.readParcelable(NdefMessage.class.getClassLoader()); + NdefMessage msg = source.readParcelable(NdefMessage.class.getClassLoader(), android.nfc.NdefMessage.class); int numUris = source.readInt(); if (numUris > 0) { uris = new Uri[numUris]; source.readTypedArray(uris, Uri.CREATOR); } - UserHandle userHandle = source.readParcelable(UserHandle.class.getClassLoader()); + UserHandle userHandle = source.readParcelable(UserHandle.class.getClassLoader(), android.os.UserHandle.class); int flags = source.readInt(); return new BeamShareData(msg, uris, userHandle, flags); diff --git a/core/java/android/os/BatteryConsumer.java b/core/java/android/os/BatteryConsumer.java index d2f788f4d0fb..47a272c2337f 100644 --- a/core/java/android/os/BatteryConsumer.java +++ b/core/java/android/os/BatteryConsumer.java @@ -192,6 +192,7 @@ public abstract class BatteryConsumer { POWER_COMPONENT_CPU, POWER_COMPONENT_MOBILE_RADIO, POWER_COMPONENT_WIFI, + POWER_COMPONENT_BLUETOOTH, }; static final int COLUMN_INDEX_BATTERY_CONSUMER_TYPE = 0; diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index a4538876c60f..2d338179186e 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -1037,6 +1037,16 @@ public abstract class BatteryStats implements Parcelable { public abstract long getBluetoothMeasuredBatteryConsumptionUC(); /** + * Returns the battery consumption (in microcoulombs) of the uid's bluetooth usage + * when in the specified process state. + * Will return {@link #POWER_DATA_UNAVAILABLE} if data is unavailable. + * + * {@hide} + */ + public abstract long getBluetoothMeasuredBatteryConsumptionUC( + @BatteryConsumer.ProcessState int processState); + + /** * Returns the battery consumption (in microcoulombs) of the uid's cpu usage, derived from * on device power measurement data. * Will return {@link #POWER_DATA_UNAVAILABLE} if data is unavailable. @@ -3396,6 +3406,11 @@ public abstract class BatteryStats implements Parcelable { public abstract WakeLockStats getWakeLockStats(); /** + * Returns aggregated Bluetooth stats. + */ + public abstract BluetoothBatteryStats getBluetoothBatteryStats(); + + /** * Returns Timers tracking the total time of each Resource Power Manager state and voter. */ public abstract Map<String, ? extends Timer> getRpmStats(); diff --git a/core/java/android/os/BatteryStatsManager.java b/core/java/android/os/BatteryStatsManager.java index 6339435e539c..2a609b8f6ae4 100644 --- a/core/java/android/os/BatteryStatsManager.java +++ b/core/java/android/os/BatteryStatsManager.java @@ -368,6 +368,21 @@ public final class BatteryStatsManager { } /** + * Retrieves accumulated bluetooth stats. + * + * @hide + */ + @RequiresPermission(android.Manifest.permission.BATTERY_STATS) + @NonNull + public BluetoothBatteryStats getBluetoothBatteryStats() { + try { + return mBatteryStats.getBluetoothBatteryStats(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Indicates an app acquiring full wifi lock. * * @param ws worksource (to be used for battery blaming). diff --git a/core/java/android/os/BluetoothBatteryStats.aidl b/core/java/android/os/BluetoothBatteryStats.aidl new file mode 100644 index 000000000000..d0514b67e223 --- /dev/null +++ b/core/java/android/os/BluetoothBatteryStats.aidl @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.os; + +/** {@hide} */ +parcelable BluetoothBatteryStats; diff --git a/core/java/android/os/BluetoothBatteryStats.java b/core/java/android/os/BluetoothBatteryStats.java new file mode 100644 index 000000000000..3d99a08a59c5 --- /dev/null +++ b/core/java/android/os/BluetoothBatteryStats.java @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.os; + +import android.annotation.NonNull; + +import java.util.ArrayList; +import java.util.List; + +/** + * Snapshot of Bluetooth battery stats. + * + * @hide + */ +public class BluetoothBatteryStats implements Parcelable { + + /** @hide */ + public static class UidStats { + public final int uid; + public final long scanTimeMs; + public final long unoptimizedScanTimeMs; + public final int scanResultCount; + public final long rxTimeMs; + public final long txTimeMs; + + public UidStats(int uid, long scanTimeMs, long unoptimizedScanTimeMs, int scanResultCount, + long rxTimeMs, long txTimeMs) { + this.uid = uid; + this.scanTimeMs = scanTimeMs; + this.unoptimizedScanTimeMs = unoptimizedScanTimeMs; + this.scanResultCount = scanResultCount; + this.rxTimeMs = rxTimeMs; + this.txTimeMs = txTimeMs; + } + + private UidStats(Parcel in) { + uid = in.readInt(); + scanTimeMs = in.readLong(); + unoptimizedScanTimeMs = in.readLong(); + scanResultCount = in.readInt(); + rxTimeMs = in.readLong(); + txTimeMs = in.readLong(); + } + + private void writeToParcel(Parcel out) { + out.writeInt(uid); + out.writeLong(scanTimeMs); + out.writeLong(unoptimizedScanTimeMs); + out.writeInt(scanResultCount); + out.writeLong(rxTimeMs); + out.writeLong(txTimeMs); + } + + @Override + public String toString() { + return "UidStats{" + + "uid=" + uid + + ", scanTimeMs=" + scanTimeMs + + ", unoptimizedScanTimeMs=" + unoptimizedScanTimeMs + + ", scanResultCount=" + scanResultCount + + ", rxTimeMs=" + rxTimeMs + + ", txTimeMs=" + txTimeMs + + '}'; + } + } + + private final List<UidStats> mUidStats; + + public BluetoothBatteryStats(@NonNull List<UidStats> uidStats) { + mUidStats = uidStats; + } + + @NonNull + public List<UidStats> getUidStats() { + return mUidStats; + } + + protected BluetoothBatteryStats(Parcel in) { + final int size = in.readInt(); + mUidStats = new ArrayList<>(size); + for (int i = 0; i < size; i++) { + mUidStats.add(new UidStats(in)); + } + } + + @Override + public void writeToParcel(@NonNull Parcel out, int flags) { + final int size = mUidStats.size(); + out.writeInt(size); + for (int i = 0; i < size; i++) { + UidStats stats = mUidStats.get(i); + stats.writeToParcel(out); + } + } + + public static final Creator<BluetoothBatteryStats> CREATOR = + new Creator<BluetoothBatteryStats>() { + @Override + public BluetoothBatteryStats createFromParcel(Parcel in) { + return new BluetoothBatteryStats(in); + } + + @Override + public BluetoothBatteryStats[] newArray(int size) { + return new BluetoothBatteryStats[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } +} diff --git a/core/java/android/os/BluetoothServiceManager.java b/core/java/android/os/BluetoothServiceManager.java new file mode 100644 index 000000000000..12f7bc84cd9c --- /dev/null +++ b/core/java/android/os/BluetoothServiceManager.java @@ -0,0 +1,121 @@ +/* + * 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. + */ +package android.os; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.annotation.SystemApi; +import android.annotation.SystemApi.Client; +import android.os.BluetoothServiceManager; + +/** + * Provides a way to register and obtain the system service binder objects managed by the bluetooth + * service. + * + * @hide + */ +@SystemApi(client = Client.MODULE_LIBRARIES) +public class BluetoothServiceManager { + + /** @hide */ + public static final String BLUETOOTH_MANAGER_SERVICE = "bluetooth_manager"; + /** + * @hide + */ + public BluetoothServiceManager() { + } + + /** + * A class that exposes the methods to register and obtain each system service. + */ + public static final class ServiceRegisterer { + private final String mServiceName; + + /** + * @hide + */ + public ServiceRegisterer(String serviceName) { + mServiceName = serviceName; + } + + /** + * Register a system server binding object for a service. + */ + public void register(@NonNull IBinder service) { + ServiceManager.addService(mServiceName, service); + } + + /** + * Get the system server binding object for a service. + * + * <p>This blocks until the service instance is ready, + * or a timeout happens, in which case it returns null. + */ + @Nullable + public IBinder get() { + return ServiceManager.getService(mServiceName); + } + + /** + * Get the system server binding object for a service. + * + * <p>This blocks until the service instance is ready, + * or a timeout happens, in which case it throws {@link ServiceNotFoundException}. + */ + @NonNull + public IBinder getOrThrow() throws ServiceNotFoundException { + try { + return ServiceManager.getServiceOrThrow(mServiceName); + } catch (ServiceManager.ServiceNotFoundException e) { + throw new ServiceNotFoundException(mServiceName); + } + } + + /** + * Get the system server binding object for a service. If the specified service is + * not available, it returns null. + */ + @Nullable + public IBinder tryGet() { + return ServiceManager.checkService(mServiceName); + } + } + + /** + * See {@link ServiceRegisterer#getOrThrow}. + * + */ + public static class ServiceNotFoundException extends ServiceManager.ServiceNotFoundException { + /** + * Constructor. + * + * @param name the name of the binder service that cannot be found. + * + */ + public ServiceNotFoundException(@NonNull String name) { + super(name); + } + } + + /** + * Returns {@link ServiceRegisterer} for the "bluetooth" service. + */ + @NonNull + public ServiceRegisterer getBluetoothManagerServiceRegisterer() { + return new ServiceRegisterer(BLUETOOTH_MANAGER_SERVICE); + } +} diff --git a/core/java/android/os/LocaleList.java b/core/java/android/os/LocaleList.java index cfa823cffe86..b74bb333deae 100644 --- a/core/java/android/os/LocaleList.java +++ b/core/java/android/os/LocaleList.java @@ -20,6 +20,7 @@ import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.Size; +import android.annotation.SuppressLint; import android.compat.annotation.UnsupportedAppUsage; import android.icu.util.ULocale; @@ -312,18 +313,30 @@ public final class LocaleList implements Parcelable { return isPseudoLocale(locale != null ? locale.toLocale() : null); } - @IntRange(from=0, to=1) - private static int matchScore(Locale supported, Locale desired) { + /** + * Determine whether two locales are considered a match, even if they are not exactly equal. + * They are considered as a match when both of their languages and scripts + * (explicit or inferred) are identical. This means that a user would be able to understand + * the content written in the supported locale even if they say they prefer the desired locale. + * + * E.g. [zh-HK] matches [zh-Hant]; [en-US] matches [en-CA] + * + * @param supported The supported {@link Locale} to be compared. + * @param desired The desired {@link Locale} to be compared. + * @return True if they match, false otherwise. + */ + public static boolean matchesLanguageAndScript(@SuppressLint("UseIcu") @NonNull + Locale supported, @SuppressLint("UseIcu") @NonNull Locale desired) { if (supported.equals(desired)) { - return 1; // return early so we don't do unnecessary computation + return true; // return early so we don't do unnecessary computation } if (!supported.getLanguage().equals(desired.getLanguage())) { - return 0; + return false; } if (isPseudoLocale(supported) || isPseudoLocale(desired)) { // The locales are not the same, but the languages are the same, and one of the locales // is a pseudo-locale. So this is not a match. - return 0; + return false; } final String supportedScr = getLikelyScript(supported); if (supportedScr.isEmpty()) { @@ -331,20 +344,17 @@ public final class LocaleList implements Parcelable { // if the locales match. So we fall back to old behavior of matching, which considered // locales with different regions different. final String supportedRegion = supported.getCountry(); - return (supportedRegion.isEmpty() || - supportedRegion.equals(desired.getCountry())) - ? 1 : 0; + return supportedRegion.isEmpty() || supportedRegion.equals(desired.getCountry()); } final String desiredScr = getLikelyScript(desired); // There is no match if the two locales use different scripts. This will most imporantly // take care of traditional vs simplified Chinese. - return supportedScr.equals(desiredScr) ? 1 : 0; + return supportedScr.equals(desiredScr); } private int findFirstMatchIndex(Locale supportedLocale) { for (int idx = 0; idx < mList.length; idx++) { - final int score = matchScore(supportedLocale, mList[idx]); - if (score > 0) { + if (matchesLanguageAndScript(supportedLocale, mList[idx])) { return idx; } } diff --git a/core/java/android/os/Message.java b/core/java/android/os/Message.java index c62df407ca77..72fb4ae03a63 100644 --- a/core/java/android/os/Message.java +++ b/core/java/android/os/Message.java @@ -654,7 +654,7 @@ public final class Message implements Parcelable { arg1 = source.readInt(); arg2 = source.readInt(); if (source.readInt() != 0) { - obj = source.readParcelable(getClass().getClassLoader()); + obj = source.readParcelable(getClass().getClassLoader(), java.lang.Object.class); } when = source.readLong(); data = source.readBundle(); diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java index 3bc3ec83bde5..e8b3ae9499fb 100644 --- a/core/java/android/os/Parcel.java +++ b/core/java/android/os/Parcel.java @@ -3711,10 +3711,10 @@ public final class Parcel { final int m = list.size(); int i = 0; for (; i < m && i < n; i++) { - list.set(i, (T) readParcelableInternal(cl, clazz)); + list.set(i, readParcelableInternal(cl, clazz)); } for (; i < n; i++) { - list.add((T) readParcelableInternal(cl, clazz)); + list.add(readParcelableInternal(cl, clazz)); } for (; i < m; i++) { list.remove(n); @@ -4217,7 +4217,8 @@ public final class Parcel { * trying to instantiate an element. */ @Nullable - public <T> T readParcelable(@Nullable ClassLoader loader, @NonNull Class<T> clazz) { + public <T extends Parcelable> T readParcelable(@Nullable ClassLoader loader, + @NonNull Class<? super T> clazz) { Objects.requireNonNull(clazz); return readParcelableInternal(loader, clazz); } @@ -4227,7 +4228,8 @@ public final class Parcel { */ @SuppressWarnings("unchecked") @Nullable - private <T> T readParcelableInternal(@Nullable ClassLoader loader, @Nullable Class<T> clazz) { + private <T extends Parcelable> T readParcelableInternal(@Nullable ClassLoader loader, + @Nullable Class<? super T> clazz) { Parcelable.Creator<?> creator = readParcelableCreatorInternal(loader, clazz); if (creator == null) { return null; @@ -4463,7 +4465,8 @@ public final class Parcel { * deserializing the object. */ @Nullable - public <T> T readSerializable(@Nullable ClassLoader loader, @NonNull Class<T> clazz) { + public <T extends Serializable> T readSerializable(@Nullable ClassLoader loader, + @NonNull Class<? super T> clazz) { Objects.requireNonNull(clazz); return readSerializableInternal( loader == null ? getClass().getClassLoader() : loader, clazz); @@ -4473,8 +4476,8 @@ public final class Parcel { * @param clazz The type of the serializable expected or {@code null} for performing no checks */ @Nullable - private <T> T readSerializableInternal(@Nullable final ClassLoader loader, - @Nullable Class<T> clazz) { + private <T extends Serializable> T readSerializableInternal(@Nullable final ClassLoader loader, + @Nullable Class<? super T> clazz) { String name = readString(); if (name == null) { // For some reason we were unable to read the name of the Serializable (either there diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java index 742a542efdd1..2fe062268112 100644 --- a/core/java/android/os/Process.java +++ b/core/java/android/os/Process.java @@ -132,6 +132,7 @@ public class Process { */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) @TestApi + @SystemApi(client = MODULE_LIBRARIES) public static final int NFC_UID = 1027; /** @@ -279,6 +280,26 @@ public class Process { public static final int LAST_APPLICATION_UID = 19999; /** + * Defines the start of a range of UIDs going from this number to + * {@link #LAST_SUPPLEMENTAL_UID} that are reserved for assigning to + * supplemental processes. There is a 1-1 mapping between a supplemental + * process UID and the app that it belongs to, which can be computed by + * subtracting (FIRST_SUPPLEMENTAL_UID - FIRST_APPLICATION_UID) from the + * uid of a supplemental process. + * + * Note that there are no GIDs associated with these processes; storage + * attribution for them will be done using project IDs. + * @hide + */ + public static final int FIRST_SUPPLEMENTAL_UID = 20000; + + /** + * Last UID that is used for supplemental processes. + * @hide + */ + public static final int LAST_SUPPLEMENTAL_UID = 29999; + + /** * First uid used for fully isolated sandboxed processes spawned from an app zygote * @hide */ @@ -880,6 +901,46 @@ public class Process { } /** + * Returns whether the provided UID belongs to a supplemental process. + * + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + public static final boolean isSupplemental(int uid) { + uid = UserHandle.getAppId(uid); + return (uid >= FIRST_SUPPLEMENTAL_UID && uid <= LAST_SUPPLEMENTAL_UID); + } + + /** + * + * Returns the app process corresponding to a supplemental process. + * + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + public static final int toAppUid(int uid) { + return uid - (FIRST_SUPPLEMENTAL_UID - FIRST_APPLICATION_UID); + } + + /** + * + * Returns the supplemental process corresponding to an app process. + * + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + public static final int toSupplementalUid(int uid) { + return uid + (FIRST_SUPPLEMENTAL_UID - FIRST_APPLICATION_UID); + } + + /** + * Returns whether the current process is a supplemental process. + */ + public static final boolean isSupplemental() { + return isSupplemental(myUid()); + } + + /** * Returns the UID assigned to a particular user name, or -1 if there is * none. If the given string consists of only numbers, it is converted * directly to a uid. diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java index ebbfe47c4417..70aaa5e52c44 100644 --- a/core/java/android/os/StrictMode.java +++ b/core/java/android/os/StrictMode.java @@ -2993,7 +2993,7 @@ public final class StrictMode { * should be removed. */ public ViolationInfo(Parcel in, boolean unsetGatheringBit) { - mViolation = (Violation) in.readSerializable(); + mViolation = (Violation) in.readSerializable(android.os.strictmode.Violation.class.getClassLoader(), android.os.strictmode.Violation.class); int binderStackSize = in.readInt(); for (int i = 0; i < binderStackSize; i++) { StackTraceElement[] traceElements = new StackTraceElement[in.readInt()]; diff --git a/core/java/android/os/SystemVibrator.java b/core/java/android/os/SystemVibrator.java index 003776175f26..0aafaf456a08 100644 --- a/core/java/android/os/SystemVibrator.java +++ b/core/java/android/os/SystemVibrator.java @@ -18,18 +18,25 @@ package android.os; import android.annotation.CallbackExecutor; import android.annotation.NonNull; +import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.util.ArrayMap; import android.util.Log; +import android.util.Range; +import android.util.Slog; import android.util.SparseArray; +import android.util.SparseBooleanArray; +import android.util.SparseIntArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import java.util.ArrayList; +import java.util.Arrays; import java.util.Objects; import java.util.concurrent.Executor; +import java.util.function.Function; /** * Vibrator implementation that controls the main system vibrator. @@ -51,7 +58,7 @@ public class SystemVibrator extends Vibrator { private final Object mLock = new Object(); @GuardedBy("mLock") - private AllVibratorsInfo mVibratorInfo; + private VibratorInfo mVibratorInfo; @UnsupportedAppUsage public SystemVibrator(Context context) { @@ -71,6 +78,11 @@ public class SystemVibrator extends Vibrator { return VibratorInfo.EMPTY_VIBRATOR_INFO; } int[] vibratorIds = mVibratorManager.getVibratorIds(); + if (vibratorIds.length == 0) { + // It is known that the device has no vibrator, so cache and return info that + // reflects the lack of support for effects/primitives. + return mVibratorInfo = new NoVibratorInfo(); + } VibratorInfo[] vibratorInfos = new VibratorInfo[vibratorIds.length]; for (int i = 0; i < vibratorIds.length; i++) { Vibrator vibrator = mVibratorManager.getVibrator(vibratorIds[i]); @@ -83,7 +95,12 @@ public class SystemVibrator extends Vibrator { } vibratorInfos[i] = vibrator.getInfo(); } - return mVibratorInfo = new AllVibratorsInfo(vibratorInfos); + if (vibratorInfos.length == 1) { + // Device has a single vibrator info, cache and return successfully loaded info. + return mVibratorInfo = new VibratorInfo(/* id= */ -1, vibratorInfos[0]); + } + // Device has multiple vibrators, generate a single info representing all of them. + return mVibratorInfo = new MultiVibratorInfo(vibratorInfos); } } @@ -257,76 +274,281 @@ public class SystemVibrator extends Vibrator { } /** - * Represents all the vibrators information as a single {@link VibratorInfo}. + * Represents a device with no vibrator as a single {@link VibratorInfo}. * - * <p>This uses the first vibrator on the list as the default one for all hardware spec, but - * uses an intersection of all vibrators to decide the capabilities and effect/primitive + * @hide + */ + @VisibleForTesting + public static class NoVibratorInfo extends VibratorInfo { + public NoVibratorInfo() { + // Use empty arrays to indicate no support, while null would indicate support unknown. + super(/* id= */ -1, + /* capabilities= */ 0, + /* supportedEffects= */ new SparseBooleanArray(), + /* supportedBraking= */ new SparseBooleanArray(), + /* supportedPrimitives= */ new SparseIntArray(), + /* primitiveDelayMax= */ 0, + /* compositionSizeMax= */ 0, + /* pwlePrimitiveDurationMax= */ 0, + /* pwleSizeMax= */ 0, + /* qFactor= */ Float.NaN, + new FrequencyProfile(/* resonantFrequencyHz= */ Float.NaN, + /* minFrequencyHz= */ Float.NaN, + /* frequencyResolutionHz= */ Float.NaN, + /* maxAmplitudes= */ null)); + } + } + + /** + * Represents multiple vibrator information as a single {@link VibratorInfo}. + * + * <p>This uses an intersection of all vibrators to decide the capabilities and effect/primitive * support. * * @hide */ @VisibleForTesting - public static class AllVibratorsInfo extends VibratorInfo { - private final VibratorInfo[] mVibratorInfos; + public static class MultiVibratorInfo extends VibratorInfo { + // Epsilon used for float comparison applied in calculations for the merged info. + private static final float EPSILON = 1e-5f; + + public MultiVibratorInfo(VibratorInfo[] vibrators) { + super(/* id= */ -1, + capabilitiesIntersection(vibrators), + supportedEffectsIntersection(vibrators), + supportedBrakingIntersection(vibrators), + supportedPrimitivesAndDurationsIntersection(vibrators), + integerLimitIntersection(vibrators, VibratorInfo::getPrimitiveDelayMax), + integerLimitIntersection(vibrators, VibratorInfo::getCompositionSizeMax), + integerLimitIntersection(vibrators, VibratorInfo::getPwlePrimitiveDurationMax), + integerLimitIntersection(vibrators, VibratorInfo::getPwleSizeMax), + floatPropertyIntersection(vibrators, VibratorInfo::getQFactor), + frequencyProfileIntersection(vibrators)); + } - public AllVibratorsInfo(VibratorInfo[] vibrators) { - super(/* id= */ -1, capabilitiesIntersection(vibrators), - vibrators.length > 0 ? vibrators[0] : VibratorInfo.EMPTY_VIBRATOR_INFO); - mVibratorInfos = vibrators; + private static int capabilitiesIntersection(VibratorInfo[] infos) { + int intersection = ~0; + for (VibratorInfo info : infos) { + intersection &= info.getCapabilities(); + } + return intersection; } - @Override - public int isEffectSupported(int effectId) { - if (mVibratorInfos.length == 0) { - return Vibrator.VIBRATION_EFFECT_SUPPORT_NO; + @Nullable + private static SparseBooleanArray supportedBrakingIntersection(VibratorInfo[] infos) { + for (VibratorInfo info : infos) { + if (!info.isBrakingSupportKnown()) { + // If one vibrator support is unknown, then the intersection is also unknown. + return null; + } } - int supported = Vibrator.VIBRATION_EFFECT_SUPPORT_YES; - for (VibratorInfo info : mVibratorInfos) { - int effectSupported = info.isEffectSupported(effectId); - if (effectSupported == Vibrator.VIBRATION_EFFECT_SUPPORT_NO) { - return effectSupported; - } else if (effectSupported == Vibrator.VIBRATION_EFFECT_SUPPORT_UNKNOWN) { - supported = effectSupported; + + SparseBooleanArray intersection = new SparseBooleanArray(); + SparseBooleanArray firstVibratorBraking = infos[0].getSupportedBraking(); + + brakingIdLoop: + for (int i = 0; i < firstVibratorBraking.size(); i++) { + int brakingId = firstVibratorBraking.keyAt(i); + if (!firstVibratorBraking.valueAt(i)) { + // The first vibrator already doesn't support this braking, so skip it. + continue brakingIdLoop; + } + + for (int j = 1; j < infos.length; j++) { + if (!infos[j].hasBrakingSupport(brakingId)) { + // One vibrator doesn't support this braking, so the intersection doesn't. + continue brakingIdLoop; + } } + + intersection.put(brakingId, true); } - return supported; + + return intersection; } - @Override - public boolean isPrimitiveSupported(int primitiveId) { - if (mVibratorInfos.length == 0) { - return false; + @Nullable + private static SparseBooleanArray supportedEffectsIntersection(VibratorInfo[] infos) { + for (VibratorInfo info : infos) { + if (!info.isEffectSupportKnown()) { + // If one vibrator support is unknown, then the intersection is also unknown. + return null; + } } - for (VibratorInfo info : mVibratorInfos) { - if (!info.isPrimitiveSupported(primitiveId)) { - return false; + + SparseBooleanArray intersection = new SparseBooleanArray(); + SparseBooleanArray firstVibratorEffects = infos[0].getSupportedEffects(); + + effectIdLoop: + for (int i = 0; i < firstVibratorEffects.size(); i++) { + int effectId = firstVibratorEffects.keyAt(i); + if (!firstVibratorEffects.valueAt(i)) { + // The first vibrator already doesn't support this effect, so skip it. + continue effectIdLoop; } + + for (int j = 1; j < infos.length; j++) { + if (infos[j].isEffectSupported(effectId) != VIBRATION_EFFECT_SUPPORT_YES) { + // One vibrator doesn't support this effect, so the intersection doesn't. + continue effectIdLoop; + } + } + + intersection.put(effectId, true); } - return true; + + return intersection; } - @Override - public int getPrimitiveDuration(int primitiveId) { - int maxDuration = 0; - for (VibratorInfo info : mVibratorInfos) { - int duration = info.getPrimitiveDuration(primitiveId); - if (duration == 0) { - return 0; + @NonNull + private static SparseIntArray supportedPrimitivesAndDurationsIntersection( + VibratorInfo[] infos) { + SparseIntArray intersection = new SparseIntArray(); + SparseIntArray firstVibratorPrimitives = infos[0].getSupportedPrimitives(); + + primitiveIdLoop: + for (int i = 0; i < firstVibratorPrimitives.size(); i++) { + int primitiveId = firstVibratorPrimitives.keyAt(i); + int primitiveDuration = firstVibratorPrimitives.valueAt(i); + if (primitiveDuration == 0) { + // The first vibrator already doesn't support this primitive, so skip it. + continue primitiveIdLoop; } - maxDuration = Math.max(maxDuration, duration); + + for (int j = 1; j < infos.length; j++) { + int vibratorPrimitiveDuration = infos[j].getPrimitiveDuration(primitiveId); + if (vibratorPrimitiveDuration == 0) { + // One vibrator doesn't support this primitive, so the intersection doesn't. + continue primitiveIdLoop; + } else { + // The primitive vibration duration is the maximum among all vibrators. + primitiveDuration = Math.max(primitiveDuration, vibratorPrimitiveDuration); + } + } + + intersection.put(primitiveId, primitiveDuration); } - return maxDuration; + return intersection; } - private static int capabilitiesIntersection(VibratorInfo[] infos) { - if (infos.length == 0) { - return 0; + private static int integerLimitIntersection(VibratorInfo[] infos, + Function<VibratorInfo, Integer> propertyGetter) { + int limit = 0; // Limit 0 means unlimited + for (VibratorInfo info : infos) { + int vibratorLimit = propertyGetter.apply(info); + if ((limit == 0) || (vibratorLimit > 0 && vibratorLimit < limit)) { + // This vibrator is limited and intersection is unlimited or has a larger limit: + // use smaller limit here for the intersection. + limit = vibratorLimit; + } } - int intersection = ~0; + return limit; + } + + private static float floatPropertyIntersection(VibratorInfo[] infos, + Function<VibratorInfo, Float> propertyGetter) { + float property = propertyGetter.apply(infos[0]); + if (Float.isNaN(property)) { + // If one vibrator is undefined then the intersection is undefined. + return Float.NaN; + } + for (int i = 1; i < infos.length; i++) { + if (Float.compare(property, propertyGetter.apply(infos[i])) != 0) { + // If one vibrator has a different value then the intersection is undefined. + return Float.NaN; + } + } + return property; + } + + @NonNull + private static FrequencyProfile frequencyProfileIntersection(VibratorInfo[] infos) { + float freqResolution = floatPropertyIntersection(infos, + info -> info.getFrequencyProfile().getFrequencyResolutionHz()); + float resonantFreq = floatPropertyIntersection(infos, + VibratorInfo::getResonantFrequencyHz); + Range<Float> freqRange = frequencyRangeIntersection(infos, freqResolution); + + if ((freqRange == null) || Float.isNaN(freqResolution)) { + return new FrequencyProfile(resonantFreq, Float.NaN, freqResolution, null); + } + + int amplitudeCount = + Math.round(1 + (freqRange.getUpper() - freqRange.getLower()) / freqResolution); + float[] maxAmplitudes = new float[amplitudeCount]; + + // Use MAX_VALUE here to ensure that the FrequencyProfile constructor called with this + // will fail if the loop below is broken and do not replace filled values with actual + // vibrator measurements. + Arrays.fill(maxAmplitudes, Float.MAX_VALUE); + for (VibratorInfo info : infos) { - intersection &= info.getCapabilities(); + Range<Float> vibratorFreqRange = info.getFrequencyProfile().getFrequencyRangeHz(); + float[] vibratorMaxAmplitudes = info.getFrequencyProfile().getMaxAmplitudes(); + int vibratorStartIdx = Math.round( + (freqRange.getLower() - vibratorFreqRange.getLower()) / freqResolution); + int vibratorEndIdx = vibratorStartIdx + maxAmplitudes.length - 1; + + if ((vibratorStartIdx < 0) || (vibratorEndIdx >= vibratorMaxAmplitudes.length)) { + Slog.w(TAG, "Error calculating the intersection of vibrator frequency" + + " profiles: attempted to fetch from vibrator " + + info.getId() + " max amplitude with bad index " + vibratorStartIdx); + return new FrequencyProfile(resonantFreq, Float.NaN, Float.NaN, null); + } + + for (int i = 0; i < maxAmplitudes.length; i++) { + maxAmplitudes[i] = Math.min(maxAmplitudes[i], + vibratorMaxAmplitudes[vibratorStartIdx + i]); + } } - return intersection; + + return new FrequencyProfile(resonantFreq, freqRange.getLower(), + freqResolution, maxAmplitudes); + } + + @Nullable + private static Range<Float> frequencyRangeIntersection(VibratorInfo[] infos, + float frequencyResolution) { + Range<Float> firstRange = infos[0].getFrequencyProfile().getFrequencyRangeHz(); + if (firstRange == null) { + // If one vibrator is undefined then the intersection is undefined. + return null; + } + float intersectionLower = firstRange.getLower(); + float intersectionUpper = firstRange.getUpper(); + + // Generate the intersection of all vibrator supported ranges, making sure that both + // min supported frequencies are aligned w.r.t. the frequency resolution. + + for (int i = 1; i < infos.length; i++) { + Range<Float> vibratorRange = infos[i].getFrequencyProfile().getFrequencyRangeHz(); + if (vibratorRange == null) { + // If one vibrator is undefined then the intersection is undefined. + return null; + } + + if ((vibratorRange.getLower() >= intersectionUpper) + || (vibratorRange.getUpper() <= intersectionLower)) { + // If the range and intersection are disjoint then the intersection is undefined + return null; + } + + float frequencyDelta = Math.abs(intersectionLower - vibratorRange.getLower()); + if ((frequencyDelta % frequencyResolution) > EPSILON) { + // If the intersection is not aligned with one vibrator then it's undefined + return null; + } + + intersectionLower = Math.max(intersectionLower, vibratorRange.getLower()); + intersectionUpper = Math.min(intersectionUpper, vibratorRange.getUpper()); + } + + if ((intersectionUpper - intersectionLower) < frequencyResolution) { + // If the intersection is empty then it's undefined. + return null; + } + + return Range.create(intersectionLower, intersectionUpper); } } diff --git a/core/java/android/os/TEST_MAPPING b/core/java/android/os/TEST_MAPPING index 07f40828eb56..22ddbccbaaae 100644 --- a/core/java/android/os/TEST_MAPPING +++ b/core/java/android/os/TEST_MAPPING @@ -31,15 +31,6 @@ ] }, { - "file_patterns": ["Environment\\.java"], - "name": "FrameworksServicesTests", - "options": [ - { - "include-filter": "com.android.server.pm.parsing.PackageInfoUserFieldsTest" - } - ] - }, - { "file_patterns": [ "BatteryStats[^/]*\\.java", "BatteryUsageStats[^/]*\\.java", diff --git a/core/java/android/os/Trace.java b/core/java/android/os/Trace.java index d974e0c0713a..6b869f13059d 100644 --- a/core/java/android/os/Trace.java +++ b/core/java/android/os/Trace.java @@ -16,7 +16,10 @@ package android.os; +import static android.annotation.SystemApi.Client.MODULE_LIBRARIES; + import android.annotation.NonNull; +import android.annotation.SystemApi; import android.compat.annotation.UnsupportedAppUsage; import dalvik.annotation.optimization.CriticalNative; @@ -90,6 +93,7 @@ public final class Trace { /** @hide */ public static final long TRACE_TAG_DATABASE = 1L << 20; /** @hide */ + @SystemApi(client = MODULE_LIBRARIES) public static final long TRACE_TAG_NETWORK = 1L << 21; /** @hide */ public static final long TRACE_TAG_ADB = 1L << 22; @@ -148,6 +152,7 @@ public final class Trace { * @hide */ @UnsupportedAppUsage + @SystemApi(client = MODULE_LIBRARIES) public static boolean isTagEnabled(long traceTag) { long tags = nativeGetEnabledTags(); return (tags & traceTag) != 0; @@ -163,7 +168,8 @@ public final class Trace { * @hide */ @UnsupportedAppUsage - public static void traceCounter(long traceTag, String counterName, int counterValue) { + @SystemApi(client = MODULE_LIBRARIES) + public static void traceCounter(long traceTag, @NonNull String counterName, int counterValue) { if (isTagEnabled(traceTag)) { nativeTraceCounter(traceTag, counterName, counterValue); } @@ -202,7 +208,8 @@ public final class Trace { * @hide */ @UnsupportedAppUsage - public static void traceBegin(long traceTag, String methodName) { + @SystemApi(client = MODULE_LIBRARIES) + public static void traceBegin(long traceTag, @NonNull String methodName) { if (isTagEnabled(traceTag)) { nativeTraceBegin(traceTag, methodName); } @@ -217,6 +224,7 @@ public final class Trace { * @hide */ @UnsupportedAppUsage + @SystemApi(client = MODULE_LIBRARIES) public static void traceEnd(long traceTag) { if (isTagEnabled(traceTag)) { nativeTraceEnd(traceTag); @@ -237,7 +245,8 @@ public final class Trace { * @hide */ @UnsupportedAppUsage - public static void asyncTraceBegin(long traceTag, String methodName, int cookie) { + @SystemApi(client = MODULE_LIBRARIES) + public static void asyncTraceBegin(long traceTag, @NonNull String methodName, int cookie) { if (isTagEnabled(traceTag)) { nativeAsyncTraceBegin(traceTag, methodName, cookie); } @@ -255,7 +264,8 @@ public final class Trace { * @hide */ @UnsupportedAppUsage - public static void asyncTraceEnd(long traceTag, String methodName, int cookie) { + @SystemApi(client = MODULE_LIBRARIES) + public static void asyncTraceEnd(long traceTag, @NonNull String methodName, int cookie) { if (isTagEnabled(traceTag)) { nativeAsyncTraceEnd(traceTag, methodName, cookie); } diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index 190f5f127f3d..f18c9c92889e 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -16,6 +16,9 @@ package android.os; +import static android.app.admin.DevicePolicyResources.Strings.Core.WORK_PROFILE_BADGED_LABEL; +import static android.app.admin.DevicePolicyResources.Strings.UNDEFINED; + import android.Manifest; import android.accounts.AccountManager; import android.annotation.ColorInt; @@ -820,6 +823,20 @@ public class UserManager { public static final String DISALLOW_ADD_MANAGED_PROFILE = "no_add_managed_profile"; /** + * Specifies if a user is disallowed from creating clone profile. + * <p>The default value for an unmanaged user is <code>false</code>. + * For users with a device owner set, the default is <code>true</code>. + * + * <p>Key for user restrictions. + * <p>Type: Boolean + * @see DevicePolicyManager#addUserRestriction(ComponentName, String) + * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) + * @see #getUserRestrictions() + * @hide + */ + public static final String DISALLOW_ADD_CLONE_PROFILE = "no_add_clone_profile"; + + /** * Specifies if a user is disallowed from disabling application verification. The default * value is <code>false</code>. * @@ -1497,6 +1514,7 @@ public class UserManager { DISALLOW_FACTORY_RESET, DISALLOW_ADD_USER, DISALLOW_ADD_MANAGED_PROFILE, + DISALLOW_ADD_CLONE_PROFILE, ENSURE_VERIFY_APPS, DISALLOW_CONFIG_CELL_BROADCASTS, DISALLOW_CONFIG_MOBILE_NETWORKS, @@ -4645,6 +4663,18 @@ public class UserManager { if (!hasBadge(userId)) { return label; } + DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class); + return dpm.getString( + getUpdatableUserBadgedLabelId(userId), + () -> getDefaultUserBadgedLabel(label, userId), + /* formatArgs= */ label); + } + + private String getUpdatableUserBadgedLabelId(int userId) { + return isManagedProfile(userId) ? WORK_PROFILE_BADGED_LABEL : UNDEFINED; + } + + private String getDefaultUserBadgedLabel(CharSequence label, int userId) { try { final int resourceId = mService.getUserBadgeLabelResId(userId); return Resources.getSystem().getString(resourceId, label); diff --git a/core/java/android/os/VibrationAttributes.java b/core/java/android/os/VibrationAttributes.java index 8834725cc3e9..8bc219b7dc57 100644 --- a/core/java/android/os/VibrationAttributes.java +++ b/core/java/android/os/VibrationAttributes.java @@ -136,6 +136,7 @@ public final class VibrationAttributes implements Parcelable { */ @IntDef(prefix = { "FLAG_" }, flag = true, value = { FLAG_BYPASS_INTERRUPTION_POLICY, + FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF }) @Retention(RetentionPolicy.SOURCE) public @interface Flag{} @@ -146,10 +147,22 @@ public final class VibrationAttributes implements Parcelable { public static final int FLAG_BYPASS_INTERRUPTION_POLICY = 0x1; /** + * Flag requesting vibration effect to be played even when user settings are disabling it. + * + * <p>Flag introduced to represent + * {@link android.view.HapticFeedbackConstants#FLAG_IGNORE_GLOBAL_SETTING} and + * {@link AudioAttributes#FLAG_BYPASS_MUTE}. + * + * @hide + */ + public static final int FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF = 0x2; + + /** * All flags supported by vibrator service, update it when adding new flag. * @hide */ - public static final int FLAG_ALL_SUPPORTED = FLAG_BYPASS_INTERRUPTION_POLICY; + public static final int FLAG_ALL_SUPPORTED = + FLAG_BYPASS_INTERRUPTION_POLICY | FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF; /** Creates a new {@link VibrationAttributes} instance with given usage. */ public static @NonNull VibrationAttributes createForUsage(int usage) { @@ -397,6 +410,11 @@ public final class VibrationAttributes implements Parcelable { if ((audio.getAllFlags() & AudioAttributes.FLAG_BYPASS_INTERRUPTION_POLICY) != 0) { mFlags |= FLAG_BYPASS_INTERRUPTION_POLICY; } + if ((audio.getAllFlags() & AudioAttributes.FLAG_BYPASS_MUTE) != 0) { + // Muted audio stream translates to vibration usage having the value + // Vibrator.VIBRATION_INTENSITY_OFF set in the user setting. + mFlags |= FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF; + } } /** diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java index 5de455695c01..ae37a714e0c8 100644 --- a/core/java/android/os/VibrationEffect.java +++ b/core/java/android/os/VibrationEffect.java @@ -576,7 +576,7 @@ public abstract class VibrationEffect implements Parcelable { private final int mRepeatIndex; Composed(@NonNull Parcel in) { - this(in.readArrayList(VibrationEffectSegment.class.getClassLoader()), in.readInt()); + this(in.readArrayList(VibrationEffectSegment.class.getClassLoader(), android.os.vibrator.VibrationEffectSegment.class), in.readInt()); } Composed(@NonNull VibrationEffectSegment segment) { diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java index eba9ff1bb4f8..23baa5d70c66 100644 --- a/core/java/android/os/Vibrator.java +++ b/core/java/android/os/Vibrator.java @@ -31,6 +31,7 @@ import android.content.res.Resources; import android.hardware.vibrator.IVibrator; import android.media.AudioAttributes; import android.os.vibrator.VibrationConfig; +import android.os.vibrator.VibratorFrequencyProfile; import android.util.Log; import java.lang.annotation.Retention; @@ -208,8 +209,8 @@ public abstract class Vibrator { /** * Check whether the vibrator has independent frequency control. * - * @return True if the hardware can control the frequency of the vibrations, otherwise false. - * @hide + * @return True if the hardware can control the frequency of the vibrations independently of + * the vibration amplitude, false otherwise. */ public boolean hasFrequencyControl() { // We currently can only control frequency of the vibration using the compose PWLE method. @@ -229,28 +230,48 @@ public abstract class Vibrator { } /** - * Gets the resonant frequency of the vibrator. + * Gets the resonant frequency of the vibrator, if applicable. * - * @return the resonant frequency of the vibrator, or {@link Float#NaN NaN} if it's unknown or - * this vibrator is a composite of multiple physical devices. - * @hide + * @return the resonant frequency of the vibrator, or {@link Float#NaN NaN} if it's unknown, not + * applicable, or if this vibrator is a composite of multiple physical devices with different + * frequencies. */ public float getResonantFrequency() { - return getInfo().getResonantFrequency(); + return getInfo().getResonantFrequencyHz(); } /** * Gets the <a href="https://en.wikipedia.org/wiki/Q_factor">Q factor</a> of the vibrator. * - * @return the Q factor of the vibrator, or {@link Float#NaN NaN} if it's unknown or - * this vibrator is a composite of multiple physical devices. - * @hide + * @return the Q factor of the vibrator, or {@link Float#NaN NaN} if it's unknown, not + * applicable, or if this vibrator is a composite of multiple physical devices with different + * Q factors. */ public float getQFactor() { return getInfo().getQFactor(); } /** + * Gets the profile that describes the vibrator output across the supported frequency range. + * + * <p>The profile describes the relative output acceleration that the device can reach when it + * vibrates at different frequencies. + * + * @return The frequency profile for this vibrator, or null if the vibrator does not have + * frequency control. If this vibrator is a composite of multiple physical devices then this + * will return a profile supported in all devices, or null if the intersection is empty or not + * available. + */ + @Nullable + public VibratorFrequencyProfile getFrequencyProfile() { + VibratorInfo.FrequencyProfile frequencyProfile = getInfo().getFrequencyProfile(); + if (frequencyProfile.isEmpty()) { + return null; + } + return new VibratorFrequencyProfile(frequencyProfile); + } + + /** * Return the maximum amplitude the vibrator can play using the audio haptic channels. * * <p>This is a positive value, or {@link Float#NaN NaN} if it's unknown. If this returns a diff --git a/core/java/android/os/VibratorInfo.java b/core/java/android/os/VibratorInfo.java index 189e454f1488..00ce14fdfd28 100644 --- a/core/java/android/os/VibratorInfo.java +++ b/core/java/android/os/VibratorInfo.java @@ -16,7 +16,6 @@ package android.os; -import android.annotation.FloatRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.hardware.vibrator.Braking; @@ -26,6 +25,8 @@ import android.util.Range; import android.util.SparseBooleanArray; import android.util.SparseIntArray; +import com.android.internal.util.Preconditions; + import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -56,7 +57,7 @@ public class VibratorInfo implements Parcelable { private final int mPwlePrimitiveDurationMax; private final int mPwleSizeMax; private final float mQFactor; - private final FrequencyMapping mFrequencyMapping; + private final FrequencyProfile mFrequencyProfile; VibratorInfo(Parcel in) { mId = in.readInt(); @@ -69,7 +70,15 @@ public class VibratorInfo implements Parcelable { mPwlePrimitiveDurationMax = in.readInt(); mPwleSizeMax = in.readInt(); mQFactor = in.readFloat(); - mFrequencyMapping = in.readParcelable(VibratorInfo.class.getClassLoader()); + mFrequencyProfile = FrequencyProfile.CREATOR.createFromParcel(in); + } + + public VibratorInfo(int id, @NonNull VibratorInfo baseVibratorInfo) { + this(id, baseVibratorInfo.mCapabilities, baseVibratorInfo.mSupportedEffects, + baseVibratorInfo.mSupportedBraking, baseVibratorInfo.mSupportedPrimitives, + baseVibratorInfo.mPrimitiveDelayMax, baseVibratorInfo.mCompositionSizeMax, + baseVibratorInfo.mPwlePrimitiveDurationMax, baseVibratorInfo.mPwleSizeMax, + baseVibratorInfo.mQFactor, baseVibratorInfo.mFrequencyProfile); } /** @@ -92,7 +101,7 @@ public class VibratorInfo implements Parcelable { * @param pwleSizeMax The maximum number of primitives supported by a PWLE * composition. * @param qFactor The vibrator quality factor. - * @param frequencyMapping The description of the vibrator supported frequencies and max + * @param frequencyProfile The description of the vibrator supported frequencies and max * amplitude mappings. * @hide */ @@ -100,7 +109,9 @@ public class VibratorInfo implements Parcelable { @Nullable SparseBooleanArray supportedBraking, @NonNull SparseIntArray supportedPrimitives, int primitiveDelayMax, int compositionSizeMax, int pwlePrimitiveDurationMax, int pwleSizeMax, - float qFactor, @NonNull FrequencyMapping frequencyMapping) { + float qFactor, @NonNull FrequencyProfile frequencyProfile) { + Preconditions.checkNotNull(supportedPrimitives); + Preconditions.checkNotNull(frequencyProfile); mId = id; mCapabilities = capabilities; mSupportedEffects = supportedEffects == null ? null : supportedEffects.clone(); @@ -111,14 +122,7 @@ public class VibratorInfo implements Parcelable { mPwlePrimitiveDurationMax = pwlePrimitiveDurationMax; mPwleSizeMax = pwleSizeMax; mQFactor = qFactor; - mFrequencyMapping = frequencyMapping; - } - - protected VibratorInfo(int id, int capabilities, VibratorInfo baseVibrator) { - this(id, capabilities, baseVibrator.mSupportedEffects, baseVibrator.mSupportedBraking, - baseVibrator.mSupportedPrimitives, baseVibrator.mPrimitiveDelayMax, - baseVibrator.mCompositionSizeMax, baseVibrator.mPwlePrimitiveDurationMax, - baseVibrator.mPwleSizeMax, baseVibrator.mQFactor, baseVibrator.mFrequencyMapping); + mFrequencyProfile = frequencyProfile; } @Override @@ -133,7 +137,7 @@ public class VibratorInfo implements Parcelable { dest.writeInt(mPwlePrimitiveDurationMax); dest.writeInt(mPwleSizeMax); dest.writeFloat(mQFactor); - dest.writeParcelable(mFrequencyMapping, flags); + mFrequencyProfile.writeToParcel(dest, flags); } @Override @@ -170,13 +174,13 @@ public class VibratorInfo implements Parcelable { && Objects.equals(mSupportedEffects, that.mSupportedEffects) && Objects.equals(mSupportedBraking, that.mSupportedBraking) && Objects.equals(mQFactor, that.mQFactor) - && Objects.equals(mFrequencyMapping, that.mFrequencyMapping); + && Objects.equals(mFrequencyProfile, that.mFrequencyProfile); } @Override public int hashCode() { int hashCode = Objects.hash(mId, mCapabilities, mSupportedEffects, mSupportedBraking, - mQFactor, mFrequencyMapping); + mQFactor, mFrequencyProfile); for (int i = 0; i < mSupportedPrimitives.size(); i++) { hashCode = 31 * hashCode + mSupportedPrimitives.keyAt(i); hashCode = 31 * hashCode + mSupportedPrimitives.valueAt(i); @@ -198,7 +202,7 @@ public class VibratorInfo implements Parcelable { + ", mPwlePrimitiveDurationMax=" + mPwlePrimitiveDurationMax + ", mPwleSizeMax=" + mPwleSizeMax + ", mQFactor=" + mQFactor - + ", mFrequencyMapping=" + mFrequencyMapping + + ", mFrequencyProfile=" + mFrequencyProfile + '}'; } @@ -234,6 +238,30 @@ public class VibratorInfo implements Parcelable { return Braking.NONE; } + /** @hide */ + @Nullable + public SparseBooleanArray getSupportedBraking() { + if (mSupportedBraking == null) { + return null; + } + return mSupportedBraking.clone(); + } + + /** @hide */ + public boolean isBrakingSupportKnown() { + return mSupportedBraking != null; + } + + /** @hide */ + public boolean hasBrakingSupport(@Braking int braking) { + return (mSupportedBraking != null) && mSupportedBraking.get(braking); + } + + /** @hide */ + public boolean isEffectSupportKnown() { + return mSupportedEffects != null; + } + /** * Query whether the vibrator supports the given effect. * @@ -252,6 +280,15 @@ public class VibratorInfo implements Parcelable { : Vibrator.VIBRATION_EFFECT_SUPPORT_NO; } + /** @hide */ + @Nullable + public SparseBooleanArray getSupportedEffects() { + if (mSupportedEffects == null) { + return null; + } + return mSupportedEffects.clone(); + } + /** * Query whether the vibrator supports the given primitive. * @@ -276,6 +313,11 @@ public class VibratorInfo implements Parcelable { return mSupportedPrimitives.get(primitiveId); } + /** @hide */ + public SparseIntArray getSupportedPrimitives() { + return mSupportedPrimitives.clone(); + } + /** * Query the maximum delay supported for a primitive in a composed effect. * @@ -329,8 +371,8 @@ public class VibratorInfo implements Parcelable { * @return the resonant frequency of the vibrator, or {@link Float#NaN NaN} if it's unknown or * this vibrator is a composite of multiple physical devices. */ - public float getResonantFrequency() { - return mFrequencyMapping.mResonantFrequencyHz; + public float getResonantFrequencyHz() { + return mFrequencyProfile.mResonantFrequencyHz; } /** @@ -344,31 +386,14 @@ public class VibratorInfo implements Parcelable { } /** - * Return a range of frequency values supported by the vibrator. + * Gets the profile of supported frequencies, including the measurements of maximum relative + * output acceleration for supported vibration frequencies. * - * @return A range of frequency values supported, in hertz. The range will always contain the - * device resonant frequency. Devices without frequency control will return null. - * @hide - */ - @Nullable - public Range<Float> getFrequencyRangeHz() { - return mFrequencyMapping.mFrequencyRangeHz; - } - - /** - * Return the maximum amplitude the vibrator can play at given frequency. - * - * @param frequencyHz The frequency, in hertz, for query. - - * @return a value in [0,1] representing the maximum amplitude the device can play at given - * frequency. Devices without frequency control will return 0 to any input. Devices with - * frequency control will return the supported value, for input in - * {@link #getFrequencyRangeHz()}, and 0 for any other input. - * @hide + * <p>If the devices does not have frequency control then the profile should be empty. */ - @FloatRange(from = 0, to = 1) - public float getMaxAmplitude(float frequencyHz) { - return mFrequencyMapping.getMaxAmplitude(frequencyHz); + @NonNull + public FrequencyProfile getFrequencyProfile() { + return mFrequencyProfile; } protected long getCapabilities() { @@ -452,7 +477,7 @@ public class VibratorInfo implements Parcelable { * Describes the maximum relative output acceleration that can be achieved for each supported * frequency in a specific vibrator. * - * <p>This mapping is defined by the following parameters: + * <p>This profile is defined by the following parameters: * * <ol> * <li>{@code minFrequencyHz}, {@code resonantFrequencyHz} and {@code frequencyResolutionHz} @@ -466,7 +491,7 @@ public class VibratorInfo implements Parcelable { * * @hide */ - public static final class FrequencyMapping implements Parcelable { + public static final class FrequencyProfile implements Parcelable { @Nullable private final Range<Float> mFrequencyRangeHz; private final float mMinFrequencyHz; @@ -474,7 +499,7 @@ public class VibratorInfo implements Parcelable { private final float mFrequencyResolutionHz; private final float[] mMaxAmplitudes; - FrequencyMapping(Parcel in) { + FrequencyProfile(Parcel in) { this(in.readFloat(), in.readFloat(), in.readFloat(), in.createFloatArray()); } @@ -484,13 +509,13 @@ public class VibratorInfo implements Parcelable { * @param resonantFrequencyHz The vibrator resonant frequency, in hertz. * @param minFrequencyHz Minimum supported frequency, in hertz. * @param frequencyResolutionHz The frequency resolution, in hertz, used by the max - * amplitudes mapping. + * amplitude measurements. * @param maxAmplitudes The max amplitude supported by each supported frequency, * starting at minimum frequency with jumps of frequency * resolution. * @hide */ - public FrequencyMapping(float resonantFrequencyHz, float minFrequencyHz, + public FrequencyProfile(float resonantFrequencyHz, float minFrequencyHz, float frequencyResolutionHz, float[] maxAmplitudes) { mMinFrequencyHz = minFrequencyHz; mResonantFrequencyHz = resonantFrequencyHz; @@ -500,18 +525,25 @@ public class VibratorInfo implements Parcelable { System.arraycopy(maxAmplitudes, 0, mMaxAmplitudes, 0, maxAmplitudes.length); } - // If any required field is undefined then leave this mapping empty. + // If any required field is undefined or has a bad value then this profile is invalid. boolean isValid = !Float.isNaN(resonantFrequencyHz) + && (resonantFrequencyHz > 0) && !Float.isNaN(minFrequencyHz) + && (minFrequencyHz > 0) && !Float.isNaN(frequencyResolutionHz) + && (frequencyResolutionHz > 0) && (mMaxAmplitudes.length > 0); + // If any max amplitude is outside the allowed range then this profile is invalid. + for (int i = 0; i < mMaxAmplitudes.length; i++) { + isValid &= (mMaxAmplitudes[i] >= 0) && (mMaxAmplitudes[i] <= 1); + } + float maxFrequencyHz = isValid ? minFrequencyHz + frequencyResolutionHz * (mMaxAmplitudes.length - 1) : Float.NaN; - // If the non-empty mapping does not have min < resonant < max frequency respected - // then leave this mapping empty. + // If the constraint min < resonant < max is not met then it is invalid. isValid &= !Float.isNaN(maxFrequencyHz) && (resonantFrequencyHz >= minFrequencyHz) && (resonantFrequencyHz <= maxFrequencyHz) @@ -520,14 +552,17 @@ public class VibratorInfo implements Parcelable { mFrequencyRangeHz = isValid ? Range.create(minFrequencyHz, maxFrequencyHz) : null; } - /** - * Returns true if this frequency mapping is empty, i.e. the only supported is the resonant - * frequency. - */ + /** Returns true if the supported frequency range is empty. */ public boolean isEmpty() { return mFrequencyRangeHz == null; } + /** Returns the supported frequency range, in hertz. */ + @Nullable + public Range<Float> getFrequencyRangeHz() { + return mFrequencyRangeHz; + } + /** * Returns the maximum relative amplitude the vibrator can reach while playing at the * given frequency. @@ -535,24 +570,43 @@ public class VibratorInfo implements Parcelable { * @param frequencyHz frequency, in hertz, for query. * @return A value in [0,1] representing the max relative amplitude supported at the given * frequency. This will return 0 if the frequency is outside the supported range, or if the - * mapping is empty. + * supported frequency range is empty. */ public float getMaxAmplitude(float frequencyHz) { - if (isEmpty() || Float.isNaN(frequencyHz)) { + if (isEmpty() || Float.isNaN(frequencyHz) || !mFrequencyRangeHz.contains(frequencyHz)) { // Unsupported frequency requested, vibrator cannot play at this frequency. return 0; } - float position = (frequencyHz - mMinFrequencyHz) / mFrequencyResolutionHz; - int floorIndex = (int) Math.floor(position); - int ceilIndex = (int) Math.ceil(position); - if (floorIndex < 0 || floorIndex >= mMaxAmplitudes.length) { - return 0; - } - if (floorIndex != ceilIndex && ceilIndex < mMaxAmplitudes.length) { - // Value in between two mapped frequency values, use the lowest supported one. - return MathUtils.min(mMaxAmplitudes[floorIndex], mMaxAmplitudes[ceilIndex]); - } - return mMaxAmplitudes[floorIndex]; + + // Subtract minFrequencyHz to simplify offset calculations. + float mappingFreq = frequencyHz - mMinFrequencyHz; + + // Find the bucket to interpolate within. + // Any calculated index should be safe, except exactly equal to max amplitude can be + // one step too high, so constrain it to guarantee safety. + int startIdx = MathUtils.constrain( + /* amount= */ (int) Math.floor(mappingFreq / mFrequencyResolutionHz), + /* low= */ 0, /* high= */ mMaxAmplitudes.length - 1); + int nextIdx = MathUtils.constrain( + /* amount= */ startIdx + 1, + /* low= */ 0, /* high= */ mMaxAmplitudes.length - 1); + + // Linearly interpolate the amplitudes based on the frequency range of the bucket. + return MathUtils.constrainedMap( + mMaxAmplitudes[startIdx], mMaxAmplitudes[nextIdx], + startIdx * mFrequencyResolutionHz, nextIdx * mFrequencyResolutionHz, + mappingFreq); + } + + /** Returns the raw list of maximum relative output accelerations from the vibrator. */ + @NonNull + public float[] getMaxAmplitudes() { + return Arrays.copyOf(mMaxAmplitudes, mMaxAmplitudes.length); + } + + /** Returns the raw frequency resolution used for max amplitude measurements, in hertz. */ + public float getFrequencyResolutionHz() { + return mFrequencyResolutionHz; } @Override @@ -573,10 +627,10 @@ public class VibratorInfo implements Parcelable { if (this == o) { return true; } - if (!(o instanceof FrequencyMapping)) { + if (!(o instanceof FrequencyProfile)) { return false; } - FrequencyMapping that = (FrequencyMapping) o; + FrequencyProfile that = (FrequencyProfile) o; return Float.compare(mMinFrequencyHz, that.mMinFrequencyHz) == 0 && Float.compare(mResonantFrequencyHz, that.mResonantFrequencyHz) == 0 && Float.compare(mFrequencyResolutionHz, that.mFrequencyResolutionHz) == 0 @@ -593,7 +647,7 @@ public class VibratorInfo implements Parcelable { @Override public String toString() { - return "FrequencyMapping{" + return "FrequencyProfile{" + "mFrequencyRange=" + mFrequencyRangeHz + ", mMinFrequency=" + mMinFrequencyHz + ", mResonantFrequency=" + mResonantFrequencyHz @@ -603,16 +657,16 @@ public class VibratorInfo implements Parcelable { } @NonNull - public static final Creator<FrequencyMapping> CREATOR = - new Creator<FrequencyMapping>() { + public static final Creator<FrequencyProfile> CREATOR = + new Creator<FrequencyProfile>() { @Override - public FrequencyMapping createFromParcel(Parcel in) { - return new FrequencyMapping(in); + public FrequencyProfile createFromParcel(Parcel in) { + return new FrequencyProfile(in); } @Override - public FrequencyMapping[] newArray(int size) { - return new FrequencyMapping[size]; + public FrequencyProfile[] newArray(int size) { + return new FrequencyProfile[size]; } }; } @@ -629,8 +683,8 @@ public class VibratorInfo implements Parcelable { private int mPwlePrimitiveDurationMax; private int mPwleSizeMax; private float mQFactor = Float.NaN; - private FrequencyMapping mFrequencyMapping = - new FrequencyMapping(Float.NaN, Float.NaN, Float.NaN, null); + private FrequencyProfile mFrequencyProfile = + new FrequencyProfile(Float.NaN, Float.NaN, Float.NaN, null); /** A builder class for a {@link VibratorInfo}. */ public Builder(int id) { @@ -702,8 +756,8 @@ public class VibratorInfo implements Parcelable { /** Configure the vibrator frequency information like resonant frequency and bandwidth. */ @NonNull - public Builder setFrequencyMapping(FrequencyMapping frequencyMapping) { - mFrequencyMapping = frequencyMapping; + public Builder setFrequencyProfile(@NonNull FrequencyProfile frequencyProfile) { + mFrequencyProfile = frequencyProfile; return this; } @@ -712,7 +766,7 @@ public class VibratorInfo implements Parcelable { public VibratorInfo build() { return new VibratorInfo(mId, mCapabilities, mSupportedEffects, mSupportedBraking, mSupportedPrimitives, mPrimitiveDelayMax, mCompositionSizeMax, - mPwlePrimitiveDurationMax, mPwleSizeMax, mQFactor, mFrequencyMapping); + mPwlePrimitiveDurationMax, mPwleSizeMax, mQFactor, mFrequencyProfile); } /** diff --git a/core/java/android/os/WorkSource.java b/core/java/android/os/WorkSource.java index 6588b5748d09..e899f7729efa 100644 --- a/core/java/android/os/WorkSource.java +++ b/core/java/android/os/WorkSource.java @@ -130,7 +130,7 @@ public class WorkSource implements Parcelable { int numChains = in.readInt(); if (numChains > 0) { mChains = new ArrayList<>(numChains); - in.readParcelableList(mChains, WorkChain.class.getClassLoader()); + in.readParcelableList(mChains, WorkChain.class.getClassLoader(), android.os.WorkSource.WorkChain.class); } else { mChains = null; } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ISmartspaceTransitionController.aidl b/core/java/android/os/logcat/ILogcatManagerService.aidl index 2b3e961ab3b2..68b5679919d6 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ISmartspaceTransitionController.aidl +++ b/core/java/android/os/logcat/ILogcatManagerService.aidl @@ -14,11 +14,13 @@ * limitations under the License. */ -package com.android.systemui.shared.system.smartspace; +package android.os.logcat; -import com.android.systemui.shared.system.smartspace.ISmartspaceCallback; +/** + * @hide + */ +interface ILogcatManagerService { + void startThread(in int uid, in int gid, in int pid, in int fd); + void finishThread(in int uid, in int gid, in int pid, in int fd); +} -// Controller that keeps track of SmartSpace instances in remote processes (such as Launcher). -interface ISmartspaceTransitionController { - oneway void setSmartspace(ISmartspaceCallback callback); -}
\ No newline at end of file diff --git a/core/java/android/os/logcat/OWNERS b/core/java/android/os/logcat/OWNERS new file mode 100644 index 000000000000..cb21a6fafd7e --- /dev/null +++ b/core/java/android/os/logcat/OWNERS @@ -0,0 +1 @@ +include platform/frameworks/base:/services/core/java/com/android/server/logcat/OWNERS diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java index 29accb9b3187..8df659de5924 100644 --- a/core/java/android/os/storage/StorageManager.java +++ b/core/java/android/os/storage/StorageManager.java @@ -80,6 +80,7 @@ import android.os.ServiceManager; import android.os.ServiceManager.ServiceNotFoundException; import android.os.SystemProperties; import android.os.UserHandle; +import android.provider.DeviceConfig; import android.provider.MediaStore; import android.provider.Settings; import android.sysprop.VoldProperties; @@ -1443,28 +1444,39 @@ public class StorageManager { * * @hide */ - public static final int STORAGE_THRESHOLD_PERCENT_HIGH = 20; + public static final int DEFAULT_STORAGE_THRESHOLD_PERCENT_HIGH = 20; + /** {@hide} */ + @TestApi + public static final String + STORAGE_THRESHOLD_PERCENT_HIGH_KEY = "storage_threshold_percent_high"; /** * Devices having below STORAGE_THRESHOLD_PERCENT_LOW of total space free are considered to be - * in low free space category. + * in low free space category and can be configured via + * Settings.Global.SYS_STORAGE_THRESHOLD_PERCENTAGE. * * @hide */ - public static final int STORAGE_THRESHOLD_PERCENT_LOW = 5; + public static final int DEFAULT_STORAGE_THRESHOLD_PERCENT_LOW = 5; /** * For devices in high free space category, CACHE_RESERVE_PERCENT_HIGH percent of total space is * allocated for cache. * * @hide */ - public static final int CACHE_RESERVE_PERCENT_HIGH = 10; + public static final int DEFAULT_CACHE_RESERVE_PERCENT_HIGH = 10; + /** {@hide} */ + @TestApi + public static final String CACHE_RESERVE_PERCENT_HIGH_KEY = "cache_reserve_percent_high"; /** * For devices in low free space category, CACHE_RESERVE_PERCENT_LOW percent of total space is * allocated for cache. * * @hide */ - public static final int CACHE_RESERVE_PERCENT_LOW = 2; + public static final int DEFAULT_CACHE_RESERVE_PERCENT_LOW = 2; + /** {@hide} */ + @TestApi + public static final String CACHE_RESERVE_PERCENT_LOW_KEY = "cache_reserve_percent_low"; private static final long DEFAULT_THRESHOLD_MAX_BYTES = DataUnit.MEBIBYTES.toBytes(500); @@ -1490,7 +1502,8 @@ public class StorageManager { @UnsupportedAppUsage public long getStorageLowBytes(File path) { final long lowPercent = Settings.Global.getInt(mResolver, - Settings.Global.SYS_STORAGE_THRESHOLD_PERCENTAGE, STORAGE_THRESHOLD_PERCENT_LOW); + Settings.Global.SYS_STORAGE_THRESHOLD_PERCENTAGE, + DEFAULT_STORAGE_THRESHOLD_PERCENT_LOW); final long lowBytes = (path.getTotalSpace() * lowPercent) / 100; final long maxLowBytes = Settings.Global.getLong(mResolver, @@ -1510,24 +1523,33 @@ public class StorageManager { @TestApi @SuppressLint("StreamFiles") public long computeStorageCacheBytes(@NonNull File path) { + final int storageThresholdPercentHigh = DeviceConfig.getInt( + DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT, + STORAGE_THRESHOLD_PERCENT_HIGH_KEY, DEFAULT_STORAGE_THRESHOLD_PERCENT_HIGH); + final int cacheReservePercentHigh = DeviceConfig.getInt( + DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT, + CACHE_RESERVE_PERCENT_HIGH_KEY, DEFAULT_CACHE_RESERVE_PERCENT_HIGH); + final int cacheReservePercentLow = DeviceConfig.getInt( + DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT, + CACHE_RESERVE_PERCENT_LOW_KEY, DEFAULT_CACHE_RESERVE_PERCENT_LOW); final long totalBytes = path.getTotalSpace(); final long usableBytes = path.getUsableSpace(); - final long storageThresholdHighBytes = totalBytes * STORAGE_THRESHOLD_PERCENT_HIGH / 100; + final long storageThresholdHighBytes = totalBytes * storageThresholdPercentHigh / 100; final long storageThresholdLowBytes = getStorageLowBytes(path); long result; if (usableBytes > storageThresholdHighBytes) { - // If free space is >STORAGE_THRESHOLD_PERCENT_HIGH of total space, - // reserve CACHE_RESERVE_PERCENT_HIGH of total space - result = totalBytes * CACHE_RESERVE_PERCENT_HIGH / 100; + // If free space is >storageThresholdPercentHigh of total space, + // reserve cacheReservePercentHigh of total space + result = totalBytes * cacheReservePercentHigh / 100; } else if (usableBytes < storageThresholdLowBytes) { - // If free space is <min(STORAGE_THRESHOLD_PERCENT_LOW of total space, 500MB), - // reserve CACHE_RESERVE_PERCENT_LOW of total space - result = totalBytes * CACHE_RESERVE_PERCENT_LOW / 100; + // If free space is <min(storageThresholdPercentLow of total space, 500MB), + // reserve cacheReservePercentLow of total space + result = totalBytes * cacheReservePercentLow / 100; } else { // Else, linearly interpolate the amount of space to reserve - double slope = (CACHE_RESERVE_PERCENT_HIGH - CACHE_RESERVE_PERCENT_LOW) * totalBytes + double slope = (cacheReservePercentHigh - cacheReservePercentLow) * totalBytes / (100.0 * (storageThresholdHighBytes - storageThresholdLowBytes)); - double intercept = totalBytes * CACHE_RESERVE_PERCENT_LOW / 100.0 + double intercept = totalBytes * cacheReservePercentLow / 100.0 - storageThresholdLowBytes * slope; result = Math.round(slope * usableBytes + intercept); } diff --git a/core/java/android/os/storage/StorageVolume.java b/core/java/android/os/storage/StorageVolume.java index b78bb253bcf7..8ee52c21e869 100644 --- a/core/java/android/os/storage/StorageVolume.java +++ b/core/java/android/os/storage/StorageVolume.java @@ -168,7 +168,7 @@ public final class StorageVolume implements Parcelable { mExternallyManaged = in.readInt() != 0; mAllowMassStorage = in.readInt() != 0; mMaxFileSize = in.readLong(); - mOwner = in.readParcelable(null); + mOwner = in.readParcelable(null, android.os.UserHandle.class); if (in.readInt() != 0) { mUuid = StorageManager.convert(in.readString8()); } else { diff --git a/core/java/android/os/vibrator/VibratorFrequencyProfile.java b/core/java/android/os/vibrator/VibratorFrequencyProfile.java new file mode 100644 index 000000000000..23b45aed8eb4 --- /dev/null +++ b/core/java/android/os/vibrator/VibratorFrequencyProfile.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.os.vibrator; + +import android.annotation.FloatRange; +import android.annotation.NonNull; +import android.os.VibratorInfo; + +import com.android.internal.util.Preconditions; + +/** + * Describes the output of a {@link android.os.Vibrator} for different vibration frequencies. + * + * <p>The profile contains the minimum and maximum supported vibration frequencies, if the device + * supports independent frequency control. + * + * <p>It also describes the relative output acceleration of a vibration at different supported + * frequencies. The acceleration is defined by a relative amplitude value between 0 and 1, + * inclusive, where 0 represents the vibrator off state and 1 represents the maximum output + * acceleration that the vibrator can reach across all supported frequencies. + * + * <p>The measurements are returned as an array of uniformly distributed amplitude values for + * frequencies between the minimum and maximum supported ones. The measurement interval is the + * frequency increment between each pair of amplitude values. + * + * <p>Vibrators without independent frequency control do not have a frequency profile. + */ +public final class VibratorFrequencyProfile { + + private final VibratorInfo.FrequencyProfile mFrequencyProfile; + + /** @hide */ + public VibratorFrequencyProfile(@NonNull VibratorInfo.FrequencyProfile frequencyProfile) { + Preconditions.checkArgument(!frequencyProfile.isEmpty(), + "Frequency profile must have a non-empty frequency range"); + mFrequencyProfile = frequencyProfile; + } + + /** + * Measurements of the maximum relative amplitude the vibrator can achieve for each supported + * frequency. + * + * <p>The frequency of a measurement is determined as: + * + * {@code getMinFrequency() + measurementIndex * getMaxAmplitudeMeasurementInterval()} + * + * <p>The returned list will not be empty, and will have entries representing frequencies from + * {@link #getMinFrequency()} to {@link #getMaxFrequency()}, inclusive. + * + * @return Array of maximum relative amplitude measurements, each value is between 0 and 1, + * inclusive. + */ + @NonNull + @FloatRange(from = 0, to = 1) + public float[] getMaxAmplitudeMeasurements() { + // VibratorInfo getters always return a copy or clone of the data objects. + return mFrequencyProfile.getMaxAmplitudes(); + } + + /** + * Gets the frequency interval used to measure the maximum relative amplitudes. + * + * @return the frequency interval used for the measurement, in hertz. + */ + public float getMaxAmplitudeMeasurementInterval() { + return mFrequencyProfile.getFrequencyResolutionHz(); + } + + /** + * Gets the minimum frequency supported by the vibrator. + * + * @return the minimum frequency supported by the vibrator, in hertz. + */ + public float getMinFrequency() { + return mFrequencyProfile.getFrequencyRangeHz().getLower(); + } + + /** + * Gets the maximum frequency supported by the vibrator. + * + * @return the maximum frequency supported by the vibrator, in hertz. + */ + public float getMaxFrequency() { + return mFrequencyProfile.getFrequencyRangeHz().getUpper(); + } +} diff --git a/core/java/android/permission/IPermissionController.aidl b/core/java/android/permission/IPermissionController.aidl index 5814bac06f59..c9dd06cfaa43 100644 --- a/core/java/android/permission/IPermissionController.aidl +++ b/core/java/android/permission/IPermissionController.aidl @@ -56,6 +56,9 @@ oneway interface IPermissionController { in AndroidFuture<String> callback); void getUnusedAppCount( in AndroidFuture callback); - void selfRevokePermissions(in String packageName, in List<String> permissions, + void getHibernationEligibility( + in String packageName, + in AndroidFuture callback); + void revokeOwnPermissionsOnKill(in String packageName, in List<String> permissions, in AndroidFuture callback); } diff --git a/core/java/android/permission/IPermissionManager.aidl b/core/java/android/permission/IPermissionManager.aidl index 8e5581b1b80e..1c0320e9a86e 100644 --- a/core/java/android/permission/IPermissionManager.aidl +++ b/core/java/android/permission/IPermissionManager.aidl @@ -76,7 +76,7 @@ interface IPermissionManager { List<SplitPermissionInfoParcelable> getSplitPermissions(); - void selfRevokePermissions(String packageName, in List<String> permissions); + void revokeOwnPermissionsOnKill(String packageName, in List<String> permissions); void startOneTimePermissionSession(String packageName, int userId, long timeout, int importanceToResetTimer, int importanceToKeepSessionAlive); diff --git a/core/java/android/permission/PermissionControllerManager.java b/core/java/android/permission/PermissionControllerManager.java index 47cd10765da0..0cf06aa364ec 100644 --- a/core/java/android/permission/PermissionControllerManager.java +++ b/core/java/android/permission/PermissionControllerManager.java @@ -128,6 +128,51 @@ public final class PermissionControllerManager { /** Count and app even if it is a system app. */ public static final int COUNT_WHEN_SYSTEM = 2; + /** @hide */ + @IntDef(prefix = { "HIBERNATION_ELIGIBILITY_"}, value = { + HIBERNATION_ELIGIBILITY_UNKNOWN, + HIBERNATION_ELIGIBILITY_ELIGIBLE, + HIBERNATION_ELIGIBILITY_EXEMPT_BY_SYSTEM, + HIBERNATION_ELIGIBILITY_EXEMPT_BY_USER, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface HibernationEligibilityFlag {} + + /** + * Unknown whether package is eligible for hibernation. + * + * @hide + */ + @SystemApi + public static final int HIBERNATION_ELIGIBILITY_UNKNOWN = -1; + + /** + * Package is eligible for app hibernation and may be hibernated when the job runs. + * + * @hide + */ + @SystemApi + public static final int HIBERNATION_ELIGIBILITY_ELIGIBLE = 0; + + /** + * Package is not eligible for app hibernation because it is categorically exempt via the + * system. + * + * @hide + */ + @SystemApi + public static final int HIBERNATION_ELIGIBILITY_EXEMPT_BY_SYSTEM = 1; + + /** + * Package is not eligible for app hibernation because it has been exempt by the user's + * preferences. Note that this should not be set if the package is exempt from hibernation by + * the system as the user preference would have no effect. + * + * @hide + */ + @SystemApi + public static final int HIBERNATION_ELIGIBILITY_EXEMPT_BY_USER = 2; + /** * Callback for delivering the result of {@link #revokeRuntimePermissions}. */ @@ -819,6 +864,39 @@ public final class PermissionControllerManager { } /** + * Get the hibernation eligibility of a package. See {@link HibernationEligibilityFlag}. + * + * @param packageName package name to check eligibility + * @param executor executor to run callback on + * @param callback callback for when result is generated + */ + @RequiresPermission(Manifest.permission.MANAGE_APP_HIBERNATION) + public void getHibernationEligibility(@NonNull String packageName, + @NonNull @CallbackExecutor Executor executor, + @NonNull IntConsumer callback) { + checkNotNull(executor); + checkNotNull(callback); + + mRemoteService.postAsync(service -> { + AndroidFuture<Integer> eligibilityResult = new AndroidFuture<>(); + service.getHibernationEligibility(packageName, eligibilityResult); + return eligibilityResult; + }).whenCompleteAsync((eligibility, err) -> { + if (err != null) { + Log.e(TAG, "Error getting hibernation eligibility", err); + callback.accept(HIBERNATION_ELIGIBILITY_UNKNOWN); + } else { + final long token = Binder.clearCallingIdentity(); + try { + callback.accept(eligibility); + } finally { + Binder.restoreCallingIdentity(token); + } + } + }, executor); + } + + /** * Triggers the revocation of one or more permissions for a package, under the following * conditions: * <ul> @@ -835,17 +913,17 @@ public final class PermissionControllerManager { * * @param packageName The name of the package for which the permissions will be revoked. * @param permissions List of permissions to be revoked. + * @param callback Callback called when the revocation request has been completed. * - * @see Context#selfRevokePermissions(Collection) + * @see Context#revokeOwnPermissionsOnKill(Collection) * * @hide */ - public void selfRevokePermissions(@NonNull String packageName, - @NonNull List<String> permissions) { + public void revokeOwnPermissionsOnKill(@NonNull String packageName, + @NonNull List<String> permissions, AndroidFuture<Void> callback) { mRemoteService.postAsync(service -> { - AndroidFuture<Void> future = new AndroidFuture<>(); - service.selfRevokePermissions(packageName, permissions, future); - return future; + service.revokeOwnPermissionsOnKill(packageName, permissions, callback); + return callback; }).whenComplete((result, err) -> { if (err != null) { Log.e(TAG, "Failed to self revoke " + String.join(",", permissions) diff --git a/core/java/android/permission/PermissionControllerService.java b/core/java/android/permission/PermissionControllerService.java index dcbab62530b1..8d9f82b04b54 100644 --- a/core/java/android/permission/PermissionControllerService.java +++ b/core/java/android/permission/PermissionControllerService.java @@ -337,10 +337,10 @@ public abstract class PermissionControllerService extends Service { * @param permissions List of permissions to be revoked. * @param callback Callback waiting for operation to be complete. * - * @see PermissionManager#selfRevokePermissions(java.util.Collection) + * @see PermissionManager#revokeOwnPermissionsOnKill(java.util.Collection) */ @BinderThread - public void onSelfRevokePermissions(@NonNull String packageName, + public void onRevokeOwnPermissionsOnKill(@NonNull String packageName, @NonNull List<String> permissions, @NonNull Runnable callback) { throw new AbstractMethodError("Must be overridden in implementing class"); } @@ -375,6 +375,22 @@ public abstract class PermissionControllerService extends Service { throw new AbstractMethodError("Must be overridden in implementing class"); } + /** + * Get the hibernation eligibility of the app. See + * {@link android.permission.PermissionControllerManager.HibernationEligibilityFlag}. + * + * @param packageName package to check eligibility + * @param callback callback after eligibility is returned + * + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.MANAGE_APP_HIBERNATION) + public void onGetHibernationEligibility(@NonNull String packageName, + @NonNull IntConsumer callback) { + throw new AbstractMethodError("Must be overridden in implementing class"); + } + @Override public final @NonNull IBinder onBind(Intent intent) { return new IPermissionController.Stub() { @@ -669,13 +685,29 @@ public abstract class PermissionControllerService extends Service { } @Override - public void selfRevokePermissions(@NonNull String packageName, + public void getHibernationEligibility(@NonNull String packageName, + @NonNull AndroidFuture callback) { + try { + Objects.requireNonNull(callback); + + enforceSomePermissionsGrantedToCaller( + Manifest.permission.MANAGE_APP_HIBERNATION); + + PermissionControllerService.this.onGetHibernationEligibility(packageName, + callback::complete); + } catch (Throwable t) { + callback.completeExceptionally(t); + } + } + + @Override + public void revokeOwnPermissionsOnKill(@NonNull String packageName, @NonNull List<String> permissions, @NonNull AndroidFuture callback) { try { enforceSomePermissionsGrantedToCaller( Manifest.permission.REVOKE_RUNTIME_PERMISSIONS); Objects.requireNonNull(callback); - onSelfRevokePermissions(packageName, permissions, + onRevokeOwnPermissionsOnKill(packageName, permissions, () -> callback.complete(null)); } catch (Throwable t) { callback.completeExceptionally(t); diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java index 13941dc5ef82..e4aee7619250 100644 --- a/core/java/android/permission/PermissionManager.java +++ b/core/java/android/permission/PermissionManager.java @@ -562,12 +562,12 @@ public final class PermissionManager { } /** - * @see Context#selfRevokePermissions(Collection) + * @see Context#revokeOwnPermissionsOnKill(Collection) * @hide */ - public void selfRevokePermissions(@NonNull Collection<String> permissions) { + public void revokeOwnPermissionsOnKill(@NonNull Collection<String> permissions) { try { - mPermissionManager.selfRevokePermissions(mContext.getPackageName(), + mPermissionManager.revokeOwnPermissionsOnKill(mContext.getPackageName(), new ArrayList<String>(permissions)); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); diff --git a/core/java/android/print/PrintJobInfo.java b/core/java/android/print/PrintJobInfo.java index 67249be2b806..9d0c8d82ed0d 100644 --- a/core/java/android/print/PrintJobInfo.java +++ b/core/java/android/print/PrintJobInfo.java @@ -231,9 +231,9 @@ public final class PrintJobInfo implements Parcelable { } private PrintJobInfo(@NonNull Parcel parcel) { - mId = parcel.readParcelable(null); + mId = parcel.readParcelable(null, android.print.PrintJobId.class); mLabel = parcel.readString(); - mPrinterId = parcel.readParcelable(null); + mPrinterId = parcel.readParcelable(null, android.print.PrinterId.class); mPrinterName = parcel.readString(); mState = parcel.readInt(); mAppId = parcel.readInt(); @@ -247,8 +247,8 @@ public final class PrintJobInfo implements Parcelable { mPageRanges[i] = (PageRange) parcelables[i]; } } - mAttributes = (PrintAttributes) parcel.readParcelable(null); - mDocumentInfo = (PrintDocumentInfo) parcel.readParcelable(null); + mAttributes = (PrintAttributes) parcel.readParcelable(null, android.print.PrintAttributes.class); + mDocumentInfo = (PrintDocumentInfo) parcel.readParcelable(null, android.print.PrintDocumentInfo.class); mProgress = parcel.readFloat(); mStatus = parcel.readCharSequence(); mStatusRes = parcel.readInt(); diff --git a/core/java/android/print/PrinterId.java b/core/java/android/print/PrinterId.java index 25260c473709..284e122fc103 100644 --- a/core/java/android/print/PrinterId.java +++ b/core/java/android/print/PrinterId.java @@ -48,7 +48,7 @@ public final class PrinterId implements Parcelable { } private PrinterId(@NonNull Parcel parcel) { - mServiceName = Preconditions.checkNotNull((ComponentName) parcel.readParcelable(null)); + mServiceName = Preconditions.checkNotNull((ComponentName) parcel.readParcelable(null, android.content.ComponentName.class)); mLocalId = Preconditions.checkNotNull(parcel.readString()); } diff --git a/core/java/android/print/PrinterInfo.java b/core/java/android/print/PrinterInfo.java index 8e03e3eb3f22..2f93e404a211 100644 --- a/core/java/android/print/PrinterInfo.java +++ b/core/java/android/print/PrinterInfo.java @@ -270,15 +270,15 @@ public final class PrinterInfo implements Parcelable { private PrinterInfo(Parcel parcel) { // mName can be null due to unchecked set in Builder.setName and status can be invalid // due to unchecked set in Builder.setStatus, hence we can only check mId for a valid state - mId = checkPrinterId((PrinterId) parcel.readParcelable(null)); + mId = checkPrinterId((PrinterId) parcel.readParcelable(null, android.print.PrinterId.class)); mName = checkName(parcel.readString()); mStatus = checkStatus(parcel.readInt()); mDescription = parcel.readString(); - mCapabilities = parcel.readParcelable(null); + mCapabilities = parcel.readParcelable(null, android.print.PrinterCapabilitiesInfo.class); mIconResourceId = parcel.readInt(); mHasCustomPrinterIcon = parcel.readByte() != 0; mCustomPrinterIconGen = parcel.readInt(); - mInfoIntent = parcel.readParcelable(null); + mInfoIntent = parcel.readParcelable(null, android.app.PendingIntent.class); } @Override diff --git a/core/java/android/printservice/PrintServiceInfo.java b/core/java/android/printservice/PrintServiceInfo.java index 0c1b61d583b3..347955718f78 100644 --- a/core/java/android/printservice/PrintServiceInfo.java +++ b/core/java/android/printservice/PrintServiceInfo.java @@ -76,7 +76,7 @@ public final class PrintServiceInfo implements Parcelable { public PrintServiceInfo(Parcel parcel) { mId = parcel.readString(); mIsEnabled = parcel.readByte() != 0; - mResolveInfo = parcel.readParcelable(null); + mResolveInfo = parcel.readParcelable(null, android.content.pm.ResolveInfo.class); mSettingsActivityName = parcel.readString(); mAddPrintersActivityName = parcel.readString(); mAdvancedPrintOptionsActivityName = parcel.readString(); diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java index 6349cde2b8fc..87f4489e30f8 100644 --- a/core/java/android/provider/DeviceConfig.java +++ b/core/java/android/provider/DeviceConfig.java @@ -464,6 +464,13 @@ public final class DeviceConfig { public static final String NAMESPACE_STORAGE_NATIVE_BOOT = "storage_native_boot"; /** + * Namespace for all Supplemental Api related features. + * @hide + */ + @SystemApi + public static final String NAMESPACE_SUPPLEMENTAL_API = "supplemental_api"; + + /** * Namespace for all SurfaceFlinger features that are used at the native level. * These features are applied on boot or after reboot. * @@ -687,6 +694,15 @@ public final class DeviceConfig { @SystemApi public static final String NAMESPACE_UWB = "uwb"; + /** + * Namespace for AmbientContextEventManagerService related features. + * + * @hide + */ + @SystemApi + public static final String NAMESPACE_AMBIENT_CONTEXT_MANAGER_SERVICE = + "ambient_context_manager_service"; + private static final Object sLock = new Object(); @GuardedBy("sLock") private static ArrayMap<OnPropertiesChangedListener, Pair<String, Executor>> sListeners = diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 5cdb1fdf64ce..3f544089fcf3 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -5120,7 +5120,12 @@ public final class Settings { * It was about AudioManager's setting and thus affected all the applications which * relied on the setting, while this is purely about the vibration setting for incoming * calls. + * + * @deprecated Replaced by using {@link android.os.VibrationAttributes#USAGE_RINGTONE} on + * vibrations for incoming calls. User settings are applied automatically by the service and + * should not be applied by individual apps. */ + @Deprecated @Readable public static final String VIBRATE_WHEN_RINGING = "vibrate_when_ringing"; @@ -5181,7 +5186,12 @@ public final class Settings { /** * Whether haptic feedback (Vibrate on tap) is enabled. The value is * boolean (1 or 0). + * + * @deprecated Replaced by using {@link android.os.VibrationAttributes#USAGE_TOUCH} on + * vibrations. User settings are applied automatically by the service and should not be + * applied by individual apps. */ + @Deprecated @Readable public static final String HAPTIC_FEEDBACK_ENABLED = "haptic_feedback_enabled"; @@ -7158,6 +7168,12 @@ public final class Settings { public static final String LOCATION_COARSE_ACCURACY_M = "locationCoarseAccuracy"; /** + * Whether or not to show display system location accesses. + * @hide + */ + public static final String LOCATION_SHOW_SYSTEM_OPS = "locationShowSystemOps"; + + /** * A flag containing settings used for biometric weak * @hide */ @@ -8974,6 +8990,15 @@ public final class Settings { public static final String UI_NIGHT_MODE = "ui_night_mode"; /** + * The current night mode custom type that has been selected by the user. Owned + * and controlled by UiModeManagerService. Constants are as per UiModeManager. + * @hide + */ + @Readable + @SuppressLint("NoSettingsProvider") + public static final String UI_NIGHT_MODE_CUSTOM_TYPE = "ui_night_mode_custom_type"; + + /** * The current night mode that has been overridden to turn on by the system. Owned * and controlled by UiModeManagerService. Constants are as per * UiModeManager. @@ -9038,6 +9063,16 @@ public final class Settings { public static final String SCREENSAVER_DEFAULT_COMPONENT = "screensaver_default_component"; /** + * The complications that are enabled to be shown over the screensaver by the user. Holds + * a comma separated list of + * {@link com.android.settingslib.dream.DreamBackend.ComplicationType}. + * + * @hide + */ + public static final String SCREENSAVER_ENABLED_COMPLICATIONS = + "screensaver_enabled_complications"; + + /** * The default NFC payment component * @hide */ @@ -10567,6 +10602,14 @@ public final class Settings { "communal_mode_trusted_networks"; /** + * Setting to allow Fast Pair scans to be enabled. + * @hide + */ + @SystemApi + @Readable + public static final String FAST_PAIR_SCAN_ENABLED = "fast_pair_scan_enabled"; + + /** * These entries are considered common between the personal and the managed profile, * since the managed profile doesn't get to change them. */ @@ -12798,16 +12841,6 @@ public final class Settings { SYS_STORAGE_CACHE_PERCENTAGE = "sys_storage_cache_percentage"; /** - * Maximum bytes of storage on the device that is reserved for cached - * data. - * - * @hide - */ - @Readable - public static final String - SYS_STORAGE_CACHE_MAX_BYTES = "sys_storage_cache_max_bytes"; - - /** * The maximum reconnect delay for short network outages or when the * network is suspended due to phone use. * diff --git a/core/java/android/service/ambientcontext/AmbientContextDetectionService.java b/core/java/android/service/ambientcontext/AmbientContextDetectionService.java new file mode 100644 index 000000000000..dccfe3693b35 --- /dev/null +++ b/core/java/android/service/ambientcontext/AmbientContextDetectionService.java @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.ambientcontext; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.app.Service; +import android.app.ambientcontext.AmbientContextEvent; +import android.app.ambientcontext.AmbientContextEventRequest; +import android.app.ambientcontext.AmbientContextEventResponse; +import android.content.Intent; +import android.os.Bundle; +import android.os.IBinder; +import android.os.RemoteCallback; +import android.util.Slog; + +import java.util.Objects; +import java.util.function.Consumer; + +/** + * Abstract base class for {@link AmbientContextEvent} detection service. + * + * <p> A service that provides requested ambient context events to the system. + * The system's default AmbientContextDetectionService implementation is configured in + * {@code config_defaultAmbientContextDetectionService}. If this config has no value, a stub is + * returned. + * + * See: {@code AmbientContextManagerService}. + * + * <pre> + * {@literal + * <service android:name=".YourAmbientContextDetectionService" + * android:permission="android.permission.BIND_AMBIENT_CONTEXT_DETECTION_SERVICE"> + * </service>} + * </pre> + * + * @hide + */ +@SystemApi +public abstract class AmbientContextDetectionService extends Service { + private static final String TAG = AmbientContextDetectionService.class.getSimpleName(); + + /** + * The {@link Intent} that must be declared as handled by the service. To be supported, the + * service must also require the + * {@link android.Manifest.permission#BIND_AMBIENT_CONTEXT_DETECTION_SERVICE} + * permission so that other applications can not abuse it. + */ + public static final String SERVICE_INTERFACE = + "android.service.ambientcontext.AmbientContextDetectionService"; + + /** + * The key for the bundle the parameter of {@code RemoteCallback#sendResult}. Implementation + * should set bundle result with this key. + * + * @hide + */ + public static final String RESPONSE_BUNDLE_KEY = + "android.service.ambientcontext.EventResponseKey"; + + @Nullable + @Override + public final IBinder onBind(@NonNull Intent intent) { + if (SERVICE_INTERFACE.equals(intent.getAction())) { + return new IAmbientContextDetectionService.Stub() { + /** {@inheritDoc} */ + @Override + public void startDetection( + @NonNull AmbientContextEventRequest request, String packageName, + RemoteCallback callback) { + Objects.requireNonNull(request); + Objects.requireNonNull(callback); + Consumer<AmbientContextEventResponse> consumer = + response -> { + Bundle bundle = new Bundle(); + bundle.putParcelable( + AmbientContextDetectionService.RESPONSE_BUNDLE_KEY, + response); + callback.sendResult(bundle); + }; + AmbientContextDetectionService.this.onStartDetection( + request, packageName, consumer); + Slog.d(TAG, "startDetection " + request); + } + + /** {@inheritDoc} */ + @Override + public void stopDetection(String packageName) { + Objects.requireNonNull(packageName); + AmbientContextDetectionService.this.onStopDetection(packageName); + } + }; + } + return null; + } + + /** + * Starts detection and provides detected events to the consumer. The ongoing detection will + * keep running, until onStopDetection is called. If there were previously requested + * detection from the same package, the previous request will be replaced with the new request. + * The implementation should keep track of whether the user consented each requested + * AmbientContextEvent for the app. If not consented, the response should set status + * STATUS_ACCESS_DENIED and include an action PendingIntent for the app to redirect the user + * to the consent screen. + * + * @param request The request with events to detect, optional detection window and other + * options. + * @param packageName the requesting app's package name + * @param consumer the consumer for the detected event + */ + public abstract void onStartDetection( + @NonNull AmbientContextEventRequest request, + @NonNull String packageName, + @NonNull Consumer<AmbientContextEventResponse> consumer); + + /** + * Stops detection of the events. Events that are not being detected will be ignored. + * + * @param packageName stops detection for the given package. + */ + public abstract void onStopDetection(@NonNull String packageName); +} diff --git a/core/java/android/service/ambientcontext/IAmbientContextDetectionService.aidl b/core/java/android/service/ambientcontext/IAmbientContextDetectionService.aidl new file mode 100644 index 000000000000..1c6e25efeabe --- /dev/null +++ b/core/java/android/service/ambientcontext/IAmbientContextDetectionService.aidl @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.ambientcontext; + +import android.app.ambientcontext.AmbientContextEventRequest; +import android.os.RemoteCallback; + +/** + * Interface for a concrete implementation to provide AmbientContextEvents to the framework. + * + * @hide + */ +oneway interface IAmbientContextDetectionService { + void startDetection(in AmbientContextEventRequest request, in String packageName, + in RemoteCallback callback); + void stopDetection(in String packageName); +}
\ No newline at end of file diff --git a/core/java/android/service/ambientcontext/OWNERS b/core/java/android/service/ambientcontext/OWNERS new file mode 100644 index 000000000000..a863297b84e8 --- /dev/null +++ b/core/java/android/service/ambientcontext/OWNERS @@ -0,0 +1,3 @@ +enxun@google.com +kxchen@google.com +tgadh@google.com diff --git a/core/java/android/service/autofill/BatchUpdates.java b/core/java/android/service/autofill/BatchUpdates.java index 8eeecc293104..c996cc088d66 100644 --- a/core/java/android/service/autofill/BatchUpdates.java +++ b/core/java/android/service/autofill/BatchUpdates.java @@ -205,7 +205,7 @@ public final class BatchUpdates implements Parcelable { builder.transformChild(ids[i], values[i]); } } - final RemoteViews updates = parcel.readParcelable(null); + final RemoteViews updates = parcel.readParcelable(null, android.widget.RemoteViews.class); if (updates != null) { builder.updateTemplate(updates); } diff --git a/core/java/android/service/autofill/CompositeUserData.java b/core/java/android/service/autofill/CompositeUserData.java index 92952cb7dc24..55ac5a5e92f0 100644 --- a/core/java/android/service/autofill/CompositeUserData.java +++ b/core/java/android/service/autofill/CompositeUserData.java @@ -197,8 +197,8 @@ public final class CompositeUserData implements FieldClassificationUserData, Par // Always go through the builder to ensure the data ingested by // the system obeys the contract of the builder to avoid attacks // using specially crafted parcels. - final UserData genericUserData = parcel.readParcelable(null); - final UserData packageUserData = parcel.readParcelable(null); + final UserData genericUserData = parcel.readParcelable(null, android.service.autofill.UserData.class); + final UserData packageUserData = parcel.readParcelable(null, android.service.autofill.UserData.class); return new CompositeUserData(genericUserData, packageUserData); } diff --git a/core/java/android/service/autofill/CustomDescription.java b/core/java/android/service/autofill/CustomDescription.java index f3f912bb3a5b..690cd0691631 100644 --- a/core/java/android/service/autofill/CustomDescription.java +++ b/core/java/android/service/autofill/CustomDescription.java @@ -437,7 +437,7 @@ public final class CustomDescription implements Parcelable { // Always go through the builder to ensure the data ingested by // the system obeys the contract of the builder to avoid attacks // using specially crafted parcels. - final RemoteViews parentPresentation = parcel.readParcelable(null); + final RemoteViews parentPresentation = parcel.readParcelable(null, android.widget.RemoteViews.class); if (parentPresentation == null) return null; final Builder builder = new Builder(parentPresentation); diff --git a/core/java/android/service/autofill/Dataset.java b/core/java/android/service/autofill/Dataset.java index 8539bf58da27..86341a908ad7 100644 --- a/core/java/android/service/autofill/Dataset.java +++ b/core/java/android/service/autofill/Dataset.java @@ -913,10 +913,10 @@ public final class Dataset implements Parcelable { public static final @NonNull Creator<Dataset> CREATOR = new Creator<Dataset>() { @Override public Dataset createFromParcel(Parcel parcel) { - final RemoteViews presentation = parcel.readParcelable(null); - final InlinePresentation inlinePresentation = parcel.readParcelable(null); + final RemoteViews presentation = parcel.readParcelable(null, android.widget.RemoteViews.class); + final InlinePresentation inlinePresentation = parcel.readParcelable(null, android.service.autofill.InlinePresentation.class); final InlinePresentation inlineTooltipPresentation = - parcel.readParcelable(null); + parcel.readParcelable(null, android.service.autofill.InlinePresentation.class); final ArrayList<AutofillId> ids = parcel.createTypedArrayList(AutofillId.CREATOR); final ArrayList<AutofillValue> values = @@ -929,8 +929,8 @@ public final class Dataset implements Parcelable { parcel.createTypedArrayList(InlinePresentation.CREATOR); final ArrayList<DatasetFieldFilter> filters = parcel.createTypedArrayList(DatasetFieldFilter.CREATOR); - final ClipData fieldContent = parcel.readParcelable(null); - final IntentSender authentication = parcel.readParcelable(null); + final ClipData fieldContent = parcel.readParcelable(null, android.content.ClipData.class); + final IntentSender authentication = parcel.readParcelable(null, android.content.IntentSender.class); final String datasetId = parcel.readString(); // Always go through the builder to ensure the data ingested by @@ -1014,7 +1014,7 @@ public final class Dataset implements Parcelable { @Override public DatasetFieldFilter createFromParcel(Parcel parcel) { - return new DatasetFieldFilter((Pattern) parcel.readSerializable()); + return new DatasetFieldFilter((Pattern) parcel.readSerializable(java.util.regex.Pattern.class.getClassLoader(), java.util.regex.Pattern.class)); } @Override diff --git a/core/java/android/service/autofill/DateTransformation.java b/core/java/android/service/autofill/DateTransformation.java index 734085737159..df5ed4dace55 100644 --- a/core/java/android/service/autofill/DateTransformation.java +++ b/core/java/android/service/autofill/DateTransformation.java @@ -114,8 +114,8 @@ public final class DateTransformation extends InternalTransformation implements new Parcelable.Creator<DateTransformation>() { @Override public DateTransformation createFromParcel(Parcel parcel) { - return new DateTransformation(parcel.readParcelable(null), - (DateFormat) parcel.readSerializable()); + return new DateTransformation(parcel.readParcelable(null, android.view.autofill.AutofillId.class), + (DateFormat) parcel.readSerializable(android.icu.text.DateFormat.class.getClassLoader(), android.icu.text.DateFormat.class)); } @Override diff --git a/core/java/android/service/autofill/DateValueSanitizer.java b/core/java/android/service/autofill/DateValueSanitizer.java index 6f7808ee181a..c7d5b79ae484 100644 --- a/core/java/android/service/autofill/DateValueSanitizer.java +++ b/core/java/android/service/autofill/DateValueSanitizer.java @@ -111,7 +111,7 @@ public final class DateValueSanitizer extends InternalSanitizer implements Sanit new Parcelable.Creator<DateValueSanitizer>() { @Override public DateValueSanitizer createFromParcel(Parcel parcel) { - return new DateValueSanitizer((DateFormat) parcel.readSerializable()); + return new DateValueSanitizer((DateFormat) parcel.readSerializable(android.icu.text.DateFormat.class.getClassLoader(), android.icu.text.DateFormat.class)); } @Override diff --git a/core/java/android/service/autofill/FillRequest.java b/core/java/android/service/autofill/FillRequest.java index af846b62ae2c..43bd4102ffb5 100644 --- a/core/java/android/service/autofill/FillRequest.java +++ b/core/java/android/service/autofill/FillRequest.java @@ -384,7 +384,7 @@ public final class FillRequest implements Parcelable { byte flg = in.readByte(); int id = in.readInt(); List<FillContext> fillContexts = new ArrayList<>(); - in.readParcelableList(fillContexts, FillContext.class.getClassLoader()); + in.readParcelableList(fillContexts, FillContext.class.getClassLoader(), android.service.autofill.FillContext.class); Bundle clientState = (flg & 0x4) == 0 ? null : in.readBundle(); int flags = in.readInt(); InlineSuggestionsRequest inlineSuggestionsRequest = (flg & 0x10) == 0 ? null : (InlineSuggestionsRequest) in.readTypedObject(InlineSuggestionsRequest.CREATOR); diff --git a/core/java/android/service/autofill/FillResponse.java b/core/java/android/service/autofill/FillResponse.java index 970cb1888317..d94988ebea66 100644 --- a/core/java/android/service/autofill/FillResponse.java +++ b/core/java/android/service/autofill/FillResponse.java @@ -834,35 +834,35 @@ public final class FillResponse implements Parcelable { // the system obeys the contract of the builder to avoid attacks // using specially crafted parcels. final Builder builder = new Builder(); - final ParceledListSlice<Dataset> datasetSlice = parcel.readParcelable(null); + final ParceledListSlice<Dataset> datasetSlice = parcel.readParcelable(null, android.content.pm.ParceledListSlice.class); final List<Dataset> datasets = (datasetSlice != null) ? datasetSlice.getList() : null; final int datasetCount = (datasets != null) ? datasets.size() : 0; for (int i = 0; i < datasetCount; i++) { builder.addDataset(datasets.get(i)); } - builder.setSaveInfo(parcel.readParcelable(null)); - builder.setClientState(parcel.readParcelable(null)); + builder.setSaveInfo(parcel.readParcelable(null, android.service.autofill.SaveInfo.class)); + builder.setClientState(parcel.readParcelable(null, android.os.Bundle.class)); // Sets authentication state. final AutofillId[] authenticationIds = parcel.readParcelableArray(null, AutofillId.class); - final IntentSender authentication = parcel.readParcelable(null); - final RemoteViews presentation = parcel.readParcelable(null); - final InlinePresentation inlinePresentation = parcel.readParcelable(null); - final InlinePresentation inlineTooltipPresentation = parcel.readParcelable(null); + final IntentSender authentication = parcel.readParcelable(null, android.content.IntentSender.class); + final RemoteViews presentation = parcel.readParcelable(null, android.widget.RemoteViews.class); + final InlinePresentation inlinePresentation = parcel.readParcelable(null, android.service.autofill.InlinePresentation.class); + final InlinePresentation inlineTooltipPresentation = parcel.readParcelable(null, android.service.autofill.InlinePresentation.class); if (authenticationIds != null) { builder.setAuthentication(authenticationIds, authentication, presentation, inlinePresentation, inlineTooltipPresentation); } - final RemoteViews header = parcel.readParcelable(null); + final RemoteViews header = parcel.readParcelable(null, android.widget.RemoteViews.class); if (header != null) { builder.setHeader(header); } - final RemoteViews footer = parcel.readParcelable(null); + final RemoteViews footer = parcel.readParcelable(null, android.widget.RemoteViews.class); if (footer != null) { builder.setFooter(footer); } - final UserData userData = parcel.readParcelable(null); + final UserData userData = parcel.readParcelable(null, android.service.autofill.UserData.class); if (userData != null) { builder.setUserData(userData); } diff --git a/core/java/android/service/autofill/ImageTransformation.java b/core/java/android/service/autofill/ImageTransformation.java index e3171594c39e..af82205b77b9 100644 --- a/core/java/android/service/autofill/ImageTransformation.java +++ b/core/java/android/service/autofill/ImageTransformation.java @@ -247,7 +247,7 @@ public final class ImageTransformation extends InternalTransformation implements new Parcelable.Creator<ImageTransformation>() { @Override public ImageTransformation createFromParcel(Parcel parcel) { - final AutofillId id = parcel.readParcelable(null); + final AutofillId id = parcel.readParcelable(null, android.view.autofill.AutofillId.class); final Pattern[] regexs = (Pattern[]) parcel.readSerializable(); final int[] resIds = parcel.createIntArray(); diff --git a/core/java/android/service/autofill/NegationValidator.java b/core/java/android/service/autofill/NegationValidator.java index d626845b3b3e..85cd981e3152 100644 --- a/core/java/android/service/autofill/NegationValidator.java +++ b/core/java/android/service/autofill/NegationValidator.java @@ -68,7 +68,7 @@ final class NegationValidator extends InternalValidator { new Parcelable.Creator<NegationValidator>() { @Override public NegationValidator createFromParcel(Parcel parcel) { - return new NegationValidator(parcel.readParcelable(null)); + return new NegationValidator(parcel.readParcelable(null, android.service.autofill.InternalValidator.class)); } @Override diff --git a/core/java/android/service/autofill/RegexValidator.java b/core/java/android/service/autofill/RegexValidator.java index 00c43473ce7f..4c58590ab7cf 100644 --- a/core/java/android/service/autofill/RegexValidator.java +++ b/core/java/android/service/autofill/RegexValidator.java @@ -96,8 +96,8 @@ public final class RegexValidator extends InternalValidator implements Validator new Parcelable.Creator<RegexValidator>() { @Override public RegexValidator createFromParcel(Parcel parcel) { - return new RegexValidator(parcel.readParcelable(null), - (Pattern) parcel.readSerializable()); + return new RegexValidator(parcel.readParcelable(null, android.view.autofill.AutofillId.class), + (Pattern) parcel.readSerializable(java.util.regex.Pattern.class.getClassLoader(), java.util.regex.Pattern.class)); } @Override diff --git a/core/java/android/service/autofill/SaveInfo.java b/core/java/android/service/autofill/SaveInfo.java index 8edfde8c3914..5fe1d4f5ca5f 100644 --- a/core/java/android/service/autofill/SaveInfo.java +++ b/core/java/android/service/autofill/SaveInfo.java @@ -888,14 +888,14 @@ public final class SaveInfo implements Parcelable { builder.setOptionalIds(optionalIds); } - builder.setNegativeAction(parcel.readInt(), parcel.readParcelable(null)); + builder.setNegativeAction(parcel.readInt(), parcel.readParcelable(null, android.content.IntentSender.class)); builder.setPositiveAction(parcel.readInt()); builder.setDescription(parcel.readCharSequence()); - final CustomDescription customDescripton = parcel.readParcelable(null); + final CustomDescription customDescripton = parcel.readParcelable(null, android.service.autofill.CustomDescription.class); if (customDescripton != null) { builder.setCustomDescription(customDescripton); } - final InternalValidator validator = parcel.readParcelable(null); + final InternalValidator validator = parcel.readParcelable(null, android.service.autofill.InternalValidator.class); if (validator != null) { builder.setValidator(validator); } @@ -909,7 +909,7 @@ public final class SaveInfo implements Parcelable { builder.addSanitizer(sanitizers[i], autofillIds); } } - final AutofillId triggerId = parcel.readParcelable(null); + final AutofillId triggerId = parcel.readParcelable(null, android.view.autofill.AutofillId.class); if (triggerId != null) { builder.setTriggerId(triggerId); } diff --git a/core/java/android/service/autofill/TextValueSanitizer.java b/core/java/android/service/autofill/TextValueSanitizer.java index 5bafa7a1ff54..46c18b23a74d 100644 --- a/core/java/android/service/autofill/TextValueSanitizer.java +++ b/core/java/android/service/autofill/TextValueSanitizer.java @@ -119,7 +119,7 @@ public final class TextValueSanitizer extends InternalSanitizer implements new Parcelable.Creator<TextValueSanitizer>() { @Override public TextValueSanitizer createFromParcel(Parcel parcel) { - return new TextValueSanitizer((Pattern) parcel.readSerializable(), parcel.readString()); + return new TextValueSanitizer((Pattern) parcel.readSerializable(java.util.regex.Pattern.class.getClassLoader(), java.util.regex.Pattern.class), parcel.readString()); } @Override diff --git a/core/java/android/service/contentcapture/ActivityEvent.java b/core/java/android/service/contentcapture/ActivityEvent.java index 74a735518120..d286942c74fa 100644 --- a/core/java/android/service/contentcapture/ActivityEvent.java +++ b/core/java/android/service/contentcapture/ActivityEvent.java @@ -149,7 +149,7 @@ public final class ActivityEvent implements Parcelable { @Override @NonNull public ActivityEvent createFromParcel(@NonNull Parcel parcel) { - final ComponentName componentName = parcel.readParcelable(null); + final ComponentName componentName = parcel.readParcelable(null, android.content.ComponentName.class); final int eventType = parcel.readInt(); return new ActivityEvent(componentName, eventType); } diff --git a/core/java/android/service/contentcapture/SnapshotData.java b/core/java/android/service/contentcapture/SnapshotData.java index bf469b4b3ad8..f72624d00061 100644 --- a/core/java/android/service/contentcapture/SnapshotData.java +++ b/core/java/android/service/contentcapture/SnapshotData.java @@ -51,8 +51,8 @@ public final class SnapshotData implements Parcelable { SnapshotData(@NonNull Parcel parcel) { mAssistData = parcel.readBundle(); - mAssistStructure = parcel.readParcelable(null); - mAssistContent = parcel.readParcelable(null); + mAssistStructure = parcel.readParcelable(null, android.app.assist.AssistStructure.class); + mAssistContent = parcel.readParcelable(null, android.app.assist.AssistContent.class); } /** diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java index 133e384dfa8f..bb1f393b99bc 100644 --- a/core/java/android/service/dreams/DreamService.java +++ b/core/java/android/service/dreams/DreamService.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (C) 2012 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package android.service.dreams; import android.annotation.IdRes; @@ -57,7 +58,6 @@ import android.view.WindowManager.LayoutParams; import android.view.accessibility.AccessibilityEvent; import com.android.internal.util.DumpUtils; -import com.android.internal.util.DumpUtils.Dump; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -111,7 +111,7 @@ import java.util.function.Consumer; * <p>If specified with the {@code <meta-data>} element, * additional information for the dream is defined using the * {@link android.R.styleable#Dream <dream>} element in a separate XML file. - * Currently, the only addtional + * Currently, the only additional * information you can provide is for a settings activity that allows the user to configure * the dream behavior. For example:</p> * <p class="code-caption">res/xml/my_dream.xml</p> @@ -159,7 +159,8 @@ import java.util.function.Consumer; * </pre> */ public class DreamService extends Service implements Window.Callback { - private final String TAG = DreamService.class.getSimpleName() + "[" + getClass().getSimpleName() + "]"; + private final String mTag = + DreamService.class.getSimpleName() + "[" + getClass().getSimpleName() + "]"; /** * The name of the dream manager service. @@ -224,13 +225,13 @@ public class DreamService extends Service implements Window.Callback { private DreamServiceWrapper mDreamServiceWrapper; private Runnable mDispatchAfterOnAttachedToWindow; - private OverlayConnection mOverlayConnection; + private final OverlayConnection mOverlayConnection; private static class OverlayConnection implements ServiceConnection { // Overlay set during onBind. private IDreamOverlay mOverlay; // A Queue of pending requests to execute on the overlay. - private ArrayDeque<Consumer<IDreamOverlay>> mRequests; + private final ArrayDeque<Consumer<IDreamOverlay>> mRequests; private boolean mBound; @@ -292,7 +293,7 @@ public class DreamService extends Service implements Window.Callback { } } - private IDreamOverlayCallback mOverlayCallback = new IDreamOverlayCallback.Stub() { + private final IDreamOverlayCallback mOverlayCallback = new IDreamOverlayCallback.Stub() { @Override public void onExitRequested() { // Simply finish dream when exit is requested. @@ -319,11 +320,11 @@ public class DreamService extends Service implements Window.Callback { public boolean dispatchKeyEvent(KeyEvent event) { // TODO: create more flexible version of mInteractive that allows use of KEYCODE_BACK if (!mInteractive) { - if (mDebug) Slog.v(TAG, "Waking up on keyEvent"); + if (mDebug) Slog.v(mTag, "Waking up on keyEvent"); wakeUp(); return true; } else if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) { - if (mDebug) Slog.v(TAG, "Waking up on back key"); + if (mDebug) Slog.v(mTag, "Waking up on back key"); wakeUp(); return true; } @@ -334,7 +335,7 @@ public class DreamService extends Service implements Window.Callback { @Override public boolean dispatchKeyShortcutEvent(KeyEvent event) { if (!mInteractive) { - if (mDebug) Slog.v(TAG, "Waking up on keyShortcutEvent"); + if (mDebug) Slog.v(mTag, "Waking up on keyShortcutEvent"); wakeUp(); return true; } @@ -347,7 +348,7 @@ public class DreamService extends Service implements Window.Callback { // TODO: create more flexible version of mInteractive that allows clicks // but finish()es on any other kind of activity if (!mInteractive && event.getActionMasked() == MotionEvent.ACTION_UP) { - if (mDebug) Slog.v(TAG, "Waking up on touchEvent"); + if (mDebug) Slog.v(mTag, "Waking up on touchEvent"); wakeUp(); return true; } @@ -358,7 +359,7 @@ public class DreamService extends Service implements Window.Callback { @Override public boolean dispatchTrackballEvent(MotionEvent event) { if (!mInteractive) { - if (mDebug) Slog.v(TAG, "Waking up on trackballEvent"); + if (mDebug) Slog.v(mTag, "Waking up on trackballEvent"); wakeUp(); return true; } @@ -369,7 +370,7 @@ public class DreamService extends Service implements Window.Callback { @Override public boolean dispatchGenericMotionEvent(MotionEvent event) { if (!mInteractive) { - if (mDebug) Slog.v(TAG, "Waking up on genericMotionEvent"); + if (mDebug) Slog.v(mTag, "Waking up on genericMotionEvent"); wakeUp(); return true; } @@ -624,7 +625,7 @@ public class DreamService extends Service implements Window.Callback { } /** - * Returns whether or not this dream is interactive. Defaults to false. + * Returns whether this dream is interactive. Defaults to false. * * @see #setInteractive(boolean) */ @@ -648,7 +649,7 @@ public class DreamService extends Service implements Window.Callback { } /** - * Returns whether or not this dream is in fullscreen mode. Defaults to false. + * Returns whether this dream is in fullscreen mode. Defaults to false. * * @see #setFullscreen(boolean) */ @@ -670,7 +671,7 @@ public class DreamService extends Service implements Window.Callback { } /** - * Returns whether or not this dream keeps the screen bright while dreaming. + * Returns whether this dream keeps the screen bright while dreaming. * Defaults to false, allowing the screen to dim if necessary. * * @see #setScreenBright(boolean) @@ -680,7 +681,7 @@ public class DreamService extends Service implements Window.Callback { } /** - * Marks this dream as windowless. Only available to doze dreams. + * Marks this dream as windowless. Only available to doze dreams. * * @hide * @@ -690,7 +691,7 @@ public class DreamService extends Service implements Window.Callback { } /** - * Returns whether or not this dream is windowless. Only available to doze dreams. + * Returns whether this dream is windowless. Only available to doze dreams. * * @hide */ @@ -717,12 +718,12 @@ public class DreamService extends Service implements Window.Callback { * Starts dozing, entering a deep dreamy sleep. * <p> * Dozing enables the system to conserve power while the user is not actively interacting - * with the device. While dozing, the display will remain on in a low-power state + * with the device. While dozing, the display will remain on in a low-power state * and will continue to show its previous contents but the application processor and * other system components will be allowed to suspend when possible. * </p><p> * While the application processor is suspended, the dream may stop executing code - * for long periods of time. Prior to being suspended, the dream may schedule periodic + * for long periods of time. Prior to being suspended, the dream may schedule periodic * wake-ups to render new content by scheduling an alarm with the {@link AlarmManager}. * The dream may also keep the CPU awake by acquiring a * {@link android.os.PowerManager#PARTIAL_WAKE_LOCK partial wake lock} when necessary. @@ -731,7 +732,7 @@ public class DreamService extends Service implements Window.Callback { * awake for very long. * </p><p> * It is a good idea to call this method some time after the dream's entry animation - * has completed and the dream is ready to doze. It is important to completely + * has completed and the dream is ready to doze. It is important to completely * finish all of the work needed before dozing since the application processor may * be suspended at any moment once this method is called unless other wake locks * are being held. @@ -752,7 +753,7 @@ public class DreamService extends Service implements Window.Callback { private void updateDoze() { if (mDreamToken == null) { - Slog.w(TAG, "Updating doze without a dream token."); + Slog.w(mTag, "Updating doze without a dream token."); return; } @@ -768,7 +769,7 @@ public class DreamService extends Service implements Window.Callback { /** * Stops dozing, returns to active dreaming. * <p> - * This method reverses the effect of {@link #startDozing}. From this moment onward, + * This method reverses the effect of {@link #startDozing}. From this moment onward, * the application processor will be kept awake as long as the dream is running * or until the dream starts dozing again. * </p> @@ -790,12 +791,11 @@ public class DreamService extends Service implements Window.Callback { /** * Returns true if the dream will allow the system to enter a low-power state while - * it is running without actually turning off the screen. Defaults to false, + * it is running without actually turning off the screen. Defaults to false, * keeping the application processor awake while the dream is running. * * @return True if the dream is dozing. * - * @see #setDozing(boolean) * @hide For use by system UI components only. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) @@ -822,7 +822,7 @@ public class DreamService extends Service implements Window.Callback { * Sets the screen state to use while dozing. * <p> * The value of this property determines the power state of the primary display - * once {@link #startDozing} has been called. The default value is + * once {@link #startDozing} has been called. The default value is * {@link Display#STATE_UNKNOWN} which lets the system decide. * The dream may set a different state before starting to doze and may * perform transitions between states while dozing to conserve power and @@ -836,7 +836,7 @@ public class DreamService extends Service implements Window.Callback { * If not using Sidekick, it is recommended that the state be set to * {@link Display#STATE_DOZE_SUSPEND} once the dream has completely * finished drawing and before it releases its wakelock - * to allow the display hardware to be fully suspended. While suspended, + * to allow the display hardware to be fully suspended. While suspended, * the display will preserve its on-screen contents. * </p><p> * If the doze suspend state is used, the dream must make sure to set the mode back @@ -844,7 +844,7 @@ public class DreamService extends Service implements Window.Callback { * since the display updates may be ignored and not seen by the user otherwise. * </p><p> * The set of available display power states and their behavior while dozing is - * hardware dependent and may vary across devices. The dream may therefore + * hardware dependent and may vary across devices. The dream may therefore * need to be modified or configured to correctly support the hardware. * </p> * @@ -883,19 +883,19 @@ public class DreamService extends Service implements Window.Callback { * Sets the screen brightness to use while dozing. * <p> * The value of this property determines the power state of the primary display - * once {@link #startDozing} has been called. The default value is + * once {@link #startDozing} has been called. The default value is * {@link PowerManager#BRIGHTNESS_DEFAULT} which lets the system decide. * The dream may set a different brightness before starting to doze and may adjust * the brightness while dozing to conserve power and achieve various effects. * </p><p> * Note that dream may specify any brightness in the full 0-255 range, including * values that are less than the minimum value for manual screen brightness - * adjustments by the user. In particular, the value may be set to 0 which may + * adjustments by the user. In particular, the value may be set to 0 which may * turn off the backlight entirely while still leaving the screen on although * this behavior is device dependent and not guaranteed. * </p><p> * The available range of display brightness values and their behavior while dozing is - * hardware dependent and may vary across devices. The dream may therefore + * hardware dependent and may vary across devices. The dream may therefore * need to be modified or configured to correctly support the hardware. * </p> * @@ -922,7 +922,7 @@ public class DreamService extends Service implements Window.Callback { */ @Override public void onCreate() { - if (mDebug) Slog.v(TAG, "onCreate()"); + if (mDebug) Slog.v(mTag, "onCreate()"); super.onCreate(); } @@ -930,7 +930,7 @@ public class DreamService extends Service implements Window.Callback { * Called when the dream's window has been created and is visible and animation may now begin. */ public void onDreamingStarted() { - if (mDebug) Slog.v(TAG, "onDreamingStarted()"); + if (mDebug) Slog.v(mTag, "onDreamingStarted()"); // hook for subclasses } @@ -939,7 +939,7 @@ public class DreamService extends Service implements Window.Callback { * before the window has been removed. */ public void onDreamingStopped() { - if (mDebug) Slog.v(TAG, "onDreamingStopped()"); + if (mDebug) Slog.v(mTag, "onDreamingStopped()"); // hook for subclasses } @@ -947,11 +947,11 @@ public class DreamService extends Service implements Window.Callback { * Called when the dream is being asked to stop itself and wake. * <p> * The default implementation simply calls {@link #finish} which ends the dream - * immediately. Subclasses may override this function to perform a smooth exit + * immediately. Subclasses may override this function to perform a smooth exit * transition then call {@link #finish} afterwards. * </p><p> * Note that the dream will only be given a short period of time (currently about - * five seconds) to wake up. If the dream does not finish itself in a timely manner + * five seconds) to wake up. If the dream does not finish itself in a timely manner * then the system will forcibly finish it once the time allowance is up. * </p> */ @@ -962,7 +962,7 @@ public class DreamService extends Service implements Window.Callback { /** {@inheritDoc} */ @Override public final IBinder onBind(Intent intent) { - if (mDebug) Slog.v(TAG, "onBind() intent = " + intent); + if (mDebug) Slog.v(mTag, "onBind() intent = " + intent); mDreamServiceWrapper = new DreamServiceWrapper(); // Connect to the overlay service if present. @@ -981,7 +981,7 @@ public class DreamService extends Service implements Window.Callback { * </p> */ public final void finish() { - if (mDebug) Slog.v(TAG, "finish(): mFinished=" + mFinished); + if (mDebug) Slog.v(mTag, "finish(): mFinished=" + mFinished); Activity activity = mActivity; if (activity != null) { @@ -998,7 +998,7 @@ public class DreamService extends Service implements Window.Callback { mFinished = true; if (mDreamToken == null) { - Slog.w(TAG, "Finish was called before the dream was attached."); + Slog.w(mTag, "Finish was called before the dream was attached."); stopSelf(); return; } @@ -1026,8 +1026,10 @@ public class DreamService extends Service implements Window.Callback { } private void wakeUp(boolean fromSystem) { - if (mDebug) Slog.v(TAG, "wakeUp(): fromSystem=" + fromSystem - + ", mWaking=" + mWaking + ", mFinished=" + mFinished); + if (mDebug) { + Slog.v(mTag, "wakeUp(): fromSystem=" + fromSystem + ", mWaking=" + mWaking + + ", mFinished=" + mFinished); + } if (!mWaking && !mFinished) { mWaking = true; @@ -1052,7 +1054,7 @@ public class DreamService extends Service implements Window.Callback { // it we were finishing immediately. if (!fromSystem && !mFinished) { if (mActivity == null) { - Slog.w(TAG, "WakeUp was called before the dream was attached."); + Slog.w(mTag, "WakeUp was called before the dream was attached."); } else { try { mDreamManager.finishSelf(mDreamToken, false /*immediate*/); @@ -1067,7 +1069,7 @@ public class DreamService extends Service implements Window.Callback { /** {@inheritDoc} */ @Override public void onDestroy() { - if (mDebug) Slog.v(TAG, "onDestroy()"); + if (mDebug) Slog.v(mTag, "onDestroy()"); // hook for subclasses // Just in case destroy came in before detach, let's take care of that now @@ -1083,9 +1085,9 @@ public class DreamService extends Service implements Window.Callback { * * Must run on mHandler. */ - private final void detach() { + private void detach() { if (mStarted) { - if (mDebug) Slog.v(TAG, "detach(): Calling onDreamingStopped()"); + if (mDebug) Slog.v(mTag, "detach(): Calling onDreamingStopped()"); mStarted = false; onDreamingStopped(); } @@ -1110,12 +1112,12 @@ public class DreamService extends Service implements Window.Callback { */ private void attach(IBinder dreamToken, boolean canDoze, IRemoteCallback started) { if (mDreamToken != null) { - Slog.e(TAG, "attach() called when dream with token=" + mDreamToken + Slog.e(mTag, "attach() called when dream with token=" + mDreamToken + " already attached"); return; } if (mFinished || mWaking) { - Slog.w(TAG, "attach() called after dream already finished"); + Slog.w(mTag, "attach() called after dream already finished"); try { mDreamManager.finishSelf(dreamToken, true /*immediate*/); } catch (RemoteException ex) { @@ -1158,10 +1160,9 @@ public class DreamService extends Service implements Window.Callback { try { if (!ActivityTaskManager.getService().startDreamActivity(i)) { detach(); - return; } } catch (RemoteException e) { - Log.w(TAG, "Could not connect to activity task manager to start dream activity"); + Log.w(mTag, "Could not connect to activity task manager to start dream activity"); e.rethrowFromSystemServer(); } } else { @@ -1191,7 +1192,7 @@ public class DreamService extends Service implements Window.Callback { // along well. Dreams usually don't need such bars anyways, so disable them by default. mWindow.clearFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); - // Hide all insets when the dream is showing + // Hide all insets when the dream is showing mWindow.getDecorView().getWindowInsetsController().hide(WindowInsets.Type.systemBars()); mWindow.setDecorFitsSystemWindows(false); @@ -1207,7 +1208,7 @@ public class DreamService extends Service implements Window.Callback { try { overlay.startDream(mWindow.getAttributes(), mOverlayCallback); } catch (RemoteException e) { - Log.e(TAG, "could not send window attributes:" + e); + Log.e(mTag, "could not send window attributes:" + e); } }); } @@ -1243,17 +1244,12 @@ public class DreamService extends Service implements Window.Callback { @Override protected void dump(final FileDescriptor fd, PrintWriter pw, final String[] args) { - DumpUtils.dumpAsync(mHandler, new Dump() { - @Override - public void dump(PrintWriter pw, String prefix) { - dumpOnHandler(fd, pw, args); - } - }, pw, "", 1000); + DumpUtils.dumpAsync(mHandler, (pw1, prefix) -> dumpOnHandler(fd, pw1, args), pw, "", 1000); } /** @hide */ protected void dumpOnHandler(FileDescriptor fd, PrintWriter pw, String[] args) { - pw.print(TAG + ": "); + pw.print(mTag + ": "); if (mFinished) { pw.println("stopped"); } else { diff --git a/core/java/android/service/games/CreateGameSessionResult.aidl b/core/java/android/service/games/CreateGameSessionResult.aidl new file mode 100644 index 000000000000..b7c5e1602891 --- /dev/null +++ b/core/java/android/service/games/CreateGameSessionResult.aidl @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.games; + + +/** + * @hide + */ +parcelable CreateGameSessionResult; diff --git a/core/java/android/service/games/CreateGameSessionResult.java b/core/java/android/service/games/CreateGameSessionResult.java new file mode 100644 index 000000000000..8448b0f433b2 --- /dev/null +++ b/core/java/android/service/games/CreateGameSessionResult.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.games; + + +import android.annotation.Hide; +import android.annotation.NonNull; +import android.os.Parcel; +import android.os.Parcelable; +import android.view.SurfaceControlViewHost; + +/** + * Internal result object that contains the successful creation of a game session. + * + * @see IGameSessionService#create(CreateGameSessionRequest, GameSessionViewHostConfiguration, + * com.android.internal.infra.AndroidFuture) + * @hide + */ +@Hide +public final class CreateGameSessionResult implements Parcelable { + + @NonNull + public static final Parcelable.Creator<CreateGameSessionResult> CREATOR = + new Parcelable.Creator<CreateGameSessionResult>() { + @Override + public CreateGameSessionResult createFromParcel(Parcel source) { + return new CreateGameSessionResult( + IGameSession.Stub.asInterface(source.readStrongBinder()), + source.readParcelable( + SurfaceControlViewHost.SurfacePackage.class.getClassLoader(), + SurfaceControlViewHost.SurfacePackage.class)); + } + + @Override + public CreateGameSessionResult[] newArray(int size) { + return new CreateGameSessionResult[0]; + } + }; + + private final IGameSession mGameSession; + private final SurfaceControlViewHost.SurfacePackage mSurfacePackage; + + public CreateGameSessionResult( + @NonNull IGameSession gameSession, + @NonNull SurfaceControlViewHost.SurfacePackage surfacePackage) { + mGameSession = gameSession; + mSurfacePackage = surfacePackage; + } + + @NonNull + public IGameSession getGameSession() { + return mGameSession; + } + + @NonNull + public SurfaceControlViewHost.SurfacePackage getSurfacePackage() { + return mSurfacePackage; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeStrongBinder(mGameSession.asBinder()); + dest.writeParcelable(mSurfacePackage, flags); + } +} diff --git a/core/java/android/service/games/GameScreenshotResult.java b/core/java/android/service/games/GameScreenshotResult.java new file mode 100644 index 000000000000..ae76e08c7971 --- /dev/null +++ b/core/java/android/service/games/GameScreenshotResult.java @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.games; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.graphics.Bitmap; +import android.os.Parcel; +import android.os.Parcelable; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Objects; + +/** + * Result object for calls to {@link IGameSessionController#takeScreenshot}. + * + * It includes a status (see {@link #getStatus}) and, if the status is + * {@link #GAME_SCREENSHOT_SUCCESS} an {@link android.graphics.Bitmap} result (see {@link + * #getBitmap}). + * + * @hide + */ +public final class GameScreenshotResult implements Parcelable { + + /** + * The status of a call to {@link IGameSessionController#takeScreenshot} will be represented by + * one of these values. + * + * @hide + */ + @IntDef(flag = false, prefix = {"GAME_SCREENSHOT_"}, value = { + GAME_SCREENSHOT_SUCCESS, // 0 + GAME_SCREENSHOT_ERROR_INTERNAL_ERROR, // 1 + }) + @Retention(RetentionPolicy.SOURCE) + public @interface GameScreenshotStatus { + } + + /** + * Indicates that the result of a call to {@link IGameSessionController#takeScreenshot} was + * successful and an {@link android.graphics.Bitmap} result should be available by calling + * {@link #getBitmap}. + * + * @hide + */ + public static final int GAME_SCREENSHOT_SUCCESS = 0; + + /** + * Indicates that the result of a call to {@link IGameSessionController#takeScreenshot} failed + * due to an internal error. + * + * This error may occur if the device is not in a suitable state for a screenshot to be taken + * (e.g., the screen is off) or if the game task is not in a suitable state for a screenshot + * to be taken (e.g., the task is not visible). To make sure that the device and game are + * in a suitable state, the caller can monitor the lifecycle methods for the {@link + * GameSession} to make sure that the game task is focused. If the conditions are met, then the + * caller may try again immediately. + * + * @hide + */ + public static final int GAME_SCREENSHOT_ERROR_INTERNAL_ERROR = 1; + + @NonNull + public static final Parcelable.Creator<GameScreenshotResult> CREATOR = + new Parcelable.Creator<GameScreenshotResult>() { + @Override + public GameScreenshotResult createFromParcel(Parcel source) { + return new GameScreenshotResult( + source.readInt(), + source.readParcelable(null, Bitmap.class)); + } + + @Override + public GameScreenshotResult[] newArray(int size) { + return new GameScreenshotResult[0]; + } + }; + + @GameScreenshotStatus + private final int mStatus; + + @Nullable + private final Bitmap mBitmap; + + /** + * Creates a successful {@link GameScreenshotResult} with the provided bitmap. + */ + public static GameScreenshotResult createSuccessResult(@NonNull Bitmap bitmap) { + return new GameScreenshotResult(GAME_SCREENSHOT_SUCCESS, bitmap); + } + + /** + * Creates a failed {@link GameScreenshotResult} with an + * {@link #GAME_SCREENSHOT_ERROR_INTERNAL_ERROR} status. + */ + public static GameScreenshotResult createInternalErrorResult() { + return new GameScreenshotResult(GAME_SCREENSHOT_ERROR_INTERNAL_ERROR, null); + } + + private GameScreenshotResult(@GameScreenshotStatus int status, @Nullable Bitmap bitmap) { + this.mStatus = status; + this.mBitmap = bitmap; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mStatus); + dest.writeParcelable(mBitmap, flags); + } + + @GameScreenshotStatus + public int getStatus() { + return mStatus; + } + + /** + * Gets the {@link Bitmap} result from a successful screenshot attempt. + * + * @return The bitmap. + * @throws IllegalStateException if this method is called when {@link #getStatus} does not + * return {@link #GAME_SCREENSHOT_SUCCESS}. + */ + @NonNull + public Bitmap getBitmap() { + if (mBitmap == null) { + throw new IllegalStateException("Bitmap not available for failed screenshot result"); + } + return mBitmap; + } + + @Override + public String toString() { + return "GameScreenshotResult{" + + "mStatus=" + + mStatus + + ", has bitmap='" + + mBitmap != null ? "yes" : "no" + + "\'}"; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + + if (!(o instanceof GameScreenshotResult)) { + return false; + } + + GameScreenshotResult that = (GameScreenshotResult) o; + return mStatus == that.mStatus + && Objects.equals(mBitmap, that.mBitmap); + } + + @Override + public int hashCode() { + return Objects.hash(mStatus, mBitmap); + } +} diff --git a/core/java/android/service/games/GameSession.java b/core/java/android/service/games/GameSession.java index 0ff08c08932b..cb5c19b72bd0 100644 --- a/core/java/android/service/games/GameSession.java +++ b/core/java/android/service/games/GameSession.java @@ -16,11 +16,32 @@ package android.service.games; +import android.annotation.Hide; +import android.annotation.IntDef; +import android.annotation.MainThread; +import android.annotation.NonNull; import android.annotation.SystemApi; +import android.content.Context; +import android.content.res.Configuration; +import android.graphics.Bitmap; +import android.graphics.Rect; import android.os.Handler; +import android.os.Looper; +import android.os.RemoteException; +import android.util.Slog; +import android.view.SurfaceControlViewHost; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.infra.AndroidFuture; import com.android.internal.util.function.pooled.PooledLambda; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.concurrent.Executor; + /** * An active game session, providing a facility for the implementation to interact with the game. * @@ -28,25 +49,177 @@ import com.android.internal.util.function.pooled.PooledLambda; * which is then returned when a game session is created via * {@link GameSessionService#onNewSession(CreateGameSessionRequest)}. * + * This class exposes various lifecycle methods which are guaranteed to be called in the following + * fashion: + * + * {@link #onCreate()}: Will always be the first lifecycle method to be called, once the game + * session is created. + * + * {@link #onGameTaskFocusChanged(boolean)}: Will be called after {@link #onCreate()} with + * focused=true when the game task first comes into focus (if it does). If the game task is focused + * when the game session is created, this method will be called immediately after + * {@link #onCreate()} with focused=true. After this method is called with focused=true, it will be + * called again with focused=false when the task goes out of focus. If this method is ever called + * with focused=true, it is guaranteed to be called again with focused=false before + * {@link #onDestroy()} is called. If the game task never comes into focus during the session + * lifetime, this method will never be called. + * + * {@link #onDestroy()}: Will always be called after {@link #onCreate()}. If the game task ever + * comes into focus before the game session is destroyed, then this method will be called after one + * or more pairs of calls to {@link #onGameTaskFocusChanged(boolean)}. + * * @hide */ @SystemApi public abstract class GameSession { + private static final String TAG = "GameSession"; + private static final boolean DEBUG = false; final IGameSession mInterface = new IGameSession.Stub() { @Override - public void destroy() { + public void onDestroyed() { Handler.getMain().executeOrSendMessage(PooledLambda.obtainMessage( GameSession::doDestroy, GameSession.this)); } + + @Override + public void onTaskFocusChanged(boolean focused) { + Handler.getMain().executeOrSendMessage(PooledLambda.obtainMessage( + GameSession::moveToState, GameSession.this, + focused ? LifecycleState.TASK_FOCUSED : LifecycleState.TASK_UNFOCUSED)); + } }; + /** + * @hide + */ + @VisibleForTesting + public enum LifecycleState { + // Initial state; may transition to CREATED. + INITIALIZED, + // May transition to TASK_FOCUSED or DESTROYED. + CREATED, + // May transition to TASK_UNFOCUSED. + TASK_FOCUSED, + // May transition to TASK_FOCUSED or DESTROYED. + TASK_UNFOCUSED, + // May not transition once reached. + DESTROYED + } + + private LifecycleState mLifecycleState = LifecycleState.INITIALIZED; + private IGameSessionController mGameSessionController; + private int mTaskId; + private GameSessionRootView mGameSessionRootView; + private SurfaceControlViewHost mSurfaceControlViewHost; + + /** + * @hide + */ + @VisibleForTesting + public void attach( + IGameSessionController gameSessionController, + int taskId, + @NonNull Context context, + @NonNull SurfaceControlViewHost surfaceControlViewHost, + int widthPx, + int heightPx) { + mGameSessionController = gameSessionController; + mTaskId = taskId; + mSurfaceControlViewHost = surfaceControlViewHost; + mGameSessionRootView = new GameSessionRootView(context, mSurfaceControlViewHost); + surfaceControlViewHost.setView(mGameSessionRootView, widthPx, heightPx); + } + + @Hide void doCreate() { - onCreate(); + moveToState(LifecycleState.CREATED); } + @Hide void doDestroy() { - onDestroy(); + mSurfaceControlViewHost.release(); + moveToState(LifecycleState.DESTROYED); + } + + /** + * @hide + */ + @VisibleForTesting + @MainThread + public void moveToState(LifecycleState newLifecycleState) { + if (DEBUG) { + Slog.d(TAG, "moveToState: " + mLifecycleState + " -> " + newLifecycleState); + } + + if (Looper.myLooper() != Looper.getMainLooper()) { + throw new RuntimeException("moveToState should be used only from the main thread"); + } + + if (mLifecycleState == newLifecycleState) { + // Nothing to do. + return; + } + + switch (mLifecycleState) { + case INITIALIZED: + if (newLifecycleState == LifecycleState.CREATED) { + onCreate(); + } else if (newLifecycleState == LifecycleState.DESTROYED) { + onCreate(); + onDestroy(); + } else { + if (DEBUG) { + Slog.d(TAG, "Ignoring moveToState: INITIALIZED -> " + newLifecycleState); + } + return; + } + break; + case CREATED: + if (newLifecycleState == LifecycleState.TASK_FOCUSED) { + onGameTaskFocusChanged(/*focused=*/ true); + } else if (newLifecycleState == LifecycleState.DESTROYED) { + onDestroy(); + } else { + if (DEBUG) { + Slog.d(TAG, "Ignoring moveToState: CREATED -> " + newLifecycleState); + } + return; + } + break; + case TASK_FOCUSED: + if (newLifecycleState == LifecycleState.TASK_UNFOCUSED) { + onGameTaskFocusChanged(/*focused=*/ false); + } else if (newLifecycleState == LifecycleState.DESTROYED) { + onGameTaskFocusChanged(/*focused=*/ false); + onDestroy(); + } else { + if (DEBUG) { + Slog.d(TAG, "Ignoring moveToState: TASK_FOCUSED -> " + newLifecycleState); + } + return; + } + break; + case TASK_UNFOCUSED: + if (newLifecycleState == LifecycleState.TASK_FOCUSED) { + onGameTaskFocusChanged(/*focused=*/ true); + } else if (newLifecycleState == LifecycleState.DESTROYED) { + onDestroy(); + } else { + if (DEBUG) { + Slog.d(TAG, "Ignoring moveToState: TASK_UNFOCUSED -> " + newLifecycleState); + } + return; + } + break; + case DESTROYED: + if (DEBUG) { + Slog.d(TAG, "Ignoring moveToState: DESTROYED -> " + newLifecycleState); + } + return; + } + + mLifecycleState = newLifecycleState; } /** @@ -54,12 +227,173 @@ public abstract class GameSession { * * This should be used perform any setup required now that the game session is created. */ - public void onCreate() {} + public void onCreate() { + } /** - * Finalizer called when the game session is ending. + * Finalizer called when the game session is ending. This method will always be called after a + * call to {@link #onCreate()}. If the game task is ever in focus, this method will be called + * after one or more pairs of calls to {@link #onGameTaskFocusChanged(boolean)}. * * This should be used to perform any cleanup before the game session is destroyed. */ - public void onDestroy() {} + public void onDestroy() { + } + + /** + * Called when the game task for this session is or unfocused. The initial call to this method + * will always come after a call to {@link #onCreate()} with focused=true (when the game task + * first comes into focus after the session is created, or immediately after the session is + * created if the game task is already focused). + * + * This should be used to perform any setup required when the game task comes into focus or any + * cleanup that is required when the game task goes out of focus. + * + * @param focused True if the game task is focused, false if the game task is unfocused. + */ + public void onGameTaskFocusChanged(boolean focused) {} + + /** + * Sets the task overlay content to an explicit view. This view is placed directly into the game + * session's task overlay view hierarchy. It can itself be a complex view hierarchy. The size + * the task overlay view will always match the dimensions of the associated task's window. The + * {@code View} may not be cleared once set, but may be replaced by invoking + * {@link #setTaskOverlayView(View, ViewGroup.LayoutParams)} again. + * + * @param view The desired content to display. + * @param layoutParams Layout parameters for the view. + */ + public void setTaskOverlayView( + @NonNull View view, + @NonNull ViewGroup.LayoutParams layoutParams) { + mGameSessionRootView.removeAllViews(); + mGameSessionRootView.addView(view, layoutParams); + } + + /** + * Root view of the {@link SurfaceControlViewHost} associated with the {@link GameSession} + * instance. It is responsible for observing changes in the size of the window and resizing + * itself to match. + */ + private static final class GameSessionRootView extends FrameLayout { + private final SurfaceControlViewHost mSurfaceControlViewHost; + + GameSessionRootView(@NonNull Context context, + SurfaceControlViewHost surfaceControlViewHost) { + super(context); + mSurfaceControlViewHost = surfaceControlViewHost; + } + + @Override + protected void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + + // TODO(b/204504596): Investigate skipping the relayout in cases where the size has + // not changed. + Rect bounds = newConfig.windowConfiguration.getBounds(); + mSurfaceControlViewHost.relayout(bounds.width(), bounds.height()); + } + } + + /** + * Interface for returning screenshot outcome from calls to {@link #takeScreenshot}. + */ + public interface ScreenshotCallback { + + /** + * The status of a failed screenshot attempt provided by {@link #onFailure}. + * + * @hide + */ + @IntDef(flag = false, prefix = {"ERROR_TAKE_SCREENSHOT_"}, value = { + ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR, // 0 + }) + @Retention(RetentionPolicy.SOURCE) + @interface ScreenshotFailureStatus { + } + + /** + * An error code indicating that an internal error occurred when attempting to take a + * screenshot of the game task. If this code is returned, the caller should verify that the + * conditions for taking a screenshot are met (device screen is on and the game task is + * visible). To do so, the caller can monitor the lifecycle methods for this session to + * make sure that the game task is focused. If the conditions are met, then the caller may + * try again immediately. + */ + int ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR = 0; + + /** + * Called when taking the screenshot failed. + * @param statusCode Indicates the reason for failure. + */ + void onFailure(@ScreenshotFailureStatus int statusCode); + + /** + * Called when taking the screenshot succeeded. + * @param bitmap The screenshot. + */ + void onSuccess(@NonNull Bitmap bitmap); + } + + /** + * Takes a screenshot of the associated game. For this call to succeed, the device screen + * must be turned on and the game task must be visible. + * + * If the callback is called with {@link ScreenshotCallback#onSuccess}, the provided {@link + * Bitmap} may be used. + * + * If the callback is called with {@link ScreenshotCallback#onFailure}, the provided status + * code should be checked. + * + * If the status code is {@link ScreenshotCallback#ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR}, + * then the caller should verify that the conditions for calling this method are met (device + * screen is on and the game task is visible). To do so, the caller can monitor the lifecycle + * methods for this session to make sure that the game task is focused. If the conditions are + * met, then the caller may try again immediately. + * + * @param executor Executor on which to run the callback. + * @param callback The callback invoked when taking screenshot has succeeded + * or failed. + * @throws IllegalStateException if this method is called prior to {@link #onCreate}. + */ + public void takeScreenshot(@NonNull Executor executor, @NonNull ScreenshotCallback callback) { + if (mGameSessionController == null) { + throw new IllegalStateException("Can not call before onCreate()"); + } + + AndroidFuture<GameScreenshotResult> takeScreenshotResult = + new AndroidFuture<GameScreenshotResult>().whenCompleteAsync((result, error) -> { + handleScreenshotResult(callback, result, error); + }, executor); + + try { + mGameSessionController.takeScreenshot(mTaskId, takeScreenshotResult); + } catch (RemoteException ex) { + takeScreenshotResult.completeExceptionally(ex); + } + } + + private void handleScreenshotResult( + @NonNull ScreenshotCallback callback, + @NonNull GameScreenshotResult result, + @NonNull Throwable error) { + if (error != null) { + Slog.w(TAG, error.getMessage(), error.getCause()); + callback.onFailure( + ScreenshotCallback.ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR); + return; + } + + @GameScreenshotResult.GameScreenshotStatus int status = result.getStatus(); + switch (status) { + case GameScreenshotResult.GAME_SCREENSHOT_SUCCESS: + callback.onSuccess(result.getBitmap()); + break; + case GameScreenshotResult.GAME_SCREENSHOT_ERROR_INTERNAL_ERROR: + Slog.w(TAG, "Error taking screenshot"); + callback.onFailure( + ScreenshotCallback.ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR); + break; + } + } } diff --git a/core/java/android/service/games/GameSessionService.java b/core/java/android/service/games/GameSessionService.java index c1a3eb5286c4..df5bad5c53b2 100644 --- a/core/java/android/service/games/GameSessionService.java +++ b/core/java/android/service/games/GameSessionService.java @@ -22,8 +22,12 @@ import android.annotation.SdkConstant; import android.annotation.SystemApi; import android.app.Service; import android.content.Intent; +import android.hardware.display.DisplayManager; +import android.os.Binder; import android.os.Handler; import android.os.IBinder; +import android.view.Display; +import android.view.SurfaceControlViewHost; import com.android.internal.infra.AndroidFuture; import com.android.internal.util.function.pooled.PooledLambda; @@ -48,8 +52,6 @@ import java.util.Objects; */ @SystemApi public abstract class GameSessionService extends Service { - private static final String TAG = "GameSessionService"; - /** * The {@link Intent} action used when binding to the service. * To be supported, the service must require the @@ -62,15 +64,28 @@ public abstract class GameSessionService extends Service { private final IGameSessionService mInterface = new IGameSessionService.Stub() { @Override - public void create(CreateGameSessionRequest createGameSessionRequest, + public void create( + IGameSessionController gameSessionController, + CreateGameSessionRequest createGameSessionRequest, + GameSessionViewHostConfiguration gameSessionViewHostConfiguration, AndroidFuture gameSessionFuture) { Handler.getMain().post(PooledLambda.obtainRunnable( GameSessionService::doCreate, GameSessionService.this, + gameSessionController, createGameSessionRequest, + gameSessionViewHostConfiguration, gameSessionFuture)); } }; + private DisplayManager mDisplayManager; + + @Override + public void onCreate() { + super.onCreate(); + mDisplayManager = this.getSystemService(DisplayManager.class); + } + @Override @Nullable public final IBinder onBind(@Nullable Intent intent) { @@ -85,12 +100,39 @@ public abstract class GameSessionService extends Service { return mInterface.asBinder(); } - private void doCreate(CreateGameSessionRequest createGameSessionRequest, - AndroidFuture<IBinder> gameSessionFuture) { + private void doCreate( + IGameSessionController gameSessionController, + CreateGameSessionRequest createGameSessionRequest, + GameSessionViewHostConfiguration gameSessionViewHostConfiguration, + AndroidFuture<CreateGameSessionResult> createGameSessionResultFuture) { GameSession gameSession = onNewSession(createGameSessionRequest); Objects.requireNonNull(gameSession); - gameSessionFuture.complete(gameSession.mInterface.asBinder()); + Display display = mDisplayManager.getDisplay(gameSessionViewHostConfiguration.mDisplayId); + if (display == null) { + createGameSessionResultFuture.completeExceptionally( + new IllegalStateException("No display found for id: " + + gameSessionViewHostConfiguration.mDisplayId)); + return; + } + + IBinder hostToken = new Binder(); + SurfaceControlViewHost surfaceControlViewHost = + new SurfaceControlViewHost(this, display, hostToken); + + gameSession.attach( + gameSessionController, + createGameSessionRequest.getTaskId(), + this, + surfaceControlViewHost, + gameSessionViewHostConfiguration.mWidthPx, + gameSessionViewHostConfiguration.mHeightPx); + + CreateGameSessionResult createGameSessionResult = + new CreateGameSessionResult(gameSession.mInterface, + surfaceControlViewHost.getSurfacePackage()); + + createGameSessionResultFuture.complete(createGameSessionResult); gameSession.doCreate(); } diff --git a/core/java/android/service/games/GameSessionViewHostConfiguration.aidl b/core/java/android/service/games/GameSessionViewHostConfiguration.aidl new file mode 100644 index 000000000000..b900b9d09b07 --- /dev/null +++ b/core/java/android/service/games/GameSessionViewHostConfiguration.aidl @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.games; + +/** + * @hide + */ +parcelable GameSessionViewHostConfiguration; diff --git a/core/java/android/service/games/GameSessionViewHostConfiguration.java b/core/java/android/service/games/GameSessionViewHostConfiguration.java new file mode 100644 index 000000000000..53db0dfae8b2 --- /dev/null +++ b/core/java/android/service/games/GameSessionViewHostConfiguration.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.games; + +import android.annotation.Hide; +import android.annotation.NonNull; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Objects; + +/** + * Represents the configuration of the {@link android.view.SurfaceControlViewHost} used to render + * the overlay for a game session. + * + * @hide + */ +@Hide +public final class GameSessionViewHostConfiguration implements Parcelable { + + @NonNull + public static final Creator<GameSessionViewHostConfiguration> CREATOR = + new Creator<GameSessionViewHostConfiguration>() { + @Override + public GameSessionViewHostConfiguration createFromParcel(Parcel source) { + return new GameSessionViewHostConfiguration( + source.readInt(), + source.readInt(), + source.readInt()); + } + + @Override + public GameSessionViewHostConfiguration[] newArray(int size) { + return new GameSessionViewHostConfiguration[0]; + } + }; + + final int mDisplayId; + final int mWidthPx; + final int mHeightPx; + + public GameSessionViewHostConfiguration(int displayId, int widthPx, int heightPx) { + this.mDisplayId = displayId; + this.mWidthPx = widthPx; + this.mHeightPx = heightPx; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mDisplayId); + dest.writeInt(mWidthPx); + dest.writeInt(mHeightPx); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof GameSessionViewHostConfiguration)) return false; + GameSessionViewHostConfiguration that = (GameSessionViewHostConfiguration) o; + return mDisplayId == that.mDisplayId && mWidthPx == that.mWidthPx + && mHeightPx == that.mHeightPx; + } + + @Override + public int hashCode() { + return Objects.hash(mDisplayId, mWidthPx, mHeightPx); + } + + @Override + public String toString() { + return "GameSessionViewHostConfiguration{" + + "mDisplayId=" + mDisplayId + + ", mWidthPx=" + mWidthPx + + ", mHeightPx=" + mHeightPx + + '}'; + } +} diff --git a/core/java/android/service/games/IGameSession.aidl b/core/java/android/service/games/IGameSession.aidl index b2e9f1d21f6e..71da6302b63d 100644 --- a/core/java/android/service/games/IGameSession.aidl +++ b/core/java/android/service/games/IGameSession.aidl @@ -20,5 +20,6 @@ package android.service.games; * @hide */ oneway interface IGameSession { - void destroy(); + void onDestroyed(); + void onTaskFocusChanged(boolean focused); } diff --git a/core/java/android/service/games/IGameSessionController.aidl b/core/java/android/service/games/IGameSessionController.aidl new file mode 100644 index 000000000000..fe1d3629918e --- /dev/null +++ b/core/java/android/service/games/IGameSessionController.aidl @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.games; + +import com.android.internal.infra.AndroidFuture; + +/** + * @hide + */ +oneway interface IGameSessionController { + void takeScreenshot(int taskId, in AndroidFuture gameScreenshotResultFuture); +}
\ No newline at end of file diff --git a/core/java/android/service/games/IGameSessionService.aidl b/core/java/android/service/games/IGameSessionService.aidl index 2a53ea7f8e4a..37cde561f549 100644 --- a/core/java/android/service/games/IGameSessionService.aidl +++ b/core/java/android/service/games/IGameSessionService.aidl @@ -16,8 +16,10 @@ package android.service.games; +import android.service.games.IGameSessionController; import android.service.games.IGameSession; import android.service.games.CreateGameSessionRequest; +import android.service.games.GameSessionViewHostConfiguration; import com.android.internal.infra.AndroidFuture; @@ -27,6 +29,8 @@ import com.android.internal.infra.AndroidFuture; */ oneway interface IGameSessionService { void create( + in IGameSessionController gameSessionController, in CreateGameSessionRequest createGameSessionRequest, - in AndroidFuture /* T=IBinder for IGameSession */ gameSessionFuture); + in GameSessionViewHostConfiguration gameSessionViewHostConfiguration, + in AndroidFuture /* T=CreateGameSessionResult */ createGameSessionResultFuture); } diff --git a/core/java/android/service/notification/Condition.java b/core/java/android/service/notification/Condition.java index 4f324f9e35bf..267b2ff818a6 100644 --- a/core/java/android/service/notification/Condition.java +++ b/core/java/android/service/notification/Condition.java @@ -114,7 +114,7 @@ public final class Condition implements Parcelable { } public Condition(Parcel source) { - this((Uri)source.readParcelable(Condition.class.getClassLoader()), + this((Uri)source.readParcelable(Condition.class.getClassLoader(), android.net.Uri.class), source.readString(), source.readString(), source.readString(), diff --git a/core/java/android/service/notification/ConversationChannelWrapper.java b/core/java/android/service/notification/ConversationChannelWrapper.java index 3d0984ca80ee..35b6bad4e40b 100644 --- a/core/java/android/service/notification/ConversationChannelWrapper.java +++ b/core/java/android/service/notification/ConversationChannelWrapper.java @@ -40,10 +40,10 @@ public final class ConversationChannelWrapper implements Parcelable { public ConversationChannelWrapper() {} protected ConversationChannelWrapper(Parcel in) { - mNotificationChannel = in.readParcelable(NotificationChannel.class.getClassLoader()); + mNotificationChannel = in.readParcelable(NotificationChannel.class.getClassLoader(), android.app.NotificationChannel.class); mGroupLabel = in.readCharSequence(); mParentChannelLabel = in.readCharSequence(); - mShortcutInfo = in.readParcelable(ShortcutInfo.class.getClassLoader()); + mShortcutInfo = in.readParcelable(ShortcutInfo.class.getClassLoader(), android.content.pm.ShortcutInfo.class); mPkg = in.readStringNoHelper(); mUid = in.readInt(); } diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java index c94595468aec..ae39d3d3c2da 100644 --- a/core/java/android/service/notification/NotificationListenerService.java +++ b/core/java/android/service/notification/NotificationListenerService.java @@ -1763,7 +1763,7 @@ public abstract class NotificationListenerService extends Service { mImportanceExplanation = in.readCharSequence(); // may be null mRankingScore = in.readFloat(); mOverrideGroupKey = in.readString(); // may be null - mChannel = in.readParcelable(cl); // may be null + mChannel = in.readParcelable(cl, android.app.NotificationChannel.class); // may be null mOverridePeople = in.createStringArrayList(); mSnoozeCriteria = in.createTypedArrayList(SnoozeCriterion.CREATOR); mShowBadge = in.readBoolean(); @@ -1776,7 +1776,7 @@ public abstract class NotificationListenerService extends Service { mCanBubble = in.readBoolean(); mIsTextChanged = in.readBoolean(); mIsConversation = in.readBoolean(); - mShortcutInfo = in.readParcelable(cl); + mShortcutInfo = in.readParcelable(cl, android.content.pm.ShortcutInfo.class); mRankingAdjustment = in.readInt(); mIsBubble = in.readBoolean(); } diff --git a/core/java/android/service/notification/NotificationRankingUpdate.java b/core/java/android/service/notification/NotificationRankingUpdate.java index c64f4c46a769..a853714c0e9d 100644 --- a/core/java/android/service/notification/NotificationRankingUpdate.java +++ b/core/java/android/service/notification/NotificationRankingUpdate.java @@ -30,7 +30,7 @@ public class NotificationRankingUpdate implements Parcelable { } public NotificationRankingUpdate(Parcel in) { - mRankingMap = in.readParcelable(getClass().getClassLoader()); + mRankingMap = in.readParcelable(getClass().getClassLoader(), android.service.notification.NotificationListenerService.RankingMap.class); } public NotificationListenerService.RankingMap getRankingMap() { diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java index c1d5a28aa349..8834ceea7453 100644 --- a/core/java/android/service/notification/ZenModeConfig.java +++ b/core/java/android/service/notification/ZenModeConfig.java @@ -211,7 +211,7 @@ public class ZenModeConfig implements Parcelable { allowCallsFrom = source.readInt(); allowMessagesFrom = source.readInt(); user = source.readInt(); - manualRule = source.readParcelable(null); + manualRule = source.readParcelable(null, android.service.notification.ZenModeConfig.ZenRule.class); final int len = source.readInt(); if (len > 0) { final String[] ids = new String[len]; @@ -1800,10 +1800,10 @@ public class ZenModeConfig implements Parcelable { name = source.readString(); } zenMode = source.readInt(); - conditionId = source.readParcelable(null); - condition = source.readParcelable(null); - component = source.readParcelable(null); - configurationActivity = source.readParcelable(null); + conditionId = source.readParcelable(null, android.net.Uri.class); + condition = source.readParcelable(null, android.service.notification.Condition.class); + component = source.readParcelable(null, android.content.ComponentName.class); + configurationActivity = source.readParcelable(null, android.content.ComponentName.class); if (source.readInt() == 1) { id = source.readString(); } @@ -1811,7 +1811,7 @@ public class ZenModeConfig implements Parcelable { if (source.readInt() == 1) { enabler = source.readString(); } - zenPolicy = source.readParcelable(null); + zenPolicy = source.readParcelable(null, android.service.notification.ZenPolicy.class); modified = source.readInt() == 1; pkg = source.readString(); } diff --git a/core/java/android/service/notification/ZenPolicy.java b/core/java/android/service/notification/ZenPolicy.java index ed3a9ac33738..a04f07380ce8 100644 --- a/core/java/android/service/notification/ZenPolicy.java +++ b/core/java/android/service/notification/ZenPolicy.java @@ -804,8 +804,8 @@ public final class ZenPolicy implements Parcelable { @Override public ZenPolicy createFromParcel(Parcel source) { ZenPolicy policy = new ZenPolicy(); - policy.mPriorityCategories = source.readArrayList(Integer.class.getClassLoader()); - policy.mVisualEffects = source.readArrayList(Integer.class.getClassLoader()); + policy.mPriorityCategories = source.readArrayList(Integer.class.getClassLoader(), java.lang.Integer.class); + policy.mVisualEffects = source.readArrayList(Integer.class.getClassLoader(), java.lang.Integer.class); policy.mPriorityCalls = source.readInt(); policy.mPriorityMessages = source.readInt(); policy.mConversationSenders = source.readInt(); diff --git a/core/java/android/service/persistentdata/PersistentDataBlockManager.java b/core/java/android/service/persistentdata/PersistentDataBlockManager.java index 8242f4e2c9dc..44a886257d5a 100644 --- a/core/java/android/service/persistentdata/PersistentDataBlockManager.java +++ b/core/java/android/service/persistentdata/PersistentDataBlockManager.java @@ -17,6 +17,7 @@ package android.service.persistentdata; import android.annotation.IntDef; +import android.annotation.NonNull; import android.annotation.RequiresPermission; import android.annotation.SuppressLint; import android.annotation.SystemApi; @@ -25,6 +26,8 @@ import android.content.Context; import android.os.RemoteException; import android.service.oemlock.OemLockManager; +import com.android.internal.R; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -50,6 +53,7 @@ import java.lang.annotation.RetentionPolicy; @SystemService(Context.PERSISTENT_DATA_BLOCK_SERVICE) public class PersistentDataBlockManager { private static final String TAG = PersistentDataBlockManager.class.getSimpleName(); + private final Context mContext; private IPersistentDataBlockService sService; /** @@ -74,7 +78,10 @@ public class PersistentDataBlockManager { public @interface FlashLockState {} /** @hide */ - public PersistentDataBlockManager(IPersistentDataBlockService service) { + public PersistentDataBlockManager( + Context context, + IPersistentDataBlockService service) { + mContext = context; sService = service; } @@ -204,4 +211,15 @@ public class PersistentDataBlockManager { throw e.rethrowFromSystemServer(); } } + + /** + * Returns the package name which can access the persistent data partition. + * + * @hide + */ + @SystemApi + @NonNull + public String getPersistentDataPackageName() { + return mContext.getString(R.string.config_persistentDataPackageName); + } } diff --git a/core/java/android/service/quickaccesswallet/GetWalletCardsResponse.java b/core/java/android/service/quickaccesswallet/GetWalletCardsResponse.java index 0551e2709de6..7471a4f399a5 100644 --- a/core/java/android/service/quickaccesswallet/GetWalletCardsResponse.java +++ b/core/java/android/service/quickaccesswallet/GetWalletCardsResponse.java @@ -63,7 +63,7 @@ public final class GetWalletCardsResponse implements Parcelable { private static GetWalletCardsResponse readFromParcel(Parcel source) { int size = source.readInt(); List<WalletCard> walletCards = - source.readParcelableList(new ArrayList<>(size), WalletCard.class.getClassLoader()); + source.readParcelableList(new ArrayList<>(size), WalletCard.class.getClassLoader(), android.service.quickaccesswallet.WalletCard.class); int selectedIndex = source.readInt(); return new GetWalletCardsResponse(walletCards, selectedIndex); } diff --git a/core/java/android/service/security/attestationverification/OWNERS b/core/java/android/service/security/attestationverification/OWNERS new file mode 100644 index 000000000000..12c997868f3c --- /dev/null +++ b/core/java/android/service/security/attestationverification/OWNERS @@ -0,0 +1 @@ +include platform/frameworks/base:/core/java/android/security/attestationverification/OWNERS diff --git a/core/java/android/service/settings/suggestions/Suggestion.java b/core/java/android/service/settings/suggestions/Suggestion.java index 3e63efbda9c0..16622d70065f 100644 --- a/core/java/android/service/settings/suggestions/Suggestion.java +++ b/core/java/android/service/settings/suggestions/Suggestion.java @@ -120,9 +120,9 @@ public final class Suggestion implements Parcelable { mId = in.readString(); mTitle = in.readCharSequence(); mSummary = in.readCharSequence(); - mIcon = in.readParcelable(Icon.class.getClassLoader()); + mIcon = in.readParcelable(Icon.class.getClassLoader(), android.graphics.drawable.Icon.class); mFlags = in.readInt(); - mPendingIntent = in.readParcelable(PendingIntent.class.getClassLoader()); + mPendingIntent = in.readParcelable(PendingIntent.class.getClassLoader(), android.app.PendingIntent.class); } public static final @android.annotation.NonNull Creator<Suggestion> CREATOR = new Creator<Suggestion>() { diff --git a/core/java/android/service/smartspace/SmartspaceService.java b/core/java/android/service/smartspace/SmartspaceService.java index 7dd85cc8f988..b903fbeb035c 100644 --- a/core/java/android/service/smartspace/SmartspaceService.java +++ b/core/java/android/service/smartspace/SmartspaceService.java @@ -245,7 +245,9 @@ public abstract class SmartspaceService extends Service { public abstract void onDestroySmartspaceSession(@NonNull SmartspaceSessionId sessionId); private void doDestroy(@NonNull SmartspaceSessionId sessionId) { - Log.d(TAG, "doDestroy mSessionCallbacks: " + mSessionCallbacks); + if (DEBUG) { + Log.d(TAG, "doDestroy mSessionCallbacks: " + mSessionCallbacks); + } super.onDestroy(); mSessionCallbacks.remove(sessionId); onDestroySmartspaceSession(sessionId); diff --git a/core/java/android/service/timezone/TimeZoneProviderEvent.java b/core/java/android/service/timezone/TimeZoneProviderEvent.java index 700528116a8f..f6433b7f371e 100644 --- a/core/java/android/service/timezone/TimeZoneProviderEvent.java +++ b/core/java/android/service/timezone/TimeZoneProviderEvent.java @@ -141,7 +141,7 @@ public final class TimeZoneProviderEvent implements Parcelable { int type = in.readInt(); long creationElapsedMillis = in.readLong(); TimeZoneProviderSuggestion suggestion = - in.readParcelable(getClass().getClassLoader()); + in.readParcelable(getClass().getClassLoader(), android.service.timezone.TimeZoneProviderSuggestion.class); String failureCause = in.readString8(); return new TimeZoneProviderEvent( type, creationElapsedMillis, suggestion, failureCause); diff --git a/core/java/android/service/timezone/TimeZoneProviderSuggestion.java b/core/java/android/service/timezone/TimeZoneProviderSuggestion.java index 229fa268a47c..4841ac189034 100644 --- a/core/java/android/service/timezone/TimeZoneProviderSuggestion.java +++ b/core/java/android/service/timezone/TimeZoneProviderSuggestion.java @@ -100,7 +100,7 @@ public final class TimeZoneProviderSuggestion implements Parcelable { public TimeZoneProviderSuggestion createFromParcel(Parcel in) { @SuppressWarnings("unchecked") ArrayList<String> timeZoneIds = - (ArrayList<String>) in.readArrayList(null /* classLoader */); + (ArrayList<String>) in.readArrayList(null /* classLoader */, java.lang.String.class); long elapsedRealtimeMillis = in.readLong(); return new TimeZoneProviderSuggestion(timeZoneIds, elapsedRealtimeMillis); } diff --git a/core/java/android/service/trust/ITrustAgentService.aidl b/core/java/android/service/trust/ITrustAgentService.aidl index 21661db0606a..ec3b8575ed36 100644 --- a/core/java/android/service/trust/ITrustAgentService.aidl +++ b/core/java/android/service/trust/ITrustAgentService.aidl @@ -25,6 +25,7 @@ import android.service.trust.ITrustAgentServiceCallback; */ interface ITrustAgentService { oneway void onUnlockAttempt(boolean successful); + oneway void onUserRequestedUnlock(); oneway void onUnlockLockout(int timeoutMs); oneway void onTrustTimeout(); oneway void onDeviceLocked(); diff --git a/core/java/android/service/trust/TrustAgentService.java b/core/java/android/service/trust/TrustAgentService.java index 22ed1b8138b9..fba61cfd801e 100644 --- a/core/java/android/service/trust/TrustAgentService.java +++ b/core/java/android/service/trust/TrustAgentService.java @@ -186,6 +186,7 @@ public class TrustAgentService extends Service { private static final int MSG_ESCROW_TOKEN_ADDED = 7; private static final int MSG_ESCROW_TOKEN_STATE_RECEIVED = 8; private static final int MSG_ESCROW_TOKEN_REMOVED = 9; + private static final int MSG_USER_REQUESTED_UNLOCK = 10; private static final String EXTRA_TOKEN = "token"; private static final String EXTRA_TOKEN_HANDLE = "token_handle"; @@ -219,6 +220,9 @@ public class TrustAgentService extends Service { case MSG_UNLOCK_ATTEMPT: onUnlockAttempt(msg.arg1 != 0); break; + case MSG_USER_REQUESTED_UNLOCK: + onUserRequestedUnlock(); + break; case MSG_UNLOCK_LOCKOUT: onDeviceUnlockLockout(msg.arg1); break; @@ -306,7 +310,7 @@ public class TrustAgentService extends Service { * * @see #FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE * - * TODO(b/213631672): Hook up call from system server & SystemUI, then un-hide + * TODO(b/213631672): Add CTS tests * @hide */ public void onUserRequestedUnlock() { @@ -665,6 +669,11 @@ public class TrustAgentService extends Service { } @Override + public void onUserRequestedUnlock() { + mHandler.obtainMessage(MSG_USER_REQUESTED_UNLOCK).sendToTarget(); + } + + @Override public void onUnlockLockout(int timeoutMs) { mHandler.obtainMessage(MSG_UNLOCK_LOCKOUT, timeoutMs, 0).sendToTarget(); } diff --git a/core/java/android/service/wallpaper/IWallpaperEngine.aidl b/core/java/android/service/wallpaper/IWallpaperEngine.aidl index 81af6a220444..93d4def2180e 100644 --- a/core/java/android/service/wallpaper/IWallpaperEngine.aidl +++ b/core/java/android/service/wallpaper/IWallpaperEngine.aidl @@ -46,4 +46,5 @@ interface IWallpaperEngine { oneway void removeLocalColorsAreas(in List<RectF> regions); oneway void addLocalColorsAreas(in List<RectF> regions); SurfaceControl mirrorSurfaceControl(); + oneway void applyDimming(float dimAmount); } diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index 73ffd66486d2..dd4355d3b7a1 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -26,6 +26,7 @@ import static android.view.SurfaceControl.METADATA_WINDOW_TYPE; import static android.view.View.SYSTEM_UI_FLAG_VISIBLE; import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; +import android.animation.ValueAnimator; import android.annotation.FloatRange; import android.annotation.NonNull; import android.annotation.Nullable; @@ -159,6 +160,7 @@ public abstract class WallpaperService extends Service { private static final int MSG_ZOOM = 10100; private static final int MSG_SCALE_PREVIEW = 10110; private static final int MSG_REPORT_SHOWN = 10150; + private static final int MSG_UPDATE_DIMMING = 10200; private static final List<Float> PROHIBITED_STEPS = Arrays.asList(0f, Float.POSITIVE_INFINITY, Float.NEGATIVE_INFINITY); @@ -167,6 +169,8 @@ public abstract class WallpaperService extends Service { private static final boolean ENABLE_WALLPAPER_DIMMING = SystemProperties.getBoolean("persist.debug.enable_wallpaper_dimming", true); + private static final long DIMMING_ANIMATION_DURATION_MS = 300L; + private final ArrayList<Engine> mActiveEngines = new ArrayList<Engine>(); @@ -221,6 +225,9 @@ public abstract class WallpaperService extends Service { boolean mOffsetsChanged; boolean mFixedSizeAllowed; boolean mShouldDim; + // Whether the wallpaper should be dimmed by default (when no additional dimming is applied) + // based on its color hints + boolean mShouldDimByDefault; int mWidth; int mHeight; int mFormat; @@ -271,7 +278,10 @@ public abstract class WallpaperService extends Service { private Display mDisplay; private Context mDisplayContext; private int mDisplayState; + private @Surface.Rotation int mDisplayInstallOrientation; private float mWallpaperDimAmount = 0.05f; + private float mPreviousWallpaperDimAmount = mWallpaperDimAmount; + private float mDefaultDimAmount = mWallpaperDimAmount; SurfaceControl mSurfaceControl = new SurfaceControl(); SurfaceControl mBbqSurfaceControl; @@ -861,15 +871,34 @@ public abstract class WallpaperService extends Service { return; } int colorHints = colors.getColorHints(); - boolean shouldDim = ((colorHints & WallpaperColors.HINT_SUPPORTS_DARK_TEXT) == 0 + mShouldDimByDefault = ((colorHints & WallpaperColors.HINT_SUPPORTS_DARK_TEXT) == 0 && (colorHints & WallpaperColors.HINT_SUPPORTS_DARK_THEME) == 0); - if (shouldDim != mShouldDim) { - mShouldDim = shouldDim; + + // If default dimming value changes and no additional dimming is applied + if (mShouldDimByDefault != mShouldDim && mWallpaperDimAmount == 0f) { + mShouldDim = mShouldDimByDefault; updateSurfaceDimming(); updateSurface(false, false, true); } } + /** + * Update the dim amount of the wallpaper by updating the surface. + * + * @param dimAmount Float amount between [0.0, 1.0] to dim the wallpaper. + */ + private void updateWallpaperDimming(float dimAmount) { + mPreviousWallpaperDimAmount = mWallpaperDimAmount; + + // Custom dim amount cannot be less than the default dim amount. + mWallpaperDimAmount = Math.max(mDefaultDimAmount, dimAmount); + // If dim amount is 0f (additional dimming is removed), then the wallpaper should dim + // based on its default wallpaper color hints. + mShouldDim = dimAmount != 0f || mShouldDimByDefault; + updateSurfaceDimming(); + updateSurface(false, false, true); + } + private void updateSurfaceDimming() { if (!ENABLE_WALLPAPER_DIMMING || mBbqSurfaceControl == null) { return; @@ -878,9 +907,21 @@ public abstract class WallpaperService extends Service { // preview mode. if (!isPreview() && mShouldDim) { Log.v(TAG, "Setting wallpaper dimming: " + mWallpaperDimAmount); - new SurfaceControl.Transaction() - .setAlpha(mBbqSurfaceControl, 1 - mWallpaperDimAmount) - .apply(); + SurfaceControl.Transaction surfaceControl = new SurfaceControl.Transaction(); + + // Animate dimming to gradually change the wallpaper alpha from the previous + // dim amount to the new amount only if the dim amount changed. + ValueAnimator animator = ValueAnimator.ofFloat( + mPreviousWallpaperDimAmount, mWallpaperDimAmount); + animator.setDuration(mPreviousWallpaperDimAmount == mWallpaperDimAmount + ? 0 : DIMMING_ANIMATION_DURATION_MS); + animator.addUpdateListener((ValueAnimator va) -> { + final float dimValue = (float) va.getAnimatedValue(); + surfaceControl + .setAlpha(mBbqSurfaceControl, 1 - dimValue) + .apply(); + }); + animator.start(); } else { Log.v(TAG, "Setting wallpaper dimming: " + 0); new SurfaceControl.Transaction() @@ -1082,6 +1123,11 @@ public abstract class WallpaperService extends Service { mWindow, mLayout, mWidth, mHeight, View.VISIBLE, 0, -1, mWinFrames, mMergedConfiguration, mSurfaceControl, mInsetsState, mTempControls, mSurfaceSize); + + final int transformHint = SurfaceControl.rotationToBufferTransform( + (mDisplayInstallOrientation + mDisplay.getRotation()) % 4); + mSurfaceControl.setTransformHint(transformHint); + if (mSurfaceControl.isValid()) { if (mBbqSurfaceControl == null) { mBbqSurfaceControl = new SurfaceControl.Builder() @@ -1095,9 +1141,9 @@ public abstract class WallpaperService extends Service { .build(); updateSurfaceDimming(); } - // Propagate transform hint from WM so we can use the right hint for the + // Propagate transform hint from WM, so we can use the right hint for the // first frame. - mBbqSurfaceControl.setTransformHint(mSurfaceControl.getTransformHint()); + mBbqSurfaceControl.setTransformHint(transformHint); Surface blastSurface = getOrCreateBLASTSurface(mSurfaceSize.x, mSurfaceSize.y, mFormat); // If blastSurface == null that means it hasn't changed since the last @@ -1332,9 +1378,12 @@ public abstract class WallpaperService extends Service { // Use window context of TYPE_WALLPAPER so client can access UI resources correctly. mDisplayContext = createDisplayContext(mDisplay) .createWindowContext(TYPE_WALLPAPER, null /* options */); - mWallpaperDimAmount = mDisplayContext.getResources().getFloat( + mDefaultDimAmount = mDisplayContext.getResources().getFloat( com.android.internal.R.dimen.config_wallpaperDimAmount); + mWallpaperDimAmount = mDefaultDimAmount; + mPreviousWallpaperDimAmount = mWallpaperDimAmount; mDisplayState = mDisplay.getState(); + mDisplayInstallOrientation = mDisplay.getInstallOrientation(); if (DEBUG) Log.v(TAG, "onCreate(): " + this); onCreate(mSurfaceHolder); @@ -1587,6 +1636,7 @@ public abstract class WallpaperService extends Service { return; } Surface surface = mSurfaceHolder.getSurface(); + if (!surface.isValid()) return; boolean widthIsLarger = mSurfaceSize.x > mSurfaceSize.y; int smaller = widthIsLarger ? mSurfaceSize.x : mSurfaceSize.y; @@ -1647,7 +1697,7 @@ public abstract class WallpaperService extends Service { Log.e(TAG, "Error creating page local color bitmap", e); continue; } - WallpaperColors color = WallpaperColors.fromBitmap(target); + WallpaperColors color = WallpaperColors.fromBitmap(target, mWallpaperDimAmount); target.recycle(); WallpaperColors currentColor = page.getColors(area); @@ -2175,6 +2225,12 @@ public abstract class WallpaperService extends Service { mDetached.set(true); } + public void applyDimming(float dimAmount) throws RemoteException { + Message msg = mCaller.obtainMessageI(MSG_UPDATE_DIMMING, + Float.floatToIntBits(dimAmount)); + mCaller.sendMessage(msg); + } + public void scalePreview(Rect position) { Message msg = mCaller.obtainMessageO(MSG_SCALE_PREVIEW, position); mCaller.sendMessage(msg); @@ -2245,6 +2301,9 @@ public abstract class WallpaperService extends Service { case MSG_ZOOM: mEngine.setZoom(Float.intBitsToFloat(message.arg1)); break; + case MSG_UPDATE_DIMMING: + mEngine.updateWallpaperDimming(Float.intBitsToFloat(message.arg1)); + break; case MSG_SCALE_PREVIEW: mEngine.scalePreview((Rect) message.obj); break; diff --git a/core/java/android/service/wallpapereffectsgeneration/OWNERS b/core/java/android/service/wallpapereffectsgeneration/OWNERS new file mode 100644 index 000000000000..d2d3e2c0a7b6 --- /dev/null +++ b/core/java/android/service/wallpapereffectsgeneration/OWNERS @@ -0,0 +1,4 @@ +susharon@google.com +shanh@google.com +huiwu@google.com +srazdan@google.com diff --git a/core/java/android/speech/tts/Voice.java b/core/java/android/speech/tts/Voice.java index 7ffe5eb7893d..0d98a6ca5f14 100644 --- a/core/java/android/speech/tts/Voice.java +++ b/core/java/android/speech/tts/Voice.java @@ -84,7 +84,7 @@ public class Voice implements Parcelable { private Voice(Parcel in) { this.mName = in.readString(); - this.mLocale = (Locale)in.readSerializable(); + this.mLocale = (Locale)in.readSerializable(java.util.Locale.class.getClassLoader(), java.util.Locale.class); this.mQuality = in.readInt(); this.mLatency = in.readInt(); this.mRequiresNetworkConnection = (in.readByte() == 1); diff --git a/core/java/android/telephony/SubscriptionPlan.java b/core/java/android/telephony/SubscriptionPlan.java index d5ac4368aa97..fb2d7714d402 100644 --- a/core/java/android/telephony/SubscriptionPlan.java +++ b/core/java/android/telephony/SubscriptionPlan.java @@ -99,7 +99,7 @@ public final class SubscriptionPlan implements Parcelable { } private SubscriptionPlan(Parcel source) { - cycleRule = source.readParcelable(null); + cycleRule = source.readParcelable(null, android.util.RecurrenceRule.class); title = source.readCharSequence(); summary = source.readCharSequence(); dataLimitBytes = source.readLong(); diff --git a/core/java/android/text/FontConfig.java b/core/java/android/text/FontConfig.java index 2f7fb2f0ab9d..32b3bc62a8cd 100644 --- a/core/java/android/text/FontConfig.java +++ b/core/java/android/text/FontConfig.java @@ -143,9 +143,9 @@ public final class FontConfig implements Parcelable { @Override public FontConfig createFromParcel(Parcel source) { List<FontFamily> families = source.readParcelableList(new ArrayList<>(), - FontFamily.class.getClassLoader()); + FontFamily.class.getClassLoader(), android.text.FontConfig.FontFamily.class); List<Alias> aliases = source.readParcelableList(new ArrayList<>(), - Alias.class.getClassLoader()); + Alias.class.getClassLoader(), android.text.FontConfig.Alias.class); long lastModifiedDate = source.readLong(); int configVersion = source.readInt(); return new FontConfig(families, aliases, lastModifiedDate, configVersion); @@ -617,7 +617,7 @@ public final class FontConfig implements Parcelable { @Override public FontFamily createFromParcel(Parcel source) { List<Font> fonts = source.readParcelableList( - new ArrayList<>(), Font.class.getClassLoader()); + new ArrayList<>(), Font.class.getClassLoader(), android.text.FontConfig.Font.class); String name = source.readString8(); String langTags = source.readString8(); int variant = source.readInt(); diff --git a/core/java/android/text/PrecomputedText.java b/core/java/android/text/PrecomputedText.java index ce63376a6d63..be66db2a4c05 100644 --- a/core/java/android/text/PrecomputedText.java +++ b/core/java/android/text/PrecomputedText.java @@ -363,6 +363,9 @@ public class PrecomputedText implements Spannable { public String toString() { int lineBreakStyle = (mLineBreakConfig != null) ? mLineBreakConfig.getLineBreakStyle() : LineBreakConfig.LINE_BREAK_STYLE_NONE; + int lineBreakWordStyle = (mLineBreakConfig != null) + ? mLineBreakConfig.getLineBreakWordStyle() + : LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE; return "{" + "textSize=" + mPaint.getTextSize() + ", textScaleX=" + mPaint.getTextScaleX() @@ -376,6 +379,7 @@ public class PrecomputedText implements Spannable { + ", breakStrategy=" + mBreakStrategy + ", hyphenationFrequency=" + mHyphenationFrequency + ", lineBreakStyle=" + lineBreakStyle + + ", lineBreakWordStyle=" + lineBreakWordStyle + "}"; } }; diff --git a/core/java/android/text/style/EasyEditSpan.java b/core/java/android/text/style/EasyEditSpan.java index ccccdcf88b69..3da83332d62e 100644 --- a/core/java/android/text/style/EasyEditSpan.java +++ b/core/java/android/text/style/EasyEditSpan.java @@ -82,7 +82,7 @@ public class EasyEditSpan implements ParcelableSpan { * Constructor called from {@link TextUtils} to restore the span. */ public EasyEditSpan(@NonNull Parcel source) { - mPendingIntent = source.readParcelable(null); + mPendingIntent = source.readParcelable(null, android.app.PendingIntent.class); mDeleteEnabled = (source.readByte() == 1); } diff --git a/core/java/android/text/style/TextAppearanceSpan.java b/core/java/android/text/style/TextAppearanceSpan.java index 23557694a48d..adb379a397b7 100644 --- a/core/java/android/text/style/TextAppearanceSpan.java +++ b/core/java/android/text/style/TextAppearanceSpan.java @@ -249,7 +249,7 @@ public class TextAppearanceSpan extends MetricAffectingSpan implements Parcelabl mTypeface = LeakyTypefaceStorage.readTypefaceFromParcel(src); mTextFontWeight = src.readInt(); - mTextLocales = src.readParcelable(LocaleList.class.getClassLoader()); + mTextLocales = src.readParcelable(LocaleList.class.getClassLoader(), android.os.LocaleList.class); mShadowRadius = src.readFloat(); mShadowDx = src.readFloat(); diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java index 52f1faef0fc3..96a1fc60e458 100644 --- a/core/java/android/util/FeatureFlagUtils.java +++ b/core/java/android/util/FeatureFlagUtils.java @@ -81,6 +81,7 @@ public class FeatureFlagUtils { DEFAULT_FLAGS.put(SETTINGS_USE_NEW_BACKUP_ELIGIBILITY_RULES, "true"); DEFAULT_FLAGS.put(SETTINGS_ENABLE_SECURITY_HUB, "true"); DEFAULT_FLAGS.put(SETTINGS_SUPPORT_LARGE_SCREEN, "true"); + DEFAULT_FLAGS.put("settings_search_always_expand", "false"); DEFAULT_FLAGS.put(SETTINGS_APP_LANGUAGE_SELECTION, "false"); DEFAULT_FLAGS.put(SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS, "true"); } diff --git a/core/java/android/util/MemoryIntArray.java b/core/java/android/util/MemoryIntArray.java index 42181c3c1722..5cbbbef2cf88 100644 --- a/core/java/android/util/MemoryIntArray.java +++ b/core/java/android/util/MemoryIntArray.java @@ -80,7 +80,7 @@ public final class MemoryIntArray implements Parcelable, Closeable { private MemoryIntArray(Parcel parcel) throws IOException { mIsOwner = false; - ParcelFileDescriptor pfd = parcel.readParcelable(null); + ParcelFileDescriptor pfd = parcel.readParcelable(null, android.os.ParcelFileDescriptor.class); if (pfd == null) { throw new IOException("No backing file descriptor"); } diff --git a/core/java/android/util/SparseDoubleArray.java b/core/java/android/util/SparseDoubleArray.java index dc93a473fe44..e8d96d8e26e4 100644 --- a/core/java/android/util/SparseDoubleArray.java +++ b/core/java/android/util/SparseDoubleArray.java @@ -81,9 +81,17 @@ public class SparseDoubleArray implements Cloneable { * if no such mapping has been made. */ public double get(int key) { + return get(key, 0); + } + + /** + * Gets the double mapped from the specified key, or the specified value + * if no such mapping has been made. + */ + public double get(int key, double valueIfKeyNotFound) { final int index = mValues.indexOfKey(key); if (index < 0) { - return 0.0d; + return valueIfKeyNotFound; } return valueAt(index); } @@ -105,7 +113,7 @@ public class SparseDoubleArray implements Cloneable { * <p>This differs from {@link #put} because instead of replacing any previous value, it adds * (in the numerical sense) to it. */ - public void add(int key, double summand) { + public void incrementValue(int key, double summand) { final double oldValue = get(key); put(key, oldValue + summand); } @@ -138,6 +146,13 @@ public class SparseDoubleArray implements Cloneable { } /** + * Removes all key-value mappings from this SparseDoubleArray. + */ + public void clear() { + mValues.clear(); + } + + /** * {@inheritDoc} * * <p>This implementation composes a string by iterating over its mappings. diff --git a/core/java/android/util/SparseLongArray.java b/core/java/android/util/SparseLongArray.java index f2bc0c5a34d6..7185972b85bf 100644 --- a/core/java/android/util/SparseLongArray.java +++ b/core/java/android/util/SparseLongArray.java @@ -164,6 +164,30 @@ public class SparseLongArray implements Cloneable { } /** + * Adds a mapping from the specified key to the specified value, + * <b>adding</b> its value to the previous mapping from the specified key if there + * was one. + * + * <p>This differs from {@link #put} because instead of replacing any previous value, it adds + * (in the numerical sense) to it. + * + * @hide + */ + public void incrementValue(int key, long summand) { + int i = ContainerHelpers.binarySearch(mKeys, mSize, key); + + if (i >= 0) { + mValues[i] += summand; + } else { + i = ~i; + + mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key); + mValues = GrowingArrayUtils.insert(mValues, mSize, i, summand); + mSize++; + } + } + + /** * Returns the number of key-value mappings that this SparseLongArray * currently stores. */ diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java index 9b8523f9b006..b8eb6027b09c 100644 --- a/core/java/android/view/Choreographer.java +++ b/core/java/android/view/Choreographer.java @@ -779,7 +779,7 @@ public final class Choreographer { + "time to " + (lastFrameOffset * 0.000001f) + " ms in the past."); } frameTimeNanos = startNanos - lastFrameOffset; - frameData.setFrameTimeNanos(-lastFrameOffset); + frameData.setFrameTimeNanos(frameTimeNanos); } if (frameTimeNanos < mLastFrameTimeNanos) { diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java index 70266c1717a1..d6e074fbe178 100644 --- a/core/java/android/view/Display.java +++ b/core/java/android/view/Display.java @@ -928,6 +928,18 @@ public final class Display { } /** + * Returns the install orientation of the display. + * @hide + */ + @Surface.Rotation + public int getInstallOrientation() { + synchronized (mLock) { + updateDisplayInfoLocked(); + return mDisplayInfo.installOrientation; + } + } + + /** * @deprecated use {@link #getRotation} * @return orientation of this display. */ diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java index b8614ccde6fd..6917d664327f 100644 --- a/core/java/android/view/DisplayInfo.java +++ b/core/java/android/view/DisplayInfo.java @@ -318,6 +318,12 @@ public final class DisplayInfo implements Parcelable { @Nullable public RoundedCorners roundedCorners; + /** + * Install orientation of the display relative to its natural orientation. + */ + @Surface.Rotation + public int installOrientation; + public static final @android.annotation.NonNull Creator<DisplayInfo> CREATOR = new Creator<DisplayInfo>() { @Override public DisplayInfo createFromParcel(Parcel source) { @@ -389,7 +395,8 @@ public final class DisplayInfo implements Parcelable { && brightnessMaximum == other.brightnessMaximum && brightnessDefault == other.brightnessDefault && Objects.equals(roundedCorners, other.roundedCorners) - && shouldConstrainMetricsForLauncher == other.shouldConstrainMetricsForLauncher; + && shouldConstrainMetricsForLauncher == other.shouldConstrainMetricsForLauncher + && installOrientation == other.installOrientation; } @Override @@ -441,6 +448,7 @@ public final class DisplayInfo implements Parcelable { brightnessDefault = other.brightnessDefault; roundedCorners = other.roundedCorners; shouldConstrainMetricsForLauncher = other.shouldConstrainMetricsForLauncher; + installOrientation = other.installOrientation; } public void readFromParcel(Parcel source) { @@ -449,8 +457,8 @@ public final class DisplayInfo implements Parcelable { type = source.readInt(); displayId = source.readInt(); displayGroupId = source.readInt(); - address = source.readParcelable(null); - deviceProductInfo = source.readParcelable(null); + address = source.readParcelable(null, android.view.DisplayAddress.class); + deviceProductInfo = source.readParcelable(null, android.hardware.display.DeviceProductInfo.class); name = source.readString8(); appWidth = source.readInt(); appHeight = source.readInt(); @@ -475,7 +483,7 @@ public final class DisplayInfo implements Parcelable { for (int i = 0; i < nColorModes; i++) { supportedColorModes[i] = source.readInt(); } - hdrCapabilities = source.readParcelable(null); + hdrCapabilities = source.readParcelable(null, android.view.Display.HdrCapabilities.class); minimalPostProcessingSupported = source.readBoolean(); logicalDensityDpi = source.readInt(); physicalXDpi = source.readFloat(); @@ -498,6 +506,7 @@ public final class DisplayInfo implements Parcelable { userDisabledHdrTypes[i] = source.readInt(); } shouldConstrainMetricsForLauncher = source.readBoolean(); + installOrientation = source.readInt(); } @Override @@ -553,6 +562,7 @@ public final class DisplayInfo implements Parcelable { dest.writeInt(userDisabledHdrTypes[i]); } dest.writeBoolean(shouldConstrainMetricsForLauncher); + dest.writeInt(installOrientation); } @Override @@ -809,6 +819,8 @@ public final class DisplayInfo implements Parcelable { sb.append(brightnessDefault); sb.append(", shouldConstrainMetricsForLauncher "); sb.append(shouldConstrainMetricsForLauncher); + sb.append(", installOrientation "); + sb.append(Surface.rotationToString(installOrientation)); sb.append("}"); return sb.toString(); } diff --git a/core/java/android/view/GestureExclusionTracker.java b/core/java/android/view/GestureExclusionTracker.java deleted file mode 100644 index fffb323e6d50..000000000000 --- a/core/java/android/view/GestureExclusionTracker.java +++ /dev/null @@ -1,142 +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. - */ - -package android.view; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.graphics.Rect; - -import com.android.internal.util.Preconditions; - -import java.lang.ref.WeakReference; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; - -/** - * Used by {@link ViewRootImpl} to track system gesture exclusion rects reported by views. - */ -class GestureExclusionTracker { - private boolean mGestureExclusionViewsChanged = false; - private boolean mRootGestureExclusionRectsChanged = false; - private List<Rect> mRootGestureExclusionRects = Collections.emptyList(); - private List<GestureExclusionViewInfo> mGestureExclusionViewInfos = new ArrayList<>(); - private List<Rect> mGestureExclusionRects = Collections.emptyList(); - - public void updateRectsForView(@NonNull View view) { - boolean found = false; - final Iterator<GestureExclusionViewInfo> i = mGestureExclusionViewInfos.iterator(); - while (i.hasNext()) { - final GestureExclusionViewInfo info = i.next(); - final View v = info.getView(); - if (v == null || !v.isAttachedToWindow() || !v.isAggregatedVisible()) { - mGestureExclusionViewsChanged = true; - i.remove(); - continue; - } - if (v == view) { - found = true; - info.mDirty = true; - break; - } - } - if (!found && view.isAttachedToWindow()) { - mGestureExclusionViewInfos.add(new GestureExclusionViewInfo(view)); - mGestureExclusionViewsChanged = true; - } - } - - @Nullable - public List<Rect> computeChangedRects() { - boolean changed = mRootGestureExclusionRectsChanged; - final Iterator<GestureExclusionViewInfo> i = mGestureExclusionViewInfos.iterator(); - final List<Rect> rects = new ArrayList<>(mRootGestureExclusionRects); - while (i.hasNext()) { - final GestureExclusionViewInfo info = i.next(); - switch (info.update()) { - case GestureExclusionViewInfo.CHANGED: - changed = true; - // Deliberate fall-through - case GestureExclusionViewInfo.UNCHANGED: - rects.addAll(info.mExclusionRects); - break; - case GestureExclusionViewInfo.GONE: - mGestureExclusionViewsChanged = true; - i.remove(); - break; - } - } - if (changed || mGestureExclusionViewsChanged) { - mGestureExclusionViewsChanged = false; - mRootGestureExclusionRectsChanged = false; - if (!mGestureExclusionRects.equals(rects)) { - mGestureExclusionRects = rects; - return rects; - } - } - return null; - } - - public void setRootSystemGestureExclusionRects(@NonNull List<Rect> rects) { - Preconditions.checkNotNull(rects, "rects must not be null"); - mRootGestureExclusionRects = rects; - mRootGestureExclusionRectsChanged = true; - } - - @NonNull - public List<Rect> getRootSystemGestureExclusionRects() { - return mRootGestureExclusionRects; - } - - private static class GestureExclusionViewInfo { - public static final int CHANGED = 0; - public static final int UNCHANGED = 1; - public static final int GONE = 2; - - private final WeakReference<View> mView; - boolean mDirty = true; - List<Rect> mExclusionRects = Collections.emptyList(); - - GestureExclusionViewInfo(View view) { - mView = new WeakReference<>(view); - } - - public View getView() { - return mView.get(); - } - - public int update() { - final View excludedView = getView(); - if (excludedView == null || !excludedView.isAttachedToWindow() - || !excludedView.isAggregatedVisible()) return GONE; - final List<Rect> localRects = excludedView.getSystemGestureExclusionRects(); - final List<Rect> newRects = new ArrayList<>(localRects.size()); - for (Rect src : localRects) { - Rect mappedRect = new Rect(src); - ViewParent p = excludedView.getParent(); - if (p != null && p.getChildVisibleRect(excludedView, mappedRect, null)) { - newRects.add(mappedRect); - } - } - - if (mExclusionRects.equals(localRects)) return UNCHANGED; - mExclusionRects = newRects; - return CHANGED; - } - } -} diff --git a/core/java/android/view/HandwritingInitiator.java b/core/java/android/view/HandwritingInitiator.java index 8524ac846d1a..097d1d0df51b 100644 --- a/core/java/android/view/HandwritingInitiator.java +++ b/core/java/android/view/HandwritingInitiator.java @@ -19,7 +19,6 @@ package android.view; import android.annotation.NonNull; import android.annotation.Nullable; import android.graphics.Rect; -import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; import com.android.internal.annotations.VisibleForTesting; @@ -75,11 +74,6 @@ public class HandwritingInitiator { @VisibleForTesting public WeakReference<View> mConnectedView = null; - /** The editor bound reported by the connected View. */ - @Nullable - @VisibleForTesting - public Rect mEditorBound = null; - /** * When InputConnection restarts for a View, View#onInputConnectionCreatedInternal * might be called before View#onInputConnectionClosedInternal, so we need to count the input @@ -174,9 +168,8 @@ public class HandwritingInitiator { * @param view the view that created the current InputConnection. * @see #onInputConnectionClosed(View) */ - public void onInputConnectionCreated(@NonNull View view, @NonNull EditorInfo editorInfo) { + public void onInputConnectionCreated(@NonNull View view) { final View connectedView = getConnectedView(); -// updateEditorBound(editorInfo.getInitialEditorBound()); if (connectedView == view) { ++mConnectionCount; } else { @@ -198,33 +191,15 @@ public class HandwritingInitiator { --mConnectionCount; if (mConnectionCount == 0) { mConnectedView = null; - mEditorBound = null; } } else { // Unexpected branch, set mConnectedView to null to avoid further problem. mConnectedView = null; - mEditorBound = null; mConnectionCount = 0; } } /** - * Notify the HandwritingInitiator that editor bound of the connected view(the view with - * active InputConnection) has be updated. - * @param editorBound new the editor bounds of the connected view. - */ - public void updateEditorBound(@NonNull Rect editorBound) { - if (mEditorBound == null) { - mEditorBound = new Rect(editorBound); - } else { - mEditorBound.left = editorBound.left; - mEditorBound.top = editorBound.top; - mEditorBound.right = editorBound.right; - mEditorBound.bottom = editorBound.bottom; - } - } - - /** * Try to initiate handwriting. For this method to successfully send startHandwriting signal, * the following 3 conditions should meet: * a) The stylus movement exceeds the touchSlop. @@ -240,18 +215,19 @@ public class HandwritingInitiator { return; } final View connectedView = getConnectedView(); - if (connectedView == null || mEditorBound == null) { + if (connectedView == null) { return; } final ViewParent viewParent = connectedView.getParent(); // Do a final check before startHandwriting. if (viewParent != null && connectedView.isAttachedToWindow()) { - final Rect editorBounds = new Rect(mEditorBound); + final Rect editorBounds = + new Rect(0, 0, connectedView.getWidth(), connectedView.getHeight()); if (viewParent.getChildVisibleRect(connectedView, editorBounds, null)) { final int roundedInitX = Math.round(mState.mStylusDownX); final int roundedInitY = Math.round(mState.mStylusDownY); if (editorBounds.contains(roundedInitX, roundedInitY)) { - startHandwriting(mConnectedView.get()); + startHandwriting(connectedView); } } } @@ -261,7 +237,7 @@ public class HandwritingInitiator { /** For test only. */ @VisibleForTesting public void startHandwriting(View view) { - // mImm.startHandwriting(view); + mImm.startStylusHandwriting(view); } private boolean largerThanTouchSlop(float x1, float y1, float x2, float y2) { diff --git a/core/java/android/view/HapticFeedbackConstants.java b/core/java/android/view/HapticFeedbackConstants.java index c5bc99d042d7..45b65e551305 100644 --- a/core/java/android/view/HapticFeedbackConstants.java +++ b/core/java/android/view/HapticFeedbackConstants.java @@ -177,6 +177,10 @@ public class HapticFeedbackConstants { * Flag for {@link View#performHapticFeedback(int, int) * View.performHapticFeedback(int, int)}: Ignore the global setting * for whether to perform haptic feedback, do it always. + * + * @deprecated Starting from {@link android.os.Build.VERSION_CODES#TIRAMISU} only privileged + * apps can ignore user settings for touch feedback. */ + @Deprecated public static final int FLAG_IGNORE_GLOBAL_SETTING = 0x0002; } diff --git a/core/java/android/view/IDisplayWindowListener.aidl b/core/java/android/view/IDisplayWindowListener.aidl index f95d6b349221..449e9b325904 100644 --- a/core/java/android/view/IDisplayWindowListener.aidl +++ b/core/java/android/view/IDisplayWindowListener.aidl @@ -16,8 +16,11 @@ package android.view; +import android.graphics.Rect; import android.content.res.Configuration; +import java.util.List; + /** * Interface to listen for changes to display window-containers. * @@ -56,4 +59,9 @@ oneway interface IDisplayWindowListener { * Called when the previous fixed rotation on a display is finished. */ void onFixedRotationFinished(int displayId); + + /** + * Called when the keep clear ares on a display have changed. + */ + void onKeepClearAreasChanged(int displayId, in List<Rect> keepClearAreas); } diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index 2c766bd6fd0d..c5ccc18b0cd4 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -25,7 +25,6 @@ import android.content.res.CompatibilityInfo; import android.content.res.Configuration; import android.graphics.Bitmap; import android.graphics.GraphicBuffer; -import android.graphics.Insets; import android.graphics.Point; import android.graphics.Rect; import android.graphics.Region; @@ -66,6 +65,7 @@ import android.view.WindowManager; import android.view.SurfaceControl; import android.view.displayhash.DisplayHash; import android.view.displayhash.VerifiedDisplayHash; +import android.window.IOnFpsCallbackListener; /** * System private interface to the window manager. @@ -484,16 +484,6 @@ interface IWindowManager void getStableInsets(int displayId, out Rect outInsets); /** - * Set the forwarded insets on the display. - * <p> - * This is only used in case a virtual display is displayed on another display that has insets, - * and the bounds of the virtual display is overlapping with the insets from the host display. - * In that case, the contents on the virtual display won't be placed over the forwarded insets. - * Only the owner of the display is permitted to set the forwarded insets on it. - */ - void setForwardedInsets(int displayId, in Insets insets); - - /** * Register shortcut key. Shortcut code is packed as: * (MetaState << Integer.SIZE) | KeyCode * @hide @@ -551,6 +541,11 @@ interface IWindowManager void stopWindowTrace(); /** + * If window tracing is active, saves the window trace to file, otherwise does nothing + */ + void saveWindowTraceToFile(); + + /** * Returns true if window trace is enabled. */ boolean isWindowTraceEnabled(); @@ -922,4 +917,28 @@ interface IWindowManager * reverts to using the default task transition with no spec changes. */ void clearTaskTransitionSpec(); + + /** + * Registers the frame rate per second count callback for one given task ID. + * Each callback can only register for receiving FPS callback for one task id until unregister + * is called. If there's no task associated with the given task id, + * {@link IllegalArgumentException} will be thrown. If a task id destroyed after a callback is + * registered, the registered callback will not be unregistered until + * {@link unregisterTaskFpsCallback()} is called + * @param taskId task id of the task. + * @param listener listener to be registered. + * + * @hide + */ + void registerTaskFpsCallback(in int taskId, in IOnFpsCallbackListener listener); + + /** + * Unregisters the frame rate per second count callback which was registered with + * {@link #registerTaskFpsCallback(int,TaskFpsCallback)}. + * + * @param listener listener to be unregistered. + * + * @hide + */ + void unregisterTaskFpsCallback(in IOnFpsCallbackListener listener); } diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl index 921ce53b3bda..62265663804f 100644 --- a/core/java/android/view/IWindowSession.aidl +++ b/core/java/android/view/IWindowSession.aidl @@ -37,6 +37,7 @@ import android.view.Surface; import android.view.SurfaceControl; import android.view.SurfaceControl.Transaction; import android.window.ClientWindowFrames; +import android.window.IOnBackInvokedCallback; import java.util.List; @@ -290,6 +291,11 @@ interface IWindowSession { oneway void reportSystemGestureExclusionChanged(IWindow window, in List<Rect> exclusionRects); /** + * Called when the keep-clear areas for this window have changed. + */ + oneway void reportKeepClearAreasChanged(IWindow window, in List<Rect> keepClearRects); + + /** * Request the server to call setInputWindowInfo on a given Surface, and return * an input channel where the client can receive input. */ @@ -328,4 +334,12 @@ interface IWindowSession { */ oneway void generateDisplayHash(IWindow window, in Rect boundsInWindow, in String hashAlgorithm, in RemoteCallback callback); + + /** + * Sets the {@link IOnBackInvokedCallback} to be invoked for a window when back is triggered. + * + * @param window The token for the window to set the callback to. + * @param callback The {@link IOnBackInvokedCallback} to set. + */ + oneway void setOnBackInvokedCallback(IWindow window, IOnBackInvokedCallback callback); } diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java index 4f1354d7eee6..188d7459f9a7 100644 --- a/core/java/android/view/InputDevice.java +++ b/core/java/android/view/InputDevice.java @@ -572,6 +572,8 @@ public final class InputDevice implements Parcelable { * @return The identifier object for this device * @hide */ + @TestApi + @NonNull public InputDeviceIdentifier getIdentifier() { return mIdentifier; } @@ -735,6 +737,21 @@ public final class InputDevice implements Parcelable { } /** + * Gets the key code produced by the specified location on a US keyboard layout. + * Key code as defined in {@link android.view.KeyEvent}. + * This API is only functional for devices with {@link InputDevice#SOURCE_KEYBOARD} available + * which can alter their key mapping using country specific keyboard layouts. + * + * @param locationKeyCode The location of a key on a US keyboard layout. + * @return The key code produced when pressing the key at the specified location, given the + * active keyboard layout. Returns {@link KeyEvent#KEYCODE_UNKNOWN} if the requested + * mapping could not be determined, or if an error occurred. + */ + public int getKeyCodeForKeyLocation(int locationKeyCode) { + return InputManager.getInstance().getKeyCodeForKeyLocation(mId, locationKeyCode); + } + + /** * Gets information about the range of values for a particular {@link MotionEvent} axis. * If the device supports multiple sources, the same axis may have different meanings * for each source. Returns information about the first axis found for any source. diff --git a/core/java/android/view/KeyboardShortcutInfo.java b/core/java/android/view/KeyboardShortcutInfo.java index 2660e74dcb20..118b03ce5504 100644 --- a/core/java/android/view/KeyboardShortcutInfo.java +++ b/core/java/android/view/KeyboardShortcutInfo.java @@ -91,7 +91,7 @@ public final class KeyboardShortcutInfo implements Parcelable { private KeyboardShortcutInfo(Parcel source) { mLabel = source.readCharSequence(); - mIcon = source.readParcelable(null); + mIcon = source.readParcelable(null, android.graphics.drawable.Icon.class); mBaseCharacter = (char) source.readInt(); mKeycode = source.readInt(); mModifiers = source.readInt(); diff --git a/core/java/android/view/OnBackInvokedCallback.java b/core/java/android/view/OnBackInvokedCallback.java new file mode 100644 index 000000000000..b5cd89cfa4e1 --- /dev/null +++ b/core/java/android/view/OnBackInvokedCallback.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view; + +import android.app.Activity; +import android.app.Dialog; + +/** + * Interface for applications to register back invocation callbacks. This allows the client + * to customize various back behaviors by overriding the corresponding callback methods. + * + * Callback instances can be added to and removed from {@link OnBackInvokedDispatcher}, held + * by classes that implement {@link OnBackInvokedDispatcherOwner} (such as {@link Activity}, + * {@link Dialog} and {@link View}). + * + * Under the hood callbacks are registered at window level. When back is triggered, + * callbacks on the in-focus window are invoked in reverse order in which they are added + * within the same priority. Between different pirorities, callbacks with higher priority + * are invoked first. + * + * See {@link OnBackInvokedDispatcher#registerOnBackInvokedCallback(OnBackInvokedCallback, int)} + * for specifying callback priority. + */ +public interface OnBackInvokedCallback { + /** + * Called when a back gesture has been started, or back button has been pressed down. + * + * @hide + */ + default void onBackStarted() { }; + + /** + * Called on back gesture progress. + * + * @param touchX Absolute X location of the touch point. + * @param touchY Absolute Y location of the touch point. + * @param progress Value between 0 and 1 on how far along the back gesture is. + * + * @hide + */ + // TODO(b/210539672): combine back progress params into BackEvent. + default void onBackProgressed(int touchX, int touchY, float progress) { }; + + /** + * Called when a back gesture or back button press has been cancelled. + * + * @hide + */ + default void onBackCancelled() { }; + + /** + * Called when a back gesture has been completed and committed, or back button pressed + * has been released and committed. + */ + default void onBackInvoked() { }; +} diff --git a/core/java/android/view/OnBackInvokedDispatcher.java b/core/java/android/view/OnBackInvokedDispatcher.java new file mode 100644 index 000000000000..05c312b56cc7 --- /dev/null +++ b/core/java/android/view/OnBackInvokedDispatcher.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.SuppressLint; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Dispatcher to register {@link OnBackInvokedCallback} instances for handling + * back invocations. + * + * It also provides interfaces to update the attributes of {@link OnBackInvokedCallback}. + * Attribute updates are proactively pushed to the window manager if they change the dispatch + * target (a.k.a. the callback to be invoked next), or its behavior. + */ +public abstract class OnBackInvokedDispatcher { + /** @hide */ + @IntDef({ + PRIORITY_DEFAULT, + PRIORITY_OVERLAY, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Priority{} + + /** + * Priority level of {@link OnBackInvokedCallback}s for overlays such as menus and + * navigation drawers that should receive back dispatch before non-overlays. + */ + public static final int PRIORITY_OVERLAY = 1000000; + + /** + * Default priority level of {@link OnBackInvokedCallback}s. + */ + public static final int PRIORITY_DEFAULT = 0; + + /** + * Registers a {@link OnBackInvokedCallback}. + * + * Within the same priority level, callbacks are invoked in the reverse order in which + * they are registered. Higher priority callbacks are invoked before lower priority ones. + * + * @param callback The callback to be registered. If the callback instance has been already + * registered, the existing instance (no matter its priority) will be + * unregistered and registered again. + * @param priority The priority of the callback. + */ + @SuppressLint("SamShouldBeLast") + public abstract void registerOnBackInvokedCallback( + @NonNull OnBackInvokedCallback callback, @Priority int priority); + + /** + * Unregisters a {@link OnBackInvokedCallback}. + * + * @param callback The callback to be unregistered. Does nothing if the callback has not been + * registered. + */ + public abstract void unregisterOnBackInvokedCallback(@NonNull OnBackInvokedCallback callback); +} diff --git a/core/java/android/view/OnBackInvokedDispatcherOwner.java b/core/java/android/view/OnBackInvokedDispatcherOwner.java new file mode 100644 index 000000000000..0e14ed4cdb07 --- /dev/null +++ b/core/java/android/view/OnBackInvokedDispatcherOwner.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view; + +import android.annotation.Nullable; + +/** + * A class that provides an {@link OnBackInvokedDispatcher} that allows you to register + * an {@link OnBackInvokedCallback} for handling the system back invocation behavior. + */ +public interface OnBackInvokedDispatcherOwner { + /** + * Returns the {@link OnBackInvokedDispatcher} that should dispatch the back invocation + * to its registered {@link OnBackInvokedCallback}s. + * Returns null when the root view is not attached to a window or a view tree with a decor. + */ + @Nullable + OnBackInvokedDispatcher getOnBackInvokedDispatcher(); +} diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index b7f9be70f7ce..e751720b7f5d 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -1831,13 +1831,15 @@ public final class SurfaceControl implements Parcelable { public float density; public boolean secure; public DeviceProductInfo deviceProductInfo; + public @Surface.Rotation int installOrientation; @Override public String toString() { return "StaticDisplayInfo{isInternal=" + isInternal + ", density=" + density + ", secure=" + secure - + ", deviceProductInfo=" + deviceProductInfo + "}"; + + ", deviceProductInfo=" + deviceProductInfo + + ", installOrientation=" + installOrientation + "}"; } @Override @@ -1848,12 +1850,13 @@ public final class SurfaceControl implements Parcelable { return isInternal == that.isInternal && density == that.density && secure == that.secure - && Objects.equals(deviceProductInfo, that.deviceProductInfo); + && Objects.equals(deviceProductInfo, that.deviceProductInfo) + && installOrientation == that.installOrientation; } @Override public int hashCode() { - return Objects.hash(isInternal, density, secure, deviceProductInfo); + return Objects.hash(isInternal, density, secure, deviceProductInfo, installOrientation); } } diff --git a/core/java/android/view/SurfaceControlFpsListener.java b/core/java/android/view/SurfaceControlFpsListener.java deleted file mode 100644 index 20a511a090b5..000000000000 --- a/core/java/android/view/SurfaceControlFpsListener.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.view; - -import android.annotation.NonNull; - -/** - * Listener for sampling the frames per second for a SurfaceControl and its children. - * This should only be used by a system component that needs to listen to a SurfaceControl's - * tree's FPS when it is not actively submitting transactions for that SurfaceControl. - * Otherwise, ASurfaceTransaction_OnComplete callbacks should be used. - * - * @hide - */ -public abstract class SurfaceControlFpsListener { - private long mNativeListener; - - public SurfaceControlFpsListener() { - mNativeListener = nativeCreate(this); - } - - protected void destroy() { - if (mNativeListener == 0) { - return; - } - unregister(); - nativeDestroy(mNativeListener); - mNativeListener = 0; - } - - @Override - protected void finalize() throws Throwable { - try { - destroy(); - } finally { - super.finalize(); - } - } - - /** - * Reports the fps from the registered SurfaceControl - */ - public abstract void onFpsReported(float fps); - - /** - * Registers the sampling listener for a particular task ID - */ - public void register(int taskId) { - if (mNativeListener == 0) { - return; - } - - nativeRegister(mNativeListener, taskId); - } - - /** - * Unregisters the sampling listener. - */ - public void unregister() { - if (mNativeListener == 0) { - return; - } - nativeUnregister(mNativeListener); - } - - /** - * Dispatch the collected sample. - * - * Called from native code on a binder thread. - */ - private static void dispatchOnFpsReported( - @NonNull SurfaceControlFpsListener listener, float fps) { - listener.onFpsReported(fps); - } - - private static native long nativeCreate(SurfaceControlFpsListener thiz); - private static native void nativeDestroy(long ptr); - private static native void nativeRegister(long ptr, int taskId); - private static native void nativeUnregister(long ptr); -} diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 41a31e1011f2..93fdee07b58e 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -834,7 +834,7 @@ import java.util.function.Predicate; */ @UiThread public class View implements Drawable.Callback, KeyEvent.Callback, - AccessibilityEventSource { + AccessibilityEventSource, OnBackInvokedDispatcherOwner { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) private static final boolean DBG = false; @@ -4734,15 +4734,18 @@ public class View implements Drawable.Callback, KeyEvent.Callback, WindowInsetsAnimation.Callback mWindowInsetsAnimationCallback; /** - * This lives here since it's only valid for interactive views. This list is null until the - * first use. + * This lives here since it's only valid for interactive views. This list is null + * until its first use. */ private List<Rect> mSystemGestureExclusionRects = null; + private List<Rect> mKeepClearRects = null; + private boolean mPreferKeepClear = false; /** - * Used to track {@link #mSystemGestureExclusionRects} + * Used to track {@link #mSystemGestureExclusionRects} and {@link #mKeepClearRects} */ public RenderNode.PositionUpdateListener mPositionUpdateListener; + private Runnable mPositionChangedUpdate; /** * Allows the application to implement custom scroll capture support. @@ -6028,6 +6031,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, case R.styleable.View_clipToOutline: setClipToOutline(a.getBoolean(attr, false)); break; + case R.styleable.View_preferKeepClear: + setPreferKeepClear(a.getBoolean(attr, false)); + break; } } @@ -11665,37 +11671,49 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } else { info.mSystemGestureExclusionRects = new ArrayList<>(rects); } - if (rects.isEmpty()) { + + updatePositionUpdateListener(); + postUpdate(this::updateSystemGestureExclusionRects); + } + + private void updatePositionUpdateListener() { + final ListenerInfo info = getListenerInfo(); + if (getSystemGestureExclusionRects().isEmpty() + && collectPreferKeepClearRects().isEmpty()) { if (info.mPositionUpdateListener != null) { mRenderNode.removePositionUpdateListener(info.mPositionUpdateListener); + info.mPositionChangedUpdate = null; } } else { if (info.mPositionUpdateListener == null) { + info.mPositionChangedUpdate = () -> { + updateSystemGestureExclusionRects(); + updateKeepClearRects(); + }; info.mPositionUpdateListener = new RenderNode.PositionUpdateListener() { @Override public void positionChanged(long n, int l, int t, int r, int b) { - postUpdateSystemGestureExclusionRects(); + postUpdate(info.mPositionChangedUpdate); } @Override public void positionLost(long frameNumber) { - postUpdateSystemGestureExclusionRects(); + postUpdate(info.mPositionChangedUpdate); } }; mRenderNode.addPositionUpdateListener(info.mPositionUpdateListener); } } - postUpdateSystemGestureExclusionRects(); } /** * WARNING: this can be called by a hwui worker thread, not just the UI thread! */ - void postUpdateSystemGestureExclusionRects() { + private void postUpdate(Runnable r) { // Potentially racey from a background thread. It's ok if it's not perfect. final Handler h = getHandler(); if (h != null) { - h.postAtFrontOfQueue(this::updateSystemGestureExclusionRects); + h.postAtFrontOfQueue(r); } } @@ -11727,6 +11745,106 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * Set a preference to keep the bounds of this view clear from floating windows above this + * view's window. This informs the system that the view is considered a vital area for the + * user and that ideally it should not be covered. Setting this is only appropriate for UI + * where the user would likely take action to uncover it. + * <p> + * The system will try to respect this, but when not possible will ignore it. + * <p> + * @see #setPreferKeepClearRects + * @see #isPreferKeepClear + * @attr ref android.R.styleable#View_preferKeepClear + */ + public final void setPreferKeepClear(boolean preferKeepClear) { + getListenerInfo().mPreferKeepClear = preferKeepClear; + updatePositionUpdateListener(); + postUpdate(this::updateKeepClearRects); + } + + /** + * Retrieve the preference for this view to be kept clear. This is set either by + * {@link #setPreferKeepClear} or via the attribute android.R.styleable#View_preferKeepClear. + * <p> + * If this is {@code true}, the system will ignore the Rects set by + * {@link #setPreferKeepClearRects} and try to keep the whole view clear. + * <p> + * @see #setPreferKeepClear + * @attr ref android.R.styleable#View_preferKeepClear + */ + public final boolean isPreferKeepClear() { + return mListenerInfo != null && mListenerInfo.mPreferKeepClear; + } + + /** + * Set a preference to keep the provided rects clear from floating windows above this + * view's window. This informs the system that these rects are considered vital areas for the + * user and that ideally they should not be covered. Setting this is only appropriate for UI + * where the user would likely take action to uncover it. + * <p> + * If the whole view is preferred to be clear ({@link #isPreferKeepClear}), the rects set here + * will be ignored. + * <p> + * The system will try to respect this preference, but when not possible will ignore it. + * <p> + * @see #setPreferKeepClear + * @see #getPreferKeepClearRects + */ + public final void setPreferKeepClearRects(@NonNull List<Rect> rects) { + final ListenerInfo info = getListenerInfo(); + if (info.mKeepClearRects != null) { + info.mKeepClearRects.clear(); + info.mKeepClearRects.addAll(rects); + } else { + info.mKeepClearRects = new ArrayList<>(rects); + } + updatePositionUpdateListener(); + postUpdate(this::updateKeepClearRects); + } + + /** + * @return the list of rects, set by {@link #setPreferKeepClearRects}. + * + * @see #setPreferKeepClearRects + */ + @NonNull + public final List<Rect> getPreferKeepClearRects() { + final ListenerInfo info = mListenerInfo; + if (info != null && info.mKeepClearRects != null) { + return new ArrayList(info.mKeepClearRects); + } + + return Collections.emptyList(); + } + + void updateKeepClearRects() { + final AttachInfo ai = mAttachInfo; + if (ai != null) { + ai.mViewRootImpl.updateKeepClearRectsForView(this); + } + } + + /** + * Retrieve the list of areas within this view's post-layout coordinate space which the + * system will try to not cover with other floating elements, like the pip window. + */ + @NonNull + List<Rect> collectPreferKeepClearRects() { + final ListenerInfo info = mListenerInfo; + if (info != null) { + final List<Rect> list = new ArrayList(); + if (info.mPreferKeepClear) { + list.add(new Rect(0, 0, getWidth(), getHeight())); + } else if (info.mKeepClearRects != null) { + list.addAll(info.mKeepClearRects); + } + return list; + } + + return Collections.emptyList(); + } + + /** * Compute the view's coordinate within the surface. * * <p>Computes the coordinates of this view in its surface. The argument @@ -15120,7 +15238,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, notifyAppearedOrDisappearedForContentCaptureIfNeeded(isVisible); if (!getSystemGestureExclusionRects().isEmpty()) { - postUpdateSystemGestureExclusionRects(); + postUpdate(this::updateSystemGestureExclusionRects); + } + + if (!collectPreferKeepClearRects().isEmpty()) { + postUpdate(this::updateKeepClearRects); } } } @@ -31276,4 +31398,23 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } return null; } + + /** + * Returns the {@link OnBackInvokedDispatcher} instance of the window this view is attached to. + * + * @return The {@link OnBackInvokedDispatcher} or {@code null} if the view is neither attached + * to a window or a view tree with a decor. + */ + @Nullable + public OnBackInvokedDispatcher getOnBackInvokedDispatcher() { + ViewParent parent = getParent(); + if (parent instanceof View) { + return ((View) parent).getOnBackInvokedDispatcher(); + } else if (parent instanceof ViewRootImpl) { + // Get the fallback dispatcher on {@link ViewRootImpl} if the view tree doesn't have + // a {@link com.android.internal.policy.DecorView}. + return ((ViewRootImpl) parent).getOnBackInvokedDispatcher(); + } + return null; + } } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 74129312b41b..97b5a3181dbf 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -192,6 +192,7 @@ import android.view.contentcapture.MainContentCaptureSession; import android.view.inputmethod.InputMethodManager; import android.widget.Scroller; import android.window.ClientWindowFrames; +import android.window.WindowOnBackInvokedDispatcher; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; @@ -309,6 +310,12 @@ public final class ViewRootImpl implements ViewParent, private @SurfaceControl.BufferTransform int mPreviousTransformHint = SurfaceControl.BUFFER_TRANSFORM_IDENTITY; /** + * The fallback {@link OnBackInvokedDispatcher} when the window doesn't have a decor view. + */ + private WindowOnBackInvokedDispatcher mFallbackOnBackInvokedDispatcher = + new WindowOnBackInvokedDispatcher(); + + /** * Callback for notifying about global configuration changes. */ public interface ConfigChangedCallback { @@ -391,6 +398,8 @@ public final class ViewRootImpl implements ViewParent, final DisplayManager mDisplayManager; final String mBasePackageName; + private @Surface.Rotation int mDisplayInstallOrientation; + final int[] mTmpLocation = new int[2]; final TypedValue mTmpValue = new TypedValue(); @@ -742,7 +751,10 @@ public final class ViewRootImpl implements ViewParent, return mImeFocusController; } - private final GestureExclusionTracker mGestureExclusionTracker = new GestureExclusionTracker(); + private final ViewRootRectTracker mGestureExclusionTracker = + new ViewRootRectTracker(v -> v.getSystemGestureExclusionRects()); + private final ViewRootRectTracker mKeepClearRectsTracker = + new ViewRootRectTracker(v -> v.collectPreferKeepClearRects()); private IAccessibilityEmbeddedConnection mAccessibilityEmbeddedConnection; @@ -871,6 +883,7 @@ public final class ViewRootImpl implements ViewParent, mFastScrollSoundEffectsEnabled = audioManager.areNavigationRepeatSoundEffectsEnabled(); mScrollCaptureRequestTimeout = SCROLL_CAPTURE_REQUEST_TIMEOUT_MILLIS; + mFallbackOnBackInvokedDispatcher.attachToWindow(mWindowSession, mWindow); } public static void addFirstDrawHandler(Runnable callback) { @@ -1017,6 +1030,7 @@ public final class ViewRootImpl implements ViewParent, mView = view; mAttachInfo.mDisplayState = mDisplay.getState(); + mDisplayInstallOrientation = mDisplay.getInstallOrientation(); mViewLayoutDirectionInitial = mView.getRawLayoutDirection(); mFallbackEventHandler.setView(view); mWindowAttributes.copyFrom(attrs); @@ -1120,6 +1134,9 @@ public final class ViewRootImpl implements ViewParent, if (pendingInsetsController != null) { pendingInsetsController.replayAndAttach(mInsetsController); } + ((RootViewSurfaceTaker) mView) + .provideWindowOnBackInvokedDispatcher() + .attachToWindow(mWindowSession, mWindow); } try { @@ -4756,7 +4773,7 @@ public final class ViewRootImpl implements ViewParent, * the root's view hierarchy. */ public void setRootSystemGestureExclusionRects(@NonNull List<Rect> rects) { - mGestureExclusionTracker.setRootSystemGestureExclusionRects(rects); + mGestureExclusionTracker.setRootRects(rects); mHandler.sendEmptyMessage(MSG_SYSTEM_GESTURE_EXCLUSION_CHANGED); } @@ -4766,7 +4783,26 @@ public final class ViewRootImpl implements ViewParent, */ @NonNull public List<Rect> getRootSystemGestureExclusionRects() { - return mGestureExclusionTracker.getRootSystemGestureExclusionRects(); + return mGestureExclusionTracker.getRootRects(); + } + + /** + * Called from View when the position listener is triggered + */ + void updateKeepClearRectsForView(View view) { + mKeepClearRectsTracker.updateRectsForView(view); + mHandler.sendEmptyMessage(MSG_KEEP_CLEAR_RECTS_CHANGED); + } + + void keepClearRectsChanged() { + final List<Rect> rectsForWindowManager = mKeepClearRectsTracker.computeChangedRects(); + if (rectsForWindowManager != null && mView != null) { + try { + mWindowSession.reportKeepClearAreasChanged(mWindow, rectsForWindowManager); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } /** @@ -5262,6 +5298,7 @@ public final class ViewRootImpl implements ViewParent, private static final int MSG_HIDE_INSETS = 35; private static final int MSG_REQUEST_SCROLL_CAPTURE = 36; private static final int MSG_WINDOW_TOUCH_MODE_CHANGED = 37; + private static final int MSG_KEEP_CLEAR_RECTS_CHANGED = 38; final class ViewRootHandler extends Handler { @@ -5330,6 +5367,8 @@ public final class ViewRootImpl implements ViewParent, return "MSG_HIDE_INSETS"; case MSG_WINDOW_TOUCH_MODE_CHANGED: return "MSG_WINDOW_TOUCH_MODE_CHANGED"; + case MSG_KEEP_CLEAR_RECTS_CHANGED: + return "MSG_KEEP_CLEAR_RECTS_CHANGED"; } return super.getMessageName(message); } @@ -5553,7 +5592,10 @@ public final class ViewRootImpl implements ViewParent, } break; case MSG_SYSTEM_GESTURE_EXCLUSION_CHANGED: { systemGestureExclusionChanged(); - } break; + } break; + case MSG_KEEP_CLEAR_RECTS_CHANGED: { + keepClearRectsChanged(); + } break; case MSG_REQUEST_SCROLL_CAPTURE: handleScrollCaptureRequest((IScrollCaptureResponseListener) msg.obj); break; @@ -7898,6 +7940,10 @@ public final class ViewRootImpl implements ViewParent, mTmpFrames, mPendingMergedConfiguration, mSurfaceControl, mTempInsets, mTempControls, mSurfaceSize); + final int transformHint = SurfaceControl.rotationToBufferTransform( + (mDisplayInstallOrientation + mDisplay.getRotation()) % 4); + mSurfaceControl.setTransformHint(transformHint); + if (mAttachInfo.mContentCaptureManager != null) { MainContentCaptureSession mainSession = mAttachInfo.mContentCaptureManager .getMainContentCaptureSession(); @@ -7916,7 +7962,6 @@ public final class ViewRootImpl implements ViewParent, mAttachInfo.mThreadedRenderer.setSurfaceControl(mSurfaceControl); mAttachInfo.mThreadedRenderer.setBlastBufferQueue(mBlastBufferQueue); } - int transformHint = mSurfaceControl.getTransformHint(); if (mPreviousTransformHint != transformHint) { mPreviousTransformHint = transformHint; dispatchTransformHintChanged(transformHint); @@ -10634,7 +10679,7 @@ public final class ViewRootImpl implements ViewParent, } mBLASTDrawConsumer = consume; return true; - } + } boolean wasRelayoutRequested() { return mRelayoutRequested; @@ -10644,4 +10689,16 @@ public final class ViewRootImpl implements ViewParent, mForceNextWindowRelayout = true; scheduleTraversals(); } + + /** + * Returns the {@link OnBackInvokedDispatcher} on the decor view if one exists, or the + * fallback {@link OnBackInvokedDispatcher} instance. + */ + @Nullable + public OnBackInvokedDispatcher getOnBackInvokedDispatcher() { + if (mView instanceof RootViewSurfaceTaker) { + return ((RootViewSurfaceTaker) mView).provideWindowOnBackInvokedDispatcher(); + } + return mFallbackOnBackInvokedDispatcher; + } } diff --git a/core/java/android/view/ViewRootRectTracker.java b/core/java/android/view/ViewRootRectTracker.java new file mode 100644 index 000000000000..fd9cc1920b39 --- /dev/null +++ b/core/java/android/view/ViewRootRectTracker.java @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.graphics.Rect; + +import com.android.internal.util.Preconditions; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.function.Function; + +/** + * Abstract class to track a collection of rects reported by the views under the same + * {@link ViewRootImpl}. + */ +class ViewRootRectTracker { + private final Function<View, List<Rect>> mRectCollector; + private boolean mViewsChanged = false; + private boolean mRootRectsChanged = false; + private List<Rect> mRootRects = Collections.emptyList(); + private List<ViewInfo> mViewInfos = new ArrayList<>(); + private List<Rect> mRects = Collections.emptyList(); + + /** + * @param rectCollector given a view returns a list of the rects of interest for this + * ViewRootRectTracker + */ + ViewRootRectTracker(Function<View, List<Rect>> rectCollector) { + mRectCollector = rectCollector; + } + + public void updateRectsForView(@NonNull View view) { + boolean found = false; + final Iterator<ViewInfo> i = mViewInfos.iterator(); + while (i.hasNext()) { + final ViewInfo info = i.next(); + final View v = info.getView(); + if (v == null || !v.isAttachedToWindow() || !v.isAggregatedVisible()) { + mViewsChanged = true; + i.remove(); + continue; + } + if (v == view) { + found = true; + info.mDirty = true; + break; + } + } + if (!found && view.isAttachedToWindow()) { + mViewInfos.add(new ViewInfo(view)); + mViewsChanged = true; + } + } + + /** + * @return all visible rects from all views in the global (root) coordinate system + */ + @Nullable + public List<Rect> computeChangedRects() { + boolean changed = mRootRectsChanged; + final Iterator<ViewInfo> i = mViewInfos.iterator(); + final List<Rect> rects = new ArrayList<>(mRootRects); + while (i.hasNext()) { + final ViewInfo info = i.next(); + switch (info.update()) { + case ViewInfo.CHANGED: + changed = true; + // Deliberate fall-through + case ViewInfo.UNCHANGED: + rects.addAll(info.mRects); + break; + case ViewInfo.GONE: + mViewsChanged = true; + i.remove(); + break; + } + } + if (changed || mViewsChanged) { + mViewsChanged = false; + mRootRectsChanged = false; + if (!mRects.equals(rects)) { + mRects = rects; + return rects; + } + } + return null; + } + + /** + * Sets rects defined in the global (root) coordinate system, i.e. not for a specific view. + */ + public void setRootRects(@NonNull List<Rect> rects) { + Preconditions.checkNotNull(rects, "rects must not be null"); + mRootRects = rects; + mRootRectsChanged = true; + } + + @NonNull + public List<Rect> getRootRects() { + return mRootRects; + } + + @NonNull + private List<Rect> getTrackedRectsForView(@NonNull View v) { + final List<Rect> rects = mRectCollector.apply(v); + return rects == null ? Collections.emptyList() : rects; + } + + private class ViewInfo { + public static final int CHANGED = 0; + public static final int UNCHANGED = 1; + public static final int GONE = 2; + + private final WeakReference<View> mView; + boolean mDirty = true; + List<Rect> mRects = Collections.emptyList(); + + ViewInfo(View view) { + mView = new WeakReference<>(view); + } + + public View getView() { + return mView.get(); + } + + public int update() { + final View view = getView(); + if (view == null || !view.isAttachedToWindow() + || !view.isAggregatedVisible()) return GONE; + final List<Rect> localRects = getTrackedRectsForView(view); + final List<Rect> newRects = new ArrayList<>(localRects.size()); + for (Rect src : localRects) { + Rect mappedRect = new Rect(src); + ViewParent p = view.getParent(); + if (p != null && p.getChildVisibleRect(view, mappedRect, null)) { + newRects.add(mappedRect); + } + } + + if (mRects.equals(localRects)) return UNCHANGED; + mRects = newRects; + return CHANGED; + } + } +} diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index 5be3a57e8527..ca7f90080c6c 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -118,6 +118,7 @@ import android.view.WindowInsets.Side.InsetsSide; import android.view.WindowInsets.Type; import android.view.WindowInsets.Type.InsetsType; import android.view.accessibility.AccessibilityNodeInfo; +import android.window.TaskFpsCallback; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -4858,4 +4859,31 @@ public interface WindowManager extends ViewManager { default boolean isTaskSnapshotSupported() { return false; } + + /** + * Registers the frame rate per second count callback for one given task ID. + * Each callback can only register for receiving FPS callback for one task id until unregister + * is called. If there's no task associated with the given task id, + * {@link IllegalArgumentException} will be thrown. If a task id destroyed after a callback is + * registered, the registered callback will not be unregistered until + * {@link #unregisterTaskFpsCallback(TaskFpsCallback))} is called + * @param taskId task id of the task. + * @param callback callback to be registered. + * + * @hide + */ + @SystemApi + default void registerTaskFpsCallback(@IntRange(from = 0) int taskId, + @NonNull TaskFpsCallback callback) {} + + /** + * Unregisters the frame rate per second count callback which was registered with + * {@link #registerTaskFpsCallback(int,TaskFpsCallback)}. + * + * @param callback callback to be unregistered. + * + * @hide + */ + @SystemApi + default void unregisterTaskFpsCallback(@NonNull TaskFpsCallback callback) {} } diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java index dd8041684c78..c16703ef50ef 100644 --- a/core/java/android/view/WindowManagerImpl.java +++ b/core/java/android/view/WindowManagerImpl.java @@ -24,6 +24,7 @@ import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING; import static android.window.WindowProviderService.isWindowProviderService; import android.annotation.CallbackExecutor; +import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UiContext; @@ -37,6 +38,7 @@ import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; import android.os.StrictMode; +import android.window.TaskFpsCallback; import android.window.WindowContext; import android.window.WindowProvider; @@ -419,4 +421,22 @@ public final class WindowManagerImpl implements WindowManager { } return false; } + + @Override + public void registerTaskFpsCallback(@IntRange(from = 0) int taskId, TaskFpsCallback callback) { + try { + WindowManagerGlobal.getWindowManagerService().registerTaskFpsCallback( + taskId, callback.getListener()); + } catch (RemoteException e) { + } + } + + @Override + public void unregisterTaskFpsCallback(TaskFpsCallback callback) { + try { + WindowManagerGlobal.getWindowManagerService().unregisterTaskFpsCallback( + callback.getListener()); + } catch (RemoteException e) { + } + } } diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java index 5176f9bb0f93..998498b0799a 100644 --- a/core/java/android/view/WindowlessWindowManager.java +++ b/core/java/android/view/WindowlessWindowManager.java @@ -28,6 +28,7 @@ import android.os.RemoteException; import android.util.Log; import android.util.MergedConfiguration; import android.window.ClientWindowFrames; +import android.window.IOnBackInvokedCallback; import java.util.HashMap; import java.util.Objects; @@ -459,6 +460,11 @@ public class WindowlessWindowManager implements IWindowSession { } @Override + public void reportKeepClearAreasChanged(android.view.IWindow window, + java.util.List<android.graphics.Rect> exclusionRects) { + } + + @Override public void grantInputChannel(int displayId, SurfaceControl surface, IWindow window, IBinder hostInputToken, int flags, int privateFlags, int type, InputChannel outInputChannel) { @@ -496,6 +502,10 @@ public class WindowlessWindowManager implements IWindowSession { } @Override + public void setOnBackInvokedCallback(IWindow iWindow, + IOnBackInvokedCallback iOnBackInvokedCallback) throws RemoteException { } + + @Override public boolean dropForAccessibility(IWindow window, int x, int y) { return false; } diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java index 6ad2d9a7adb1..a427ab8fe837 100644 --- a/core/java/android/view/accessibility/AccessibilityEvent.java +++ b/core/java/android/view/accessibility/AccessibilityEvent.java @@ -1315,7 +1315,7 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par record.mContentDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel); record.mBeforeText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel); record.mParcelableData = parcel.readParcelable(null); - parcel.readList(record.mText, null); + parcel.readList(record.mText, null, java.lang.CharSequence.class); record.mSourceWindowId = parcel.readInt(); record.mSourceNodeId = parcel.readLong(); record.mSourceDisplayId = parcel.readInt(); diff --git a/core/java/android/view/accessibility/AccessibilityWindowInfo.java b/core/java/android/view/accessibility/AccessibilityWindowInfo.java index 67e6d3f2aec3..540f5dc27f7e 100644 --- a/core/java/android/view/accessibility/AccessibilityWindowInfo.java +++ b/core/java/android/view/accessibility/AccessibilityWindowInfo.java @@ -917,7 +917,7 @@ public final class AccessibilityWindowInfo implements Parcelable { final int count = source.readInt(); for (int i = 0; i < count; i++) { List<AccessibilityWindowInfo> windows = new ArrayList<>(); - source.readParcelableList(windows, loader); + source.readParcelableList(windows, loader, android.view.accessibility.AccessibilityWindowInfo.class); array.put(source.readInt(), windows); } return array; diff --git a/core/java/android/view/accessibility/CaptioningManager.java b/core/java/android/view/accessibility/CaptioningManager.java index 05c74f2d0111..4f9781b6b6af 100644 --- a/core/java/android/view/accessibility/CaptioningManager.java +++ b/core/java/android/view/accessibility/CaptioningManager.java @@ -254,7 +254,13 @@ public class CaptioningManager { * Returns true if system wide call captioning is enabled for this device. */ public boolean isCallCaptioningEnabled() { - return mResources.getBoolean(R.bool.config_systemCaptionsServiceCallsEnabled); + try { + return mResources.getBoolean( + R.bool.config_systemCaptionsServiceCallsEnabled); + } catch (Resources.NotFoundException e) { + // The resource may not be defined, return false in that case + return false; + } } private void notifyEnabledChanged() { diff --git a/core/java/android/view/autofill/ParcelableMap.java b/core/java/android/view/autofill/ParcelableMap.java index d8459aa22fa1..3fa7734e56fc 100644 --- a/core/java/android/view/autofill/ParcelableMap.java +++ b/core/java/android/view/autofill/ParcelableMap.java @@ -56,8 +56,8 @@ class ParcelableMap extends HashMap<AutofillId, AutofillValue> implements Parcel ParcelableMap map = new ParcelableMap(size); for (int i = 0; i < size; i++) { - AutofillId key = source.readParcelable(null); - AutofillValue value = source.readParcelable(null); + AutofillId key = source.readParcelable(null, android.view.autofill.AutofillId.class); + AutofillValue value = source.readParcelable(null, android.view.autofill.AutofillValue.class); map.put(key, value); } diff --git a/core/java/android/view/contentcapture/ContentCaptureCondition.java b/core/java/android/view/contentcapture/ContentCaptureCondition.java index 027c8d20ccc6..685ea1aeaba8 100644 --- a/core/java/android/view/contentcapture/ContentCaptureCondition.java +++ b/core/java/android/view/contentcapture/ContentCaptureCondition.java @@ -133,7 +133,7 @@ public final class ContentCaptureCondition implements Parcelable { @Override public ContentCaptureCondition createFromParcel(@NonNull Parcel parcel) { - return new ContentCaptureCondition(parcel.readParcelable(null), + return new ContentCaptureCondition(parcel.readParcelable(null, android.content.LocusId.class), parcel.readInt()); } diff --git a/core/java/android/view/contentcapture/ContentCaptureContext.java b/core/java/android/view/contentcapture/ContentCaptureContext.java index 3bc9a967ea20..59b5286f6fc5 100644 --- a/core/java/android/view/contentcapture/ContentCaptureContext.java +++ b/core/java/android/view/contentcapture/ContentCaptureContext.java @@ -419,7 +419,7 @@ public final class ContentCaptureContext implements Parcelable { final ContentCaptureContext clientContext; if (hasClientContext) { // Must reconstruct the client context using the Builder API - final LocusId id = parcel.readParcelable(null); + final LocusId id = parcel.readParcelable(null, android.content.LocusId.class); final Bundle extras = parcel.readBundle(); final Builder builder = new Builder(id); if (extras != null) builder.setExtras(extras); @@ -427,7 +427,7 @@ public final class ContentCaptureContext implements Parcelable { } else { clientContext = null; } - final ComponentName componentName = parcel.readParcelable(null); + final ComponentName componentName = parcel.readParcelable(null, android.content.ComponentName.class); if (componentName == null) { // Client-state only return clientContext; diff --git a/core/java/android/view/contentcapture/ContentCaptureEvent.java b/core/java/android/view/contentcapture/ContentCaptureEvent.java index 0f4bc191fe4e..ba4176faa283 100644 --- a/core/java/android/view/contentcapture/ContentCaptureEvent.java +++ b/core/java/android/view/contentcapture/ContentCaptureEvent.java @@ -620,7 +620,7 @@ public final class ContentCaptureEvent implements Parcelable { final int type = parcel.readInt(); final long eventTime = parcel.readLong(); final ContentCaptureEvent event = new ContentCaptureEvent(sessionId, type, eventTime); - final AutofillId id = parcel.readParcelable(null); + final AutofillId id = parcel.readParcelable(null, android.view.autofill.AutofillId.class); if (id != null) { event.setAutofillId(id); } @@ -637,13 +637,13 @@ public final class ContentCaptureEvent implements Parcelable { event.setParentSessionId(parcel.readInt()); } if (type == TYPE_SESSION_STARTED || type == TYPE_CONTEXT_UPDATED) { - event.setClientContext(parcel.readParcelable(null)); + event.setClientContext(parcel.readParcelable(null, android.view.contentcapture.ContentCaptureContext.class)); } if (type == TYPE_VIEW_INSETS_CHANGED) { - event.setInsets(parcel.readParcelable(null)); + event.setInsets(parcel.readParcelable(null, android.graphics.Insets.class)); } if (type == TYPE_WINDOW_BOUNDS_CHANGED) { - event.setBounds(parcel.readParcelable(null)); + event.setBounds(parcel.readParcelable(null, android.graphics.Rect.class)); } if (type == TYPE_VIEW_TEXT_CHANGED) { event.setComposingIndex(parcel.readInt(), parcel.readInt()); diff --git a/core/java/android/view/contentcapture/ViewNode.java b/core/java/android/view/contentcapture/ViewNode.java index 1b4a00f81e44..1762a5817aaf 100644 --- a/core/java/android/view/contentcapture/ViewNode.java +++ b/core/java/android/view/contentcapture/ViewNode.java @@ -124,10 +124,10 @@ public final class ViewNode extends AssistStructure.ViewNode { mFlags = nodeFlags; if ((nodeFlags & FLAGS_HAS_AUTOFILL_ID) != 0) { - mAutofillId = parcel.readParcelable(null); + mAutofillId = parcel.readParcelable(null, android.view.autofill.AutofillId.class); } if ((nodeFlags & FLAGS_HAS_AUTOFILL_PARENT_ID) != 0) { - mParentAutofillId = parcel.readParcelable(null); + mParentAutofillId = parcel.readParcelable(null, android.view.autofill.AutofillId.class); } if ((nodeFlags & FLAGS_HAS_TEXT) != 0) { mText = new ViewNodeText(parcel, (nodeFlags & FLAGS_HAS_COMPLEX_TEXT) == 0); @@ -169,7 +169,7 @@ public final class ViewNode extends AssistStructure.ViewNode { mExtras = parcel.readBundle(); } if ((nodeFlags & FLAGS_HAS_LOCALE_LIST) != 0) { - mLocaleList = parcel.readParcelable(null); + mLocaleList = parcel.readParcelable(null, android.os.LocaleList.class); } if ((nodeFlags & FLAGS_HAS_MIME_TYPES) != 0) { mReceiveContentMimeTypes = parcel.readStringArray(); @@ -196,7 +196,7 @@ public final class ViewNode extends AssistStructure.ViewNode { mAutofillHints = parcel.readStringArray(); } if ((nodeFlags & FLAGS_HAS_AUTOFILL_VALUE) != 0) { - mAutofillValue = parcel.readParcelable(null); + mAutofillValue = parcel.readParcelable(null, android.view.autofill.AutofillValue.class); } if ((nodeFlags & FLAGS_HAS_AUTOFILL_OPTIONS) != 0) { mAutofillOptions = parcel.readCharSequenceArray(); diff --git a/core/java/android/view/inputmethod/CursorAnchorInfo.java b/core/java/android/view/inputmethod/CursorAnchorInfo.java index e3d3bfd30b9c..8600f55eee70 100644 --- a/core/java/android/view/inputmethod/CursorAnchorInfo.java +++ b/core/java/android/view/inputmethod/CursorAnchorInfo.java @@ -147,7 +147,7 @@ public final class CursorAnchorInfo implements Parcelable { mInsertionMarkerTop = source.readFloat(); mInsertionMarkerBaseline = source.readFloat(); mInsertionMarkerBottom = source.readFloat(); - mCharacterBoundsArray = source.readParcelable(SparseRectFArray.class.getClassLoader()); + mCharacterBoundsArray = source.readParcelable(SparseRectFArray.class.getClassLoader(), android.view.inputmethod.SparseRectFArray.class); mEditorBoundsInfo = source.readTypedObject(EditorBoundsInfo.CREATOR); mMatrixValues = source.createFloatArray(); } diff --git a/core/java/android/view/inputmethod/EditorInfo.java b/core/java/android/view/inputmethod/EditorInfo.java index 4cbd477d807a..09a14484095e 100644 --- a/core/java/android/view/inputmethod/EditorInfo.java +++ b/core/java/android/view/inputmethod/EditorInfo.java @@ -1067,7 +1067,7 @@ public class EditorInfo implements InputType, Parcelable { res.hintText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source); res.label = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source); res.packageName = source.readString(); - res.autofillId = source.readParcelable(AutofillId.class.getClassLoader()); + res.autofillId = source.readParcelable(AutofillId.class.getClassLoader(), android.view.autofill.AutofillId.class); res.fieldId = source.readInt(); res.fieldName = source.readString(); res.extras = source.readBundle(); diff --git a/core/java/android/view/inputmethod/InlineSuggestionsRequest.java b/core/java/android/view/inputmethod/InlineSuggestionsRequest.java index e1e175512edc..70279cc8e845 100644 --- a/core/java/android/view/inputmethod/InlineSuggestionsRequest.java +++ b/core/java/android/view/inputmethod/InlineSuggestionsRequest.java @@ -490,7 +490,7 @@ public final class InlineSuggestionsRequest implements Parcelable { boolean clientSupported = (flg & 0x200) != 0; int maxSuggestionCount = in.readInt(); List<InlinePresentationSpec> inlinePresentationSpecs = new ArrayList<>(); - in.readParcelableList(inlinePresentationSpecs, InlinePresentationSpec.class.getClassLoader()); + in.readParcelableList(inlinePresentationSpecs, InlinePresentationSpec.class.getClassLoader(), android.widget.inline.InlinePresentationSpec.class); String hostPackageName = in.readString(); LocaleList supportedLocales = (LocaleList) in.readTypedObject(LocaleList.CREATOR); Bundle extras = in.readBundle(); diff --git a/core/java/android/view/inputmethod/InlineSuggestionsResponse.java b/core/java/android/view/inputmethod/InlineSuggestionsResponse.java index b393c67d7876..532fc85dcc44 100644 --- a/core/java/android/view/inputmethod/InlineSuggestionsResponse.java +++ b/core/java/android/view/inputmethod/InlineSuggestionsResponse.java @@ -170,7 +170,7 @@ public final class InlineSuggestionsResponse implements Parcelable { // static FieldType unparcelFieldName(Parcel in) { ... } List<InlineSuggestion> inlineSuggestions = new ArrayList<>(); - in.readParcelableList(inlineSuggestions, InlineSuggestion.class.getClassLoader()); + in.readParcelableList(inlineSuggestions, InlineSuggestion.class.getClassLoader(), android.view.inputmethod.InlineSuggestion.class); this.mInlineSuggestions = inlineSuggestions; com.android.internal.util.AnnotationValidations.validate( diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 6fc246eb2514..6a22023dc9da 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -2139,7 +2139,7 @@ public final class InputMethodManager { view.onInputConnectionOpenedInternal(ic, tba, icHandler); final ViewRootImpl viewRoot = view.getViewRootImpl(); if (viewRoot != null) { - viewRoot.getHandwritingInitiator().onInputConnectionCreated(view, tba); + viewRoot.getHandwritingInitiator().onInputConnectionCreated(view); } } diff --git a/core/java/android/view/textclassifier/ConversationAction.java b/core/java/android/view/textclassifier/ConversationAction.java index bf0409dfc919..a4a5a1ed0ac9 100644 --- a/core/java/android/view/textclassifier/ConversationAction.java +++ b/core/java/android/view/textclassifier/ConversationAction.java @@ -141,7 +141,7 @@ public final class ConversationAction implements Parcelable { private ConversationAction(Parcel in) { mType = in.readString(); - mAction = in.readParcelable(null); + mAction = in.readParcelable(null, android.app.RemoteAction.class); mTextReply = in.readCharSequence(); mScore = in.readFloat(); mExtras = in.readBundle(); diff --git a/core/java/android/view/textclassifier/ConversationActions.java b/core/java/android/view/textclassifier/ConversationActions.java index 6ad5cb913553..7a6a3cd026fd 100644 --- a/core/java/android/view/textclassifier/ConversationActions.java +++ b/core/java/android/view/textclassifier/ConversationActions.java @@ -149,7 +149,7 @@ public final class ConversationActions implements Parcelable { } private Message(Parcel in) { - mAuthor = in.readParcelable(null); + mAuthor = in.readParcelable(null, android.app.Person.class); mReferenceTime = in.readInt() == 0 ? null @@ -331,13 +331,13 @@ public final class ConversationActions implements Parcelable { private static Request readFromParcel(Parcel in) { List<Message> conversation = new ArrayList<>(); - in.readParcelableList(conversation, null); - TextClassifier.EntityConfig typeConfig = in.readParcelable(null); + in.readParcelableList(conversation, null, android.view.textclassifier.ConversationActions.Message.class); + TextClassifier.EntityConfig typeConfig = in.readParcelable(null, android.view.textclassifier.TextClassifier.EntityConfig.class); int maxSuggestions = in.readInt(); List<String> hints = new ArrayList<>(); in.readStringList(hints); Bundle extras = in.readBundle(); - SystemTextClassifierMetadata systemTcMetadata = in.readParcelable(null); + SystemTextClassifierMetadata systemTcMetadata = in.readParcelable(null, android.view.textclassifier.SystemTextClassifierMetadata.class); Request request = new Request( conversation, diff --git a/core/java/android/view/textclassifier/SelectionEvent.java b/core/java/android/view/textclassifier/SelectionEvent.java index 858825b1d5ac..b34701082b80 100644 --- a/core/java/android/view/textclassifier/SelectionEvent.java +++ b/core/java/android/view/textclassifier/SelectionEvent.java @@ -172,7 +172,7 @@ public final class SelectionEvent implements Parcelable { mEnd = in.readInt(); mSmartStart = in.readInt(); mSmartEnd = in.readInt(); - mSystemTcMetadata = in.readParcelable(null); + mSystemTcMetadata = in.readParcelable(null, android.view.textclassifier.SystemTextClassifierMetadata.class); } @Override diff --git a/core/java/android/view/textclassifier/TextClassification.java b/core/java/android/view/textclassifier/TextClassification.java index 7db35d4bf8b5..8b04d35734ec 100644 --- a/core/java/android/view/textclassifier/TextClassification.java +++ b/core/java/android/view/textclassifier/TextClassification.java @@ -713,12 +713,12 @@ public final class TextClassification implements Parcelable { final CharSequence text = in.readCharSequence(); final int startIndex = in.readInt(); final int endIndex = in.readInt(); - final LocaleList defaultLocales = in.readParcelable(null); + final LocaleList defaultLocales = in.readParcelable(null, android.os.LocaleList.class); final String referenceTimeString = in.readString(); final ZonedDateTime referenceTime = referenceTimeString == null ? null : ZonedDateTime.parse(referenceTimeString); final Bundle extras = in.readBundle(); - final SystemTextClassifierMetadata systemTcMetadata = in.readParcelable(null); + final SystemTextClassifierMetadata systemTcMetadata = in.readParcelable(null, android.view.textclassifier.SystemTextClassifierMetadata.class); final Request request = new Request(text, startIndex, endIndex, defaultLocales, referenceTime, extras); diff --git a/core/java/android/view/textclassifier/TextClassificationContext.java b/core/java/android/view/textclassifier/TextClassificationContext.java index 5d5683f7110e..3a50809ea8b4 100644 --- a/core/java/android/view/textclassifier/TextClassificationContext.java +++ b/core/java/android/view/textclassifier/TextClassificationContext.java @@ -159,7 +159,7 @@ public final class TextClassificationContext implements Parcelable { mPackageName = in.readString(); mWidgetType = in.readString(); mWidgetVersion = in.readString(); - mSystemTcMetadata = in.readParcelable(null); + mSystemTcMetadata = in.readParcelable(null, android.view.textclassifier.SystemTextClassifierMetadata.class); } public static final @android.annotation.NonNull Parcelable.Creator<TextClassificationContext> CREATOR = diff --git a/core/java/android/view/textclassifier/TextClassifierEvent.java b/core/java/android/view/textclassifier/TextClassifierEvent.java index 90667cf54f93..195565c5bc09 100644 --- a/core/java/android/view/textclassifier/TextClassifierEvent.java +++ b/core/java/android/view/textclassifier/TextClassifierEvent.java @@ -189,7 +189,7 @@ public abstract class TextClassifierEvent implements Parcelable { mEventCategory = in.readInt(); mEventType = in.readInt(); mEntityTypes = in.readStringArray(); - mEventContext = in.readParcelable(null); + mEventContext = in.readParcelable(null, android.view.textclassifier.TextClassificationContext.class); mResultId = in.readString(); mEventIndex = in.readInt(); int scoresLength = in.readInt(); diff --git a/core/java/android/view/textclassifier/TextLanguage.java b/core/java/android/view/textclassifier/TextLanguage.java index 604979b1ac78..67167c6d3e65 100644 --- a/core/java/android/view/textclassifier/TextLanguage.java +++ b/core/java/android/view/textclassifier/TextLanguage.java @@ -295,7 +295,7 @@ public final class TextLanguage implements Parcelable { private static Request readFromParcel(Parcel in) { final CharSequence text = in.readCharSequence(); final Bundle extra = in.readBundle(); - final SystemTextClassifierMetadata systemTcMetadata = in.readParcelable(null); + final SystemTextClassifierMetadata systemTcMetadata = in.readParcelable(null, android.view.textclassifier.SystemTextClassifierMetadata.class); final Request request = new Request(text, extra); request.setSystemTextClassifierMetadata(systemTcMetadata); diff --git a/core/java/android/view/textclassifier/TextLinks.java b/core/java/android/view/textclassifier/TextLinks.java index dea3a9010b18..445e9ecff54f 100644 --- a/core/java/android/view/textclassifier/TextLinks.java +++ b/core/java/android/view/textclassifier/TextLinks.java @@ -558,13 +558,13 @@ public final class TextLinks implements Parcelable { private static Request readFromParcel(Parcel in) { final String text = in.readString(); - final LocaleList defaultLocales = in.readParcelable(null); - final EntityConfig entityConfig = in.readParcelable(null); + final LocaleList defaultLocales = in.readParcelable(null, android.os.LocaleList.class); + final EntityConfig entityConfig = in.readParcelable(null, android.view.textclassifier.TextClassifier.EntityConfig.class); final Bundle extras = in.readBundle(); final String referenceTimeString = in.readString(); final ZonedDateTime referenceTime = referenceTimeString == null ? null : ZonedDateTime.parse(referenceTimeString); - final SystemTextClassifierMetadata systemTcMetadata = in.readParcelable(null); + final SystemTextClassifierMetadata systemTcMetadata = in.readParcelable(null, android.view.textclassifier.SystemTextClassifierMetadata.class); final Request request = new Request(text, defaultLocales, entityConfig, /* legacyFallback= */ true, referenceTime, extras); diff --git a/core/java/android/view/textclassifier/TextSelection.java b/core/java/android/view/textclassifier/TextSelection.java index c1913f69546c..dda0fcdd44fd 100644 --- a/core/java/android/view/textclassifier/TextSelection.java +++ b/core/java/android/view/textclassifier/TextSelection.java @@ -489,9 +489,9 @@ public final class TextSelection implements Parcelable { final CharSequence text = in.readCharSequence(); final int startIndex = in.readInt(); final int endIndex = in.readInt(); - final LocaleList defaultLocales = in.readParcelable(null); + final LocaleList defaultLocales = in.readParcelable(null, android.os.LocaleList.class); final Bundle extras = in.readBundle(); - final SystemTextClassifierMetadata systemTcMetadata = in.readParcelable(null); + final SystemTextClassifierMetadata systemTcMetadata = in.readParcelable(null, android.view.textclassifier.SystemTextClassifierMetadata.class); final boolean includeTextClassification = in.readBoolean(); final Request request = new Request(text, startIndex, endIndex, defaultLocales, @@ -548,6 +548,6 @@ public final class TextSelection implements Parcelable { mEntityConfidence = EntityConfidence.CREATOR.createFromParcel(in); mId = in.readString(); mExtras = in.readBundle(); - mTextClassification = in.readParcelable(TextClassification.class.getClassLoader()); + mTextClassification = in.readParcelable(TextClassification.class.getClassLoader(), android.view.textclassifier.TextClassification.class); } } diff --git a/core/java/android/view/translation/TranslationRequest.java b/core/java/android/view/translation/TranslationRequest.java index 0d41851ca704..027edc21389f 100644 --- a/core/java/android/view/translation/TranslationRequest.java +++ b/core/java/android/view/translation/TranslationRequest.java @@ -255,9 +255,9 @@ public final class TranslationRequest implements Parcelable { int flags = in.readInt(); List<TranslationRequestValue> translationRequestValues = new ArrayList<>(); - in.readParcelableList(translationRequestValues, TranslationRequestValue.class.getClassLoader()); + in.readParcelableList(translationRequestValues, TranslationRequestValue.class.getClassLoader(), android.view.translation.TranslationRequestValue.class); List<ViewTranslationRequest> viewTranslationRequests = new ArrayList<>(); - in.readParcelableList(viewTranslationRequests, ViewTranslationRequest.class.getClassLoader()); + in.readParcelableList(viewTranslationRequests, ViewTranslationRequest.class.getClassLoader(), android.view.translation.ViewTranslationRequest.class); this.mFlags = flags; diff --git a/core/java/android/view/translation/TranslationSpec.java b/core/java/android/view/translation/TranslationSpec.java index efc3d8ba8096..76dda5fbe83b 100644 --- a/core/java/android/view/translation/TranslationSpec.java +++ b/core/java/android/view/translation/TranslationSpec.java @@ -64,7 +64,7 @@ public final class TranslationSpec implements Parcelable { } static ULocale unparcelLocale(Parcel in) { - return (ULocale) in.readSerializable(); + return (ULocale) in.readSerializable(android.icu.util.ULocale.class.getClassLoader(), android.icu.util.ULocale.class); } /** diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index ac28c31ecf49..dd70d69a8e02 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -17,6 +17,7 @@ package android.widget; import static android.view.ContentInfo.SOURCE_DRAG_AND_DROP; +import static android.widget.TextView.ACCESSIBILITY_ACTION_SMART_START_ID; import android.R; import android.animation.ValueAnimator; @@ -84,6 +85,7 @@ import android.text.style.URLSpan; import android.util.ArraySet; import android.util.DisplayMetrics; import android.util.Log; +import android.util.Pair; import android.util.SparseArray; import android.util.TypedValue; import android.view.ActionMode; @@ -112,6 +114,7 @@ import android.view.ViewParent; import android.view.ViewTreeObserver; import android.view.WindowManager; import android.view.accessibility.AccessibilityNodeInfo; +import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; import android.view.animation.LinearInterpolator; import android.view.inputmethod.CorrectionInfo; import android.view.inputmethod.CursorAnchorInfo; @@ -449,11 +452,14 @@ public class Editor { private int mLineChangeSlopMax; private int mLineChangeSlopMin; + private final AccessibilitySmartActions mA11ySmartActions; + Editor(TextView textView) { mTextView = textView; // Synchronize the filter list, which places the undo input filter at the end. mTextView.setFilters(mTextView.getFilters()); mProcessTextIntentActionsHandler = new ProcessTextIntentActionsHandler(this); + mA11ySmartActions = new AccessibilitySmartActions(mTextView); mHapticTextHandleEnabled = mTextView.getContext().getResources().getBoolean( com.android.internal.R.bool.config_enableHapticTextHandle); @@ -4381,6 +4387,7 @@ public class Editor { item.setShowAsAction(showAsAction); mAssistClickHandlers.put(item, TextClassification.createIntentOnClickListener(action.getActionIntent())); + mA11ySmartActions.addAction(action); return item; } @@ -4394,6 +4401,7 @@ public class Editor { } i++; } + mA11ySmartActions.reset(); } private boolean hasLegacyAssistItem(TextClassification classification) { @@ -7656,7 +7664,7 @@ public class Editor { private final PackageManager mPackageManager; private final String mPackageName; private final SparseArray<Intent> mAccessibilityIntents = new SparseArray<>(); - private final SparseArray<AccessibilityNodeInfo.AccessibilityAction> mAccessibilityActions = + private final SparseArray<AccessibilityAction> mAccessibilityActions = new SparseArray<>(); private final List<ResolveInfo> mSupportedActivities = new ArrayList<>(); @@ -7706,8 +7714,7 @@ public class Editor { int actionId = TextView.ACCESSIBILITY_ACTION_PROCESS_TEXT_START_ID + i++; mAccessibilityActions.put( actionId, - new AccessibilityNodeInfo.AccessibilityAction( - actionId, getLabel(resolveInfo))); + new AccessibilityAction(actionId, getLabel(resolveInfo))); mAccessibilityIntents.put( actionId, createProcessTextIntentForResolveInfo(resolveInfo)); } @@ -7786,6 +7793,65 @@ public class Editor { } } + /** + * Accessibility helper for "smart" (i.e. textAssist) actions. + * Helps ensure that "smart" actions are shown in the accessibility menu. + * NOTE that these actions are only available when an action mode is live. + * + * @hide + */ + private static final class AccessibilitySmartActions { + + private final TextView mTextView; + private final SparseArray<Pair<AccessibilityAction, RemoteAction>> mActions = + new SparseArray<>(); + + private AccessibilitySmartActions(TextView textView) { + mTextView = Objects.requireNonNull(textView); + } + + private void addAction(RemoteAction action) { + final int actionId = ACCESSIBILITY_ACTION_SMART_START_ID + mActions.size(); + mActions.put(actionId, + new Pair(new AccessibilityAction(actionId, action.getTitle()), action)); + } + + private void reset() { + mActions.clear(); + } + + void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo nodeInfo) { + for (int i = 0; i < mActions.size(); i++) { + nodeInfo.addAction(mActions.valueAt(i).first); + } + } + + boolean performAccessibilityAction(int actionId) { + final Pair<AccessibilityAction, RemoteAction> pair = mActions.get(actionId); + if (pair != null) { + TextClassification.createIntentOnClickListener(pair.second.getActionIntent()) + .onClick(mTextView); + return true; + } + return false; + } + } + + /** + * Initializes the nodeInfo with smart actions. + */ + void onInitializeSmartActionsAccessibilityNodeInfo(AccessibilityNodeInfo nodeInfo) { + mA11ySmartActions.onInitializeAccessibilityNodeInfo(nodeInfo); + } + + /** + * Handles the accessibility action if it is an active smart action. + * Return false if this method does not hanle the action. + */ + boolean performSmartActionsAccessibilityAction(int actionId) { + return mA11ySmartActions.performAccessibilityAction(actionId); + } + static void logCursor(String location, @Nullable String msgFormat, Object ... msgArgs) { if (msgFormat == null) { Log.d(TAG, location); diff --git a/core/java/android/widget/ExpandableListView.java b/core/java/android/widget/ExpandableListView.java index 51869d4e04d5..e243aae81da4 100644 --- a/core/java/android/widget/ExpandableListView.java +++ b/core/java/android/widget/ExpandableListView.java @@ -1309,7 +1309,7 @@ public class ExpandableListView extends ListView { private SavedState(Parcel in) { super(in); expandedGroupMetadataList = new ArrayList<ExpandableListConnector.GroupMetadata>(); - in.readList(expandedGroupMetadataList, ExpandableListConnector.class.getClassLoader()); + in.readList(expandedGroupMetadataList, ExpandableListConnector.class.getClassLoader(), android.widget.ExpandableListConnector.GroupMetadata.class); } @Override diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index e60f9a648730..b21d08c8e664 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -1489,7 +1489,7 @@ public class RemoteViews implements Parcelable, Filter { SetRippleDrawableColor(Parcel parcel) { viewId = parcel.readInt(); - mColorStateList = parcel.readParcelable(null); + mColorStateList = parcel.readParcelable(null, android.content.res.ColorStateList.class); } public void writeToParcel(Parcel dest, int flags) { diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 0fe06befa789..41c540116928 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -350,6 +350,7 @@ import java.util.function.Supplier; * @attr ref android.R.styleable#TextView_breakStrategy * @attr ref android.R.styleable#TextView_hyphenationFrequency * @attr ref android.R.styleable#TextView_lineBreakStyle + * @attr ref android.R.styleable#TextView_lineBreakWordStyle * @attr ref android.R.styleable#TextView_autoSizeTextType * @attr ref android.R.styleable#TextView_autoSizeMinTextSize * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize @@ -442,6 +443,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener // Accessibility action start id for "process text" actions. static final int ACCESSIBILITY_ACTION_PROCESS_TEXT_START_ID = 0x10000100; + /** Accessibility action start id for "smart" actions. @hide */ + static final int ACCESSIBILITY_ACTION_SMART_START_ID = 0x10001000; + /** * @hide */ @@ -458,6 +462,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private static final int FLOATING_TOOLBAR_SELECT_ALL_REFRESH_DELAY = 500; + // The default value of the line break style. + private static final int DEFAULT_LINE_BREAK_STYLE = LineBreakConfig.LINE_BREAK_STYLE_NONE; + + // The default value of the line break word style. + private static final int DEFAULT_LINE_BREAK_WORD_STYLE = + LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE; + /** * This change ID enables the fallback text line spacing (line height) for BoringLayout. * @hide @@ -1450,6 +1461,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener a.getInt(attr, LineBreakConfig.LINE_BREAK_STYLE_NONE)); break; + case com.android.internal.R.styleable.TextView_lineBreakWordStyle: + mLineBreakConfig.setLineBreakWordStyle( + a.getInt(attr, LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE)); + break; + case com.android.internal.R.styleable.TextView_autoSizeTextType: mAutoSizeTextType = a.getInt(attr, AUTO_SIZE_TEXT_TYPE_NONE); break; @@ -3982,6 +3998,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener float mLetterSpacing = 0; String mFontFeatureSettings = null; String mFontVariationSettings = null; + boolean mHasLineBreakStyle = false; + boolean mHasLineBreakWordStyle = false; + int mLineBreakStyle = DEFAULT_LINE_BREAK_STYLE; + int mLineBreakWordStyle = DEFAULT_LINE_BREAK_WORD_STYLE; @Override public String toString() { @@ -4012,6 +4032,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener + " mLetterSpacing:" + mLetterSpacing + "\n" + " mFontFeatureSettings:" + mFontFeatureSettings + "\n" + " mFontVariationSettings:" + mFontVariationSettings + "\n" + + " mHasLineBreakStyle:" + mHasLineBreakStyle + "\n" + + " mHasLineBreakWordStyle:" + mHasLineBreakWordStyle + "\n" + + " mLineBreakStyle:" + mLineBreakStyle + "\n" + + " mLineBreakWordStyle:" + mLineBreakWordStyle + "\n" + "}"; } } @@ -4059,6 +4083,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener com.android.internal.R.styleable.TextAppearance_fontFeatureSettings); sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontVariationSettings, com.android.internal.R.styleable.TextAppearance_fontVariationSettings); + sAppearanceValues.put(com.android.internal.R.styleable.TextView_lineBreakStyle, + com.android.internal.R.styleable.TextAppearance_lineBreakStyle); + sAppearanceValues.put(com.android.internal.R.styleable.TextView_lineBreakWordStyle, + com.android.internal.R.styleable.TextAppearance_lineBreakWordStyle); } /** @@ -4174,6 +4202,16 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener case com.android.internal.R.styleable.TextAppearance_fontVariationSettings: attributes.mFontVariationSettings = appearance.getString(attr); break; + case com.android.internal.R.styleable.TextAppearance_lineBreakStyle: + attributes.mHasLineBreakStyle = true; + attributes.mLineBreakStyle = + appearance.getInt(attr, attributes.mLineBreakStyle); + break; + case com.android.internal.R.styleable.TextAppearance_lineBreakWordStyle: + attributes.mHasLineBreakWordStyle = true; + attributes.mLineBreakWordStyle = + appearance.getInt(attr, attributes.mLineBreakWordStyle); + break; default: } } @@ -4239,9 +4277,46 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (attributes.mFontVariationSettings != null) { setFontVariationSettings(attributes.mFontVariationSettings); } + + if (attributes.mHasLineBreakStyle || attributes.mHasLineBreakWordStyle) { + updateLineBreakConfigFromTextAppearance(attributes.mHasLineBreakStyle, + attributes.mHasLineBreakWordStyle, attributes.mLineBreakStyle, + attributes.mLineBreakWordStyle); + } } /** + * Updates the LineBreakConfig from the TextAppearance. + * + * This method updates the given line configuration from the TextAppearance. This method will + * request new layout if line break config has been changed. + * + * @param isLineBreakStyleSpecified true if the line break style is specified. + * @param isLineBreakWordStyleSpecified true if the line break word style is specified. + * @param lineBreakStyle the value of the line break style in the TextAppearance. + * @param lineBreakWordStyle the value of the line break word style in the TextAppearance. + */ + private void updateLineBreakConfigFromTextAppearance(boolean isLineBreakStyleSpecified, + boolean isLineBreakWordStyleSpecified, + @LineBreakConfig.LineBreakStyle int lineBreakStyle, + @LineBreakConfig.LineBreakWordStyle int lineBreakWordStyle) { + boolean updated = false; + if (isLineBreakStyleSpecified && mLineBreakConfig.getLineBreakStyle() != lineBreakStyle) { + mLineBreakConfig.setLineBreakStyle(lineBreakStyle); + updated = true; + } + if (isLineBreakWordStyleSpecified + && mLineBreakConfig.getLineBreakWordStyle() != lineBreakWordStyle) { + mLineBreakConfig.setLineBreakWordStyle(lineBreakWordStyle); + updated = true; + } + if (updated && mLayout != null) { + nullLayouts(); + requestLayout(); + invalidate(); + } + } + /** * Get the default primary {@link Locale} of the text in this TextView. This will always be * the first member of {@link #getTextLocales()}. * @return the default primary {@link Locale} of the text in this TextView. @@ -4797,18 +4872,29 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener /** * Sets line break configuration indicates which strategy needs to be used when calculating the - * text wrapping. There are thee strategies for the line break style(lb): + * text wrapping. + * <P> + * There are two types of line break rules that can be configured at the same time. One is + * line break style(lb) and the other is line break word style(lw). The line break style + * affects rule-based breaking. The line break word style affects dictionary-based breaking + * and provide phrase-based breaking opportunities. There are several types for the + * line break style: * {@link LineBreakConfig#LINE_BREAK_STYLE_LOOSE}, * {@link LineBreakConfig#LINE_BREAK_STYLE_NORMAL} and * {@link LineBreakConfig#LINE_BREAK_STYLE_STRICT}. - * The default value of the line break style is {@link LineBreakConfig#LINE_BREAK_STYLE_NONE}, - * which means no line break style is specified. + * The type for the line break word style is + * {@link LineBreakConfig#LINE_BREAK_WORD_STYLE_PHRASE}. + * The default values of the line break style and the line break word style are + * {@link LineBreakConfig#LINE_BREAK_STYLE_NONE} and + * {@link LineBreakConfig#LINE_BREAK_WORD_STYLE_NONE} respectively, indicating that no line + * breaking rules are specified. * See <a href="https://drafts.csswg.org/css-text/#line-break-property"> * the line-break property</a> * * @param lineBreakConfig the line break config for text wrapping. */ public void setLineBreakConfig(@NonNull LineBreakConfig lineBreakConfig) { + Objects.requireNonNull(lineBreakConfig); if (mLineBreakConfig.equals(lineBreakConfig)) { return; } @@ -4855,7 +4941,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener mTextDir = params.getTextDirection(); mBreakStrategy = params.getBreakStrategy(); mHyphenationFrequency = params.getHyphenationFrequency(); - mLineBreakConfig.set(params.getLineBreakConfig()); + if (params.getLineBreakConfig() != null) { + mLineBreakConfig.set(params.getLineBreakConfig()); + } else { + // Set default value if the line break config in the PrecomputedText.Params is null. + mLineBreakConfig.setLineBreakStyle(DEFAULT_LINE_BREAK_STYLE); + mLineBreakConfig.setLineBreakWordStyle(DEFAULT_LINE_BREAK_WORD_STYLE); + } if (mLayout != null) { nullLayouts(); requestLayout(); @@ -12223,6 +12315,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } if (canProcessText()) { // also implies mEditor is not null. mEditor.mProcessTextIntentActionsHandler.onInitializeAccessibilityNodeInfo(info); + mEditor.onInitializeSmartActionsAccessibilityNodeInfo(info); } } @@ -12426,9 +12519,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener */ @Override public boolean performAccessibilityActionInternal(int action, Bundle arguments) { - if (mEditor != null - && mEditor.mProcessTextIntentActionsHandler.performAccessibilityAction(action)) { - return true; + if (mEditor != null) { + if (mEditor.mProcessTextIntentActionsHandler.performAccessibilityAction(action) + || mEditor.performSmartActionsAccessibilityAction(action)) { + return true; + } } switch (action) { case AccessibilityNodeInfo.ACTION_CLICK: { diff --git a/core/java/android/window/BackNavigationInfo.aidl b/core/java/android/window/BackNavigationInfo.aidl new file mode 100644 index 000000000000..1529902b9c20 --- /dev/null +++ b/core/java/android/window/BackNavigationInfo.aidl @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.window; + +/** + * @hide + */ +parcelable BackNavigationInfo;
\ No newline at end of file diff --git a/core/java/android/window/BackNavigationInfo.java b/core/java/android/window/BackNavigationInfo.java new file mode 100644 index 000000000000..571714cc05d5 --- /dev/null +++ b/core/java/android/window/BackNavigationInfo.java @@ -0,0 +1,242 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.window; + +import static java.util.Objects.requireNonNull; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.WindowConfiguration; +import android.hardware.HardwareBuffer; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.RemoteCallback; +import android.view.SurfaceControl; + +/** + * Information to be sent to SysUI about a back event. + * + * @hide + */ +public final class BackNavigationInfo implements Parcelable { + + /** + * The target of the back navigation is undefined. + */ + public static final int TYPE_UNDEFINED = -1; + + /** + * Navigating back will close the currently visible dialog + */ + public static final int TYPE_DIALOG_CLOSE = 0; + + /** + * Navigating back will bring the user back to the home screen + */ + public static final int TYPE_RETURN_TO_HOME = 1; + + /** + * Navigating back will bring the user to the previous activity in the same Task + */ + public static final int TYPE_CROSS_ACTIVITY = 2; + + /** + * Navigating back will bring the user to the previous activity in the previous Task + */ + public static final int TYPE_CROSS_TASK = 3; + + /** + * Defines the type of back destinations a back even can lead to. This is used to define the + * type of animation that need to be run on SystemUI. + */ + @IntDef(prefix = "TYPE_", value = { + TYPE_UNDEFINED, + TYPE_DIALOG_CLOSE, + TYPE_RETURN_TO_HOME, + TYPE_CROSS_ACTIVITY, + TYPE_CROSS_TASK}) + @interface BackTargetType { + } + + private final int mType; + @Nullable + private final SurfaceControl mDepartingWindowContainer; + @Nullable + private final SurfaceControl mScreenshotSurface; + @Nullable + private final HardwareBuffer mScreenshotBuffer; + @Nullable + private final RemoteCallback mRemoteCallback; + @Nullable + private final WindowConfiguration mTaskWindowConfiguration; + + /** + * Create a new {@link BackNavigationInfo} instance. + * + * @param type The {@link BackTargetType} of the destination (what will be displayed after + * the back action) + * @param topWindowLeash The leash to animate away the current topWindow. The consumer + * of the leash is responsible for removing it. + * @param screenshotSurface The screenshot of the previous activity to be displayed. + * @param screenshotBuffer A buffer containing a screenshot used to display the activity. + * See {@link #getScreenshotHardwareBuffer()} for information + * about nullity. + * @param taskWindowConfiguration The window configuration of the Task being animated + * beneath. + * @param onBackNavigationDone The callback to be called once the client is done with the back + * preview. + */ + public BackNavigationInfo(@BackTargetType int type, + @Nullable SurfaceControl topWindowLeash, + @Nullable SurfaceControl screenshotSurface, + @Nullable HardwareBuffer screenshotBuffer, + @Nullable WindowConfiguration taskWindowConfiguration, + @NonNull RemoteCallback onBackNavigationDone) { + mType = type; + mDepartingWindowContainer = topWindowLeash; + mScreenshotSurface = screenshotSurface; + mScreenshotBuffer = screenshotBuffer; + mTaskWindowConfiguration = taskWindowConfiguration; + mRemoteCallback = onBackNavigationDone; + } + + private BackNavigationInfo(@NonNull Parcel in) { + mType = in.readInt(); + mDepartingWindowContainer = in.readTypedObject(SurfaceControl.CREATOR); + mScreenshotSurface = in.readTypedObject(SurfaceControl.CREATOR); + mScreenshotBuffer = in.readTypedObject(HardwareBuffer.CREATOR); + mTaskWindowConfiguration = in.readTypedObject(WindowConfiguration.CREATOR); + mRemoteCallback = requireNonNull(in.readTypedObject(RemoteCallback.CREATOR)); + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mType); + dest.writeTypedObject(mDepartingWindowContainer, flags); + dest.writeTypedObject(mScreenshotSurface, flags); + dest.writeTypedObject(mScreenshotBuffer, flags); + dest.writeTypedObject(mTaskWindowConfiguration, flags); + dest.writeTypedObject(mRemoteCallback, flags); + } + + /** + * Returns the type of back navigation that is about to happen. + * @see BackTargetType + */ + public @BackTargetType int getType() { + return mType; + } + + /** + * Returns a leash to the top window container that needs to be animated. This can be null if + * the back animation is controlled by the application. + */ + @Nullable + public SurfaceControl getDepartingWindowContainer() { + return mDepartingWindowContainer; + } + + /** + * Returns the {@link SurfaceControl} that should be used to display a screenshot of the + * previous activity. + */ + @Nullable + public SurfaceControl getScreenshotSurface() { + return mScreenshotSurface; + } + + /** + * Returns the {@link HardwareBuffer} containing the screenshot the activity about to be + * shown. This can be null if one of the following conditions is met: + * <ul> + * <li>The screenshot is not available + * <li> The previous activity is the home screen ( {@link #TYPE_RETURN_TO_HOME} + * <li> The current window is a dialog ({@link #TYPE_DIALOG_CLOSE} + * <li> The back animation is controlled by the application + * </ul> + */ + @Nullable + public HardwareBuffer getScreenshotHardwareBuffer() { + return mScreenshotBuffer; + } + + /** + * Returns the {@link WindowConfiguration} of the current task. This is null when the top + * application is controlling the back animation. + */ + @Nullable + public WindowConfiguration getTaskWindowConfiguration() { + return mTaskWindowConfiguration; + } + + /** + * Callback to be called when the back preview is finished in order to notify the server that + * it can clean up the resources created for the animation. + */ + public void onBackNavigationFinished() { + mRemoteCallback.sendResult(null); + } + + @Override + public int describeContents() { + return 0; + } + + public static final Creator<BackNavigationInfo> CREATOR = new Creator<BackNavigationInfo>() { + @Override + public BackNavigationInfo createFromParcel(Parcel in) { + return new BackNavigationInfo(in); + } + + @Override + public BackNavigationInfo[] newArray(int size) { + return new BackNavigationInfo[size]; + } + }; + + @Override + public String toString() { + return "BackNavigationInfo{" + + "mType=" + typeToString(mType) + " (" + mType + ")" + + ", mDepartingWindowContainer=" + mDepartingWindowContainer + + ", mScreenshotSurface=" + mScreenshotSurface + + ", mTaskWindowConfiguration= " + mTaskWindowConfiguration + + ", mScreenshotBuffer=" + mScreenshotBuffer + + ", mRemoteCallback=" + mRemoteCallback + + '}'; + } + + /** + * Translates the {@link BackNavigationInfo} integer type to its String representation + */ + public static String typeToString(@BackTargetType int type) { + switch (type) { + case TYPE_UNDEFINED: + return "TYPE_UNDEFINED"; + case TYPE_DIALOG_CLOSE: + return "TYPE_DIALOG_CLOSE"; + case TYPE_RETURN_TO_HOME: + return "TYPE_RETURN_TO_HOME"; + case TYPE_CROSS_ACTIVITY: + return "TYPE_CROSS_ACTIVITY"; + case TYPE_CROSS_TASK: + return "TYPE_CROSS_TASK"; + } + return String.valueOf(type); + } +} diff --git a/core/java/android/window/IOnBackInvokedCallback.aidl b/core/java/android/window/IOnBackInvokedCallback.aidl new file mode 100644 index 000000000000..a42863c3126f --- /dev/null +++ b/core/java/android/window/IOnBackInvokedCallback.aidl @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + + */ + +package android.window; + +/** + * Interface that wraps a {@link OnBackInvokedCallback} object, to be stored in window manager + * and called from back handling process when back is invoked. + * + * @hide + */ +oneway interface IOnBackInvokedCallback { + /** + * Called when a back gesture has been started, or back button has been pressed down. + * Wraps {@link OnBackInvokedCallback#onBackStarted()}. + */ + void onBackStarted(); + + /** + * Called on back gesture progress. + * Wraps {@link OnBackInvokedCallback#onBackProgressed()}. + * + * @param touchX Absolute X location of the touch point. + * @param touchY Absolute Y location of the touch point. + * @param progress Value between 0 and 1 on how far along the back gesture is. + */ + void onBackProgressed(int touchX, int touchY, float progress); + + /** + * Called when a back gesture or back button press has been cancelled. + * Wraps {@link OnBackInvokedCallback#onBackCancelled()}. + */ + void onBackCancelled(); + + /** + * Called when a back gesture has been completed and committed, or back button pressed + * has been released and committed. + * Wraps {@link OnBackInvokedCallback#onBackInvoked()}. + */ + void onBackInvoked(); +} diff --git a/core/java/android/window/IOnFpsCallbackListener.aidl b/core/java/android/window/IOnFpsCallbackListener.aidl new file mode 100644 index 000000000000..3091df3b23a3 --- /dev/null +++ b/core/java/android/window/IOnFpsCallbackListener.aidl @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.window; + +/** + * @hide + */ +oneway interface IOnFpsCallbackListener { + + /** + * Reports the fps from the registered task + * @param fps The frame rate per second of the task that has the registered task id + * and its children. + */ + void onFpsReported(in float fps); +} diff --git a/core/java/android/window/TaskFpsCallback.java b/core/java/android/window/TaskFpsCallback.java new file mode 100644 index 000000000000..a8e01b6df4b8 --- /dev/null +++ b/core/java/android/window/TaskFpsCallback.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.window; + +import android.annotation.BinderThread; +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.os.RemoteException; + +import java.util.concurrent.Executor; + +/** + * Callback for sampling the frames per second for a task and its children. + * This should only be used by a system component that needs to listen to a task's + * tree's FPS when it is not actively submitting transactions for that corresponding SurfaceControl. + * Otherwise, ASurfaceTransaction_OnComplete callbacks should be used. + * + * Each callback can only register for receiving FPS report for one task id until + * {@link WindowManager#unregister()} is called. + * + * @hide + */ +@SystemApi +public final class TaskFpsCallback { + + /** + * Listener interface to receive frame per second of a task. + */ + public interface OnFpsCallbackListener { + /** + * Reports the fps from the registered task + * @param fps The frame per second of the task that has the registered task id + * and its children. + */ + void onFpsReported(float fps); + } + + private final IOnFpsCallbackListener mListener; + + public TaskFpsCallback(@NonNull Executor executor, @NonNull OnFpsCallbackListener listener) { + mListener = new IOnFpsCallbackListener.Stub() { + @Override + public void onFpsReported(float fps) { + executor.execute(() -> { + listener.onFpsReported(fps); + }); + } + }; + } + + /** + * @hide + */ + public IOnFpsCallbackListener getListener() { + return mListener; + } + + /** + * Dispatch the collected sample. + * + * Called from native code on a binder thread. + */ + @BinderThread + private static void dispatchOnFpsReported( + @NonNull IOnFpsCallbackListener listener, float fps) { + try { + listener.onFpsReported(fps); + } catch (RemoteException e) { + /* ignore */ + } + } +} diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java new file mode 100644 index 000000000000..29786046e49c --- /dev/null +++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java @@ -0,0 +1,201 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.window; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Handler; +import android.os.RemoteException; +import android.util.Log; +import android.view.IWindow; +import android.view.IWindowSession; +import android.view.OnBackInvokedCallback; +import android.view.OnBackInvokedDispatcher; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.TreeMap; + +/** + * Provides window based implementation of {@link OnBackInvokedDispatcher}. + * + * Callbacks with higher priorities receive back dispatching first. + * Within the same priority, callbacks receive back dispatching in the reverse order + * in which they are added. + * + * When the top priority callback is updated, the new callback is propagated to the Window Manager + * if the window the instance is associated with has been attached. It is allowed to register / + * unregister {@link OnBackInvokedCallback}s before the window is attached, although callbacks + * will not receive dispatches until window attachment. + * + * @hide + */ +public class WindowOnBackInvokedDispatcher extends OnBackInvokedDispatcher { + private IWindowSession mWindowSession; + private IWindow mWindow; + private static final String TAG = "WindowOnBackDispatcher"; + private static final boolean DEBUG = false; + + /** The currently most prioritized callback. */ + @Nullable + private OnBackInvokedCallbackWrapper mTopCallback; + + /** Convenience hashmap to quickly decide if a callback has been added. */ + private final HashMap<OnBackInvokedCallback, Integer> mAllCallbacks = new HashMap<>(); + /** Holds all callbacks by priorities. */ + private final TreeMap<Integer, ArrayList<OnBackInvokedCallback>> + mOnBackInvokedCallbacks = new TreeMap<>(); + + /** + * Sends the pending top callback (if one exists) to WM when the view root + * is attached a window. + */ + public void attachToWindow(@NonNull IWindowSession windowSession, @NonNull IWindow window) { + mWindowSession = windowSession; + mWindow = window; + if (mTopCallback != null) { + setTopOnBackInvokedCallback(mTopCallback); + } + } + + /** Detaches the dispatcher instance from its window. */ + public void detachFromWindow() { + mWindow = null; + mWindowSession = null; + } + + // TODO: Take an Executor for the callback to run on. + @Override + public void registerOnBackInvokedCallback( + @NonNull OnBackInvokedCallback callback, @Priority int priority) { + if (!mOnBackInvokedCallbacks.containsKey(priority)) { + mOnBackInvokedCallbacks.put(priority, new ArrayList<>()); + } + ArrayList<OnBackInvokedCallback> callbacks = mOnBackInvokedCallbacks.get(priority); + + // If callback has already been added, remove it and re-add it. + if (mAllCallbacks.containsKey(callback)) { + if (DEBUG) { + Log.i(TAG, "Callback already added. Removing and re-adding it."); + } + Integer prevPriority = mAllCallbacks.get(callback); + mOnBackInvokedCallbacks.get(prevPriority).remove(callback); + } + + callbacks.add(callback); + mAllCallbacks.put(callback, priority); + if (mTopCallback == null || (mTopCallback.getCallback() != callback + && mAllCallbacks.get(mTopCallback.getCallback()) <= priority)) { + setTopOnBackInvokedCallback(new OnBackInvokedCallbackWrapper(callback, priority)); + } + } + + @Override + public void unregisterOnBackInvokedCallback(@NonNull OnBackInvokedCallback callback) { + if (!mAllCallbacks.containsKey(callback)) { + if (DEBUG) { + Log.i(TAG, "Callback not found. returning..."); + } + return; + } + Integer priority = mAllCallbacks.get(callback); + mOnBackInvokedCallbacks.get(priority).remove(callback); + mAllCallbacks.remove(callback); + if (mTopCallback != null && mTopCallback.getCallback() == callback) { + findAndSetTopOnBackInvokedCallback(); + } + } + + /** Clears all registered callbacks on the instance. */ + public void clear() { + mAllCallbacks.clear(); + mTopCallback = null; + mOnBackInvokedCallbacks.clear(); + } + + /** + * Iterates through all callbacks to find the most prioritized one and pushes it to + * window manager. + */ + private void findAndSetTopOnBackInvokedCallback() { + if (mAllCallbacks.isEmpty()) { + setTopOnBackInvokedCallback(null); + return; + } + + for (Integer priority : mOnBackInvokedCallbacks.descendingKeySet()) { + ArrayList<OnBackInvokedCallback> callbacks = mOnBackInvokedCallbacks.get(priority); + if (!callbacks.isEmpty()) { + OnBackInvokedCallbackWrapper callback = new OnBackInvokedCallbackWrapper( + callbacks.get(callbacks.size() - 1), priority); + setTopOnBackInvokedCallback(callback); + return; + } + } + setTopOnBackInvokedCallback(null); + } + + // Pushes the top priority callback to window manager. + private void setTopOnBackInvokedCallback(@Nullable OnBackInvokedCallbackWrapper callback) { + mTopCallback = callback; + if (mWindowSession == null || mWindow == null) { + return; + } + try { + mWindowSession.setOnBackInvokedCallback(mWindow, mTopCallback); + } catch (RemoteException e) { + Log.e(TAG, "Failed to set OnBackInvokedCallback to WM. Error: " + e); + } + } + + private class OnBackInvokedCallbackWrapper extends IOnBackInvokedCallback.Stub { + private final OnBackInvokedCallback mCallback; + private final @Priority int mPriority; + + OnBackInvokedCallbackWrapper( + @NonNull OnBackInvokedCallback callback, @Priority int priority) { + mCallback = callback; + mPriority = priority; + } + + @NonNull + public OnBackInvokedCallback getCallback() { + return mCallback; + } + + @Override + public void onBackStarted() throws RemoteException { + Handler.getMain().post(() -> mCallback.onBackStarted()); + } + + @Override + public void onBackProgressed(int touchX, int touchY, float progress) + throws RemoteException { + Handler.getMain().post(() -> mCallback.onBackProgressed(touchX, touchY, progress)); + } + + @Override + public void onBackCancelled() throws RemoteException { + Handler.getMain().post(() -> mCallback.onBackCancelled()); + } + + @Override + public void onBackInvoked() throws RemoteException { + Handler.getMain().post(() -> mCallback.onBackInvoked()); + } + } +} diff --git a/core/java/com/android/ims/internal/uce/options/OptionsCapInfo.java b/core/java/com/android/ims/internal/uce/options/OptionsCapInfo.java index 6f83bf3224a8..d709acfc2872 100644 --- a/core/java/com/android/ims/internal/uce/options/OptionsCapInfo.java +++ b/core/java/com/android/ims/internal/uce/options/OptionsCapInfo.java @@ -89,6 +89,6 @@ public class OptionsCapInfo implements Parcelable { public void readFromParcel(Parcel source) { mSdp = source.readString(); - mCapInfo = source.readParcelable(CapInfo.class.getClassLoader()); + mCapInfo = source.readParcelable(CapInfo.class.getClassLoader(), com.android.ims.internal.uce.common.CapInfo.class); } }
\ No newline at end of file diff --git a/core/java/com/android/ims/internal/uce/options/OptionsCmdStatus.java b/core/java/com/android/ims/internal/uce/options/OptionsCmdStatus.java index 461f8bfb48c8..559d61b20d8c 100644 --- a/core/java/com/android/ims/internal/uce/options/OptionsCmdStatus.java +++ b/core/java/com/android/ims/internal/uce/options/OptionsCmdStatus.java @@ -147,8 +147,8 @@ public class OptionsCmdStatus implements Parcelable { /** @hide */ public void readFromParcel(Parcel source) { mUserData = source.readInt(); - mCmdId = source.readParcelable(OptionsCmdId.class.getClassLoader()); - mStatus = source.readParcelable(StatusCode.class.getClassLoader()); - mCapInfo = source.readParcelable(CapInfo.class.getClassLoader()); + mCmdId = source.readParcelable(OptionsCmdId.class.getClassLoader(), com.android.ims.internal.uce.options.OptionsCmdId.class); + mStatus = source.readParcelable(StatusCode.class.getClassLoader(), com.android.ims.internal.uce.common.StatusCode.class); + mCapInfo = source.readParcelable(CapInfo.class.getClassLoader(), com.android.ims.internal.uce.common.CapInfo.class); } }
\ No newline at end of file diff --git a/core/java/com/android/ims/internal/uce/options/OptionsSipResponse.java b/core/java/com/android/ims/internal/uce/options/OptionsSipResponse.java index 32420816f5ab..160f9ebaebc8 100644 --- a/core/java/com/android/ims/internal/uce/options/OptionsSipResponse.java +++ b/core/java/com/android/ims/internal/uce/options/OptionsSipResponse.java @@ -180,7 +180,7 @@ public class OptionsSipResponse implements Parcelable { mRequestId = source.readInt(); mSipResponseCode = source.readInt(); mReasonPhrase = source.readString(); - mCmdId = source.readParcelable(OptionsCmdId.class.getClassLoader()); + mCmdId = source.readParcelable(OptionsCmdId.class.getClassLoader(), com.android.ims.internal.uce.options.OptionsCmdId.class); mRetryAfter = source.readInt(); mReasonHeader = source.readString(); } diff --git a/core/java/com/android/ims/internal/uce/presence/PresCapInfo.java b/core/java/com/android/ims/internal/uce/presence/PresCapInfo.java index ec8b6bfa4ef3..f0ee5f3bb77d 100644 --- a/core/java/com/android/ims/internal/uce/presence/PresCapInfo.java +++ b/core/java/com/android/ims/internal/uce/presence/PresCapInfo.java @@ -105,6 +105,6 @@ public class PresCapInfo implements Parcelable { /** @hide */ public void readFromParcel(Parcel source) { mContactUri = source.readString(); - mCapInfo = source.readParcelable(CapInfo.class.getClassLoader()); + mCapInfo = source.readParcelable(CapInfo.class.getClassLoader(), com.android.ims.internal.uce.common.CapInfo.class); } } diff --git a/core/java/com/android/ims/internal/uce/presence/PresCmdStatus.java b/core/java/com/android/ims/internal/uce/presence/PresCmdStatus.java index 7e22106f3be3..8fbb000c20f5 100644 --- a/core/java/com/android/ims/internal/uce/presence/PresCmdStatus.java +++ b/core/java/com/android/ims/internal/uce/presence/PresCmdStatus.java @@ -146,8 +146,8 @@ public class PresCmdStatus implements Parcelable{ public void readFromParcel(Parcel source) { mUserData = source.readInt(); mRequestId = source.readInt(); - mCmdId = source.readParcelable(PresCmdId.class.getClassLoader()); - mStatus = source.readParcelable(StatusCode.class.getClassLoader()); + mCmdId = source.readParcelable(PresCmdId.class.getClassLoader(), com.android.ims.internal.uce.presence.PresCmdId.class); + mStatus = source.readParcelable(StatusCode.class.getClassLoader(), com.android.ims.internal.uce.common.StatusCode.class); } }
\ No newline at end of file diff --git a/core/java/com/android/ims/internal/uce/presence/PresResInfo.java b/core/java/com/android/ims/internal/uce/presence/PresResInfo.java index 2f797b41b14f..954c2b61c286 100644 --- a/core/java/com/android/ims/internal/uce/presence/PresResInfo.java +++ b/core/java/com/android/ims/internal/uce/presence/PresResInfo.java @@ -122,6 +122,6 @@ public class PresResInfo implements Parcelable { public void readFromParcel(Parcel source) { mResUri = source.readString(); mDisplayName = source.readString(); - mInstanceInfo = source.readParcelable(PresResInstanceInfo.class.getClassLoader()); + mInstanceInfo = source.readParcelable(PresResInstanceInfo.class.getClassLoader(), com.android.ims.internal.uce.presence.PresResInstanceInfo.class); } }
\ No newline at end of file diff --git a/core/java/com/android/ims/internal/uce/presence/PresRlmiInfo.java b/core/java/com/android/ims/internal/uce/presence/PresRlmiInfo.java index e33aa1303886..63247dbd8172 100644 --- a/core/java/com/android/ims/internal/uce/presence/PresRlmiInfo.java +++ b/core/java/com/android/ims/internal/uce/presence/PresRlmiInfo.java @@ -236,7 +236,7 @@ public class PresRlmiInfo implements Parcelable { mListName = source.readString(); mRequestId = source.readInt(); mPresSubscriptionState = source.readParcelable( - PresSubscriptionState.class.getClassLoader()); + PresSubscriptionState.class.getClassLoader(), com.android.ims.internal.uce.presence.PresSubscriptionState.class); mSubscriptionExpireTime = source.readInt(); mSubscriptionTerminatedReason = source.readString(); } diff --git a/core/java/com/android/ims/internal/uce/presence/PresSipResponse.java b/core/java/com/android/ims/internal/uce/presence/PresSipResponse.java index 5e394efed294..8097a3797556 100644 --- a/core/java/com/android/ims/internal/uce/presence/PresSipResponse.java +++ b/core/java/com/android/ims/internal/uce/presence/PresSipResponse.java @@ -185,7 +185,7 @@ public class PresSipResponse implements Parcelable { mRequestId = source.readInt(); mSipResponseCode = source.readInt(); mReasonPhrase = source.readString(); - mCmdId = source.readParcelable(PresCmdId.class.getClassLoader()); + mCmdId = source.readParcelable(PresCmdId.class.getClassLoader(), com.android.ims.internal.uce.presence.PresCmdId.class); mRetryAfter = source.readInt(); mReasonHeader = source.readString(); } diff --git a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java index 14fd4c20e648..40ca9fb22d09 100644 --- a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java +++ b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java @@ -90,6 +90,14 @@ public class AccessibilityShortcutController { public static final ComponentName ACCESSIBILITY_BUTTON_COMPONENT_NAME = new ComponentName("com.android.server.accessibility", "AccessibilityButton"); + public static final ComponentName COLOR_INVERSION_TILE_COMPONENT_NAME = + new ComponentName("com.android.server.accessibility", "ColorInversionTile"); + public static final ComponentName DALTONIZER_TILE_COMPONENT_NAME = + new ComponentName("com.android.server.accessibility", "ColorCorrectionTile"); + public static final ComponentName ONE_HANDED_TILE_COMPONENT_NAME = + new ComponentName("com.android.server.accessibility", "OneHandedModeTile"); + public static final ComponentName REDUCE_BRIGHT_COLORS_TILE_SERVICE_COMPONENT_NAME = + new ComponentName("com.android.server.accessibility", "ReduceBrightColorsTile"); private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder() .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) diff --git a/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java index b723db287823..4ad232ad25eb 100644 --- a/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java +++ b/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java @@ -467,8 +467,21 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter { } protected void showEmptyState(ResolverListAdapter activeListAdapter, + @DrawableRes int iconRes, String title, String subtitle) { + showEmptyState(activeListAdapter, iconRes, title, subtitle, /* buttonOnClick */ null); + } + + protected void showEmptyState(ResolverListAdapter activeListAdapter, @DrawableRes int iconRes, @StringRes int titleRes, @StringRes int subtitleRes, View.OnClickListener buttonOnClick) { + String title = titleRes == 0 ? null : mContext.getString(titleRes); + String subtitle = subtitleRes == 0 ? null : mContext.getString(subtitleRes); + showEmptyState(activeListAdapter, iconRes, title, subtitle, buttonOnClick); + } + + protected void showEmptyState(ResolverListAdapter activeListAdapter, + @DrawableRes int iconRes, String title, String subtitle, + View.OnClickListener buttonOnClick) { ProfileDescriptor descriptor = getItem( userHandleToPageIndex(activeListAdapter.getUserHandle())); descriptor.rootView.findViewById(R.id.resolver_list).setVisibility(View.GONE); @@ -479,15 +492,15 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter { View container = emptyStateView.findViewById(R.id.resolver_empty_state_container); setupContainerPadding(container); - TextView title = emptyStateView.findViewById(R.id.resolver_empty_state_title); - title.setText(titleRes); + TextView titleView = emptyStateView.findViewById(R.id.resolver_empty_state_title); + titleView.setText(title); - TextView subtitle = emptyStateView.findViewById(R.id.resolver_empty_state_subtitle); - if (subtitleRes != 0) { - subtitle.setVisibility(View.VISIBLE); - subtitle.setText(subtitleRes); + TextView subtitleView = emptyStateView.findViewById(R.id.resolver_empty_state_subtitle); + if (subtitle != null) { + subtitleView.setVisibility(View.VISIBLE); + subtitleView.setText(subtitle); } else { - subtitle.setVisibility(View.GONE); + subtitleView.setVisibility(View.GONE); } Button button = emptyStateView.findViewById(R.id.resolver_empty_state_button); diff --git a/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java index 3b6a877907f2..393bff483a20 100644 --- a/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java +++ b/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java @@ -16,7 +16,17 @@ package com.android.internal.app; +import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_ACCESS_PERSONAL; +import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_ACCESS_WORK; +import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_SHARE_WITH_PERSONAL; +import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_SHARE_WITH_WORK; +import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CROSS_PROFILE_BLOCKED_TITLE; +import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_NO_PERSONAL_APPS; +import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_NO_WORK_APPS; +import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_WORK_PAUSED_TITLE; + import android.annotation.Nullable; +import android.app.admin.DevicePolicyManager; import android.content.Context; import android.os.UserHandle; import android.view.LayoutInflater; @@ -184,8 +194,8 @@ public class ChooserMultiProfilePagerAdapter extends AbstractMultiProfilePagerAd View.OnClickListener listener) { showEmptyState(activeListAdapter, R.drawable.ic_work_apps_off, - R.string.resolver_turn_on_work_apps, - /* subtitleRes */ 0, + getWorkAppPausedTitle(), + /* subtitle = */ null, listener); } @@ -194,13 +204,13 @@ public class ChooserMultiProfilePagerAdapter extends AbstractMultiProfilePagerAd if (mIsSendAction) { showEmptyState(activeListAdapter, R.drawable.ic_sharing_disabled, - R.string.resolver_cross_profile_blocked, - R.string.resolver_cant_share_with_work_apps_explanation); + getCrossProfileBlockedTitle(), + getCantShareWithWorkMessage()); } else { showEmptyState(activeListAdapter, R.drawable.ic_sharing_disabled, - R.string.resolver_cross_profile_blocked, - R.string.resolver_cant_access_work_apps_explanation); + getCrossProfileBlockedTitle(), + getCantAccessWorkMessage()); } } @@ -209,13 +219,13 @@ public class ChooserMultiProfilePagerAdapter extends AbstractMultiProfilePagerAd if (mIsSendAction) { showEmptyState(activeListAdapter, R.drawable.ic_sharing_disabled, - R.string.resolver_cross_profile_blocked, - R.string.resolver_cant_share_with_personal_apps_explanation); + getCrossProfileBlockedTitle(), + getCantShareWithPersonalMessage()); } else { showEmptyState(activeListAdapter, R.drawable.ic_sharing_disabled, - R.string.resolver_cross_profile_blocked, - R.string.resolver_cant_access_personal_apps_explanation); + getCrossProfileBlockedTitle(), + getCantAccessPersonalMessage()); } } @@ -223,8 +233,8 @@ public class ChooserMultiProfilePagerAdapter extends AbstractMultiProfilePagerAd protected void showNoPersonalAppsAvailableEmptyState(ResolverListAdapter listAdapter) { showEmptyState(listAdapter, R.drawable.ic_no_apps, - R.string.resolver_no_personal_apps_available, - /* subtitleRes */ 0); + getNoPersonalAppsAvailableMessage(), + /* subtitle= */ null); } @@ -232,10 +242,65 @@ public class ChooserMultiProfilePagerAdapter extends AbstractMultiProfilePagerAd protected void showNoWorkAppsAvailableEmptyState(ResolverListAdapter listAdapter) { showEmptyState(listAdapter, R.drawable.ic_no_apps, - R.string.resolver_no_work_apps_available, - /* subtitleRes */ 0); + getNoWorkAppsAvailableMessage(), + /* subtitle = */ null); + } + + private String getWorkAppPausedTitle() { + return getContext().getSystemService(DevicePolicyManager.class).getString( + RESOLVER_WORK_PAUSED_TITLE, + () -> getContext().getString(R.string.resolver_turn_on_work_apps)); + } + + private String getCrossProfileBlockedTitle() { + return getContext().getSystemService(DevicePolicyManager.class).getString( + RESOLVER_CROSS_PROFILE_BLOCKED_TITLE, + () -> getContext().getString(R.string.resolver_cross_profile_blocked)); + } + + private String getCantShareWithWorkMessage() { + return getContext().getSystemService(DevicePolicyManager.class).getString( + RESOLVER_CANT_SHARE_WITH_WORK, + () -> getContext().getString( + R.string.resolver_cant_share_with_work_apps_explanation)); + } + + private String getCantShareWithPersonalMessage() { + return getContext().getSystemService(DevicePolicyManager.class).getString( + RESOLVER_CANT_SHARE_WITH_PERSONAL, + () -> getContext().getString( + R.string.resolver_cant_share_with_personal_apps_explanation)); } + private String getCantAccessWorkMessage() { + return getContext().getSystemService(DevicePolicyManager.class).getString( + RESOLVER_CANT_ACCESS_WORK, + () -> getContext().getString( + R.string.resolver_cant_access_work_apps_explanation)); + } + + private String getCantAccessPersonalMessage() { + return getContext().getSystemService(DevicePolicyManager.class).getString( + RESOLVER_CANT_ACCESS_PERSONAL, + () -> getContext().getString( + R.string.resolver_cant_access_personal_apps_explanation)); + } + + private String getNoWorkAppsAvailableMessage() { + return getContext().getSystemService(DevicePolicyManager.class).getString( + RESOLVER_NO_WORK_APPS, + () -> getContext().getString( + R.string.resolver_no_work_apps_available)); + } + + private String getNoPersonalAppsAvailableMessage() { + return getContext().getSystemService(DevicePolicyManager.class).getString( + RESOLVER_NO_PERSONAL_APPS, + () -> getContext().getString( + R.string.resolver_no_personal_apps_available)); + } + + void setEmptyStateBottomOffset(int bottomOffset) { mBottomOffset = bottomOffset; } diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl index 587876df0df6..9648008274ef 100644 --- a/core/java/com/android/internal/app/IBatteryStats.aidl +++ b/core/java/com/android/internal/app/IBatteryStats.aidl @@ -21,6 +21,7 @@ import com.android.internal.os.BatteryStatsImpl; import android.bluetooth.BluetoothActivityEnergyInfo; import android.os.BatteryUsageStats; import android.os.BatteryUsageStatsQuery; +import android.os.BluetoothBatteryStats; import android.os.ParcelFileDescriptor; import android.os.WakeLockStats; import android.os.WorkSource; @@ -162,6 +163,10 @@ interface IBatteryStats { @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BATTERY_STATS)") WakeLockStats getWakeLockStats(); + /** {@hide} */ + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BATTERY_STATS)") + BluetoothBatteryStats getBluetoothBatteryStats(); + HealthStatsParceler takeUidSnapshot(int uid); HealthStatsParceler[] takeUidSnapshots(in int[] uid); diff --git a/core/java/com/android/internal/app/IntentForwarderActivity.java b/core/java/com/android/internal/app/IntentForwarderActivity.java index 0f37dc5949f9..25b8dba2870b 100644 --- a/core/java/com/android/internal/app/IntentForwarderActivity.java +++ b/core/java/com/android/internal/app/IntentForwarderActivity.java @@ -16,13 +16,14 @@ package com.android.internal.app; +import static android.app.admin.DevicePolicyResources.Strings.Core.FORWARD_INTENT_TO_PERSONAL; +import static android.app.admin.DevicePolicyResources.Strings.Core.FORWARD_INTENT_TO_WORK; import static android.content.pm.PackageManager.MATCH_DEFAULT_ONLY; import static com.android.internal.app.ResolverActivity.EXTRA_CALLING_USER; import static com.android.internal.app.ResolverActivity.EXTRA_SELECTED_PROFILE; import android.annotation.Nullable; -import android.annotation.StringRes; import android.app.Activity; import android.app.ActivityThread; import android.app.AppGlobals; @@ -101,16 +102,16 @@ public class IntentForwarderActivity extends Activity { Intent intentReceived = getIntent(); String className = intentReceived.getComponent().getClassName(); final int targetUserId; - final int userMessageId; + final String userMessage; if (className.equals(FORWARD_INTENT_TO_PARENT)) { - userMessageId = com.android.internal.R.string.forward_intent_to_owner; + userMessage = getForwardToPersonalMessage(); targetUserId = getProfileParent(); getMetricsLogger().write( new LogMaker(MetricsEvent.ACTION_SWITCH_SHARE_PROFILE) .setSubtype(MetricsEvent.PARENT_PROFILE)); } else if (className.equals(FORWARD_INTENT_TO_MANAGED_PROFILE)) { - userMessageId = com.android.internal.R.string.forward_intent_to_work; + userMessage = getForwardToWorkMessage(); targetUserId = getManagedProfile(); getMetricsLogger().write( @@ -118,7 +119,7 @@ public class IntentForwarderActivity extends Activity { .setSubtype(MetricsEvent.MANAGED_PROFILE)); } else { Slog.wtf(TAG, IntentForwarderActivity.class.getName() + " cannot be called directly"); - userMessageId = -1; + userMessage = null; targetUserId = UserHandle.USER_NULL; } if (targetUserId == UserHandle.USER_NULL) { @@ -156,11 +157,23 @@ public class IntentForwarderActivity extends Activity { return targetResolveInfo; }, mExecutorService) .thenAcceptAsync(result -> { - maybeShowDisclosure(intentReceived, result, userMessageId); + maybeShowDisclosure(intentReceived, result, userMessage); finish(); }, getApplicationContext().getMainExecutor()); } + private String getForwardToPersonalMessage() { + return getSystemService(DevicePolicyManager.class).getString( + FORWARD_INTENT_TO_PERSONAL, + () -> getString(com.android.internal.R.string.forward_intent_to_owner)); + } + + private String getForwardToWorkMessage() { + return getSystemService(DevicePolicyManager.class).getString( + FORWARD_INTENT_TO_WORK, + () -> getString(com.android.internal.R.string.forward_intent_to_work)); + } + private boolean isIntentForwarderResolveInfo(ResolveInfo resolveInfo) { if (resolveInfo == null) { return false; @@ -183,9 +196,9 @@ public class IntentForwarderActivity extends Activity { } private void maybeShowDisclosure( - Intent intentReceived, ResolveInfo resolveInfo, int messageId) { - if (shouldShowDisclosure(resolveInfo, intentReceived)) { - mInjector.showToast(messageId, Toast.LENGTH_LONG); + Intent intentReceived, ResolveInfo resolveInfo, @Nullable String message) { + if (shouldShowDisclosure(resolveInfo, intentReceived) && message != null) { + mInjector.showToast(message, Toast.LENGTH_LONG); } } @@ -405,8 +418,8 @@ public class IntentForwarderActivity extends Activity { } @Override - public void showToast(int messageId, int duration) { - Toast.makeText(IntentForwarderActivity.this, getString(messageId), duration).show(); + public void showToast(String message, int duration) { + Toast.makeText(IntentForwarderActivity.this, message, duration).show(); } } @@ -419,6 +432,6 @@ public class IntentForwarderActivity extends Activity { CompletableFuture<ResolveInfo> resolveActivityAsUser(Intent intent, int flags, int userId); - void showToast(@StringRes int messageId, int duration); + void showToast(String message, int duration); } } diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java index f9a8c7b58897..347153c7e53e 100644 --- a/core/java/com/android/internal/app/ResolverActivity.java +++ b/core/java/com/android/internal/app/ResolverActivity.java @@ -17,6 +17,13 @@ package com.android.internal.app; import static android.Manifest.permission.INTERACT_ACROSS_PROFILES; +import static android.app.admin.DevicePolicyResources.Strings.Core.FORWARD_INTENT_TO_PERSONAL; +import static android.app.admin.DevicePolicyResources.Strings.Core.FORWARD_INTENT_TO_WORK; +import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_PERSONAL_TAB; +import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_PERSONAL_TAB_ACCESSIBILITY; +import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_WORK_PROFILE_NOT_SUPPORTED; +import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_WORK_TAB; +import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_WORK_TAB_ACCESSIBILITY; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.content.PermissionChecker.PID_UNKNOWN; import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS; @@ -32,6 +39,7 @@ import android.app.VoiceInteractor.PickOptionRequest; import android.app.VoiceInteractor.PickOptionRequest.Option; import android.app.VoiceInteractor.Prompt; import android.app.admin.DevicePolicyEventLogger; +import android.app.admin.DevicePolicyManager; import android.compat.annotation.UnsupportedAppUsage; import android.content.BroadcastReceiver; import android.content.ComponentName; @@ -123,7 +131,7 @@ public class ResolverActivity extends Activity implements protected View mProfileView; private int mLastSelected = AbsListView.INVALID_POSITION; private boolean mResolvingHome = false; - private int mProfileSwitchMessageId = -1; + private String mProfileSwitchMessage; private int mLayoutId; @VisibleForTesting protected final ArrayList<Intent> mIntents = new ArrayList<>(); @@ -441,7 +449,7 @@ public class ResolverActivity extends Activity implements // Determine whether we should show that intent is forwarded // from managed profile to owner or other way around. - setProfileSwitchMessageId(intent.getContentUserHint()); + setProfileSwitchMessage(intent.getContentUserHint()); mLaunchedFromUid = getLaunchedFromUid(); if (mLaunchedFromUid < 0 || UserHandle.isIsolated(mLaunchedFromUid)) { @@ -674,7 +682,7 @@ public class ResolverActivity extends Activity implements } // Do not show the profile switch message anymore. - mProfileSwitchMessageId = -1; + mProfileSwitchMessage = null; onTargetSelected(dri, false); if (!mAwaitingDelegateResponse) { @@ -828,7 +836,7 @@ public class ResolverActivity extends Activity implements } } - private void setProfileSwitchMessageId(int contentUserHint) { + private void setProfileSwitchMessage(int contentUserHint) { if (contentUserHint != UserHandle.USER_CURRENT && contentUserHint != UserHandle.myUserId()) { UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE); @@ -837,13 +845,25 @@ public class ResolverActivity extends Activity implements : false; boolean targetIsManaged = userManager.isManagedProfile(); if (originIsManaged && !targetIsManaged) { - mProfileSwitchMessageId = com.android.internal.R.string.forward_intent_to_owner; + mProfileSwitchMessage = getForwardToPersonalMsg(); } else if (!originIsManaged && targetIsManaged) { - mProfileSwitchMessageId = com.android.internal.R.string.forward_intent_to_work; + mProfileSwitchMessage = getForwardToWorkMsg(); } } } + private String getForwardToPersonalMsg() { + return getSystemService(DevicePolicyManager.class).getString( + FORWARD_INTENT_TO_PERSONAL, + () -> getString(com.android.internal.R.string.forward_intent_to_owner)); + } + + private String getForwardToWorkMsg() { + return getSystemService(DevicePolicyManager.class).getString( + FORWARD_INTENT_TO_WORK, + () -> getString(com.android.internal.R.string.forward_intent_to_work)); + } + /** * Turn on launch mode that is safe to use when forwarding intents received from * applications and running in system processes. This mode uses Activity.startActivityAsCaller @@ -1095,9 +1115,9 @@ public class ResolverActivity extends Activity implements ResolveInfo ri = mMultiProfilePagerAdapter.getActiveListAdapter() .resolveInfoForPosition(which, hasIndexBeenFiltered); if (mResolvingHome && hasManagedProfile() && !supportsManagedProfiles(ri)) { - Toast.makeText(this, String.format(getResources().getString( - com.android.internal.R.string.activity_resolver_work_profiles_support), - ri.activityInfo.loadLabel(getPackageManager()).toString()), + Toast.makeText(this, + getWorkProfileNotSupportedMsg( + ri.activityInfo.loadLabel(getPackageManager()).toString()), Toast.LENGTH_LONG).show(); return; } @@ -1128,6 +1148,15 @@ public class ResolverActivity extends Activity implements } } + private String getWorkProfileNotSupportedMsg(String launcherName) { + return getSystemService(DevicePolicyManager.class).getString( + RESOLVER_WORK_PROFILE_NOT_SUPPORTED, + () -> getString( + com.android.internal.R.string.activity_resolver_work_profiles_support, + launcherName), + launcherName); + } + /** * Replace me in subclasses! */ @@ -1394,8 +1423,8 @@ public class ResolverActivity extends Activity implements } // If needed, show that intent is forwarded // from managed profile to owner or other way around. - if (mProfileSwitchMessageId != -1) { - Toast.makeText(this, getString(mProfileSwitchMessageId), Toast.LENGTH_LONG).show(); + if (mProfileSwitchMessage != null) { + Toast.makeText(this, mProfileSwitchMessage, Toast.LENGTH_LONG).show(); } if (!mSafeForwardingMode) { if (cti.startAsUser(this, null, user)) { @@ -1742,12 +1771,12 @@ public class ResolverActivity extends Activity implements viewPager.setSaveEnabled(false); TabHost.TabSpec tabSpec = tabHost.newTabSpec(TAB_TAG_PERSONAL) .setContent(R.id.profile_pager) - .setIndicator(getString(R.string.resolver_personal_tab)); + .setIndicator(getPersonalTabLabel()); tabHost.addTab(tabSpec); tabSpec = tabHost.newTabSpec(TAB_TAG_WORK) .setContent(R.id.profile_pager) - .setIndicator(getString(R.string.resolver_work_tab)); + .setIndicator(getWorkTabLabel()); tabHost.addTab(tabSpec); TabWidget tabWidget = tabHost.getTabWidget(); @@ -1799,6 +1828,16 @@ public class ResolverActivity extends Activity implements findViewById(R.id.resolver_tab_divider).setVisibility(View.VISIBLE); } + private String getPersonalTabLabel() { + return getSystemService(DevicePolicyManager.class).getString( + RESOLVER_PERSONAL_TAB, () -> getString(R.string.resolver_personal_tab)); + } + + private String getWorkTabLabel() { + return getSystemService(DevicePolicyManager.class).getString( + RESOLVER_WORK_TAB, () -> getString(R.string.resolver_work_tab)); + } + void onHorizontalSwipeStateChanged(int state) {} private void maybeHideDivider() { @@ -1830,8 +1869,6 @@ public class ResolverActivity extends Activity implements } private void resetTabsHeaderStyle(TabWidget tabWidget) { - String workContentDescription = getString(R.string.resolver_work_tab_accessibility); - String personalContentDescription = getString(R.string.resolver_personal_tab_accessibility); for (int i = 0; i < tabWidget.getChildCount(); i++) { View tabView = tabWidget.getChildAt(i); TextView title = tabView.findViewById(android.R.id.title); @@ -1839,14 +1876,26 @@ public class ResolverActivity extends Activity implements title.setTextColor(getAttrColor(this, android.R.attr.textColorTertiary)); title.setTextSize(TypedValue.COMPLEX_UNIT_PX, getResources().getDimension(R.dimen.resolver_tab_text_size)); - if (title.getText().equals(getString(R.string.resolver_personal_tab))) { - tabView.setContentDescription(personalContentDescription); - } else if (title.getText().equals(getString(R.string.resolver_work_tab))) { - tabView.setContentDescription(workContentDescription); + if (title.getText().equals(getPersonalTabLabel())) { + tabView.setContentDescription(getPersonalTabAccessibilityLabel()); + } else if (title.getText().equals(getWorkTabLabel())) { + tabView.setContentDescription(getWorkTabAccessibilityLabel()); } } } + private String getPersonalTabAccessibilityLabel() { + return getSystemService(DevicePolicyManager.class).getString( + RESOLVER_PERSONAL_TAB_ACCESSIBILITY, + () -> getString(R.string.resolver_personal_tab_accessibility)); + } + + private String getWorkTabAccessibilityLabel() { + return getSystemService(DevicePolicyManager.class).getString( + RESOLVER_WORK_TAB_ACCESSIBILITY, + () -> getString(R.string.resolver_work_tab_accessibility)); + } + private static int getAttrColor(Context context, int attr) { TypedArray ta = context.obtainStyledAttributes(new int[]{attr}); int colorAccent = ta.getColor(0, 0); diff --git a/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java index 622f1668d052..4da59a3e77d9 100644 --- a/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java +++ b/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java @@ -16,7 +16,15 @@ package com.android.internal.app; +import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_ACCESS_PERSONAL; +import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_ACCESS_WORK; +import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CROSS_PROFILE_BLOCKED_TITLE; +import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_NO_PERSONAL_APPS; +import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_NO_WORK_APPS; +import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_WORK_PAUSED_TITLE; + import android.annotation.Nullable; +import android.app.admin.DevicePolicyManager; import android.content.Context; import android.content.res.Resources; import android.os.UserHandle; @@ -196,8 +204,8 @@ public class ResolverMultiProfilePagerAdapter extends AbstractMultiProfilePagerA View.OnClickListener listener) { showEmptyState(activeListAdapter, R.drawable.ic_work_apps_off, - R.string.resolver_turn_on_work_apps, - /* subtitleRes */ 0, + getWorkAppPausedTitle(), + /* subtitle = */ null, listener); } @@ -205,32 +213,72 @@ public class ResolverMultiProfilePagerAdapter extends AbstractMultiProfilePagerA protected void showNoPersonalToWorkIntentsEmptyState(ResolverListAdapter activeListAdapter) { showEmptyState(activeListAdapter, R.drawable.ic_sharing_disabled, - R.string.resolver_cross_profile_blocked, - R.string.resolver_cant_access_work_apps_explanation); + getCrossProfileBlockedTitle(), + getCantAccessWorkMessage()); } @Override protected void showNoWorkToPersonalIntentsEmptyState(ResolverListAdapter activeListAdapter) { showEmptyState(activeListAdapter, R.drawable.ic_sharing_disabled, - R.string.resolver_cross_profile_blocked, - R.string.resolver_cant_access_personal_apps_explanation); + getCrossProfileBlockedTitle(), + getCantAccessPersonalMessage()); } @Override protected void showNoPersonalAppsAvailableEmptyState(ResolverListAdapter listAdapter) { showEmptyState(listAdapter, R.drawable.ic_no_apps, - R.string.resolver_no_personal_apps_available, - /* subtitleRes */ 0); + getNoPersonalAppsAvailableMessage(), + /* subtitle = */ null); } @Override protected void showNoWorkAppsAvailableEmptyState(ResolverListAdapter listAdapter) { showEmptyState(listAdapter, R.drawable.ic_no_apps, - R.string.resolver_no_work_apps_available, - /* subtitleRes */ 0); + getNoWorkAppsAvailableMessage(), + /* subtitle= */ null); + } + + private String getWorkAppPausedTitle() { + return getContext().getSystemService(DevicePolicyManager.class).getString( + RESOLVER_WORK_PAUSED_TITLE, + () -> getContext().getString(R.string.resolver_turn_on_work_apps)); + } + + private String getCrossProfileBlockedTitle() { + return getContext().getSystemService(DevicePolicyManager.class).getString( + RESOLVER_CROSS_PROFILE_BLOCKED_TITLE, + () -> getContext().getString(R.string.resolver_cross_profile_blocked)); + } + + private String getCantAccessWorkMessage() { + return getContext().getSystemService(DevicePolicyManager.class).getString( + RESOLVER_CANT_ACCESS_WORK, + () -> getContext().getString( + R.string.resolver_cant_access_work_apps_explanation)); + } + + private String getCantAccessPersonalMessage() { + return getContext().getSystemService(DevicePolicyManager.class).getString( + RESOLVER_CANT_ACCESS_PERSONAL, + () -> getContext().getString( + R.string.resolver_cant_access_personal_apps_explanation)); + } + + private String getNoWorkAppsAvailableMessage() { + return getContext().getSystemService(DevicePolicyManager.class).getString( + RESOLVER_NO_WORK_APPS, + () -> getContext().getString( + R.string.resolver_no_work_apps_available)); + } + + private String getNoPersonalAppsAvailableMessage() { + return getContext().getSystemService(DevicePolicyManager.class).getString( + RESOLVER_NO_PERSONAL_APPS, + () -> getContext().getString( + R.string.resolver_no_personal_apps_available)); } void setUseLayoutWithDefault(boolean useLayoutWithDefault) { diff --git a/core/java/com/android/internal/app/UnlaunchableAppActivity.java b/core/java/com/android/internal/app/UnlaunchableAppActivity.java index ca0856238b90..3531fad353db 100644 --- a/core/java/com/android/internal/app/UnlaunchableAppActivity.java +++ b/core/java/com/android/internal/app/UnlaunchableAppActivity.java @@ -16,11 +16,14 @@ package com.android.internal.app; +import static android.app.admin.DevicePolicyResources.Strings.Core.UNLAUNCHABLE_APP_WORK_PAUSED_MESSAGE; +import static android.app.admin.DevicePolicyResources.Strings.Core.UNLAUNCHABLE_APP_WORK_PAUSED_TITLE; import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import android.app.Activity; import android.app.AlertDialog; +import android.app.admin.DevicePolicyManager; import android.content.ComponentName; import android.content.DialogInterface; import android.content.Intent; @@ -70,8 +73,8 @@ public class UnlaunchableAppActivity extends Activity String dialogTitle; String dialogMessage = null; if (mReason == UNLAUNCHABLE_REASON_QUIET_MODE) { - dialogTitle = getResources().getString(R.string.work_mode_off_title); - dialogMessage = getResources().getString(R.string.work_mode_off_message); + dialogTitle = getDialogTitle(); + dialogMessage = getDialogMessage(); } else { Log.wtf(TAG, "Invalid unlaunchable type: " + mReason); finish(); @@ -91,6 +94,17 @@ public class UnlaunchableAppActivity extends Activity builder.show(); } + private String getDialogTitle() { + return getSystemService(DevicePolicyManager.class).getString( + UNLAUNCHABLE_APP_WORK_PAUSED_TITLE, () -> getString(R.string.work_mode_off_title)); + } + + private String getDialogMessage() { + return getSystemService(DevicePolicyManager.class).getString( + UNLAUNCHABLE_APP_WORK_PAUSED_MESSAGE, + () -> getString(R.string.work_mode_off_message)); + } + @Override public void onDismiss(DialogInterface dialog) { finish(); diff --git a/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java b/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java index 9c3c22451c5a..301de2d3529e 100644 --- a/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java +++ b/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java @@ -237,12 +237,12 @@ public class DisplayResolveInfo implements TargetInfo, Parcelable { private DisplayResolveInfo(Parcel in) { mDisplayLabel = in.readCharSequence(); mExtendedInfo = in.readCharSequence(); - mResolvedIntent = in.readParcelable(null /* ClassLoader */); + mResolvedIntent = in.readParcelable(null /* ClassLoader */, android.content.Intent.class); mSourceIntents.addAll( Arrays.asList((Intent[]) in.readParcelableArray(null /* ClassLoader */, Intent.class))); mIsSuspended = in.readBoolean(); mPinned = in.readBoolean(); - mResolveInfo = in.readParcelable(null /* ClassLoader */); + mResolveInfo = in.readParcelable(null /* ClassLoader */, android.content.pm.ResolveInfo.class); } } diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java index 13a39de3e365..0ada13a73ad2 100644 --- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java +++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java @@ -163,6 +163,12 @@ public final class SystemUiDeviceConfigFlags { public static final String PROPERTY_LOCATION_INDICATORS_SMALL_ENABLED = "location_indicators_small_enabled"; + /** + * Whether to show the location indicator for system apps. + */ + public static final String PROPERTY_LOCATION_INDICATORS_SHOW_SYSTEM = + "location_indicators_show_system"; + // Flags related to Assistant /** diff --git a/core/java/com/android/internal/graphics/ColorUtils.java b/core/java/com/android/internal/graphics/ColorUtils.java index 537e797c9bac..dff9551c0c07 100644 --- a/core/java/com/android/internal/graphics/ColorUtils.java +++ b/core/java/com/android/internal/graphics/ColorUtils.java @@ -62,7 +62,10 @@ public final class ColorUtils { return Color.argb(a, r, g, b); } - private static int compositeAlpha(int foregroundAlpha, int backgroundAlpha) { + /** + * Returns the composite alpha of the given foreground and background alpha. + */ + public static int compositeAlpha(int foregroundAlpha, int backgroundAlpha) { return 0xFF - (((0xFF - backgroundAlpha) * (0xFF - foregroundAlpha)) / 0xFF); } diff --git a/core/java/com/android/internal/logging/UiEventLogger.java b/core/java/com/android/internal/logging/UiEventLogger.java index 2f4a14fad2ee..5378b03fe1c5 100644 --- a/core/java/com/android/internal/logging/UiEventLogger.java +++ b/core/java/com/android/internal/logging/UiEventLogger.java @@ -58,6 +58,14 @@ public interface UiEventLogger { void log(@NonNull UiEventEnum event); /** + * Log a simple event with an instance id, without package information. + * Does nothing if event.getId() <= 0. + * @param event an enum implementing UiEventEnum interface. + * @param instance An identifier obtained from an InstanceIdSequence. If null, reduces to log(). + */ + void log(@NonNull UiEventEnum event, @Nullable InstanceId instance); + + /** * Log an event with package information. Does nothing if event.getId() <= 0. * Give both uid and packageName if both are known, but one may be omitted if unknown. * @param event an enum implementing UiEventEnum interface. diff --git a/core/java/com/android/internal/logging/UiEventLoggerImpl.java b/core/java/com/android/internal/logging/UiEventLoggerImpl.java index c0f44a5eb39b..983e0fe6144e 100644 --- a/core/java/com/android/internal/logging/UiEventLoggerImpl.java +++ b/core/java/com/android/internal/logging/UiEventLoggerImpl.java @@ -42,16 +42,21 @@ public class UiEventLoggerImpl implements UiEventLogger { } @Override + public void log(UiEventEnum event, InstanceId instanceId) { + logWithInstanceId(event, 0, null, instanceId); + } + + @Override public void logWithInstanceId(UiEventEnum event, int uid, String packageName, InstanceId instance) { final int eventID = event.getId(); - if ((eventID > 0) && (instance != null)) { + if ((eventID > 0) && (instance != null)) { FrameworkStatsLog.write(FrameworkStatsLog.UI_EVENT_REPORTED, /* event_id = 1 */ eventID, /* uid = 2 */ uid, /* package_name = 3 */ packageName, /* instance_id = 4 */ instance.getId()); - } else { + } else if (eventID > 0) { log(event, uid, packageName); } } @@ -78,7 +83,7 @@ public class UiEventLoggerImpl implements UiEventLogger { /* package_name = 2 */ packageName, /* instance_id = 3 */ instance.getId(), /* position_picked = 4 */ position); - } else { + } else if ((eventID > 0)) { logWithPosition(event, uid, packageName, position); } } diff --git a/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java b/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java index 2d09434807a6..e303890c245a 100644 --- a/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java +++ b/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java @@ -88,6 +88,11 @@ public class UiEventLoggerFake implements UiEventLogger { } @Override + public void log(UiEventEnum event, InstanceId instance) { + logWithInstanceId(event, 0, null, instance); + } + + @Override public void log(UiEventEnum event, int uid, String packageName) { final int eventId = event.getId(); if (eventId > 0) { diff --git a/core/java/com/android/internal/midi/MidiFramer.java b/core/java/com/android/internal/midi/MidiFramer.java index 62517fa82953..bf23ad190ef1 100644 --- a/core/java/com/android/internal/midi/MidiFramer.java +++ b/core/java/com/android/internal/midi/MidiFramer.java @@ -99,6 +99,12 @@ public class MidiFramer extends MidiReceiver { } } else { // data byte if (!mInSysEx) { + // Hack to avoid crashing if we start parsing in the middle + // of a data stream + if (mNeeded <= 0) { + break; + } + mBuffer[mCount++] = currentByte; if (--mNeeded == 0) { if (mRunningStatus != 0) { diff --git a/core/java/com/android/internal/midi/OWNERS b/core/java/com/android/internal/midi/OWNERS new file mode 100644 index 000000000000..af273a6f50e0 --- /dev/null +++ b/core/java/com/android/internal/midi/OWNERS @@ -0,0 +1 @@ +include /services/midi/OWNERS diff --git a/core/java/com/android/internal/net/LegacyVpnInfo.java b/core/java/com/android/internal/net/LegacyVpnInfo.java index 43984b59378c..b3bc93a058cf 100644 --- a/core/java/com/android/internal/net/LegacyVpnInfo.java +++ b/core/java/com/android/internal/net/LegacyVpnInfo.java @@ -69,7 +69,7 @@ public class LegacyVpnInfo implements Parcelable { LegacyVpnInfo info = new LegacyVpnInfo(); info.key = in.readString(); info.state = in.readInt(); - info.intent = in.readParcelable(null); + info.intent = in.readParcelable(null, android.app.PendingIntent.class); return info; } diff --git a/core/java/com/android/internal/net/VpnConfig.java b/core/java/com/android/internal/net/VpnConfig.java index 2ae56f808972..b579be03acbd 100644 --- a/core/java/com/android/internal/net/VpnConfig.java +++ b/core/java/com/android/internal/net/VpnConfig.java @@ -208,7 +208,7 @@ public class VpnConfig implements Parcelable { config.searchDomains = in.createStringArrayList(); config.allowedApplications = in.createStringArrayList(); config.disallowedApplications = in.createStringArrayList(); - config.configureIntent = in.readParcelable(null); + config.configureIntent = in.readParcelable(null, android.app.PendingIntent.class); config.startTime = in.readLong(); config.legacy = in.readInt() != 0; config.blocking = in.readInt() != 0; @@ -217,7 +217,7 @@ public class VpnConfig implements Parcelable { config.allowIPv6 = in.readInt() != 0; config.isMetered = in.readInt() != 0; config.underlyingNetworks = in.createTypedArray(Network.CREATOR); - config.proxyInfo = in.readParcelable(null); + config.proxyInfo = in.readParcelable(null, android.net.ProxyInfo.class); return config; } diff --git a/core/java/com/android/internal/net/VpnProfile.java b/core/java/com/android/internal/net/VpnProfile.java index d8dc1436128e..519faa8456cc 100644 --- a/core/java/com/android/internal/net/VpnProfile.java +++ b/core/java/com/android/internal/net/VpnProfile.java @@ -182,9 +182,9 @@ public final class VpnProfile implements Cloneable, Parcelable { ipsecCaCert = in.readString(); ipsecServerCert = in.readString(); saveLogin = in.readInt() != 0; - proxy = in.readParcelable(null); + proxy = in.readParcelable(null, android.net.ProxyInfo.class); mAllowedAlgorithms = new ArrayList<>(); - in.readList(mAllowedAlgorithms, null); + in.readList(mAllowedAlgorithms, null, java.lang.String.class); isBypassable = in.readBoolean(); isMetered = in.readBoolean(); maxMtu = in.readInt(); diff --git a/core/java/com/android/internal/notification/SystemNotificationChannels.java b/core/java/com/android/internal/notification/SystemNotificationChannels.java index 2f40d3b457c6..3b6f8f6187e1 100644 --- a/core/java/com/android/internal/notification/SystemNotificationChannels.java +++ b/core/java/com/android/internal/notification/SystemNotificationChannels.java @@ -14,10 +14,13 @@ package com.android.internal.notification; +import static android.app.admin.DevicePolicyResources.Strings.Core.NOTIFICATION_CHANNEL_DEVICE_ADMIN; + import android.app.INotificationManager; import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; +import android.app.admin.DevicePolicyManager; import android.content.Context; import android.content.pm.ParceledListSlice; import android.media.AudioAttributes; @@ -143,7 +146,7 @@ public class SystemNotificationChannels { final NotificationChannel deviceAdmin = new NotificationChannel( DEVICE_ADMIN, - context.getString(R.string.notification_channel_device_admin), + getDeviceAdminNotificationChannelName(context), NotificationManager.IMPORTANCE_HIGH); channelsList.add(deviceAdmin); @@ -209,6 +212,12 @@ public class SystemNotificationChannels { nm.createNotificationChannels(channelsList); } + private static String getDeviceAdminNotificationChannelName(Context context) { + DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class); + return dpm.getString(NOTIFICATION_CHANNEL_DEVICE_ADMIN, + () -> context.getString(R.string.notification_channel_device_admin)); + } + /** Remove notification channels which are no longer used */ public static void removeDeprecated(Context context) { final NotificationManager nm = context.getSystemService(NotificationManager.class); diff --git a/core/java/com/android/internal/os/AppFuseMount.java b/core/java/com/android/internal/os/AppFuseMount.java index 04d72117d28a..5404fea68672 100644 --- a/core/java/com/android/internal/os/AppFuseMount.java +++ b/core/java/com/android/internal/os/AppFuseMount.java @@ -57,7 +57,7 @@ public class AppFuseMount implements Parcelable { new Parcelable.Creator<AppFuseMount>() { @Override public AppFuseMount createFromParcel(Parcel in) { - return new AppFuseMount(in.readInt(), in.readParcelable(null)); + return new AppFuseMount(in.readInt(), in.readParcelable(null, android.os.ParcelFileDescriptor.class)); } @Override diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index a4183ca8f163..8213c863875c 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -45,6 +45,7 @@ import android.os.BatteryConsumer; import android.os.BatteryManager; import android.os.BatteryStats; import android.os.Binder; +import android.os.BluetoothBatteryStats; import android.os.Build; import android.os.Handler; import android.os.IBatteryPropertiesRegistrar; @@ -84,7 +85,6 @@ import android.util.Log; import android.util.LongSparseArray; import android.util.LongSparseLongArray; import android.util.MutableInt; -import android.util.Pools; import android.util.PrintWriterPrinter; import android.util.Printer; import android.util.Slog; @@ -137,9 +137,7 @@ import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Queue; -import java.util.Set; import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.locks.ReentrantLock; @@ -265,6 +263,7 @@ public class BatteryStatsImpl extends BatteryStats { MeasuredEnergyStats.POWER_BUCKET_CPU, MeasuredEnergyStats.POWER_BUCKET_MOBILE_RADIO, MeasuredEnergyStats.POWER_BUCKET_WIFI, + MeasuredEnergyStats.POWER_BUCKET_BLUETOOTH, }; // TimeInState counters need NUM_PROCESS_STATE states in order to accommodate @@ -1198,6 +1197,48 @@ public class BatteryStatsImpl extends BatteryStats { return new WakeLockStats(uidWakeLockStats); } + @Override + @GuardedBy("this") + public BluetoothBatteryStats getBluetoothBatteryStats() { + final long elapsedRealtimeUs = mClock.elapsedRealtime() * 1000; + ArrayList<BluetoothBatteryStats.UidStats> uidStats = new ArrayList<>(); + for (int i = mUidStats.size() - 1; i >= 0; i--) { + final Uid uid = mUidStats.valueAt(i); + final Timer scanTimer = uid.getBluetoothScanTimer(); + final long scanTimeMs = + scanTimer != null ? scanTimer.getTotalTimeLocked( + elapsedRealtimeUs, STATS_SINCE_CHARGED) / 1000 : 0; + + final Timer unoptimizedScanTimer = uid.getBluetoothUnoptimizedScanTimer(); + final long unoptimizedScanTimeMs = + unoptimizedScanTimer != null ? unoptimizedScanTimer.getTotalTimeLocked( + elapsedRealtimeUs, STATS_SINCE_CHARGED) / 1000 : 0; + + final Counter scanResultCounter = uid.getBluetoothScanResultCounter(); + final int scanResultCount = + scanResultCounter != null ? scanResultCounter.getCountLocked( + STATS_SINCE_CHARGED) : 0; + + final ControllerActivityCounter counter = uid.getBluetoothControllerActivity(); + final long rxTimeMs = counter != null ? counter.getRxTimeCounter().getCountLocked( + STATS_SINCE_CHARGED) : 0; + final long txTimeMs = counter != null ? counter.getTxTimeCounters()[0].getCountLocked( + STATS_SINCE_CHARGED) : 0; + + if (scanTimeMs != 0 || unoptimizedScanTimeMs != 0 || scanResultCount != 0 + || rxTimeMs != 0 || txTimeMs != 0) { + uidStats.add(new BluetoothBatteryStats.UidStats(uid.getUid(), + scanTimeMs, + unoptimizedScanTimeMs, + scanResultCount, + rxTimeMs, + txTimeMs)); + } + } + + return new BluetoothBatteryStats(uidStats); + } + String mLastWakeupReason = null; long mLastWakeupUptimeMs = 0; private final HashMap<String, SamplingTimer> mWakeupReasonStats = new HashMap<>(); @@ -8371,6 +8412,11 @@ public class BatteryStatsImpl extends BatteryStats { if (wifiControllerActivity != null) { wifiControllerActivity.setState(batteryConsumerProcessState, elapsedTimeMs); } + final ControllerActivityCounterImpl bluetoothControllerActivity = + getBluetoothControllerActivity(); + if (bluetoothControllerActivity != null) { + bluetoothControllerActivity.setState(batteryConsumerProcessState, elapsedTimeMs); + } final MeasuredEnergyStats energyStats = getOrCreateMeasuredEnergyStatsIfSupportedLocked(); if (energyStats != null) { @@ -8718,7 +8764,7 @@ public class BatteryStatsImpl extends BatteryStats { } @Override - public ControllerActivityCounter getBluetoothControllerActivity() { + public ControllerActivityCounterImpl getBluetoothControllerActivity() { return mBluetoothControllerActivity; } @@ -8839,6 +8885,14 @@ public class BatteryStatsImpl extends BatteryStats { @GuardedBy("mBsi") @Override + public long getBluetoothMeasuredBatteryConsumptionUC( + @BatteryConsumer.ProcessState int processState) { + return getMeasuredBatteryConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_BLUETOOTH, + processState); + } + + @GuardedBy("mBsi") + @Override public long getCpuMeasuredBatteryConsumptionUC() { return getMeasuredBatteryConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_CPU); } @@ -11424,6 +11478,13 @@ public class BatteryStatsImpl extends BatteryStats { wifiControllerActivity.setState(batteryConsumerProcessState, elapsedRealtimeMs); } + final ControllerActivityCounterImpl bluetoothControllerActivity = + getBluetoothControllerActivity(); + if (bluetoothControllerActivity != null) { + bluetoothControllerActivity.setState(batteryConsumerProcessState, + elapsedRealtimeMs); + } + final MeasuredEnergyStats energyStats = getOrCreateMeasuredEnergyStatsIfSupportedLocked(); if (energyStats != null) { @@ -12669,8 +12730,6 @@ public class BatteryStatsImpl extends BatteryStats { } } - private final Pools.Pool<NetworkStats> mNetworkStatsPool = new Pools.SynchronizedPool<>(6); - private final Object mWifiNetworkLock = new Object(); @GuardedBy("mWifiNetworkLock") @@ -12688,13 +12747,15 @@ public class BatteryStatsImpl extends BatteryStats { private NetworkStats mLastModemNetworkStats = new NetworkStats(0, -1); @VisibleForTesting - protected NetworkStats readNetworkStatsLocked(@NonNull NetworkStatsManager networkStatsManager, - String[] ifaces) { - Objects.requireNonNull(networkStatsManager); - if (!ArrayUtils.isEmpty(ifaces)) { - return networkStatsManager.getDetailedUidStats(Set.of(ifaces)); - } - return null; + protected NetworkStats readMobileNetworkStatsLocked( + @NonNull NetworkStatsManager networkStatsManager) { + return networkStatsManager.getMobileUidStats(); + } + + @VisibleForTesting + protected NetworkStats readWifiNetworkStatsLocked( + @NonNull NetworkStatsManager networkStatsManager) { + return networkStatsManager.getWifiUidStats(); } /** @@ -12714,21 +12775,15 @@ public class BatteryStatsImpl extends BatteryStats { // Grab a separate lock to acquire the network stats, which may do I/O. NetworkStats delta = null; synchronized (mWifiNetworkLock) { - final NetworkStats latestStats = readNetworkStatsLocked(networkStatsManager, - mWifiIfaces); + final NetworkStats latestStats = readWifiNetworkStatsLocked(networkStatsManager); if (latestStats != null) { - delta = NetworkStats.subtract(latestStats, mLastWifiNetworkStats, null, null, - mNetworkStatsPool.acquire()); - mNetworkStatsPool.release(mLastWifiNetworkStats); + delta = latestStats.subtract(mLastWifiNetworkStats); mLastWifiNetworkStats = latestStats; } } synchronized (this) { if (!mOnBatteryInternal || mIgnoreNextExternalStats) { - if (delta != null) { - mNetworkStatsPool.release(delta); - } if (mIgnoreNextExternalStats) { // TODO: Strictly speaking, we should re-mark all 5 timers for each uid (and the // global one) here like we do for display. But I'm not sure it's worth the @@ -12746,63 +12801,63 @@ public class BatteryStatsImpl extends BatteryStats { SparseLongArray rxPackets = new SparseLongArray(); SparseLongArray txPackets = new SparseLongArray(); + SparseLongArray rxTimesMs = new SparseLongArray(); + SparseLongArray txTimesMs = new SparseLongArray(); long totalTxPackets = 0; long totalRxPackets = 0; if (delta != null) { - NetworkStats.Entry entry = new NetworkStats.Entry(); - final int size = delta.size(); - for (int i = 0; i < size; i++) { - entry = delta.getValues(i, entry); - + for (NetworkStats.Entry entry : delta) { if (DEBUG_ENERGY) { - Slog.d(TAG, "Wifi uid " + entry.uid + ": delta rx=" + entry.rxBytes - + " tx=" + entry.txBytes + " rxPackets=" + entry.rxPackets - + " txPackets=" + entry.txPackets); + Slog.d(TAG, "Wifi uid " + entry.getUid() + + ": delta rx=" + entry.getRxBytes() + + " tx=" + entry.getTxBytes() + + " rxPackets=" + entry.getRxPackets() + + " txPackets=" + entry.getTxPackets()); } - if (entry.rxBytes == 0 && entry.txBytes == 0) { + if (entry.getRxBytes() == 0 && entry.getTxBytes() == 0) { // Skip the lookup below since there is no work to do. continue; } - final int uid = mapUid(entry.uid); + final int uid = mapUid(entry.getUid()); final Uid u = getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs); - if (entry.rxBytes != 0) { - u.noteNetworkActivityLocked(NETWORK_WIFI_RX_DATA, entry.rxBytes, - entry.rxPackets); - if (entry.set == NetworkStats.SET_DEFAULT) { // Background transfers - u.noteNetworkActivityLocked(NETWORK_WIFI_BG_RX_DATA, entry.rxBytes, - entry.rxPackets); + if (entry.getRxBytes() != 0) { + u.noteNetworkActivityLocked(NETWORK_WIFI_RX_DATA, entry.getRxBytes(), + entry.getRxPackets()); + if (entry.getSet() == NetworkStats.SET_DEFAULT) { // Background transfers + u.noteNetworkActivityLocked(NETWORK_WIFI_BG_RX_DATA, entry.getRxBytes(), + entry.getRxPackets()); } mNetworkByteActivityCounters[NETWORK_WIFI_RX_DATA].addCountLocked( - entry.rxBytes); + entry.getRxBytes()); mNetworkPacketActivityCounters[NETWORK_WIFI_RX_DATA].addCountLocked( - entry.rxPackets); + entry.getRxPackets()); - add(rxPackets, uid, entry.rxPackets); + rxPackets.incrementValue(uid, entry.getRxPackets()); // Sum the total number of packets so that the Rx Power can // be evenly distributed amongst the apps. - totalRxPackets += entry.rxPackets; + totalRxPackets += entry.getRxPackets(); } - if (entry.txBytes != 0) { - u.noteNetworkActivityLocked(NETWORK_WIFI_TX_DATA, entry.txBytes, - entry.txPackets); - if (entry.set == NetworkStats.SET_DEFAULT) { // Background transfers - u.noteNetworkActivityLocked(NETWORK_WIFI_BG_TX_DATA, entry.txBytes, - entry.txPackets); + if (entry.getTxBytes() != 0) { + u.noteNetworkActivityLocked(NETWORK_WIFI_TX_DATA, entry.getTxBytes(), + entry.getTxPackets()); + if (entry.getSet() == NetworkStats.SET_DEFAULT) { // Background transfers + u.noteNetworkActivityLocked(NETWORK_WIFI_BG_TX_DATA, entry.getTxBytes(), + entry.getTxPackets()); } mNetworkByteActivityCounters[NETWORK_WIFI_TX_DATA].addCountLocked( - entry.txBytes); + entry.getTxBytes()); mNetworkPacketActivityCounters[NETWORK_WIFI_TX_DATA].addCountLocked( - entry.txPackets); + entry.getTxPackets()); - add(txPackets, uid, entry.txPackets); + txPackets.incrementValue(uid, entry.getTxPackets()); // Sum the total number of packets so that the Tx Power can // be evenly distributed amongst the apps. - totalTxPackets += entry.txPackets; + totalTxPackets += entry.getTxPackets(); } // Calculate consumed energy for this uid. Only do so if WifiReporting isn't @@ -12828,13 +12883,12 @@ public class BatteryStatsImpl extends BatteryStats { } } - uidEstimatedConsumptionMah.add(u.getUid(), + uidEstimatedConsumptionMah.incrementValue(u.getUid(), mWifiPowerCalculator.calcPowerWithoutControllerDataMah( - entry.rxPackets, entry.txPackets, + entry.getRxPackets(), entry.getTxPackets(), uidRunningMs, uidScanMs, uidBatchScanMs)); } } - mNetworkStatsPool.release(delta); delta = null; } @@ -12923,12 +12977,9 @@ public class BatteryStatsImpl extends BatteryStats { + scanTxTimeSinceMarkMs + " ms)"); } - ControllerActivityCounterImpl activityCounter = - uid.getOrCreateWifiControllerActivityLocked(); - activityCounter.getOrCreateRxTimeCounter() - .increment(scanRxTimeSinceMarkMs, elapsedRealtimeMs); - activityCounter.getOrCreateTxTimeCounters()[0] - .increment(scanTxTimeSinceMarkMs, elapsedRealtimeMs); + rxTimesMs.incrementValue(uid.getUid(), scanRxTimeSinceMarkMs); + txTimesMs.incrementValue(uid.getUid(), scanTxTimeSinceMarkMs); + leftOverRxTimeMs -= scanRxTimeSinceMarkMs; leftOverTxTimeMs -= scanTxTimeSinceMarkMs; } @@ -12955,7 +13006,7 @@ public class BatteryStatsImpl extends BatteryStats { if (uidEstimatedConsumptionMah != null) { double uidEstMah = mWifiPowerCalculator.calcPowerFromControllerDataMah( scanRxTimeSinceMarkMs, scanTxTimeSinceMarkMs, myIdleTimeMs); - uidEstimatedConsumptionMah.add(uid.getUid(), uidEstMah); + uidEstimatedConsumptionMah.incrementValue(uid.getUid(), uidEstMah); } } @@ -12967,36 +13018,51 @@ public class BatteryStatsImpl extends BatteryStats { // Distribute the remaining Tx power appropriately between all apps that transmitted // packets. for (int i = 0; i < txPackets.size(); i++) { - final Uid uid = getUidStatsLocked(txPackets.keyAt(i), - elapsedRealtimeMs, uptimeMs); + final int uid = txPackets.keyAt(i); final long myTxTimeMs = (txPackets.valueAt(i) * leftOverTxTimeMs) / totalTxPackets; + txTimesMs.incrementValue(uid, myTxTimeMs); + } + + // Distribute the remaining Rx power appropriately between all apps that received + // packets. + for (int i = 0; i < rxPackets.size(); i++) { + final int uid = rxPackets.keyAt(i); + final long myRxTimeMs = (rxPackets.valueAt(i) * leftOverRxTimeMs) + / totalRxPackets; + rxTimesMs.incrementValue(uid, myRxTimeMs); + } + + for (int i = 0; i < txTimesMs.size(); i++) { + final int uid = txTimesMs.keyAt(i); + final long myTxTimeMs = txTimesMs.valueAt(i); if (DEBUG_ENERGY) { - Slog.d(TAG, " TxTime for UID " + uid.getUid() + ": " + myTxTimeMs + " ms"); + Slog.d(TAG, " TxTime for UID " + uid + ": " + myTxTimeMs + " ms"); } - uid.getOrCreateWifiControllerActivityLocked().getOrCreateTxTimeCounters()[0] + getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs) + .getOrCreateWifiControllerActivityLocked() + .getOrCreateTxTimeCounters()[0] .increment(myTxTimeMs, elapsedRealtimeMs); if (uidEstimatedConsumptionMah != null) { - uidEstimatedConsumptionMah.add(uid.getUid(), + uidEstimatedConsumptionMah.incrementValue(uid, mWifiPowerCalculator.calcPowerFromControllerDataMah( 0, myTxTimeMs, 0)); } } - // Distribute the remaining Rx power appropriately between all apps that received - // packets. - for (int i = 0; i < rxPackets.size(); i++) { - final Uid uid = getUidStatsLocked(rxPackets.keyAt(i), - elapsedRealtimeMs, uptimeMs); - final long myRxTimeMs = (rxPackets.valueAt(i) * leftOverRxTimeMs) - / totalRxPackets; + for (int i = 0; i < rxTimesMs.size(); i++) { + final int uid = rxTimesMs.keyAt(i); + final long myRxTimeMs = rxTimesMs.valueAt(i); if (DEBUG_ENERGY) { - Slog.d(TAG, " RxTime for UID " + uid.getUid() + ": " + myRxTimeMs + " ms"); + Slog.d(TAG, " RxTime for UID " + uid + ": " + myRxTimeMs + " ms"); } - uid.getOrCreateWifiControllerActivityLocked().getOrCreateRxTimeCounter() + + getUidStatsLocked(rxTimesMs.keyAt(i), elapsedRealtimeMs, uptimeMs) + .getOrCreateWifiControllerActivityLocked() + .getOrCreateRxTimeCounter() .increment(myRxTimeMs, elapsedRealtimeMs); if (uidEstimatedConsumptionMah != null) { - uidEstimatedConsumptionMah.add(uid.getUid(), + uidEstimatedConsumptionMah.incrementValue(uid, mWifiPowerCalculator.calcPowerFromControllerDataMah( myRxTimeMs, 0, 0)); } @@ -13004,7 +13070,6 @@ public class BatteryStatsImpl extends BatteryStats { // Any left over power use will be picked up by the WiFi category in BatteryStatsHelper. - // Update WiFi controller stats. mWifiActivity.getOrCreateRxTimeCounter().increment( info.getControllerRxDurationMillis(), elapsedRealtimeMs); @@ -13083,21 +13148,15 @@ public class BatteryStatsImpl extends BatteryStats { // Grab a separate lock to acquire the network stats, which may do I/O. NetworkStats delta = null; synchronized (mModemNetworkLock) { - final NetworkStats latestStats = readNetworkStatsLocked(networkStatsManager, - mModemIfaces); + final NetworkStats latestStats = readMobileNetworkStatsLocked(networkStatsManager); if (latestStats != null) { - delta = NetworkStats.subtract(latestStats, mLastModemNetworkStats, null, null, - mNetworkStatsPool.acquire()); - mNetworkStatsPool.release(mLastModemNetworkStats); + delta = latestStats.subtract(mLastModemNetworkStats); mLastModemNetworkStats = latestStats; } } synchronized (this) { if (!mOnBatteryInternal || mIgnoreNextExternalStats) { - if (delta != null) { - mNetworkStatsPool.release(delta); - } return; } @@ -13224,7 +13283,7 @@ public class BatteryStatsImpl extends BatteryStats { // Distribute measured mobile radio charge consumption based on app radio // active time if (uidEstimatedConsumptionMah != null) { - uidEstimatedConsumptionMah.add(u.getUid(), + uidEstimatedConsumptionMah.incrementValue(u.getUid(), mMobileRadioPowerCalculator.calcPowerFromRadioActiveDurationMah( appRadioTimeUs / 1000)); } @@ -13303,7 +13362,6 @@ public class BatteryStatsImpl extends BatteryStats { totalEstimatedConsumptionMah, elapsedRealtimeMs); } - mNetworkStatsPool.release(delta); delta = null; } } @@ -13349,8 +13407,8 @@ public class BatteryStatsImpl extends BatteryStats { energy = info.getControllerEnergyUsed(); if (!info.getUidTraffic().isEmpty()) { for (UidTraffic traffic : info.getUidTraffic()) { - add(uidRxBytes, traffic.getUid(), traffic.getRxBytes()); - add(uidTxBytes, traffic.getUid(), traffic.getTxBytes()); + uidRxBytes.incrementValue(traffic.getUid(), traffic.getRxBytes()); + uidTxBytes.incrementValue(traffic.getUid(), traffic.getTxBytes()); } } } @@ -13442,6 +13500,9 @@ public class BatteryStatsImpl extends BatteryStats { long leftOverRxTimeMs = rxTimeMs; long leftOverTxTimeMs = txTimeMs; + final SparseLongArray rxTimesMs = new SparseLongArray(uidCount); + final SparseLongArray txTimesMs = new SparseLongArray(uidCount); + for (int i = 0; i < uidCount; i++) { final Uid u = mUidStats.valueAt(i); if (u.mBluetoothScanTimer == null) { @@ -13471,15 +13532,11 @@ public class BatteryStatsImpl extends BatteryStats { scanTimeTxSinceMarkMs = (txTimeMs * scanTimeTxSinceMarkMs) / totalScanTimeMs; } - final ControllerActivityCounterImpl counter = - u.getOrCreateBluetoothControllerActivityLocked(); - counter.getOrCreateRxTimeCounter() - .increment(scanTimeRxSinceMarkMs, elapsedRealtimeMs); - counter.getOrCreateTxTimeCounters()[0] - .increment(scanTimeTxSinceMarkMs, elapsedRealtimeMs); + rxTimesMs.incrementValue(u.getUid(), scanTimeRxSinceMarkMs); + txTimesMs.incrementValue(u.getUid(), scanTimeTxSinceMarkMs); if (uidEstimatedConsumptionMah != null) { - uidEstimatedConsumptionMah.add(u.getUid(), + uidEstimatedConsumptionMah.incrementValue(u.getUid(), mBluetoothPowerCalculator.calculatePowerMah( scanTimeRxSinceMarkMs, scanTimeTxSinceMarkMs, 0)); } @@ -13540,29 +13597,45 @@ public class BatteryStatsImpl extends BatteryStats { if (totalRxBytes > 0 && rxBytes > 0) { final long timeRxMs = (leftOverRxTimeMs * rxBytes) / totalRxBytes; - if (DEBUG_ENERGY) { - Slog.d(TAG, "UID=" + uid + " rx_bytes=" + rxBytes + " rx_time=" + timeRxMs); - } - counter.getOrCreateRxTimeCounter().increment(timeRxMs, elapsedRealtimeMs); - - if (uidEstimatedConsumptionMah != null) { - uidEstimatedConsumptionMah.add(u.getUid(), - mBluetoothPowerCalculator.calculatePowerMah(timeRxMs, 0, 0)); - } + rxTimesMs.incrementValue(uid, timeRxMs); } if (totalTxBytes > 0 && txBytes > 0) { final long timeTxMs = (leftOverTxTimeMs * txBytes) / totalTxBytes; - if (DEBUG_ENERGY) { - Slog.d(TAG, "UID=" + uid + " tx_bytes=" + txBytes + " tx_time=" + timeTxMs); - } - counter.getOrCreateTxTimeCounters()[0] - .increment(timeTxMs, elapsedRealtimeMs); + txTimesMs.incrementValue(uid, timeTxMs); + } + } - if (uidEstimatedConsumptionMah != null) { - uidEstimatedConsumptionMah.add(u.getUid(), - mBluetoothPowerCalculator.calculatePowerMah(0, timeTxMs, 0)); - } + for (int i = 0; i < txTimesMs.size(); i++) { + final int uid = txTimesMs.keyAt(i); + final long myTxTimeMs = txTimesMs.valueAt(i); + if (DEBUG_ENERGY) { + Slog.d(TAG, " TxTime for UID " + uid + ": " + myTxTimeMs + " ms"); + } + getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs) + .getOrCreateBluetoothControllerActivityLocked() + .getOrCreateTxTimeCounters()[0] + .increment(myTxTimeMs, elapsedRealtimeMs); + if (uidEstimatedConsumptionMah != null) { + uidEstimatedConsumptionMah.incrementValue(uid, + mBluetoothPowerCalculator.calculatePowerMah(0, myTxTimeMs, 0)); + } + } + + for (int i = 0; i < rxTimesMs.size(); i++) { + final int uid = rxTimesMs.keyAt(i); + final long myRxTimeMs = rxTimesMs.valueAt(i); + if (DEBUG_ENERGY) { + Slog.d(TAG, " RxTime for UID " + uid + ": " + myRxTimeMs + " ms"); + } + + getUidStatsLocked(rxTimesMs.keyAt(i), elapsedRealtimeMs, uptimeMs) + .getOrCreateBluetoothControllerActivityLocked() + .getOrCreateRxTimeCounter() + .increment(myRxTimeMs, elapsedRealtimeMs); + if (uidEstimatedConsumptionMah != null) { + uidEstimatedConsumptionMah.incrementValue(uid, + mBluetoothPowerCalculator.calculatePowerMah(myRxTimeMs, 0, 0)); } } } @@ -16183,6 +16256,18 @@ public class BatteryStatsImpl extends BatteryStats { iPw.decreaseIndent(); } + /** + * Dump Power Profile + */ + @GuardedBy("this") + public void dumpPowerProfileLocked(PrintWriter pw) { + final IndentingPrintWriter iPw = new IndentingPrintWriter(pw, " "); + iPw.printf("Power Profile: \n"); + iPw.increaseIndent(); + mPowerProfile.dump(iPw); + iPw.decreaseIndent(); + } + final ReentrantLock mWriteLock = new ReentrantLock(); @GuardedBy("this") @@ -18140,8 +18225,4 @@ public class BatteryStatsImpl extends BatteryStats { pw.println(); dumpMeasuredEnergyStatsLocked(pw); } - - private static void add(SparseLongArray array, int key, long delta) { - array.put(key, array.get(key) + delta); - } } diff --git a/core/java/com/android/internal/os/BatteryUsageStatsStore.java b/core/java/com/android/internal/os/BatteryUsageStatsStore.java index fd54b32bd12f..af82f40013d8 100644 --- a/core/java/com/android/internal/os/BatteryUsageStatsStore.java +++ b/core/java/com/android/internal/os/BatteryUsageStatsStore.java @@ -58,6 +58,7 @@ public class BatteryUsageStatsStore { new BatteryUsageStatsQuery.Builder() .setMaxStatsAgeMs(0) .includePowerModels() + .includeProcessStateData() .build()); private static final String BATTERY_USAGE_STATS_DIR = "battery-usage-stats"; private static final String SNAPSHOT_FILE_EXTENSION = ".bus"; diff --git a/core/java/com/android/internal/os/BinderLatencyObserver.java b/core/java/com/android/internal/os/BinderLatencyObserver.java index 20cf102953e4..e9d55db3a5b4 100644 --- a/core/java/com/android/internal/os/BinderLatencyObserver.java +++ b/core/java/com/android/internal/os/BinderLatencyObserver.java @@ -19,7 +19,6 @@ package com.android.internal.os; import android.annotation.Nullable; import android.os.Binder; import android.os.Handler; -import android.os.Looper; import android.os.SystemClock; import android.util.ArrayMap; import android.util.Slog; @@ -181,7 +180,7 @@ public class BinderLatencyObserver { } public Handler getHandler() { - return new Handler(Looper.getMainLooper()); + return BackgroundThread.getHandler(); } } diff --git a/core/java/com/android/internal/os/BluetoothPowerCalculator.java b/core/java/com/android/internal/os/BluetoothPowerCalculator.java index c322258eda85..20535d29afcd 100644 --- a/core/java/com/android/internal/os/BluetoothPowerCalculator.java +++ b/core/java/com/android/internal/os/BluetoothPowerCalculator.java @@ -15,6 +15,7 @@ */ package com.android.internal.os; +import android.annotation.Nullable; import android.os.BatteryConsumer; import android.os.BatteryStats; import android.os.BatteryStats.ControllerActivityCounter; @@ -26,19 +27,33 @@ import android.os.UserHandle; import android.util.Log; import android.util.SparseArray; +import java.util.Arrays; import java.util.List; public class BluetoothPowerCalculator extends PowerCalculator { private static final String TAG = "BluetoothPowerCalc"; private static final boolean DEBUG = BatteryStatsHelper.DEBUG; + + private static final BatteryConsumer.Key[] UNINITIALIZED_KEYS = new BatteryConsumer.Key[0]; + private final double mIdleMa; private final double mRxMa; private final double mTxMa; private final boolean mHasBluetoothPowerController; private static class PowerAndDuration { + // Return value of BT duration per app public long durationMs; + // Return value of BT power per app public double powerMah; + + public BatteryConsumer.Key[] keys; + public double[] powerPerKeyMah; + + // Aggregated BT duration across all apps + public long totalDurationMs; + // Aggregated BT power across all apps + public double totalPowerMah; } public BluetoothPowerCalculator(PowerProfile profile) { @@ -55,59 +70,88 @@ public class BluetoothPowerCalculator extends PowerCalculator { return; } - final PowerAndDuration total = new PowerAndDuration(); + BatteryConsumer.Key[] keys = UNINITIALIZED_KEYS; + final PowerAndDuration powerAndDuration = new PowerAndDuration(); final SparseArray<UidBatteryConsumer.Builder> uidBatteryConsumerBuilders = builder.getUidBatteryConsumerBuilders(); for (int i = uidBatteryConsumerBuilders.size() - 1; i >= 0; i--) { final UidBatteryConsumer.Builder app = uidBatteryConsumerBuilders.valueAt(i); - calculateApp(app, total, query); + if (keys == UNINITIALIZED_KEYS) { + if (query.isProcessStateDataNeeded()) { + keys = app.getKeys(BatteryConsumer.POWER_COMPONENT_BLUETOOTH); + powerAndDuration.keys = keys; + powerAndDuration.powerPerKeyMah = new double[keys.length]; + } else { + keys = null; + } + } + calculateApp(app, powerAndDuration, query); } final long measuredChargeUC = batteryStats.getBluetoothMeasuredBatteryConsumptionUC(); final int powerModel = getPowerModel(measuredChargeUC, query); final ControllerActivityCounter activityCounter = batteryStats.getBluetoothControllerActivity(); - final long systemDurationMs = calculateDuration(activityCounter); - final double systemPowerMah = calculatePowerMah(powerModel, measuredChargeUC, - activityCounter, query.shouldForceUsePowerProfileModel()); + calculatePowerAndDuration(null, powerModel, measuredChargeUC, + activityCounter, query.shouldForceUsePowerProfileModel(), powerAndDuration); // Subtract what the apps used, but clamp to 0. - final long systemComponentDurationMs = Math.max(0, systemDurationMs - total.durationMs); + final long systemComponentDurationMs = Math.max(0, + powerAndDuration.durationMs - powerAndDuration.totalDurationMs); if (DEBUG) { Log.d(TAG, "Bluetooth active: time=" + (systemComponentDurationMs) - + " power=" + formatCharge(systemPowerMah)); + + " power=" + formatCharge(powerAndDuration.powerMah)); } builder.getAggregateBatteryConsumerBuilder( - BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE) - .setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_BLUETOOTH, systemDurationMs) + BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE) + .setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_BLUETOOTH, + powerAndDuration.durationMs) .setConsumedPower(BatteryConsumer.POWER_COMPONENT_BLUETOOTH, - Math.max(systemPowerMah, total.powerMah), powerModel); + Math.max(powerAndDuration.powerMah, powerAndDuration.totalPowerMah), + powerModel); builder.getAggregateBatteryConsumerBuilder( - BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS) - .setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_BLUETOOTH, total.durationMs) - .setConsumedPower(BatteryConsumer.POWER_COMPONENT_BLUETOOTH, total.powerMah, + BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS) + .setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_BLUETOOTH, + powerAndDuration.totalDurationMs) + .setConsumedPower(BatteryConsumer.POWER_COMPONENT_BLUETOOTH, + powerAndDuration.totalPowerMah, powerModel); } - private void calculateApp(UidBatteryConsumer.Builder app, PowerAndDuration total, + private void calculateApp(UidBatteryConsumer.Builder app, PowerAndDuration powerAndDuration, BatteryUsageStatsQuery query) { final long measuredChargeUC = app.getBatteryStatsUid().getBluetoothMeasuredBatteryConsumptionUC(); final int powerModel = getPowerModel(measuredChargeUC, query); final ControllerActivityCounter activityCounter = app.getBatteryStatsUid().getBluetoothControllerActivity(); - final long durationMs = calculateDuration(activityCounter); - final double powerMah = calculatePowerMah(powerModel, measuredChargeUC, activityCounter, - query.shouldForceUsePowerProfileModel()); + calculatePowerAndDuration(app.getBatteryStatsUid(), powerModel, measuredChargeUC, + activityCounter, query.shouldForceUsePowerProfileModel(), powerAndDuration); - app.setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_BLUETOOTH, durationMs) - .setConsumedPower(BatteryConsumer.POWER_COMPONENT_BLUETOOTH, powerMah, powerModel); + app.setUsageDurationMillis( + BatteryConsumer.POWER_COMPONENT_BLUETOOTH, powerAndDuration.durationMs) + .setConsumedPower( + BatteryConsumer.POWER_COMPONENT_BLUETOOTH, powerAndDuration.powerMah, + powerModel); + + powerAndDuration.totalDurationMs += powerAndDuration.durationMs; + powerAndDuration.totalPowerMah += powerAndDuration.powerMah; - total.durationMs += durationMs; - total.powerMah += powerMah; + if (query.isProcessStateDataNeeded() && powerAndDuration.keys != null) { + for (int j = 0; j < powerAndDuration.keys.length; j++) { + BatteryConsumer.Key key = powerAndDuration.keys[j]; + final int processState = key.processState; + if (processState == BatteryConsumer.PROCESS_STATE_UNSPECIFIED) { + // Already populated with the powerAndDuration across all process states + continue; + } + + app.setConsumedPower(key, powerAndDuration.powerPerKeyMah[j], powerModel); + } + } } @Override @@ -117,12 +161,12 @@ public class BluetoothPowerCalculator extends PowerCalculator { return; } - PowerAndDuration total = new PowerAndDuration(); + PowerAndDuration powerAndDuration = new PowerAndDuration(); for (int i = sippers.size() - 1; i >= 0; i--) { final BatterySipper app = sippers.get(i); if (app.drainType == BatterySipper.DrainType.APP) { - calculateApp(app, app.uidObj, statsType, total); + calculateApp(app, app.uidObj, statsType, powerAndDuration); } } @@ -131,13 +175,14 @@ public class BluetoothPowerCalculator extends PowerCalculator { final int powerModel = getPowerModel(measuredChargeUC); final ControllerActivityCounter activityCounter = batteryStats.getBluetoothControllerActivity(); - final long systemDurationMs = calculateDuration(activityCounter); - final double systemPowerMah = - calculatePowerMah(powerModel, measuredChargeUC, activityCounter, false); + calculatePowerAndDuration(null, powerModel, measuredChargeUC, activityCounter, false, + powerAndDuration); // Subtract what the apps used, but clamp to 0. - final double powerMah = Math.max(0, systemPowerMah - total.powerMah); - final long durationMs = Math.max(0, systemDurationMs - total.durationMs); + final double powerMah = Math.max(0, + powerAndDuration.powerMah - powerAndDuration.totalPowerMah); + final long durationMs = Math.max(0, + powerAndDuration.durationMs - powerAndDuration.totalDurationMs); if (DEBUG && powerMah != 0) { Log.d(TAG, "Bluetooth active: time=" + (durationMs) + " power=" + formatCharge(powerMah)); @@ -160,65 +205,102 @@ public class BluetoothPowerCalculator extends PowerCalculator { } private void calculateApp(BatterySipper app, BatteryStats.Uid u, int statsType, - PowerAndDuration total) { - + PowerAndDuration powerAndDuration) { final long measuredChargeUC = u.getBluetoothMeasuredBatteryConsumptionUC(); final int powerModel = getPowerModel(measuredChargeUC); final ControllerActivityCounter activityCounter = u.getBluetoothControllerActivity(); - final long durationMs = calculateDuration(activityCounter); - final double powerMah = calculatePowerMah(powerModel, measuredChargeUC, activityCounter, - false); + calculatePowerAndDuration(u, powerModel, measuredChargeUC, activityCounter, + false, powerAndDuration); - app.bluetoothRunningTimeMs = durationMs; - app.bluetoothPowerMah = powerMah; + app.bluetoothRunningTimeMs = powerAndDuration.durationMs; + app.bluetoothPowerMah = powerAndDuration.powerMah; app.btRxBytes = u.getNetworkActivityBytes(BatteryStats.NETWORK_BT_RX_DATA, statsType); app.btTxBytes = u.getNetworkActivityBytes(BatteryStats.NETWORK_BT_TX_DATA, statsType); - total.durationMs += durationMs; - total.powerMah += powerMah; + powerAndDuration.totalDurationMs += powerAndDuration.durationMs; + powerAndDuration.totalPowerMah += powerAndDuration.powerMah; } - private long calculateDuration(ControllerActivityCounter counter) { + /** Returns bluetooth power usage based on the best data available. */ + private void calculatePowerAndDuration(@Nullable BatteryStats.Uid uid, + @BatteryConsumer.PowerModel int powerModel, + long measuredChargeUC, ControllerActivityCounter counter, boolean ignoreReportedPower, + PowerAndDuration powerAndDuration) { if (counter == null) { - return 0; + powerAndDuration.durationMs = 0; + powerAndDuration.powerMah = 0; + if (powerAndDuration.powerPerKeyMah != null) { + Arrays.fill(powerAndDuration.powerPerKeyMah, 0); + } + return; } - return counter.getIdleTimeCounter().getCountLocked(BatteryStats.STATS_SINCE_CHARGED) - + counter.getRxTimeCounter().getCountLocked(BatteryStats.STATS_SINCE_CHARGED) - + counter.getTxTimeCounters()[0].getCountLocked(BatteryStats.STATS_SINCE_CHARGED); - } - - /** Returns bluetooth power usage based on the best data available. */ - private double calculatePowerMah(@BatteryConsumer.PowerModel int powerModel, - long measuredChargeUC, ControllerActivityCounter counter, boolean ignoreReportedPower) { - if (powerModel == BatteryConsumer.POWER_MODEL_MEASURED_ENERGY) { - return uCtoMah(measuredChargeUC); - } + final BatteryStats.LongCounter idleTimeCounter = counter.getIdleTimeCounter(); + final BatteryStats.LongCounter rxTimeCounter = counter.getRxTimeCounter(); + final BatteryStats.LongCounter txTimeCounter = counter.getTxTimeCounters()[0]; + final long idleTimeMs = idleTimeCounter.getCountLocked(BatteryStats.STATS_SINCE_CHARGED); + final long rxTimeMs = rxTimeCounter.getCountLocked(BatteryStats.STATS_SINCE_CHARGED); + final long txTimeMs = txTimeCounter.getCountLocked(BatteryStats.STATS_SINCE_CHARGED); - if (counter == null) { - return 0; - } + powerAndDuration.durationMs = idleTimeMs + rxTimeMs + txTimeMs; - if (!ignoreReportedPower) { - final double powerMah = - counter.getPowerCounter().getCountLocked(BatteryStats.STATS_SINCE_CHARGED) - / (double) (1000 * 60 * 60); - if (powerMah != 0) { - return powerMah; + if (powerModel == BatteryConsumer.POWER_MODEL_MEASURED_ENERGY) { + powerAndDuration.powerMah = uCtoMah(measuredChargeUC); + if (uid != null && powerAndDuration.keys != null) { + for (int i = 0; i < powerAndDuration.keys.length; i++) { + BatteryConsumer.Key key = powerAndDuration.keys[i]; + final int processState = key.processState; + if (processState == BatteryConsumer.PROCESS_STATE_UNSPECIFIED) { + // Already populated with the powerAndDuration across all process states + continue; + } + + powerAndDuration.powerPerKeyMah[i] = + uCtoMah(uid.getBluetoothMeasuredBatteryConsumptionUC(processState)); + } + } + } else { + if (!ignoreReportedPower) { + final double powerMah = + counter.getPowerCounter().getCountLocked(BatteryStats.STATS_SINCE_CHARGED) + / (double) (1000 * 60 * 60); + if (powerMah != 0) { + powerAndDuration.powerMah = powerMah; + if (powerAndDuration.powerPerKeyMah != null) { + // Leave this use case unsupported: used energy is reported + // via BluetoothActivityEnergyInfo rather than PowerStats HAL. + Arrays.fill(powerAndDuration.powerPerKeyMah, 0); + } + return; + } } - } - if (!mHasBluetoothPowerController) { - return 0; + if (mHasBluetoothPowerController) { + powerAndDuration.powerMah = calculatePowerMah(rxTimeMs, txTimeMs, idleTimeMs); + + if (powerAndDuration.keys != null) { + for (int i = 0; i < powerAndDuration.keys.length; i++) { + BatteryConsumer.Key key = powerAndDuration.keys[i]; + final int processState = key.processState; + if (processState == BatteryConsumer.PROCESS_STATE_UNSPECIFIED) { + // Already populated with the powerAndDuration across all process states + continue; + } + + powerAndDuration.powerPerKeyMah[i] = + calculatePowerMah( + rxTimeCounter.getCountForProcessState(processState), + txTimeCounter.getCountForProcessState(processState), + idleTimeCounter.getCountForProcessState(processState)); + } + } + } else { + powerAndDuration.powerMah = 0; + if (powerAndDuration.powerPerKeyMah != null) { + Arrays.fill(powerAndDuration.powerPerKeyMah, 0); + } + } } - - final long idleTimeMs = - counter.getIdleTimeCounter().getCountLocked(BatteryStats.STATS_SINCE_CHARGED); - final long rxTimeMs = - counter.getRxTimeCounter().getCountLocked(BatteryStats.STATS_SINCE_CHARGED); - final long txTimeMs = - counter.getTxTimeCounters()[0].getCountLocked(BatteryStats.STATS_SINCE_CHARGED); - return calculatePowerMah(rxTimeMs, txTimeMs, idleTimeMs); } /** Returns estimated bluetooth power usage based on usage times. */ diff --git a/core/java/com/android/internal/os/OWNERS b/core/java/com/android/internal/os/OWNERS index 7766b77ab3c6..fd1d86b27834 100644 --- a/core/java/com/android/internal/os/OWNERS +++ b/core/java/com/android/internal/os/OWNERS @@ -12,4 +12,5 @@ per-file *PowerCalculator* = file:/BATTERY_STATS_OWNERS per-file *PowerEstimator* = file:/BATTERY_STATS_OWNERS per-file *Kernel* = file:/BATTERY_STATS_OWNERS per-file *MultiState* = file:/BATTERY_STATS_OWNERS +per-file *PowerProfile* = file:/BATTERY_STATS_OWNERS diff --git a/core/java/com/android/internal/os/PowerProfile.java b/core/java/com/android/internal/os/PowerProfile.java index 4d19b35b1e16..54e65e079b83 100644 --- a/core/java/com/android/internal/os/PowerProfile.java +++ b/core/java/com/android/internal/os/PowerProfile.java @@ -17,24 +17,31 @@ package com.android.internal.os; +import android.annotation.LongDef; import android.annotation.StringDef; +import android.annotation.XmlRes; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.res.Resources; import android.content.res.XmlResourceParser; +import android.util.IndentingPrintWriter; import android.util.Slog; import android.util.proto.ProtoOutputStream; +import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.power.ModemPowerProfile; import com.android.internal.util.XmlUtils; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; +import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; /** @@ -259,6 +266,34 @@ public class PowerProfile { public @interface PowerGroup {} /** + * Constants for generating a 64bit power constant key. + * + * The bitfields of a key describes what its corresponding power constant represents: + * [63:40] - RESERVED + * [39:32] - {@link Subsystem} (max count = 16). + * [31:0] - per Subsystem fields, see {@link ModemPowerProfile}. + * + */ + private static final long SUBSYSTEM_MASK = 0xF_0000_0000L; + /** + * Power constant not associated with a subsystem. + */ + public static final long SUBSYSTEM_NONE = 0x0_0000_0000L; + /** + * Modem power constant. + */ + public static final long SUBSYSTEM_MODEM = 0x1_0000_0000L; + + @LongDef(prefix = { "SUBSYSTEM_" }, value = { + SUBSYSTEM_NONE, + SUBSYSTEM_MODEM, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Subsystem {} + + private static final long SUBSYSTEM_FIELDS_MASK = 0xFFFF_FFFF; + + /** * A map from Power Use Item to its power consumption. */ static final HashMap<String, Double> sPowerItemMap = new HashMap<>(); @@ -268,12 +303,16 @@ public class PowerProfile { */ static final HashMap<String, Double[]> sPowerArrayMap = new HashMap<>(); + static final ModemPowerProfile sModemPowerProfile = new ModemPowerProfile(); + private static final String TAG_DEVICE = "device"; private static final String TAG_ITEM = "item"; private static final String TAG_ARRAY = "array"; private static final String TAG_ARRAYITEM = "value"; private static final String ATTR_NAME = "name"; + private static final String TAG_MODEM = "modem"; + private static final Object sLock = new Object(); @VisibleForTesting @@ -289,19 +328,40 @@ public class PowerProfile { public PowerProfile(Context context, boolean forTest) { // Read the XML file for the given profile (normally only one per device) synchronized (sLock) { - if (sPowerItemMap.size() == 0 && sPowerArrayMap.size() == 0) { - readPowerValuesFromXml(context, forTest); - } - initCpuClusters(); - initDisplays(); + final int xmlId = forTest ? com.android.internal.R.xml.power_profile_test + : com.android.internal.R.xml.power_profile; + initLocked(context, xmlId); + } + } + + /** + * Reinitialize the PowerProfile with the provided XML. + * WARNING: use only for testing! + */ + @VisibleForTesting + public void forceInitForTesting(Context context, @XmlRes int xmlId) { + synchronized (sLock) { + sPowerItemMap.clear(); + sPowerArrayMap.clear(); + sModemPowerProfile.clear(); + initLocked(context, xmlId); + } + + } + + @GuardedBy("sLock") + private void initLocked(Context context, @XmlRes int xmlId) { + if (sPowerItemMap.size() == 0 && sPowerArrayMap.size() == 0) { + readPowerValuesFromXml(context, xmlId); } + initCpuClusters(); + initDisplays(); + initModem(); } - private void readPowerValuesFromXml(Context context, boolean forTest) { - final int id = forTest ? com.android.internal.R.xml.power_profile_test : - com.android.internal.R.xml.power_profile; + private void readPowerValuesFromXml(Context context, @XmlRes int xmlId) { final Resources resources = context.getResources(); - XmlResourceParser parser = resources.getXml(id); + XmlResourceParser parser = resources.getXml(xmlId); boolean parsingArray = false; ArrayList<Double> array = new ArrayList<>(); String arrayName = null; @@ -340,6 +400,8 @@ public class PowerProfile { array.add(value); } } + } else if (element.equals(TAG_MODEM)) { + sModemPowerProfile.parseFromXml(parser); } } if (parsingArray) { @@ -515,6 +577,39 @@ public class PowerProfile { return mNumDisplays; } + private void initModem() { + handleDeprecatedModemConstant(ModemPowerProfile.MODEM_DRAIN_TYPE_SLEEP, + POWER_MODEM_CONTROLLER_SLEEP, 0); + handleDeprecatedModemConstant(ModemPowerProfile.MODEM_DRAIN_TYPE_IDLE, + POWER_MODEM_CONTROLLER_IDLE, 0); + handleDeprecatedModemConstant( + ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT | ModemPowerProfile.MODEM_DRAIN_TYPE_RX, + POWER_MODEM_CONTROLLER_RX, 0); + handleDeprecatedModemConstant( + ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_0, POWER_MODEM_CONTROLLER_TX, 0); + handleDeprecatedModemConstant( + ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_1, POWER_MODEM_CONTROLLER_TX, 1); + handleDeprecatedModemConstant( + ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_2, POWER_MODEM_CONTROLLER_TX, 2); + handleDeprecatedModemConstant( + ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_3, POWER_MODEM_CONTROLLER_TX, 3); + handleDeprecatedModemConstant( + ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_4, POWER_MODEM_CONTROLLER_TX, 4); + } + + private void handleDeprecatedModemConstant(int key, String deprecatedKey, int level) { + final double drain = sModemPowerProfile.getAverageBatteryDrainMa(key); + if (!Double.isNaN(drain)) return; // Value already set, don't overwrite it. + + final double deprecatedDrain = getAveragePower(deprecatedKey, level); + sModemPowerProfile.setPowerConstant(key, Double.toString(deprecatedDrain)); + } + /** * Returns the number of memory bandwidth buckets defined in power_profile.xml, or a * default value if the subsystem has no recorded value. @@ -560,6 +655,43 @@ public class PowerProfile { } /** + * Returns the average current in mA consumed by a subsystem's specified operation, or the given + * default value if the subsystem has no recorded value. + * + * @param key that describes a subsystem's battery draining operation + * The key is built from multiple constant, see {@link Subsystem} and + * {@link ModemPowerProfile}. + * @param defaultValue the value to return if the subsystem has no recorded value. + * @return the average current in milliAmps. + */ + public double getAverageBatteryDrainOrDefaultMa(long key, double defaultValue) { + final long subsystemType = key & SUBSYSTEM_MASK; + final int subsystemFields = (int) (key & SUBSYSTEM_FIELDS_MASK); + + final double value; + if (subsystemType == SUBSYSTEM_MODEM) { + value = sModemPowerProfile.getAverageBatteryDrainMa(subsystemFields); + } else { + value = Double.NaN; + } + + if (Double.isNaN(value)) return defaultValue; + return value; + } + + /** + * Returns the average current in mA consumed by a subsystem's specified operation. + * + * @param key that describes a subsystem's battery draining operation + * The key is built from multiple constant, see {@link Subsystem} and + * {@link ModemPowerProfile}. + * @return the average current in milliAmps. + */ + public double getAverageBatteryDrainMa(long key) { + return getAverageBatteryDrainOrDefaultMa(key, 0); + } + + /** * Returns the average current in mA consumed by the subsystem for the given level. * * @param type the subsystem type @@ -784,6 +916,25 @@ public class PowerProfile { PowerProfileProto.BATTERY_CAPACITY); } + /** + * Dump the PowerProfile values. + */ + public void dump(PrintWriter pw) { + final IndentingPrintWriter ipw = new IndentingPrintWriter(pw); + sPowerItemMap.forEach((key, value) -> { + ipw.print(key, value); + ipw.println(); + }); + sPowerArrayMap.forEach((key, value) -> { + ipw.print(key, Arrays.toString(value)); + ipw.println(); + }); + ipw.println("Modem values:"); + ipw.increaseIndent(); + sModemPowerProfile.dump(ipw); + ipw.decreaseIndent(); + } + // Writes items in sPowerItemMap to proto if exists. private void writePowerConstantToProto(ProtoOutputStream proto, String key, long fieldId) { if (sPowerItemMap.containsKey(key)) { diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java index ba7a0ef893d0..d7eeb7b8dec0 100644 --- a/core/java/com/android/internal/policy/DecorView.java +++ b/core/java/com/android/internal/policy/DecorView.java @@ -87,6 +87,7 @@ import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.MotionEvent; +import android.view.OnBackInvokedDispatcher; import android.view.PendingInsetsController; import android.view.ThreadedRenderer; import android.view.View; @@ -108,6 +109,7 @@ import android.view.animation.AnimationUtils; import android.view.animation.Interpolator; import android.widget.FrameLayout; import android.widget.PopupWindow; +import android.window.WindowOnBackInvokedDispatcher; import com.android.internal.R; import com.android.internal.graphics.drawable.BackgroundBlurDrawable; @@ -294,6 +296,7 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind return true; }; private Consumer<Boolean> mCrossWindowBlurEnabledListener; + private final WindowOnBackInvokedDispatcher mOnBackInvokedDispatcher; DecorView(Context context, int featureId, PhoneWindow window, WindowManager.LayoutParams params) { @@ -322,6 +325,7 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind initResizingPaints(); mLegacyNavigationBarBackgroundPaint.setColor(Color.BLACK); + mOnBackInvokedDispatcher = new WindowOnBackInvokedDispatcher(); } void setBackgroundFallback(@Nullable Drawable fallbackDrawable) { @@ -1869,6 +1873,7 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind } mPendingInsetsController.detach(); + mOnBackInvokedDispatcher.detachFromWindow(); } @Override @@ -1913,6 +1918,11 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind return mPendingInsetsController; } + @Override + public WindowOnBackInvokedDispatcher provideWindowOnBackInvokedDispatcher() { + return mOnBackInvokedDispatcher; + } + private ActionMode createActionMode( int type, ActionMode.Callback2 callback, View originatingView) { switch (type) { @@ -2367,6 +2377,7 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind } } } + mOnBackInvokedDispatcher.clear(); } @Override @@ -2648,6 +2659,15 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind } } + /** + * Returns the {@link OnBackInvokedDispatcher} on the decor view. + */ + @Override + @Nullable + public OnBackInvokedDispatcher getOnBackInvokedDispatcher() { + return mOnBackInvokedDispatcher; + } + @Override public String toString() { return "DecorView@" + Integer.toHexString(this.hashCode()) + "[" diff --git a/core/java/com/android/internal/power/ModemPowerProfile.java b/core/java/com/android/internal/power/ModemPowerProfile.java new file mode 100644 index 000000000000..afea69a9c3f2 --- /dev/null +++ b/core/java/com/android/internal/power/ModemPowerProfile.java @@ -0,0 +1,475 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.power; + +import android.annotation.IntDef; +import android.content.res.XmlResourceParser; +import android.telephony.ModemActivityInfo; +import android.telephony.ServiceState; +import android.telephony.TelephonyManager; +import android.util.Slog; +import android.util.SparseArray; +import android.util.SparseDoubleArray; + +import com.android.internal.util.XmlUtils; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.io.PrintWriter; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Arrays; + +/** + * ModemPowerProfile for handling the modem element in the power_profile.xml + */ +public class ModemPowerProfile { + private static final String TAG = "ModemPowerProfile"; + + private static final String TAG_SLEEP = "sleep"; + private static final String TAG_IDLE = "idle"; + private static final String TAG_ACTIVE = "active"; + private static final String TAG_RECEIVE = "receive"; + private static final String TAG_TRANSMIT = "transmit"; + private static final String ATTR_RAT = "rat"; + private static final String ATTR_NR_FREQUENCY = "nrFrequency"; + private static final String ATTR_LEVEL = "level"; + + /** + * A flattened list of the modem power constant extracted from the given XML parser. + * + * The bitfields of a key describes what its corresponding power constant represents: + * [31:28] - {@link ModemDrainType} (max count = 16). + * [27:24] - {@link ModemTxLevel} (only for {@link MODEM_DRAIN_TYPE_TX}) (max count = 16). + * [23:20] - {@link ModemRatType} (max count = 16). + * [19:16] - {@link ModemNrFrequencyRange} (only for {@link MODEM_RAT_TYPE_NR}) + * (max count = 16). + * [15:0] - RESERVED + */ + private final SparseDoubleArray mPowerConstants = new SparseDoubleArray(); + + private static final int MODEM_DRAIN_TYPE_MASK = 0xF000_0000; + private static final int MODEM_TX_LEVEL_MASK = 0x0F00_0000; + private static final int MODEM_RAT_TYPE_MASK = 0x00F0_0000; + private static final int MODEM_NR_FREQUENCY_RANGE_MASK = 0x000F_0000; + + /** + * Corresponds to the overall modem battery drain while asleep. + */ + public static final int MODEM_DRAIN_TYPE_SLEEP = 0x0000_0000; + + /** + * Corresponds to the overall modem battery drain while idle. + */ + public static final int MODEM_DRAIN_TYPE_IDLE = 0x1000_0000; + + /** + * Corresponds to the modem battery drain while receiving data. A specific Rx battery drain + * power constant can be selected using a bitwise OR (|) with {@link ModemRatType} and + * {@link ModemNrFrequencyRange} (when applicable). + */ + public static final int MODEM_DRAIN_TYPE_RX = 0x2000_0000; + + /** + * Corresponds to the modem battery drain while receiving data. + * {@link ModemTxLevel} must be specified with this drain type. + * Specific Tx battery drain power constanta can be selected using a bitwise OR (|) with + * {@link ModemRatType} and {@link ModemNrFrequencyRange} (when applicable). + */ + public static final int MODEM_DRAIN_TYPE_TX = 0x3000_0000; + + @IntDef(prefix = {"MODEM_DRAIN_TYPE_"}, value = { + MODEM_DRAIN_TYPE_SLEEP, + MODEM_DRAIN_TYPE_IDLE, + MODEM_DRAIN_TYPE_RX, + MODEM_DRAIN_TYPE_TX, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ModemDrainType { + } + + + private static final SparseArray<String> MODEM_DRAIN_TYPE_NAMES = new SparseArray<>(4); + static { + MODEM_DRAIN_TYPE_NAMES.put(MODEM_DRAIN_TYPE_SLEEP, "SLEEP"); + MODEM_DRAIN_TYPE_NAMES.put(MODEM_DRAIN_TYPE_IDLE, "IDLE"); + MODEM_DRAIN_TYPE_NAMES.put(MODEM_DRAIN_TYPE_RX, "RX"); + MODEM_DRAIN_TYPE_NAMES.put(MODEM_DRAIN_TYPE_TX, "TX"); + } + + /** + * Corresponds to {@link ModemActivityInfo#TX_POWER_LEVEL_0}. + */ + + public static final int MODEM_TX_LEVEL_0 = 0x0000_0000; + + /** + * Corresponds to {@link ModemActivityInfo#TX_POWER_LEVEL_1}. + */ + + public static final int MODEM_TX_LEVEL_1 = 0x0100_0000; + + /** + * Corresponds to {@link ModemActivityInfo#TX_POWER_LEVEL_2}. + */ + + public static final int MODEM_TX_LEVEL_2 = 0x0200_0000; + + /** + * Corresponds to {@link ModemActivityInfo#TX_POWER_LEVEL_3}. + */ + + public static final int MODEM_TX_LEVEL_3 = 0x0300_0000; + + /** + * Corresponds to {@link ModemActivityInfo#TX_POWER_LEVEL_4}. + */ + + public static final int MODEM_TX_LEVEL_4 = 0x0400_0000; + + private static final int MODEM_TX_LEVEL_COUNT = 5; + + @IntDef(prefix = {"MODEM_TX_LEVEL_"}, value = { + MODEM_TX_LEVEL_0, + MODEM_TX_LEVEL_1, + MODEM_TX_LEVEL_2, + MODEM_TX_LEVEL_3, + MODEM_TX_LEVEL_4, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ModemTxLevel { + } + + private static final SparseArray<String> MODEM_TX_LEVEL_NAMES = new SparseArray<>(5); + static { + MODEM_DRAIN_TYPE_NAMES.put(MODEM_TX_LEVEL_0, "0"); + MODEM_DRAIN_TYPE_NAMES.put(MODEM_TX_LEVEL_1, "1"); + MODEM_DRAIN_TYPE_NAMES.put(MODEM_TX_LEVEL_2, "2"); + MODEM_DRAIN_TYPE_NAMES.put(MODEM_TX_LEVEL_3, "3"); + MODEM_DRAIN_TYPE_NAMES.put(MODEM_TX_LEVEL_4, "4"); + } + + private static final int[] MODEM_TX_LEVEL_MAP = new int[]{ + MODEM_TX_LEVEL_0, + MODEM_TX_LEVEL_1, + MODEM_TX_LEVEL_2, + MODEM_TX_LEVEL_3, + MODEM_TX_LEVEL_4}; + + /** + * Fallback for any active modem usage that does not match specified Radio Access Technology + * (RAT) power constants. + */ + public static final int MODEM_RAT_TYPE_DEFAULT = 0x0000_0000; + + /** + * Corresponds to active modem usage on 4G {@link TelephonyManager#NETWORK_TYPE_LTE} RAT. + */ + public static final int MODEM_RAT_TYPE_LTE = 0x0010_0000; + + /** + * Corresponds to active modem usage on 5G {@link TelephonyManager#NETWORK_TYPE_NR} RAT. + */ + public static final int MODEM_RAT_TYPE_NR = 0x0020_0000; + + @IntDef(prefix = {"MODEM_RAT_TYPE_"}, value = { + MODEM_RAT_TYPE_DEFAULT, + MODEM_RAT_TYPE_LTE, + MODEM_RAT_TYPE_NR, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ModemRatType { + } + + private static final SparseArray<String> MODEM_RAT_TYPE_NAMES = new SparseArray<>(3); + static { + MODEM_RAT_TYPE_NAMES.put(MODEM_RAT_TYPE_DEFAULT, "DEFAULT"); + MODEM_RAT_TYPE_NAMES.put(MODEM_RAT_TYPE_LTE, "LTE"); + MODEM_RAT_TYPE_NAMES.put(MODEM_RAT_TYPE_NR, "NR"); + } + + /** + * Fallback for any active 5G modem usage that does not match specified NR frequency power + * constants. + */ + public static final int MODEM_NR_FREQUENCY_RANGE_DEFAULT = 0x0000_0000; + + /** + * Corresponds to active NR modem usage on {@link ServiceState#FREQUENCY_RANGE_LOW}. + */ + public static final int MODEM_NR_FREQUENCY_RANGE_LOW = 0x0001_0000; + + /** + * Corresponds to active NR modem usage on {@link ServiceState#FREQUENCY_RANGE_MID}. + */ + public static final int MODEM_NR_FREQUENCY_RANGE_MID = 0x0002_0000; + + /** + * Corresponds to active NR modem usage on {@link ServiceState#FREQUENCY_RANGE_HIGH}. + */ + public static final int MODEM_NR_FREQUENCY_RANGE_HIGH = 0x0003_0000; + + /** + * Corresponds to active NR modem usage on {@link ServiceState#FREQUENCY_RANGE_MMWAVE}. + */ + public static final int MODEM_NR_FREQUENCY_RANGE_MMWAVE = 0x0004_0000; + + @IntDef(prefix = {"MODEM_NR_FREQUENCY_RANGE_"}, value = { + MODEM_RAT_TYPE_DEFAULT, + MODEM_NR_FREQUENCY_RANGE_LOW, + MODEM_NR_FREQUENCY_RANGE_MID, + MODEM_NR_FREQUENCY_RANGE_HIGH, + MODEM_NR_FREQUENCY_RANGE_MMWAVE, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ModemNrFrequencyRange { + } + private static final SparseArray<String> MODEM_NR_FREQUENCY_RANGE_NAMES = new SparseArray<>(5); + static { + MODEM_NR_FREQUENCY_RANGE_NAMES.put(MODEM_NR_FREQUENCY_RANGE_DEFAULT, "DEFAULT"); + MODEM_NR_FREQUENCY_RANGE_NAMES.put(MODEM_NR_FREQUENCY_RANGE_LOW, "LOW"); + MODEM_NR_FREQUENCY_RANGE_NAMES.put(MODEM_NR_FREQUENCY_RANGE_MID, "MID"); + MODEM_NR_FREQUENCY_RANGE_NAMES.put(MODEM_NR_FREQUENCY_RANGE_HIGH, "HIGH"); + MODEM_NR_FREQUENCY_RANGE_NAMES.put(MODEM_NR_FREQUENCY_RANGE_MMWAVE, "MMWAVE"); + } + + public ModemPowerProfile() { + } + + /** + * Generates a ModemPowerProfile object from the <modem /> element of a power_profile.xml + */ + public void parseFromXml(XmlResourceParser parser) throws IOException, + XmlPullParserException { + final int depth = parser.getDepth(); + while (XmlUtils.nextElementWithin(parser, depth)) { + final String name = parser.getName(); + switch (name) { + case TAG_SLEEP: + if (parser.next() != XmlPullParser.TEXT) { + continue; + } + final String sleepDrain = parser.getText(); + setPowerConstant(MODEM_DRAIN_TYPE_SLEEP, sleepDrain); + break; + case TAG_IDLE: + if (parser.next() != XmlPullParser.TEXT) { + continue; + } + final String idleDrain = parser.getText(); + setPowerConstant(MODEM_DRAIN_TYPE_IDLE, idleDrain); + break; + case TAG_ACTIVE: + parseActivePowerConstantsFromXml(parser); + break; + default: + Slog.e(TAG, "Unexpected element parsed: " + name); + } + } + } + + /** Parse the <active /> XML element */ + private void parseActivePowerConstantsFromXml(XmlResourceParser parser) + throws IOException, XmlPullParserException { + // Parse attributes to get the type of active modem usage the power constants are for. + final int ratType; + final int nrfType; + try { + ratType = getTypeFromAttribute(parser, ATTR_RAT, MODEM_RAT_TYPE_NAMES); + if (ratType == MODEM_RAT_TYPE_NR) { + nrfType = getTypeFromAttribute(parser, ATTR_NR_FREQUENCY, + MODEM_NR_FREQUENCY_RANGE_NAMES); + } else { + nrfType = 0; + } + } catch (IllegalArgumentException iae) { + Slog.e(TAG, "Failed parse to active modem power constants", iae); + return; + } + + // Parse and populate the active modem use power constants. + final int depth = parser.getDepth(); + while (XmlUtils.nextElementWithin(parser, depth)) { + final String name = parser.getName(); + switch (name) { + case TAG_RECEIVE: + if (parser.next() != XmlPullParser.TEXT) { + continue; + } + final String rxDrain = parser.getText(); + final int rxKey = MODEM_DRAIN_TYPE_RX | ratType | nrfType; + setPowerConstant(rxKey, rxDrain); + break; + case TAG_TRANSMIT: + final int level = XmlUtils.readIntAttribute(parser, ATTR_LEVEL, -1); + if (parser.next() != XmlPullParser.TEXT) { + continue; + } + final String txDrain = parser.getText(); + if (level < 0 || level >= MODEM_TX_LEVEL_COUNT) { + Slog.e(TAG, + "Unexpected tx level: " + level + ". Must be between 0 and " + ( + MODEM_TX_LEVEL_COUNT - 1)); + continue; + } + final int modemTxLevel = MODEM_TX_LEVEL_MAP[level]; + final int txKey = MODEM_DRAIN_TYPE_TX | modemTxLevel | ratType | nrfType; + setPowerConstant(txKey, txDrain); + break; + default: + Slog.e(TAG, "Unexpected element parsed: " + name); + } + } + } + + private static int getTypeFromAttribute(XmlResourceParser parser, String attr, + SparseArray<String> names) { + final String value = XmlUtils.readStringAttribute(parser, attr); + if (value == null) { + // Attribute was not specified, just use the default. + return 0; + } + int index = -1; + final int size = names.size(); + // Manual linear search for string. (SparseArray uses == not equals.) + for (int i = 0; i < size; i++) { + if (value.equals(names.valueAt(i))) { + index = i; + } + } + if (index < 0) { + final String[] stringNames = new String[size]; + for (int i = 0; i < size; i++) { + stringNames[i] = names.valueAt(i); + } + throw new IllegalArgumentException( + "Unexpected " + attr + " value : " + value + ". Acceptable values are " + + Arrays.toString(stringNames)); + } + return names.keyAt(index); + } + + /** + * Set the average battery drain in milli-amps of the modem for a given drain type. + * + * @param key a key built from the union of {@link ModemDrainType}, {@link ModemTxLevel}, + * {@link ModemRatType}, and {@link ModemNrFrequencyRange}.key + * @param value the battery dram in milli-amps for the given key. + */ + public void setPowerConstant(int key, String value) { + try { + mPowerConstants.put(key, Double.valueOf(value)); + } catch (Exception e) { + Slog.e(TAG, "Failed to set power constant 0x" + Integer.toHexString( + key) + "(" + keyToString(key) + ") to " + value, e); + } + } + + /** + * Returns the average battery drain in milli-amps of the modem for a given drain type. + * Returns {@link Double.NaN} if a suitable value is not found for the given key. + * + * @param key a key built from the union of {@link ModemDrainType}, {@link ModemTxLevel}, + * {@link ModemRatType}, and {@link ModemNrFrequencyRange}. + */ + public double getAverageBatteryDrainMa(int key) { + int bestKey = key; + double value; + value = mPowerConstants.get(bestKey, Double.NaN); + if (!Double.isNaN(value)) return value; + // The power constant for given key was not explicitly set. Try to fallback to possible + // defaults. + + if ((bestKey & MODEM_NR_FREQUENCY_RANGE_MASK) != MODEM_NR_FREQUENCY_RANGE_DEFAULT) { + // Fallback to NR Frequency default value + bestKey &= ~MODEM_NR_FREQUENCY_RANGE_MASK; + bestKey |= MODEM_NR_FREQUENCY_RANGE_DEFAULT; + value = mPowerConstants.get(bestKey, Double.NaN); + if (!Double.isNaN(value)) return value; + } + + if ((bestKey & MODEM_RAT_TYPE_MASK) != MODEM_RAT_TYPE_DEFAULT) { + // Fallback to RAT default value + bestKey &= ~MODEM_RAT_TYPE_MASK; + bestKey |= MODEM_RAT_TYPE_DEFAULT; + value = mPowerConstants.get(bestKey, Double.NaN); + if (!Double.isNaN(value)) return value; + } + + Slog.w(TAG, + "getAverageBatteryDrainMaH called with unexpected key: 0x" + Integer.toHexString( + key) + ", " + keyToString(key)); + return Double.NaN; + } + + private static String keyToString(int key) { + StringBuilder sb = new StringBuilder(); + final int drainType = key & MODEM_DRAIN_TYPE_MASK; + appendFieldToString(sb, "drain", MODEM_DRAIN_TYPE_NAMES, drainType); + sb.append(","); + + if (drainType == MODEM_DRAIN_TYPE_TX) { + final int txLevel = key & MODEM_TX_LEVEL_MASK; + appendFieldToString(sb, "level", MODEM_TX_LEVEL_NAMES, txLevel); + } + + final int ratType = key & MODEM_RAT_TYPE_MASK; + appendFieldToString(sb, "RAT", MODEM_RAT_TYPE_NAMES, ratType); + + if (ratType == MODEM_RAT_TYPE_NR) { + sb.append(","); + final int nrFreq = key & MODEM_NR_FREQUENCY_RANGE_MASK; + appendFieldToString(sb, "nrFreq", MODEM_NR_FREQUENCY_RANGE_NAMES, nrFreq); + } + return sb.toString(); + } + private static void appendFieldToString(StringBuilder sb, String fieldName, + SparseArray<String> names, int key) { + sb.append(fieldName); + sb.append(":"); + final String name = names.get(key, null); + if (name == null) { + sb.append("UNKNOWN(0x"); + sb.append(Integer.toHexString(key)); + sb.append(")"); + } else { + sb.append(name); + } + } + + /** + * Clear this ModemPowerProfile power constants. + */ + public void clear() { + mPowerConstants.clear(); + } + + + /** + * Dump this ModemPowerProfile power constants. + */ + public void dump(PrintWriter pw) { + final int size = mPowerConstants.size(); + for (int i = 0; i < size; i++) { + pw.print(keyToString(mPowerConstants.keyAt(i))); + pw.print("="); + pw.println(mPowerConstants.valueAt(i)); + } + } +} diff --git a/core/java/com/android/internal/protolog/ProtoLogGroup.java b/core/java/com/android/internal/protolog/ProtoLogGroup.java index 5ac493637822..def598ca8724 100644 --- a/core/java/com/android/internal/protolog/ProtoLogGroup.java +++ b/core/java/com/android/internal/protolog/ProtoLogGroup.java @@ -85,6 +85,8 @@ public enum ProtoLogGroup implements IProtoLogGroup { WM_DEBUG_LAYER_MIRRORING(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true, Consts.TAG_WM), WM_DEBUG_WALLPAPER(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, Consts.TAG_WM), + WM_DEBUG_BACK_PREVIEW(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true, + "CoreBackPreview"), TEST_GROUP(true, true, false, "WindowManagerProtoLogTest"); private final boolean mEnabled; diff --git a/core/java/com/android/internal/statusbar/StatusBarIcon.java b/core/java/com/android/internal/statusbar/StatusBarIcon.java index 1d626235c4d2..4f80afaab696 100644 --- a/core/java/com/android/internal/statusbar/StatusBarIcon.java +++ b/core/java/com/android/internal/statusbar/StatusBarIcon.java @@ -81,9 +81,9 @@ public class StatusBarIcon implements Parcelable { } public void readFromParcel(Parcel in) { - this.icon = (Icon) in.readParcelable(null); + this.icon = (Icon) in.readParcelable(null, android.graphics.drawable.Icon.class); this.pkg = in.readString(); - this.user = (UserHandle) in.readParcelable(null); + this.user = (UserHandle) in.readParcelable(null, android.os.UserHandle.class); this.iconLevel = in.readInt(); this.visible = in.readInt() != 0; this.number = in.readInt(); diff --git a/core/java/com/android/internal/usb/DumpUtils.java b/core/java/com/android/internal/usb/DumpUtils.java index 32601368f2fb..b32a6b078884 100644 --- a/core/java/com/android/internal/usb/DumpUtils.java +++ b/core/java/com/android/internal/usb/DumpUtils.java @@ -244,7 +244,12 @@ public class DumpUtils { writeContaminantPresenceStatus(dump, "contaminant_presence_status", UsbPortStatusProto.CONTAMINANT_PRESENCE_STATUS, status.getContaminantDetectionStatus()); - + dump.write("usb_data_status", UsbPortStatusProto.USB_DATA_STATUS, + UsbPort.usbDataStatusToString(status.getUsbDataStatus())); + dump.write("is_power_transfer_limited", UsbPortStatusProto.IS_POWER_TRANSFER_LIMITED, + status.isPowerTransferLimited()); + dump.write("usb_power_brick_status", UsbPortStatusProto.USB_POWER_BRICK_STATUS, + UsbPort.powerBrickStatusToString(status.getPowerBrickStatus())); dump.end(token); } } diff --git a/core/java/com/android/internal/util/FileRotator.java b/core/java/com/android/internal/util/FileRotator.java index 3ca33203f554..4b3af1536175 100644 --- a/core/java/com/android/internal/util/FileRotator.java +++ b/core/java/com/android/internal/util/FileRotator.java @@ -17,7 +17,7 @@ package com.android.internal.util; import android.os.FileUtils; -import android.util.Slog; +import android.util.Log; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; @@ -32,7 +32,6 @@ import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; import libcore.io.IoUtils; -import libcore.io.Streams; /** * Utility that rotates files over time, similar to {@code logrotate}. There is @@ -47,6 +46,8 @@ import libcore.io.Streams; * <p> * Users must periodically call {@link #maybeRotate(long)} to perform actual * rotation. Not inherently thread safe. + * + * @hide */ public class FileRotator { private static final String TAG = "FileRotator"; @@ -110,7 +111,7 @@ public class FileRotator { if (!name.startsWith(mPrefix)) continue; if (name.endsWith(SUFFIX_BACKUP)) { - if (LOGD) Slog.d(TAG, "recovering " + name); + if (LOGD) Log.d(TAG, "recovering " + name); final File backupFile = new File(mBasePath, name); final File file = new File( @@ -120,7 +121,7 @@ public class FileRotator { backupFile.renameTo(file); } else if (name.endsWith(SUFFIX_NO_BACKUP)) { - if (LOGD) Slog.d(TAG, "recovering " + name); + if (LOGD) Log.d(TAG, "recovering " + name); final File noBackupFile = new File(mBasePath, name); final File file = new File( @@ -231,7 +232,7 @@ public class FileRotator { * if the write fails. */ private void rewriteSingle(Rewriter rewriter, String name) throws IOException { - if (LOGD) Slog.d(TAG, "rewriting " + name); + if (LOGD) Log.d(TAG, "rewriting " + name); final File file = new File(mBasePath, name); final File backupFile; @@ -291,7 +292,7 @@ public class FileRotator { // read file when it overlaps if (info.startMillis <= matchEndMillis && matchStartMillis <= info.endMillis) { - if (LOGD) Slog.d(TAG, "reading matching " + name); + if (LOGD) Log.d(TAG, "reading matching " + name); final File file = new File(mBasePath, name); readFile(file, reader); @@ -348,7 +349,7 @@ public class FileRotator { if (info.isActive()) { if (info.startMillis <= rotateBefore) { // found active file; rotate if old enough - if (LOGD) Slog.d(TAG, "rotating " + name); + if (LOGD) Log.d(TAG, "rotating " + name); info.endMillis = currentTimeMillis; @@ -358,7 +359,7 @@ public class FileRotator { } } else if (info.endMillis <= deleteBefore) { // found rotated file; delete if old enough - if (LOGD) Slog.d(TAG, "deleting " + name); + if (LOGD) Log.d(TAG, "deleting " + name); final File file = new File(mBasePath, name); file.delete(); @@ -383,7 +384,10 @@ public class FileRotator { writer.write(bos); bos.flush(); } finally { - FileUtils.sync(fos); + try { + fos.getFD().sync(); + } catch (IOException e) { + } IoUtils.closeQuietly(bos); } } diff --git a/core/java/com/android/internal/util/ScreenshotHelper.java b/core/java/com/android/internal/util/ScreenshotHelper.java index f46223ac8769..d3c3917cd791 100644 --- a/core/java/com/android/internal/util/ScreenshotHelper.java +++ b/core/java/com/android/internal/util/ScreenshotHelper.java @@ -71,11 +71,11 @@ public class ScreenshotHelper { if (in.readInt() == 1) { mBitmapBundle = in.readBundle(getClass().getClassLoader()); - mBoundsInScreen = in.readParcelable(Rect.class.getClassLoader()); - mInsets = in.readParcelable(Insets.class.getClassLoader()); + mBoundsInScreen = in.readParcelable(Rect.class.getClassLoader(), android.graphics.Rect.class); + mInsets = in.readParcelable(Insets.class.getClassLoader(), android.graphics.Insets.class); mTaskId = in.readInt(); mUserId = in.readInt(); - mTopComponent = in.readParcelable(ComponentName.class.getClassLoader()); + mTopComponent = in.readParcelable(ComponentName.class.getClassLoader(), android.content.ComponentName.class); } } diff --git a/core/java/com/android/internal/view/IInputMethod.aidl b/core/java/com/android/internal/view/IInputMethod.aidl index 402fa64036a0..6a626ee6eb8f 100644 --- a/core/java/com/android/internal/view/IInputMethod.aidl +++ b/core/java/com/android/internal/view/IInputMethod.aidl @@ -53,8 +53,6 @@ oneway interface IInputMethod { void setSessionEnabled(IInputMethodSession session, boolean enabled); - void revokeSession(IInputMethodSession session); - void showSoftInput(in IBinder showInputToken, int flags, in ResultReceiver resultReceiver); void hideSoftInput(in IBinder hideInputToken, int flags, in ResultReceiver resultReceiver); diff --git a/core/java/com/android/internal/view/RootViewSurfaceTaker.java b/core/java/com/android/internal/view/RootViewSurfaceTaker.java index efd5fb2e1edf..4b89bf5082ba 100644 --- a/core/java/com/android/internal/view/RootViewSurfaceTaker.java +++ b/core/java/com/android/internal/view/RootViewSurfaceTaker.java @@ -15,11 +15,12 @@ */ package com.android.internal.view; +import android.annotation.NonNull; import android.annotation.Nullable; import android.view.InputQueue; import android.view.PendingInsetsController; import android.view.SurfaceHolder; -import android.view.WindowInsetsController; +import android.window.WindowOnBackInvokedDispatcher; /** hahahah */ public interface RootViewSurfaceTaker { @@ -30,4 +31,6 @@ public interface RootViewSurfaceTaker { InputQueue.Callback willYouTakeTheInputQueue(); void onRootViewScrollYChanged(int scrollY); @Nullable PendingInsetsController providePendingInsetsController(); + /** @hide */ + @NonNull WindowOnBackInvokedDispatcher provideWindowOnBackInvokedDispatcher(); } diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java index f91776eaf259..f8ccde4cf4d9 100644 --- a/core/java/com/android/internal/widget/LockPatternUtils.java +++ b/core/java/com/android/internal/widget/LockPatternUtils.java @@ -1028,15 +1028,6 @@ public class LockPatternUtils { } /** - * @return Whether tactile feedback for the pattern is enabled. - */ - @UnsupportedAppUsage - public boolean isTactileFeedbackEnabled() { - return Settings.System.getIntForUser(mContentResolver, - Settings.System.HAPTIC_FEEDBACK_ENABLED, 1, UserHandle.USER_CURRENT) != 0; - } - - /** * Set and store the lockout deadline, meaning the user can't attempt their unlock * pattern until the deadline has passed. * @return the chosen deadline. diff --git a/core/java/com/android/internal/widget/LockPatternView.java b/core/java/com/android/internal/widget/LockPatternView.java index 3994fbdca2df..2b6b933c6886 100644 --- a/core/java/com/android/internal/widget/LockPatternView.java +++ b/core/java/com/android/internal/widget/LockPatternView.java @@ -139,7 +139,6 @@ public class LockPatternView extends View { private boolean mInputEnabled = true; @UnsupportedAppUsage private boolean mInStealthMode = false; - private boolean mEnableHapticFeedback = true; @UnsupportedAppUsage private boolean mPatternInProgress = false; private boolean mFadePattern = true; @@ -401,13 +400,6 @@ public class LockPatternView extends View { } /** - * @return Whether the view has tactile feedback enabled. - */ - public boolean isTactileFeedbackEnabled() { - return mEnableHapticFeedback; - } - - /** * Set whether the view is in stealth mode. If true, there will be no * visible feedback as the user enters the pattern. * @@ -427,17 +419,6 @@ public class LockPatternView extends View { } /** - * Set whether the view will use tactile feedback. If true, there will be - * tactile feedback as the user enters the pattern. - * - * @param tactileFeedbackEnabled Whether tactile feedback is enabled - */ - @UnsupportedAppUsage - public void setTactileFeedbackEnabled(boolean tactileFeedbackEnabled) { - mEnableHapticFeedback = tactileFeedbackEnabled; - } - - /** * Set the call back for pattern detection. * @param onPatternListener The call back. */ @@ -783,11 +764,9 @@ public class LockPatternView extends View { addCellToPattern(fillInGapCell); } addCellToPattern(cell); - if (mEnableHapticFeedback) { - performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY, - HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING - | HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING); - } + performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY, + HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING + | HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING); return cell; } return null; @@ -1462,7 +1441,7 @@ public class LockPatternView extends View { return new SavedState(superState, patternString, mPatternDisplayMode.ordinal(), - mInputEnabled, mInStealthMode, mEnableHapticFeedback); + mInputEnabled, mInStealthMode); } @Override @@ -1475,7 +1454,6 @@ public class LockPatternView extends View { mPatternDisplayMode = DisplayMode.values()[ss.getDisplayMode()]; mInputEnabled = ss.isInputEnabled(); mInStealthMode = ss.isInStealthMode(); - mEnableHapticFeedback = ss.isTactileFeedbackEnabled(); } /** @@ -1487,20 +1465,18 @@ public class LockPatternView extends View { private final int mDisplayMode; private final boolean mInputEnabled; private final boolean mInStealthMode; - private final boolean mTactileFeedbackEnabled; /** * Constructor called from {@link LockPatternView#onSaveInstanceState()} */ @UnsupportedAppUsage private SavedState(Parcelable superState, String serializedPattern, int displayMode, - boolean inputEnabled, boolean inStealthMode, boolean tactileFeedbackEnabled) { + boolean inputEnabled, boolean inStealthMode) { super(superState); mSerializedPattern = serializedPattern; mDisplayMode = displayMode; mInputEnabled = inputEnabled; mInStealthMode = inStealthMode; - mTactileFeedbackEnabled = tactileFeedbackEnabled; } /** @@ -1513,7 +1489,6 @@ public class LockPatternView extends View { mDisplayMode = in.readInt(); mInputEnabled = (Boolean) in.readValue(null); mInStealthMode = (Boolean) in.readValue(null); - mTactileFeedbackEnabled = (Boolean) in.readValue(null); } public String getSerializedPattern() { @@ -1532,10 +1507,6 @@ public class LockPatternView extends View { return mInStealthMode; } - public boolean isTactileFeedbackEnabled(){ - return mTactileFeedbackEnabled; - } - @Override public void writeToParcel(Parcel dest, int flags) { super.writeToParcel(dest, flags); @@ -1543,7 +1514,6 @@ public class LockPatternView extends View { dest.writeInt(mDisplayMode); dest.writeValue(mInputEnabled); dest.writeValue(mInStealthMode); - dest.writeValue(mTactileFeedbackEnabled); } @SuppressWarnings({ "unused", "hiding" }) // Found using reflection diff --git a/core/java/com/android/internal/widget/SlidingTab.java b/core/java/com/android/internal/widget/SlidingTab.java index 5e6f3a46de7d..11566d9a026d 100644 --- a/core/java/com/android/internal/widget/SlidingTab.java +++ b/core/java/com/android/internal/widget/SlidingTab.java @@ -22,10 +22,9 @@ import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Rect; import android.graphics.drawable.Drawable; -import android.media.AudioAttributes; -import android.os.UserHandle; +import android.os.VibrationAttributes; +import android.os.VibrationEffect; import android.os.Vibrator; -import android.provider.Settings; import android.util.AttributeSet; import android.util.Log; import android.view.Gravity; @@ -68,10 +67,8 @@ public class SlidingTab extends ViewGroup { private boolean mHoldLeftOnTransition = true; private boolean mHoldRightOnTransition = true; - private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder() - .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) - .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION) - .build(); + private static final VibrationAttributes TOUCH_VIBRATION_ATTRIBUTES = + VibrationAttributes.createForUsage(VibrationAttributes.USAGE_TOUCH); private OnTriggerListener mOnTriggerListener; private int mGrabbedState = OnTriggerListener.NO_HANDLE; @@ -834,16 +831,12 @@ public class SlidingTab extends ViewGroup { * Triggers haptic feedback. */ private synchronized void vibrate(long duration) { - final boolean hapticEnabled = Settings.System.getIntForUser( - mContext.getContentResolver(), Settings.System.HAPTIC_FEEDBACK_ENABLED, 1, - UserHandle.USER_CURRENT) != 0; - if (hapticEnabled) { - if (mVibrator == null) { - mVibrator = (android.os.Vibrator) getContext() - .getSystemService(Context.VIBRATOR_SERVICE); - } - mVibrator.vibrate(duration, VIBRATION_ATTRIBUTES); + if (mVibrator == null) { + mVibrator = getContext().getSystemService(Vibrator.class); } + mVibrator.vibrate( + VibrationEffect.createOneShot(duration, VibrationEffect.DEFAULT_AMPLITUDE), + TOUCH_VIBRATION_ATTRIBUTES); } /** diff --git a/core/jni/Android.bp b/core/jni/Android.bp index a3ac472bb900..430d84ebb3e1 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -124,7 +124,6 @@ cc_library_shared { "android_view_PointerIcon.cpp", "android_view_Surface.cpp", "android_view_SurfaceControl.cpp", - "android_view_SurfaceControlFpsListener.cpp", "android_view_SurfaceControlHdrLayerInfoListener.cpp", "android_graphics_BLASTBufferQueue.cpp", "android_view_SurfaceSession.cpp", @@ -255,7 +254,6 @@ cc_library_shared { "libandroidicu", "libbattery", "libbpf_android", - "libnetdbpf", "libnetdutils", "libmemtrack", "libandroidfw", diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index 21ec64bba931..f4296becf484 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -123,7 +123,6 @@ extern int register_android_view_InputApplicationHandle(JNIEnv* env); extern int register_android_view_InputWindowHandle(JNIEnv* env); extern int register_android_view_Surface(JNIEnv* env); extern int register_android_view_SurfaceControl(JNIEnv* env); -extern int register_android_view_SurfaceControlFpsListener(JNIEnv* env); extern int register_android_view_SurfaceControlHdrLayerInfoListener(JNIEnv* env); extern int register_android_view_SurfaceSession(JNIEnv* env); extern int register_android_view_CompositionSamplingListener(JNIEnv* env); @@ -1546,7 +1545,6 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_android_view_InputWindowHandle), REG_JNI(register_android_view_Surface), REG_JNI(register_android_view_SurfaceControl), - REG_JNI(register_android_view_SurfaceControlFpsListener), REG_JNI(register_android_view_SurfaceControlHdrLayerInfoListener), REG_JNI(register_android_view_SurfaceSession), REG_JNI(register_android_view_CompositionSamplingListener), diff --git a/core/jni/OWNERS b/core/jni/OWNERS index 4357729095af..9a460f58effe 100644 --- a/core/jni/OWNERS +++ b/core/jni/OWNERS @@ -76,6 +76,9 @@ per-file AndroidRuntime.cpp = file:/graphics/java/android/graphics/OWNERS per-file AndroidRuntime.cpp = calin@google.com, ngeoffray@google.com, oth@google.com # Although marked "view" this is mostly graphics stuff per-file android_view_* = file:/graphics/java/android/graphics/OWNERS +# File used for Android Studio layoutlib +per-file LayoutlibLoader.cpp = file:/graphics/java/android/graphics/OWNERS +per-file LayoutlibLoader.cpp = diegoperez@google.com, jgaillard@google.com # Verity per-file com_android_internal_security_Verity* = ebiggers@google.com, victorhsieh@google.com diff --git a/core/jni/android_media_AudioAttributes.cpp b/core/jni/android_media_AudioAttributes.cpp index 423ef7cd980b..d7fc1cc007a6 100644 --- a/core/jni/android_media_AudioAttributes.cpp +++ b/core/jni/android_media_AudioAttributes.cpp @@ -57,7 +57,7 @@ static struct { jmethodID setUsage; jmethodID setSystemUsage; jmethodID setInternalCapturePreset; - jmethodID setContentType; + jmethodID setInternalContentType; jmethodID replaceFlags; jmethodID addTag; } gAudioAttributesBuilderMethods; @@ -127,7 +127,7 @@ static jint nativeAudioAttributesToJavaAudioAttributes( gAudioAttributesBuilderMethods.setInternalCapturePreset, attributes.source); env->CallObjectMethod(jAttributeBuilder.get(), - gAudioAttributesBuilderMethods.setContentType, + gAudioAttributesBuilderMethods.setInternalContentType, attributes.content_type); env->CallObjectMethod(jAttributeBuilder.get(), gAudioAttributesBuilderMethods.replaceFlags, @@ -202,9 +202,9 @@ int register_android_media_AudioAttributes(JNIEnv *env) gAudioAttributesBuilderMethods.setInternalCapturePreset = GetMethodIDOrDie( env, audioAttributesBuilderClass, "setInternalCapturePreset", "(I)Landroid/media/AudioAttributes$Builder;"); - gAudioAttributesBuilderMethods.setContentType = GetMethodIDOrDie( - env, audioAttributesBuilderClass, "setContentType", - "(I)Landroid/media/AudioAttributes$Builder;"); + gAudioAttributesBuilderMethods.setInternalContentType = + GetMethodIDOrDie(env, audioAttributesBuilderClass, "setInternalContentType", + "(I)Landroid/media/AudioAttributes$Builder;"); gAudioAttributesBuilderMethods.replaceFlags = GetMethodIDOrDie( env, audioAttributesBuilderClass, "replaceFlags", "(I)Landroid/media/AudioAttributes$Builder;"); diff --git a/core/jni/android_text_Hyphenator.cpp b/core/jni/android_text_Hyphenator.cpp index 3651dbdb3fa3..571a8e2e64be 100644 --- a/core/jni/android_text_Hyphenator.cpp +++ b/core/jni/android_text_Hyphenator.cpp @@ -127,6 +127,7 @@ static void init() { addHyphenator("or", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Oriya addHyphenator("pa", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Punjabi addHyphenator("pt", 2, 3); // Portuguese + addHyphenator("ru", 2, 2); // Russian addHyphenator("sk", 2, 2); // Slovak addHyphenator("sl", 2, 2); // Slovenian addHyphenator("sq", 2, 2); // Albanian diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index dd5af0435acc..d5470cc5a888 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -91,6 +91,7 @@ static struct { jfieldID density; jfieldID secure; jfieldID deviceProductInfo; + jfieldID installOrientation; } gStaticDisplayInfoClassInfo; static struct { @@ -1210,6 +1211,8 @@ static jobject nativeGetStaticDisplayInfo(JNIEnv* env, jclass clazz, jobject tok env->SetBooleanField(object, gStaticDisplayInfoClassInfo.secure, info.secure); env->SetObjectField(object, gStaticDisplayInfoClassInfo.deviceProductInfo, convertDeviceProductInfoToJavaObject(env, info.deviceProductInfo)); + env->SetIntField(object, gStaticDisplayInfoClassInfo.installOrientation, + static_cast<uint32_t>(info.installOrientation)); return object; } @@ -2152,6 +2155,8 @@ int register_android_view_SurfaceControl(JNIEnv* env) gStaticDisplayInfoClassInfo.deviceProductInfo = GetFieldIDOrDie(env, infoClazz, "deviceProductInfo", "Landroid/hardware/display/DeviceProductInfo;"); + gStaticDisplayInfoClassInfo.installOrientation = + GetFieldIDOrDie(env, infoClazz, "installOrientation", "I"); jclass dynamicInfoClazz = FindClassOrDie(env, "android/view/SurfaceControl$DynamicDisplayInfo"); gDynamicDisplayInfoClassInfo.clazz = MakeGlobalRefOrDie(env, dynamicInfoClazz); diff --git a/core/jni/android_window_WindowInfosListener.cpp b/core/jni/android_window_WindowInfosListener.cpp index 1381de567b55..08c9f20b0815 100644 --- a/core/jni/android_window_WindowInfosListener.cpp +++ b/core/jni/android_window_WindowInfosListener.cpp @@ -72,25 +72,29 @@ struct WindowInfosListener : public gui::WindowInfosListener { return; } - jobjectArray jWindowHandlesArray = - env->NewObjectArray(windowInfos.size(), gInputWindowHandleClass, nullptr); + ScopedLocalRef<jobjectArray> + jWindowHandlesArray(env, + env->NewObjectArray(windowInfos.size(), gInputWindowHandleClass, + nullptr)); for (int i = 0; i < windowInfos.size(); i++) { ScopedLocalRef<jobject> jWindowHandle(env, android_view_InputWindowHandle_fromWindowInfo(env, windowInfos[i])); - env->SetObjectArrayElement(jWindowHandlesArray, i, jWindowHandle.get()); + env->SetObjectArrayElement(jWindowHandlesArray.get(), i, jWindowHandle.get()); } - jobjectArray jDisplayInfoArray = - env->NewObjectArray(displayInfos.size(), gDisplayInfoClassInfo.clazz, nullptr); + ScopedLocalRef<jobjectArray> + jDisplayInfoArray(env, + env->NewObjectArray(displayInfos.size(), + gDisplayInfoClassInfo.clazz, nullptr)); for (int i = 0; i < displayInfos.size(); i++) { ScopedLocalRef<jobject> jDisplayInfo(env, fromDisplayInfo(env, displayInfos[i])); - env->SetObjectArrayElement(jDisplayInfoArray, i, jDisplayInfo.get()); + env->SetObjectArrayElement(jDisplayInfoArray.get(), i, jDisplayInfo.get()); } - env->CallVoidMethod(listener, gListenerClassInfo.onWindowInfosChanged, jWindowHandlesArray, - jDisplayInfoArray); + env->CallVoidMethod(listener, gListenerClassInfo.onWindowInfosChanged, + jWindowHandlesArray.get(), jDisplayInfoArray.get()); env->DeleteGlobalRef(listener); if (env->ExceptionCheck()) { diff --git a/core/proto/OWNERS b/core/proto/OWNERS index 78650ed34813..a4463e4e96c5 100644 --- a/core/proto/OWNERS +++ b/core/proto/OWNERS @@ -18,7 +18,8 @@ per-file usagestatsservice.proto, usagestatsservice_v2.proto = file:/core/java/a per-file apphibernationservice.proto = file:/core/java/android/apphibernation/OWNERS # Biometrics -kchyn@google.com +jaggies@google.com +jbolinger@google.com # Launcher hyunyoungs@google.com diff --git a/core/proto/android/server/vibrator/vibratormanagerservice.proto b/core/proto/android/server/vibrator/vibratormanagerservice.proto index fbe2170ea51c..2f2158d4d5a0 100644 --- a/core/proto/android/server/vibrator/vibratormanagerservice.proto +++ b/core/proto/android/server/vibrator/vibratormanagerservice.proto @@ -97,7 +97,7 @@ message VibrationProto { optional int32 status = 6; } -// Next id: 24 +// Next id: 25 message VibratorManagerServiceDumpProto { option (.android.msg_privacy).dest = DEST_AUTOMATIC; repeated int32 vibrator_ids = 1; @@ -106,6 +106,7 @@ message VibratorManagerServiceDumpProto { optional VibrationProto current_external_vibration = 4; optional bool vibrator_under_external_control = 5; optional bool low_power_mode = 6; + optional bool vibrate_on = 24; optional int32 alarm_intensity = 18; optional int32 alarm_default_intensity = 19; optional int32 haptic_feedback_intensity = 7; diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto index 23453876c2d5..11560a5ccd1b 100644 --- a/core/proto/android/server/windowmanagerservice.proto +++ b/core/proto/android/server/windowmanagerservice.proto @@ -225,6 +225,7 @@ message DisplayContentProto { repeated InsetsSourceProviderProto insets_source_providers = 35; optional bool is_sleeping = 36; repeated string sleep_tokens = 37; + repeated .android.graphics.RectProto keep_clear_areas = 38; } @@ -443,6 +444,7 @@ message WindowStateProto { optional bool force_seamless_rotation = 42; optional bool has_compat_scale = 43; optional float global_scale = 44; + repeated .android.graphics.RectProto keep_clear_areas = 45; } message IdentifierProto { diff --git a/core/proto/android/service/netstats.proto b/core/proto/android/service/netstats.proto index c8cdfddc3985..ba2b6d6bd7e0 100644 --- a/core/proto/android/service/netstats.proto +++ b/core/proto/android/service/netstats.proto @@ -17,15 +17,11 @@ syntax = "proto2"; package android.service; -import "frameworks/base/core/proto/android/privacy.proto"; - option java_multiple_files = true; option java_outer_classname = "NetworkStatsServiceProto"; // Represents dumpsys from NetworkStatsService (netstats). message NetworkStatsServiceDumpProto { - option (android.msg_privacy).dest = DEST_AUTOMATIC; - repeated NetworkInterfaceProto active_interfaces = 1; repeated NetworkInterfaceProto active_uid_interfaces = 2; @@ -45,8 +41,6 @@ message NetworkStatsServiceDumpProto { // Corresponds to NetworkStatsService.mActiveIfaces/mActiveUidIfaces. message NetworkInterfaceProto { - option (android.msg_privacy).dest = DEST_AUTOMATIC; - // Name of the network interface (eg: wlan). optional string interface = 1; @@ -55,26 +49,14 @@ message NetworkInterfaceProto { // Corresponds to NetworkIdentitySet. message NetworkIdentitySetProto { - option (android.msg_privacy).dest = DEST_AUTOMATIC; - repeated NetworkIdentityProto identities = 1; } // Corresponds to NetworkIdentity. message NetworkIdentityProto { - option (android.msg_privacy).dest = DEST_AUTOMATIC; - // Constants from ConnectivityManager.TYPE_*. optional int32 type = 1; - // Full subscriber ID on eng builds. The IMSI is scrubbed on user & userdebug - // builds to only include the info about the GSM network operator (the info - // that uniquely identifies the subscriber is removed). - optional string subscriber_id = 2 [ (android.privacy).dest = DEST_EXPLICIT ]; - - // Name of the network (eg: MyWifi). - optional string network_id = 3 [ (android.privacy).dest = DEST_EXPLICIT ]; - optional bool roaming = 4; optional bool metered = 5; @@ -86,8 +68,6 @@ message NetworkIdentityProto { // Corresponds to NetworkStatsRecorder. message NetworkStatsRecorderProto { - option (android.msg_privacy).dest = DEST_AUTOMATIC; - optional int64 pending_total_bytes = 1; optional NetworkStatsCollectionProto complete_history = 2; @@ -95,15 +75,11 @@ message NetworkStatsRecorderProto { // Corresponds to NetworkStatsCollection. message NetworkStatsCollectionProto { - option (android.msg_privacy).dest = DEST_AUTOMATIC; - repeated NetworkStatsCollectionStatsProto stats = 1; } // Corresponds to NetworkStatsCollection.mStats. message NetworkStatsCollectionStatsProto { - option (android.msg_privacy).dest = DEST_AUTOMATIC; - optional NetworkStatsCollectionKeyProto key = 1; optional NetworkStatsHistoryProto history = 2; @@ -111,8 +87,6 @@ message NetworkStatsCollectionStatsProto { // Corresponds to NetworkStatsCollection.Key. message NetworkStatsCollectionKeyProto { - option (android.msg_privacy).dest = DEST_AUTOMATIC; - optional NetworkIdentitySetProto identity = 1; optional int32 uid = 2; @@ -124,8 +98,6 @@ message NetworkStatsCollectionKeyProto { // Corresponds to NetworkStatsHistory. message NetworkStatsHistoryProto { - option (android.msg_privacy).dest = DEST_AUTOMATIC; - // Duration for this bucket in milliseconds. optional int64 bucket_duration_ms = 1; @@ -134,8 +106,6 @@ message NetworkStatsHistoryProto { // Corresponds to each bucket in NetworkStatsHistory. message NetworkStatsHistoryBucketProto { - option (android.msg_privacy).dest = DEST_AUTOMATIC; - // Bucket start time in milliseconds since epoch. optional int64 bucket_start_ms = 1; diff --git a/core/proto/android/service/usb.proto b/core/proto/android/service/usb.proto index 97097ff91219..c5eaf42784ab 100644 --- a/core/proto/android/service/usb.proto +++ b/core/proto/android/service/usb.proto @@ -196,9 +196,19 @@ message UsbIsHeadsetProto { message UsbPortManagerProto { option (android.msg_privacy).dest = DEST_AUTOMATIC; + enum HalVersion { + V_UNKNOWN = 0; + V1_0 = 10; + V1_1 = 11; + V1_2 = 12; + V1_3 = 13; + V2 = 20; + } + optional bool is_simulation_active = 1; repeated UsbPortInfoProto usb_ports = 2; optional bool enable_usb_data_signaling = 3; + optional HalVersion hal_version = 4; } message UsbPortInfoProto { @@ -254,6 +264,9 @@ message UsbPortStatusProto { optional DataRole data_role = 4; repeated UsbPortStatusRoleCombinationProto role_combinations = 5; optional android.service.ContaminantPresenceStatus contaminant_presence_status = 6; + optional string usb_data_status = 7; + optional bool is_power_transfer_limited = 8; + optional string usb_power_brick_status = 9; } message UsbPortStatusRoleCombinationProto { diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index ae2052b66f9c..3a842ee6e7f3 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -4251,7 +4251,8 @@ <!-- Allows low-level access to setting the keyboard layout. <p>Not for use by third-party applications. - @hide --> + @hide + @TestApi --> <permission android:name="android.permission.SET_KEYBOARD_LAYOUT" android:protectionLevel="signature" /> @@ -4745,6 +4746,12 @@ <permission android:name="android.permission.CAPTURE_AUDIO_HOTWORD" android:protectionLevel="signature|privileged|role" /> + <!-- @SystemApi Allows an application to access the ultrasound content. + <p>Not for use by third-party applications.</p> + @hide --> + <permission android:name="android.permission.ACCESS_ULTRASOUND" + android:protectionLevel="signature|privileged" /> + <!-- Puts an application in the chain of trust for sound trigger operations. Being in the chain of trust allows an application to delegate an identity of a separate entity to the sound trigger system @@ -5122,6 +5129,11 @@ <permission android:name="android.permission.SET_WALLPAPER_COMPONENT" android:protectionLevel="signature|privileged" /> + <!-- @SystemApi Allows applications to set the wallpaper dim amount. + @hide. --> + <permission android:name="android.permission.SET_WALLPAPER_DIM_AMOUNT" + android:protectionLevel="signature|privileged" /> + <!-- @SystemApi Allows applications to read dream settings and dream state. @hide --> <permission android:name="android.permission.READ_DREAM_STATE" @@ -6032,10 +6044,15 @@ <permission android:name="android.permission.MANAGE_TOAST_RATE_LIMITING" android:protectionLevel="signature" /> - <!-- Allows managing the Game Mode - @hide Used internally. --> + <!-- @SystemApi Allows managing the Game Mode + @hide --> <permission android:name="android.permission.MANAGE_GAME_MODE" - android:protectionLevel="signature" /> + android:protectionLevel="signature|privileged" /> + + <!-- @SystemApi Allows accessing the frame rate per second of a given application + @hide --> + <permission android:name="android.permission.ACCESS_FPS_COUNTER" + android:protectionLevel="signature|privileged" /> <!-- @SystemApi Allows the holder to register callbacks to inform the RebootReadinessManager when they are performing reboot-blocking work. @@ -6094,11 +6111,21 @@ <permission android:name="android.permission.CAPTURE_BLACKOUT_CONTENT" android:protectionLevel="signature" /> - <!-- @SystemApi Allows an application to query over global data in AppSearch. + <!-- @SystemApi Allows an application to query over global data in AppSearch. @hide --> <permission android:name="android.permission.READ_GLOBAL_APP_SEARCH_DATA" android:protectionLevel="internal|role" /> + <!-- Allows an application to query over global data in AppSearch that's visible to the + ASSISTANT role. --> + <permission android:name="android.permission.READ_ASSISTANT_APP_SEARCH_DATA" + android:protectionLevel="internal|role" /> + + <!-- Allows an application to query over global data in AppSearch that's visible to the + HOME role. --> + <permission android:name="android.permission.READ_HOME_APP_SEARCH_DATA" + android:protectionLevel="internal|role" /> + <!-- @SystemApi Allows an application to create virtual devices in VirtualDeviceManager. @hide --> <permission android:name="android.permission.CREATE_VIRTUAL_DEVICE" @@ -6112,7 +6139,7 @@ <permission android:name="android.permission.SEND_SAFETY_CENTER_UPDATE" android:protectionLevel="signature|privileged" /> - <!-- Allows an application to launch device manager setup screens. + <!-- @SystemApi Allows an application to launch device manager setup screens. <p>Not for use by third-party applications. @hide --> @@ -6140,6 +6167,19 @@ <permission android:name="android.permission.MANAGE_SAFETY_CENTER" android:protectionLevel="internal|installer|role" /> + <!-- @SystemApi Allows an application to access the AmbientContextEvent service. + @hide + --> + <permission android:name="android.permission.ACCESS_AMBIENT_CONTEXT_EVENT" + android:protectionLevel="internal|role"/> + + <!-- @SystemApi Required by a AmbientContextEventDetectionService + to ensure that only the service with this permission can bind to it. + @hide + --> + <permission android:name="android.permission.BIND_AMBIENT_CONTEXT_DETECTION_SERVICE" + android:protectionLevel="signature" /> + <!-- Attribution for Geofencing service. --> <attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/> <!-- Attribution for Country Detector. --> diff --git a/core/res/OWNERS b/core/res/OWNERS index b18a9896cb2a..4bea4d55e5ef 100644 --- a/core/res/OWNERS +++ b/core/res/OWNERS @@ -34,3 +34,7 @@ per-file res/values/dimens_car.xml = file:/platform/packages/services/Car:/OWNER # Wear per-file res/*-watch/* = file:/platform/frameworks/opt/wear:/OWNERS + +# PowerProfile +per-file res/xml/power_profile.xml = file:/BATTERY_STATS_OWNERS +per-file res/xml/power_profile_test.xml = file:/BATTERY_STATS_OWNERS diff --git a/core/res/res/drawable/ic_ime_nav_back.xml b/core/res/res/drawable/ic_ime_nav_back.xml new file mode 100644 index 000000000000..ca329aa64bd4 --- /dev/null +++ b/core/res/res/drawable/ic_ime_nav_back.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2022 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="28dp" + android:height="28dp" + android:autoMirrored="true" + android:viewportWidth="28" + android:viewportHeight="28"> + <path + android:pathData="M16.78,10.03l-3.97,3.97l3.97,3.97l-1.06,1.06l-5.03,-5.03l5.03,-5.03z" + android:fillColor="#FFFFFFFF" /> +</vector>
\ No newline at end of file diff --git a/core/res/res/drawable/ic_ime_switcher.xml b/core/res/res/drawable/ic_ime_switcher.xml new file mode 100644 index 000000000000..6c3b76627323 --- /dev/null +++ b/core/res/res/drawable/ic_ime_switcher.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2022 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="20dp" + android:height="20dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:pathData="M19,7h2v2h-2V7zM15,7h2v2h-2V7zM3,7h2v2H3V7zM7,7h2v2H7V7zM11,7h2v2h-2V7zM19,11h2v2h-2V11zM15,11h2v2h-2V11zM3,11h2v2H3V11zM7,11h2v2H7V11zM11,11h2v2h-2V11zM7,15h10v2H7V15z" + android:fillColor="#FFFFFFFF"/> +</vector> diff --git a/core/res/res/layout/input_method_nav_back.xml b/core/res/res/layout/input_method_nav_back.xml new file mode 100644 index 000000000000..671766aec281 --- /dev/null +++ b/core/res/res/layout/input_method_nav_back.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2022 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<android.inputmethodservice.navigationbar.KeyButtonView + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/input_method_nav_back" + android:layout_width="@dimen/input_method_navigation_key_width" + android:layout_height="match_parent" + android:layout_weight="0" + android:scaleType="center" + android:contentDescription="@string/input_method_nav_back_button_desc" + android:paddingStart="@dimen/input_method_navigation_key_padding" + android:paddingEnd="@dimen/input_method_navigation_key_padding" + /> diff --git a/core/res/res/layout/input_method_nav_home_handle.xml b/core/res/res/layout/input_method_nav_home_handle.xml new file mode 100644 index 000000000000..501f5126aff8 --- /dev/null +++ b/core/res/res/layout/input_method_nav_home_handle.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2022 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<android.inputmethodservice.navigationbar.NavigationHandle + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/input_method_nav_home_handle" + android:layout_width="72dp" + android:layout_height="match_parent" + android:layout_weight="0" + android:paddingStart="@dimen/input_method_navigation_key_padding" + android:paddingEnd="@dimen/input_method_navigation_key_padding" + /> diff --git a/core/res/res/layout/input_method_nav_ime_switcher.xml b/core/res/res/layout/input_method_nav_ime_switcher.xml new file mode 100644 index 000000000000..b571ba927837 --- /dev/null +++ b/core/res/res/layout/input_method_nav_ime_switcher.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2022 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<android.inputmethodservice.navigationbar.KeyButtonView + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/input_method_nav_ime_switcher" + android:layout_width="@dimen/input_method_navigation_key_width" + android:layout_height="match_parent" + android:layout_weight="0" + android:contentDescription="@string/input_method_ime_switch_button_desc" + android:scaleType="center" + android:paddingStart="@dimen/input_method_navigation_key_padding" + android:paddingEnd="@dimen/input_method_navigation_key_padding" + /> diff --git a/core/res/res/layout/input_method_navigation_bar.xml b/core/res/res/layout/input_method_navigation_bar.xml new file mode 100644 index 000000000000..ce402fbce891 --- /dev/null +++ b/core/res/res/layout/input_method_navigation_bar.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2022 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<android.inputmethodservice.navigationbar.NavigationBarView + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/input_method_navigation_bar_view" + android:layout_height="match_parent" + android:layout_width="match_parent" + android:clipChildren="false" + android:clipToPadding="false"> + + <android.inputmethodservice.navigationbar.NavigationBarInflaterView + android:id="@+id/input_method_nav_inflater" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:clipChildren="false" + android:clipToPadding="false" /> + +</android.inputmethodservice.navigationbar.NavigationBarView> diff --git a/core/res/res/layout/input_method_navigation_layout.xml b/core/res/res/layout/input_method_navigation_layout.xml new file mode 100644 index 000000000000..05e750ad34ff --- /dev/null +++ b/core/res/res/layout/input_method_navigation_layout.xml @@ -0,0 +1,57 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2022 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<FrameLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_marginStart="@dimen/input_method_rounded_corner_content_padding" + android:layout_marginEnd="@dimen/input_method_rounded_corner_content_padding" + android:paddingStart="@dimen/input_method_nav_content_padding" + android:paddingEnd="@dimen/input_method_nav_content_padding" + android:clipChildren="false" + android:clipToPadding="false" + android:id="@+id/input_method_nav_horizontal"> + + <FrameLayout + android:id="@+id/input_method_nav_buttons" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:clipChildren="false" + android:clipToPadding="false"> + + <LinearLayout + android:id="@+id/input_method_nav_ends_group" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="horizontal" + android:clipToPadding="false" + android:clipChildren="false" /> + + <LinearLayout + android:id="@+id/input_method_nav_center_group" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:layout_gravity="center" + android:gravity="center" + android:orientation="horizontal" + android:clipToPadding="false" + android:clipChildren="false" /> + + </FrameLayout> + +</FrameLayout> diff --git a/core/res/res/values-sw360dp/dimens.xml b/core/res/res/values-sw360dp/dimens.xml index 4c74264c676a..00de60efbeb2 100644 --- a/core/res/res/values-sw360dp/dimens.xml +++ b/core/res/res/values-sw360dp/dimens.xml @@ -18,4 +18,8 @@ --> <resources> <dimen name="chooser_grid_padding">16dp</dimen> + + <!-- Copied from SysUI's @dimen/navigation_key_width for the embedded nav bar in the IME --> + <dimen name="input_method_navigation_key_width">80dip</dimen> + </resources> diff --git a/core/res/res/values-sw372dp/dimens.xml b/core/res/res/values-sw372dp/dimens.xml new file mode 100644 index 000000000000..cb29a1944a22 --- /dev/null +++ b/core/res/res/values-sw372dp/dimens.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2022 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<resources> + <!-- Copied from SysUI's @dimen/nav_content_padding for the embedded nav bar in the IME --> + <dimen name="input_method_nav_content_padding">8dp</dimen> +</resources> diff --git a/core/res/res/values-sw600dp/dimens.xml b/core/res/res/values-sw600dp/dimens.xml index e8f15fd022d7..4c70ea32bb5b 100644 --- a/core/res/res/values-sw600dp/dimens.xml +++ b/core/res/res/values-sw600dp/dimens.xml @@ -41,6 +41,11 @@ <!-- Size of lockscreen outerring on unsecure unlock LockScreen --> <dimen name="keyguard_lockscreen_outerring_diameter">364dp</dimen> + <!-- Copied from SysUI's @dimen/navigation_key_width for the embedded nav bar in the IME --> + <dimen name="input_method_navigation_key_width">128dp</dimen> + <!-- Copied from SysUI's @dimen/navigation_key_padding for the embedded nav bar in the IME --> + <dimen name="input_method_navigation_key_padding">25dp</dimen> + <!-- Height of FaceUnlock view in keyguard --> <dimen name="face_unlock_height">430dip</dimen> diff --git a/core/res/res/values-sw900dp/dimens.xml b/core/res/res/values-sw900dp/dimens.xml index 11092b2cb9e9..9ec420453f6b 100644 --- a/core/res/res/values-sw900dp/dimens.xml +++ b/core/res/res/values-sw900dp/dimens.xml @@ -24,4 +24,12 @@ the same as @dimen/navigation_bar_height --> <dimen name="navigation_bar_height_landscape">56dp</dimen> + <!-- Copied from SysUI's @dimen/navigation_key_width for the embedded nav bar in the IME. --> + <dimen name="input_method_navigation_key_width">80dp</dimen> + <!-- Copied from SysUI's @dimen/navigation_key_padding for the embedded nav bar in the IME. --> + <dimen name="input_method_navigation_key_padding">0dp</dimen> + <!-- Copied from SysUI's @dimen/key_button_ripple_max_width for the embedded nav bar in the + IME. --> + <dimen name="input_method_nav_key_button_ripple_max_width">76dp</dimen> + </resources> diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index e232d858bb43..8696f5a94750 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -3347,6 +3347,14 @@ <p>Note that this flag will only be respected if the View's Outline returns true from {@link android.graphics.Outline#canClip()}. --> <attr name="clipToOutline" format="boolean" /> + + <!-- <p> Sets a preference to keep the bounds of this view clear from floating windows + above this view's window. This informs the system that the view is considered a vital + area for the user and that ideally it should not be covered. Setting this is only + appropriate for UI where the user would likely take action to uncover it. + <p>The system will try to respect this, but when not possible will ignore it. + See {@link android.view.View#setPreferKeepClear}. --> + <attr name="preferKeepClear" format="boolean" /> </declare-styleable> <!-- Attributes that can be assigned to a tag for a particular View. --> @@ -5030,6 +5038,10 @@ <attr name="fontFeatureSettings" format="string" /> <!-- Font variation settings. --> <attr name="fontVariationSettings" format="string"/> + <!-- Specifies the strictness of line-breaking rules applied within an element. --> + <attr name="lineBreakStyle" /> + <!-- Specifies the phrase-based breaking opportunities. --> + <attr name="lineBreakWordStyle" /> </declare-styleable> <declare-styleable name="TextClock"> <!-- Specifies the formatting pattern used to show the time and/or date @@ -5428,6 +5440,13 @@ <!-- ndicates breaking text with the most strictest line-breaking rules. --> <enum name="strict" value="3" /> </attr> + <!-- Specify the phrase-based line break can be used when calculating the text wrapping.--> + <attr name="lineBreakWordStyle"> + <!-- No line break word style specific. --> + <enum name="none" value="0" /> + <!-- Specify the phrase based breaking. --> + <enum name="phrase" value="1" /> + </attr> <!-- Specify the type of auto-size. Note that this feature is not supported by EditText, works only for TextView. --> <attr name="autoSizeTextType" format="enum"> @@ -9376,11 +9395,12 @@ <attr name="canPauseRecording" format="boolean" /> </declare-styleable> - <!-- Use <code>tv-iapp</code> as the root tag of the XML resource that describes a - {@link android.media.tv.interactive.TvIAppService}, which is referenced from its - {@link android.media.tv.interactive.TvIAppService#SERVICE_META_DATA} meta-data entry. - Described here are the attributes that can be included in that tag. --> - <declare-styleable name="TvIAppService"> + <!-- Use <code>tv-interactive-app</code> as the root tag of the XML resource that describes a + {@link android.media.tv.interactive.TvInteractiveAppService}, which is referenced + from its + {@link android.media.tv.interactive.TvInteractiveAppService#SERVICE_META_DATA} + meta-data entry. Described here are the attributes that can be included in that tag. --> + <declare-styleable name="TvInteractiveAppService"> <!-- The interactive app types that the TV interactive app service supports. Reference to a string array resource that describes the supported types, e.g. HbbTv, Ginga. --> diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index a5954338910d..3a2fb6e70ba8 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -401,6 +401,15 @@ and before. --> <attr name="sharedUserMaxSdkVersion" format="integer" /> + <!-- Whether the application should inherit all AndroidKeyStore keys of its shared user + group in the case of leaving its shared user ID in an upgrade. If set to false, all + AndroidKeyStore keys will remain in the shared user group, and the application will no + longer have access to those keys after the upgrade. If set to true, all AndroidKeyStore + keys owned by the shared user group will be transferred to the upgraded application; + other applications in the shared user group will no longer have access to those keys + after the migration. The default value is false if not explicitly set. --> + <attr name="inheritKeyStoreKeys" format="boolean" /> + <!-- Internal version code. This is the number used to determine whether one version is more recent than another: it has no other meaning than that higher numbers are more recent. You could use this number to @@ -1677,6 +1686,7 @@ <attr name="sharedUserId" /> <attr name="sharedUserLabel" /> <attr name="sharedUserMaxSdkVersion" /> + <attr name="inheritKeyStoreKeys" /> <attr name="installLocation" /> <attr name="isolatedSplits" /> <attr name="isFeatureSplit" /> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index f4b7b73f4a6e..c0c8618aaf96 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -2135,6 +2135,11 @@ <string name="config_systemAppProtectionService" translatable="false"></string> <!-- The name of the package that will hold the system calendar sync manager role. --> <string name="config_systemAutomotiveCalendarSyncManager" translatable="false"></string> + <!-- The name of the package that will hold the default automotive navigation role. --> + <string name="config_defaultAutomotiveNavigation" translatable="false"></string> + + <!-- The name of the package that will handle updating the device management role. --> + <string name="config_deviceManagerUpdater" translatable="false"></string> <!-- The name of the package that will be allowed to change its components' label/icon. --> <string name="config_overrideComponentUiPackage" translatable="false">com.android.stk</string> @@ -4072,6 +4077,12 @@ --> <string name="config_defaultRotationResolverService" translatable="false"></string> + <!-- The component name for the default system AmbientContextEvent detection service. + This service must be trusted, as it can be activated without explicit consent of the user. + See android.service.ambientcontext.AmbientContextDetectionService. + --> + <string name="config_defaultAmbientContextDetectionService" translatable="false"></string> + <!-- The component name for the system-wide captions service. This service must be trusted, as it controls part of the UI of the volume bar. Example: "com.android.captions/.SystemCaptionsService" @@ -4242,7 +4253,7 @@ <string translatable="false" name="config_defaultRingtoneVibrationSound"></string> <!-- Default number of notifications from the same app before they are automatically grouped by the OS --> - <integer translatable="false" name="config_autoGroupAtCount">4</integer> + <integer translatable="false" name="config_autoGroupAtCount">2</integer> <!-- The OEM specified sensor type for the lift trigger to launch the camera app. --> <integer name="config_cameraLiftTriggerSensorType">-1</integer> @@ -5607,4 +5618,7 @@ <!-- Flag indicating if help links for Settings app should be enabled. --> <bool name="config_settingsHelpLinksEnabled">false</bool> + + <!-- Whether or not to enable the lock screen entry point for the QR code scanner. --> + <bool name="config_enableQrCodeScannerOnLockScreen">false</bool> </resources> diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml index a877bd39bceb..3f08e4b9d9ad 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -131,6 +131,19 @@ corners. --> <dimen name="rounded_corner_radius_bottom_adjustment">0px</dimen> + <!-- Copied from SysUI's @dimen/navigation_key_width for the embedded nav bar in the IME. --> + <dimen name="input_method_navigation_key_width">70dp</dimen> + <!-- Copied from SysUI's @dimen/navigation_key_padding for the embedded nav bar in the IME. --> + <dimen name="input_method_navigation_key_padding">0dp</dimen> + <!-- Copied from SysUI's @dimen/nav_content_padding for the embedded nav bar in the IME. --> + <dimen name="input_method_nav_content_padding">0px</dimen> + <!-- Copied from SysUI's @dimen/rounded_corner_content_padding for the embedded nav bar in the + IME. --> + <dimen name="input_method_rounded_corner_content_padding">0px</dimen> + <!-- Copied from SysUI's @dimen/key_button_ripple_max_width for the embedded nav bar in the + IME. --> + <dimen name="input_method_nav_key_button_ripple_max_width">95dp</dimen> + <!-- Width of the window of the divider bar used to resize docked stacks. --> <dimen name="docked_stack_divider_thickness">48dp</dimen> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index e83f4a66883c..505fe59cfefd 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -3212,6 +3212,7 @@ <public type="attr" name="shouldUseDefaultUnfoldTransition" id="0x0101064c" /> <public type="attr" name="lineBreakStyle" id="0x0101064d" /> + <public type="attr" name="lineBreakWordStyle" id="0x0101064e" /> <staging-public-group-final type="id" first-id="0x01fe0000"> <public name="accessibilityActionDragStart" /> @@ -3256,6 +3257,8 @@ <public name="gameSessionService" /> <public name="localeConfig" /> <public name="showBackground" /> + <public name="inheritKeyStoreKeys" /> + <public name="preferKeepClear" /> </staging-public-group> <staging-public-group type="id" first-id="0x01de0000"> @@ -3279,6 +3282,8 @@ <public name="config_systemAppProtectionService" /> <!-- @hide @SystemApi @TestApi --> <public name="config_systemAutomotiveCalendarSyncManager" /> + <!-- @hide @SystemApi --> + <public name="config_defaultAutomotiveNavigation" /> </staging-public-group> <staging-public-group type="dimen" first-id="0x01db0000"> @@ -3322,6 +3327,8 @@ <staging-public-group type="bool" first-id="0x01cf0000"> <!-- @hide @TestApi --> <public name="config_preventImeStartupUnlessTextEditor" /> + <!-- @hide @SystemApi --> + <public name="config_enableQrCodeScannerOnLockScreen" /> </staging-public-group> <staging-public-group type="fraction" first-id="0x01ce0000"> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 6577ebc8cd3f..1a5d8b7ca8fd 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -3277,6 +3277,11 @@ <!-- Title for EditText context menu [CHAR LIMIT=20] --> <string name="editTextMenuTitle">Text actions</string> + <!-- Content description of the back button for accessibility (not shown on the screen). [CHAR LIMIT=NONE] --> + <string name="input_method_nav_back_button_desc">Back</string> + <!-- Content description of the switch input method button for accessibility (not shown on the screen). [CHAR LIMIT=NONE] --> + <string name="input_method_ime_switch_button_desc">Switch input method</string> + <!-- If the device is getting low on internal storage, a notification is shown to the user. This is the title of that notification. --> <string name="low_internal_storage_view_title">Storage space running out</string> <!-- If the device is getting low on internal storage, a notification is shown to the user. This is the message of that notification. --> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 6bc35ec2fffe..8c8ef120f781 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2439,6 +2439,24 @@ <!-- From PinyinIME(!!!) --> <java-symbol type="string" name="inputMethod" /> + <!-- Gestural Nav buttons within InputMethodService --> + <java-symbol type="dimen" name="input_method_nav_key_button_ripple_max_width" /> + <java-symbol type="drawable" name="ic_ime_nav_back" /> + <java-symbol type="drawable" name="ic_ime_switcher" /> + <java-symbol type="id" name="input_method_nav_back" /> + <java-symbol type="id" name="input_method_nav_buttons" /> + <java-symbol type="id" name="input_method_nav_center_group" /> + <java-symbol type="id" name="input_method_nav_ends_group" /> + <java-symbol type="id" name="input_method_nav_home_handle" /> + <java-symbol type="id" name="input_method_nav_horizontal" /> + <java-symbol type="id" name="input_method_nav_ime_switcher" /> + <java-symbol type="id" name="input_method_nav_inflater" /> + <java-symbol type="layout" name="input_method_navigation_bar" /> + <java-symbol type="layout" name="input_method_navigation_layout" /> + <java-symbol type="layout" name="input_method_nav_back" /> + <java-symbol type="layout" name="input_method_nav_home_handle" /> + <java-symbol type="layout" name="input_method_nav_ime_switcher" /> + <!-- From Chromium-WebView --> <java-symbol type="attr" name="actionModeWebSearchDrawable" /> <java-symbol type="string" name="websearch" /> @@ -3652,6 +3670,7 @@ <java-symbol type="string" name="config_defaultRotationResolverService" /> <java-symbol type="string" name="config_defaultSystemCaptionsService" /> <java-symbol type="string" name="config_defaultSystemCaptionsManagerService" /> + <java-symbol type="string" name="config_defaultAmbientContextDetectionService" /> <java-symbol type="string" name="config_retailDemoPackage" /> <java-symbol type="string" name="config_retailDemoPackageSignature" /> @@ -4646,4 +4665,6 @@ <java-symbol type="string" name="config_supervisedUserCreationPackage"/> <java-symbol type="bool" name="config_enableSafetyCenter" /> + + <java-symbol type="string" name="config_deviceManagerUpdater" /> </resources> diff --git a/core/res/res/xml/power_profile.xml b/core/res/res/xml/power_profile.xml index d310736ae121..fc63657f04d0 100644 --- a/core/res/res/xml/power_profile.xml +++ b/core/res/res/xml/power_profile.xml @@ -144,17 +144,49 @@ <value>2</value> <!-- 4097-/hr --> </array> - <!-- Cellular modem related values. Default is 0.--> - <item name="modem.controller.sleep">0</item> - <item name="modem.controller.idle">0</item> - <item name="modem.controller.rx">0</item> - <array name="modem.controller.tx"> <!-- Strength 0 to 4 --> - <value>0</value> - <value>0</value> - <value>0</value> - <value>0</value> - <value>0</value> - </array> + <!-- Cellular modem related values.--> + <modem> + <!-- Modem sleep drain current value in mA. --> + <sleep>0</sleep> + <!-- Modem idle drain current value in mA. --> + <idle>0</idle> + <!-- Modem active drain current values. + Multiple <active /> can be defined to specify current drain for different modes of + operation. + Available attributes: + rat - Specify the current drain for a Radio Access Technology. + Available options are "LTE", "NR" and "DEFAULT". + <active rat="default" /> will be used for any usage that does not match any other + defined <active /> rat. + + nrFrequency - Specify the current drain for a frequency level while NR is active. + Available options are "LOW", "MID", "HIGH", "MMWAVE", and "DEFAULT", + where, + "LOW" indicated <1GHz frequencies, + "MID" indicates 1GHz to 3GHz frequencies, + "HIGH" indicates 3GHz to 6GHz frequencies, + "MMWAVE"indicates >6GHz frequencies. + <active rat="NR" nrFrequency="default"/> will be used for any usage that + does not match any other defined <active rat="NR" /> nrFrequency. + --> + <active rat="DEFAULT"> + <!-- Transmit current drain in mA. --> + <receive>0</receive> + + <!-- Transmit current drains in mA. Must be defined for all levels (0 to 4) --> + <transmit level="0">0</transmit> + <transmit level="1">0</transmit> + <transmit level="2">0</transmit> + <transmit level="3">0</transmit> + <transmit level="4">0</transmit> + </active> + <!-- Additional <active /> may be defined. + Example: + <active rat="LTE"> ... </active> + <active rat="NR" nrFrequency="MMWAVE"> ... </active> + <active rat="NR" nrFrequency="DEFAULT"> ... </active> + --> + </modem> <item name="modem.controller.voltage">0</item> <!-- GPS related values. Default is 0.--> @@ -163,5 +195,4 @@ <value>0</value> </array> <item name="gps.voltage">0</item> - </device> diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml index f2b35c72a567..a80424e500c4 100644 --- a/core/tests/coretests/AndroidManifest.xml +++ b/core/tests/coretests/AndroidManifest.xml @@ -38,6 +38,7 @@ <uses-permission android:name="android.permission.ACCESS_CACHE_FILESYSTEM" /> <uses-permission android:name="android.permission.ACCESS_DOWNLOAD_MANAGER" /> <uses-permission android:name="android.permission.ACCESS_DOWNLOAD_MANAGER_ADVANCED" /> + <uses-permission android:name="android.permission.ACCESS_FPS_COUNTER" /> <uses-permission android:name="android.permission.DOWNLOAD_CACHE_NON_PURGEABLE" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> @@ -64,6 +65,7 @@ <uses-permission android:name="android.permission.READ_CONTACTS" /> <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" /> <uses-permission android:name="android.permission.READ_DREAM_STATE" /> + <uses-permission android:name="android.permission.REAL_GET_TASKS"/> <uses-permission android:name="android.permission.WRITE_DREAM_STATE" /> <uses-permission android:name="android.permission.READ_CLIPBOARD_IN_BACKGROUND" /> <uses-permission android:name="android.permission.READ_LOGS"/> diff --git a/core/tests/coretests/res/xml/power_profile_test.xml b/core/tests/coretests/res/xml/power_profile_test.xml new file mode 100644 index 000000000000..22571142a350 --- /dev/null +++ b/core/tests/coretests/res/xml/power_profile_test.xml @@ -0,0 +1,123 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<device name="Android"> + <!-- This is the battery capacity in mAh --> + <item name="battery.capacity">3000</item> + + <!-- Number of cores each CPU cluster contains --> + <array name="cpu.clusters.cores"> + <value>4</value> <!-- Cluster 0 has 4 cores (cpu0, cpu1, cpu2, cpu3) --> + <value>4</value> <!-- Cluster 1 has 4 cores (cpu4, cpu5, cpu5, cpu7) --> + </array> + + <!-- Power consumption when CPU is suspended --> + <item name="cpu.suspend">5</item> + <!-- Additional power consumption when CPU is in a kernel idle loop --> + <item name="cpu.idle">1.11</item> + <!-- Additional power consumption by CPU excluding cluster and core when running --> + <item name="cpu.active">2.55</item> + + <!-- Additional power consumption by CPU cluster0 itself when running excluding cores in it --> + <item name="cpu.cluster_power.cluster0">2.11</item> + <!-- Additional power consumption by CPU cluster1 itself when running excluding cores in it --> + <item name="cpu.cluster_power.cluster1">2.22</item> + + <!-- Different CPU speeds as reported in + /sys/devices/system/cpu/cpu0/cpufreq/stats/scaling_available_frequencies --> + <array name="cpu.core_speeds.cluster0"> + <value>300000</value> <!-- 300 MHz CPU speed --> + <value>1000000</value> <!-- 1000 MHz CPU speed --> + <value>2000000</value> <!-- 2000 MHz CPU speed --> + </array> + <!-- Different CPU speeds as reported in + /sys/devices/system/cpu/cpu4/cpufreq/stats/scaling_available_frequencies --> + <array name="cpu.core_speeds.cluster1"> + <value>300000</value> <!-- 300 MHz CPU speed --> + <value>1000000</value> <!-- 1000 MHz CPU speed --> + <value>2500000</value> <!-- 2500 MHz CPU speed --> + <value>3000000</value> <!-- 3000 MHz CPU speed --> + </array> + + <!-- Additional power used by a CPU from cluster 0 when running at different + speeds. Currently this measurement also includes cluster cost. --> + <array name="cpu.core_power.cluster0"> + <value>10</value> <!-- 300 MHz CPU speed --> + <value>20</value> <!-- 1000 MHz CPU speed --> + <value>30</value> <!-- 1900 MHz CPU speed --> + </array> + <!-- Additional power used by a CPU from cluster 1 when running at different + speeds. Currently this measurement also includes cluster cost. --> + <array name="cpu.core_power.cluster1"> + <value>25</value> <!-- 300 MHz CPU speed --> + <value>35</value> <!-- 1000 MHz CPU speed --> + <value>50</value> <!-- 2500 MHz CPU speed --> + <value>60</value> <!-- 3000 MHz CPU speed --> + </array> + + <!-- Power used by display unit in ambient display mode, including back lighting--> + <item name="ambient.on">0.5</item> + <!-- Additional power used when screen is turned on at minimum brightness --> + <item name="screen.on">100</item> + <!-- Additional power used when screen is at maximum brightness, compared to + screen at minimum brightness --> + <item name="screen.full">800</item> + + <!-- Average power used by the camera flash module when on --> + <item name="camera.flashlight">500</item> + <!-- Average power use by the camera subsystem for a typical camera + application. Intended as a rough estimate for an application running a + preview and capturing approximately 10 full-resolution pictures per + minute. --> + <item name="camera.avg">600</item> + + <!-- Additional power used by the audio hardware, probably due to DSP --> + <item name="audio">100.0</item> + + <!-- Additional power used by the video hardware, probably due to DSP --> + <item name="video">150.0</item> <!-- ~50mA --> + + <!-- Additional power used when GPS is acquiring a signal --> + <item name="gps.on">10</item> + + <!-- Additional power used when cellular radio is transmitting/receiving --> + <item name="radio.active">60</item> + <!-- Additional power used when cellular radio is paging the tower --> + <item name="radio.scanning">3</item> + <!-- Additional power used when the cellular radio is on. Multi-value entry, + one per signal strength (no signal, weak, moderate, strong) --> + <array name="radio.on"> <!-- Strength 0 to BINS-1 --> + <value>6</value> <!-- none --> + <value>5</value> <!-- poor --> + <value>4</value> <!-- moderate --> + <value>3</value> <!-- good --> + <value>3</value> <!-- great --> + </array> + + <!-- Cellular modem related values. These constants are deprecated, but still supported and + need to be tested --> + <item name="modem.controller.sleep">123</item> + <item name="modem.controller.idle">456</item> + <item name="modem.controller.rx">789</item> + <array name="modem.controller.tx"> <!-- Strength 0 to 4 --> + <value>10</value> + <value>20</value> + <value>30</value> + <value>40</value> + <value>50</value> + </array> +</device>
\ No newline at end of file diff --git a/core/tests/coretests/res/xml/power_profile_test_modem.xml b/core/tests/coretests/res/xml/power_profile_test_modem.xml new file mode 100644 index 000000000000..ff36a9c94e0b --- /dev/null +++ b/core/tests/coretests/res/xml/power_profile_test_modem.xml @@ -0,0 +1,151 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +** +** Copyright 2021, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License") +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +--> + +<device name="test"> + <test-modem name="testModemPowerProfile_defaultRat"> + <!-- Modem sleep drain current value in mA. --> + <sleep>10</sleep> + <!-- Modem idle drain current value in mA. --> + <idle>20</idle> + <active rat="DEFAULT"> + <!-- Transmit current drain in mA. --> + <receive>30</receive> + <!-- Transmit current drains in mA. Must be defined for all levels (0 to 4) --> + <transmit level="0">40</transmit> + <transmit level="1">50</transmit> + <transmit level="2">60</transmit> + <transmit level="3">70</transmit> + <transmit level="4">80</transmit> + </active> + </test-modem> + + <test-modem name="testModemPowerProfile_partiallyDefined"> + <!-- Modem sleep drain current value in mA. --> + <sleep>1</sleep> + <!-- Modem idle drain current value in mA. --> + <idle>2</idle> + <active rat="DEFAULT"> + <!-- Transmit current drain in mA. --> + <receive>3</receive> + <!-- Transmit current drains in mA. Must be defined for all levels (0 to 4) --> + <transmit level="0">4</transmit> + <transmit level="1">5</transmit> + <transmit level="2">6</transmit> + <transmit level="3">7</transmit> + <transmit level="4">8</transmit> + </active> + <active rat="NR" nrFrequency="DEFAULT"> + <!-- Transmit current drain in mA. --> + <receive>13</receive> + <!-- Transmit current drains in mA. Must be defined for all levels (0 to 4) --> + <transmit level="0">14</transmit> + <transmit level="1">15</transmit> + <transmit level="2">16</transmit> + <transmit level="3">17</transmit> + <transmit level="4">18</transmit> + </active> + <active rat="NR" nrFrequency="MMWAVE"> + <!-- Transmit current drain in mA. --> + <receive>53</receive> + <!-- Transmit current drains in mA. Must be defined for all levels (0 to 4) --> + <transmit level="0">54</transmit> + <transmit level="1">55</transmit> + <transmit level="2">56</transmit> + <transmit level="3">57</transmit> + <transmit level="4">58</transmit> + </active> + </test-modem> + + <test-modem name="testModemPowerProfile_fullyDefined"> + <!-- Modem sleep drain current value in mA. --> + <sleep>1</sleep> + <!-- Modem idle drain current value in mA. --> + <idle>2</idle> + <active rat="DEFAULT"> + <!-- Transmit current drain in mA. --> + <receive>3</receive> + <!-- Transmit current drains in mA. Must be defined for all levels (0 to 4) --> + <transmit level="0">4</transmit> + <transmit level="1">5</transmit> + <transmit level="2">6</transmit> + <transmit level="3">7</transmit> + <transmit level="4">8</transmit> + </active> + <active rat="LTE"> + <!-- Transmit current drain in mA. --> + <receive>10</receive> + <!-- Transmit current drains in mA. Must be defined for all levels (0 to 4) --> + <transmit level="0">20</transmit> + <transmit level="1">30</transmit> + <transmit level="2">40</transmit> + <transmit level="3">50</transmit> + <transmit level="4">60</transmit> + </active> + <active rat="NR" nrFrequency="DEFAULT"> + <!-- Transmit current drain in mA. --> + <receive>13</receive> + <!-- Transmit current drains in mA. Must be defined for all levels (0 to 4) --> + <transmit level="0">14</transmit> + <transmit level="1">15</transmit> + <transmit level="2">16</transmit> + <transmit level="3">17</transmit> + <transmit level="4">18</transmit> + </active> + <active rat="NR" nrFrequency="LOW"> + <!-- Transmit current drain in mA. --> + <receive>23</receive> + <!-- Transmit current drains in mA. Must be defined for all levels (0 to 4) --> + <transmit level="0">24</transmit> + <transmit level="1">25</transmit> + <transmit level="2">26</transmit> + <transmit level="3">27</transmit> + <transmit level="4">28</transmit> + </active> + <active rat="NR" nrFrequency="MID"> + <!-- Transmit current drain in mA. --> + <receive>33</receive> + <!-- Transmit current drains in mA. Must be defined for all levels (0 to 4) --> + <transmit level="0">34</transmit> + <transmit level="1">35</transmit> + <transmit level="2">36</transmit> + <transmit level="3">37</transmit> + <transmit level="4">38</transmit> + </active> + <active rat="NR" nrFrequency="HIGH"> + <!-- Transmit current drain in mA. --> + <receive>43</receive> + <!-- Transmit current drains in mA. Must be defined for all levels (0 to 4) --> + <transmit level="0">44</transmit> + <transmit level="1">45</transmit> + <transmit level="2">46</transmit> + <transmit level="3">47</transmit> + <transmit level="4">48</transmit> + </active> + <active rat="NR" nrFrequency="MMWAVE"> + <!-- Transmit current drain in mA. --> + <receive>53</receive> + <!-- Transmit current drains in mA. Must be defined for all levels (0 to 4) --> + <transmit level="0">54</transmit> + <transmit level="1">55</transmit> + <transmit level="2">56</transmit> + <transmit level="3">57</transmit> + <transmit level="4">58</transmit> + </active> + </test-modem> +</device> diff --git a/core/tests/coretests/src/android/content/pm/CrossProfileAppsTest.java b/core/tests/coretests/src/android/content/pm/CrossProfileAppsTest.java index 0cfcd8f85784..b66642c20808 100644 --- a/core/tests/coretests/src/android/content/pm/CrossProfileAppsTest.java +++ b/core/tests/coretests/src/android/content/pm/CrossProfileAppsTest.java @@ -16,11 +16,17 @@ package android.content.pm; +import static android.app.admin.DevicePolicyResources.Strings.Core.SWITCH_TO_PERSONAL_LABEL; +import static android.app.admin.DevicePolicyResources.Strings.Core.SWITCH_TO_WORK_LABEL; + +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.app.admin.DevicePolicyManager; import android.content.Context; import android.content.res.Resources; import android.graphics.drawable.Drawable; @@ -58,6 +64,8 @@ public class CrossProfileAppsTest { @Mock private UserManager mUserManager; @Mock + private DevicePolicyManager mDevicePolicyManager; + @Mock private ICrossProfileApps mService; @Mock private Resources mResources; @@ -75,6 +83,10 @@ public class CrossProfileAppsTest { when(mContext.getPackageName()).thenReturn(MY_PACKAGE); when(mContext.getSystemServiceName(UserManager.class)).thenReturn(Context.USER_SERVICE); when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager); + when(mContext.getSystemServiceName(DevicePolicyManager.class)).thenReturn( + Context.DEVICE_POLICY_SERVICE); + when(mContext.getSystemService(Context.DEVICE_POLICY_SERVICE)).thenReturn( + mDevicePolicyManager); } @Before @@ -98,7 +110,7 @@ public class CrossProfileAppsTest { setValidTargetProfile(MANAGED_PROFILE); mCrossProfileApps.getProfileSwitchingLabel(MANAGED_PROFILE); - verify(mResources).getString(R.string.managed_profile_label); + verify(mDevicePolicyManager).getString(eq(SWITCH_TO_WORK_LABEL), any()); } @Test @@ -106,7 +118,7 @@ public class CrossProfileAppsTest { setValidTargetProfile(PERSONAL_PROFILE); mCrossProfileApps.getProfileSwitchingLabel(PERSONAL_PROFILE); - verify(mResources).getString(R.string.user_owner_label); + verify(mDevicePolicyManager).getString(eq(SWITCH_TO_PERSONAL_LABEL), any()); } @Test(expected = SecurityException.class) diff --git a/core/tests/coretests/src/android/os/VibratorInfoTest.java b/core/tests/coretests/src/android/os/VibratorInfoTest.java index d0e03a24427e..88766e2d97d4 100644 --- a/core/tests/coretests/src/android/os/VibratorInfoTest.java +++ b/core/tests/coretests/src/android/os/VibratorInfoTest.java @@ -25,7 +25,6 @@ import static org.junit.Assert.assertTrue; import android.hardware.vibrator.Braking; import android.hardware.vibrator.IVibrator; import android.platform.test.annotations.Presubmit; -import android.util.Range; import org.junit.Test; import org.junit.runner.RunWith; @@ -43,10 +42,10 @@ public class VibratorInfoTest { private static final float[] TEST_AMPLITUDE_MAP = new float[]{ /* 50Hz= */ 0.1f, 0.2f, 0.4f, 0.8f, /* 150Hz= */ 1f, 0.9f, /* 200Hz= */ 0.8f}; - private static final VibratorInfo.FrequencyMapping EMPTY_FREQUENCY_MAPPING = - new VibratorInfo.FrequencyMapping(Float.NaN, Float.NaN, Float.NaN, null); - private static final VibratorInfo.FrequencyMapping TEST_FREQUENCY_MAPPING = - new VibratorInfo.FrequencyMapping(TEST_RESONANT_FREQUENCY, TEST_MIN_FREQUENCY, + private static final VibratorInfo.FrequencyProfile EMPTY_FREQUENCY_PROFILE = + new VibratorInfo.FrequencyProfile(Float.NaN, Float.NaN, Float.NaN, null); + private static final VibratorInfo.FrequencyProfile TEST_FREQUENCY_PROFILE = + new VibratorInfo.FrequencyProfile(TEST_RESONANT_FREQUENCY, TEST_MIN_FREQUENCY, TEST_FREQUENCY_RESOLUTION, TEST_AMPLITUDE_MAP); @Test @@ -142,95 +141,109 @@ public class VibratorInfoTest { } @Test - public void testGetFrequencyRangeHz_invalidFrequencyMappingReturnsNull() { + public void testGetFrequencyProfile_unsetProfileIsEmpty() { + assertTrue( + new VibratorInfo.Builder(TEST_VIBRATOR_ID).build().getFrequencyProfile().isEmpty()); + } + + @Test + public void testFrequencyProfile_invalidValuesCreatesEmptyProfile() { // Invalid, contains NaN values or empty array. - assertNull(new VibratorInfo.Builder(TEST_VIBRATOR_ID).build().getFrequencyRangeHz()); - assertNull(new VibratorInfo.Builder(TEST_VIBRATOR_ID) - .setFrequencyMapping(new VibratorInfo.FrequencyMapping( - Float.NaN, 50, 25, TEST_AMPLITUDE_MAP)) - .build().getFrequencyRangeHz()); - assertNull(new VibratorInfo.Builder(TEST_VIBRATOR_ID) - .setFrequencyMapping(new VibratorInfo.FrequencyMapping( - 150, Float.NaN, 25, TEST_AMPLITUDE_MAP)) - .build().getFrequencyRangeHz()); - assertNull(new VibratorInfo.Builder(TEST_VIBRATOR_ID) - .setFrequencyMapping(new VibratorInfo.FrequencyMapping( - 150, 50, Float.NaN, TEST_AMPLITUDE_MAP)) - .build().getFrequencyRangeHz()); - assertNull(new VibratorInfo.Builder(TEST_VIBRATOR_ID) - .setFrequencyMapping(new VibratorInfo.FrequencyMapping(150, 50, 25, null)) - .build().getFrequencyRangeHz()); + assertTrue(new VibratorInfo.FrequencyProfile( + Float.NaN, 50, 25, TEST_AMPLITUDE_MAP).isEmpty()); + assertTrue(new VibratorInfo.FrequencyProfile( + 150, Float.NaN, 25, TEST_AMPLITUDE_MAP).isEmpty()); + assertTrue(new VibratorInfo.FrequencyProfile( + 150, 50, Float.NaN, TEST_AMPLITUDE_MAP).isEmpty()); + assertTrue(new VibratorInfo.FrequencyProfile(150, 50, 25, null).isEmpty()); + // Invalid, contains zero or negative frequency values. + assertTrue(new VibratorInfo.FrequencyProfile(-1, 50, 25, TEST_AMPLITUDE_MAP).isEmpty()); + assertTrue(new VibratorInfo.FrequencyProfile(150, 0, 25, TEST_AMPLITUDE_MAP).isEmpty()); + assertTrue(new VibratorInfo.FrequencyProfile(150, 50, -2, TEST_AMPLITUDE_MAP).isEmpty()); + // Invalid max amplitude entries. + assertTrue(new VibratorInfo.FrequencyProfile( + 150, 50, 50, new float[] { -1, 0, 1, 1, 0 }).isEmpty()); + assertTrue(new VibratorInfo.FrequencyProfile( + 150, 50, 50, new float[] { 0, 1, 2, 1, 0 }).isEmpty()); // Invalid, minFrequency > resonantFrequency - assertNull(new VibratorInfo.Builder(TEST_VIBRATOR_ID) - .setFrequencyMapping(new VibratorInfo.FrequencyMapping( - /* resonantFrequencyHz= */ 150, /* minFrequencyHz= */ 250, 25, null)) - .build().getFrequencyRangeHz()); + assertTrue(new VibratorInfo.FrequencyProfile( + /* resonantFrequencyHz= */ 150, /* minFrequencyHz= */ 250, 25, TEST_AMPLITUDE_MAP) + .isEmpty()); // Invalid, maxFrequency < resonantFrequency by changing resolution. - assertNull(new VibratorInfo.Builder(TEST_VIBRATOR_ID) - .setFrequencyMapping(new VibratorInfo.FrequencyMapping( - 150, 50, /* frequencyResolutionHz= */ 10, null)) - .build().getFrequencyRangeHz()); + assertTrue(new VibratorInfo.FrequencyProfile( + 150, 50, /* frequencyResolutionHz= */ 10, TEST_AMPLITUDE_MAP).isEmpty()); } @Test - public void testGetFrequencyRangeHz_resultRangeDerivedFromHalMapping() { - VibratorInfo info = new VibratorInfo.Builder(TEST_VIBRATOR_ID) - .setFrequencyMapping(new VibratorInfo.FrequencyMapping( - /* resonantFrequencyHz= */ 150, - /* minFrequencyHz= */ 50, - /* frequencyResolutionHz= */ 25, - new float[]{ - /* 50Hz= */ 0.1f, 0.2f, 0.4f, 0.8f, /* 150Hz= */ 1f, 0.9f, - /* 200Hz= */ 0.8f})) - .build(); + public void testGetFrequencyRangeHz_emptyProfileReturnsNull() { + assertNull(new VibratorInfo.FrequencyProfile( + Float.NaN, 50, 25, TEST_AMPLITUDE_MAP).getFrequencyRangeHz()); + assertNull(new VibratorInfo.FrequencyProfile( + 150, Float.NaN, 25, TEST_AMPLITUDE_MAP).getFrequencyRangeHz()); + assertNull(new VibratorInfo.FrequencyProfile( + 150, 50, Float.NaN, TEST_AMPLITUDE_MAP).getFrequencyRangeHz()); + assertNull(new VibratorInfo.FrequencyProfile(150, 50, 25, null).getFrequencyRangeHz()); + } - assertEquals(Range.create(50f, 200f), info.getFrequencyRangeHz()); + @Test + public void testGetFrequencyRangeHz_validProfileReturnsMappedValues() { + VibratorInfo.FrequencyProfile profile = new VibratorInfo.FrequencyProfile( + /* resonantFrequencyHz= */ 150, + /* minFrequencyHz= */ 50, + /* frequencyResolutionHz= */ 25, + /* maxAmplitudes= */ new float[]{ + /* 50Hz= */ 0.1f, 0.2f, 0.4f, 0.8f, /* 150Hz= */ 1f, 0.9f, + /* 200Hz= */ 0.8f}); + + assertEquals(50f, profile.getFrequencyRangeHz().getLower(), TEST_TOLERANCE); + assertEquals(200f, profile.getFrequencyRangeHz().getUpper(), TEST_TOLERANCE); } @Test - public void testGetMaxAmplitude_emptyMappingReturnsAlwaysZero() { - VibratorInfo info = new VibratorInfo.Builder(TEST_VIBRATOR_ID).build(); - assertEquals(0f, info.getMaxAmplitude(Float.NaN), TEST_TOLERANCE); - assertEquals(0f, info.getMaxAmplitude(100f), TEST_TOLERANCE); - assertEquals(0f, info.getMaxAmplitude(200f), TEST_TOLERANCE); + public void testGetMaxAmplitude_emptyProfileReturnsAlwaysZero() { + VibratorInfo.FrequencyProfile profile = EMPTY_FREQUENCY_PROFILE; + assertEquals(0f, profile.getMaxAmplitude(Float.NaN), TEST_TOLERANCE); + assertEquals(0f, profile.getMaxAmplitude(100f), TEST_TOLERANCE); + assertEquals(0f, profile.getMaxAmplitude(200f), TEST_TOLERANCE); - info = new VibratorInfo.Builder(TEST_VIBRATOR_ID) - .setFrequencyMapping(new VibratorInfo.FrequencyMapping( + profile = new VibratorInfo.FrequencyProfile( /* resonantFrequencyHz= */ 150, /* minFrequencyHz= */ Float.NaN, /* frequencyResolutionHz= */ Float.NaN, - null)) - .build(); + /* maxAmplitudes= */ null); - assertEquals(0f, info.getMaxAmplitude(Float.NaN), TEST_TOLERANCE); - assertEquals(0f, info.getMaxAmplitude(100f), TEST_TOLERANCE); - assertEquals(0f, info.getMaxAmplitude(150f), TEST_TOLERANCE); + assertEquals(0f, profile.getMaxAmplitude(Float.NaN), TEST_TOLERANCE); + assertEquals(0f, profile.getMaxAmplitude(100f), TEST_TOLERANCE); + assertEquals(0f, profile.getMaxAmplitude(150f), TEST_TOLERANCE); } @Test - public void testGetMaxAmplitude_validMappingReturnsMappedValues() { - VibratorInfo info = new VibratorInfo.Builder(TEST_VIBRATOR_ID) - .setFrequencyMapping(new VibratorInfo.FrequencyMapping( + public void testGetMaxAmplitude_validProfileReturnsMappedValues() { + VibratorInfo.FrequencyProfile profile = new VibratorInfo.FrequencyProfile( /* resonantFrequencyHz= */ 150, /* minFrequencyHz= */ 50, /* frequencyResolutionHz= */ 25, - new float[]{ + /* maxAmplitudes= */ new float[]{ /* 50Hz= */ 0.1f, 0.2f, 0.4f, 0.8f, /* 150Hz= */ 1f, 0.9f, - /* 200Hz= */ 0.8f})) - .build(); + /* 200Hz= */ 0.8f}); - assertEquals(1f, info.getMaxAmplitude(150f), TEST_TOLERANCE); - assertEquals(0.9f, info.getMaxAmplitude(175f), TEST_TOLERANCE); - assertEquals(0.8f, info.getMaxAmplitude(125f), TEST_TOLERANCE); - assertEquals(0.8f, info.getMaxAmplitude(info.getFrequencyRangeHz().getUpper()), - TEST_TOLERANCE); // 200Hz - assertEquals(0.1f, info.getMaxAmplitude(info.getFrequencyRangeHz().getLower()), - TEST_TOLERANCE); // 50Hz - - // 145Hz maps to the max amplitude for 125Hz, which is lower. - assertEquals(0.8f, info.getMaxAmplitude(145f), TEST_TOLERANCE); // 145Hz - // 185Hz maps to the max amplitude for 200Hz, which is lower. - assertEquals(0.8f, info.getMaxAmplitude(185f), TEST_TOLERANCE); // 185Hz + // Values in the max amplitudes array should return exact measurement. + assertEquals(1f, profile.getMaxAmplitude(150f), TEST_TOLERANCE); + assertEquals(0.9f, profile.getMaxAmplitude(175f), TEST_TOLERANCE); + assertEquals(0.8f, profile.getMaxAmplitude(125f), TEST_TOLERANCE); + + // Min and max frequencies should return exact measurement from array. + assertEquals(0.8f, profile.getMaxAmplitude(200f), TEST_TOLERANCE); + assertEquals(0.1f, profile.getMaxAmplitude(50f), TEST_TOLERANCE); + + // Values outside [50Hz, 200Hz] just return 0. + assertEquals(0f, profile.getMaxAmplitude(49f), TEST_TOLERANCE); + assertEquals(0f, profile.getMaxAmplitude(201f), TEST_TOLERANCE); + + // 145Hz maps to linear value between 125Hz and 150Hz max amplitudes 0.8 and 1. + assertEquals(0.96f, profile.getMaxAmplitude(145f), TEST_TOLERANCE); + // 185Hz maps to linear value between 175Hz and 200Hz max amplitudes 0.9 and 0.8. + assertEquals(0.86f, profile.getMaxAmplitude(185f), TEST_TOLERANCE); } @Test @@ -245,7 +258,7 @@ public class VibratorInfoTest { .setPwlePrimitiveDurationMax(50) .setPwleSizeMax(20) .setQFactor(2f) - .setFrequencyMapping(TEST_FREQUENCY_MAPPING); + .setFrequencyProfile(TEST_FREQUENCY_PROFILE); VibratorInfo complete = completeBuilder.build(); assertEquals(complete, complete); @@ -272,23 +285,21 @@ public class VibratorInfoTest { .build(); assertNotEquals(complete, completeWithDifferentPrimitiveDuration); - VibratorInfo completeWithDifferentFrequencyMapping = completeBuilder - .setFrequencyMapping(new VibratorInfo.FrequencyMapping( + VibratorInfo completeWithDifferentFrequencyProfile = completeBuilder + .setFrequencyProfile(new VibratorInfo.FrequencyProfile( TEST_RESONANT_FREQUENCY + 20, TEST_MIN_FREQUENCY + 10, TEST_FREQUENCY_RESOLUTION + 5, TEST_AMPLITUDE_MAP)) .build(); - assertNotEquals(complete, completeWithDifferentFrequencyMapping); + assertNotEquals(complete, completeWithDifferentFrequencyProfile); - VibratorInfo completeWithEmptyFrequencyMapping = completeBuilder - .setFrequencyMapping(EMPTY_FREQUENCY_MAPPING) + VibratorInfo completeWithEmptyFrequencyProfile = completeBuilder + .setFrequencyProfile(EMPTY_FREQUENCY_PROFILE) .build(); - assertNotEquals(complete, completeWithEmptyFrequencyMapping); + assertNotEquals(complete, completeWithEmptyFrequencyProfile); - VibratorInfo completeWithUnknownQFactor = completeBuilder - .setQFactor(Float.NaN) - .build(); + VibratorInfo completeWithUnknownQFactor = completeBuilder.setQFactor(Float.NaN).build(); assertNotEquals(complete, completeWithUnknownQFactor); VibratorInfo completeWithDifferentQFactor = completeBuilder @@ -316,7 +327,7 @@ public class VibratorInfoTest { .setSupportedEffects(VibrationEffect.EFFECT_CLICK) .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 20) .setQFactor(Float.NaN) - .setFrequencyMapping(TEST_FREQUENCY_MAPPING) + .setFrequencyProfile(TEST_FREQUENCY_PROFILE) .build(); Parcel parcel = Parcel.obtain(); diff --git a/core/tests/coretests/src/android/os/VibratorTest.java b/core/tests/coretests/src/android/os/VibratorTest.java index 981086d6b152..7a66befad1a1 100644 --- a/core/tests/coretests/src/android/os/VibratorTest.java +++ b/core/tests/coretests/src/android/os/VibratorTest.java @@ -61,6 +61,8 @@ public class VibratorTest { @Rule public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule(); + private static final float TEST_TOLERANCE = 1e-5f; + private Context mContextSpy; private Vibrator mVibratorSpy; @@ -76,6 +78,9 @@ public class VibratorTest { @Test public void getId_returnsDefaultId() { assertEquals(-1, mVibratorSpy.getId()); + assertEquals(-1, new SystemVibrator.NoVibratorInfo().getId()); + assertEquals(-1, new SystemVibrator.MultiVibratorInfo(new VibratorInfo[] { + VibratorInfo.EMPTY_VIBRATOR_INFO, VibratorInfo.EMPTY_VIBRATOR_INFO }).getId()); } @Test @@ -90,8 +95,7 @@ public class VibratorTest { @Test public void areEffectsSupported_noVibrator_returnsAlwaysNo() { - SystemVibrator.AllVibratorsInfo info = new SystemVibrator.AllVibratorsInfo( - new VibratorInfo[0]); + VibratorInfo info = new SystemVibrator.NoVibratorInfo(); assertEquals(Vibrator.VIBRATION_EFFECT_SUPPORT_NO, info.isEffectSupported(VibrationEffect.EFFECT_CLICK)); } @@ -104,7 +108,7 @@ public class VibratorTest { VibratorInfo unsupportedVibrator = new VibratorInfo.Builder(/* id= */ 2) .setSupportedEffects(new int[0]) .build(); - SystemVibrator.AllVibratorsInfo info = new SystemVibrator.AllVibratorsInfo( + VibratorInfo info = new SystemVibrator.MultiVibratorInfo( new VibratorInfo[]{supportedVibrator, unsupportedVibrator}); assertEquals(Vibrator.VIBRATION_EFFECT_SUPPORT_NO, info.isEffectSupported(VibrationEffect.EFFECT_CLICK)); @@ -116,7 +120,7 @@ public class VibratorTest { .setSupportedEffects(VibrationEffect.EFFECT_CLICK) .build(); VibratorInfo unknownSupportVibrator = VibratorInfo.EMPTY_VIBRATOR_INFO; - SystemVibrator.AllVibratorsInfo info = new SystemVibrator.AllVibratorsInfo( + VibratorInfo info = new SystemVibrator.MultiVibratorInfo( new VibratorInfo[]{supportedVibrator, unknownSupportVibrator}); assertEquals(Vibrator.VIBRATION_EFFECT_SUPPORT_UNKNOWN, info.isEffectSupported(VibrationEffect.EFFECT_CLICK)); @@ -130,7 +134,7 @@ public class VibratorTest { VibratorInfo secondVibrator = new VibratorInfo.Builder(/* id= */ 2) .setSupportedEffects(VibrationEffect.EFFECT_CLICK) .build(); - SystemVibrator.AllVibratorsInfo info = new SystemVibrator.AllVibratorsInfo( + VibratorInfo info = new SystemVibrator.MultiVibratorInfo( new VibratorInfo[]{firstVibrator, secondVibrator}); assertEquals(Vibrator.VIBRATION_EFFECT_SUPPORT_YES, info.isEffectSupported(VibrationEffect.EFFECT_CLICK)); @@ -148,8 +152,7 @@ public class VibratorTest { @Test public void arePrimitivesSupported_noVibrator_returnsAlwaysFalse() { - SystemVibrator.AllVibratorsInfo info = new SystemVibrator.AllVibratorsInfo( - new VibratorInfo[0]); + VibratorInfo info = new SystemVibrator.NoVibratorInfo(); assertFalse(info.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_CLICK)); } @@ -160,7 +163,7 @@ public class VibratorTest { .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 10) .build(); VibratorInfo unsupportedVibrator = VibratorInfo.EMPTY_VIBRATOR_INFO; - SystemVibrator.AllVibratorsInfo info = new SystemVibrator.AllVibratorsInfo( + VibratorInfo info = new SystemVibrator.MultiVibratorInfo( new VibratorInfo[]{supportedVibrator, unsupportedVibrator}); assertFalse(info.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_CLICK)); } @@ -175,7 +178,7 @@ public class VibratorTest { .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS) .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 15) .build(); - SystemVibrator.AllVibratorsInfo info = new SystemVibrator.AllVibratorsInfo( + VibratorInfo info = new SystemVibrator.MultiVibratorInfo( new VibratorInfo[]{firstVibrator, secondVibrator}); assertTrue(info.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_CLICK)); } @@ -192,8 +195,7 @@ public class VibratorTest { @Test public void getPrimitivesDurations_noVibrator_returnsAlwaysZero() { - SystemVibrator.AllVibratorsInfo info = new SystemVibrator.AllVibratorsInfo( - new VibratorInfo[0]); + VibratorInfo info = new SystemVibrator.NoVibratorInfo(); assertEquals(0, info.getPrimitiveDuration(VibrationEffect.Composition.PRIMITIVE_CLICK)); } @@ -204,7 +206,7 @@ public class VibratorTest { .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 10) .build(); VibratorInfo unsupportedVibrator = VibratorInfo.EMPTY_VIBRATOR_INFO; - SystemVibrator.AllVibratorsInfo info = new SystemVibrator.AllVibratorsInfo( + VibratorInfo info = new SystemVibrator.MultiVibratorInfo( new VibratorInfo[]{supportedVibrator, unsupportedVibrator}); assertEquals(0, info.getPrimitiveDuration(VibrationEffect.Composition.PRIMITIVE_CLICK)); } @@ -219,12 +221,180 @@ public class VibratorTest { .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS) .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 20) .build(); - SystemVibrator.AllVibratorsInfo info = new SystemVibrator.AllVibratorsInfo( + VibratorInfo info = new SystemVibrator.MultiVibratorInfo( new VibratorInfo[]{firstVibrator, secondVibrator}); assertEquals(20, info.getPrimitiveDuration(VibrationEffect.Composition.PRIMITIVE_CLICK)); } @Test + public void getQFactorAndResonantFrequency_noVibrator_returnsNaN() { + VibratorInfo info = new SystemVibrator.NoVibratorInfo(); + + assertTrue(Float.isNaN(info.getQFactor())); + assertTrue(Float.isNaN(info.getResonantFrequencyHz())); + } + + @Test + public void getQFactorAndResonantFrequency_differentValues_returnsNaN() { + VibratorInfo firstVibrator = new VibratorInfo.Builder(/* id= */ 1) + .setQFactor(1f) + .setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, 1, 1, null)) + .build(); + VibratorInfo secondVibrator = new VibratorInfo.Builder(/* id= */ 2) + .setQFactor(2f) + .setFrequencyProfile(new VibratorInfo.FrequencyProfile(2, 2, 2, null)) + .build(); + VibratorInfo info = new SystemVibrator.MultiVibratorInfo( + new VibratorInfo[]{firstVibrator, secondVibrator}); + + assertTrue(Float.isNaN(info.getQFactor())); + assertTrue(Float.isNaN(info.getResonantFrequencyHz())); + + // One vibrator with values undefined. + VibratorInfo thirdVibrator = new VibratorInfo.Builder(/* id= */ 3).build(); + info = new SystemVibrator.MultiVibratorInfo( + new VibratorInfo[]{firstVibrator, thirdVibrator}); + + assertTrue(Float.isNaN(info.getQFactor())); + assertTrue(Float.isNaN(info.getResonantFrequencyHz())); + } + + @Test + public void getQFactorAndResonantFrequency_sameValues_returnsValue() { + VibratorInfo firstVibrator = new VibratorInfo.Builder(/* id= */ 1) + .setQFactor(10f) + .setFrequencyProfile(new VibratorInfo.FrequencyProfile( + /* resonantFrequencyHz= */ 11, 10, 0.5f, null)) + .build(); + VibratorInfo secondVibrator = new VibratorInfo.Builder(/* id= */ 2) + .setQFactor(10f) + .setFrequencyProfile(new VibratorInfo.FrequencyProfile( + /* resonantFrequencyHz= */ 11, 5, 1, null)) + .build(); + VibratorInfo info = new SystemVibrator.MultiVibratorInfo( + new VibratorInfo[]{firstVibrator, secondVibrator}); + + assertEquals(10f, info.getQFactor(), TEST_TOLERANCE); + assertEquals(11f, info.getResonantFrequencyHz(), TEST_TOLERANCE); + } + + @Test + public void getFrequencyProfile_noVibrator_returnsEmpty() { + VibratorInfo info = new SystemVibrator.NoVibratorInfo(); + + assertTrue(info.getFrequencyProfile().isEmpty()); + } + + @Test + public void getFrequencyProfile_differentResonantFrequencyOrResolutionValues_returnsEmpty() { + VibratorInfo firstVibrator = new VibratorInfo.Builder(/* id= */ 1) + .setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, 1, 1, + new float[] { 0, 1 })) + .build(); + VibratorInfo differentResonantFrequency = new VibratorInfo.Builder(/* id= */ 2) + .setFrequencyProfile(new VibratorInfo.FrequencyProfile(2, 1, 1, + new float[] { 0, 1 })) + .build(); + VibratorInfo info = new SystemVibrator.MultiVibratorInfo( + new VibratorInfo[]{firstVibrator, differentResonantFrequency}); + + assertTrue(info.getFrequencyProfile().isEmpty()); + + VibratorInfo differentFrequencyResolution = new VibratorInfo.Builder(/* id= */ 2) + .setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, 1, 2, + new float[] { 0, 1 })) + .build(); + info = new SystemVibrator.MultiVibratorInfo( + new VibratorInfo[]{firstVibrator, differentFrequencyResolution}); + + assertTrue(info.getFrequencyProfile().isEmpty()); + } + + @Test + public void getFrequencyProfile_missingValues_returnsEmpty() { + VibratorInfo firstVibrator = new VibratorInfo.Builder(/* id= */ 1) + .setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, 1, 1, + new float[] { 0, 1 })) + .build(); + VibratorInfo missingResonantFrequency = new VibratorInfo.Builder(/* id= */ 2) + .setFrequencyProfile(new VibratorInfo.FrequencyProfile(Float.NaN, 1, 1, + new float[] { 0, 1 })) + .build(); + VibratorInfo info = new SystemVibrator.MultiVibratorInfo( + new VibratorInfo[]{firstVibrator, missingResonantFrequency}); + + assertTrue(info.getFrequencyProfile().isEmpty()); + + VibratorInfo missingMinFrequency = new VibratorInfo.Builder(/* id= */ 2) + .setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, Float.NaN, 1, + new float[] { 0, 1 })) + .build(); + info = new SystemVibrator.MultiVibratorInfo( + new VibratorInfo[]{firstVibrator, missingMinFrequency}); + + assertTrue(info.getFrequencyProfile().isEmpty()); + + VibratorInfo missingFrequencyResolution = new VibratorInfo.Builder(/* id= */ 2) + .setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, 1, Float.NaN, + new float[] { 0, 1 })) + .build(); + info = new SystemVibrator.MultiVibratorInfo( + new VibratorInfo[]{firstVibrator, missingFrequencyResolution}); + + assertTrue(info.getFrequencyProfile().isEmpty()); + + VibratorInfo missingMaxAmplitudes = new VibratorInfo.Builder(/* id= */ 2) + .setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, 1, 1, null)) + .build(); + info = new SystemVibrator.MultiVibratorInfo( + new VibratorInfo[]{firstVibrator, missingMaxAmplitudes}); + + assertTrue(info.getFrequencyProfile().isEmpty()); + } + + @Test + public void getFrequencyProfile_unalignedMaxAmplitudes_returnsEmpty() { + VibratorInfo firstVibrator = new VibratorInfo.Builder(/* id= */ 1) + .setFrequencyProfile(new VibratorInfo.FrequencyProfile(11, 10, 0.5f, + new float[] { 0, 1, 1, 0 })) + .build(); + VibratorInfo unalignedMinFrequency = new VibratorInfo.Builder(/* id= */ 2) + .setFrequencyProfile(new VibratorInfo.FrequencyProfile(11, 10.1f, 0.5f, + new float[] { 0, 1, 1, 0 })) + .build(); + VibratorInfo thirdVibrator = new VibratorInfo.Builder(/* id= */ 2) + .setFrequencyProfile(new VibratorInfo.FrequencyProfile(11, 10.5f, 0.5f, + new float[] { 0, 1, 1, 0 })) + .build(); + VibratorInfo info = new SystemVibrator.MultiVibratorInfo( + new VibratorInfo[]{firstVibrator, unalignedMinFrequency, thirdVibrator}); + + assertTrue(info.getFrequencyProfile().isEmpty()); + } + + @Test + public void getFrequencyProfile_alignedProfiles_returnsIntersection() { + VibratorInfo firstVibrator = new VibratorInfo.Builder(/* id= */ 1) + .setFrequencyProfile(new VibratorInfo.FrequencyProfile(11, 10, 0.5f, + new float[] { 0.5f, 1, 1, 0.5f })) + .build(); + VibratorInfo secondVibrator = new VibratorInfo.Builder(/* id= */ 2) + .setFrequencyProfile(new VibratorInfo.FrequencyProfile(11, 10.5f, 0.5f, + new float[] { 1, 1, 1 })) + .build(); + VibratorInfo thirdVibrator = new VibratorInfo.Builder(/* id= */ 3) + .setFrequencyProfile(new VibratorInfo.FrequencyProfile(11, 10.5f, 0.5f, + new float[] { 0.8f, 1, 0.8f, 0.5f })) + .build(); + VibratorInfo info = new SystemVibrator.MultiVibratorInfo( + new VibratorInfo[]{firstVibrator, secondVibrator, thirdVibrator}); + + assertEquals( + new VibratorInfo.FrequencyProfile(11, 10.5f, 0.5f, new float[] { 0.8f, 1, 0.5f }), + info.getFrequencyProfile()); + } + + @Test public void vibrate_withVibrationAttributes_usesGivenAttributes() { VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK); VibrationAttributes attributes = new VibrationAttributes.Builder().setUsage( diff --git a/core/tests/coretests/src/android/util/SparseDoubleArrayTest.java b/core/tests/coretests/src/android/util/SparseDoubleArrayTest.java index 2dd3f69852c1..ba9c8d92e173 100644 --- a/core/tests/coretests/src/android/util/SparseDoubleArrayTest.java +++ b/core/tests/coretests/src/android/util/SparseDoubleArrayTest.java @@ -64,12 +64,12 @@ public class SparseDoubleArrayTest { } @Test - public void testAdd() { + public void testIncrementValue() { final SparseDoubleArray sda = new SparseDoubleArray(); sda.put(4, 6.1); - sda.add(4, -1.2); - sda.add(2, -1.2); + sda.incrementValue(4, -1.2); + sda.incrementValue(2, -1.2); assertEquals(6.1 - 1.2, sda.get(4), PRECISION); assertEquals(-1.2, sda.get(2), PRECISION); diff --git a/core/tests/coretests/src/android/util/SparseLongArrayTest.java b/core/tests/coretests/src/android/util/SparseLongArrayTest.java index df2d752e04b9..b29b6f1f8e9d 100644 --- a/core/tests/coretests/src/android/util/SparseLongArrayTest.java +++ b/core/tests/coretests/src/android/util/SparseLongArrayTest.java @@ -154,4 +154,16 @@ public class SparseLongArrayTest { assertRemoved(startIndex, endIndex); assertTrue(isSame(sparseLongArray2, mSparseLongArray)); } + + @Test + public void testIncrementValue() { + final SparseLongArray sla = new SparseLongArray(); + + sla.put(4, 6); + sla.incrementValue(4, 4); + sla.incrementValue(2, 5); + + assertEquals(6 + 4, sla.get(4)); + assertEquals(5, sla.get(2)); + } } diff --git a/core/tests/coretests/src/android/view/HandwritingInitiatorTest.java b/core/tests/coretests/src/android/view/HandwritingInitiatorTest.java index 8f044616e323..5ea91997b1f5 100644 --- a/core/tests/coretests/src/android/view/HandwritingInitiatorTest.java +++ b/core/tests/coretests/src/android/view/HandwritingInitiatorTest.java @@ -33,7 +33,6 @@ import android.app.Instrumentation; import android.content.Context; import android.graphics.Rect; import android.platform.test.annotations.Presubmit; -import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -57,7 +56,6 @@ public class HandwritingInitiatorTest { private static final int TOUCH_SLOP = 8; private static final long TAP_TIMEOUT = ViewConfiguration.getTapTimeout(); private static final Rect sHwArea = new Rect(100, 200, 500, 500); - private static final EditorInfo sFakeEditorInfo = new EditorInfo(); private HandwritingInitiator mHandwritingInitiator; private View mTestView; @@ -72,7 +70,6 @@ public class HandwritingInitiatorTest { InputMethodManager inputMethodManager = context.getSystemService(InputMethodManager.class); mHandwritingInitiator = spy(new HandwritingInitiator(viewConfiguration, inputMethodManager)); - mHandwritingInitiator.updateEditorBound(sHwArea); // mock a parent so that HandwritingInitiator can get ViewGroup parent = new ViewGroup(context) { @@ -82,10 +79,7 @@ public class HandwritingInitiatorTest { } @Override public boolean getChildVisibleRect(View child, Rect r, android.graphics.Point offset) { - r.left = sHwArea.left; - r.top = sHwArea.top; - r.right = sHwArea.right; - r.bottom = sHwArea.bottom; + r.set(sHwArea); return true; } }; @@ -97,7 +91,7 @@ public class HandwritingInitiatorTest { @Test public void onTouchEvent_startHandwriting_when_stylusMoveOnce_withinHWArea() { - mHandwritingInitiator.onInputConnectionCreated(mTestView, sFakeEditorInfo); + mHandwritingInitiator.onInputConnectionCreated(mTestView); final int x1 = (sHwArea.left + sHwArea.right) / 2; final int y1 = (sHwArea.top + sHwArea.bottom) / 2; MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0); @@ -109,13 +103,13 @@ public class HandwritingInitiatorTest { MotionEvent stylusEvent2 = createStylusEvent(ACTION_MOVE, x2, y2, 0); mHandwritingInitiator.onTouchEvent(stylusEvent2); - // Stylus movement win HandwritingArea should trigger IMM.startHandwriting once. + // Stylus movement within HandwritingArea should trigger IMM.startHandwriting once. verify(mHandwritingInitiator, times(1)).startHandwriting(mTestView); } @Test public void onTouchEvent_startHandwritingOnce_when_stylusMoveMultiTimes_withinHWArea() { - mHandwritingInitiator.onInputConnectionCreated(mTestView, sFakeEditorInfo); + mHandwritingInitiator.onInputConnectionCreated(mTestView); final int x1 = (sHwArea.left + sHwArea.right) / 2; final int y1 = (sHwArea.top + sHwArea.bottom) / 2; MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0); @@ -152,14 +146,14 @@ public class HandwritingInitiatorTest { mHandwritingInitiator.onTouchEvent(stylusEvent2); // InputConnection is created after stylus movement. - mHandwritingInitiator.onInputConnectionCreated(mTestView, sFakeEditorInfo); + mHandwritingInitiator.onInputConnectionCreated(mTestView); verify(mHandwritingInitiator, times(1)).startHandwriting(mTestView); } @Test public void onTouchEvent_notStartHandwriting_when_stylusTap_withinHWArea() { - mHandwritingInitiator.onInputConnectionCreated(mTestView, sFakeEditorInfo); + mHandwritingInitiator.onInputConnectionCreated(mTestView); final int x1 = 200; final int y1 = 200; MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0); @@ -175,7 +169,7 @@ public class HandwritingInitiatorTest { @Test public void onTouchEvent_notStartHandwriting_when_stylusMove_outOfHWArea() { - mHandwritingInitiator.onInputConnectionCreated(mTestView, sFakeEditorInfo); + mHandwritingInitiator.onInputConnectionCreated(mTestView); final int x1 = 10; final int y1 = 10; MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0); @@ -191,7 +185,7 @@ public class HandwritingInitiatorTest { @Test public void onTouchEvent_notStartHandwriting_when_stylusMove_afterTapTimeOut() { - mHandwritingInitiator.onInputConnectionCreated(mTestView, sFakeEditorInfo); + mHandwritingInitiator.onInputConnectionCreated(mTestView); final int x1 = 10; final int y1 = 10; final long time1 = 10L; @@ -210,18 +204,17 @@ public class HandwritingInitiatorTest { @Test public void onInputConnectionCreated_inputConnectionCreated() { - mHandwritingInitiator.onInputConnectionCreated(mTestView, sFakeEditorInfo); + mHandwritingInitiator.onInputConnectionCreated(mTestView); assertThat(mHandwritingInitiator.mConnectedView).isNotNull(); assertThat(mHandwritingInitiator.mConnectedView.get()).isEqualTo(mTestView); } @Test public void onInputConnectionCreated_inputConnectionClosed() { - mHandwritingInitiator.onInputConnectionCreated(mTestView, sFakeEditorInfo); + mHandwritingInitiator.onInputConnectionCreated(mTestView); mHandwritingInitiator.onInputConnectionClosed(mTestView); assertThat(mHandwritingInitiator.mConnectedView).isNull(); - assertThat(mHandwritingInitiator.mEditorBound).isNull(); } @Test @@ -229,22 +222,14 @@ public class HandwritingInitiatorTest { // When IMM restarts input connection, View#onInputConnectionCreatedInternal might be // called before View#onInputConnectionClosedInternal. As a result, we need to handle the // case where "one view "2 InputConnections". - mHandwritingInitiator.onInputConnectionCreated(mTestView, sFakeEditorInfo); - mHandwritingInitiator.onInputConnectionCreated(mTestView, sFakeEditorInfo); + mHandwritingInitiator.onInputConnectionCreated(mTestView); + mHandwritingInitiator.onInputConnectionCreated(mTestView); mHandwritingInitiator.onInputConnectionClosed(mTestView); assertThat(mHandwritingInitiator.mConnectedView).isNotNull(); assertThat(mHandwritingInitiator.mConnectedView.get()).isEqualTo(mTestView); } - @Test - public void updateEditorBound() { - Rect rect = new Rect(1, 2, 3, 4); - mHandwritingInitiator.updateEditorBound(rect); - - assertThat(mHandwritingInitiator.mEditorBound).isEqualTo(rect); - } - private MotionEvent createStylusEvent(int action, int x, int y, long eventTime) { MotionEvent.PointerProperties[] properties = MotionEvent.PointerProperties.createArray(1); properties[0].toolType = MotionEvent.TOOL_TYPE_STYLUS; diff --git a/core/tests/coretests/src/android/view/SurfaceControlFpsListenerTest.java b/core/tests/coretests/src/android/view/SurfaceControlFpsListenerTest.java deleted file mode 100644 index c15fc3a15112..000000000000 --- a/core/tests/coretests/src/android/view/SurfaceControlFpsListenerTest.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.view; - -import android.platform.test.annotations.Presubmit; - -import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; - -import org.junit.Test; -import org.junit.runner.RunWith; - -@RunWith(AndroidJUnit4.class) -@SmallTest -@Presubmit -public class SurfaceControlFpsListenerTest { - - @Test - public void registersAndUnregisters() { - - SurfaceControlFpsListener listener = new SurfaceControlFpsListener() { - @Override - public void onFpsReported(float fps) { - // Ignore - } - }; - - listener.register(0); - - listener.unregister(); - } -} diff --git a/core/tests/coretests/src/android/window/TaskFpsCallbackTest.java b/core/tests/coretests/src/android/window/TaskFpsCallbackTest.java new file mode 100644 index 000000000000..bf508db56852 --- /dev/null +++ b/core/tests/coretests/src/android/window/TaskFpsCallbackTest.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.window; + +import static org.junit.Assert.assertEquals; + +import android.app.ActivityManager; +import android.app.ActivityTaskManager; +import android.content.Context; +import android.platform.test.annotations.Presubmit; +import android.view.WindowManager; + +import androidx.test.filters.SmallTest; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.List; + +@RunWith(AndroidJUnit4.class) +@SmallTest +@Presubmit +public class TaskFpsCallbackTest { + + private Context mContext; + private WindowManager mWindowManager; + private ActivityTaskManager mActivityTaskManager; + + @Before + public void setup() { + mContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + mActivityTaskManager = mContext.getSystemService(ActivityTaskManager.class); + mWindowManager = mContext.getSystemService(WindowManager.class); + } + + @Test + public void testRegisterAndUnregister() { + + final TaskFpsCallback.OnFpsCallbackListener listener = fps -> { + // Ignore + }; + final TaskFpsCallback callback = new TaskFpsCallback(Runnable::run, listener); + + final List<ActivityManager.RunningTaskInfo> tasks = mActivityTaskManager.getTasks(1); + assertEquals(tasks.size(), 1); + mWindowManager.registerTaskFpsCallback(tasks.get(0).taskId, callback); + mWindowManager.unregisterTaskFpsCallback(callback); + } +} diff --git a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java new file mode 100644 index 000000000000..a1a1e20d6982 --- /dev/null +++ b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.window; + +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.verifyZeroInteractions; + +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; +import android.view.IWindow; +import android.view.IWindowSession; +import android.view.OnBackInvokedCallback; +import android.view.OnBackInvokedDispatcher; + +import androidx.test.filters.SmallTest; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; + +/** + * Tests for {@link WindowOnBackInvokedDispatcherTest} + * + * <p>Build/Install/Run: + * atest FrameworksCoreTests:WindowOnBackInvokedDispatcherTest + */ +@RunWith(AndroidJUnit4.class) +@SmallTest +@Presubmit +public class WindowOnBackInvokedDispatcherTest { + @Mock + private IWindowSession mWindowSession; + @Mock + private IWindow mWindow; + private WindowOnBackInvokedDispatcher mDispatcher; + @Mock + private OnBackInvokedCallback mCallback1; + @Mock + private OnBackInvokedCallback mCallback2; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + mDispatcher = new WindowOnBackInvokedDispatcher(); + mDispatcher.attachToWindow(mWindowSession, mWindow); + } + + private void waitForIdle() { + InstrumentationRegistry.getInstrumentation().waitForIdleSync(); + } + + @Test + public void propagatesTopCallback_samePriority() throws RemoteException { + ArgumentCaptor<IOnBackInvokedCallback> captor = + ArgumentCaptor.forClass(IOnBackInvokedCallback.class); + + mDispatcher.registerOnBackInvokedCallback( + mCallback1, OnBackInvokedDispatcher.PRIORITY_DEFAULT); + mDispatcher.registerOnBackInvokedCallback( + mCallback2, OnBackInvokedDispatcher.PRIORITY_DEFAULT); + + verify(mWindowSession, times(2)) + .setOnBackInvokedCallback(Mockito.eq(mWindow), captor.capture()); + captor.getAllValues().get(0).onBackStarted(); + waitForIdle(); + verify(mCallback1).onBackStarted(); + verifyZeroInteractions(mCallback2); + + captor.getAllValues().get(1).onBackStarted(); + waitForIdle(); + verify(mCallback2).onBackStarted(); + verifyNoMoreInteractions(mCallback1); + } + + @Test + public void propagatesTopCallback_differentPriority() throws RemoteException { + ArgumentCaptor<IOnBackInvokedCallback> captor = + ArgumentCaptor.forClass(IOnBackInvokedCallback.class); + + mDispatcher.registerOnBackInvokedCallback( + mCallback1, OnBackInvokedDispatcher.PRIORITY_OVERLAY); + mDispatcher.registerOnBackInvokedCallback( + mCallback2, OnBackInvokedDispatcher.PRIORITY_DEFAULT); + + verify(mWindowSession) + .setOnBackInvokedCallback(Mockito.eq(mWindow), captor.capture()); + verifyNoMoreInteractions(mWindowSession); + captor.getValue().onBackStarted(); + waitForIdle(); + verify(mCallback1).onBackStarted(); + } + + @Test + public void propagatesTopCallback_withRemoval() throws RemoteException { + mDispatcher.registerOnBackInvokedCallback( + mCallback1, OnBackInvokedDispatcher.PRIORITY_DEFAULT); + mDispatcher.registerOnBackInvokedCallback( + mCallback2, OnBackInvokedDispatcher.PRIORITY_DEFAULT); + + reset(mWindowSession); + mDispatcher.unregisterOnBackInvokedCallback(mCallback1); + verifyZeroInteractions(mWindowSession); + + mDispatcher.unregisterOnBackInvokedCallback(mCallback2); + verify(mWindowSession).setOnBackInvokedCallback(Mockito.eq(mWindow), isNull()); + } + + + @Test + public void propagatesTopCallback_sameInstanceAddedTwice() throws RemoteException { + ArgumentCaptor<IOnBackInvokedCallback> captor = + ArgumentCaptor.forClass(IOnBackInvokedCallback.class); + + mDispatcher.registerOnBackInvokedCallback(mCallback1, + OnBackInvokedDispatcher.PRIORITY_OVERLAY); + mDispatcher.registerOnBackInvokedCallback( + mCallback2, OnBackInvokedDispatcher.PRIORITY_DEFAULT); + mDispatcher.registerOnBackInvokedCallback( + mCallback1, OnBackInvokedDispatcher.PRIORITY_DEFAULT); + + reset(mWindowSession); + mDispatcher.registerOnBackInvokedCallback( + mCallback2, OnBackInvokedDispatcher.PRIORITY_OVERLAY); + verify(mWindowSession) + .setOnBackInvokedCallback(Mockito.eq(mWindow), captor.capture()); + captor.getValue().onBackStarted(); + waitForIdle(); + verify(mCallback2).onBackStarted(); + } +} diff --git a/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java b/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java index 43590bae6770..1f6b57e5d615 100644 --- a/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java +++ b/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java @@ -297,7 +297,7 @@ public class IntentForwarderActivityTest { mActivityRule.launchActivity(intent); verify(mIPm).canForwardTo(any(), any(), anyInt(), anyInt()); - verify(sInjector).showToast(anyInt(), anyInt()); + verify(sInjector).showToast(anyString(), anyInt()); } @Test @@ -312,7 +312,7 @@ public class IntentForwarderActivityTest { mActivityRule.launchActivity(intent); verify(mIPm).canForwardTo(any(), any(), anyInt(), anyInt()); - verify(sInjector, never()).showToast(anyInt(), anyInt()); + verify(sInjector, never()).showToast(anyString(), anyInt()); } @Test @@ -324,7 +324,7 @@ public class IntentForwarderActivityTest { mActivityRule.launchActivity(intent); verify(mIPm).canForwardTo(any(), any(), anyInt(), anyInt()); - verify(sInjector, never()).showToast(anyInt(), anyInt()); + verify(sInjector, never()).showToast(anyString(), anyInt()); } @Test @@ -336,7 +336,7 @@ public class IntentForwarderActivityTest { mActivityRule.launchActivity(intent); verify(mIPm).canForwardTo(any(), any(), anyInt(), anyInt()); - verify(sInjector, never()).showToast(anyInt(), anyInt()); + verify(sInjector, never()).showToast(anyString(), anyInt()); } @Test @@ -348,7 +348,7 @@ public class IntentForwarderActivityTest { mActivityRule.launchActivity(intent); verify(mIPm).canForwardTo(any(), any(), anyInt(), anyInt()); - verify(sInjector, never()).showToast(anyInt(), anyInt()); + verify(sInjector, never()).showToast(anyString(), anyInt()); } @Test @@ -360,7 +360,7 @@ public class IntentForwarderActivityTest { mActivityRule.launchActivity(intent); verify(mIPm).canForwardTo(any(), any(), anyInt(), anyInt()); - verify(sInjector, never()).showToast(anyInt(), anyInt()); + verify(sInjector, never()).showToast(anyString(), anyInt()); } @Test @@ -372,7 +372,7 @@ public class IntentForwarderActivityTest { mActivityRule.launchActivity(intent); verify(mIPm).canForwardTo(any(), any(), anyInt(), anyInt()); - verify(sInjector).showToast(anyInt(), anyInt()); + verify(sInjector).showToast(anyString(), anyInt()); } @Test @@ -386,7 +386,7 @@ public class IntentForwarderActivityTest { mActivityRule.launchActivity(intent); verify(mIPm).canForwardTo(any(), any(), anyInt(), anyInt()); - verify(sInjector, never()).showToast(anyInt(), anyInt()); + verify(sInjector, never()).showToast(anyString(), anyInt()); } @Test @@ -399,7 +399,7 @@ public class IntentForwarderActivityTest { mActivityRule.launchActivity(intent); verify(mIPm).canForwardTo(any(), any(), anyInt(), anyInt()); - verify(sInjector, never()).showToast(anyInt(), anyInt()); + verify(sInjector, never()).showToast(anyString(), anyInt()); } @Test @@ -412,7 +412,7 @@ public class IntentForwarderActivityTest { mActivityRule.launchActivity(intent); verify(mIPm).canForwardTo(any(), any(), anyInt(), anyInt()); - verify(sInjector, never()).showToast(anyInt(), anyInt()); + verify(sInjector, never()).showToast(anyString(), anyInt()); } @Test @@ -425,7 +425,7 @@ public class IntentForwarderActivityTest { mActivityRule.launchActivity(intent); verify(mIPm).canForwardTo(any(), any(), anyInt(), anyInt()); - verify(sInjector, never()).showToast(anyInt(), anyInt()); + verify(sInjector, never()).showToast(anyString(), anyInt()); } @Test @@ -438,7 +438,7 @@ public class IntentForwarderActivityTest { mActivityRule.launchActivity(intent); verify(mIPm).canForwardTo(any(), any(), anyInt(), anyInt()); - verify(sInjector, never()).showToast(anyInt(), anyInt()); + verify(sInjector, never()).showToast(anyString(), anyInt()); } @Test @@ -452,7 +452,7 @@ public class IntentForwarderActivityTest { mActivityRule.launchActivity(intent); verify(mIPm).canForwardTo(any(), any(), anyInt(), anyInt()); - verify(sInjector, never()).showToast(anyInt(), anyInt()); + verify(sInjector, never()).showToast(anyString(), anyInt()); } @Test @@ -466,7 +466,7 @@ public class IntentForwarderActivityTest { mActivityRule.launchActivity(intent); verify(mIPm).canForwardTo(any(), any(), anyInt(), anyInt()); - verify(sInjector, never()).showToast(anyInt(), anyInt()); + verify(sInjector, never()).showToast(anyString(), anyInt()); } @Test @@ -480,7 +480,7 @@ public class IntentForwarderActivityTest { mActivityRule.launchActivity(intent); verify(mIPm).canForwardTo(any(), any(), anyInt(), anyInt()); - verify(sInjector, never()).showToast(anyInt(), anyInt()); + verify(sInjector, never()).showToast(anyString(), anyInt()); } @Test @@ -494,7 +494,7 @@ public class IntentForwarderActivityTest { mActivityRule.launchActivity(intent); verify(mIPm).canForwardTo(any(), any(), anyInt(), anyInt()); - verify(sInjector, never()).showToast(anyInt(), anyInt()); + verify(sInjector, never()).showToast(anyString(), anyInt()); } @Test @@ -507,7 +507,7 @@ public class IntentForwarderActivityTest { mActivityRule.launchActivity(intent); verify(mIPm).canForwardTo(any(), any(), anyInt(), anyInt()); - verify(sInjector).showToast(anyInt(), anyInt()); + verify(sInjector).showToast(anyString(), anyInt()); } @Test @@ -521,7 +521,7 @@ public class IntentForwarderActivityTest { mActivityRule.launchActivity(intent); verify(mIPm).canForwardTo(any(), any(), anyInt(), anyInt()); - verify(sInjector).showToast(anyInt(), anyInt()); + verify(sInjector).showToast(anyString(), anyInt()); } @Test @@ -535,7 +535,7 @@ public class IntentForwarderActivityTest { mActivityRule.launchActivity(intent); verify(mIPm).canForwardTo(any(), any(), anyInt(), anyInt()); - verify(sInjector).showToast(anyInt(), anyInt()); + verify(sInjector).showToast(anyString(), anyInt()); } @Test @@ -551,7 +551,7 @@ public class IntentForwarderActivityTest { mActivityRule.launchActivity(intent); verify(mIPm).canForwardTo(any(), any(), anyInt(), anyInt()); - verify(sInjector, never()).showToast(anyInt(), anyInt()); + verify(sInjector, never()).showToast(anyString(), anyInt()); } @Test @@ -692,6 +692,6 @@ public class IntentForwarderActivityTest { } @Override - public void showToast(int messageId, int duration) {} + public void showToast(String message, int duration) {} } } diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java index 388cf6e15e0b..be8045ddc7b2 100644 --- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java +++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java @@ -37,8 +37,12 @@ import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.when; import android.app.ActivityManager; +import android.bluetooth.BluetoothActivityEnergyInfo; +import android.bluetooth.UidTraffic; import android.os.BatteryStats; +import android.os.BluetoothBatteryStats; import android.os.WakeLockStats; +import android.os.WorkSource; import android.util.SparseArray; import android.view.Display; @@ -47,6 +51,8 @@ import androidx.test.runner.AndroidJUnit4; import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidFreqTimeReader; +import com.google.common.collect.ImmutableList; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -66,6 +72,8 @@ public class BatteryStatsImplTest { private KernelCpuUidFreqTimeReader mKernelUidCpuFreqTimeReader; @Mock private KernelSingleUidTimeReader mKernelSingleUidTimeReader; + @Mock + private PowerProfile mPowerProfile; private final MockClock mMockClock = new MockClock(); private MockBatteryStatsImpl mBatteryStatsImpl; @@ -79,6 +87,7 @@ public class BatteryStatsImplTest { when(mKernelUidCpuFreqTimeReader.allUidTimesAvailable()).thenReturn(true); when(mKernelSingleUidTimeReader.singleUidCpuTimesAvailable()).thenReturn(true); mBatteryStatsImpl = new MockBatteryStatsImpl(mMockClock) + .setPowerProfile(mPowerProfile) .setKernelCpuUidFreqTimeReader(mKernelUidCpuFreqTimeReader) .setKernelSingleUidTimeReader(mKernelSingleUidTimeReader); } @@ -559,4 +568,38 @@ public class BatteryStatsImplTest { assertThat(wakeLock2.timeHeldMs).isEqualTo(3000); // 9000-6000 assertThat(wakeLock2.totalTimeHeldMs).isEqualTo(4000); // (5000-4000) + (9000-6000) } + + @Test + public void testGetBluetoothBatteryStats() { + when(mPowerProfile.getAveragePower( + PowerProfile.POWER_BLUETOOTH_CONTROLLER_OPERATING_VOLTAGE)).thenReturn(3.0); + mBatteryStatsImpl.setOnBatteryInternal(true); + mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0); + + final WorkSource ws = new WorkSource(10042); + mBatteryStatsImpl.noteBluetoothScanStartedFromSourceLocked(ws, false, 1000, 1000); + mBatteryStatsImpl.noteBluetoothScanStoppedFromSourceLocked(ws, false, 5000, 5000); + mBatteryStatsImpl.noteBluetoothScanStartedFromSourceLocked(ws, true, 6000, 6000); + mBatteryStatsImpl.noteBluetoothScanStoppedFromSourceLocked(ws, true, 9000, 9000); + mBatteryStatsImpl.noteBluetoothScanResultsFromSourceLocked(ws, 42, 9000, 9000); + + BluetoothActivityEnergyInfo info = new BluetoothActivityEnergyInfo(1000, + BluetoothActivityEnergyInfo.BT_STACK_STATE_STATE_ACTIVE, 9000, 8000, 12000, 0); + info.setUidTraffic(ImmutableList.of( + new UidTraffic(10042, 3000, 4000), + new UidTraffic(10043, 5000, 8000))); + mBatteryStatsImpl.updateBluetoothStateLocked(info, -1, 1000, 1000); + + BluetoothBatteryStats stats = + mBatteryStatsImpl.getBluetoothBatteryStats(); + assertThat(stats.getUidStats()).hasSize(2); + + final BluetoothBatteryStats.UidStats uidStats = + stats.getUidStats().stream().filter(u -> u.uid == 10042).findFirst().get(); + assertThat(uidStats.scanTimeMs).isEqualTo(7000); // 4000+3000 + assertThat(uidStats.unoptimizedScanTimeMs).isEqualTo(3000); + assertThat(uidStats.scanResultCount).isEqualTo(42); + assertThat(uidStats.rxTimeMs).isEqualTo(7375); // Some scan time is treated as RX + assertThat(uidStats.txTimeMs).isEqualTo(7666); // Some scan time is treated as TX + } } diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java index 9699275d3037..8cc4c348111c 100644 --- a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java +++ b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java @@ -83,7 +83,7 @@ public class BatteryUsageStatsTest { final Parcel parcel = Parcel.obtain(); parcel.writeParcelable(outBatteryUsageStats, 0); - assertThat(parcel.dataSize()).isLessThan(6000); + assertThat(parcel.dataSize()).isLessThan(7000); parcel.setDataPosition(0); diff --git a/core/tests/coretests/src/com/android/internal/os/BluetoothPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/BluetoothPowerCalculatorTest.java index d361da95a1b9..ed035e5166b3 100644 --- a/core/tests/coretests/src/com/android/internal/os/BluetoothPowerCalculatorTest.java +++ b/core/tests/coretests/src/com/android/internal/os/BluetoothPowerCalculatorTest.java @@ -25,18 +25,21 @@ import android.os.BatteryConsumer; import android.os.BatteryStats; import android.os.BatteryUsageStatsQuery; import android.os.Process; +import android.os.UidBatteryConsumer; +import android.os.WorkSource; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.google.common.collect.ImmutableList; + import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; -import java.util.ArrayList; - @RunWith(AndroidJUnit4.class) @SmallTest +@SuppressWarnings("GuardedBy") public class BluetoothPowerCalculatorTest { private static final double PRECISION = 0.00001; private static final int APP_UID = Process.FIRST_APPLICATION_UID + 42; @@ -50,6 +53,12 @@ public class BluetoothPowerCalculatorTest { @Test public void testTimerBasedModel() { + BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats(); + + final WorkSource ws = new WorkSource(APP_UID); + batteryStats.noteBluetoothScanStartedFromSourceLocked(ws, false, 0, 0); + batteryStats.noteBluetoothScanStoppedFromSourceLocked(ws, false, 1000, 1000); + setupBluetoothEnergyInfo(0, BatteryStats.POWER_DATA_UNAVAILABLE); BluetoothPowerCalculator calculator = @@ -57,8 +66,81 @@ public class BluetoothPowerCalculatorTest { mStatsRule.apply(BatteryUsageStatsRule.POWER_PROFILE_MODEL_ONLY, calculator); - assertCalculatedPower(0.08216, 0.18169, 0.26388, 0.26386, - BatteryConsumer.POWER_MODEL_POWER_PROFILE); + assertBluetoothPowerAndDuration( + mStatsRule.getUidBatteryConsumer(Process.BLUETOOTH_UID), + 0.06944, 3000, BatteryConsumer.POWER_MODEL_POWER_PROFILE); + assertBluetoothPowerAndDuration( + mStatsRule.getUidBatteryConsumer(APP_UID), + 0.19444, 9000, BatteryConsumer.POWER_MODEL_POWER_PROFILE); + assertBluetoothPowerAndDuration( + mStatsRule.getDeviceBatteryConsumer(), + 0.26388, 12000, BatteryConsumer.POWER_MODEL_POWER_PROFILE); + assertBluetoothPowerAndDuration( + mStatsRule.getAppsBatteryConsumer(), + 0.26388, 12000, BatteryConsumer.POWER_MODEL_POWER_PROFILE); + } + + @Test + public void testTimerBasedModel_byProcessState() { + mStatsRule.setTime(1000, 1000); + + BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats(); + + BatteryStatsImpl.Uid uid = batteryStats.getUidStatsLocked(APP_UID); + uid.setProcessStateForTest( + BatteryStats.Uid.PROCESS_STATE_FOREGROUND, 1000); + + BluetoothActivityEnergyInfo info1 = new BluetoothActivityEnergyInfo(2000, + BluetoothActivityEnergyInfo.BT_STACK_STATE_STATE_ACTIVE, 1000, 2000, 3000, 4000); + info1.setUidTraffic(ImmutableList.of( + new UidTraffic(Process.BLUETOOTH_UID, 1000, 2000), + new UidTraffic(APP_UID, 3000, 4000))); + + batteryStats.updateBluetoothStateLocked(info1, + 0/*1_000_000*/, 2000, 2000); + + uid.setProcessStateForTest( + BatteryStats.Uid.PROCESS_STATE_BACKGROUND, 3000); + + BluetoothActivityEnergyInfo info2 = new BluetoothActivityEnergyInfo(4000, + BluetoothActivityEnergyInfo.BT_STACK_STATE_STATE_ACTIVE, 5000, 6000, 7000, 8000); + info2.setUidTraffic(ImmutableList.of( + new UidTraffic(Process.BLUETOOTH_UID, 5000, 6000), + new UidTraffic(APP_UID, 7000, 8000))); + + batteryStats.updateBluetoothStateLocked(info2, + 0 /*5_000_000 */, 4000, 4000); + + BluetoothPowerCalculator calculator = + new BluetoothPowerCalculator(mStatsRule.getPowerProfile()); + + mStatsRule.apply(new BatteryUsageStatsQuery.Builder() + .powerProfileModeledOnly() + .includePowerModels() + .includeProcessStateData() + .build(), calculator); + + UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID); + assertThat(uidConsumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_BLUETOOTH)) + .isEqualTo(6166); + assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_BLUETOOTH)) + .isWithin(PRECISION).of(0.1226666); + assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_BLUETOOTH)) + .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE); + + final BatteryConsumer.Key foreground = uidConsumer.getKey( + BatteryConsumer.POWER_COMPONENT_BLUETOOTH, + BatteryConsumer.PROCESS_STATE_FOREGROUND); + final BatteryConsumer.Key background = uidConsumer.getKey( + BatteryConsumer.POWER_COMPONENT_BLUETOOTH, + BatteryConsumer.PROCESS_STATE_BACKGROUND); + final BatteryConsumer.Key fgs = uidConsumer.getKey( + BatteryConsumer.POWER_COMPONENT_BLUETOOTH, + BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE); + + assertThat(uidConsumer.getConsumedPower(foreground)).isWithin(PRECISION).of(0.081); + assertThat(uidConsumer.getConsumedPower(background)).isWithin(PRECISION).of(0.0416666); + assertThat(uidConsumer.getConsumedPower(fgs)).isWithin(PRECISION).of(0); } @Test @@ -71,8 +153,18 @@ public class BluetoothPowerCalculatorTest { mStatsRule.apply(new BatteryUsageStatsQuery.Builder().includePowerModels().build(), calculator); - assertCalculatedPower(0.08216, 0.18169, 0.30030, 0.26386, - BatteryConsumer.POWER_MODEL_POWER_PROFILE); + assertBluetoothPowerAndDuration( + mStatsRule.getUidBatteryConsumer(Process.BLUETOOTH_UID), + 0.08216, 3583, BatteryConsumer.POWER_MODEL_POWER_PROFILE); + assertBluetoothPowerAndDuration( + mStatsRule.getUidBatteryConsumer(APP_UID), + 0.18169, 8416, BatteryConsumer.POWER_MODEL_POWER_PROFILE); + assertBluetoothPowerAndDuration( + mStatsRule.getDeviceBatteryConsumer(), + 0.30030, 12000, BatteryConsumer.POWER_MODEL_POWER_PROFILE); + assertBluetoothPowerAndDuration( + mStatsRule.getAppsBatteryConsumer(), + 0.26386, 11999, BatteryConsumer.POWER_MODEL_POWER_PROFILE); } @Test @@ -85,10 +177,84 @@ public class BluetoothPowerCalculatorTest { mStatsRule.apply(calculator); - assertCalculatedPower(0.10378, 0.22950, 0.33333, 0.33329, - BatteryConsumer.POWER_MODEL_MEASURED_ENERGY); + assertBluetoothPowerAndDuration( + mStatsRule.getUidBatteryConsumer(Process.BLUETOOTH_UID), + 0.10378, 3583, BatteryConsumer.POWER_MODEL_MEASURED_ENERGY); + assertBluetoothPowerAndDuration( + mStatsRule.getUidBatteryConsumer(APP_UID), + 0.22950, 8416, BatteryConsumer.POWER_MODEL_MEASURED_ENERGY); + assertBluetoothPowerAndDuration( + mStatsRule.getDeviceBatteryConsumer(), + 0.33333, 12000, BatteryConsumer.POWER_MODEL_MEASURED_ENERGY); + assertBluetoothPowerAndDuration( + mStatsRule.getAppsBatteryConsumer(), + 0.33329, 11999, BatteryConsumer.POWER_MODEL_MEASURED_ENERGY); + } + + @Test + public void testMeasuredEnergyBasedModel_byProcessState() { + mStatsRule.initMeasuredEnergyStatsLocked(); + mStatsRule.setTime(1000, 1000); + + BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats(); + + BatteryStatsImpl.Uid uid = batteryStats.getUidStatsLocked(APP_UID); + uid.setProcessStateForTest( + BatteryStats.Uid.PROCESS_STATE_FOREGROUND, 1000); + + BluetoothActivityEnergyInfo info1 = new BluetoothActivityEnergyInfo(2000, + BluetoothActivityEnergyInfo.BT_STACK_STATE_STATE_ACTIVE, 1000, 2000, 3000, 4000); + info1.setUidTraffic(ImmutableList.of( + new UidTraffic(Process.BLUETOOTH_UID, 1000, 2000), + new UidTraffic(APP_UID, 3000, 4000))); + + batteryStats.updateBluetoothStateLocked(info1, + 1_000_000, 2000, 2000); + + uid.setProcessStateForTest( + BatteryStats.Uid.PROCESS_STATE_BACKGROUND, 3000); + + BluetoothActivityEnergyInfo info2 = new BluetoothActivityEnergyInfo(4000, + BluetoothActivityEnergyInfo.BT_STACK_STATE_STATE_ACTIVE, 5000, 6000, 7000, 8000); + info2.setUidTraffic(ImmutableList.of( + new UidTraffic(Process.BLUETOOTH_UID, 5000, 6000), + new UidTraffic(APP_UID, 7000, 8000))); + + batteryStats.updateBluetoothStateLocked(info2, + 5_000_000, 4000, 4000); + + BluetoothPowerCalculator calculator = + new BluetoothPowerCalculator(mStatsRule.getPowerProfile()); + + mStatsRule.apply(new BatteryUsageStatsQuery.Builder() + .includePowerModels() + .includeProcessStateData() + .build(), calculator); + + UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID); + assertThat(uidConsumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_BLUETOOTH)) + .isEqualTo(6166); + assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_BLUETOOTH)) + .isWithin(PRECISION).of(0.8220561); + assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_BLUETOOTH)) + .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY); + + final BatteryConsumer.Key foreground = uidConsumer.getKey( + BatteryConsumer.POWER_COMPONENT_BLUETOOTH, + BatteryConsumer.PROCESS_STATE_FOREGROUND); + final BatteryConsumer.Key background = uidConsumer.getKey( + BatteryConsumer.POWER_COMPONENT_BLUETOOTH, + BatteryConsumer.PROCESS_STATE_BACKGROUND); + final BatteryConsumer.Key fgs = uidConsumer.getKey( + BatteryConsumer.POWER_COMPONENT_BLUETOOTH, + BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE); + + assertThat(uidConsumer.getConsumedPower(foreground)).isWithin(PRECISION).of(0.4965352); + assertThat(uidConsumer.getConsumedPower(background)).isWithin(PRECISION).of(0.3255208); + assertThat(uidConsumer.getConsumedPower(fgs)).isWithin(PRECISION).of(0); } + @Test public void testIgnoreMeasuredEnergyBasedModel() { mStatsRule.initMeasuredEnergyStatsLocked(); @@ -99,36 +265,29 @@ public class BluetoothPowerCalculatorTest { mStatsRule.apply(BatteryUsageStatsRule.POWER_PROFILE_MODEL_ONLY, calculator); - assertCalculatedPower(0.08216, 0.18169, 0.26388, 0.26386, - BatteryConsumer.POWER_MODEL_POWER_PROFILE); - } - - private void setupBluetoothEnergyInfo(long reportedEnergyUc, long consumedEnergyUc) { - final BluetoothActivityEnergyInfo info = new BluetoothActivityEnergyInfo(1000, - BluetoothActivityEnergyInfo.BT_STACK_STATE_STATE_ACTIVE, 7000, 5000, 0, - reportedEnergyUc); - info.setUidTraffic(new ArrayList<UidTraffic>(){{ - add(new UidTraffic(Process.BLUETOOTH_UID, 1000, 2000)); - add(new UidTraffic(APP_UID, 3000, 4000)); - }}); - mStatsRule.getBatteryStats().updateBluetoothStateLocked(info, - consumedEnergyUc, 1000, 1000); - } - - private void assertCalculatedPower(double bluetoothUidPowerMah, double appPowerMah, - double devicePowerMah, double allAppsPowerMah, int powerModelPowerProfile) { assertBluetoothPowerAndDuration( mStatsRule.getUidBatteryConsumer(Process.BLUETOOTH_UID), - bluetoothUidPowerMah, 3583, powerModelPowerProfile); + 0.08216, 3583, BatteryConsumer.POWER_MODEL_POWER_PROFILE); assertBluetoothPowerAndDuration( mStatsRule.getUidBatteryConsumer(APP_UID), - appPowerMah, 8416, powerModelPowerProfile); + 0.18169, 8416, BatteryConsumer.POWER_MODEL_POWER_PROFILE); assertBluetoothPowerAndDuration( mStatsRule.getDeviceBatteryConsumer(), - devicePowerMah, 12000, powerModelPowerProfile); + 0.26388, 12000, BatteryConsumer.POWER_MODEL_POWER_PROFILE); assertBluetoothPowerAndDuration( mStatsRule.getAppsBatteryConsumer(), - allAppsPowerMah, 11999, powerModelPowerProfile); + 0.26386, 11999, BatteryConsumer.POWER_MODEL_POWER_PROFILE); + } + + private void setupBluetoothEnergyInfo(long reportedEnergyUc, long consumedEnergyUc) { + final BluetoothActivityEnergyInfo info = new BluetoothActivityEnergyInfo(1000, + BluetoothActivityEnergyInfo.BT_STACK_STATE_STATE_ACTIVE, 7000, 5000, 0, + reportedEnergyUc); + info.setUidTraffic(ImmutableList.of( + new UidTraffic(Process.BLUETOOTH_UID, 1000, 2000), + new UidTraffic(APP_UID, 3000, 4000))); + mStatsRule.getBatteryStats().updateBluetoothStateLocked(info, + consumedEnergyUc, 1000, 1000); } private void assertBluetoothPowerAndDuration(@Nullable BatteryConsumer batteryConsumer, diff --git a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java index bddb3a1906fd..1bb41a8cfffd 100644 --- a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java +++ b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java @@ -43,6 +43,7 @@ import java.util.concurrent.Future; */ public class MockBatteryStatsImpl extends BatteryStatsImpl { public boolean mForceOnBattery; + // The mNetworkStats will be used for both wifi and mobile categories private NetworkStats mNetworkStats; private DummyExternalStatsSync mExternalStatsSync = new DummyExternalStatsSync(); @@ -118,11 +119,16 @@ public class MockBatteryStatsImpl extends BatteryStatsImpl { } @Override - protected NetworkStats readNetworkStatsLocked(@NonNull NetworkStatsManager networkStatsManager, - String[] ifaces) { + protected NetworkStats readMobileNetworkStatsLocked( + @NonNull NetworkStatsManager networkStatsManager) { return mNetworkStats; } + @Override + protected NetworkStats readWifiNetworkStatsLocked( + @NonNull NetworkStatsManager networkStatsManager) { + return mNetworkStats; + } public MockBatteryStatsImpl setPowerProfile(PowerProfile powerProfile) { mPowerProfile = powerProfile; return this; diff --git a/core/tests/coretests/src/com/android/internal/os/PowerProfileTest.java b/core/tests/coretests/src/com/android/internal/os/PowerProfileTest.java index 88ee405483db..bc3b4229f5e5 100644 --- a/core/tests/coretests/src/com/android/internal/os/PowerProfileTest.java +++ b/core/tests/coretests/src/com/android/internal/os/PowerProfileTest.java @@ -21,29 +21,49 @@ import static com.android.internal.os.PowerProfile.POWER_GROUP_DISPLAY_AMBIENT; import static com.android.internal.os.PowerProfile.POWER_GROUP_DISPLAY_SCREEN_FULL; import static com.android.internal.os.PowerProfile.POWER_GROUP_DISPLAY_SCREEN_ON; +import android.annotation.XmlRes; +import android.content.Context; +import android.content.res.Resources; +import android.content.res.XmlResourceParser; + import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; +import com.android.frameworks.coretests.R; +import com.android.internal.power.ModemPowerProfile; +import com.android.internal.util.XmlUtils; + import junit.framework.TestCase; import org.junit.Before; import org.junit.Test; /* - * Keep this file in sync with frameworks/base/core/res/res/xml/power_profile_test.xml + * Keep this file in sync with frameworks/base/core/res/res/xml/power_profile_test.xml and + * frameworks/base/core/tests/coretests/res/xml/power_profile_test_modem.xml + * + * Run with: + * atest com.android.internal.os.PowerProfileTest */ @SmallTest public class PowerProfileTest extends TestCase { + static final String TAG_TEST_MODEM = "test-modem"; + static final String ATTR_NAME = "name"; + private PowerProfile mProfile; + private Context mContext; @Before public void setUp() { - mProfile = new PowerProfile(InstrumentationRegistry.getContext(), true); + mContext = InstrumentationRegistry.getContext(); + mProfile = new PowerProfile(mContext); } @Test public void testPowerProfile() { + mProfile.forceInitForTesting(mContext, R.xml.power_profile_test); + assertEquals(2, mProfile.getNumCpuClusters()); assertEquals(4, mProfile.getNumCoresInCpuCluster(0)); assertEquals(4, mProfile.getNumCoresInCpuCluster(1)); @@ -65,6 +85,435 @@ public class PowerProfileTest extends TestCase { mProfile.getAveragePowerForOrdinal(POWER_GROUP_DISPLAY_SCREEN_FULL, 0)); assertEquals(100.0, mProfile.getAveragePower(PowerProfile.POWER_AUDIO)); assertEquals(150.0, mProfile.getAveragePower(PowerProfile.POWER_VIDEO)); + + assertEquals(123.0, mProfile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_SLEEP)); + assertEquals(456.0, mProfile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_IDLE)); + assertEquals(789.0, mProfile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_RX)); + assertEquals(10.0, mProfile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_TX, 0)); + assertEquals(20.0, mProfile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_TX, 1)); + assertEquals(30.0, mProfile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_TX, 2)); + assertEquals(40.0, mProfile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_TX, 3)); + assertEquals(50.0, mProfile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_TX, 4)); + + // Deprecated Modem constants should work with current format. + assertEquals(123.0, mProfile.getAverageBatteryDrainMa( + PowerProfile.SUBSYSTEM_MODEM | ModemPowerProfile.MODEM_DRAIN_TYPE_SLEEP)); + assertEquals(456.0, mProfile.getAverageBatteryDrainMa( + PowerProfile.SUBSYSTEM_MODEM | ModemPowerProfile.MODEM_DRAIN_TYPE_IDLE)); + assertEquals(789.0, mProfile.getAverageBatteryDrainMa( + PowerProfile.SUBSYSTEM_MODEM | ModemPowerProfile.MODEM_DRAIN_TYPE_RX)); + assertEquals(10.0, mProfile.getAverageBatteryDrainMa( + PowerProfile.SUBSYSTEM_MODEM | ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_0)); + assertEquals(20.0, mProfile.getAverageBatteryDrainMa( + PowerProfile.SUBSYSTEM_MODEM | ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_1)); + assertEquals(30.0, mProfile.getAverageBatteryDrainMa( + PowerProfile.SUBSYSTEM_MODEM | ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_2)); + assertEquals(40.0, mProfile.getAverageBatteryDrainMa( + PowerProfile.SUBSYSTEM_MODEM | ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_3)); + assertEquals(50.0, mProfile.getAverageBatteryDrainMa( + PowerProfile.SUBSYSTEM_MODEM | ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_4)); + } + + @Test + public void testModemPowerProfile_defaultRat() throws Exception { + final XmlResourceParser parser = getTestModemElement(R.xml.power_profile_test_modem, + "testModemPowerProfile_defaultRat"); + ModemPowerProfile mpp = new ModemPowerProfile(); + mpp.parseFromXml(parser); + assertEquals(10.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_DRAIN_TYPE_SLEEP)); + assertEquals(20.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_DRAIN_TYPE_IDLE)); + + // Only default RAT was defined, all other RAT's should fallback to the default value. + assertEquals(30.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT | ModemPowerProfile.MODEM_DRAIN_TYPE_RX)); + assertEquals(30.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_LTE | ModemPowerProfile.MODEM_DRAIN_TYPE_RX)); + assertEquals(30.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_NR | ModemPowerProfile.MODEM_DRAIN_TYPE_RX)); + + assertEquals(40.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_0)); + assertEquals(40.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_LTE | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_0)); + assertEquals(40.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_NR | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_0)); + + assertEquals(50.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_1)); + assertEquals(50.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_LTE | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_1)); + assertEquals(50.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_NR | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_1)); + + assertEquals(60.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_2)); + assertEquals(60.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_LTE | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_2)); + assertEquals(60.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_NR | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_2)); + + assertEquals(70.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_3)); + assertEquals(70.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_LTE | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_3)); + assertEquals(70.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_NR | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_3)); + + assertEquals(80.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_4)); + assertEquals(80.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_LTE | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_4)); + assertEquals(80.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_NR | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_4)); } + @Test + public void testModemPowerProfile_partiallyDefined() throws Exception { + final XmlResourceParser parser = getTestModemElement(R.xml.power_profile_test_modem, + "testModemPowerProfile_partiallyDefined"); + ModemPowerProfile mpp = new ModemPowerProfile(); + mpp.parseFromXml(parser); + assertEquals(1.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_DRAIN_TYPE_SLEEP)); + assertEquals(2.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_DRAIN_TYPE_IDLE)); + + assertEquals(3.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT | ModemPowerProfile.MODEM_DRAIN_TYPE_RX)); + assertEquals(4.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_0)); + assertEquals(5.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_1)); + assertEquals(6.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_2)); + assertEquals(7.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_3)); + assertEquals(8.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_4)); + + // LTE RAT power constants were not defined, fallback to defaults + assertEquals(3.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_LTE | ModemPowerProfile.MODEM_DRAIN_TYPE_RX)); + assertEquals(4.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_LTE | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_0)); + assertEquals(5.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_LTE | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_1)); + assertEquals(6.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_LTE | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_2)); + assertEquals(7.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_LTE | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_3)); + assertEquals(8.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_LTE | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_4)); + + assertEquals(13.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR + | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_DEFAULT + | ModemPowerProfile.MODEM_DRAIN_TYPE_RX)); + assertEquals(14.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR + | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_DEFAULT + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_0)); + assertEquals(15.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR + | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_DEFAULT + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_1)); + assertEquals(16.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR + | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_DEFAULT + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_2)); + assertEquals(17.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR + | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_DEFAULT + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_3)); + assertEquals(18.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR + | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_DEFAULT + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_4)); + + // Non-mmwave NR frequency power constants were not defined, fallback to defaults + assertEquals(13.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_NR | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_LOW + | ModemPowerProfile.MODEM_DRAIN_TYPE_RX)); + assertEquals(14.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_NR | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_LOW + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_0)); + assertEquals(15.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_NR | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_LOW + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_1)); + assertEquals(16.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_NR | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_LOW + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_2)); + assertEquals(17.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_NR | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_LOW + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_3)); + assertEquals(18.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_NR | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_LOW + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_4)); + + assertEquals(13.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_NR | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_MID + | ModemPowerProfile.MODEM_DRAIN_TYPE_RX)); + assertEquals(14.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_NR | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_MID + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_0)); + assertEquals(15.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_NR | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_MID + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_1)); + assertEquals(16.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_NR | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_MID + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_2)); + assertEquals(17.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_NR | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_MID + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_3)); + assertEquals(18.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_NR | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_MID + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_4)); + + assertEquals(13.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR + | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_HIGH + | ModemPowerProfile.MODEM_DRAIN_TYPE_RX)); + assertEquals(14.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR + | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_HIGH + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_0)); + assertEquals(15.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR + | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_HIGH + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_1)); + assertEquals(16.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR + | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_HIGH + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_2)); + assertEquals(17.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR + | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_HIGH + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_3)); + assertEquals(18.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR + | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_HIGH + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_4)); + + assertEquals(53.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR + | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_MMWAVE + | ModemPowerProfile.MODEM_DRAIN_TYPE_RX)); + assertEquals(54.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR + | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_MMWAVE + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_0)); + assertEquals(55.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR + | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_MMWAVE + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_1)); + assertEquals(56.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR + | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_MMWAVE + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_2)); + assertEquals(57.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR + | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_MMWAVE + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_3)); + assertEquals(58.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR + | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_MMWAVE + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_4)); + } + + @Test + public void testModemPowerProfile_fullyDefined() throws Exception { + final XmlResourceParser parser = getTestModemElement(R.xml.power_profile_test_modem, + "testModemPowerProfile_fullyDefined"); + ModemPowerProfile mpp = new ModemPowerProfile(); + mpp.parseFromXml(parser); + assertEquals(1.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_DRAIN_TYPE_SLEEP)); + assertEquals(2.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_DRAIN_TYPE_IDLE)); + + // Only default RAT was defined, all other RAT's should fallback to the default value. + assertEquals(3.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT | ModemPowerProfile.MODEM_DRAIN_TYPE_RX)); + assertEquals(4.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_0)); + assertEquals(5.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_1)); + assertEquals(6.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_2)); + assertEquals(7.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_3)); + assertEquals(8.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_4)); + + assertEquals(10.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_LTE | ModemPowerProfile.MODEM_DRAIN_TYPE_RX)); + assertEquals(20.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_LTE | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_0)); + assertEquals(30.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_LTE | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_1)); + assertEquals(40.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_LTE | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_2)); + assertEquals(50.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_LTE | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_3)); + assertEquals(60.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_LTE | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_4)); + + assertEquals(13.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR + | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_DEFAULT + | ModemPowerProfile.MODEM_DRAIN_TYPE_RX)); + assertEquals(14.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR + | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_DEFAULT + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_0)); + assertEquals(15.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR + | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_DEFAULT + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_1)); + assertEquals(16.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR + | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_DEFAULT + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_2)); + assertEquals(17.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR + | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_DEFAULT + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_3)); + assertEquals(18.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR + | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_DEFAULT + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_4)); + + assertEquals(23.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_NR | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_LOW + | ModemPowerProfile.MODEM_DRAIN_TYPE_RX)); + assertEquals(24.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_NR | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_LOW + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_0)); + assertEquals(25.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_NR | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_LOW + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_1)); + assertEquals(26.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_NR | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_LOW + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_2)); + assertEquals(27.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_NR | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_LOW + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_3)); + assertEquals(28.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_NR | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_LOW + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_4)); + + assertEquals(33.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_NR | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_MID + | ModemPowerProfile.MODEM_DRAIN_TYPE_RX)); + assertEquals(34.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_NR | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_MID + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_0)); + assertEquals(35.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_NR | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_MID + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_1)); + assertEquals(36.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_NR | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_MID + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_2)); + assertEquals(37.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_NR | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_MID + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_3)); + assertEquals(38.0, mpp.getAverageBatteryDrainMa( + ModemPowerProfile.MODEM_RAT_TYPE_NR | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_MID + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_4)); + + assertEquals(43.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR + | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_HIGH + | ModemPowerProfile.MODEM_DRAIN_TYPE_RX)); + assertEquals(44.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR + | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_HIGH + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_0)); + assertEquals(45.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR + | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_HIGH + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_1)); + assertEquals(46.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR + | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_HIGH + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_2)); + assertEquals(47.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR + | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_HIGH + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_3)); + assertEquals(48.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR + | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_HIGH + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_4)); + + assertEquals(53.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR + | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_MMWAVE + | ModemPowerProfile.MODEM_DRAIN_TYPE_RX)); + assertEquals(54.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR + | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_MMWAVE + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_0)); + assertEquals(55.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR + | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_MMWAVE + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_1)); + assertEquals(56.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR + | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_MMWAVE + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_2)); + assertEquals(57.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR + | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_MMWAVE + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_3)); + assertEquals(58.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR + | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_MMWAVE + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_4)); + } + + private XmlResourceParser getTestModemElement(@XmlRes int xmlId, String elementName) + throws Exception { + final String element = TAG_TEST_MODEM; + final Resources resources = mContext.getResources(); + XmlResourceParser parser = resources.getXml(xmlId); + while (true) { + XmlUtils.nextElement(parser); + final String e = parser.getName(); + if (e == null) break; + if (!e.equals(element)) continue; + + final String name = parser.getAttributeValue(null, ATTR_NAME); + if (!name.equals(elementName)) continue; + + return parser; + } + fail("Unanable to find element " + element + " with name " + elementName); + return null; + } } diff --git a/core/tests/coretests/src/com/android/internal/os/WifiPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/WifiPowerCalculatorTest.java index a7873576e728..a36839910742 100644 --- a/core/tests/coretests/src/com/android/internal/os/WifiPowerCalculatorTest.java +++ b/core/tests/coretests/src/com/android/internal/os/WifiPowerCalculatorTest.java @@ -92,7 +92,10 @@ public class WifiPowerCalculatorTest { final BatteryStatsImpl batteryStats = setupTestNetworkNumbers(); final WifiActivityEnergyInfo energyInfo = setupPowerControllerBasedModelEnergyNumbersInfo(); - batteryStats.updateWifiState(energyInfo, POWER_DATA_UNAVAILABLE, 1000, 1000, + batteryStats.noteWifiScanStartedLocked(APP_UID, 500, 500); + batteryStats.noteWifiScanStoppedLocked(APP_UID, 1500, 1500); + + batteryStats.updateWifiState(energyInfo, POWER_DATA_UNAVAILABLE, 2000, 2000, mNetworkStatsManager); WifiPowerCalculator calculator = new WifiPowerCalculator(mStatsRule.getPowerProfile()); @@ -100,15 +103,15 @@ public class WifiPowerCalculatorTest { UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID); assertThat(uidConsumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_WIFI)) - .isEqualTo(1423); + .isEqualTo(2473); assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI)) - .isWithin(PRECISION).of(0.2214666); + .isWithin(PRECISION).of(0.3964); assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_WIFI)) .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE); BatteryConsumer deviceConsumer = mStatsRule.getDeviceBatteryConsumer(); assertThat(deviceConsumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_WIFI)) - .isEqualTo(4002); + .isEqualTo(4001); assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI)) .isWithin(PRECISION).of(0.86666); assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_WIFI)) diff --git a/data/etc/com.android.settings.xml b/data/etc/com.android.settings.xml index 3fdb0da80875..ddcab6eb76c8 100644 --- a/data/etc/com.android.settings.xml +++ b/data/etc/com.android.settings.xml @@ -30,6 +30,7 @@ <permission name="android.permission.MANAGE_DEBUGGING"/> <permission name="android.permission.MANAGE_DEVICE_ADMINS"/> <permission name="android.permission.MANAGE_FINGERPRINT"/> + <permission name="android.permission.MANAGE_GAME_MODE" /> <permission name="android.permission.MANAGE_USB"/> <permission name="android.permission.MANAGE_USERS"/> <permission name="android.permission.MANAGE_USER_OEM_UNLOCK_STATE" /> @@ -59,5 +60,6 @@ <permission name="android.permission.READ_DREAM_STATE"/> <permission name="android.permission.READ_DREAM_SUPPRESSION"/> <permission name="android.permission.RESTART_WIFI_SUBSYSTEM"/> + <permission name="android.permission.READ_SAFETY_CENTER_STATUS" /> </privapp-permissions> </permissions> diff --git a/data/etc/com.android.systemui.xml b/data/etc/com.android.systemui.xml index f2a33de008d6..d95644a02e69 100644 --- a/data/etc/com.android.systemui.xml +++ b/data/etc/com.android.systemui.xml @@ -30,6 +30,7 @@ <permission name="android.permission.GET_APP_OPS_STATS"/> <permission name="android.permission.INTERACT_ACROSS_USERS"/> <permission name="android.permission.MANAGE_DEBUGGING"/> + <permission name="android.permission.MANAGE_GAME_MODE" /> <permission name="android.permission.MANAGE_SENSOR_PRIVACY"/> <permission name="android.permission.MANAGE_USB"/> <permission name="android.permission.MANAGE_USERS"/> @@ -50,6 +51,7 @@ <permission name="android.permission.REAL_GET_TASKS"/> <permission name="android.permission.REQUEST_NETWORK_SCORES"/> <permission name="android.permission.RECEIVE_MEDIA_RESOURCE_USAGE"/> + <permission name="android.permission.SET_WALLPAPER_DIM_AMOUNT"/> <permission name="android.permission.START_ACTIVITIES_FROM_BACKGROUND" /> <permission name="android.permission.START_ACTIVITY_AS_CALLER"/> <permission name="android.permission.START_TASKS_FROM_RECENTS"/> @@ -71,5 +73,6 @@ <permission name="android.permission.USE_BACKGROUND_BLUR" /> <permission name="android.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS" /> <permission name="android.permission.FORCE_STOP_PACKAGES" /> + <permission name="android.permission.ACCESS_FPS_COUNTER" /> </privapp-permissions> </permissions> diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index 6f5951bdaca6..de086df13722 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -333,6 +333,7 @@ applications that come with the platform <permission name="android.permission.LOCAL_MAC_ADDRESS"/> <permission name="android.permission.MANAGE_ACCESSIBILITY"/> <permission name="android.permission.MANAGE_DEVICE_ADMINS"/> + <permission name="android.permission.MANAGE_GAME_MODE"/> <permission name="android.permission.MANAGE_ROLLBACKS"/> <permission name="android.permission.MANAGE_USB"/> <permission name="android.permission.MODIFY_APPWIDGET_BIND_PERMISSIONS"/> @@ -394,6 +395,9 @@ applications that come with the platform <permission name="android.permission.SET_WALLPAPER_COMPONENT" /> <permission name="android.permission.SET_WALLPAPER_DIM_AMOUNT" /> <permission name="android.permission.REQUEST_NOTIFICATION_ASSISTANT_SERVICE" /> + <!-- Permission required for CTS test - TrustTestCases --> + <permission name="android.permission.PROVIDE_TRUST_AGENT" /> + <permission name="android.permission.ACCESS_KEYGUARD_SECURE_STORAGE" /> <!-- Permissions required for Incremental CTS tests --> <permission name="com.android.permission.USE_INSTALLER_V2"/> <permission name="android.permission.LOADER_USAGE_STATS"/> @@ -571,6 +575,7 @@ applications that come with the platform <privapp-permissions package="com.android.settings"> <permission name="android.permission.INSTALL_DYNAMIC_SYSTEM"/> <permission name="android.permission.BIND_CELL_BROADCAST_SERVICE"/> + <permission name="android.permission.READ_SAFETY_CENTER_STATUS" /> </privapp-permissions> <privapp-permissions package="com.android.bips"> diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index 535d656462f4..07523299c353 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -103,18 +103,6 @@ "group": "WM_DEBUG_STATES", "at": "com\/android\/server\/wm\/TaskFragment.java" }, - "-2002500255": { - "message": "Defer removing snapshot surface in %dms", - "level": "VERBOSE", - "group": "WM_DEBUG_STARTING_WINDOW", - "at": "com\/android\/server\/wm\/TaskSnapshotSurface.java" - }, - "-1991255017": { - "message": "Drawing snapshot surface sizeMismatch=%b", - "level": "VERBOSE", - "group": "WM_DEBUG_STARTING_WINDOW", - "at": "com\/android\/server\/wm\/TaskSnapshotSurface.java" - }, "-1980468143": { "message": "DisplayArea appeared name=%s", "level": "VERBOSE", @@ -505,12 +493,6 @@ "group": "WM_DEBUG_STATES", "at": "com\/android\/server\/wm\/ActivityRecord.java" }, - "-1556507536": { - "message": "Passing transform hint %d for window %s%s", - "level": "VERBOSE", - "group": "WM_DEBUG_ORIENTATION", - "at": "com\/android\/server\/wm\/WindowManagerService.java" - }, "-1554521902": { "message": "showInsets(ime) was requested by different window: %s ", "level": "WARN", @@ -745,6 +727,12 @@ "group": "WM_DEBUG_BOOT", "at": "com\/android\/server\/wm\/WindowManagerService.java" }, + "-1343787701": { + "message": "startBackNavigation task=%s, topRunningActivity=%s", + "level": "DEBUG", + "group": "WM_DEBUG_BACK_PREVIEW", + "at": "com\/android\/server\/wm\/BackNavigationController.java" + }, "-1340540100": { "message": "Creating SnapshotStartingData", "level": "VERBOSE", @@ -1597,12 +1585,6 @@ "group": "WM_DEBUG_FOCUS_LIGHT", "at": "com\/android\/server\/wm\/DisplayContent.java" }, - "-405536909": { - "message": "Removing snapshot surface", - "level": "VERBOSE", - "group": "WM_DEBUG_STARTING_WINDOW", - "at": "com\/android\/server\/wm\/TaskSnapshotSurface.java" - }, "-401282500": { "message": "destroyIfPossible: r=%s destroy returned removed=%s", "level": "DEBUG", @@ -1867,6 +1849,12 @@ "group": "WM_DEBUG_STATES", "at": "com\/android\/server\/wm\/Task.java" }, + "-134091882": { + "message": "Screenshotting Activity %s", + "level": "VERBOSE", + "group": "WM_DEBUG_BACK_PREVIEW", + "at": "com\/android\/server\/wm\/TaskFragment.java" + }, "-124316973": { "message": "Translucent=%s Floating=%s ShowWallpaper=%s Disable=%s", "level": "VERBOSE", @@ -1951,6 +1939,12 @@ "group": "WM_DEBUG_APP_TRANSITIONS_ANIM", "at": "com\/android\/server\/wm\/WindowContainer.java" }, + "-23020844": { + "message": "Back: Reset surfaces", + "level": "DEBUG", + "group": "WM_DEBUG_BACK_PREVIEW", + "at": "com\/android\/server\/wm\/BackNavigationController.java" + }, "-21399771": { "message": "activity %s already destroying, skipping request with reason:%s", "level": "VERBOSE", @@ -2005,12 +1999,6 @@ "group": "WM_ERROR", "at": "com\/android\/server\/wm\/WindowManagerService.java" }, - "44438983": { - "message": "performLayout: Activity exiting now removed %s", - "level": "VERBOSE", - "group": "WM_DEBUG_ADD_REMOVE", - "at": "com\/android\/server\/wm\/DisplayContent.java" - }, "45285419": { "message": "startingWindow was set but startingSurface==null, couldn't remove", "level": "VERBOSE", @@ -2767,6 +2755,12 @@ "group": "WM_SHOW_SURFACE_ALLOC", "at": "com\/android\/server\/wm\/WindowStateAnimator.java" }, + "751854538": { + "message": "DisplayArea keep clear rects changed name =%s", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_ORGANIZER", + "at": "com\/android\/server\/wm\/DisplayAreaOrganizerController.java" + }, "765395228": { "message": "onAnimationFinished(): controller=%s reorderMode=%d", "level": "DEBUG", @@ -3271,12 +3265,6 @@ "group": "WM_DEBUG_LAYER_MIRRORING", "at": "com\/android\/server\/wm\/DisplayContent.java" }, - "1417601133": { - "message": "Enqueueing ADD_STARTING", - "level": "VERBOSE", - "group": "WM_DEBUG_STARTING_WINDOW", - "at": "com\/android\/server\/wm\/ActivityRecord.java" - }, "1422781269": { "message": "Resuming rotation after re-position", "level": "DEBUG", @@ -3397,6 +3385,12 @@ "group": "WM_DEBUG_APP_TRANSITIONS", "at": "com\/android\/server\/wm\/ActivityRecord.java" }, + "1554795024": { + "message": "Previous Activity is %s", + "level": "DEBUG", + "group": "WM_DEBUG_BACK_PREVIEW", + "at": "com\/android\/server\/wm\/BackNavigationController.java" + }, "1557732761": { "message": "For Intent %s bringing to top: %s", "level": "DEBUG", @@ -3697,12 +3691,6 @@ "group": "WM_DEBUG_WINDOW_ORGANIZER", "at": "com\/android\/server\/wm\/DisplayAreaPolicyBuilder.java" }, - "1884961873": { - "message": "Sleep still need to stop %d activities", - "level": "VERBOSE", - "group": "WM_DEBUG_STATES", - "at": "com\/android\/server\/wm\/Task.java" - }, "1891501279": { "message": "cancelAnimation(): reason=%s", "level": "DEBUG", @@ -3924,6 +3912,9 @@ "WM_DEBUG_APP_TRANSITIONS_ANIM": { "tag": "WindowManager" }, + "WM_DEBUG_BACK_PREVIEW": { + "tag": "CoreBackPreview" + }, "WM_DEBUG_BOOT": { "tag": "WindowManager" }, diff --git a/graphics/java/android/graphics/text/LineBreakConfig.java b/graphics/java/android/graphics/text/LineBreakConfig.java index 4d818583fd23..cffdf28dbc27 100644 --- a/graphics/java/android/graphics/text/LineBreakConfig.java +++ b/graphics/java/android/graphics/text/LineBreakConfig.java @@ -17,7 +17,7 @@ package android.graphics.text; import android.annotation.IntDef; -import android.annotation.Nullable; +import android.annotation.NonNull; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -58,7 +58,28 @@ public final class LineBreakConfig { @Retention(RetentionPolicy.SOURCE) public @interface LineBreakStyle {} + /** + * No line break word style specified. + */ + public static final int LINE_BREAK_WORD_STYLE_NONE = 0; + + /** + * Indicates the line breaking is based on the phrased. This makes text wrapping only on + * meaningful words. The support of the text wrapping word style varies depending on the + * locales. If the locale does not support the phrase based text wrapping, + * there will be no effect. + */ + public static final int LINE_BREAK_WORD_STYLE_PHRASE = 1; + + /** @hide */ + @IntDef(prefix = { "LINE_BREAK_WORD_STYLE_" }, value = { + LINE_BREAK_WORD_STYLE_NONE, LINE_BREAK_WORD_STYLE_PHRASE + }) + @Retention(RetentionPolicy.SOURCE) + public @interface LineBreakWordStyle {} + private @LineBreakStyle int mLineBreakStyle = LINE_BREAK_STYLE_NONE; + private @LineBreakWordStyle int mLineBreakWordStyle = LINE_BREAK_WORD_STYLE_NONE; public LineBreakConfig() { } @@ -66,14 +87,12 @@ public final class LineBreakConfig { /** * Set the line break configuration. * - * @param config the new line break configuration. + * @param lineBreakConfig the new line break configuration. */ - public void set(@Nullable LineBreakConfig config) { - if (config != null) { - mLineBreakStyle = config.getLineBreakStyle(); - } else { - mLineBreakStyle = LineBreakConfig.LINE_BREAK_STYLE_NONE; - } + public void set(@NonNull LineBreakConfig lineBreakConfig) { + Objects.requireNonNull(lineBreakConfig); + mLineBreakStyle = lineBreakConfig.getLineBreakStyle(); + mLineBreakWordStyle = lineBreakConfig.getLineBreakWordStyle(); } /** @@ -94,17 +113,36 @@ public final class LineBreakConfig { mLineBreakStyle = lineBreakStyle; } + /** + * Get the line break word style. + * + * @return The current line break word style to be used for the text wrapping. + */ + public @LineBreakWordStyle int getLineBreakWordStyle() { + return mLineBreakWordStyle; + } + + /** + * Set the line break word style. + * + * @param lineBreakWordStyle the new line break word style. + */ + public void setLineBreakWordStyle(@LineBreakWordStyle int lineBreakWordStyle) { + mLineBreakWordStyle = lineBreakWordStyle; + } + @Override public boolean equals(Object o) { if (o == null) return false; if (this == o) return true; if (!(o instanceof LineBreakConfig)) return false; LineBreakConfig that = (LineBreakConfig) o; - return mLineBreakStyle == that.mLineBreakStyle; + return (mLineBreakStyle == that.mLineBreakStyle) + && (mLineBreakWordStyle == that.mLineBreakWordStyle); } @Override public int hashCode() { - return Objects.hash(mLineBreakStyle); + return Objects.hash(mLineBreakStyle, mLineBreakWordStyle); } } diff --git a/graphics/java/android/graphics/text/MeasuredText.java b/graphics/java/android/graphics/text/MeasuredText.java index 5f4afb7b9888..6d691c122576 100644 --- a/graphics/java/android/graphics/text/MeasuredText.java +++ b/graphics/java/android/graphics/text/MeasuredText.java @@ -264,8 +264,10 @@ public class MeasuredText { Preconditions.checkArgument(end <= mText.length, "Style exceeds the text length"); int lbStyle = (lineBreakConfig != null) ? lineBreakConfig.getLineBreakStyle() : LineBreakConfig.LINE_BREAK_STYLE_NONE; - nAddStyleRun(mNativePtr, paint.getNativeInstance(), lbStyle, mCurrentOffset, end, - isRtl); + int lbWordStyle = (lineBreakConfig != null) ? lineBreakConfig.getLineBreakWordStyle() : + LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE; + nAddStyleRun(mNativePtr, paint.getNativeInstance(), lbStyle, lbWordStyle, + mCurrentOffset, end, isRtl); mCurrentOffset = end; return this; } @@ -445,7 +447,8 @@ public class MeasuredText { * * @param nativeBuilderPtr The native MeasuredParagraph builder pointer. * @param paintPtr The native paint pointer to be applied. - * @param lineBreakStyle The line break style of the text. + * @param lineBreakStyle The line break style(lb) of the text. + * @param lineBreakWordStyle The line break word style(lw) of the text. * @param start The start offset in the copied buffer. * @param end The end offset in the copied buffer. * @param isRtl True if the text is RTL. @@ -453,6 +456,7 @@ public class MeasuredText { private static native void nAddStyleRun(/* Non Zero */ long nativeBuilderPtr, /* Non Zero */ long paintPtr, int lineBreakStyle, + int lineBreakWordStyle, @IntRange(from = 0) int start, @IntRange(from = 0) int end, boolean isRtl); diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java index a9543443d3f4..8811a7fec932 100644 --- a/keystore/java/android/security/KeyStore.java +++ b/keystore/java/android/security/KeyStore.java @@ -20,7 +20,6 @@ import android.compat.annotation.UnsupportedAppUsage; import android.os.Build; import android.os.UserHandle; import android.security.maintenance.UserState; -import android.system.keystore2.Domain; /** * @hide This should not be made public in its present form because it @@ -120,15 +119,6 @@ public class KeyStore { } /** - * Forwards the request to clear a UID to Keystore 2.0. - * @hide - */ - public boolean clearUid(int uid) { - return AndroidKeyStoreMaintenance.clearNamespace(Domain.APP, uid) == 0; - } - - - /** * Add an authentication record to the keystore authorization table. * * @param authToken The packed bytes of a hw_auth_token_t to be provided to keymaster. diff --git a/libs/WindowManager/Shell/res/values-af/strings_tv.xml b/libs/WindowManager/Shell/res/values-af/strings_tv.xml index 6ce588034f9e..3edb8e967768 100644 --- a/libs/WindowManager/Shell/res/values-af/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-af/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Titellose program)"</string> <string name="pip_close" msgid="9135220303720555525">"Maak PIP toe"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Volskerm"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-am/strings_tv.xml b/libs/WindowManager/Shell/res/values-am/strings_tv.xml index fcb87c5682e3..b1c6542ce616 100644 --- a/libs/WindowManager/Shell/res/values-am/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-am/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(ርዕስ የሌለው ፕሮግራም)"</string> <string name="pip_close" msgid="9135220303720555525">"PIPን ዝጋ"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"ሙሉ ማያ ገጽ"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-ar/strings_tv.xml b/libs/WindowManager/Shell/res/values-ar/strings_tv.xml index 4eef29e2ed12..dfc505365ca4 100644 --- a/libs/WindowManager/Shell/res/values-ar/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-ar/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(ليس هناك عنوان للبرنامج)"</string> <string name="pip_close" msgid="9135220303720555525">"إغلاق PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"ملء الشاشة"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-as/strings_tv.xml b/libs/WindowManager/Shell/res/values-as/strings_tv.xml index 170b2dbd458c..352bde5a1931 100644 --- a/libs/WindowManager/Shell/res/values-as/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-as/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(শিৰোনামবিহীন কাৰ্যক্ৰম)"</string> <string name="pip_close" msgid="9135220303720555525">"পিপ বন্ধ কৰক"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"সম্পূৰ্ণ স্ক্ৰীন"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-az/strings_tv.xml b/libs/WindowManager/Shell/res/values-az/strings_tv.xml index c9f1acbef31b..9b46d5fc222c 100644 --- a/libs/WindowManager/Shell/res/values-az/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-az/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Başlıqsız proqram)"</string> <string name="pip_close" msgid="9135220303720555525">"PIP bağlayın"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Tam ekran"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-b+sr+Latn/strings_tv.xml b/libs/WindowManager/Shell/res/values-b+sr+Latn/strings_tv.xml index 6fbc91bbec60..790a6d471e55 100644 --- a/libs/WindowManager/Shell/res/values-b+sr+Latn/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-b+sr+Latn/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Program bez naslova)"</string> <string name="pip_close" msgid="9135220303720555525">"Zatvori PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Ceo ekran"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-be/strings_tv.xml b/libs/WindowManager/Shell/res/values-be/strings_tv.xml index d33bf99e2ebd..c4df7fc74121 100644 --- a/libs/WindowManager/Shell/res/values-be/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-be/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Праграма без назвы)"</string> <string name="pip_close" msgid="9135220303720555525">"Закрыць PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Поўнаэкранны рэжым"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-bg/strings_tv.xml b/libs/WindowManager/Shell/res/values-bg/strings_tv.xml index f4fad601179f..cbb00ae19024 100644 --- a/libs/WindowManager/Shell/res/values-bg/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-bg/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Програма без заглавие)"</string> <string name="pip_close" msgid="9135220303720555525">"Затваряне на PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Цял екран"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-bn/strings_tv.xml b/libs/WindowManager/Shell/res/values-bn/strings_tv.xml index 0eb83a0276e6..f24c92a797e5 100644 --- a/libs/WindowManager/Shell/res/values-bn/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-bn/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(শিরোনামহীন প্রোগ্রাম)"</string> <string name="pip_close" msgid="9135220303720555525">"PIP বন্ধ করুন"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"পূর্ণ স্ক্রিন"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-bs/strings_tv.xml b/libs/WindowManager/Shell/res/values-bs/strings_tv.xml index 9a655bb41066..80bac2a07da3 100644 --- a/libs/WindowManager/Shell/res/values-bs/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-bs/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Program bez naslova)"</string> <string name="pip_close" msgid="9135220303720555525">"Zatvori sliku u slici"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Cijeli ekran"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-ca/strings_tv.xml b/libs/WindowManager/Shell/res/values-ca/strings_tv.xml index b80fc41402dd..66cd93a2c78e 100644 --- a/libs/WindowManager/Shell/res/values-ca/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-ca/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Programa sense títol)"</string> <string name="pip_close" msgid="9135220303720555525">"Tanca PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Pantalla completa"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-cs/strings_tv.xml b/libs/WindowManager/Shell/res/values-cs/strings_tv.xml index 56abcbe473fb..500050b09cab 100644 --- a/libs/WindowManager/Shell/res/values-cs/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-cs/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Bez názvu)"</string> <string name="pip_close" msgid="9135220303720555525">"Ukončit obraz v obraze (PIP)"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Celá obrazovka"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-da/strings_tv.xml b/libs/WindowManager/Shell/res/values-da/strings_tv.xml index fdb6b783399e..896895b90f47 100644 --- a/libs/WindowManager/Shell/res/values-da/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-da/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Program uden titel)"</string> <string name="pip_close" msgid="9135220303720555525">"Luk integreret billede"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Fuld skærm"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-de/strings_tv.xml b/libs/WindowManager/Shell/res/values-de/strings_tv.xml index 02cce9d73647..7809efe85afa 100644 --- a/libs/WindowManager/Shell/res/values-de/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-de/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Kein Sendungsname gefunden)"</string> <string name="pip_close" msgid="9135220303720555525">"PIP schließen"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Vollbild"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-el/strings_tv.xml b/libs/WindowManager/Shell/res/values-el/strings_tv.xml index 880ea37e6bf7..088bcc39b859 100644 --- a/libs/WindowManager/Shell/res/values-el/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-el/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Δεν υπάρχει τίτλος προγράμματος)"</string> <string name="pip_close" msgid="9135220303720555525">"Κλείσιμο PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Πλήρης οθόνη"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-en-rAU/strings_tv.xml b/libs/WindowManager/Shell/res/values-en-rAU/strings_tv.xml index e3f08c8cc76f..7900fdc3a0b1 100644 --- a/libs/WindowManager/Shell/res/values-en-rAU/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-en-rAU/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(No title program)"</string> <string name="pip_close" msgid="9135220303720555525">"Close PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Full screen"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-en-rCA/strings_tv.xml b/libs/WindowManager/Shell/res/values-en-rCA/strings_tv.xml index e3f08c8cc76f..7900fdc3a0b1 100644 --- a/libs/WindowManager/Shell/res/values-en-rCA/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-en-rCA/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(No title program)"</string> <string name="pip_close" msgid="9135220303720555525">"Close PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Full screen"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-en-rGB/strings_tv.xml b/libs/WindowManager/Shell/res/values-en-rGB/strings_tv.xml index e3f08c8cc76f..7900fdc3a0b1 100644 --- a/libs/WindowManager/Shell/res/values-en-rGB/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-en-rGB/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(No title program)"</string> <string name="pip_close" msgid="9135220303720555525">"Close PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Full screen"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-en-rIN/strings_tv.xml b/libs/WindowManager/Shell/res/values-en-rIN/strings_tv.xml index e3f08c8cc76f..7900fdc3a0b1 100644 --- a/libs/WindowManager/Shell/res/values-en-rIN/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-en-rIN/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(No title program)"</string> <string name="pip_close" msgid="9135220303720555525">"Close PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Full screen"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-en-rXC/strings_tv.xml b/libs/WindowManager/Shell/res/values-en-rXC/strings_tv.xml index 3f9ef0ea2816..3b12d90f33a2 100644 --- a/libs/WindowManager/Shell/res/values-en-rXC/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-en-rXC/strings_tv.xml @@ -21,4 +21,5 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(No title program)"</string> <string name="pip_close" msgid="9135220303720555525">"Close PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Full screen"</string> + <string name="pip_move" msgid="1544227837964635439">"Move PIP"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-es-rUS/strings_tv.xml b/libs/WindowManager/Shell/res/values-es-rUS/strings_tv.xml index 5d5954a19761..3be850a90c30 100644 --- a/libs/WindowManager/Shell/res/values-es-rUS/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-es-rUS/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Sin título de programa)"</string> <string name="pip_close" msgid="9135220303720555525">"Cerrar PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Pantalla completa"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-es/strings_tv.xml b/libs/WindowManager/Shell/res/values-es/strings_tv.xml index d31b9b45cae3..7eba361df2c3 100644 --- a/libs/WindowManager/Shell/res/values-es/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-es/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Programa sin título)"</string> <string name="pip_close" msgid="9135220303720555525">"Cerrar PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Pantalla completa"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-et/strings_tv.xml b/libs/WindowManager/Shell/res/values-et/strings_tv.xml index bc7a6adafc03..ca6e6695b52b 100644 --- a/libs/WindowManager/Shell/res/values-et/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-et/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Programmi pealkiri puudub)"</string> <string name="pip_close" msgid="9135220303720555525">"Sule PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Täisekraan"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-eu/strings_tv.xml b/libs/WindowManager/Shell/res/values-eu/strings_tv.xml index cf5f98883082..3f47e9591f66 100644 --- a/libs/WindowManager/Shell/res/values-eu/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-eu/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Programa izengabea)"</string> <string name="pip_close" msgid="9135220303720555525">"Itxi PIPa"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Pantaila osoa"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-fa/strings_tv.xml b/libs/WindowManager/Shell/res/values-fa/strings_tv.xml index 5b815b4c7b86..cc5cf64d01dd 100644 --- a/libs/WindowManager/Shell/res/values-fa/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-fa/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(برنامه بدون عنوان)"</string> <string name="pip_close" msgid="9135220303720555525">"بستن PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"تمام صفحه"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-fi/strings_tv.xml b/libs/WindowManager/Shell/res/values-fi/strings_tv.xml index 77ad6eef91e7..b77988659cc3 100644 --- a/libs/WindowManager/Shell/res/values-fi/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-fi/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Nimetön)"</string> <string name="pip_close" msgid="9135220303720555525">"Sulje PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Koko näyttö"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-fr-rCA/strings_tv.xml b/libs/WindowManager/Shell/res/values-fr-rCA/strings_tv.xml index 0ec7f40f0e9f..1798c7db482c 100644 --- a/libs/WindowManager/Shell/res/values-fr-rCA/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-fr-rCA/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Aucun programme de titre)"</string> <string name="pip_close" msgid="9135220303720555525">"Fermer mode IDI"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Plein écran"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-fr/strings_tv.xml b/libs/WindowManager/Shell/res/values-fr/strings_tv.xml index 27fd155535b7..b039934ada8c 100644 --- a/libs/WindowManager/Shell/res/values-fr/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-fr/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Programme sans titre)"</string> <string name="pip_close" msgid="9135220303720555525">"Fermer mode PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Plein écran"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-gl/strings_tv.xml b/libs/WindowManager/Shell/res/values-gl/strings_tv.xml index df96f6cb794d..0d91eba31492 100644 --- a/libs/WindowManager/Shell/res/values-gl/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-gl/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Programa sen título)"</string> <string name="pip_close" msgid="9135220303720555525">"Pechar PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Pantalla completa"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-gu/strings_tv.xml b/libs/WindowManager/Shell/res/values-gu/strings_tv.xml index 3608f1d530c0..a748df3e2f43 100644 --- a/libs/WindowManager/Shell/res/values-gu/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-gu/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(કોઈ ટાઇટલ પ્રોગ્રામ નથી)"</string> <string name="pip_close" msgid="9135220303720555525">"PIP બંધ કરો"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"પૂર્ણ સ્ક્રીન"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-hi/strings_tv.xml b/libs/WindowManager/Shell/res/values-hi/strings_tv.xml index 720bb6ca5e24..040072bf5087 100644 --- a/libs/WindowManager/Shell/res/values-hi/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-hi/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(कोई शीर्षक कार्यक्रम नहीं)"</string> <string name="pip_close" msgid="9135220303720555525">"PIP बंद करें"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"फ़ुल स्क्रीन"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-hr/strings_tv.xml b/libs/WindowManager/Shell/res/values-hr/strings_tv.xml index 21f8cb63f470..20081e4b49dc 100644 --- a/libs/WindowManager/Shell/res/values-hr/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-hr/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Program bez naslova)"</string> <string name="pip_close" msgid="9135220303720555525">"Zatvori PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Cijeli zaslon"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-hu/strings_tv.xml b/libs/WindowManager/Shell/res/values-hu/strings_tv.xml index 0010086bb0b5..c78146d9a773 100644 --- a/libs/WindowManager/Shell/res/values-hu/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-hu/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Cím nélküli program)"</string> <string name="pip_close" msgid="9135220303720555525">"PIP bezárása"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Teljes képernyő"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-hy/strings_tv.xml b/libs/WindowManager/Shell/res/values-hy/strings_tv.xml index cb18762be48b..55d5bd76ecd3 100644 --- a/libs/WindowManager/Shell/res/values-hy/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-hy/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Առանց վերնագրի ծրագիր)"</string> <string name="pip_close" msgid="9135220303720555525">"Փակել PIP-ն"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Լիէկրան"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-in/strings_tv.xml b/libs/WindowManager/Shell/res/values-in/strings_tv.xml index 8f3a28764b00..640185263f8e 100644 --- a/libs/WindowManager/Shell/res/values-in/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-in/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Program tanpa judul)"</string> <string name="pip_close" msgid="9135220303720555525">"Tutup PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Layar penuh"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-is/strings_tv.xml b/libs/WindowManager/Shell/res/values-is/strings_tv.xml index 1f148d948a0e..fa36829f5634 100644 --- a/libs/WindowManager/Shell/res/values-is/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-is/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Efni án titils)"</string> <string name="pip_close" msgid="9135220303720555525">"Loka mynd í mynd"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Allur skjárinn"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-it/strings_tv.xml b/libs/WindowManager/Shell/res/values-it/strings_tv.xml index 127454cf28bf..f6e91be5b4ae 100644 --- a/libs/WindowManager/Shell/res/values-it/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-it/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Programma senza titolo)"</string> <string name="pip_close" msgid="9135220303720555525">"Chiudi PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Schermo intero"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-iw/strings_tv.xml b/libs/WindowManager/Shell/res/values-iw/strings_tv.xml index ef98a9c41cf2..356e8d536e4c 100644 --- a/libs/WindowManager/Shell/res/values-iw/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-iw/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(תוכנית ללא כותרת)"</string> <string name="pip_close" msgid="9135220303720555525">"סגירת PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"מסך מלא"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-ja/strings_tv.xml b/libs/WindowManager/Shell/res/values-ja/strings_tv.xml index b7ab28c44fd2..07684d3acbe6 100644 --- a/libs/WindowManager/Shell/res/values-ja/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-ja/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(無題の番組)"</string> <string name="pip_close" msgid="9135220303720555525">"PIP を閉じる"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"全画面表示"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-ka/strings_tv.xml b/libs/WindowManager/Shell/res/values-ka/strings_tv.xml index 1bf4b8ebdcda..043e5eb8fda9 100644 --- a/libs/WindowManager/Shell/res/values-ka/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-ka/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(პროგრამის სათაურის გარეშე)"</string> <string name="pip_close" msgid="9135220303720555525">"PIP-ის დახურვა"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"სრულ ეკრანზე"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-kk/strings_tv.xml b/libs/WindowManager/Shell/res/values-kk/strings_tv.xml index 8f1e725e79e2..79437975ca7e 100644 --- a/libs/WindowManager/Shell/res/values-kk/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-kk/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Атаусыз бағдарлама)"</string> <string name="pip_close" msgid="9135220303720555525">"PIP жабу"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Толық экран"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-km/strings_tv.xml b/libs/WindowManager/Shell/res/values-km/strings_tv.xml index b55997056e66..2e5625407ae9 100644 --- a/libs/WindowManager/Shell/res/values-km/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-km/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(កម្មវិធីគ្មានចំណងជើង)"</string> <string name="pip_close" msgid="9135220303720555525">"បិទ PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"ពេញអេក្រង់"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-kn/strings_tv.xml b/libs/WindowManager/Shell/res/values-kn/strings_tv.xml index 9d3942fa4dd3..6c8880d46cef 100644 --- a/libs/WindowManager/Shell/res/values-kn/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-kn/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(ಶೀರ್ಷಿಕೆ ರಹಿತ ಕಾರ್ಯಕ್ರಮ)"</string> <string name="pip_close" msgid="9135220303720555525">"PIP ಮುಚ್ಚಿ"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"ಪೂರ್ಣ ಪರದೆ"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-ko/strings_tv.xml b/libs/WindowManager/Shell/res/values-ko/strings_tv.xml index 46d6ad4e0b0f..35b1b19f629c 100644 --- a/libs/WindowManager/Shell/res/values-ko/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-ko/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(제목 없는 프로그램)"</string> <string name="pip_close" msgid="9135220303720555525">"PIP 닫기"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"전체화면"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-ky/strings_tv.xml b/libs/WindowManager/Shell/res/values-ky/strings_tv.xml index d5d1d7ef914e..72d70f056ce9 100644 --- a/libs/WindowManager/Shell/res/values-ky/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-ky/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Аталышы жок программа)"</string> <string name="pip_close" msgid="9135220303720555525">"PIP\'ти жабуу"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Толук экран"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-lo/strings_tv.xml b/libs/WindowManager/Shell/res/values-lo/strings_tv.xml index f6362c120b9f..3604726b7b69 100644 --- a/libs/WindowManager/Shell/res/values-lo/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-lo/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(ໂປຣແກຣມບໍ່ມີຊື່)"</string> <string name="pip_close" msgid="9135220303720555525">"ປິດ PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"ເຕັມໜ້າຈໍ"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-lt/strings_tv.xml b/libs/WindowManager/Shell/res/values-lt/strings_tv.xml index e4695a05f038..fa5a4c4427bf 100644 --- a/libs/WindowManager/Shell/res/values-lt/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-lt/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Programa be pavadinimo)"</string> <string name="pip_close" msgid="9135220303720555525">"Uždaryti PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Visas ekranas"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-lv/strings_tv.xml b/libs/WindowManager/Shell/res/values-lv/strings_tv.xml index f2b037fbeeee..cafd43ac9ef4 100644 --- a/libs/WindowManager/Shell/res/values-lv/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-lv/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Programma bez nosaukuma)"</string> <string name="pip_close" msgid="9135220303720555525">"Aizvērt PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Pilnekrāna režīms"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-mk/strings_tv.xml b/libs/WindowManager/Shell/res/values-mk/strings_tv.xml index 25dc764f4d5e..b927b568301a 100644 --- a/libs/WindowManager/Shell/res/values-mk/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-mk/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Програма без наслов)"</string> <string name="pip_close" msgid="9135220303720555525">"Затвори PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Цел екран"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-ml/strings_tv.xml b/libs/WindowManager/Shell/res/values-ml/strings_tv.xml index c74e0bbfaa5b..aef059fcec4f 100644 --- a/libs/WindowManager/Shell/res/values-ml/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-ml/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(പേരില്ലാത്ത പ്രോഗ്രാം)"</string> <string name="pip_close" msgid="9135220303720555525">"PIP അടയ്ക്കുക"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"പൂര്ണ്ണ സ്ക്രീന്"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-mn/strings_tv.xml b/libs/WindowManager/Shell/res/values-mn/strings_tv.xml index 55519d462b69..7dfec68fc958 100644 --- a/libs/WindowManager/Shell/res/values-mn/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-mn/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Гарчиггүй хөтөлбөр)"</string> <string name="pip_close" msgid="9135220303720555525">"PIP-г хаах"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Бүтэн дэлгэц"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-mr/strings_tv.xml b/libs/WindowManager/Shell/res/values-mr/strings_tv.xml index ad2cfc6035c2..447cb7d26544 100644 --- a/libs/WindowManager/Shell/res/values-mr/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-mr/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(शीर्षक नसलेला कार्यक्रम)"</string> <string name="pip_close" msgid="9135220303720555525">"PIP बंद करा"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"फुल स्क्रीन"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-ms/strings_tv.xml b/libs/WindowManager/Shell/res/values-ms/strings_tv.xml index b2d7214381ef..3a5584def102 100644 --- a/libs/WindowManager/Shell/res/values-ms/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-ms/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Program tiada tajuk)"</string> <string name="pip_close" msgid="9135220303720555525">"Tutup PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Skrin penuh"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-my/strings_tv.xml b/libs/WindowManager/Shell/res/values-my/strings_tv.xml index c18d53932163..84ec0e5caf11 100644 --- a/libs/WindowManager/Shell/res/values-my/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-my/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(ခေါင်းစဉ်မဲ့ အစီအစဉ်)"</string> <string name="pip_close" msgid="9135220303720555525">"PIP ကိုပိတ်ပါ"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"မျက်နှာပြင် အပြည့်"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-nb/strings_tv.xml b/libs/WindowManager/Shell/res/values-nb/strings_tv.xml index 8a7f315606ad..78ec6db0bd98 100644 --- a/libs/WindowManager/Shell/res/values-nb/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-nb/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Program uten tittel)"</string> <string name="pip_close" msgid="9135220303720555525">"Lukk PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Fullskjerm"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-ne/strings_tv.xml b/libs/WindowManager/Shell/res/values-ne/strings_tv.xml index 87fa3279f05e..4458a14f0a83 100644 --- a/libs/WindowManager/Shell/res/values-ne/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-ne/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(शीर्षकविहीन कार्यक्रम)"</string> <string name="pip_close" msgid="9135220303720555525">"PIP लाई बन्द गर्नुहोस्"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"फुल स्क्रिन"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-nl/strings_tv.xml b/libs/WindowManager/Shell/res/values-nl/strings_tv.xml index df3809e5d6c6..d21515dc31f6 100644 --- a/libs/WindowManager/Shell/res/values-nl/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-nl/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Naamloos programma)"</string> <string name="pip_close" msgid="9135220303720555525">"PIP sluiten"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Volledig scherm"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-or/strings_tv.xml b/libs/WindowManager/Shell/res/values-or/strings_tv.xml index 295a5c4ee1ce..a6793502d0df 100644 --- a/libs/WindowManager/Shell/res/values-or/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-or/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(କୌଣସି ଟାଇଟଲ୍ ପ୍ରୋଗ୍ରାମ୍ ନାହିଁ)"</string> <string name="pip_close" msgid="9135220303720555525">"PIP ବନ୍ଦ କରନ୍ତୁ"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"ପୂର୍ଣ୍ଣ ସ୍କ୍ରୀନ୍"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-pa/strings_tv.xml b/libs/WindowManager/Shell/res/values-pa/strings_tv.xml index e32895a9a239..a0ff4f370f9f 100644 --- a/libs/WindowManager/Shell/res/values-pa/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-pa/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(ਸਿਰਲੇਖ-ਰਹਿਤ ਪ੍ਰੋਗਰਾਮ)"</string> <string name="pip_close" msgid="9135220303720555525">"PIP ਬੰਦ ਕਰੋ"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"ਪੂਰੀ ਸਕ੍ਰੀਨ"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-pl/strings_tv.xml b/libs/WindowManager/Shell/res/values-pl/strings_tv.xml index 286fd7b2ff0f..6320893389a6 100644 --- a/libs/WindowManager/Shell/res/values-pl/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-pl/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Program bez tytułu)"</string> <string name="pip_close" msgid="9135220303720555525">"Zamknij PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Pełny ekran"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-pt-rBR/strings_tv.xml b/libs/WindowManager/Shell/res/values-pt-rBR/strings_tv.xml index 57edcdf74cf4..fef9d470d87f 100644 --- a/libs/WindowManager/Shell/res/values-pt-rBR/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-pt-rBR/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(programa sem título)"</string> <string name="pip_close" msgid="9135220303720555525">"Fechar PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Tela cheia"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-pt-rPT/strings_tv.xml b/libs/WindowManager/Shell/res/values-pt-rPT/strings_tv.xml index 9372e0f637cb..461571f57ea7 100644 --- a/libs/WindowManager/Shell/res/values-pt-rPT/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-pt-rPT/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Sem título do programa)"</string> <string name="pip_close" msgid="9135220303720555525">"Fechar PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Ecrã inteiro"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-pt/strings_tv.xml b/libs/WindowManager/Shell/res/values-pt/strings_tv.xml index 57edcdf74cf4..fef9d470d87f 100644 --- a/libs/WindowManager/Shell/res/values-pt/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-pt/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(programa sem título)"</string> <string name="pip_close" msgid="9135220303720555525">"Fechar PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Tela cheia"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-ro/strings_tv.xml b/libs/WindowManager/Shell/res/values-ro/strings_tv.xml index 9438e4955b68..80bf151939ce 100644 --- a/libs/WindowManager/Shell/res/values-ro/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-ro/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Program fără titlu)"</string> <string name="pip_close" msgid="9135220303720555525">"Închideți PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Ecran complet"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-ru/strings_tv.xml b/libs/WindowManager/Shell/res/values-ru/strings_tv.xml index 24785aa7e184..de5348aaff1e 100644 --- a/libs/WindowManager/Shell/res/values-ru/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-ru/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Без названия)"</string> <string name="pip_close" msgid="9135220303720555525">"\"Кадр в кадре\" – выйти"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Во весь экран"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-si/strings_tv.xml b/libs/WindowManager/Shell/res/values-si/strings_tv.xml index 62ee6d4f44d2..047004048665 100644 --- a/libs/WindowManager/Shell/res/values-si/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-si/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(මාතෘකාවක් නැති වැඩසටහන)"</string> <string name="pip_close" msgid="9135220303720555525">"PIP වසන්න"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"සම්පූර්ණ තිරය"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-sk/strings_tv.xml b/libs/WindowManager/Shell/res/values-sk/strings_tv.xml index a7a515cdc61c..41a432c5c649 100644 --- a/libs/WindowManager/Shell/res/values-sk/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-sk/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Program bez názvu)"</string> <string name="pip_close" msgid="9135220303720555525">"Zavrieť režim PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Celá obrazovka"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-sl/strings_tv.xml b/libs/WindowManager/Shell/res/values-sl/strings_tv.xml index fe5c9ae5d2a8..de5605f91b64 100644 --- a/libs/WindowManager/Shell/res/values-sl/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-sl/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Program brez naslova)"</string> <string name="pip_close" msgid="9135220303720555525">"Zapri način PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Celozaslonsko"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-sq/strings_tv.xml b/libs/WindowManager/Shell/res/values-sq/strings_tv.xml index 1d5583b2c826..08a640962401 100644 --- a/libs/WindowManager/Shell/res/values-sq/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-sq/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Program pa titull)"</string> <string name="pip_close" msgid="9135220303720555525">"Mbyll PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Ekrani i plotë"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-sr/strings_tv.xml b/libs/WindowManager/Shell/res/values-sr/strings_tv.xml index 62ad1e8f6e69..f932928ad158 100644 --- a/libs/WindowManager/Shell/res/values-sr/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-sr/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Програм без наслова)"</string> <string name="pip_close" msgid="9135220303720555525">"Затвори PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Цео екран"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-sv/strings_tv.xml b/libs/WindowManager/Shell/res/values-sv/strings_tv.xml index 74fb590c3e4d..1428fdbacf4e 100644 --- a/libs/WindowManager/Shell/res/values-sv/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-sv/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Namnlöst program)"</string> <string name="pip_close" msgid="9135220303720555525">"Stäng PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Helskärm"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-sw/strings_tv.xml b/libs/WindowManager/Shell/res/values-sw/strings_tv.xml index cf0d8a9b3910..615209f9e9bd 100644 --- a/libs/WindowManager/Shell/res/values-sw/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-sw/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Programu isiyo na jina)"</string> <string name="pip_close" msgid="9135220303720555525">"Funga PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Skrini nzima"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-ta/strings_tv.xml b/libs/WindowManager/Shell/res/values-ta/strings_tv.xml index 8bca46314e30..71c242c94d1a 100644 --- a/libs/WindowManager/Shell/res/values-ta/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-ta/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(தலைப்பு இல்லை)"</string> <string name="pip_close" msgid="9135220303720555525">"PIPஐ மூடு"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"முழுத்திரை"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-te/strings_tv.xml b/libs/WindowManager/Shell/res/values-te/strings_tv.xml index 47489efbc4c2..f2dfb39cb99d 100644 --- a/libs/WindowManager/Shell/res/values-te/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-te/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(శీర్షిక లేని ప్రోగ్రామ్)"</string> <string name="pip_close" msgid="9135220303720555525">"PIPని మూసివేయి"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"పూర్తి స్క్రీన్"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-th/strings_tv.xml b/libs/WindowManager/Shell/res/values-th/strings_tv.xml index d3797e7c3cde..e810c885a898 100644 --- a/libs/WindowManager/Shell/res/values-th/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-th/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(ไม่มีชื่อรายการ)"</string> <string name="pip_close" msgid="9135220303720555525">"ปิด PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"เต็มหน้าจอ"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-tl/strings_tv.xml b/libs/WindowManager/Shell/res/values-tl/strings_tv.xml index b01c1115cd34..11d2953467b2 100644 --- a/libs/WindowManager/Shell/res/values-tl/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-tl/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Walang pamagat na programa)"</string> <string name="pip_close" msgid="9135220303720555525">"Isara ang PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Full screen"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-tr/strings_tv.xml b/libs/WindowManager/Shell/res/values-tr/strings_tv.xml index c92c4d02f465..6ed6e9fa1656 100644 --- a/libs/WindowManager/Shell/res/values-tr/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-tr/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Başlıksız program)"</string> <string name="pip_close" msgid="9135220303720555525">"PIP\'yi kapat"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Tam ekran"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-uk/strings_tv.xml b/libs/WindowManager/Shell/res/values-uk/strings_tv.xml index 74d4723d7850..482f59a4c117 100644 --- a/libs/WindowManager/Shell/res/values-uk/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-uk/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Програма без назви)"</string> <string name="pip_close" msgid="9135220303720555525">"Закрити PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"На весь екран"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-ur/strings_tv.xml b/libs/WindowManager/Shell/res/values-ur/strings_tv.xml index 317953309947..c1954c750941 100644 --- a/libs/WindowManager/Shell/res/values-ur/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-ur/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(بلا عنوان پروگرام)"</string> <string name="pip_close" msgid="9135220303720555525">"PIP بند کریں"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"فُل اسکرین"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-uz/strings_tv.xml b/libs/WindowManager/Shell/res/values-uz/strings_tv.xml index ae5a647301c8..514055261e3b 100644 --- a/libs/WindowManager/Shell/res/values-uz/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-uz/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Nomsiz)"</string> <string name="pip_close" msgid="9135220303720555525">"Kadr ichida kadr – chiqish"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Butun ekran"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-vi/strings_tv.xml b/libs/WindowManager/Shell/res/values-vi/strings_tv.xml index 082d12596076..e54d866f10e9 100644 --- a/libs/WindowManager/Shell/res/values-vi/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-vi/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Không có chương trình tiêu đề)"</string> <string name="pip_close" msgid="9135220303720555525">"Đóng PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Toàn màn hình"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-zh-rCN/strings_tv.xml b/libs/WindowManager/Shell/res/values-zh-rCN/strings_tv.xml index cb3fcf7c4c16..9ce1e6c41e75 100644 --- a/libs/WindowManager/Shell/res/values-zh-rCN/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-zh-rCN/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(节目没有标题)"</string> <string name="pip_close" msgid="9135220303720555525">"关闭画中画"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"全屏"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-zh-rHK/strings_tv.xml b/libs/WindowManager/Shell/res/values-zh-rHK/strings_tv.xml index 956243ed6e6d..984677290a85 100644 --- a/libs/WindowManager/Shell/res/values-zh-rHK/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-zh-rHK/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(沒有標題的節目)"</string> <string name="pip_close" msgid="9135220303720555525">"關閉 PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"全螢幕"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-zh-rTW/strings_tv.xml b/libs/WindowManager/Shell/res/values-zh-rTW/strings_tv.xml index 08b2f4bbca89..7314d486e8ff 100644 --- a/libs/WindowManager/Shell/res/values-zh-rTW/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-zh-rTW/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(無標題的節目)"</string> <string name="pip_close" msgid="9135220303720555525">"關閉子母畫面"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"全螢幕"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-zu/strings_tv.xml b/libs/WindowManager/Shell/res/values-zu/strings_tv.xml index 89c7f498652d..63d9dd57fe54 100644 --- a/libs/WindowManager/Shell/res/values-zu/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-zu/strings_tv.xml @@ -21,4 +21,6 @@ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Alukho uhlelo lwesihloko)"</string> <string name="pip_close" msgid="9135220303720555525">"Vala i-PIP"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"Iskrini esigcwele"</string> + <!-- no translation found for pip_move (1544227837964635439) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml index 0cdaa206c156..1b8032b7077b 100644 --- a/libs/WindowManager/Shell/res/values/config.xml +++ b/libs/WindowManager/Shell/res/values/config.xml @@ -43,6 +43,9 @@ <!-- PiP minimum size, which is a % based off the shorter side of display width and height --> <fraction name="config_pipShortestEdgePercent">40%</fraction> + <!-- Show PiP enter split icon, which allows apps to directly enter splitscreen from PiP. --> + <bool name="config_pipEnableEnterSplitButton">false</bool> + <!-- Animation duration when using long press on recents to dock --> <integer name="long_press_dock_anim_duration">250</integer> diff --git a/services/core/java/com/android/server/wm/BackGestureController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java index f8f625486e56..b310dd638e6c 100644 --- a/services/core/java/com/android/server/wm/BackGestureController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java @@ -14,24 +14,22 @@ * limitations under the License. */ -package com.android.server.wm; +package com.android.wm.shell.back; -import android.os.SystemProperties; +import android.view.MotionEvent; /** - * Controller to handle actions related to the back gesture on the server side. + * Interface for SysUI to get access to the Back animation related methods. */ -public class BackGestureController { +public interface BackAnimation { - private static final String BACK_PREDICTABILITY_PROP = "persist.debug.back_predictability"; - - public static boolean isEnabled() { - return SystemProperties.getInt(BACK_PREDICTABILITY_PROP, 0) > 0; - } + /** + * Called when a {@link MotionEvent} is generated by a back gesture. + */ + void onBackMotion(MotionEvent event); /** - * Start a remote animation the back gesture. + * Sets whether the back gesture is past the trigger threshold or not. */ - public void startBackPreview() { - } + void setTriggerBack(boolean triggerBack); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java new file mode 100644 index 000000000000..229e8ee04982 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java @@ -0,0 +1,286 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.back; + +import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.ActivityTaskManager; +import android.app.IActivityTaskManager; +import android.app.WindowConfiguration; +import android.graphics.Point; +import android.graphics.PointF; +import android.hardware.HardwareBuffer; +import android.os.RemoteException; +import android.os.SystemProperties; +import android.util.Log; +import android.view.MotionEvent; +import android.view.SurfaceControl; +import android.window.BackNavigationInfo; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.protolog.common.ProtoLog; +import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.common.annotations.ShellMainThread; + +/** + * Controls the window animation run when a user initiates a back gesture. + */ +public class BackAnimationController { + + private static final String BACK_PREDICTABILITY_PROP = "persist.debug.back_predictability"; + public static final boolean IS_ENABLED = SystemProperties + .getInt(BACK_PREDICTABILITY_PROP, 0) > 0; + private static final String TAG = "BackAnimationController"; + + /** + * Location of the initial touch event of the back gesture. + */ + private final PointF mInitTouchLocation = new PointF(); + + /** + * Raw delta between {@link #mInitTouchLocation} and the last touch location. + */ + private final Point mTouchEventDelta = new Point(); + private final ShellExecutor mShellExecutor; + + /** True when a back gesture is ongoing */ + private boolean mBackGestureStarted = false; + + /** @see #setTriggerBack(boolean) */ + private boolean mTriggerBack; + + @Nullable + private BackNavigationInfo mBackNavigationInfo; + private final SurfaceControl.Transaction mTransaction; + private final IActivityTaskManager mActivityTaskManager; + + public BackAnimationController(@ShellMainThread ShellExecutor shellExecutor) { + this(shellExecutor, new SurfaceControl.Transaction(), ActivityTaskManager.getService()); + } + + @VisibleForTesting + BackAnimationController(@NonNull ShellExecutor shellExecutor, + @NonNull SurfaceControl.Transaction transaction, + @NonNull IActivityTaskManager activityTaskManager) { + mShellExecutor = shellExecutor; + mTransaction = transaction; + mActivityTaskManager = activityTaskManager; + } + + public BackAnimation getBackAnimationImpl() { + return mBackAnimation; + } + + private final BackAnimation mBackAnimation = new BackAnimationImpl(); + + private class BackAnimationImpl implements BackAnimation { + + @Override + public void onBackMotion(MotionEvent event) { + mShellExecutor.execute(() -> onMotionEvent(event)); + } + + @Override + public void setTriggerBack(boolean triggerBack) { + mShellExecutor.execute(() -> BackAnimationController.this.setTriggerBack(triggerBack)); + } + } + + /** + * Called when a new motion event needs to be transferred to this + * {@link BackAnimationController} + */ + public void onMotionEvent(MotionEvent event) { + int action = event.getActionMasked(); + if (action == MotionEvent.ACTION_DOWN) { + initAnimation(event); + } else if (action == MotionEvent.ACTION_MOVE) { + onMove(event); + } else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { + onGestureFinished(); + } + } + + private void initAnimation(MotionEvent event) { + ProtoLog.d(WM_SHELL_BACK_PREVIEW, "initAnimation mMotionStarted=%b", mBackGestureStarted); + if (mBackGestureStarted) { + Log.e(TAG, "Animation is being initialized but is already started."); + return; + } + + if (mBackNavigationInfo != null) { + finishAnimation(); + } + mInitTouchLocation.set(event.getX(), event.getY()); + mBackGestureStarted = true; + + try { + mBackNavigationInfo = mActivityTaskManager.startBackNavigation(); + onBackNavigationInfoReceived(mBackNavigationInfo); + } catch (RemoteException remoteException) { + Log.e(TAG, "Failed to initAnimation", remoteException); + finishAnimation(); + } + } + + private void onBackNavigationInfoReceived(@Nullable BackNavigationInfo backNavigationInfo) { + if (backNavigationInfo == null + || backNavigationInfo.getDepartingWindowContainer() == null) { + Log.e(TAG, "Received BackNavigationInfo is null."); + finishAnimation(); + return; + } + + HardwareBuffer hardwareBuffer = backNavigationInfo.getScreenshotHardwareBuffer(); + if (hardwareBuffer != null) { + displayTargetScreenshot(hardwareBuffer, + backNavigationInfo.getTaskWindowConfiguration()); + } + mTransaction.apply(); + } + + /** + * Display the screenshot of the activity beneath. + * + * @param hardwareBuffer The buffer containing the screenshot. + */ + private void displayTargetScreenshot(@NonNull HardwareBuffer hardwareBuffer, + WindowConfiguration taskWindowConfiguration) { + SurfaceControl screenshotSurface = + mBackNavigationInfo == null ? null : mBackNavigationInfo.getScreenshotSurface(); + if (screenshotSurface == null) { + Log.e(TAG, "BackNavigationInfo doesn't contain a surface for the screenshot. "); + return; + } + + // Scale the buffer to fill the whole Task + float sx = 1; + float sy = 1; + float w = taskWindowConfiguration.getBounds().width(); + float h = taskWindowConfiguration.getBounds().height(); + + if (w != hardwareBuffer.getWidth()) { + sx = w / hardwareBuffer.getWidth(); + } + + if (h != hardwareBuffer.getHeight()) { + sy = h / hardwareBuffer.getHeight(); + } + mTransaction.setScale(screenshotSurface, sx, sy); + mTransaction.setBuffer(screenshotSurface, hardwareBuffer); + mTransaction.setVisibility(screenshotSurface, true); + } + + private void onMove(MotionEvent event) { + if (!mBackGestureStarted || mBackNavigationInfo == null) { + return; + } + int deltaX = Math.round(event.getX() - mInitTouchLocation.x); + int deltaY = Math.round(event.getY() - mInitTouchLocation.y); + ProtoLog.v(WM_SHELL_BACK_PREVIEW, "Runner move: %d %d", deltaX, deltaY); + SurfaceControl topWindowLeash = mBackNavigationInfo.getDepartingWindowContainer(); + mTransaction.setPosition(topWindowLeash, deltaX, deltaY); + mTouchEventDelta.set(deltaX, deltaY); + mTransaction.apply(); + } + + private void onGestureFinished() { + ProtoLog.d(WM_SHELL_BACK_PREVIEW, "onGestureFinished() mTriggerBack == %s", mTriggerBack); + if (mBackGestureStarted) { + if (mTriggerBack) { + prepareTransition(); + } else { + resetPositionAnimated(); + } + } + mBackGestureStarted = false; + mTriggerBack = false; + } + + /** + * Animate the top window leash to its initial position. + */ + private void resetPositionAnimated() { + mBackGestureStarted = false; + // TODO(208786853) Handle overlap with a new coming gesture. + ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Runner: Back not triggered, cancelling animation " + + "mLastPos=%s mInitTouch=%s", mTouchEventDelta, mInitTouchLocation); + + // TODO(208427216) : Replace placeholder animation with an actual one. + ValueAnimator animation = ValueAnimator.ofFloat(0f, 1f).setDuration(200); + animation.addUpdateListener(animation1 -> { + if (mBackNavigationInfo == null) { + return; + } + float fraction = animation1.getAnimatedFraction(); + int deltaX = Math.round(mTouchEventDelta.x - (mTouchEventDelta.x * fraction)); + int deltaY = Math.round(mTouchEventDelta.y - (mTouchEventDelta.y * fraction)); + mTransaction.setPosition(mBackNavigationInfo.getDepartingWindowContainer(), + deltaX, deltaY); + mTransaction.apply(); + }); + + animation.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: onAnimationEnd"); + finishAnimation(); + } + }); + animation.start(); + } + + private void prepareTransition() { + ProtoLog.d(WM_SHELL_BACK_PREVIEW, "prepareTransition()"); + mTriggerBack = false; + mBackGestureStarted = false; + } + + /** + * Sets to true when the back gesture has passed the triggering threshold, false otherwise. + */ + public void setTriggerBack(boolean triggerBack) { + mTriggerBack = triggerBack; + } + + private void finishAnimation() { + ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: finishAnimation()"); + mBackGestureStarted = false; + mTouchEventDelta.set(0, 0); + mInitTouchLocation.set(0, 0); + BackNavigationInfo backNavigationInfo = mBackNavigationInfo; + mBackNavigationInfo = null; + if (backNavigationInfo == null) { + return; + } + SurfaceControl topWindowLeash = backNavigationInfo.getDepartingWindowContainer(); + if (topWindowLeash != null && topWindowLeash.isValid()) { + mTransaction.remove(topWindowLeash); + } + SurfaceControl screenshotSurface = backNavigationInfo.getScreenshotSurface(); + if (screenshotSurface != null && screenshotSurface.isValid()) { + mTransaction.remove(screenshotSurface); + } + mTransaction.apply(); + backNavigationInfo.onBackNavigationFinished(); + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java index 682363904a23..57cb7a5a57d7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java @@ -17,6 +17,8 @@ package com.android.wm.shell.bubbles; import static android.app.ActivityTaskManager.INVALID_TASK_ID; +import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_DELETED; +import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_UPDATED; import static android.service.notification.NotificationListenerService.REASON_CANCEL; import static android.view.View.INVISIBLE; import static android.view.View.VISIBLE; @@ -42,6 +44,7 @@ import android.annotation.NonNull; import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.Notification; +import android.app.NotificationChannel; import android.app.PendingIntent; import android.content.Context; import android.content.pm.ActivityInfo; @@ -1092,6 +1095,24 @@ public class BubbleController { } } + @VisibleForTesting + public void onNotificationChannelModified(String pkg, UserHandle user, + NotificationChannel channel, int modificationType) { + // Only query overflow bubbles here because active bubbles will have an active notification + // and channel changes we care about would result in a ranking update. + List<Bubble> overflowBubbles = new ArrayList<>(mBubbleData.getOverflowBubbles()); + for (int i = 0; i < overflowBubbles.size(); i++) { + Bubble b = overflowBubbles.get(i); + if (Objects.equals(b.getShortcutId(), channel.getConversationId()) + && b.getPackageName().equals(pkg) + && b.getUser().getIdentifier() == user.getIdentifier()) { + if (!channel.canBubble() || channel.isDeleted()) { + mBubbleData.dismissBubbleWithKey(b.getKey(), DISMISS_NO_LONGER_BUBBLE); + } + } + } + } + /** * Retrieves any bubbles that are part of the notification group represented by the provided * group key. @@ -1670,6 +1691,19 @@ public class BubbleController { } @Override + public void onNotificationChannelModified(String pkg, + UserHandle user, NotificationChannel channel, int modificationType) { + // Bubbles only cares about updates or deletions. + if (modificationType == NOTIFICATION_CHANNEL_OR_GROUP_UPDATED + || modificationType == NOTIFICATION_CHANNEL_OR_GROUP_DELETED) { + mMainExecutor.execute(() -> { + BubbleController.this.onNotificationChannelModified(pkg, user, channel, + modificationType); + }); + } + } + + @Override public void onStatusBarVisibilityChanged(boolean visible) { mMainExecutor.execute(() -> { BubbleController.this.onStatusBarVisibilityChanged(visible); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java index c82249b8a369..af403d23d69c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java @@ -21,9 +21,12 @@ import static java.lang.annotation.ElementType.LOCAL_VARIABLE; import static java.lang.annotation.ElementType.PARAMETER; import static java.lang.annotation.RetentionPolicy.SOURCE; +import android.app.NotificationChannel; import android.content.pm.UserInfo; import android.content.res.Configuration; import android.os.Bundle; +import android.os.UserHandle; +import android.service.notification.NotificationListenerService; import android.service.notification.NotificationListenerService.RankingMap; import android.util.ArraySet; import android.util.Pair; @@ -191,10 +194,26 @@ public interface Bubbles { * @param entryDataByKey a map of ranking key to bubble entry and whether the entry should * bubble up */ - void onRankingUpdated(RankingMap rankingMap, + void onRankingUpdated( + RankingMap rankingMap, HashMap<String, Pair<BubbleEntry, Boolean>> entryDataByKey); /** + * Called when a notification channel is modified, in response to + * {@link NotificationListenerService#onNotificationChannelModified}. + * + * @param pkg the package the notification channel belongs to. + * @param user the user the notification channel belongs to. + * @param channel the channel being modified. + * @param modificationType the type of modification that occurred to the channel. + */ + void onNotificationChannelModified( + String pkg, + UserHandle user, + NotificationChannel channel, + int modificationType); + + /** * Called when the status bar has become visible or invisible (either permanently or * temporarily). */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java index 7db49f05a5dc..e2bc36028405 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java @@ -19,6 +19,7 @@ package com.android.wm.shell.common; import android.annotation.Nullable; import android.content.Context; import android.content.res.Configuration; +import android.graphics.Rect; import android.hardware.display.DisplayManager; import android.os.RemoteException; import android.util.Slog; @@ -34,6 +35,7 @@ import com.android.wm.shell.common.DisplayChangeController.OnDisplayChangingList import com.android.wm.shell.common.annotations.ShellMainThread; import java.util.ArrayList; +import java.util.List; /** * This module deals with display rotations coming from WM. When WM starts a rotation: after it has @@ -243,6 +245,19 @@ public class DisplayController { } } + private void onKeepClearAreasChanged(int displayId, List<Rect> keepClearAreas) { + synchronized (mDisplays) { + if (mDisplays.get(displayId) == null || getDisplay(displayId) == null) { + Slog.w(TAG, "Skipping onKeepClearAreasChanged on unknown" + + " display, displayId=" + displayId); + return; + } + for (int i = mDisplayChangedListeners.size() - 1; i >= 0; --i) { + mDisplayChangedListeners.get(i).onKeepClearAreasChanged(displayId, keepClearAreas); + } + } + } + private static class DisplayRecord { private int mDisplayId; private Context mContext; @@ -301,6 +316,13 @@ public class DisplayController { DisplayController.this.onFixedRotationFinished(displayId); }); } + + @Override + public void onKeepClearAreasChanged(int displayId, List<Rect> keepClearAreas) { + mMainExecutor.execute(() -> { + DisplayController.this.onKeepClearAreasChanged(displayId, keepClearAreas); + }); + } } /** @@ -335,5 +357,10 @@ public class DisplayController { * Called when fixed rotation on a display is finished. */ default void onFixedRotationFinished(int displayId) {} + + /** + * Called when keep-clear areas on a display have changed. + */ + default void onKeepClearAreasChanged(int displayId, List<Rect> keepClearAreas) {} } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/magnetictarget/MagnetizedObject.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/magnetictarget/MagnetizedObject.kt index 9e012598554b..aac1d0626d30 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/magnetictarget/MagnetizedObject.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/magnetictarget/MagnetizedObject.kt @@ -17,13 +17,10 @@ package com.android.wm.shell.common.magnetictarget import android.annotation.SuppressLint import android.content.Context -import android.database.ContentObserver import android.graphics.PointF -import android.os.Handler -import android.os.UserHandle +import android.os.VibrationAttributes import android.os.VibrationEffect import android.os.Vibrator -import android.provider.Settings import android.view.MotionEvent import android.view.VelocityTracker import android.view.View @@ -147,6 +144,8 @@ abstract class MagnetizedObject<T : Any>( private val velocityTracker: VelocityTracker = VelocityTracker.obtain() private val vibrator: Vibrator = context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator + private val vibrationAttributes: VibrationAttributes = VibrationAttributes.createForUsage( + VibrationAttributes.USAGE_TOUCH) private var touchDown = PointF() private var touchSlop = 0 @@ -268,10 +267,6 @@ abstract class MagnetizedObject<T : Any>( */ var flungIntoTargetSpringConfig = springConfig - init { - initHapticSettingObserver(context) - } - /** * Adds the provided MagneticTarget to this object. The object will now be attracted to the * target if it strays within its magnetic field or is flung towards it. @@ -468,8 +463,8 @@ abstract class MagnetizedObject<T : Any>( /** Plays the given vibration effect if haptics are enabled. */ @SuppressLint("MissingPermission") private fun vibrateIfEnabled(effectId: Int) { - if (hapticsEnabled && systemHapticsEnabled) { - vibrator.vibrate(VibrationEffect.createPredefined(effectId)) + if (hapticsEnabled) { + vibrator.vibrate(VibrationEffect.createPredefined(effectId), vibrationAttributes) } } @@ -622,44 +617,6 @@ abstract class MagnetizedObject<T : Any>( } companion object { - - /** - * Whether the HAPTIC_FEEDBACK_ENABLED setting is true. - * - * We put it in the companion object because we need to register a settings observer and - * [MagnetizedObject] doesn't have an obvious lifecycle so we don't have a good time to - * remove that observer. Since this settings is shared among all instances we just let all - * instances read from this value. - */ - private var systemHapticsEnabled = false - private var hapticSettingObserverInitialized = false - - private fun initHapticSettingObserver(context: Context) { - if (hapticSettingObserverInitialized) { - return - } - - val hapticSettingObserver = - object : ContentObserver(Handler.getMain()) { - override fun onChange(selfChange: Boolean) { - systemHapticsEnabled = - Settings.System.getIntForUser( - context.contentResolver, - Settings.System.HAPTIC_FEEDBACK_ENABLED, - 0, - UserHandle.USER_CURRENT) != 0 - } - } - - context.contentResolver.registerContentObserver( - Settings.System.getUriFor(Settings.System.HAPTIC_FEEDBACK_ENABLED), - true /* notifyForDescendants */, hapticSettingObserver) - - // Trigger the observer once to initialize systemHapticsEnabled. - hapticSettingObserver.onChange(false /* selfChange */) - hapticSettingObserverInitialized = true - } - /** * Magnetizes the given view. Magnetized views are attracted to one or more magnetic * targets. Magnetic targets attract objects that are dragged near them, and hold them there diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java index 23d9b8b14159..f61e62444366 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java @@ -40,6 +40,8 @@ import com.android.wm.shell.TaskViewTransitions; import com.android.wm.shell.WindowManagerShellWrapper; import com.android.wm.shell.apppairs.AppPairs; import com.android.wm.shell.apppairs.AppPairsController; +import com.android.wm.shell.back.BackAnimation; +import com.android.wm.shell.back.BackAnimationController; import com.android.wm.shell.bubbles.BubbleController; import com.android.wm.shell.bubbles.Bubbles; import com.android.wm.shell.common.DisplayController; @@ -238,6 +240,17 @@ public abstract class WMShellBaseModule { } // + // Back animation + // + + @WMSingleton + @Provides + static Optional<BackAnimation> provideBackAnimation( + Optional<BackAnimationController> backAnimationController) { + return backAnimationController.map(BackAnimationController::getBackAnimationImpl); + } + + // // Bubbles (optional feature) // @@ -678,4 +691,16 @@ public abstract class WMShellBaseModule { legacySplitScreenOptional, splitScreenOptional, pipOptional, oneHandedOptional, hideDisplayCutout, appPairsOptional, recentTasksOptional, mainExecutor); } + + @WMSingleton + @Provides + static Optional<BackAnimationController> provideBackAnimationController( + @ShellMainThread ShellExecutor shellExecutor + ) { + if (BackAnimationController.IS_ENABLED) { + return Optional.of( + new BackAnimationController(shellExecutor)); + } + return Optional.empty(); + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java index cc37caec5225..7879e7a5bb00 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java @@ -246,10 +246,11 @@ public class WMShellModule { PipBoundsState pipBoundsState, PipMediaController pipMediaController, SystemWindows systemWindows, Optional<SplitScreenController> splitScreenOptional, + PipUiEventLogger pipUiEventLogger, @ShellMainThread ShellExecutor mainExecutor, @ShellMainThread Handler mainHandler) { return new PhonePipMenuController(context, pipBoundsState, pipMediaController, - systemWindows, splitScreenOptional, mainExecutor, mainHandler); + systemWindows, splitScreenOptional, pipUiEventLogger, mainExecutor, mainHandler); } @WMSingleton diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java index 4c09a4e9938f..17005ea7d500 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java @@ -397,8 +397,6 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, return; } - mPipUiEventLoggerLogger.log( - PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_EXPAND_TO_FULLSCREEN); final WindowContainerTransaction wct = new WindowContainerTransaction(); if (ENABLE_SHELL_TRANSITIONS) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java index 69e78364c5e1..3e5d5f645725 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java @@ -330,12 +330,10 @@ public class PipTransition extends PipTransitionController { @NonNull TransitionInfo.Change pipChange) { final int rotateDelta = deltaRotation(displayRotationChange.getStartRotation(), displayRotationChange.getEndRotation()); - final int displayW = displayRotationChange.getEndAbsBounds().width(); - final int displayH = displayRotationChange.getEndAbsBounds().height(); // Counter-rotate all "going-away" things since they are still in the old orientation. final CounterRotatorHelper rotator = new CounterRotatorHelper(); - rotator.handleClosingChanges(info, startTransaction, rotateDelta, displayW, displayH); + rotator.handleClosingChanges(info, startTransaction, displayRotationChange); mFinishCallback = (wct, wctCB) -> { mPipOrganizer.onExitPipFinished(pipChange.getTaskInfo()); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUiEventLogger.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUiEventLogger.java index a0a76d801cf4..9c23a32a7d2b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUiEventLogger.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUiEventLogger.java @@ -107,7 +107,10 @@ public class PipUiEventLogger { PICTURE_IN_PICTURE_STASH_LEFT(710), @UiEvent(doc = "User stashed picture-in-picture to the right side") - PICTURE_IN_PICTURE_STASH_RIGHT(711); + PICTURE_IN_PICTURE_STASH_RIGHT(711), + + @UiEvent(doc = "User taps on the settings button in PiP menu") + PICTURE_IN_PICTURE_SHOW_SETTINGS(933); private final int mId; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java index 101a55d8d367..6ec8f5b924f8 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java @@ -44,6 +44,7 @@ import com.android.wm.shell.pip.PipBoundsState; import com.android.wm.shell.pip.PipMediaController; import com.android.wm.shell.pip.PipMediaController.ActionListener; import com.android.wm.shell.pip.PipMenuController; +import com.android.wm.shell.pip.PipUiEventLogger; import com.android.wm.shell.splitscreen.SplitScreenController; import java.io.PrintWriter; @@ -118,6 +119,7 @@ public class PhonePipMenuController implements PipMenuController { private final ArrayList<Listener> mListeners = new ArrayList<>(); private final SystemWindows mSystemWindows; private final Optional<SplitScreenController> mSplitScreenController; + private final PipUiEventLogger mPipUiEventLogger; private ParceledListSlice<RemoteAction> mAppActions; private ParceledListSlice<RemoteAction> mMediaActions; private SyncRtSurfaceTransactionApplier mApplier; @@ -150,6 +152,7 @@ public class PhonePipMenuController implements PipMenuController { public PhonePipMenuController(Context context, PipBoundsState pipBoundsState, PipMediaController mediaController, SystemWindows systemWindows, Optional<SplitScreenController> splitScreenOptional, + PipUiEventLogger pipUiEventLogger, ShellExecutor mainExecutor, Handler mainHandler) { mContext = context; mPipBoundsState = pipBoundsState; @@ -158,6 +161,7 @@ public class PhonePipMenuController implements PipMenuController { mMainExecutor = mainExecutor; mMainHandler = mainHandler; mSplitScreenController = splitScreenOptional; + mPipUiEventLogger = pipUiEventLogger; } public boolean isMenuVisible() { @@ -187,7 +191,7 @@ public class PhonePipMenuController implements PipMenuController { detachPipMenuView(); } mPipMenuView = new PipMenuView(mContext, this, mMainExecutor, mMainHandler, - mSplitScreenController); + mSplitScreenController, mPipUiEventLogger); mSystemWindows.addView(mPipMenuView, getPipMenuLayoutParams(MENU_WINDOW_TITLE, 0 /* width */, 0 /* height */), 0, SHELL_ROOT_LAYER_PIP); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java index da4bbe81a3e6..225305bd5178 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java @@ -63,6 +63,7 @@ import android.widget.LinearLayout; import com.android.wm.shell.R; import com.android.wm.shell.animation.Interpolators; import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.pip.PipUiEventLogger; import com.android.wm.shell.pip.PipUtils; import com.android.wm.shell.splitscreen.SplitScreenController; @@ -104,8 +105,6 @@ public class PipMenuView extends FrameLayout { private static final float MENU_BACKGROUND_ALPHA = 0.3f; private static final float DISABLED_ACTION_ALPHA = 0.54f; - private static final boolean ENABLE_ENTER_SPLIT = true; - private int mMenuState; private boolean mAllowMenuTimeout = true; private boolean mAllowTouches = true; @@ -121,8 +120,9 @@ public class PipMenuView extends FrameLayout { private int mBetweenActionPaddingLand; private AnimatorSet mMenuContainerAnimator; - private PhonePipMenuController mController; - private Optional<SplitScreenController> mSplitScreenControllerOptional; + private final PhonePipMenuController mController; + private final Optional<SplitScreenController> mSplitScreenControllerOptional; + private final PipUiEventLogger mPipUiEventLogger; private ValueAnimator.AnimatorUpdateListener mMenuBgUpdateListener = new ValueAnimator.AnimatorUpdateListener() { @@ -152,13 +152,15 @@ public class PipMenuView extends FrameLayout { public PipMenuView(Context context, PhonePipMenuController controller, ShellExecutor mainExecutor, Handler mainHandler, - Optional<SplitScreenController> splitScreenController) { + Optional<SplitScreenController> splitScreenController, + PipUiEventLogger pipUiEventLogger) { super(context, null, 0); mContext = context; mController = controller; mMainExecutor = mainExecutor; mMainHandler = mainHandler; mSplitScreenControllerOptional = splitScreenController; + mPipUiEventLogger = pipUiEventLogger; mAccessibilityManager = context.getSystemService(AccessibilityManager.class); inflate(context, R.layout.pip_menu, this); @@ -277,6 +279,8 @@ public class PipMenuView extends FrameLayout { boolean resizeMenuOnShow, boolean withDelay, boolean showResizeHandle) { mAllowMenuTimeout = allowMenuTimeout; mDidLastShowMenuResize = resizeMenuOnShow; + final boolean enableEnterSplit = + mContext.getResources().getBoolean(R.bool.config_pipEnableEnterSplitButton); if (mMenuState != menuState) { // Disallow touches if the menu needs to resize while showing, and we are transitioning // to/from a full menu state. @@ -297,7 +301,7 @@ public class PipMenuView extends FrameLayout { mDismissButton.getAlpha(), 1f); ObjectAnimator enterSplitAnim = ObjectAnimator.ofFloat(mEnterSplitButton, View.ALPHA, mEnterSplitButton.getAlpha(), - ENABLE_ENTER_SPLIT && mFocusedTaskAllowSplitScreen ? 1f : 0f); + enableEnterSplit && mFocusedTaskAllowSplitScreen ? 1f : 0f); if (menuState == MENU_STATE_FULL) { mMenuContainerAnimator.playTogether(menuAnim, settingsAnim, dismissAnim, enterSplitAnim); @@ -539,6 +543,8 @@ public class PipMenuView extends FrameLayout { // handles the message hideMenu(mController::onPipExpand, false /* notifyMenuVisibility */, true /* resize */, ANIM_TYPE_HIDE); + mPipUiEventLogger.log( + PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_EXPAND_TO_FULLSCREEN); } private void dismissPip() { @@ -547,6 +553,7 @@ public class PipMenuView extends FrameLayout { // any other dismissal that will update the touch state and fade out the PIP task // and the menu view at the same time. mController.onPipDismiss(); + mPipUiEventLogger.log(PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_TAP_TO_REMOVE); } } @@ -566,6 +573,7 @@ public class PipMenuView extends FrameLayout { Uri.fromParts("package", topPipActivityInfo.first.getPackageName(), null)); settingsIntent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK); mContext.startActivityAsUser(settingsIntent, UserHandle.of(topPipActivityInfo.second)); + mPipUiEventLogger.log(PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_SHOW_SETTINGS); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java index 3ace5f405d36..350f2856e2bc 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java @@ -149,7 +149,6 @@ public class PipTouchHandler { @Override public void onPipDismiss() { - mPipUiEventLogger.log(PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_TAP_TO_REMOVE); mTouchState.removeDoubleTapTimeoutCallback(); mMotionHelper.dismissPip(); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java index 79c1df2174b9..20c4e21a811d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java @@ -34,6 +34,8 @@ public enum ShellProtoLogGroup implements IProtoLogGroup { Consts.TAG_WM_SHELL), WM_SHELL_STARTING_WINDOW(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, Consts.TAG_WM_STARTING_WINDOW), + WM_SHELL_BACK_PREVIEW(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true, + "ShellBackPreview"), TEST_GROUP(true, true, false, "WindowManagerShellProtoLogTest"); private final boolean mEnabled; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java index 8664d9be3340..d30d0cc95f46 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java @@ -70,7 +70,8 @@ class SplitScreenTransitions { IBinder mPendingRecent = null; private IBinder mAnimatingTransition = null; - private OneShotRemoteHandler mRemoteHandler = null; + private OneShotRemoteHandler mPendingRemoteHandler = null; + private OneShotRemoteHandler mActiveRemoteHandler = null; private final Transitions.TransitionFinishCallback mRemoteFinishCB = this::onFinish; @@ -96,10 +97,11 @@ class SplitScreenTransitions { @NonNull WindowContainerToken mainRoot, @NonNull WindowContainerToken sideRoot) { mFinishCallback = finishCallback; mAnimatingTransition = transition; - if (mRemoteHandler != null) { - mRemoteHandler.startAnimation(transition, info, startTransaction, finishTransaction, - mRemoteFinishCB); - mRemoteHandler = null; + if (mPendingRemoteHandler != null) { + mPendingRemoteHandler.startAnimation(transition, info, startTransaction, + finishTransaction, mRemoteFinishCB); + mActiveRemoteHandler = mPendingRemoteHandler; + mPendingRemoteHandler = null; return; } playInternalAnimation(transition, info, startTransaction, mainRoot, sideRoot); @@ -172,15 +174,14 @@ class SplitScreenTransitions { IBinder startEnterTransition(@WindowManager.TransitionType int transitType, @NonNull WindowContainerTransaction wct, @Nullable RemoteTransition remoteTransition, @NonNull Transitions.TransitionHandler handler) { + final IBinder transition = mTransitions.startTransition(transitType, wct, handler); + mPendingEnter = transition; + if (remoteTransition != null) { // Wrapping it for ease-of-use (OneShot handles all the binder linking/death stuff) - mRemoteHandler = new OneShotRemoteHandler( + mPendingRemoteHandler = new OneShotRemoteHandler( mTransitions.getMainExecutor(), remoteTransition); - } - final IBinder transition = mTransitions.startTransition(transitType, wct, handler); - mPendingEnter = transition; - if (mRemoteHandler != null) { - mRemoteHandler.setTransition(transition); + mPendingRemoteHandler.setTransition(transition); } return transition; } @@ -211,9 +212,9 @@ class SplitScreenTransitions { if (remoteTransition != null) { // Wrapping it for ease-of-use (OneShot handles all the binder linking/death stuff) - mRemoteHandler = new OneShotRemoteHandler( + mPendingRemoteHandler = new OneShotRemoteHandler( mTransitions.getMainExecutor(), remoteTransition); - mRemoteHandler.setTransition(transition); + mPendingRemoteHandler.setTransition(transition); } ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " splitTransition " @@ -221,6 +222,13 @@ class SplitScreenTransitions { return transition; } + void mergeAnimation(IBinder transition, TransitionInfo info, SurfaceControl.Transaction t, + IBinder mergeTarget, Transitions.TransitionFinishCallback finishCallback) { + if (mergeTarget == mAnimatingTransition && mActiveRemoteHandler != null) { + mActiveRemoteHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback); + } + } + void onFinish(WindowContainerTransaction wct, WindowContainerTransactionCallback wctCB) { if (!mAnimations.isEmpty()) return; mOnFinish.run(); @@ -241,11 +249,13 @@ class SplitScreenTransitions { } if (mAnimatingTransition == mPendingRecent) { // If the wct is not null while finishing recent transition, it indicates it's not - // returning to home and hence needing the wct to reorder tasks. - final boolean toHome = wct == null; - mStageCoordinator.finishRecentAnimation(toHome); + // dismissing split and thus need to reorder split task so they can be on top again. + final boolean dismissSplit = wct == null; + mStageCoordinator.finishRecentAnimation(dismissSplit); mPendingRecent = null; } + mPendingRemoteHandler = null; + mActiveRemoteHandler = null; mAnimatingTransition = null; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index c8120509f0db..e592101d2b20 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -166,17 +166,6 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, @StageType private int mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED; - private final Runnable mOnTransitionAnimationComplete = () -> { - // If still playing, let it finish. - if (!isSplitScreenVisible()) { - // Update divider state after animation so that it is still around and positioned - // properly for the animation itself. - mSplitLayout.release(); - mSplitLayout.resetDividerPosition(); - mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED; - } - }; - private final SplitWindowManager.ParentContainerCallbacks mParentContainerCallbacks = new SplitWindowManager.ParentContainerCallbacks() { @Override @@ -237,7 +226,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, deviceStateManager.registerCallback(taskOrganizer.getExecutor(), new DeviceStateManager.FoldStateListener(mContext, this::onFoldedStateChanged)); mSplitTransitions = new SplitScreenTransitions(transactionPool, transitions, - mOnTransitionAnimationComplete, this); + this::onTransitionAnimationComplete, this); mDisplayController.addDisplayWindowListener(this); mDisplayLayout = new DisplayLayout(displayController.getDisplayLayout(displayId)); transitions.addHandler(this); @@ -267,7 +256,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, mRootTDAOrganizer.registerListener(displayId, this); mSplitLayout = splitLayout; mSplitTransitions = new SplitScreenTransitions(transactionPool, transitions, - mOnTransitionAnimationComplete, this); + this::onTransitionAnimationComplete, this); mMainUnfoldController = unfoldControllerProvider.get().orElse(null); mSideUnfoldController = unfoldControllerProvider.get().orElse(null); mLogger = logger; @@ -1234,7 +1223,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, final ActivityManager.RunningTaskInfo triggerTask = request.getTriggerTask(); if (triggerTask == null) { // Still want to monitor everything while in split-screen, so return non-null. - return isSplitScreenVisible() ? new WindowContainerTransaction() : null; + return mMainStage.isActive() ? new WindowContainerTransaction() : null; } else if (triggerTask.displayId != mDisplayId) { // Skip handling task on the other display. return null; @@ -1250,7 +1239,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, mRecentTasks.ifPresent(recentTasks -> recentTasks.removeSplitPair(triggerTask.taskId)); } - if (isSplitScreenVisible()) { + if (mMainStage.isActive()) { // Try to handle everything while in split-screen, so return a WCT even if it's empty. ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " split is active so using split" + "Transition to handle request. triggerTask=%d type=%s mainChildren=%d" @@ -1296,6 +1285,13 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, } @Override + public void mergeAnimation(IBinder transition, TransitionInfo info, + SurfaceControl.Transaction t, IBinder mergeTarget, + Transitions.TransitionFinishCallback finishCallback) { + mSplitTransitions.mergeAnimation(transition, info, t, mergeTarget, finishCallback); + } + + @Override public void onTransitionMerged(@NonNull IBinder transition) { // Once the pending enter transition got merged, make sure to bring divider bar visible and // clear the pending transition from cache to prevent mess-up the following state. @@ -1375,6 +1371,17 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, return true; } + void onTransitionAnimationComplete() { + // If still playing, let it finish. + if (!mMainStage.isActive()) { + // Update divider state after animation so that it is still around and positioned + // properly for the animation itself. + mSplitLayout.release(); + mSplitLayout.resetDividerPosition(); + mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED; + } + } + private boolean startPendingEnterAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t) { // First, verify that we actually have opened apps in both splits. @@ -1513,8 +1520,17 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, return true; } - void finishRecentAnimation(boolean toHome) { - if (toHome) { + void finishRecentAnimation(boolean dismissSplit) { + // Exclude the case that the split screen has been dismissed already. + if (!mMainStage.isActive()) { + // The latest split dismissing transition might be a no-op transition and thus won't + // callback startAnimation, update split visibility here to cover this kind of no-op + // transition case. + setSplitsVisible(false); + return; + } + + if (dismissSplit) { final WindowContainerTransaction wct = new WindowContainerTransaction(); prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, wct); mSplitTransitions.startDismissTransition(null /* transition */, wct, this, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/CounterRotatorHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/CounterRotatorHelper.java index 8c71f749b2c3..19133e29de4b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/CounterRotatorHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/CounterRotatorHelper.java @@ -18,7 +18,9 @@ package com.android.wm.shell.transition; import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; +import android.graphics.Rect; import android.util.ArrayMap; +import android.util.RotationUtils; import android.view.SurfaceControl; import android.window.TransitionInfo; import android.window.WindowContainerToken; @@ -35,11 +37,21 @@ import java.util.List; */ public class CounterRotatorHelper { private final ArrayMap<WindowContainerToken, CounterRotator> mRotatorMap = new ArrayMap<>(); + private final Rect mLastDisplayBounds = new Rect(); + private int mLastRotationDelta; /** Puts the surface controls of closing changes to counter-rotated surfaces. */ public void handleClosingChanges(@NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, - int rotateDelta, int displayW, int displayH) { + @NonNull TransitionInfo.Change displayRotationChange) { + final int rotationDelta = RotationUtils.deltaRotation( + displayRotationChange.getStartRotation(), displayRotationChange.getEndRotation()); + final Rect displayBounds = displayRotationChange.getEndAbsBounds(); + final int displayW = displayBounds.width(); + final int displayH = displayBounds.height(); + mLastRotationDelta = rotationDelta; + mLastDisplayBounds.set(displayBounds); + final List<TransitionInfo.Change> changes = info.getChanges(); final int numChanges = changes.size(); for (int i = numChanges - 1; i >= 0; --i) { @@ -53,7 +65,7 @@ public class CounterRotatorHelper { CounterRotator crot = mRotatorMap.get(parent); if (crot == null) { crot = new CounterRotator(); - crot.setup(startTransaction, info.getChange(parent).getLeash(), rotateDelta, + crot.setup(startTransaction, info.getChange(parent).getLeash(), rotationDelta, displayW, displayH); final SurfaceControl rotatorSc = crot.getSurface(); if (rotatorSc != null) { @@ -70,6 +82,18 @@ public class CounterRotatorHelper { } /** + * Returns the rotated end bounds if the change is put in previous rotation. Otherwise the + * original end bounds are returned. + */ + @NonNull + public Rect getEndBoundsInStartRotation(@NonNull TransitionInfo.Change change) { + if (mLastRotationDelta == 0) return change.getEndAbsBounds(); + final Rect rotatedBounds = new Rect(change.getEndAbsBounds()); + RotationUtils.rotateBounds(rotatedBounds, mLastDisplayBounds, mLastRotationDelta); + return rotatedBounds; + } + + /** * Removes the counter rotation surface in the finish transaction. No need to reparent the * children as the finish transaction should have already taken care of that. * @@ -80,5 +104,6 @@ public class CounterRotatorHelper { mRotatorMap.valueAt(i).cleanUp(finishTransaction); } mRotatorMap.clear(); + mLastRotationDelta = 0; } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java index 5054e60d09f8..13e81bdb3c0b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java @@ -124,6 +124,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { /** Keeps track of the currently-running animations associated with each transition. */ private final ArrayMap<IBinder, ArrayList<Animator>> mAnimations = new ArrayMap<>(); + private final CounterRotatorHelper mRotator = new CounterRotatorHelper(); private final Rect mInsets = new Rect(0, 0, 0, 0); private float mTransitionAnimationScaleSetting = 1.0f; @@ -277,8 +278,6 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { final ArrayList<Animator> animations = new ArrayList<>(); mAnimations.put(transition, animations); - final CounterRotatorHelper rotator = new CounterRotatorHelper(); - final Runnable onAnimFinish = () -> { if (!animations.isEmpty()) return; @@ -298,9 +297,6 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { final boolean isTask = change.getTaskInfo() != null; if (change.getMode() == TRANSIT_CHANGE && (change.getFlags() & FLAG_IS_DISPLAY) != 0) { - int rotateDelta = change.getEndRotation() - change.getStartRotation(); - int displayW = change.getEndAbsBounds().width(); - int displayH = change.getEndAbsBounds().height(); if (info.getType() == TRANSIT_CHANGE) { boolean isSeamless = isRotationSeamless(info, mDisplayController); final int anim = getRotationAnimation(info); @@ -314,8 +310,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { } } else { // Opening/closing an app into a new orientation. - rotator.handleClosingChanges(info, startTransaction, rotateDelta, - displayW, displayH); + mRotator.handleClosingChanges(info, startTransaction, change); } } @@ -384,9 +379,12 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { } } + final Rect clipRect = Transitions.isClosingType(change.getMode()) + ? mRotator.getEndBoundsInStartRotation(change) + : change.getEndAbsBounds(); startSurfaceAnimation(animations, a, change.getLeash(), onAnimFinish, mTransactionPool, mMainExecutor, mAnimExecutor, null /* position */, - cornerRadius, change.getEndAbsBounds()); + cornerRadius, clipRect); if (info.getAnimationOptions() != null) { attachThumbnail(animations, onAnimFinish, change, info.getAnimationOptions(), @@ -401,7 +399,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { } startTransaction.apply(); - rotator.cleanUp(finishTransaction); + mRotator.cleanUp(finishTransaction); TransitionMetrics.getInstance().reportAnimationStart(transition); // run finish now in-case there are no animations onAnimFinish.run(); @@ -458,6 +456,9 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { final TransitionInfo.AnimationOptions options = info.getAnimationOptions(); final int overrideType = options != null ? options.getType() : ANIM_NONE; final boolean canCustomContainer = isTask ? !sDisableCustomTaskAnimationProperty : true; + final Rect endBounds = Transitions.isClosingType(changeMode) + ? mRotator.getEndBoundsInStartRotation(change) + : change.getEndAbsBounds(); if (info.isKeyguardGoingAway()) { a = mTransitionAnimation.loadKeyguardExitAnimation(flags, @@ -475,8 +476,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { a = new AlphaAnimation(1.f, 1.f); a.setDuration(TransitionAnimation.DEFAULT_APP_TRANSITION_DURATION); } else if (type == TRANSIT_RELAUNCH) { - a = mTransitionAnimation.createRelaunchAnimation( - change.getEndAbsBounds(), mInsets, change.getEndAbsBounds()); + a = mTransitionAnimation.createRelaunchAnimation(endBounds, mInsets, endBounds); } else if (overrideType == ANIM_CUSTOM && (canCustomContainer || options.getOverrideTaskTransition())) { a = mTransitionAnimation.loadAnimationRes(options.getPackageName(), enter @@ -485,16 +485,15 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { a = mTransitionAnimation.loadCrossProfileAppEnterAnimation(); } else if (overrideType == ANIM_CLIP_REVEAL) { a = mTransitionAnimation.createClipRevealAnimationLocked(type, wallpaperTransit, enter, - change.getEndAbsBounds(), change.getEndAbsBounds(), - options.getTransitionBounds()); + endBounds, endBounds, options.getTransitionBounds()); } else if (overrideType == ANIM_SCALE_UP) { a = mTransitionAnimation.createScaleUpAnimationLocked(type, wallpaperTransit, enter, - change.getEndAbsBounds(), options.getTransitionBounds()); + endBounds, options.getTransitionBounds()); } else if (overrideType == ANIM_THUMBNAIL_SCALE_UP || overrideType == ANIM_THUMBNAIL_SCALE_DOWN) { final boolean scaleUp = overrideType == ANIM_THUMBNAIL_SCALE_UP; a = mTransitionAnimation.createThumbnailEnterExitAnimationLocked(enter, scaleUp, - change.getEndAbsBounds(), type, wallpaperTransit, options.getThumbnail(), + endBounds, type, wallpaperTransit, options.getThumbnail(), options.getTransitionBounds()); } else if ((changeFlags & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0 && isOpeningType) { // This received a transferred starting window, so don't animate @@ -567,8 +566,9 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { if (a != null) { if (!a.isInitialized()) { - Rect end = change.getEndAbsBounds(); - a.initialize(end.width(), end.height(), end.width(), end.height()); + final int width = endBounds.width(); + final int height = endBounds.height(); + a.initialize(width, height, width, height); } a.restrictDuration(MAX_ANIMATION_DURATION); a.scaleCurrentDuration(mTransitionAnimationScaleSetting); diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt index 68b0b4e48252..cb478c84c2b7 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt @@ -118,10 +118,10 @@ fun FlickerTestParameter.dockedStackSecondaryBoundsIsVisibleAtEnd( fun getPrimaryRegion(dividerRegion: Region, rotation: Int): Region { val displayBounds = WindowUtils.getDisplayBounds(rotation) return if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) { - Region(0, 0, displayBounds.bounds.right, + Region.from(0, 0, displayBounds.bounds.right, dividerRegion.bounds.top + WindowUtils.dockedStackDividerInset) } else { - Region(0, 0, dividerRegion.bounds.left + WindowUtils.dockedStackDividerInset, + Region.from(0, 0, dividerRegion.bounds.left + WindowUtils.dockedStackDividerInset, displayBounds.bounds.bottom) } } @@ -129,10 +129,10 @@ fun getPrimaryRegion(dividerRegion: Region, rotation: Int): Region { fun getSecondaryRegion(dividerRegion: Region, rotation: Int): Region { val displayBounds = WindowUtils.getDisplayBounds(rotation) return if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) { - Region(0, dividerRegion.bounds.bottom - WindowUtils.dockedStackDividerInset, + Region.from(0, dividerRegion.bounds.bottom - WindowUtils.dockedStackDividerInset, displayBounds.bounds.right, displayBounds.bounds.bottom) } else { - Region(dividerRegion.bounds.right - WindowUtils.dockedStackDividerInset, 0, + Region.from(dividerRegion.bounds.right - WindowUtils.dockedStackDividerInset, 0, displayBounds.bounds.right, displayBounds.bounds.bottom) } }
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/OWNERS b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/OWNERS new file mode 100644 index 000000000000..8446b37dbf06 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/OWNERS @@ -0,0 +1,2 @@ +# window manager > wm shell > Split Screen +# Bug component: 928697 diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreen.kt index af629cc9f8ee..f8d14c6e6d04 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreen.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreen.kt @@ -24,6 +24,9 @@ import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.annotation.Group4 import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.wm.shell.flicker.helpers.BaseAppHelper +import org.junit.Assume +import org.junit.Before import org.junit.runner.RunWith import org.junit.Test import org.junit.runners.Parameterized @@ -59,6 +62,12 @@ class ExpandBubbleScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(test } } + @Before + fun setup() { + // This test doesn't work in shell transitions because of b/205288792 + Assume.assumeFalse(BaseAppHelper.isShellTransitionsEnabled()) + } + @Presubmit @Test fun testAppIsAlwaysVisible() { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreen.kt index add11c10d75f..c93c5ad97bdb 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreen.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreen.kt @@ -25,6 +25,9 @@ import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.annotation.Group4 import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.wm.shell.flicker.helpers.BaseAppHelper +import org.junit.Assume +import org.junit.Before import org.junit.runner.RunWith import org.junit.Test import org.junit.runners.Parameterized @@ -67,6 +70,12 @@ class MultiBubblesScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(test } } + @Before + fun setup() { + // This test doesn't work in shell transitions because of b/205288792 + Assume.assumeFalse(BaseAppHelper.isShellTransitionsEnabled()) + } + @Presubmit @Test fun testAppIsAlwaysVisible() { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OWNERS b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OWNERS new file mode 100644 index 000000000000..566acc87e42d --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OWNERS @@ -0,0 +1,2 @@ +# window manager > wm shell > Bubbles +# Bug component: 555586 diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt index efae20731bef..cf4ea467a29b 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt @@ -28,14 +28,14 @@ class AppPairsHelper( component: FlickerComponentName ) : BaseAppHelper(instrumentation, activityLabel, component) { fun getPrimaryBounds(dividerBounds: Region): Region { - val primaryAppBounds = Region(0, 0, dividerBounds.bounds.right, + val primaryAppBounds = Region.from(0, 0, dividerBounds.bounds.right, dividerBounds.bounds.bottom + WindowUtils.dockedStackDividerInset) return primaryAppBounds } fun getSecondaryBounds(dividerBounds: Region): Region { val displayBounds = WindowUtils.displayBounds - val secondaryAppBounds = Region(0, + val secondaryAppBounds = Region.from(0, dividerBounds.bounds.bottom - WindowUtils.dockedStackDividerInset, displayBounds.right, displayBounds.bottom - WindowUtils.navigationBarHeight) return secondaryAppBounds diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OWNERS b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OWNERS new file mode 100644 index 000000000000..8446b37dbf06 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OWNERS @@ -0,0 +1,2 @@ +# window manager > wm shell > Split Screen +# Bug component: 928697 diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt index 264d482426cb..a510d699387e 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt @@ -166,9 +166,9 @@ class ResizeLegacySplitScreen( val dividerBounds = layer(DOCKED_STACK_DIVIDER_COMPONENT).visibleRegion.region.bounds - val topAppBounds = Region(0, 0, dividerBounds.right, + val topAppBounds = Region.from(0, 0, dividerBounds.right, dividerBounds.top + WindowUtils.dockedStackDividerInset) - val bottomAppBounds = Region(0, + val bottomAppBounds = Region.from(0, dividerBounds.bottom - WindowUtils.dockedStackDividerInset, displayBounds.right, displayBounds.bottom - WindowUtils.navigationBarHeight) @@ -187,9 +187,9 @@ class ResizeLegacySplitScreen( val dividerBounds = layer(DOCKED_STACK_DIVIDER_COMPONENT).visibleRegion.region.bounds - val topAppBounds = Region(0, 0, dividerBounds.right, + val topAppBounds = Region.from(0, 0, dividerBounds.right, dividerBounds.top + WindowUtils.dockedStackDividerInset) - val bottomAppBounds = Region(0, + val bottomAppBounds = Region.from(0, dividerBounds.bottom - WindowUtils.dockedStackDividerInset, displayBounds.right, displayBounds.bottom - WindowUtils.navigationBarHeight) diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt index 2c08b7f5fac2..3a9a0705908d 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt @@ -82,6 +82,11 @@ class ExitPipViaExpandButtonClickTest( @Test override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales() + /** {@inheritDoc} */ + @FlakyTest(bugId = 197726610) + @Test + override fun pipLayerExpands() = super.pipLayerExpands() + companion object { /** * Creates the test configurations. diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt index e340f4cd8595..03c8929f9919 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt @@ -101,6 +101,11 @@ class ExitPipViaIntentTest(testSpec: FlickerTestParameter) : ExitPipToAppTransit @Test override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales() + /** {@inheritDoc} */ + @FlakyTest(bugId = 197726610) + @Test + override fun pipLayerExpands() = super.pipLayerExpands() + companion object { /** * Creates the test configurations. diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt index 8adebb8f28c9..976b7c6980a1 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt @@ -90,6 +90,11 @@ class ExitPipWithDismissButtonTest(testSpec: FlickerTestParameter) : ExitPipTran @Test override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales() + /** {@inheritDoc} */ + @FlakyTest(bugId = 215869110) + @Test + override fun focusDoesNotChange() = super.focusDoesNotChange() + companion object { /** * Creates the test configurations. diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/OWNERS b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/OWNERS new file mode 100644 index 000000000000..172e24bf4574 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/OWNERS @@ -0,0 +1,2 @@ +# window manager > wm shell > Picture-In-Picture +# Bug component: 316251 diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java new file mode 100644 index 000000000000..960c7ac4099a --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.back; + +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import android.app.IActivityTaskManager; +import android.app.WindowConfiguration; +import android.hardware.HardwareBuffer; +import android.os.RemoteCallback; +import android.os.RemoteException; +import android.testing.AndroidTestingRunner; +import android.view.MotionEvent; +import android.view.SurfaceControl; +import android.window.BackNavigationInfo; + +import androidx.test.filters.SmallTest; + +import com.android.wm.shell.TestShellExecutor; +import com.android.wm.shell.common.ShellExecutor; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * atest WMShellUnitTests:BackAnimationControllerTest + */ +@SmallTest +@RunWith(AndroidTestingRunner.class) +public class BackAnimationControllerTest { + + private final ShellExecutor mShellExecutor = new TestShellExecutor(); + + @Mock + private SurfaceControl.Transaction mTransaction; + + @Mock + private IActivityTaskManager mActivityTaskManager; + + private BackAnimationController mController; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + mController = new BackAnimationController( + mShellExecutor, mTransaction, mActivityTaskManager); + } + + private void createNavigationInfo(SurfaceControl topWindowLeash, + SurfaceControl screenshotSurface, + HardwareBuffer hardwareBuffer) { + BackNavigationInfo navigationInfo = new BackNavigationInfo( + BackNavigationInfo.TYPE_RETURN_TO_HOME, + topWindowLeash, + screenshotSurface, + hardwareBuffer, + new WindowConfiguration(), + new RemoteCallback((bundle) -> {})); + try { + doReturn(navigationInfo).when(mActivityTaskManager).startBackNavigation(); + } catch (RemoteException ex) { + ex.rethrowFromSystemServer(); + } + } + + @Test + public void screenshotAttachedAndVisible() { + SurfaceControl topWindowLeash = new SurfaceControl(); + SurfaceControl screenshotSurface = new SurfaceControl(); + HardwareBuffer hardwareBuffer = mock(HardwareBuffer.class); + createNavigationInfo(topWindowLeash, screenshotSurface, hardwareBuffer); + mController.onMotionEvent(MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0)); + verify(mTransaction).setBuffer(screenshotSurface, hardwareBuffer); + verify(mTransaction).setVisibility(screenshotSurface, true); + verify(mTransaction).apply(); + } + + @Test + public void surfaceMovesWithGesture() { + SurfaceControl topWindowLeash = new SurfaceControl(); + SurfaceControl screenshotSurface = new SurfaceControl(); + HardwareBuffer hardwareBuffer = mock(HardwareBuffer.class); + createNavigationInfo(topWindowLeash, screenshotSurface, hardwareBuffer); + mController.onMotionEvent(MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0)); + mController.onMotionEvent(MotionEvent.obtain(10, 0, MotionEvent.ACTION_MOVE, 100, 100, 0)); + verify(mTransaction).setPosition(topWindowLeash, 100, 100); + verify(mTransaction, atLeastOnce()).apply(); + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java index fe66e225ad4a..35e498262707 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java @@ -19,7 +19,6 @@ package com.android.wm.shell.draganddrop; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; -import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; import static android.content.ClipDescription.MIMETYPE_APPLICATION_ACTIVITY; import static android.content.ClipDescription.MIMETYPE_APPLICATION_SHORTCUT; import static android.content.ClipDescription.MIMETYPE_APPLICATION_TASK; @@ -111,7 +110,6 @@ public class DragAndDropPolicyTest { private ActivityManager.RunningTaskInfo mHomeTask; private ActivityManager.RunningTaskInfo mFullscreenAppTask; private ActivityManager.RunningTaskInfo mNonResizeableFullscreenAppTask; - private ActivityManager.RunningTaskInfo mSplitPrimaryAppTask; @Before public void setUp() throws RemoteException { @@ -144,8 +142,6 @@ public class DragAndDropPolicyTest { mNonResizeableFullscreenAppTask = createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD); mNonResizeableFullscreenAppTask.isResizeable = false; - mSplitPrimaryAppTask = createTaskInfo(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, - ACTIVITY_TYPE_STANDARD); setRunningTask(mFullscreenAppTask); } diff --git a/libs/androidfw/Locale.cpp b/libs/androidfw/Locale.cpp index 3eedda88fdce..d87a3ce72177 100644 --- a/libs/androidfw/Locale.cpp +++ b/libs/androidfw/Locale.cpp @@ -29,40 +29,33 @@ using ::android::StringPiece; namespace android { -void LocaleValue::set_language(const char* language_chars) { +template <size_t N, class Transformer> +static void safe_transform_copy(const char* source, char (&dest)[N], Transformer t) { size_t i = 0; - while ((*language_chars) != '\0') { - language[i++] = ::tolower(*language_chars); - language_chars++; + while (i < N && (*source) != '\0') { + dest[i++] = t(i, *source); + source++; + } + while (i < N) { + dest[i++] = '\0'; } } +void LocaleValue::set_language(const char* language_chars) { + safe_transform_copy(language_chars, language, [](size_t, char c) { return ::tolower(c); }); +} + void LocaleValue::set_region(const char* region_chars) { - size_t i = 0; - while ((*region_chars) != '\0') { - region[i++] = ::toupper(*region_chars); - region_chars++; - } + safe_transform_copy(region_chars, region, [](size_t, char c) { return ::toupper(c); }); } void LocaleValue::set_script(const char* script_chars) { - size_t i = 0; - while ((*script_chars) != '\0') { - if (i == 0) { - script[i++] = ::toupper(*script_chars); - } else { - script[i++] = ::tolower(*script_chars); - } - script_chars++; - } + safe_transform_copy(script_chars, script, + [](size_t i, char c) { return i ? ::tolower(c) : ::toupper(c); }); } void LocaleValue::set_variant(const char* variant_chars) { - size_t i = 0; - while ((*variant_chars) != '\0') { - variant[i++] = *variant_chars; - variant_chars++; - } + safe_transform_copy(variant_chars, variant, [](size_t, char c) { return c; }); } static inline bool is_alpha(const std::string& str) { @@ -234,6 +227,10 @@ ssize_t LocaleValue::InitFromParts(std::vector<std::string>::iterator iter, return static_cast<ssize_t>(iter - start_iter); } +// Make sure the following memcpy's are properly sized. +static_assert(sizeof(ResTable_config::localeScript) == sizeof(LocaleValue::script)); +static_assert(sizeof(ResTable_config::localeVariant) == sizeof(LocaleValue::variant)); + void LocaleValue::InitFromResTable(const ResTable_config& config) { config.unpackLanguage(language); config.unpackRegion(region); diff --git a/libs/hwui/jni/text/MeasuredText.cpp b/libs/hwui/jni/text/MeasuredText.cpp index 09539ecc34b0..76ea2d5c9ff3 100644 --- a/libs/hwui/jni/text/MeasuredText.cpp +++ b/libs/hwui/jni/text/MeasuredText.cpp @@ -65,11 +65,13 @@ static jlong nInitBuilder(CRITICAL_JNI_PARAMS) { // Regular JNI static void nAddStyleRun(JNIEnv* /* unused */, jclass /* unused */, jlong builderPtr, - jlong paintPtr, jint lbStyle, jint start, jint end, jboolean isRtl) { + jlong paintPtr, jint lbStyle, jint lbWordStyle, jint start, jint end, + jboolean isRtl) { Paint* paint = toPaint(paintPtr); const Typeface* typeface = Typeface::resolveDefault(paint->getAndroidTypeface()); minikin::MinikinPaint minikinPaint = MinikinUtils::prepareMinikinPaint(paint, typeface); - toBuilder(builderPtr)->addStyleRun(start, end, std::move(minikinPaint), lbStyle, isRtl); + toBuilder(builderPtr) + ->addStyleRun(start, end, std::move(minikinPaint), lbStyle, lbWordStyle, isRtl); } // Regular JNI @@ -144,7 +146,7 @@ static jint nGetMemoryUsage(CRITICAL_JNI_PARAMS_COMMA jlong ptr) { static const JNINativeMethod gMTBuilderMethods[] = { // MeasuredParagraphBuilder native functions. {"nInitBuilder", "()J", (void*)nInitBuilder}, - {"nAddStyleRun", "(JJIIIZ)V", (void*)nAddStyleRun}, + {"nAddStyleRun", "(JJIIIIZ)V", (void*)nAddStyleRun}, {"nAddReplacementRun", "(JJIIF)V", (void*)nAddReplacementRun}, {"nBuildMeasuredText", "(JJ[CZZZ)J", (void*)nBuildMeasuredText}, {"nFreeBuilder", "(J)V", (void*)nFreeBuilder}, diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp index d86237789b11..f627a3c656d1 100644 --- a/libs/hwui/renderthread/RenderThread.cpp +++ b/libs/hwui/renderthread/RenderThread.cpp @@ -117,7 +117,7 @@ void RenderThread::extendedFrameCallback(const AChoreographerFrameCallbackData* RenderThread* rt = reinterpret_cast<RenderThread*>(data); size_t preferredFrameTimelineIndex = AChoreographerFrameCallbackData_getPreferredFrameTimelineIndex(cbData); - int64_t vsyncId = AChoreographerFrameCallbackData_getFrameTimelineVsyncId( + AVsyncId vsyncId = AChoreographerFrameCallbackData_getFrameTimelineVsyncId( cbData, preferredFrameTimelineIndex); int64_t frameDeadline = AChoreographerFrameCallbackData_getFrameTimelineDeadlineNanos( cbData, preferredFrameTimelineIndex); diff --git a/libs/services/Android.bp b/libs/services/Android.bp index bf2e764aae6a..f656ebfc3b77 100644 --- a/libs/services/Android.bp +++ b/libs/services/Android.bp @@ -27,6 +27,7 @@ cc_library_shared { name: "libservices", srcs: [ ":IDropBoxManagerService.aidl", + ":ILogcatManagerService_aidl", "src/content/ComponentName.cpp", "src/os/DropBoxManager.cpp", ], diff --git a/location/java/android/location/GnssMeasurement.java b/location/java/android/location/GnssMeasurement.java index cdfa02c8b28f..ab3dafe9cec7 100644 --- a/location/java/android/location/GnssMeasurement.java +++ b/location/java/android/location/GnssMeasurement.java @@ -1868,7 +1868,7 @@ public final class GnssMeasurement implements Parcelable { gnssMeasurement.mSatelliteInterSignalBiasUncertaintyNanos = parcel.readDouble(); if (gnssMeasurement.hasSatellitePvt()) { ClassLoader classLoader = getClass().getClassLoader(); - gnssMeasurement.mSatellitePvt = parcel.readParcelable(classLoader); + gnssMeasurement.mSatellitePvt = parcel.readParcelable(classLoader, android.location.SatellitePvt.class); } if (gnssMeasurement.hasCorrelationVectors()) { CorrelationVector[] correlationVectorsArray = diff --git a/location/java/android/location/GnssMeasurementsEvent.java b/location/java/android/location/GnssMeasurementsEvent.java index 075ddebc859f..0397740d104e 100644 --- a/location/java/android/location/GnssMeasurementsEvent.java +++ b/location/java/android/location/GnssMeasurementsEvent.java @@ -160,7 +160,7 @@ public final class GnssMeasurementsEvent implements Parcelable { new Creator<GnssMeasurementsEvent>() { @Override public GnssMeasurementsEvent createFromParcel(Parcel in) { - GnssClock clock = in.readParcelable(getClass().getClassLoader()); + GnssClock clock = in.readParcelable(getClass().getClassLoader(), android.location.GnssClock.class); List<GnssMeasurement> measurements = in.createTypedArrayList(GnssMeasurement.CREATOR); List<GnssAutomaticGainControl> agcs = in.createTypedArrayList( GnssAutomaticGainControl.CREATOR); diff --git a/location/java/android/location/GpsMeasurementsEvent.java b/location/java/android/location/GpsMeasurementsEvent.java index f3feb7a4c7b6..6b834f324839 100644 --- a/location/java/android/location/GpsMeasurementsEvent.java +++ b/location/java/android/location/GpsMeasurementsEvent.java @@ -112,7 +112,7 @@ public class GpsMeasurementsEvent implements Parcelable { public GpsMeasurementsEvent createFromParcel(Parcel in) { ClassLoader classLoader = getClass().getClassLoader(); - GpsClock clock = in.readParcelable(classLoader); + GpsClock clock = in.readParcelable(classLoader, android.location.GpsClock.class); int measurementsLength = in.readInt(); GpsMeasurement[] measurementsArray = new GpsMeasurement[measurementsLength]; diff --git a/location/java/android/location/GpsNavigationMessageEvent.java b/location/java/android/location/GpsNavigationMessageEvent.java index 2d5d6ebd5990..b37fe3dfb792 100644 --- a/location/java/android/location/GpsNavigationMessageEvent.java +++ b/location/java/android/location/GpsNavigationMessageEvent.java @@ -92,7 +92,7 @@ public class GpsNavigationMessageEvent implements Parcelable { @Override public GpsNavigationMessageEvent createFromParcel(Parcel in) { ClassLoader classLoader = getClass().getClassLoader(); - GpsNavigationMessage navigationMessage = in.readParcelable(classLoader); + GpsNavigationMessage navigationMessage = in.readParcelable(classLoader, android.location.GpsNavigationMessage.class); return new GpsNavigationMessageEvent(navigationMessage); } diff --git a/location/java/android/location/SatellitePvt.java b/location/java/android/location/SatellitePvt.java index 794a8d0731f9..aa43cfd8711c 100644 --- a/location/java/android/location/SatellitePvt.java +++ b/location/java/android/location/SatellitePvt.java @@ -465,9 +465,9 @@ public final class SatellitePvt implements Parcelable { public SatellitePvt createFromParcel(Parcel in) { int flags = in.readInt(); ClassLoader classLoader = getClass().getClassLoader(); - PositionEcef positionEcef = in.readParcelable(classLoader); - VelocityEcef velocityEcef = in.readParcelable(classLoader); - ClockInfo clockInfo = in.readParcelable(classLoader); + PositionEcef positionEcef = in.readParcelable(classLoader, android.location.SatellitePvt.PositionEcef.class); + VelocityEcef velocityEcef = in.readParcelable(classLoader, android.location.SatellitePvt.VelocityEcef.class); + ClockInfo clockInfo = in.readParcelable(classLoader, android.location.SatellitePvt.ClockInfo.class); double ionoDelayMeters = in.readDouble(); double tropoDelayMeters = in.readDouble(); diff --git a/media/aidl/android/media/audio/common/AudioContentType.aidl b/media/aidl/android/media/audio/common/AudioContentType.aidl index 50ac181adcb2..f42ae2fedc52 100644 --- a/media/aidl/android/media/audio/common/AudioContentType.aidl +++ b/media/aidl/android/media/audio/common/AudioContentType.aidl @@ -50,4 +50,8 @@ enum AudioContentType { * in a game. These sounds are mostly synthesized or short Foley sounds. */ SONIFICATION = 4, + /** + * Content type value to use when the content type is ultrasound. + */ + ULTRASOUND = 1997, } diff --git a/media/aidl/android/media/audio/common/AudioDeviceType.aidl b/media/aidl/android/media/audio/common/AudioDeviceType.aidl index afe6d105eb1b..8e200de34e3d 100644 --- a/media/aidl/android/media/audio/common/AudioDeviceType.aidl +++ b/media/aidl/android/media/audio/common/AudioDeviceType.aidl @@ -168,4 +168,8 @@ enum AudioDeviceType { * Output into a speaker of a phone / table dock. */ OUT_DOCK = 145, + /** + * Output to a broadcast group. + */ + OUT_BROADCAST = 146, } diff --git a/media/aidl/android/media/audio/common/AudioInputFlags.aidl b/media/aidl/android/media/audio/common/AudioInputFlags.aidl index e4b6ec242ef4..83a5d9dc98e4 100644 --- a/media/aidl/android/media/audio/common/AudioInputFlags.aidl +++ b/media/aidl/android/media/audio/common/AudioInputFlags.aidl @@ -59,4 +59,8 @@ enum AudioInputFlags { * Input contains an encoded audio stream. */ DIRECT = 7, + /** + * Input is for capturing "ultrasound" audio commands. + */ + ULTRASOUND = 8, } diff --git a/media/aidl/android/media/audio/common/AudioOutputFlags.aidl b/media/aidl/android/media/audio/common/AudioOutputFlags.aidl index 050503647d33..2556b68809cc 100644 --- a/media/aidl/android/media/audio/common/AudioOutputFlags.aidl +++ b/media/aidl/android/media/audio/common/AudioOutputFlags.aidl @@ -26,7 +26,7 @@ package android.media.audio.common; */ @VintfStability @Backing(type="int") -enum AudioOutputFlags { +enum AudioOutputFlags{ /** * Output must not be altered by the framework, it bypasses software mixers. */ @@ -97,4 +97,12 @@ enum AudioOutputFlags { * tracks. */ GAPLESS_OFFLOAD = 15, + /** + * Output is used for spatial audio. + */ + SPATIALIZER = 16, + /** + * Output is used for transmitting ultrasound audio. + */ + ULTRASOUND = 17, } diff --git a/media/aidl/android/media/audio/common/AudioSource.aidl b/media/aidl/android/media/audio/common/AudioSource.aidl index 527ee39b3267..77799946c04a 100644 --- a/media/aidl/android/media/audio/common/AudioSource.aidl +++ b/media/aidl/android/media/audio/common/AudioSource.aidl @@ -87,4 +87,8 @@ enum AudioSource { * hotword detection. Same tuning as VOICE_RECOGNITION. */ HOTWORD = 1999, + /** Microphone audio source for ultrasound sound if available, + * behaves like DEFAULT otherwise. + */ + ULTRASOUND = 2000, } diff --git a/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioContentType.aidl b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioContentType.aidl index 3798b8253766..f9ac61426fa3 100644 --- a/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioContentType.aidl +++ b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioContentType.aidl @@ -40,4 +40,5 @@ enum AudioContentType { MUSIC = 2, MOVIE = 3, SONIFICATION = 4, + ULTRASOUND = 1997, } diff --git a/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioDeviceType.aidl b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioDeviceType.aidl index 0b7b77ca8718..6a7b6864b801 100644 --- a/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioDeviceType.aidl +++ b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioDeviceType.aidl @@ -67,4 +67,5 @@ enum AudioDeviceType { OUT_SUBMIX = 143, OUT_TELEPHONY_TX = 144, OUT_DOCK = 145, + OUT_BROADCAST = 146, } diff --git a/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioInputFlags.aidl b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioInputFlags.aidl index 8a5dae0cc612..37aa64ae4cee 100644 --- a/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioInputFlags.aidl +++ b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioInputFlags.aidl @@ -43,4 +43,5 @@ enum AudioInputFlags { VOIP_TX = 5, HW_AV_SYNC = 6, DIRECT = 7, + ULTRASOUND = 8, } diff --git a/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioOutputFlags.aidl b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioOutputFlags.aidl index ed16d177e18c..4a512a8049a2 100644 --- a/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioOutputFlags.aidl +++ b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioOutputFlags.aidl @@ -51,4 +51,6 @@ enum AudioOutputFlags { VOIP_RX = 13, INCALL_MUSIC = 14, GAPLESS_OFFLOAD = 15, + SPATIALIZER = 16, + ULTRASOUND = 17, } diff --git a/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioSource.aidl b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioSource.aidl index d1dfe416d692..acf822e5e0a5 100644 --- a/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioSource.aidl +++ b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioSource.aidl @@ -50,4 +50,5 @@ enum AudioSource { ECHO_REFERENCE = 1997, FM_TUNER = 1998, HOTWORD = 1999, + ULTRASOUND = 2000, } diff --git a/media/java/android/media/AudioAttributes.java b/media/java/android/media/AudioAttributes.java index 85e49cc5430b..ded9597b68ef 100644 --- a/media/java/android/media/AudioAttributes.java +++ b/media/java/android/media/AudioAttributes.java @@ -101,6 +101,13 @@ public final class AudioAttributes implements Parcelable { * or short Foley sounds. */ public final static int CONTENT_TYPE_SONIFICATION = 4; + /** + * @hide + * Content type value to use when the content type is ultrasound. + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.ACCESS_ULTRASOUND) + public static final int CONTENT_TYPE_ULTRASOUND = 1997; /** * Invalid value, only ever used for an uninitialized usage value @@ -958,6 +965,26 @@ public final class AudioAttributes implements Parcelable { } /** + * @hide + * Sets the attribute describing the content type of the audio signal, such as speech, + * , music or ultrasound. + * @param contentType the content type values. + * @return the same Builder instance. + */ + @SystemApi + public @NonNull Builder setInternalContentType(@AttrInternalContentType int contentType) { + switch (contentType) { + case CONTENT_TYPE_ULTRASOUND: + mContentType = contentType; + break; + default: + setContentType(contentType); + break; + } + return this; + } + + /** * Sets the combination of flags. * * This is a bitwise OR with the existing flags. @@ -1234,7 +1261,8 @@ public final class AudioAttributes implements Parcelable { /** * @hide * Same as {@link #setCapturePreset(int)} but authorizes the use of HOTWORD, - * REMOTE_SUBMIX, RADIO_TUNER, VOICE_DOWNLINK, VOICE_UPLINK, VOICE_CALL and ECHO_REFERENCE. + * REMOTE_SUBMIX, RADIO_TUNER, VOICE_DOWNLINK, VOICE_UPLINK, VOICE_CALL, ECHO_REFERENCE + * and ULTRASOUND * @param preset * @return the same Builder instance. */ @@ -1246,7 +1274,8 @@ public final class AudioAttributes implements Parcelable { || (preset == MediaRecorder.AudioSource.VOICE_DOWNLINK) || (preset == MediaRecorder.AudioSource.VOICE_UPLINK) || (preset == MediaRecorder.AudioSource.VOICE_CALL) - || (preset == MediaRecorder.AudioSource.ECHO_REFERENCE)) { + || (preset == MediaRecorder.AudioSource.ECHO_REFERENCE) + || (preset == MediaRecorder.AudioSource.ULTRASOUND)) { mSource = preset; } else { setCapturePreset(preset); @@ -1589,6 +1618,7 @@ public final class AudioAttributes implements Parcelable { case CONTENT_TYPE_MUSIC: return new String("CONTENT_TYPE_MUSIC"); case CONTENT_TYPE_MOVIE: return new String("CONTENT_TYPE_MOVIE"); case CONTENT_TYPE_SONIFICATION: return new String("CONTENT_TYPE_SONIFICATION"); + case CONTENT_TYPE_ULTRASOUND: return new String("CONTENT_TYPE_ULTRASOUND"); default: return new String("unknown content type " + mContentType); } } @@ -1823,4 +1853,16 @@ public final class AudioAttributes implements Parcelable { }) @Retention(RetentionPolicy.SOURCE) public @interface AttributeContentType {} + + /** @hide */ + @IntDef({ + CONTENT_TYPE_UNKNOWN, + CONTENT_TYPE_SPEECH, + CONTENT_TYPE_MUSIC, + CONTENT_TYPE_MOVIE, + CONTENT_TYPE_SONIFICATION, + CONTENT_TYPE_ULTRASOUND + }) + @Retention(RetentionPolicy.SOURCE) + public @interface AttrInternalContentType {} } diff --git a/media/java/android/media/AudioDeviceInfo.java b/media/java/android/media/AudioDeviceInfo.java index 211a50ee93af..dd17dc649bc7 100644 --- a/media/java/android/media/AudioDeviceInfo.java +++ b/media/java/android/media/AudioDeviceInfo.java @@ -177,6 +177,11 @@ public final class AudioDeviceInfo { */ public static final int TYPE_HDMI_EARC = 29; + /** + * A device type describing a Bluetooth Low Energy (BLE) broadcast group. + */ + public static final int TYPE_BLE_BROADCAST = 30; + /** @hide */ @IntDef(flag = false, prefix = "TYPE", value = { TYPE_BUILTIN_EARPIECE, @@ -207,7 +212,8 @@ public final class AudioDeviceInfo { TYPE_REMOTE_SUBMIX, TYPE_BLE_HEADSET, TYPE_BLE_SPEAKER, - TYPE_ECHO_REFERENCE} + TYPE_ECHO_REFERENCE, + TYPE_BLE_BROADCAST} ) @Retention(RetentionPolicy.SOURCE) public @interface AudioDeviceType {} @@ -264,7 +270,8 @@ public final class AudioDeviceInfo { TYPE_HEARING_AID, TYPE_BUILTIN_SPEAKER_SAFE, TYPE_BLE_HEADSET, - TYPE_BLE_SPEAKER} + TYPE_BLE_SPEAKER, + TYPE_BLE_BROADCAST} ) @Retention(RetentionPolicy.SOURCE) public @interface AudioDeviceTypeOut {} @@ -296,6 +303,7 @@ public final class AudioDeviceInfo { case TYPE_BUILTIN_SPEAKER_SAFE: case TYPE_BLE_HEADSET: case TYPE_BLE_SPEAKER: + case TYPE_BLE_BROADCAST: return true; default: return false; @@ -636,6 +644,7 @@ public final class AudioDeviceInfo { INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_REMOTE_SUBMIX, TYPE_REMOTE_SUBMIX); INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_BLE_HEADSET, TYPE_BLE_HEADSET); INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_BLE_SPEAKER, TYPE_BLE_SPEAKER); + INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_BLE_BROADCAST, TYPE_BLE_BROADCAST); INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_BUILTIN_MIC, TYPE_BUILTIN_MIC); INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_BLUETOOTH_SCO_HEADSET, TYPE_BLUETOOTH_SCO); @@ -690,6 +699,7 @@ public final class AudioDeviceInfo { EXT_TO_INT_DEVICE_MAPPING.put(TYPE_REMOTE_SUBMIX, AudioSystem.DEVICE_OUT_REMOTE_SUBMIX); EXT_TO_INT_DEVICE_MAPPING.put(TYPE_BLE_HEADSET, AudioSystem.DEVICE_OUT_BLE_HEADSET); EXT_TO_INT_DEVICE_MAPPING.put(TYPE_BLE_SPEAKER, AudioSystem.DEVICE_OUT_BLE_SPEAKER); + EXT_TO_INT_DEVICE_MAPPING.put(TYPE_BLE_BROADCAST, AudioSystem.DEVICE_OUT_BLE_BROADCAST); // privileges mapping to input device EXT_TO_INT_INPUT_DEVICE_MAPPING = new SparseIntArray(); diff --git a/media/java/android/media/AudioDevicePort.java b/media/java/android/media/AudioDevicePort.java index ebe08822a0c9..9211c53a17bc 100644 --- a/media/java/android/media/AudioDevicePort.java +++ b/media/java/android/media/AudioDevicePort.java @@ -90,7 +90,8 @@ public class AudioDevicePort extends AudioPort { * {@link AudioManager#DEVICE_OUT_BLE_HEADSET}, {@link AudioManager#DEVICE_OUT_BLE_SPEAKER}) * use the MAC address of the bluetooth device in the form "00:11:22:AA:BB:CC" as reported by * {@link BluetoothDevice#getAddress()}. - * - Deivces that do not have an address will indicate an empty string "". + * - Bluetooth LE broadcast group ({@link AudioManager#DEVICE_OUT_BLE_BROADCAST} use the group number. + * - Devices that do not have an address will indicate an empty string "". */ public String address() { return mAddress; diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 68e5d94f7559..c4cef4c77835 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -5440,6 +5440,10 @@ public class AudioManager { */ public static final int DEVICE_OUT_BLE_SPEAKER = AudioSystem.DEVICE_OUT_BLE_SPEAKER; /** @hide + * The audio output device code for a BLE audio brodcast group. + */ + public static final int DEVICE_OUT_BLE_BROADCAST = AudioSystem.DEVICE_OUT_BLE_BROADCAST; + /** @hide * This is not used as a returned value from {@link #getDevicesForStream}, but could be * used in the future in a set method to select whatever default device is chosen by the * platform-specific implementation. diff --git a/media/java/android/media/AudioRecord.java b/media/java/android/media/AudioRecord.java index e76bb42560f6..52838898146d 100644 --- a/media/java/android/media/AudioRecord.java +++ b/media/java/android/media/AudioRecord.java @@ -1033,11 +1033,12 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection, //-------------- // audio source - if ( (audioSource < MediaRecorder.AudioSource.DEFAULT) || - ((audioSource > MediaRecorder.getAudioSourceMax()) && - (audioSource != MediaRecorder.AudioSource.RADIO_TUNER) && - (audioSource != MediaRecorder.AudioSource.ECHO_REFERENCE) && - (audioSource != MediaRecorder.AudioSource.HOTWORD)) ) { + if ((audioSource < MediaRecorder.AudioSource.DEFAULT) + || ((audioSource > MediaRecorder.getAudioSourceMax()) + && (audioSource != MediaRecorder.AudioSource.RADIO_TUNER) + && (audioSource != MediaRecorder.AudioSource.ECHO_REFERENCE) + && (audioSource != MediaRecorder.AudioSource.HOTWORD) + && (audioSource != MediaRecorder.AudioSource.ULTRASOUND))) { throw new IllegalArgumentException("Invalid audio source " + audioSource); } mRecordSource = audioSource; diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java index af5a3da5f3e2..306479a5b819 100644 --- a/media/java/android/media/AudioSystem.java +++ b/media/java/android/media/AudioSystem.java @@ -249,7 +249,8 @@ public class AudioSystem AUDIO_FORMAT_SBC, AUDIO_FORMAT_APTX, AUDIO_FORMAT_APTX_HD, - AUDIO_FORMAT_LDAC} + AUDIO_FORMAT_LDAC, + AUDIO_FORMAT_LC3} ) @Retention(RetentionPolicy.SOURCE) public @interface AudioFormatNativeEnumForBtCodec {} @@ -281,6 +282,7 @@ public class AudioSystem case AUDIO_FORMAT_APTX: return BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX; case AUDIO_FORMAT_APTX_HD: return BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD; case AUDIO_FORMAT_LDAC: return BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC; + case AUDIO_FORMAT_LC3: return BluetoothCodecConfig.SOURCE_CODEC_TYPE_LC3; default: Log.e(TAG, "Unknown audio format 0x" + Integer.toHexString(audioFormat) + " for conversion to BT codec"); @@ -321,6 +323,8 @@ public class AudioSystem return AudioSystem.AUDIO_FORMAT_APTX_HD; case BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC: return AudioSystem.AUDIO_FORMAT_LDAC; + case BluetoothCodecConfig.SOURCE_CODEC_TYPE_LC3: + return AudioSystem.AUDIO_FORMAT_LC3; default: Log.e(TAG, "Unknown BT codec 0x" + Integer.toHexString(btCodec) + " for conversion to audio format"); @@ -421,6 +425,8 @@ public class AudioSystem return "AUDIO_FORMAT_LHDC_LL"; case /* AUDIO_FORMAT_APTX_TWSP */ 0x2A000000: return "AUDIO_FORMAT_APTX_TWSP"; + case /* AUDIO_FORMAT_LC3 */ 0x2B000000: + return "AUDIO_FORMAT_LC3"; /* Aliases */ case /* AUDIO_FORMAT_PCM_16_BIT */ 0x1: @@ -983,6 +989,8 @@ public class AudioSystem public static final int DEVICE_OUT_BLE_HEADSET = 0x20000000; /** @hide */ public static final int DEVICE_OUT_BLE_SPEAKER = 0x20000001; + /** @hide */ + public static final int DEVICE_OUT_BLE_BROADCAST = 0x20000002; /** @hide */ public static final int DEVICE_OUT_DEFAULT = DEVICE_BIT_DEFAULT; @@ -1043,6 +1051,7 @@ public class AudioSystem DEVICE_OUT_ALL_SET.add(DEVICE_OUT_ECHO_CANCELLER); DEVICE_OUT_ALL_SET.add(DEVICE_OUT_BLE_HEADSET); DEVICE_OUT_ALL_SET.add(DEVICE_OUT_BLE_SPEAKER); + DEVICE_OUT_ALL_SET.add(DEVICE_OUT_BLE_BROADCAST); DEVICE_OUT_ALL_SET.add(DEVICE_OUT_DEFAULT); DEVICE_OUT_ALL_A2DP_SET = new HashSet<>(); @@ -1073,6 +1082,7 @@ public class AudioSystem DEVICE_OUT_ALL_BLE_SET = new HashSet<>(); DEVICE_OUT_ALL_BLE_SET.add(DEVICE_OUT_BLE_HEADSET); DEVICE_OUT_ALL_BLE_SET.add(DEVICE_OUT_BLE_SPEAKER); + DEVICE_OUT_ALL_BLE_SET.add(DEVICE_OUT_BLE_BROADCAST); } // input devices @@ -1256,6 +1266,7 @@ public class AudioSystem /** @hide */ public static final String DEVICE_OUT_ECHO_CANCELLER_NAME = "echo_canceller"; /** @hide */ public static final String DEVICE_OUT_BLE_HEADSET_NAME = "ble_headset"; /** @hide */ public static final String DEVICE_OUT_BLE_SPEAKER_NAME = "ble_speaker"; + /** @hide */ public static final String DEVICE_OUT_BLE_BROADCAST_NAME = "ble_broadcast"; /** @hide */ public static final String DEVICE_IN_COMMUNICATION_NAME = "communication"; /** @hide */ public static final String DEVICE_IN_AMBIENT_NAME = "ambient"; @@ -1355,6 +1366,8 @@ public class AudioSystem return DEVICE_OUT_BLE_HEADSET_NAME; case DEVICE_OUT_BLE_SPEAKER: return DEVICE_OUT_BLE_SPEAKER_NAME; + case DEVICE_OUT_BLE_BROADCAST: + return DEVICE_OUT_BLE_BROADCAST_NAME; case DEVICE_OUT_DEFAULT: default: return "0x" + Integer.toHexString(device); diff --git a/media/java/android/media/ImageWriter.java b/media/java/android/media/ImageWriter.java index 1fc2cf9edc90..6168c221bf6e 100644 --- a/media/java/android/media/ImageWriter.java +++ b/media/java/android/media/ImageWriter.java @@ -18,12 +18,16 @@ package android.media; import android.annotation.IntRange; import android.annotation.NonNull; +import android.annotation.SuppressLint; import android.graphics.GraphicBuffer; import android.graphics.ImageFormat; import android.graphics.ImageFormat.Format; import android.graphics.PixelFormat; import android.graphics.Rect; +import android.hardware.DataSpace; +import android.hardware.DataSpace.NamedDataSpace; import android.hardware.HardwareBuffer; +import android.hardware.HardwareBuffer.Usage; import android.hardware.camera2.params.StreamConfigurationMap; import android.hardware.camera2.utils.SurfaceUtils; import android.os.Handler; @@ -95,10 +99,18 @@ public class ImageWriter implements AutoCloseable { private ListenerHandler mListenerHandler; private long mNativeContext; + private int mWidth; + private int mHeight; + private final int mMaxImages; + private @Usage long mUsage = HardwareBuffer.USAGE_CPU_WRITE_OFTEN; + private @HardwareBuffer.Format int mHardwareBufferFormat; + private @NamedDataSpace long mDataSpace; + private boolean mUseLegacyImageFormat; + private boolean mUseSurfaceImageFormatInfo; + // Field below is used by native code, do not access or modify. private int mWriterFormat; - private final int mMaxImages; // Keep track of the currently dequeued Image. This need to be thread safe as the images // could be closed by different threads (e.g., application thread and GC thread). private List<Image> mDequeuedImages = new CopyOnWriteArrayList<>(); @@ -131,7 +143,7 @@ public class ImageWriter implements AutoCloseable { */ public static @NonNull ImageWriter newInstance(@NonNull Surface surface, @IntRange(from = 1) int maxImages) { - return new ImageWriter(surface, maxImages, ImageFormat.UNKNOWN, -1 /*width*/, + return new ImageWriter(surface, maxImages, true, ImageFormat.UNKNOWN, -1 /*width*/, -1 /*height*/); } @@ -183,7 +195,7 @@ public class ImageWriter implements AutoCloseable { if (!ImageFormat.isPublicFormat(format) && !PixelFormat.isPublicFormat(format)) { throw new IllegalArgumentException("Invalid format is specified: " + format); } - return new ImageWriter(surface, maxImages, format, width, height); + return new ImageWriter(surface, maxImages, false, format, width, height); } /** @@ -232,48 +244,49 @@ public class ImageWriter implements AutoCloseable { if (!ImageFormat.isPublicFormat(format) && !PixelFormat.isPublicFormat(format)) { throw new IllegalArgumentException("Invalid format is specified: " + format); } - return new ImageWriter(surface, maxImages, format, -1 /*width*/, -1 /*height*/); + return new ImageWriter(surface, maxImages, false, format, -1 /*width*/, -1 /*height*/); } - /** - * @hide - */ - protected ImageWriter(Surface surface, int maxImages, int format, int width, int height) { + private void initializeImageWriter(Surface surface, int maxImages, + boolean useSurfaceImageFormatInfo, boolean useLegacyImageFormat, int imageFormat, + int hardwareBufferFormat, long dataSpace, int width, int height, long usage) { if (surface == null || maxImages < 1) { throw new IllegalArgumentException("Illegal input argument: surface " + surface - + ", maxImages: " + maxImages); + + ", maxImages: " + maxImages); } - mMaxImages = maxImages; - + mUseSurfaceImageFormatInfo = useSurfaceImageFormatInfo; + mUseLegacyImageFormat = useLegacyImageFormat; // Note that the underlying BufferQueue is working in synchronous mode // to avoid dropping any buffers. - mNativeContext = nativeInit(new WeakReference<>(this), surface, maxImages, format, width, - height); - - // nativeInit internally overrides UNKNOWN format. So does surface format query after - // nativeInit and before getEstimatedNativeAllocBytes(). - if (format == ImageFormat.UNKNOWN) { - format = SurfaceUtils.getSurfaceFormat(surface); - } - // Several public formats use the same native HAL_PIXEL_FORMAT_BLOB. The native - // allocation estimation sequence depends on the public formats values. To avoid - // possible errors, convert where necessary. - if (format == StreamConfigurationMap.HAL_PIXEL_FORMAT_BLOB) { - int surfaceDataspace = SurfaceUtils.getSurfaceDataspace(surface); - switch (surfaceDataspace) { - case StreamConfigurationMap.HAL_DATASPACE_DEPTH: - format = ImageFormat.DEPTH_POINT_CLOUD; - break; - case StreamConfigurationMap.HAL_DATASPACE_DYNAMIC_DEPTH: - format = ImageFormat.DEPTH_JPEG; - break; - case StreamConfigurationMap.HAL_DATASPACE_HEIF: - format = ImageFormat.HEIC; - break; - default: - format = ImageFormat.JPEG; + mNativeContext = nativeInit(new WeakReference<>(this), surface, maxImages, width, height, + useSurfaceImageFormatInfo, hardwareBufferFormat, dataSpace, usage); + + if (useSurfaceImageFormatInfo) { + // nativeInit internally overrides UNKNOWN format. So does surface format query after + // nativeInit and before getEstimatedNativeAllocBytes(). + imageFormat = SurfaceUtils.getSurfaceFormat(surface); + // Several public formats use the same native HAL_PIXEL_FORMAT_BLOB. The native + // allocation estimation sequence depends on the public formats values. To avoid + // possible errors, convert where necessary. + if (imageFormat == StreamConfigurationMap.HAL_PIXEL_FORMAT_BLOB) { + int surfaceDataspace = SurfaceUtils.getSurfaceDataspace(surface); + switch (surfaceDataspace) { + case StreamConfigurationMap.HAL_DATASPACE_DEPTH: + imageFormat = ImageFormat.DEPTH_POINT_CLOUD; + break; + case StreamConfigurationMap.HAL_DATASPACE_DYNAMIC_DEPTH: + imageFormat = ImageFormat.DEPTH_JPEG; + break; + case StreamConfigurationMap.HAL_DATASPACE_HEIF: + imageFormat = ImageFormat.HEIC; + break; + default: + imageFormat = ImageFormat.JPEG; + } } + mHardwareBufferFormat = PublicFormatUtils.getHalFormat(imageFormat); + mDataSpace = PublicFormatUtils.getHalDataspace(imageFormat); } // Estimate the native buffer allocation size and register it so it gets accounted for // during GC. Note that this doesn't include the buffers required by the buffer queue @@ -282,12 +295,49 @@ public class ImageWriter implements AutoCloseable { // complex, and 1 buffer is enough for the VM to treat the ImageWriter as being of some // size. Size surfSize = SurfaceUtils.getSurfaceSize(surface); + mWidth = width == -1 ? surfSize.getWidth() : width; + mHeight = height == -1 ? surfSize.getHeight() : height; + mEstimatedNativeAllocBytes = - ImageUtils.getEstimatedNativeAllocBytes(surfSize.getWidth(),surfSize.getHeight(), - format, /*buffer count*/ 1); + ImageUtils.getEstimatedNativeAllocBytes(mWidth, mHeight, + useLegacyImageFormat ? imageFormat : hardwareBufferFormat, /*buffer count*/ 1); VMRuntime.getRuntime().registerNativeAllocation(mEstimatedNativeAllocBytes); } + private ImageWriter(Surface surface, int maxImages, boolean useSurfaceImageFormatInfo, + int imageFormat, int width, int height) { + mMaxImages = maxImages; + // update hal format and dataspace only if image format is overridden by producer. + mHardwareBufferFormat = PublicFormatUtils.getHalFormat(imageFormat); + mDataSpace = PublicFormatUtils.getHalDataspace(imageFormat); + + initializeImageWriter(surface, maxImages, useSurfaceImageFormatInfo, true, + imageFormat, mHardwareBufferFormat, mDataSpace, width, height, mUsage); + } + + private ImageWriter(Surface surface, int maxImages, boolean useSurfaceImageFormatInfo, + int imageFormat, int width, int height, long usage) { + mMaxImages = maxImages; + mUsage = usage; + mHardwareBufferFormat = PublicFormatUtils.getHalFormat(imageFormat); + mDataSpace = PublicFormatUtils.getHalDataspace(imageFormat); + + initializeImageWriter(surface, maxImages, useSurfaceImageFormatInfo, true, + imageFormat, mHardwareBufferFormat, mDataSpace, width, height, usage); + } + + private ImageWriter(Surface surface, int maxImages, boolean useSurfaceImageFormatInfo, + int hardwareBufferFormat, long dataSpace, int width, int height, long usage) { + mMaxImages = maxImages; + mUsage = usage; + mHardwareBufferFormat = hardwareBufferFormat; + mDataSpace = dataSpace; + int publicFormat = PublicFormatUtils.getPublicFormat(hardwareBufferFormat, dataSpace); + + initializeImageWriter(surface, maxImages, useSurfaceImageFormatInfo, false, + publicFormat, hardwareBufferFormat, dataSpace, width, height, usage); + } + /** * <p> * Maximum number of Images that can be dequeued from the ImageWriter @@ -316,6 +366,30 @@ public class ImageWriter implements AutoCloseable { } /** + * The width of {@link Image Images}, in pixels. + * + * <p>If {@link Builder#setWidthAndHeight} is not called, the default width of the Image + * depends on the Surface provided by customer end-point.</p> + * + * @return the expected actual width of an Image. + */ + public int getWidth() { + return mWidth; + } + + /** + * The height of {@link Image Images}, in pixels. + * + * <p>If {@link Builder#setWidthAndHeight} is not called, the default height of the Image + * depends on the Surface provided by customer end-point.</p> + * + * @return the expected height of an Image. + */ + public int getHeight() { + return mHeight; + } + + /** * <p> * Dequeue the next available input Image for the application to produce * data into. @@ -490,6 +564,41 @@ public class ImageWriter implements AutoCloseable { } /** + * Get the ImageWriter usage flag. + * + * @return The ImageWriter usage flag. + */ + public @Usage long getUsage() { + return mUsage; + } + + /** + * Get the ImageWriter hardwareBuffer format. + * + * <p>Use this function if the ImageWriter instance is created by builder pattern + * {@code ImageWriter.Builder} and using {@link Builder#setHardwareBufferFormat} and + * {@link Builder#setDataSpace}.</p> + * + * @return The ImageWriter hardwareBuffer format. + */ + public @HardwareBuffer.Format int getHardwareBufferFormat() { + return mHardwareBufferFormat; + } + + /** + * Get the ImageWriter dataspace. + * + * <p>Use this function if the ImageWriter instance is created by builder pattern + * {@code ImageWriter.Builder} and {@link Builder#setDataSpace}.</p> + * + * @return The ImageWriter dataspace. + */ + @SuppressLint("MethodNameUnits") + public @NamedDataSpace long getDataSpace() { + return mDataSpace; + } + + /** * ImageWriter callback interface, used to to asynchronously notify the * application of various ImageWriter events. */ @@ -755,6 +864,155 @@ public class ImageWriter implements AutoCloseable { return true; } + /** + * Builder class for {@link ImageWriter} objects. + */ + public static final class Builder { + private Surface mSurface; + private int mWidth = -1; + private int mHeight = -1; + private int mMaxImages = 1; + private int mImageFormat = ImageFormat.UNKNOWN; + private @Usage long mUsage = HardwareBuffer.USAGE_CPU_WRITE_OFTEN; + private @HardwareBuffer.Format int mHardwareBufferFormat = HardwareBuffer.RGBA_8888; + private @NamedDataSpace long mDataSpace = DataSpace.DATASPACE_UNKNOWN; + private boolean mUseSurfaceImageFormatInfo = true; + // set this as true temporarily now as a workaround to get correct format + // when using surface format by default without overriding the image format + // in the builder pattern + private boolean mUseLegacyImageFormat = true; + + /** + * Constructs a new builder for {@link ImageWriter}. + * + * @param surface The destination Surface this writer produces Image data into. + */ + public Builder(@NonNull Surface surface) { + mSurface = surface; + } + + /** + * Set the width and height of images. Default size is dependent on the Surface that is + * provided by the downstream end-point. + * + * @param width The width in pixels that will be passed to the producer. + * @param height The height in pixels that will be passed to the producer. + * @return the Builder instance with customized width and height. + */ + @SuppressLint("MissingGetterMatchingBuilder") + public @NonNull Builder setWidthAndHeight(@IntRange(from = 1) int width, + @IntRange(from = 1) int height) { + mWidth = width; + mHeight = height; + return this; + } + + /** + * Set the maximum number of images. Default value is 1. + * + * @param maxImages The maximum number of Images the user will want to access simultaneously + * for producing Image data. + * @return the Builder instance with customized usage value. + */ + public @NonNull Builder setMaxImages(@IntRange(from = 1) int maxImages) { + mMaxImages = maxImages; + return this; + } + + /** + * Set the image format of this ImageWriter. + * Default format depends on the Surface provided. + * + * @param imageFormat The format of the {@link ImageWriter}. It can be any valid specified + * by {@link ImageFormat} or {@link PixelFormat}. + * @return the Builder instance with customized image format. + */ + @SuppressLint("MissingGetterMatchingBuilder") + public @NonNull Builder setImageFormat(@Format int imageFormat) { + if (!ImageFormat.isPublicFormat(imageFormat) + && !PixelFormat.isPublicFormat(imageFormat)) { + throw new IllegalArgumentException( + "Invalid imageFormat is specified: " + imageFormat); + } + mImageFormat = imageFormat; + mUseLegacyImageFormat = true; + mHardwareBufferFormat = HardwareBuffer.RGBA_8888; + mDataSpace = DataSpace.DATASPACE_UNKNOWN; + mUseSurfaceImageFormatInfo = false; + return this; + } + + /** + * Set the hardwareBuffer format of this ImageWriter. The default value is + * {@link HardwareBuffer#RGBA_8888 HardwareBuffer.RGBA_8888}. + * + * <p>This function works together with {@link #setDataSpace} for an + * {@link ImageWriter} instance. Setting at least one of these two replaces + * {@link #setImageFormat} function.</p> + * + * @param hardwareBufferFormat The HardwareBuffer format of the image that this writer + * will produce. + * @return the Builder instance with customized buffer format. + * + * @see #setDataSpace + * @see #setImageFormat + */ + public @NonNull Builder setHardwareBufferFormat( + @HardwareBuffer.Format int hardwareBufferFormat) { + mHardwareBufferFormat = hardwareBufferFormat; + mImageFormat = ImageFormat.UNKNOWN; + mUseLegacyImageFormat = false; + mUseSurfaceImageFormatInfo = false; + return this; + } + + /** + * Set the dataspace of this ImageWriter. + * The default value is {@link DataSpace#DATASPACE_UNKNOWN}. + * + * @param dataSpace The dataspace of the image that this writer will produce. + * @return the builder instance with customized dataspace value. + * + * @see #setHardwareBufferFormat + */ + public @NonNull Builder setDataSpace(@NamedDataSpace long dataSpace) { + mDataSpace = dataSpace; + mImageFormat = ImageFormat.UNKNOWN; + mUseLegacyImageFormat = false; + mUseSurfaceImageFormatInfo = false; + return this; + } + + /** + * Set the usage flag of this ImageWriter. + * Default value is {@link HardwareBuffer#USAGE_CPU_WRITE_OFTEN}. + * + * @param usage The intended usage of the images produced by this ImageWriter. + * @return the Builder instance with customized usage flag. + * + * @see HardwareBuffer + */ + public @NonNull Builder setUsage(@Usage long usage) { + mUsage = usage; + return this; + } + + /** + * Builds a new ImageWriter object. + * + * @return The new ImageWriter object. + */ + public @NonNull ImageWriter build() { + if (mUseLegacyImageFormat) { + return new ImageWriter(mSurface, mMaxImages, mUseSurfaceImageFormatInfo, + mImageFormat, mWidth, mHeight, mUsage); + } else { + return new ImageWriter(mSurface, mMaxImages, mUseSurfaceImageFormatInfo, + mHardwareBufferFormat, mDataSpace, mWidth, mHeight, mUsage); + } + } + } + private static class WriterSurfaceImage extends android.media.Image { private ImageWriter mOwner; // This field is used by native code, do not access or modify. @@ -774,6 +1032,13 @@ public class ImageWriter implements AutoCloseable { public WriterSurfaceImage(ImageWriter writer) { mOwner = writer; + mWidth = writer.mWidth; + mHeight = writer.mHeight; + + if (!writer.mUseLegacyImageFormat) { + mFormat = PublicFormatUtils.getPublicFormat( + writer.mHardwareBufferFormat, writer.mDataSpace); + } } @Override @@ -969,8 +1234,9 @@ public class ImageWriter implements AutoCloseable { } // Native implemented ImageWriter methods. - private synchronized native long nativeInit(Object weakSelf, Surface surface, int maxImgs, - int format, int width, int height); + private synchronized native long nativeInit(Object weakSelf, Surface surface, int maxImages, + int width, int height, boolean useSurfaceImageFormatInfo, int hardwareBufferFormat, + long dataSpace, long usage); private synchronized native void nativeClose(long nativeCtx); diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java index 3c152fb68c0a..e75df1d9b691 100644 --- a/media/java/android/media/MediaCodecInfo.java +++ b/media/java/android/media/MediaCodecInfo.java @@ -603,6 +603,18 @@ public final class MediaCodecInfo { public static final String FEATURE_QpBounds = "qp-bounds"; /** + * <b>video encoder only</b>: codec supports exporting encoding statistics. + * Encoders with this feature can provide the App clients with the encoding statistics + * information about the frame. + * The scope of encoding statistics is controlled by + * {@link MediaFormat#KEY_VIDEO_ENCODING_STATISTICS_LEVEL}. + * + * @see MediaFormat#KEY_VIDEO_ENCODING_STATISTICS_LEVEL + */ + @SuppressLint("AllUpper") // for consistency with other FEATURE_* constants + public static final String FEATURE_EncodingStatistics = "encoding-statistics"; + + /** * Query codec feature capabilities. * <p> * These features are supported to be used by the codec. These @@ -641,6 +653,7 @@ public final class MediaCodecInfo { new Feature(FEATURE_MultipleFrames, (1 << 1), false), new Feature(FEATURE_DynamicTimestamp, (1 << 2), false), new Feature(FEATURE_QpBounds, (1 << 3), false), + new Feature(FEATURE_EncodingStatistics, (1 << 4), false), // feature to exclude codec from REGULAR codec list new Feature(FEATURE_SpecialCodec, (1 << 30), false, true), }; diff --git a/media/java/android/media/MediaDescription.java b/media/java/android/media/MediaDescription.java index 458562afb6ef..dece6bdbb35f 100644 --- a/media/java/android/media/MediaDescription.java +++ b/media/java/android/media/MediaDescription.java @@ -142,10 +142,10 @@ public class MediaDescription implements Parcelable { mTitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); mSubtitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); mDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); - mIcon = in.readParcelable(null); - mIconUri = in.readParcelable(null); + mIcon = in.readParcelable(null, android.graphics.Bitmap.class); + mIconUri = in.readParcelable(null, android.net.Uri.class); mExtras = in.readBundle(); - mMediaUri = in.readParcelable(null); + mMediaUri = in.readParcelable(null, android.net.Uri.class); } /** diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java index 4891d74f868f..4956dbefa240 100644 --- a/media/java/android/media/MediaFormat.java +++ b/media/java/android/media/MediaFormat.java @@ -1176,6 +1176,76 @@ public final class MediaFormat { public static final String KEY_VIDEO_QP_B_MIN = "video-qp-b-min"; /** + * A key describing the level of encoding statistics information emitted from video encoder. + * + * The associated value is an integer. + */ + public static final String KEY_VIDEO_ENCODING_STATISTICS_LEVEL = + "video-encoding-statistics-level"; + + /** + * Encoding Statistics Level None. + * Encoder generates no information about Encoding statistics. + */ + public static final int VIDEO_ENCODING_STATISTICS_LEVEL_NONE = 0; + + /** + * Encoding Statistics Level 1. + * Encoder generates {@link MediaFormat#KEY_PICTURE_TYPE} and + * {@link MediaFormat#KEY_VIDEO_QP_AVERAGE} for each frame. + */ + public static final int VIDEO_ENCODING_STATISTICS_LEVEL_1 = 1; + + /** @hide */ + @IntDef({ + VIDEO_ENCODING_STATISTICS_LEVEL_NONE, + VIDEO_ENCODING_STATISTICS_LEVEL_1, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface VideoEncodingStatisticsLevel {} + + /** + * A key describing the per-frame average block QP (Quantization Parameter). + * This is a part of a video 'Encoding Statistics' export feature. + * This value is emitted from video encoder for a video frame. + * The average value is rounded down (using floor()) to integer value. + * + * The associated value is an integer. + */ + public static final String KEY_VIDEO_QP_AVERAGE = "video-qp-average"; + + /** + * A key describing the picture type of the encoded frame. + * This is a part of a video 'Encoding Statistics' export feature. + * This value is emitted from video encoder for a video frame. + * + * The associated value is an integer. + */ + public static final String KEY_PICTURE_TYPE = "picture-type"; + + /** Picture Type is unknown. */ + public static final int PICTURE_TYPE_UNKNOWN = 0; + + /** Picture Type is I Frame. */ + public static final int PICTURE_TYPE_I = 1; + + /** Picture Type is P Frame. */ + public static final int PICTURE_TYPE_P = 2; + + /** Picture Type is B Frame. */ + public static final int PICTURE_TYPE_B = 3; + + /** @hide */ + @IntDef({ + PICTURE_TYPE_UNKNOWN, + PICTURE_TYPE_I, + PICTURE_TYPE_P, + PICTURE_TYPE_B, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface PictureType {} + + /** * A key describing the audio session ID of the AudioTrack associated * to a tunneled video codec. * The associated value is an integer. diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java index 77c1e55b08cb..d7857a01f7ea 100644 --- a/media/java/android/media/MediaRecorder.java +++ b/media/java/android/media/MediaRecorder.java @@ -365,6 +365,7 @@ public class MediaRecorder implements AudioRouting, */ public static final int VOICE_PERFORMANCE = 10; + /** * Source for an echo canceller to capture the reference signal to be cancelled. * <p> @@ -408,6 +409,15 @@ public class MediaRecorder implements AudioRouting, @SystemApi @RequiresPermission(android.Manifest.permission.CAPTURE_AUDIO_HOTWORD) public static final int HOTWORD = 1999; + + /** Microphone audio source for ultrasound sound if available, behaves like + * {@link #DEFAULT} otherwise. + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.ACCESS_ULTRASOUND) + public static final int ULTRASOUND = 2000; + } /** @hide */ @@ -442,6 +452,7 @@ public class MediaRecorder implements AudioRouting, AudioSource.ECHO_REFERENCE, AudioSource.RADIO_TUNER, AudioSource.HOTWORD, + AudioSource.ULTRASOUND, }) @Retention(RetentionPolicy.SOURCE) public @interface SystemSource {} @@ -454,20 +465,20 @@ public class MediaRecorder implements AudioRouting, */ public static boolean isSystemOnlyAudioSource(int source) { switch(source) { - case AudioSource.DEFAULT: - case AudioSource.MIC: - case AudioSource.VOICE_UPLINK: - case AudioSource.VOICE_DOWNLINK: - case AudioSource.VOICE_CALL: - case AudioSource.CAMCORDER: - case AudioSource.VOICE_RECOGNITION: - case AudioSource.VOICE_COMMUNICATION: - //case REMOTE_SUBMIX: considered "system" as it requires system permissions - case AudioSource.UNPROCESSED: - case AudioSource.VOICE_PERFORMANCE: - return false; - default: - return true; + case AudioSource.DEFAULT: + case AudioSource.MIC: + case AudioSource.VOICE_UPLINK: + case AudioSource.VOICE_DOWNLINK: + case AudioSource.VOICE_CALL: + case AudioSource.CAMCORDER: + case AudioSource.VOICE_RECOGNITION: + case AudioSource.VOICE_COMMUNICATION: + //case REMOTE_SUBMIX: considered "system" as it requires system permissions + case AudioSource.UNPROCESSED: + case AudioSource.VOICE_PERFORMANCE: + return false; + default: + return true; } } @@ -491,6 +502,7 @@ public class MediaRecorder implements AudioRouting, case AudioSource.ECHO_REFERENCE: case AudioSource.RADIO_TUNER: case AudioSource.HOTWORD: + case AudioSource.ULTRASOUND: return true; default: return false; @@ -500,38 +512,40 @@ public class MediaRecorder implements AudioRouting, /** @hide */ public static final String toLogFriendlyAudioSource(int source) { switch(source) { - case AudioSource.DEFAULT: - return "DEFAULT"; - case AudioSource.MIC: - return "MIC"; - case AudioSource.VOICE_UPLINK: - return "VOICE_UPLINK"; - case AudioSource.VOICE_DOWNLINK: - return "VOICE_DOWNLINK"; - case AudioSource.VOICE_CALL: - return "VOICE_CALL"; - case AudioSource.CAMCORDER: - return "CAMCORDER"; - case AudioSource.VOICE_RECOGNITION: - return "VOICE_RECOGNITION"; - case AudioSource.VOICE_COMMUNICATION: - return "VOICE_COMMUNICATION"; - case AudioSource.REMOTE_SUBMIX: - return "REMOTE_SUBMIX"; - case AudioSource.UNPROCESSED: - return "UNPROCESSED"; - case AudioSource.ECHO_REFERENCE: - return "ECHO_REFERENCE"; - case AudioSource.VOICE_PERFORMANCE: - return "VOICE_PERFORMANCE"; - case AudioSource.RADIO_TUNER: - return "RADIO_TUNER"; - case AudioSource.HOTWORD: - return "HOTWORD"; - case AudioSource.AUDIO_SOURCE_INVALID: - return "AUDIO_SOURCE_INVALID"; - default: - return "unknown source " + source; + case AudioSource.DEFAULT: + return "DEFAULT"; + case AudioSource.MIC: + return "MIC"; + case AudioSource.VOICE_UPLINK: + return "VOICE_UPLINK"; + case AudioSource.VOICE_DOWNLINK: + return "VOICE_DOWNLINK"; + case AudioSource.VOICE_CALL: + return "VOICE_CALL"; + case AudioSource.CAMCORDER: + return "CAMCORDER"; + case AudioSource.VOICE_RECOGNITION: + return "VOICE_RECOGNITION"; + case AudioSource.VOICE_COMMUNICATION: + return "VOICE_COMMUNICATION"; + case AudioSource.REMOTE_SUBMIX: + return "REMOTE_SUBMIX"; + case AudioSource.UNPROCESSED: + return "UNPROCESSED"; + case AudioSource.ECHO_REFERENCE: + return "ECHO_REFERENCE"; + case AudioSource.VOICE_PERFORMANCE: + return "VOICE_PERFORMANCE"; + case AudioSource.RADIO_TUNER: + return "RADIO_TUNER"; + case AudioSource.HOTWORD: + return "HOTWORD"; + case AudioSource.ULTRASOUND: + return "ULTRASOUND"; + case AudioSource.AUDIO_SOURCE_INVALID: + return "AUDIO_SOURCE_INVALID"; + default: + return "unknown source " + source; } } diff --git a/media/java/android/media/MediaRoute2Info.java b/media/java/android/media/MediaRoute2Info.java index 9c9e83b0987d..2427fa64562d 100644 --- a/media/java/android/media/MediaRoute2Info.java +++ b/media/java/android/media/MediaRoute2Info.java @@ -371,7 +371,7 @@ public final class MediaRoute2Info implements Parcelable { mFeatures = in.createStringArrayList(); mType = in.readInt(); mIsSystem = in.readBoolean(); - mIconUri = in.readParcelable(null); + mIconUri = in.readParcelable(null, android.net.Uri.class); mDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); mConnectionState = in.readInt(); mClientPackageName = in.readString(); diff --git a/media/java/android/media/Ringtone.java b/media/java/android/media/Ringtone.java index 3cf03417334b..86a94a9e0662 100644 --- a/media/java/android/media/Ringtone.java +++ b/media/java/android/media/Ringtone.java @@ -504,49 +504,51 @@ public class Ringtone { } private boolean playFallbackRingtone() { - if (mAudioManager.getStreamVolume(AudioAttributes.toLegacyStreamType(mAudioAttributes)) - != 0) { - int ringtoneType = RingtoneManager.getDefaultType(mUri); - if (ringtoneType == -1 || - RingtoneManager.getActualDefaultRingtoneUri(mContext, ringtoneType) != null) { - // Default ringtone, try fallback ringtone. - try { - AssetFileDescriptor afd = mContext.getResources().openRawResourceFd( - com.android.internal.R.raw.fallbackring); - if (afd != null) { - mLocalPlayer = new MediaPlayer(); - if (afd.getDeclaredLength() < 0) { - mLocalPlayer.setDataSource(afd.getFileDescriptor()); - } else { - mLocalPlayer.setDataSource(afd.getFileDescriptor(), - afd.getStartOffset(), - afd.getDeclaredLength()); - } - mLocalPlayer.setAudioAttributes(mAudioAttributes); - synchronized (mPlaybackSettingsLock) { - applyPlaybackProperties_sync(); - } - if (mVolumeShaperConfig != null) { - mVolumeShaper = mLocalPlayer.createVolumeShaper(mVolumeShaperConfig); - } - mLocalPlayer.prepare(); - startLocalPlayer(); - afd.close(); - return true; - } else { - Log.e(TAG, "Could not load fallback ringtone"); - } - } catch (IOException ioe) { - destroyLocalPlayer(); - Log.e(TAG, "Failed to open fallback ringtone"); - } catch (NotFoundException nfe) { - Log.e(TAG, "Fallback ringtone does not exist"); - } + int streamType = AudioAttributes.toLegacyStreamType(mAudioAttributes); + if (mAudioManager.getStreamVolume(streamType) == 0) { + return false; + } + int ringtoneType = RingtoneManager.getDefaultType(mUri); + if (ringtoneType != -1 && + RingtoneManager.getActualDefaultRingtoneUri(mContext, ringtoneType) == null) { + Log.w(TAG, "not playing fallback for " + mUri); + return false; + } + // Default ringtone, try fallback ringtone. + try { + AssetFileDescriptor afd = mContext.getResources().openRawResourceFd( + com.android.internal.R.raw.fallbackring); + if (afd == null) { + Log.e(TAG, "Could not load fallback ringtone"); + return false; + } + mLocalPlayer = new MediaPlayer(); + if (afd.getDeclaredLength() < 0) { + mLocalPlayer.setDataSource(afd.getFileDescriptor()); } else { - Log.w(TAG, "not playing fallback for " + mUri); + mLocalPlayer.setDataSource(afd.getFileDescriptor(), + afd.getStartOffset(), + afd.getDeclaredLength()); } + mLocalPlayer.setAudioAttributes(mAudioAttributes); + synchronized (mPlaybackSettingsLock) { + applyPlaybackProperties_sync(); + } + if (mVolumeShaperConfig != null) { + mVolumeShaper = mLocalPlayer.createVolumeShaper(mVolumeShaperConfig); + } + mLocalPlayer.prepare(); + startLocalPlayer(); + afd.close(); + } catch (IOException ioe) { + destroyLocalPlayer(); + Log.e(TAG, "Failed to open fallback ringtone"); + return false; + } catch (NotFoundException nfe) { + Log.e(TAG, "Fallback ringtone does not exist"); + return false; } - return false; + return true; } void setTitle(String title) { diff --git a/media/java/android/media/midi/IMidiManager.aidl b/media/java/android/media/midi/IMidiManager.aidl index d3c8e0adb3ce..b03f78504635 100644 --- a/media/java/android/media/midi/IMidiManager.aidl +++ b/media/java/android/media/midi/IMidiManager.aidl @@ -31,6 +31,8 @@ interface IMidiManager { MidiDeviceInfo[] getDevices(); + MidiDeviceInfo[] getDevicesForTransport(int transport); + // for device creation & removal notifications void registerListener(IBinder clientToken, in IMidiDeviceListener listener); void unregisterListener(IBinder clientToken, in IMidiDeviceListener listener); @@ -43,7 +45,7 @@ interface IMidiManager // for registering built-in MIDI devices MidiDeviceInfo registerDeviceServer(in IMidiDeviceServer server, int numInputPorts, int numOutputPorts, in String[] inputPortNames, in String[] outputPortNames, - in Bundle properties, int type); + in Bundle properties, int type, int defaultProtocol); // for unregistering built-in MIDI devices void unregisterDeviceServer(in IMidiDeviceServer server); diff --git a/media/java/android/media/midi/MidiDeviceInfo.java b/media/java/android/media/midi/MidiDeviceInfo.java index dd3b6dbd6a39..48c50f01c01c 100644 --- a/media/java/android/media/midi/MidiDeviceInfo.java +++ b/media/java/android/media/midi/MidiDeviceInfo.java @@ -16,11 +16,15 @@ package android.media.midi; +import android.annotation.IntDef; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import android.util.Log; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * This class contains information to describe a MIDI device. * For now we only have information that can be retrieved easily for USB devices, @@ -54,6 +58,110 @@ public final class MidiDeviceInfo implements Parcelable { public static final int TYPE_BLUETOOTH = 3; /** + * Constant representing a default protocol with Universal MIDI Packets (UMP). + * UMP is defined in "Universal MIDI Packet (UMP) Format and MIDI 2.0 Protocol" spec. + * All UMP data should be a multiple of 4 bytes. + * Use UMP to negotiate with the device with MIDI-CI. + * MIDI-CI is defined in "MIDI Capability Inquiry (MIDI-CI)" spec. + * Call {@link MidiManager#getDevicesForTransport} with parameter + * {@link MidiManager#TRANSPORT_UNIVERSAL_MIDI_PACKETS} to get devices with this transport. + * @see MidiDeviceInfo#getDefaultProtocol + */ + public static final int PROTOCOL_UMP_USE_MIDI_CI = 0; + + /** + * Constant representing a default protocol with Universal MIDI Packets (UMP). + * UMP is defined in "Universal MIDI Packet (UMP) Format and MIDI 2.0 Protocol" spec. + * All UMP data should be a multiple of 4 bytes. + * Use MIDI 1.0 through UMP with packet sizes up to 64 bits. + * Call {@link MidiManager#getDevicesForTransport} with parameter + * {@link MidiManager#TRANSPORT_UNIVERSAL_MIDI_PACKETS} to get devices with this transport. + * @see MidiDeviceInfo#getDefaultProtocol + */ + public static final int PROTOCOL_UMP_MIDI_1_0_UP_TO_64_BITS = 1; + + /** + * Constant representing a default protocol with Universal MIDI Packets (UMP). + * UMP is defined in "Universal MIDI Packet (UMP) Format and MIDI 2.0 Protocol" spec. + * All UMP data should be a multiple of 4 bytes. + * Use MIDI 1.0 through UMP with packet sizes up to 64 bits and jitter reduction timestamps. + * Call {@link MidiManager#getDevicesForTransport} with parameter + * {@link MidiManager#TRANSPORT_UNIVERSAL_MIDI_PACKETS} to get devices with this transport. + * @see MidiDeviceInfo#getDefaultProtocol + */ + public static final int PROTOCOL_UMP_MIDI_1_0_UP_TO_64_BITS_AND_JRTS = 2; + + /** + * Constant representing a default protocol with Universal MIDI Packets (UMP). + * UMP is defined in "Universal MIDI Packet (UMP) Format and MIDI 2.0 Protocol" spec. + * All UMP data should be a multiple of 4 bytes. + * Use MIDI 1.0 through UMP with packet sizes up to 128 bits. + * Call {@link MidiManager#getDevicesForTransport} with parameter + * {@link MidiManager#TRANSPORT_UNIVERSAL_MIDI_PACKETS} to get devices with this transport. + * @see MidiDeviceInfo#getDefaultProtocol + */ + public static final int PROTOCOL_UMP_MIDI_1_0_UP_TO_128_BITS = 3; + + /** + * Constant representing a default protocol with Universal MIDI Packets (UMP). + * UMP is defined in "Universal MIDI Packet (UMP) Format and MIDI 2.0 Protocol" spec. + * All UMP data should be a multiple of 4 bytes. + * Use MIDI 1.0 through UMP with packet sizes up to 128 bits and jitter reduction timestamps. + * Call {@link MidiManager#getDevicesForTransport} with parameter + * {@link MidiManager#TRANSPORT_UNIVERSAL_MIDI_PACKETS} to get devices with this transport. + * @see MidiDeviceInfo#getDefaultProtocol + */ + public static final int PROTOCOL_UMP_MIDI_1_0_UP_TO_128_BITS_AND_JRTS = 4; + + /** + * Constant representing a default protocol with Universal MIDI Packets (UMP). + * UMP is defined in "Universal MIDI Packet (UMP) Format and MIDI 2.0 Protocol" spec. + * All UMP data should be a multiple of 4 bytes. + * Use MIDI 2.0 through UMP. + * Call {@link MidiManager#getDevicesForTransport} with parameter + * {@link MidiManager#TRANSPORT_UNIVERSAL_MIDI_PACKETS} to get devices with this transport. + * @see MidiDeviceInfo#getDefaultProtocol + */ + public static final int PROTOCOL_UMP_MIDI_2_0 = 17; + + /** + * Constant representing a default protocol with Universal MIDI Packets (UMP). + * UMP is defined in "Universal MIDI Packet (UMP) Format and MIDI 2.0 Protocol" spec. + * All UMP data should be a multiple of 4 bytes. + * Use MIDI 2.0 through UMP and jitter reduction timestamps. + * Call {@link MidiManager#getDevicesForTransport} with parameter + * {@link MidiManager#TRANSPORT_UNIVERSAL_MIDI_PACKETS} to get devices with this transport. + * @see MidiDeviceInfo#getDefaultProtocol + */ + public static final int PROTOCOL_UMP_MIDI_2_0_AND_JRTS = 18; + + /** + * Constant representing a device with an unknown default protocol. + * If Universal MIDI Packets (UMP) are needed, use MIDI-CI through MIDI 1.0. + * UMP is defined in "Universal MIDI Packet (UMP) Format and MIDI 2.0 Protocol" spec. + * MIDI-CI is defined in "MIDI Capability Inquiry (MIDI-CI)" spec. + * @see MidiDeviceInfo#getDefaultProtocol + */ + public static final int PROTOCOL_UNKNOWN = -1; + + /** + * @see MidiDeviceInfo#getDefaultProtocol + * @hide + */ + @IntDef(prefix = { "PROTOCOL_" }, value = { + PROTOCOL_UMP_USE_MIDI_CI, + PROTOCOL_UMP_MIDI_1_0_UP_TO_64_BITS, + PROTOCOL_UMP_MIDI_1_0_UP_TO_64_BITS_AND_JRTS, + PROTOCOL_UMP_MIDI_1_0_UP_TO_128_BITS, + PROTOCOL_UMP_MIDI_1_0_UP_TO_128_BITS_AND_JRTS, + PROTOCOL_UMP_MIDI_2_0, + PROTOCOL_UMP_MIDI_2_0_AND_JRTS, + PROTOCOL_UNKNOWN + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Protocol {} + + /** * Bundle key for the device's user visible name property. * The value for this property is of type {@link java.lang.String}. * Used with the {@link android.os.Bundle} returned by {@link #getProperties}. @@ -196,6 +304,7 @@ public final class MidiDeviceInfo implements Parcelable { private final String[] mOutputPortNames; private final Bundle mProperties; private final boolean mIsPrivate; + private final int mDefaultProtocol; /** * MidiDeviceInfo should only be instantiated by MidiService implementation @@ -203,7 +312,7 @@ public final class MidiDeviceInfo implements Parcelable { */ public MidiDeviceInfo(int type, int id, int numInputPorts, int numOutputPorts, String[] inputPortNames, String[] outputPortNames, Bundle properties, - boolean isPrivate) { + boolean isPrivate, int defaultProtocol) { // Check num ports for out-of-range values. Typical values will be // between zero and three. More than 16 would be very unlikely // because the port index field in the USB packet is only 4 bits. @@ -234,6 +343,7 @@ public final class MidiDeviceInfo implements Parcelable { } mProperties = properties; mIsPrivate = isPrivate; + mDefaultProtocol = defaultProtocol; } /** @@ -312,6 +422,18 @@ public final class MidiDeviceInfo implements Parcelable { return mIsPrivate; } + /** + * Returns the default protocol. For most devices, this will be {@link #PROTOCOL_UNKNOWN}. + * Returning {@link #PROTOCOL_UNKNOWN} is not an error; the device just doesn't support + * Universal MIDI Packets by default. + * + * @return the device's default protocol. + */ + @Protocol + public int getDefaultProtocol() { + return mDefaultProtocol; + } + @Override public boolean equals(Object o) { if (o instanceof MidiDeviceInfo) { @@ -331,11 +453,12 @@ public final class MidiDeviceInfo implements Parcelable { // This is a hack to force the mProperties Bundle to unparcel so we can // print all the names and values. mProperties.getString(PROPERTY_NAME); - return ("MidiDeviceInfo[mType=" + mType + - ",mInputPortCount=" + mInputPortCount + - ",mOutputPortCount=" + mOutputPortCount + - ",mProperties=" + mProperties + - ",mIsPrivate=" + mIsPrivate); + return ("MidiDeviceInfo[mType=" + mType + + ",mInputPortCount=" + mInputPortCount + + ",mOutputPortCount=" + mOutputPortCount + + ",mProperties=" + mProperties + + ",mIsPrivate=" + mIsPrivate + + ",mDefaultProtocol=" + mDefaultProtocol); } public static final @android.annotation.NonNull Parcelable.Creator<MidiDeviceInfo> CREATOR = @@ -349,10 +472,12 @@ public final class MidiDeviceInfo implements Parcelable { String[] inputPortNames = in.createStringArray(); String[] outputPortNames = in.createStringArray(); boolean isPrivate = (in.readInt() == 1); + int defaultProtocol = in.readInt(); Bundle basicPropertiesIgnored = in.readBundle(); Bundle properties = in.readBundle(); return new MidiDeviceInfo(type, id, inputPortCount, outputPortCount, - inputPortNames, outputPortNames, properties, isPrivate); + inputPortNames, outputPortNames, properties, isPrivate, + defaultProtocol); } public MidiDeviceInfo[] newArray(int size) { @@ -390,6 +515,7 @@ public final class MidiDeviceInfo implements Parcelable { parcel.writeStringArray(mInputPortNames); parcel.writeStringArray(mOutputPortNames); parcel.writeInt(mIsPrivate ? 1 : 0); + parcel.writeInt(mDefaultProtocol); // "Basic" properties only contain properties of primitive types // and thus can be read back by native code. "Extra" properties is // a superset that contains all properties. diff --git a/media/java/android/media/midi/MidiDeviceStatus.java b/media/java/android/media/midi/MidiDeviceStatus.java index b11827966b1a..aa0626742ac2 100644 --- a/media/java/android/media/midi/MidiDeviceStatus.java +++ b/media/java/android/media/midi/MidiDeviceStatus.java @@ -115,7 +115,7 @@ public final class MidiDeviceStatus implements Parcelable { new Parcelable.Creator<MidiDeviceStatus>() { public MidiDeviceStatus createFromParcel(Parcel in) { ClassLoader classLoader = MidiDeviceInfo.class.getClassLoader(); - MidiDeviceInfo deviceInfo = in.readParcelable(classLoader); + MidiDeviceInfo deviceInfo = in.readParcelable(classLoader, android.media.midi.MidiDeviceInfo.class); boolean[] inputPortOpen = in.createBooleanArray(); int[] outputPortOpenCount = in.createIntArray(); return new MidiDeviceStatus(deviceInfo, inputPortOpen, outputPortOpenCount); diff --git a/media/java/android/media/midi/MidiManager.java b/media/java/android/media/midi/MidiManager.java index dee94c681e87..5348d4e358d0 100644 --- a/media/java/android/media/midi/MidiManager.java +++ b/media/java/android/media/midi/MidiManager.java @@ -16,19 +16,31 @@ package android.media.midi; +import android.annotation.IntDef; +import android.annotation.NonNull; import android.annotation.RequiresFeature; import android.annotation.SystemService; import android.bluetooth.BluetoothDevice; import android.content.Context; import android.content.pm.PackageManager; import android.os.Binder; -import android.os.IBinder; import android.os.Bundle; import android.os.Handler; +import android.os.IBinder; import android.os.RemoteException; +import android.util.ArraySet; import android.util.Log; +import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Collections; +import java.util.Objects; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executor; + +// BLE-MIDI /** * This class is the public application interface to the MIDI service. @@ -39,6 +51,39 @@ public final class MidiManager { private static final String TAG = "MidiManager"; /** + * Constant representing MIDI devices. + * These devices do NOT support Universal MIDI Packets by default. + * These support the original MIDI 1.0 byte stream. + * When communicating to a USB device, a raw byte stream will be padded for USB. + * Likewise, for a Bluetooth device, the raw bytes will be converted for Bluetooth. + * For virtual devices, the byte stream will be passed directly. + * If Universal MIDI Packets are needed, please use MIDI-CI. + * @see MidiManager#getDevicesForTransport + */ + public static final int TRANSPORT_MIDI_BYTE_STREAM = 1; + + /** + * Constant representing Universal MIDI devices. + * These devices do support Universal MIDI Packets (UMP) by default. + * When sending data to these devices, please send UMP. + * Packets should always be a multiple of 4 bytes. + * UMP is defined in the USB MIDI 2.0 spec. Please read the standard for more info. + * @see MidiManager#getDevicesForTransport + */ + public static final int TRANSPORT_UNIVERSAL_MIDI_PACKETS = 2; + + /** + * @see MidiManager#getDevicesForTransport + * @hide + */ + @IntDef(prefix = { "TRANSPORT_" }, value = { + TRANSPORT_MIDI_BYTE_STREAM, + TRANSPORT_UNIVERSAL_MIDI_PACKETS + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Transport {} + + /** * Intent for starting BluetoothMidiService * @hide */ @@ -68,43 +113,67 @@ public final class MidiManager { private class DeviceListener extends IMidiDeviceListener.Stub { private final DeviceCallback mCallback; private final Handler mHandler; + private final Executor mExecutor; + private final int mTransport; - public DeviceListener(DeviceCallback callback, Handler handler) { + DeviceListener(DeviceCallback callback, Handler handler, int transport) { mCallback = callback; mHandler = handler; + mExecutor = null; + mTransport = transport; + } + + DeviceListener(DeviceCallback callback, Executor executor, int transport) { + mCallback = callback; + mHandler = null; + mExecutor = executor; + mTransport = transport; } @Override public void onDeviceAdded(MidiDeviceInfo device) { - if (mHandler != null) { - final MidiDeviceInfo deviceF = device; - mHandler.post(new Runnable() { - @Override public void run() { - mCallback.onDeviceAdded(deviceF); - } - }); - } else { - mCallback.onDeviceAdded(device); + if (shouldInvokeCallback(device)) { + if (mExecutor != null) { + mExecutor.execute(() -> + mCallback.onDeviceAdded(device)); + } else if (mHandler != null) { + final MidiDeviceInfo deviceF = device; + mHandler.post(new Runnable() { + @Override public void run() { + mCallback.onDeviceAdded(deviceF); + } + }); + } else { + mCallback.onDeviceAdded(device); + } } } @Override public void onDeviceRemoved(MidiDeviceInfo device) { - if (mHandler != null) { - final MidiDeviceInfo deviceF = device; - mHandler.post(new Runnable() { - @Override public void run() { - mCallback.onDeviceRemoved(deviceF); - } - }); - } else { - mCallback.onDeviceRemoved(device); + if (shouldInvokeCallback(device)) { + if (mExecutor != null) { + mExecutor.execute(() -> + mCallback.onDeviceRemoved(device)); + } else if (mHandler != null) { + final MidiDeviceInfo deviceF = device; + mHandler.post(new Runnable() { + @Override public void run() { + mCallback.onDeviceRemoved(deviceF); + } + }); + } else { + mCallback.onDeviceRemoved(device); + } } } @Override public void onDeviceStatusChanged(MidiDeviceStatus status) { - if (mHandler != null) { + if (mExecutor != null) { + mExecutor.execute(() -> + mCallback.onDeviceStatusChanged(status)); + } else if (mHandler != null) { final MidiDeviceStatus statusF = status; mHandler.post(new Runnable() { @Override public void run() { @@ -115,6 +184,25 @@ public final class MidiManager { mCallback.onDeviceStatusChanged(status); } } + + /** + * Used to figure out whether callbacks should be invoked. Only invoke callbacks of + * the correct type. + * + * @param MidiDeviceInfo the device to check + * @return whether to invoke a callback + */ + private boolean shouldInvokeCallback(MidiDeviceInfo device) { + // UMP devices have protocols that are not PROTOCOL_UNKNOWN + if (mTransport == TRANSPORT_UNIVERSAL_MIDI_PACKETS) { + return (device.getDefaultProtocol() != MidiDeviceInfo.PROTOCOL_UNKNOWN); + } else if (mTransport == TRANSPORT_MIDI_BYTE_STREAM) { + return (device.getDefaultProtocol() == MidiDeviceInfo.PROTOCOL_UNKNOWN); + } else { + Log.e(TAG, "Invalid transport type: " + mTransport); + return false; + } + } } /** @@ -167,8 +255,10 @@ public final class MidiManager { } /** - * Registers a callback to receive notifications when MIDI devices are added and removed. - * + * Registers a callback to receive notifications when MIDI 1.0 devices are added and removed. + * These are devices that do not default to Universal MIDI Packets. To register for a callback + * for those, call {@link #registerDeviceCallback} instead. + * The {@link DeviceCallback#onDeviceStatusChanged} method will be called immediately * for any devices that have open ports. This allows applications to know which input * ports are already in use and, therefore, unavailable. @@ -180,9 +270,42 @@ public final class MidiManager { * @param handler The {@link android.os.Handler Handler} that will be used for delivering the * device notifications. If handler is null, then the thread used for the * callback is unspecified. + * @deprecated Use the {@link #registerDeviceCallback} + * method with Executor and transport instead. */ + @Deprecated public void registerDeviceCallback(DeviceCallback callback, Handler handler) { - DeviceListener deviceListener = new DeviceListener(callback, handler); + DeviceListener deviceListener = new DeviceListener(callback, handler, + TRANSPORT_MIDI_BYTE_STREAM); + try { + mService.registerListener(mToken, deviceListener); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + mDeviceListeners.put(callback, deviceListener); + } + + /** + * Registers a callback to receive notifications when MIDI devices are added and removed + * for a specific transport type. + * + * The {@link DeviceCallback#onDeviceStatusChanged} method will be called immediately + * for any devices that have open ports. This allows applications to know which input + * ports are already in use and, therefore, unavailable. + * + * Applications should call {@link #getDevicesForTransport} before registering the callback + * to get a list of devices already added. + * + * @param transport The transport to be used. This is either TRANSPORT_MIDI_BYTE_STREAM or + * TRANSPORT_UNIVERSAL_MIDI_PACKETS. + * @param executor The {@link Executor} that will be used for delivering the + * device notifications. + * @param callback a {@link DeviceCallback} for MIDI device notifications + */ + public void registerDeviceCallback(@Transport int transport, + @NonNull Executor executor, @NonNull DeviceCallback callback) { + Objects.requireNonNull(executor); + DeviceListener deviceListener = new DeviceListener(callback, executor, transport); try { mService.registerListener(mToken, deviceListener); } catch (RemoteException e) { @@ -208,10 +331,14 @@ public final class MidiManager { } /** - * Gets the list of all connected MIDI devices. + * Gets a list of connected MIDI devices. This returns all devices that do + * not default to Universal MIDI Packets. To get those instead, please call + * {@link #getDevicesForTransport} instead. * - * @return an array of all MIDI devices + * @return an array of MIDI devices + * @deprecated Use {@link #getDevicesForTransport} instead. */ + @Deprecated public MidiDeviceInfo[] getDevices() { try { return mService.getDevices(); @@ -220,6 +347,28 @@ public final class MidiManager { } } + /** + * Gets a list of connected MIDI devices by transport. TRANSPORT_MIDI_BYTE_STREAM + * is used for MIDI 1.0 and is the most common. + * For devices with built in Universal MIDI Packet support, use + * TRANSPORT_UNIVERSAL_MIDI_PACKETS instead. + * + * @param transport The transport to be used. This is either TRANSPORT_MIDI_BYTE_STREAM or + * TRANSPORT_UNIVERSAL_MIDI_PACKETS. + * @return a collection of MIDI devices + */ + public @NonNull Set<MidiDeviceInfo> getDevicesForTransport(@Transport int transport) { + try { + MidiDeviceInfo[] devices = mService.getDevicesForTransport(transport); + if (devices == null) { + return Collections.emptySet(); + } + return new ArraySet<>(devices); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + private void sendOpenDeviceResponse(final MidiDevice device, final OnDeviceOpenedListener listener, Handler handler) { if (handler != null) { @@ -284,9 +433,11 @@ public final class MidiManager { final OnDeviceOpenedListener listenerF = listener; final Handler handlerF = handler; + Log.d(TAG, "openBluetoothDevice() " + bluetoothDevice); IMidiDeviceOpenCallback callback = new IMidiDeviceOpenCallback.Stub() { @Override public void onDeviceOpened(IMidiDeviceServer server, IBinder deviceToken) { + Log.d(TAG, "onDeviceOpened() server:" + server); MidiDevice device = null; if (server != null) { try { @@ -308,16 +459,26 @@ public final class MidiManager { } } + /** @hide */ // for now + public void closeBluetoothDevice(@NonNull MidiDevice midiDevice) { + try { + midiDevice.close(); + } catch (IOException ex) { + Log.e(TAG, "Exception closing BLE-MIDI device" + ex); + } + } + /** @hide */ public MidiDeviceServer createDeviceServer(MidiReceiver[] inputPortReceivers, int numOutputPorts, String[] inputPortNames, String[] outputPortNames, - Bundle properties, int type, MidiDeviceServer.Callback callback) { + Bundle properties, int type, int defaultProtocol, + MidiDeviceServer.Callback callback) { try { MidiDeviceServer server = new MidiDeviceServer(mService, inputPortReceivers, numOutputPorts, callback); MidiDeviceInfo deviceInfo = mService.registerDeviceServer(server.getBinderInterface(), inputPortReceivers.length, numOutputPorts, inputPortNames, outputPortNames, - properties, type); + properties, type, defaultProtocol); if (deviceInfo == null) { Log.e(TAG, "registerVirtualDevice failed"); return null; diff --git a/media/java/android/media/midi/package.html b/media/java/android/media/midi/package.html index 33c54900cd07..67df1b2fa315 100644 --- a/media/java/android/media/midi/package.html +++ b/media/java/android/media/midi/package.html @@ -405,5 +405,46 @@ apps using the <a href="#get_list_of_already_plugged_in_entities">MIDI device discovery calls described above</a>. </p> +<h1 id=using_midi_2_0_over_usb>Using MIDI 2.0 over USB</h1> + +<p>An app can use MIDI 2.0 over USB starting in Android T. MIDI 2.0 packets are embedded in +Universal MIDI Packets, or UMP for short. A MIDI 2.0 USB device should create two interfaces, +one endpoint that accepts only MIDI 1.0 packets and one that accepts only UMP packets. +For more info about MIDI 2.0 and UMP, please read the MIDI 2.0 USB spec.</p> + +<p>MidiManager.getDevices() would simply return the 1.0 interface. This interface should work +exactly the same as before. In order to use the new UMP interface, retrieve the device with the +following code snippet.</p> + +<pre class=prettyprint> +Collection<MidiDeviceInfo> universalDeviceInfos = midiManager.getDevicesForTransport( + MidiManager.TRANSPORT_UNIVERSAL_MIDI_PACKETS); +</pre> + +<p>UMP Packets are always in multiple of 4 bytes. For each set of 4 bytes, they are sent in network +order. Compare the following NoteOn code snippet with the NoteOn code snippet above. </p> + +<pre class=prettyprint> +byte[] buffer = new byte[32]; +int numBytes = 0; +int channel = 3; // MIDI channels 1-16 are encoded as 0-15. +int group = 0; +buffer[numBytes++] = (byte)(0x20 + group); // MIDI 1.0 voice message +buffer[numBytes++] = (byte)(0x90 + (channel - 1)); // note on +buffer[numBytes++] = (byte)60; // pitch is middle C +buffer[numBytes++] = (byte)127; // max velocity +int offset = 0; +// post is non-blocking +inputPort.send(buffer, offset, numBytes); +</pre> + +<p>MIDI 2.0 messages can be sent through UMP after negotiating with the device. This is called +MIDI-CI and is documented in the MIDI 2.0 spec. Some USB devices support pre-negotiated MIDI 2.0. +For a MidiDeviceInfo, you can query the defaultProtocol.</p> + +<pre class=prettyprint> +int defaultProtocol = info.getDefaultProtocol(); +</pre> + </body> </html> diff --git a/media/java/android/media/musicrecognition/RecognitionRequest.java b/media/java/android/media/musicrecognition/RecognitionRequest.java index 3298d634d342..b8757a351e24 100644 --- a/media/java/android/media/musicrecognition/RecognitionRequest.java +++ b/media/java/android/media/musicrecognition/RecognitionRequest.java @@ -152,8 +152,8 @@ public final class RecognitionRequest implements Parcelable { } private RecognitionRequest(Parcel in) { - mAudioFormat = in.readParcelable(AudioFormat.class.getClassLoader()); - mAudioAttributes = in.readParcelable(AudioAttributes.class.getClassLoader()); + mAudioFormat = in.readParcelable(AudioFormat.class.getClassLoader(), android.media.AudioFormat.class); + mAudioAttributes = in.readParcelable(AudioAttributes.class.getClassLoader(), android.media.AudioAttributes.class); mCaptureSession = in.readInt(); mMaxAudioLengthSeconds = in.readInt(); mIgnoreBeginningFrames = in.readInt(); diff --git a/media/java/android/media/session/MediaController.java b/media/java/android/media/session/MediaController.java index 1da41fb87b40..955ae3ca28fb 100644 --- a/media/java/android/media/session/MediaController.java +++ b/media/java/android/media/session/MediaController.java @@ -1022,7 +1022,7 @@ public final class MediaController { mVolumeControl = in.readInt(); mMaxVolume = in.readInt(); mCurrentVolume = in.readInt(); - mAudioAttrs = in.readParcelable(null); + mAudioAttrs = in.readParcelable(null, android.media.AudioAttributes.class); mVolumeControlId = in.readString(); } diff --git a/media/java/android/media/tv/AitInfo.java b/media/java/android/media/tv/AitInfo.java index ff4c6253b599..71b1634cef92 100644 --- a/media/java/android/media/tv/AitInfo.java +++ b/media/java/android/media/tv/AitInfo.java @@ -17,11 +17,12 @@ package android.media.tv; import android.annotation.NonNull; +import android.media.tv.interactive.TvInteractiveAppInfo; import android.os.Parcel; import android.os.Parcelable; /** - * AIT info. + * AIT (Application Information Table) info. * @hide */ public final class AitInfo implements Parcelable { @@ -50,14 +51,15 @@ public final class AitInfo implements Parcelable { /** * Constructs AIT info. */ - public AitInfo(int type, int version) { + public AitInfo(@TvInteractiveAppInfo.InteractiveAppType int type, int version) { mType = type; mVersion = version; } /** - * Gets type. + * Gets interactive app type. */ + @TvInteractiveAppInfo.InteractiveAppType public int getType() { return mType; } diff --git a/media/java/android/media/tv/DsmccResponse.java b/media/java/android/media/tv/DsmccResponse.java index 4d496207051a..3ca63e323bcd 100644 --- a/media/java/android/media/tv/DsmccResponse.java +++ b/media/java/android/media/tv/DsmccResponse.java @@ -17,10 +17,13 @@ package android.media.tv; import android.annotation.NonNull; +import android.annotation.StringDef; import android.os.Parcel; import android.os.ParcelFileDescriptor; import android.os.Parcelable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.List; @@ -29,6 +32,27 @@ public final class DsmccResponse extends BroadcastInfoResponse implements Parcel public static final @TvInputManager.BroadcastInfoType int responseType = TvInputManager.BROADCAST_INFO_TYPE_DSMCC; + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @StringDef(prefix = "BIOP_MESSAGE_TYPE_", value = { + BIOP_MESSAGE_TYPE_DIRECTORY, + BIOP_MESSAGE_TYPE_FILE, + BIOP_MESSAGE_TYPE_STREAM, + BIOP_MESSAGE_TYPE_SERVICE_GATEWAY, + + }) + public @interface BiopMessageType {} + + /** Broadcast Inter-ORB Protocol (BIOP) message types */ + /** BIOP directory message */ + public static final String BIOP_MESSAGE_TYPE_DIRECTORY = "directory"; + /** BIOP file message */ + public static final String BIOP_MESSAGE_TYPE_FILE = "file"; + /** BIOP stream message */ + public static final String BIOP_MESSAGE_TYPE_STREAM = "stream"; + /** BIOP service gateway message */ + public static final String BIOP_MESSAGE_TYPE_SERVICE_GATEWAY = "service_gateway"; + public static final @NonNull Parcelable.Creator<DsmccResponse> CREATOR = new Parcelable.Creator<DsmccResponse>() { @Override @@ -43,39 +67,173 @@ public final class DsmccResponse extends BroadcastInfoResponse implements Parcel } }; + private final @BiopMessageType String mBiopMessageType; private final ParcelFileDescriptor mFileDescriptor; - private final boolean mIsDirectory; - private final List<String> mChildren; + private final List<String> mChildList; + private final int[] mEventIds; + private final String[] mEventNames; public static DsmccResponse createFromParcelBody(Parcel in) { return new DsmccResponse(in); } + /** + * Constructs a BIOP file message response. + */ public DsmccResponse(int requestId, int sequence, @ResponseResult int responseResult, - ParcelFileDescriptor file, boolean isDirectory, List<String> children) { + @NonNull ParcelFileDescriptor file) { super(responseType, requestId, sequence, responseResult); + mBiopMessageType = BIOP_MESSAGE_TYPE_FILE; mFileDescriptor = file; - mIsDirectory = isDirectory; - mChildren = children; + mChildList = null; + mEventIds = null; + mEventNames = null; + } + + /** + * Constructs a BIOP service gateway or directory message response. + */ + public DsmccResponse(int requestId, int sequence, @ResponseResult int responseResult, + boolean isServiceGateway, @NonNull List<String> childList) { + super(responseType, requestId, sequence, responseResult); + if (isServiceGateway) { + mBiopMessageType = BIOP_MESSAGE_TYPE_SERVICE_GATEWAY; + } else { + mBiopMessageType = BIOP_MESSAGE_TYPE_DIRECTORY; + } + mFileDescriptor = null; + mChildList = childList; + mEventIds = null; + mEventNames = null; + } + + /** + * Constructs a BIOP stream message response. + * + * <p>The current stream message response does not support other stream messages types than + * stream event message type. + */ + public DsmccResponse(int requestId, int sequence, @ResponseResult int responseResult, + @NonNull int[] eventIds, @NonNull String[] eventNames) { + super(responseType, requestId, sequence, responseResult); + mBiopMessageType = BIOP_MESSAGE_TYPE_STREAM; + mFileDescriptor = null; + mChildList = null; + mEventIds = eventIds; + mEventNames = eventNames; + if (mEventIds.length != eventNames.length) { + throw new IllegalStateException("The size of eventIds and eventNames must be equal"); + } } - protected DsmccResponse(Parcel source) { + private DsmccResponse(@NonNull Parcel source) { super(responseType, source); - mFileDescriptor = source.readFileDescriptor(); - mIsDirectory = (source.readInt() == 1); - mChildren = new ArrayList<>(); - source.readStringList(mChildren); + + mBiopMessageType = source.readString(); + switch (mBiopMessageType) { + case BIOP_MESSAGE_TYPE_SERVICE_GATEWAY: + case BIOP_MESSAGE_TYPE_DIRECTORY: + int childNum = source.readInt(); + mChildList = new ArrayList<>(); + for (int i = 0; i < childNum; i++) { + mChildList.add(source.readString()); + } + mFileDescriptor = null; + mEventIds = null; + mEventNames = null; + break; + case BIOP_MESSAGE_TYPE_FILE: + mFileDescriptor = source.readFileDescriptor(); + mChildList = null; + mEventIds = null; + mEventNames = null; + break; + case BIOP_MESSAGE_TYPE_STREAM: + int eventNum = source.readInt(); + mEventIds = new int[eventNum]; + mEventNames = new String[eventNum]; + for (int i = 0; i < eventNum; i++) { + mEventIds[i] = source.readInt(); + mEventNames[i] = source.readString(); + } + mChildList = null; + mFileDescriptor = null; + break; + default: + throw new IllegalStateException("unexpected BIOP message type"); + } + } + + /** Returns the BIOP message type */ + @NonNull + public @BiopMessageType String getBiopMessageType() { + return mBiopMessageType; } + /** Returns the file descriptor for a given file message response */ + @NonNull public ParcelFileDescriptor getFile() { + if (!mBiopMessageType.equals(BIOP_MESSAGE_TYPE_FILE)) { + throw new IllegalStateException("Not file object"); + } return mFileDescriptor; } + /** + * Returns a list of subobject names for the given service gateway or directory message + * response. + */ + @NonNull + public List<String> getChildList() { + if (!mBiopMessageType.equals(BIOP_MESSAGE_TYPE_DIRECTORY) + && !mBiopMessageType.equals(BIOP_MESSAGE_TYPE_SERVICE_GATEWAY)) { + throw new IllegalStateException("Not directory object"); + } + return new ArrayList<String>(mChildList); + } + + /** Returns all event IDs carried in a given stream message response. */ + @NonNull + public int[] getStreamEventIds() { + if (!mBiopMessageType.equals(BIOP_MESSAGE_TYPE_STREAM)) { + throw new IllegalStateException("Not stream event object"); + } + return mEventIds; + } + + /** Returns all event names carried in a given stream message response */ + @NonNull + public String[] getStreamEventNames() { + if (!mBiopMessageType.equals(BIOP_MESSAGE_TYPE_STREAM)) { + throw new IllegalStateException("Not stream event object"); + } + return mEventNames; + } + @Override public void writeToParcel(@NonNull Parcel dest, int flags) { super.writeToParcel(dest, flags); - mFileDescriptor.writeToParcel(dest, flags); - dest.writeInt(mIsDirectory ? 1 : 0); - dest.writeStringList(mChildren); + dest.writeString(mBiopMessageType); + switch (mBiopMessageType) { + case BIOP_MESSAGE_TYPE_SERVICE_GATEWAY: + case BIOP_MESSAGE_TYPE_DIRECTORY: + dest.writeInt(mChildList.size()); + for (String child : mChildList) { + dest.writeString(child); + } + break; + case BIOP_MESSAGE_TYPE_FILE: + dest.writeFileDescriptor(mFileDescriptor.getFileDescriptor()); + break; + case BIOP_MESSAGE_TYPE_STREAM: + dest.writeInt(mEventIds.length); + for (int i = 0; i < mEventIds.length; i++) { + dest.writeInt(mEventIds[i]); + dest.writeString(mEventNames[i]); + } + break; + default: + throw new IllegalStateException("unexpected BIOP message type"); + } } } diff --git a/media/java/android/media/tv/StreamEventResponse.java b/media/java/android/media/tv/StreamEventResponse.java index fd7580107c71..903fab5c2268 100644 --- a/media/java/android/media/tv/StreamEventResponse.java +++ b/media/java/android/media/tv/StreamEventResponse.java @@ -39,54 +39,53 @@ public final class StreamEventResponse extends BroadcastInfoResponse implements } }; - private final String mName; - private final String mText; - private final String mData; - private final String mStatus; + private final int mEventId; + private final long mNpt; + private final byte[] mData; public static StreamEventResponse createFromParcelBody(Parcel in) { return new StreamEventResponse(in); } public StreamEventResponse(int requestId, int sequence, @ResponseResult int responseResult, - String name, String text, String data, String status) { + int eventId, long npt, @NonNull byte[] data) { super(responseType, requestId, sequence, responseResult); - mName = name; - mText = text; + mEventId = eventId; + mNpt = npt; mData = data; - mStatus = status; } - protected StreamEventResponse(Parcel source) { + private StreamEventResponse(@NonNull Parcel source) { super(responseType, source); - mName = source.readString(); - mText = source.readString(); - mData = source.readString(); - mStatus = source.readString(); + mEventId = source.readInt(); + mNpt = source.readLong(); + int dataLength = source.readInt(); + mData = new byte[dataLength]; + source.readByteArray(mData); } - public String getName() { - return mName; + /** Returns the event ID */ + public int getEventId() { + return mEventId; } - public String getText() { - return mText; + /** Returns the NPT(Normal Play Time) value when the event occurred or will occur */ + public long getNpt() { + return mNpt; } - public String getData() { + /** Returns the application specific data */ + @NonNull + public byte[] getData() { return mData; } - public String getStatus() { - return mStatus; - } - @Override public void writeToParcel(@NonNull Parcel dest, int flags) { super.writeToParcel(dest, flags); - dest.writeString(mName); - dest.writeString(mText); - dest.writeString(mData); - dest.writeString(mStatus); + dest.writeInt(mEventId); + dest.writeLong(mNpt); + dest.writeInt(mData.length); + dest.writeByteArray(mData); } } diff --git a/media/java/android/media/tv/TvContentRatingSystemInfo.java b/media/java/android/media/tv/TvContentRatingSystemInfo.java index f44ded3dbd37..947b2d67bfce 100644 --- a/media/java/android/media/tv/TvContentRatingSystemInfo.java +++ b/media/java/android/media/tv/TvContentRatingSystemInfo.java @@ -94,8 +94,8 @@ public final class TvContentRatingSystemInfo implements Parcelable { }; private TvContentRatingSystemInfo(Parcel in) { - mXmlUri = in.readParcelable(null); - mApplicationInfo = in.readParcelable(null); + mXmlUri = in.readParcelable(null, android.net.Uri.class); + mApplicationInfo = in.readParcelable(null, android.content.pm.ApplicationInfo.class); } @Override diff --git a/media/java/android/media/tv/TvInputInfo.java b/media/java/android/media/tv/TvInputInfo.java index 54cb2bff5566..e60d5378f88c 100644 --- a/media/java/android/media/tv/TvInputInfo.java +++ b/media/java/android/media/tv/TvInputInfo.java @@ -653,16 +653,16 @@ public final class TvInputInfo implements Parcelable { mType = in.readInt(); mIsHardwareInput = in.readByte() == 1; mLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); - mIconUri = in.readParcelable(null); + mIconUri = in.readParcelable(null, android.net.Uri.class); mLabelResId = in.readInt(); - mIcon = in.readParcelable(null); - mIconStandby = in.readParcelable(null); - mIconDisconnected = in.readParcelable(null); + mIcon = in.readParcelable(null, android.graphics.drawable.Icon.class); + mIconStandby = in.readParcelable(null, android.graphics.drawable.Icon.class); + mIconDisconnected = in.readParcelable(null, android.graphics.drawable.Icon.class); mSetupActivity = in.readString(); mCanRecord = in.readByte() == 1; mCanPauseRecording = in.readByte() == 1; mTunerCount = in.readInt(); - mHdmiDeviceInfo = in.readParcelable(null); + mHdmiDeviceInfo = in.readParcelable(null, android.hardware.hdmi.HdmiDeviceInfo.class); mIsConnectedToHdmiSwitch = in.readByte() == 1; mHdmiConnectionRelativePosition = in.readInt(); mParentId = in.readString(); diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java index 98d1599e62e9..f438d293ac8e 100644 --- a/media/java/android/media/tv/TvInputManager.java +++ b/media/java/android/media/tv/TvInputManager.java @@ -31,7 +31,7 @@ import android.graphics.Rect; import android.media.AudioDeviceInfo; import android.media.AudioFormat.Encoding; import android.media.PlaybackParams; -import android.media.tv.interactive.TvIAppManager; +import android.media.tv.interactive.TvInteractiveAppManager; import android.net.Uri; import android.os.Binder; import android.os.Bundle; @@ -2318,7 +2318,7 @@ public final class TvInputManager { // @GuardedBy("mMetadataLock") private int mVideoHeight; - private TvIAppManager.Session mIAppSession; + private TvInteractiveAppManager.Session mIAppSession; private boolean mIAppNotificationEnabled = false; private Session(IBinder token, InputChannel channel, ITvInputManager service, int userId, @@ -2331,11 +2331,11 @@ public final class TvInputManager { mSessionCallbackRecordMap = sessionCallbackRecordMap; } - public TvIAppManager.Session getInteractiveAppSession() { + public TvInteractiveAppManager.Session getInteractiveAppSession() { return mIAppSession; } - public void setInteractiveAppSession(TvIAppManager.Session iAppSession) { + public void setInteractiveAppSession(TvInteractiveAppManager.Session iAppSession) { this.mIAppSession = iAppSession; } @@ -2593,9 +2593,9 @@ public final class TvInputManager { /** * Enables interactive app notification. + * * @param enabled {@code true} if you want to enable interactive app notifications. * {@code false} otherwise. - * @hide */ public void setInteractiveAppNotificationEnabled(boolean enabled) { if (mToken == null) { diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java index 524ba34685b5..9bc736743ecc 100755 --- a/media/java/android/media/tv/TvInputService.java +++ b/media/java/android/media/tv/TvInputService.java @@ -945,7 +945,13 @@ public abstract class TvInputService extends Service { } /** - * Notifies AIT info updated. + * Informs the app that the AIT (Application Information Table) is updated. + * + * <p>This method should also be call when + * {@link #onSetInteractiveAppNotificationEnabled(boolean)} is called to send the first AIT + * info. + * + * @see #onSetInteractiveAppNotificationEnabled(boolean) * @hide */ public void notifyAitInfoUpdated(@NonNull final AitInfo aitInfo) { @@ -1198,7 +1204,16 @@ public abstract class TvInputService extends Service { /** * Enables or disables interactive app notification. + * + * <p>This method enables or disables the event detection from the corresponding TV input. + * When it's enabled, the TV input service detects events related to interactive app, such + * as AIT (Application Information Table) and sends to TvView or the linked TV interactive + * app service. + * * @param enabled {@code true} to enable, {@code false} to disable. + * + * @see TvView#setInteractiveAppNotificationEnabled(boolean) + * @see Session#notifyAitInfoUpdated(android.media.tv.AitInfo) * @hide */ public void onSetInteractiveAppNotificationEnabled(boolean enabled) { diff --git a/media/java/android/media/tv/TvView.java b/media/java/android/media/tv/TvView.java index 71f6ad6dd034..d2086c502a5c 100644 --- a/media/java/android/media/tv/TvView.java +++ b/media/java/android/media/tv/TvView.java @@ -481,9 +481,18 @@ public class TvView extends ViewGroup { } /** - * Enables interactive app notification. + * Enables or disables interactive app notification. + * + * <p>This method enables or disables the event detection from the corresponding TV input. When + * it's enabled, the TV input service detects events related to interactive app, such as + * AIT (Application Information Table) and sends to TvView or the linked TV interactive app + * service. + * * @param enabled {@code true} if you want to enable interactive app notifications. * {@code false} otherwise. + * + * @see TvInputService.Session#notifyAitInfoUpdated(android.media.tv.AitInfo) + * @see android.media.tv.interactive.TvInteractiveAppView#setTvView(TvView) * @hide */ public void setInteractiveAppNotificationEnabled(boolean enabled) { @@ -1062,12 +1071,12 @@ public class TvView extends ViewGroup { } /** - * This is called when the AIT info has been updated. + * This is called when the AIT (Application Information Table) info has been updated. * * @param aitInfo The current AIT info. * @hide */ - public void onAitInfoUpdated(String inputId, AitInfo aitInfo) { + public void onAitInfoUpdated(@NonNull String inputId, @NonNull AitInfo aitInfo) { } /** diff --git a/media/java/android/media/tv/interactive/AppLinkInfo.aidl b/media/java/android/media/tv/interactive/AppLinkInfo.aidl new file mode 100644 index 000000000000..7c52d018a3d6 --- /dev/null +++ b/media/java/android/media/tv/interactive/AppLinkInfo.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media.tv.interactive; + +parcelable AppLinkInfo;
\ No newline at end of file diff --git a/media/java/android/media/tv/interactive/AppLinkInfo.java b/media/java/android/media/tv/interactive/AppLinkInfo.java new file mode 100644 index 000000000000..5cce44319e5d --- /dev/null +++ b/media/java/android/media/tv/interactive/AppLinkInfo.java @@ -0,0 +1,234 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media.tv.interactive; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * App link information used by TV interactive app to launch Android apps. + * @hide + */ +public final class AppLinkInfo implements Parcelable { + private @NonNull String mPackageName; + private @NonNull String mClassName; + private @Nullable String mUriScheme; + private @Nullable String mUriHost; + private @Nullable String mUriPrefix; + + + /** + * Creates a new AppLinkInfo. + */ + private AppLinkInfo( + @NonNull String packageName, + @NonNull String className, + @Nullable String uriScheme, + @Nullable String uriHost, + @Nullable String uriPrefix) { + this.mPackageName = packageName; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mPackageName); + this.mClassName = className; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mClassName); + this.mUriScheme = uriScheme; + this.mUriHost = uriHost; + this.mUriPrefix = uriPrefix; + } + + /** + * Gets package name of the App link. + */ + @NonNull + public String getPackageName() { + return mPackageName; + } + + /** + * Gets package class of the App link. + */ + @NonNull + public String getClassName() { + return mClassName; + } + + /** + * Gets URI scheme of the App link. + */ + @Nullable + public String getUriScheme() { + return mUriScheme; + } + + /** + * Gets URI host of the App link. + */ + @Nullable + public String getUriHost() { + return mUriHost; + } + + /** + * Gets URI prefix of the App link. + */ + @Nullable + public String getUriPrefix() { + return mUriPrefix; + } + + @Override + public String toString() { + return "AppLinkInfo { " + + "packageName = " + mPackageName + ", " + + "className = " + mClassName + ", " + + "uriScheme = " + mUriScheme + ", " + + "uriHost = " + mUriHost + ", " + + "uriPrefix = " + mUriPrefix + + " }"; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeString(mPackageName); + dest.writeString(mClassName); + dest.writeString(mUriScheme); + dest.writeString(mUriHost); + dest.writeString(mUriPrefix); + } + + @Override + public int describeContents() { + return 0; + } + + /* package-private */ AppLinkInfo(@NonNull Parcel in) { + String packageName = in.readString(); + String className = in.readString(); + String uriScheme = in.readString(); + String uriHost = in.readString(); + String uriPrefix = in.readString(); + + this.mPackageName = packageName; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mPackageName); + this.mClassName = className; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mClassName); + this.mUriScheme = uriScheme; + this.mUriHost = uriHost; + this.mUriPrefix = uriPrefix; + } + + @NonNull + public static final Parcelable.Creator<AppLinkInfo> CREATOR = + new Parcelable.Creator<AppLinkInfo>() { + @Override + public AppLinkInfo[] newArray(int size) { + return new AppLinkInfo[size]; + } + + @Override + public AppLinkInfo createFromParcel(@NonNull Parcel in) { + return new AppLinkInfo(in); + } + }; + + /** + * A builder for {@link AppLinkInfo} + */ + public static final class Builder { + private @NonNull String mPackageName; + private @NonNull String mClassName; + private @Nullable String mUriScheme; + private @Nullable String mUriHost; + private @Nullable String mUriPrefix; + + /** + * Creates a new Builder. + */ + public Builder( + @NonNull String packageName, + @NonNull String className) { + mPackageName = packageName; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mPackageName); + mClassName = className; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mClassName); + } + + /** + * Sets package name of the App link. + */ + @NonNull + public Builder setPackageName(@NonNull String value) { + mPackageName = value; + return this; + } + + /** + * Sets app name of the App link. + */ + @NonNull + public Builder setClassName(@NonNull String value) { + mClassName = value; + return this; + } + + /** + * Sets URI scheme of the App link. + */ + @NonNull + public Builder setUriScheme(@Nullable String value) { + mUriScheme = value; + return this; + } + + /** + * Sets URI host of the App link. + */ + @NonNull + public Builder setUriHost(@Nullable String value) { + mUriHost = value; + return this; + } + + /** + * Sets URI prefix of the App link. + */ + @NonNull + public Builder setUriPrefix(@Nullable String value) { + mUriPrefix = value; + return this; + } + + /** Builds the instance. This builder should not be touched after calling this! */ + @NonNull + public AppLinkInfo build() { + AppLinkInfo o = new AppLinkInfo( + mPackageName, + mClassName, + mUriScheme, + mUriHost, + mUriPrefix); + return o; + } + } +} diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl index 1a8fc4671ec3..a3e58d16f655 100644 --- a/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl +++ b/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl @@ -34,7 +34,7 @@ oneway interface ITvInteractiveAppClient { void onLayoutSurface(int left, int top, int right, int bottom, int seq); void onBroadcastInfoRequest(in BroadcastInfoRequest request, int seq); void onRemoveBroadcastInfo(int id, int seq); - void onSessionStateChanged(int state, int seq); + void onSessionStateChanged(int state, int err, int seq); void onBiInteractiveAppCreated(in Uri biIAppUri, in String biIAppId, int seq); void onTeletextAppStateChanged(int state, int seq); void onCommandRequest(in String cmdType, in Bundle parameters, int seq); diff --git a/media/java/android/media/tv/interactive/ITvIAppManager.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl index a19a2d2d6135..aaabe342d9f1 100644 --- a/media/java/android/media/tv/interactive/ITvIAppManager.aidl +++ b/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl @@ -20,6 +20,7 @@ import android.graphics.Rect; import android.media.tv.AdResponse; import android.media.tv.BroadcastInfoResponse; import android.media.tv.TvTrackInfo; +import android.media.tv.interactive.AppLinkInfo; import android.media.tv.interactive.ITvInteractiveAppClient; import android.media.tv.interactive.ITvInteractiveAppManagerCallback; import android.media.tv.interactive.TvInteractiveAppInfo; @@ -31,11 +32,11 @@ import android.view.Surface; * Interface to the TV interactive app service. * @hide */ -interface ITvIAppManager { +interface ITvInteractiveAppManager { List<TvInteractiveAppInfo> getTvInteractiveAppServiceList(int userId); void prepare(String tiasId, int type, int userId); - void registerAppLinkInfo(String tiasId, in Bundle info, int userId); - void unregisterAppLinkInfo(String tiasId, in Bundle info, int userId); + void registerAppLinkInfo(String tiasId, in AppLinkInfo info, int userId); + void unregisterAppLinkInfo(String tiasId, in AppLinkInfo info, int userId); void sendAppLinkCommand(String tiasId, in Bundle command, int userId); void startInteractiveApp(in IBinder sessionToken, int userId); void stopInteractiveApp(in IBinder sessionToken, int userId); diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppManagerCallback.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppManagerCallback.aidl index f4510f6c60a3..23be4c64fcc4 100644 --- a/media/java/android/media/tv/interactive/ITvInteractiveAppManagerCallback.aidl +++ b/media/java/android/media/tv/interactive/ITvInteractiveAppManagerCallback.aidl @@ -27,5 +27,5 @@ interface ITvInteractiveAppManagerCallback { void onInteractiveAppServiceRemoved(in String iAppServiceId); void onInteractiveAppServiceUpdated(in String iAppServiceId); void onTvInteractiveAppInfoUpdated(in TvInteractiveAppInfo tvIAppInfo); - void onStateChanged(in String iAppServiceId, int type, int state); + void onStateChanged(in String iAppServiceId, int type, int state, int err); }
\ No newline at end of file diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppService.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppService.aidl index c1e66229670a..b6d518ff7242 100644 --- a/media/java/android/media/tv/interactive/ITvInteractiveAppService.aidl +++ b/media/java/android/media/tv/interactive/ITvInteractiveAppService.aidl @@ -16,6 +16,7 @@ package android.media.tv.interactive; +import android.media.tv.interactive.AppLinkInfo; import android.media.tv.interactive.ITvInteractiveAppServiceCallback; import android.media.tv.interactive.ITvInteractiveAppSessionCallback; import android.os.Bundle; @@ -23,7 +24,7 @@ import android.view.InputChannel; /** * Top-level interface to a TV Interactive App component (implemented in a Service). It's used for - * TvIAppManagerService to communicate with TvIAppService. + * TvInteractiveAppManagerService to communicate with TvInteractiveAppService. * @hide */ oneway interface ITvInteractiveAppService { @@ -32,7 +33,7 @@ oneway interface ITvInteractiveAppService { void createSession(in InputChannel channel, in ITvInteractiveAppSessionCallback callback, in String iAppServiceId, int type); void prepare(int type); - void registerAppLinkInfo(in Bundle info); - void unregisterAppLinkInfo(in Bundle info); + void registerAppLinkInfo(in AppLinkInfo info); + void unregisterAppLinkInfo(in AppLinkInfo info); void sendAppLinkCommand(in Bundle command); }
\ No newline at end of file diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppServiceCallback.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppServiceCallback.aidl index f56d3bd284da..970b94327572 100644 --- a/media/java/android/media/tv/interactive/ITvInteractiveAppServiceCallback.aidl +++ b/media/java/android/media/tv/interactive/ITvInteractiveAppServiceCallback.aidl @@ -17,10 +17,10 @@ package android.media.tv.interactive; /** - * Helper interface for ITvInteractiveAppService to allow the TvIAppService to notify the - * TvIAppManagerService. + * Helper interface for ITvInteractiveAppService to allow the TvInteractiveAppService to notify the + * TvInteractiveAppManagerService. * @hide */ oneway interface ITvInteractiveAppServiceCallback { - void onStateChanged(int type, int state); + void onStateChanged(int type, int state, int error); }
\ No newline at end of file diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl index c270424b4067..385f0d4f766a 100644 --- a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl +++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl @@ -24,7 +24,7 @@ import android.net.Uri; import android.os.Bundle; /** - * Helper interface for ITvInteractiveAppSession to allow TvIAppService to notify the + * Helper interface for ITvInteractiveAppSession to allow TvInteractiveAppService to notify the * system service when there is a related event. * @hide */ @@ -33,7 +33,7 @@ oneway interface ITvInteractiveAppSessionCallback { void onLayoutSurface(int left, int top, int right, int bottom); void onBroadcastInfoRequest(in BroadcastInfoRequest request); void onRemoveBroadcastInfo(int id); - void onSessionStateChanged(int state); + void onSessionStateChanged(int state, int err); void onBiInteractiveAppCreated(in Uri biIAppUri, in String biIAppId); void onTeletextAppStateChanged(int state); void onCommandRequest(in String cmdType, in Bundle parameters); diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppInfo.java b/media/java/android/media/tv/interactive/TvInteractiveAppInfo.java index 2f96552f6f32..e1f535c93d19 100644 --- a/media/java/android/media/tv/interactive/TvInteractiveAppInfo.java +++ b/media/java/android/media/tv/interactive/TvInteractiveAppInfo.java @@ -44,7 +44,6 @@ import java.util.List; /** * This class is used to specify meta information of a TV interactive app. - * @hide */ public final class TvInteractiveAppInfo implements Parcelable { private static final boolean DEBUG = false; @@ -59,7 +58,7 @@ public final class TvInteractiveAppInfo implements Parcelable { INTERACTIVE_APP_TYPE_ATSC, INTERACTIVE_APP_TYPE_GINGA, }) - @interface InteractiveAppType {} + public @interface InteractiveAppType {} /** HbbTV interactive app type */ public static final int INTERACTIVE_APP_TYPE_HBBTV = 0x1; @@ -77,7 +76,7 @@ public final class TvInteractiveAppInfo implements Parcelable { throw new IllegalArgumentException("context cannot be null."); } Intent intent = - new Intent(TvIAppService.SERVICE_INTERFACE).setComponent(component); + new Intent(TvInteractiveAppService.SERVICE_INTERFACE).setComponent(component); ResolveInfo resolveInfo = context.getPackageManager().resolveService(intent, PackageManager.GET_SERVICES | PackageManager.GET_META_DATA); if (resolveInfo == null) { @@ -171,10 +170,10 @@ public final class TvInteractiveAppInfo implements Parcelable { ServiceInfo si = resolveInfo.serviceInfo; PackageManager pm = context.getPackageManager(); try (XmlResourceParser parser = - si.loadXmlMetaData(pm, TvIAppService.SERVICE_META_DATA)) { + si.loadXmlMetaData(pm, TvInteractiveAppService.SERVICE_META_DATA)) { if (parser == null) { throw new IllegalStateException( - "No " + TvIAppService.SERVICE_META_DATA + "No " + TvInteractiveAppService.SERVICE_META_DATA + " meta-data found for " + si.name); } @@ -194,9 +193,9 @@ public final class TvInteractiveAppInfo implements Parcelable { } TypedArray sa = res.obtainAttributes(attrs, - com.android.internal.R.styleable.TvIAppService); + com.android.internal.R.styleable.TvInteractiveAppService); CharSequence[] textArr = sa.getTextArray( - com.android.internal.R.styleable.TvIAppService_supportedTypes); + com.android.internal.R.styleable.TvInteractiveAppService_supportedTypes); for (CharSequence cs : textArr) { types.add(cs.toString().toLowerCase()); } diff --git a/media/java/android/media/tv/interactive/TvIAppManager.java b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java index f819438944f4..39be501a072d 100755 --- a/media/java/android/media/tv/interactive/TvIAppManager.java +++ b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java @@ -16,6 +16,7 @@ package android.media.tv.interactive; +import android.annotation.CallbackExecutor; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -52,45 +53,128 @@ import java.lang.annotation.RetentionPolicy; import java.util.Iterator; import java.util.LinkedList; import java.util.List; +import java.util.concurrent.Executor; /** * Central system API to the overall TV interactive application framework (TIAF) architecture, which * arbitrates interaction between applications and interactive apps. */ -@SystemService(Context.TV_IAPP_SERVICE) -public final class TvIAppManager { +@SystemService(Context.TV_INTERACTIVE_APP_SERVICE) +public final class TvInteractiveAppManager { // TODO: cleanup and unhide public APIs - private static final String TAG = "TvIAppManager"; + private static final String TAG = "TvInteractiveAppManager"; /** @hide */ @Retention(RetentionPolicy.SOURCE) - @IntDef(flag = false, prefix = "TV_INTERACTIVE_APP_RTE_STATE_", value = { - TV_INTERACTIVE_APP_RTE_STATE_UNREALIZED, - TV_INTERACTIVE_APP_RTE_STATE_PREPARING, - TV_INTERACTIVE_APP_RTE_STATE_READY, - TV_INTERACTIVE_APP_RTE_STATE_ERROR}) - public @interface TvInteractiveAppRteState {} + @IntDef(flag = false, prefix = "SERVICE_STATE_", value = { + SERVICE_STATE_UNREALIZED, + SERVICE_STATE_PREPARING, + SERVICE_STATE_READY, + SERVICE_STATE_ERROR}) + public @interface ServiceState {} /** - * Unrealized state of interactive app RTE. + * Unrealized state of interactive app service. * @hide */ - public static final int TV_INTERACTIVE_APP_RTE_STATE_UNREALIZED = 1; + public static final int SERVICE_STATE_UNREALIZED = 1; /** - * Preparing state of interactive app RTE. + * Preparing state of interactive app service. * @hide */ - public static final int TV_INTERACTIVE_APP_RTE_STATE_PREPARING = 2; + public static final int SERVICE_STATE_PREPARING = 2; /** - * Ready state of interactive app RTE. + * Ready state of interactive app service. * @hide */ - public static final int TV_INTERACTIVE_APP_RTE_STATE_READY = 3; + public static final int SERVICE_STATE_READY = 3; /** - * Error state of interactive app RTE. + * Error state of interactive app service. * @hide */ - public static final int TV_INTERACTIVE_APP_RTE_STATE_ERROR = 4; + public static final int SERVICE_STATE_ERROR = 4; + + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = false, prefix = "INTERACTIVE_APP_STATE_", value = { + INTERACTIVE_APP_STATE_STOPPED, + INTERACTIVE_APP_STATE_RUNNING, + INTERACTIVE_APP_STATE_ERROR}) + public @interface InteractiveAppState {} + + /** + * Stopped (or not started) state of interactive application. + * @hide + */ + public static final int INTERACTIVE_APP_STATE_STOPPED = 1; + /** + * Running state of interactive application. + * @hide + */ + public static final int INTERACTIVE_APP_STATE_RUNNING = 2; + /** + * Error state of interactive application. + * @hide + */ + public static final int INTERACTIVE_APP_STATE_ERROR = 3; + + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = false, prefix = "ERROR_", value = { + ERROR_NONE, + ERROR_UNKNOWN, + ERROR_NOT_SUPPORTED, + ERROR_WEAK_SIGNAL, + ERROR_RESOURCE_UNAVAILABLE, + ERROR_BLOCKED, + ERROR_ENCRYPTED, + ERROR_UNKNOWN_CHANNEL, + }) + public @interface ErrorCode {} + + /** + * No error. + * @hide + */ + public static final int ERROR_NONE = 0; + /** + * Unknown error code. + * @hide + */ + public static final int ERROR_UNKNOWN = 1; + /** + * Error code for an unsupported channel. + * @hide + */ + public static final int ERROR_NOT_SUPPORTED = 2; + /** + * Error code for weak signal. + * @hide + */ + public static final int ERROR_WEAK_SIGNAL = 3; + /** + * Error code when resource (e.g. tuner) is unavailable. + * @hide + */ + public static final int ERROR_RESOURCE_UNAVAILABLE = 4; + /** + * Error code for blocked contents. + * @hide + */ + public static final int ERROR_BLOCKED = 5; + /** + * Error code when the key or module is missing for the encrypted channel. + * @hide + */ + public static final int ERROR_ENCRYPTED = 6; + /** + * Error code when the current channel is an unknown channel. + * @hide + */ + public static final int ERROR_UNKNOWN_CHANNEL = 7; + /** @hide */ @Retention(RetentionPolicy.SOURCE) @@ -190,7 +274,7 @@ public final class TvIAppManager { */ public static final String KEY_BACK_URI = "back_uri"; - private final ITvIAppManager mService; + private final ITvInteractiveAppManager mService; private final int mUserId; // A mapping from the sequence number of a session to its SessionCallbackRecord. @@ -209,7 +293,7 @@ public final class TvIAppManager { private final ITvInteractiveAppClient mClient; /** @hide */ - public TvIAppManager(ITvIAppManager service, int userId) { + public TvInteractiveAppManager(ITvInteractiveAppManager service, int userId) { mService = service; mUserId = userId; mClient = new ITvInteractiveAppClient.Stub() { @@ -285,7 +369,7 @@ public final class TvIAppManager { @Override public void onCommandRequest( - @TvIAppService.InteractiveAppServiceCommandType String cmdType, + @TvInteractiveAppService.InteractiveAppServiceCommandType String cmdType, Bundle parameters, int seq) { synchronized (mSessionCallbackRecordMap) { @@ -383,14 +467,14 @@ public final class TvIAppManager { } @Override - public void onSessionStateChanged(int state, int seq) { + public void onSessionStateChanged(int state, int err, int seq) { synchronized (mSessionCallbackRecordMap) { SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); if (record == null) { Log.e(TAG, "Callback not found for seq " + seq); return; } - record.postSessionStateChanged(state); + record.postSessionStateChanged(state, err); } } @@ -458,10 +542,10 @@ public final class TvIAppManager { } @Override - public void onStateChanged(String iAppServiceId, int type, int state) { + public void onStateChanged(String iAppServiceId, int type, int state, int err) { synchronized (mLock) { for (TvInteractiveAppCallbackRecord record : mCallbackRecords) { - record.postStateChanged(iAppServiceId, type, state); + record.postStateChanged(iAppServiceId, type, state, err); } } } @@ -484,9 +568,10 @@ public final class TvIAppManager { * This is called when a TV Interactive App service is added to the system. * * <p>Normally it happens when the user installs a new TV Interactive App service package - * that implements {@link TvIAppService} interface. + * that implements {@link TvInteractiveAppService} interface. * * @param iAppServiceId The ID of the TV Interactive App service. + * @hide */ public void onInteractiveAppServiceAdded(@NonNull String iAppServiceId) { } @@ -498,6 +583,7 @@ public final class TvIAppManager { * App service package. * * @param iAppServiceId The ID of the TV Interactive App service. + * @hide */ public void onInteractiveAppServiceRemoved(@NonNull String iAppServiceId) { } @@ -509,6 +595,7 @@ public final class TvIAppManager { * re-installed or a newer version of the package exists becomes available/unavailable. * * @param iAppServiceId The ID of the TV Interactive App service. + * @hide */ public void onInteractiveAppServiceUpdated(@NonNull String iAppServiceId) { } @@ -524,26 +611,34 @@ public final class TvIAppManager { * * @param iAppInfo The <code>TvInteractiveAppInfo</code> object that contains new * information. + * @hide */ public void onTvInteractiveAppInfoUpdated(@NonNull TvInteractiveAppInfo iAppInfo) { } /** * This is called when the state of the interactive app service is changed. - * @hide + * + * @param type the interactive app type + * @param state the current state of the service of the given type + * @param err the error code for error state. {@link #ERROR_NONE} is used when the state is + * not {@link #SERVICE_STATE_ERROR}. */ public void onTvInteractiveAppServiceStateChanged( - @NonNull String iAppServiceId, int type, @TvInteractiveAppRteState int state) { + @NonNull String iAppServiceId, + @TvInteractiveAppInfo.InteractiveAppType int type, + @ServiceState int state, + @ErrorCode int err) { } } private static final class TvInteractiveAppCallbackRecord { private final TvInteractiveAppCallback mCallback; - private final Handler mHandler; + private final Executor mExecutor; - TvInteractiveAppCallbackRecord(TvInteractiveAppCallback callback, Handler handler) { + TvInteractiveAppCallbackRecord(TvInteractiveAppCallback callback, Executor executor) { mCallback = callback; - mHandler = handler; + mExecutor = executor; } public TvInteractiveAppCallback getCallback() { @@ -551,7 +646,7 @@ public final class TvIAppManager { } public void postInteractiveAppServiceAdded(final String iAppServiceId) { - mHandler.post(new Runnable() { + mExecutor.execute(new Runnable() { @Override public void run() { mCallback.onInteractiveAppServiceAdded(iAppServiceId); @@ -560,7 +655,7 @@ public final class TvIAppManager { } public void postInteractiveAppServiceRemoved(final String iAppServiceId) { - mHandler.post(new Runnable() { + mExecutor.execute(new Runnable() { @Override public void run() { mCallback.onInteractiveAppServiceRemoved(iAppServiceId); @@ -569,7 +664,7 @@ public final class TvIAppManager { } public void postInteractiveAppServiceUpdated(final String iAppServiceId) { - mHandler.post(new Runnable() { + mExecutor.execute(new Runnable() { @Override public void run() { mCallback.onInteractiveAppServiceUpdated(iAppServiceId); @@ -578,7 +673,7 @@ public final class TvIAppManager { } public void postTvInteractiveAppInfoUpdated(final TvInteractiveAppInfo iAppInfo) { - mHandler.post(new Runnable() { + mExecutor.execute(new Runnable() { @Override public void run() { mCallback.onTvInteractiveAppInfoUpdated(iAppInfo); @@ -586,11 +681,12 @@ public final class TvIAppManager { }); } - public void postStateChanged(String iAppServiceId, int type, int state) { - mHandler.post(new Runnable() { + public void postStateChanged(String iAppServiceId, int type, int state, int err) { + mExecutor.execute(new Runnable() { @Override public void run() { - mCallback.onTvInteractiveAppServiceStateChanged(iAppServiceId, type, state); + mCallback.onTvInteractiveAppServiceStateChanged( + iAppServiceId, type, state, err); } }); } @@ -635,7 +731,6 @@ public final class TvIAppManager { * * @return List of {@link TvInteractiveAppInfo} for each TV Interactive App service that * describes its meta information. - * @hide */ @NonNull public List<TvInteractiveAppInfo> getTvInteractiveAppServiceList() { @@ -662,7 +757,8 @@ public final class TvIAppManager { * Registers app link info. * @hide */ - public void registerAppLinkInfo(@NonNull String tvIAppServiceId, @NonNull Bundle appLinkInfo) { + public void registerAppLinkInfo( + @NonNull String tvIAppServiceId, @NonNull AppLinkInfo appLinkInfo) { try { mService.registerAppLinkInfo(tvIAppServiceId, appLinkInfo, mUserId); } catch (RemoteException e) { @@ -675,7 +771,7 @@ public final class TvIAppManager { * @hide */ public void unregisterAppLinkInfo( - @NonNull String tvIAppServiceId, @NonNull Bundle appLinkInfo) { + @NonNull String tvIAppServiceId, @NonNull AppLinkInfo appLinkInfo) { try { mService.unregisterAppLinkInfo(tvIAppServiceId, appLinkInfo, mUserId); } catch (RemoteException e) { @@ -699,15 +795,16 @@ public final class TvIAppManager { * Registers a {@link TvInteractiveAppCallback}. * * @param callback A callback used to monitor status of the TV Interactive App services. - * @param handler A {@link Handler} that the status change will be delivered to. + * @param executor A {@link Executor} that the status change will be delivered to. * @hide */ public void registerCallback( - @NonNull TvInteractiveAppCallback callback, @NonNull Handler handler) { + @NonNull TvInteractiveAppCallback callback, + @CallbackExecutor @NonNull Executor executor) { Preconditions.checkNotNull(callback); - Preconditions.checkNotNull(handler); + Preconditions.checkNotNull(executor); synchronized (mLock) { - mCallbackRecords.add(new TvInteractiveAppCallbackRecord(callback, handler)); + mCallbackRecords.add(new TvInteractiveAppCallbackRecord(callback, executor)); } } @@ -742,7 +839,7 @@ public final class TvIAppManager { private static final long INPUT_SESSION_NOT_RESPONDING_TIMEOUT = 2500; - private final ITvIAppManager mService; + private final ITvInteractiveAppManager mService; private final int mUserId; private final int mSeq; private final SparseArray<SessionCallbackRecord> mSessionCallbackRecordMap; @@ -759,7 +856,7 @@ public final class TvIAppManager { private TvInputEventSender mSender; private InputChannel mInputChannel; - private Session(IBinder token, InputChannel channel, ITvIAppManager service, + private Session(IBinder token, InputChannel channel, ITvInteractiveAppManager service, int userId, int seq, SparseArray<SessionCallbackRecord> sessionCallbackRecordMap) { mToken = token; mInputChannel = channel; @@ -1142,7 +1239,7 @@ public final class TvIAppManager { } /** - * Notifies IAPP session when video is available. + * Notifies Interactive APP session when video is available. */ public void notifyVideoAvailable() { if (mToken == null) { @@ -1157,7 +1254,7 @@ public final class TvIAppManager { } /** - * Notifies IAPP session when video is unavailable. + * Notifies Interactive APP session when video is unavailable. */ public void notifyVideoUnavailable(int reason) { if (mToken == null) { @@ -1172,7 +1269,7 @@ public final class TvIAppManager { } /** - * Notifies IAPP session when content is allowed. + * Notifies Interactive APP session when content is allowed. */ public void notifyContentAllowed() { if (mToken == null) { @@ -1187,7 +1284,7 @@ public final class TvIAppManager { } /** - * Notifies IAPP session when content is blocked. + * Notifies Interactive APP session when content is blocked. */ public void notifyContentBlocked(TvContentRating rating) { if (mToken == null) { @@ -1478,7 +1575,7 @@ public final class TvIAppManager { } void postCommandRequest( - final @TvIAppService.InteractiveAppServiceCommandType String cmdType, + final @TvInteractiveAppService.InteractiveAppServiceCommandType String cmdType, final Bundle parameters) { mHandler.post(new Runnable() { @Override @@ -1553,11 +1650,11 @@ public final class TvIAppManager { }); } - void postSessionStateChanged(int state) { + void postSessionStateChanged(int state, int err) { mHandler.post(new Runnable() { @Override public void run() { - mSessionCallback.onSessionStateChanged(mSession, state); + mSessionCallback.onSessionStateChanged(mSession, state, err); } }); } @@ -1587,28 +1684,28 @@ public final class TvIAppManager { */ public abstract static class SessionCallback { /** - * This is called after {@link TvIAppManager#createSession} has been processed. + * This is called after {@link TvInteractiveAppManager#createSession} has been processed. * - * @param session A {@link TvIAppManager.Session} instance created. This can be + * @param session A {@link TvInteractiveAppManager.Session} instance created. This can be * {@code null} if the creation request failed. */ public void onSessionCreated(@Nullable Session session) { } /** - * This is called when {@link TvIAppManager.Session} is released. + * This is called when {@link TvInteractiveAppManager.Session} is released. * This typically happens when the process hosting the session has crashed or been killed. * - * @param session the {@link TvIAppManager.Session} instance released. + * @param session the {@link TvInteractiveAppManager.Session} instance released. */ public void onSessionReleased(@NonNull Session session) { } /** - * This is called when {@link TvIAppService.Session#layoutSurface} is called to + * This is called when {@link TvInteractiveAppService.Session#layoutSurface} is called to * change the layout of surface. * - * @param session A {@link TvIAppManager.Session} associated with this callback. + * @param session A {@link TvInteractiveAppManager.Session} associated with this callback. * @param left Left position. * @param top Top position. * @param right Right position. @@ -1618,85 +1715,90 @@ public final class TvIAppManager { } /** - * This is called when {@link TvIAppService.Session#requestCommand} is called. + * This is called when {@link TvInteractiveAppService.Session#requestCommand} is called. * - * @param session A {@link TvIAppManager.Session} associated with this callback. + * @param session A {@link TvInteractiveAppManager.Session} associated with this callback. * @param cmdType type of the command. * @param parameters parameters of the command. */ public void onCommandRequest( Session session, - @TvIAppService.InteractiveAppServiceCommandType String cmdType, + @TvInteractiveAppService.InteractiveAppServiceCommandType String cmdType, Bundle parameters) { } /** - * This is called when {@link TvIAppService.Session#SetVideoBounds} is called. + * This is called when {@link TvInteractiveAppService.Session#SetVideoBounds} is called. * - * @param session A {@link TvIAppManager.Session} associated with this callback. + * @param session A {@link TvInteractiveAppManager.Session} associated with this callback. */ public void onSetVideoBounds(Session session, Rect rect) { } /** - * This is called when {@link TvIAppService.Session#RequestCurrentChannelUri} is + * This is called when {@link TvInteractiveAppService.Session#RequestCurrentChannelUri} is * called. * - * @param session A {@link TvIAppManager.Session} associated with this callback. + * @param session A {@link TvInteractiveAppManager.Session} associated with this callback. */ public void onRequestCurrentChannelUri(Session session) { } /** - * This is called when {@link TvIAppService.Session#RequestCurrentChannelLcn} is + * This is called when {@link TvInteractiveAppService.Session#RequestCurrentChannelLcn} is * called. * - * @param session A {@link TvIAppManager.Session} associated with this callback. + * @param session A {@link TvInteractiveAppManager.Session} associated with this callback. */ public void onRequestCurrentChannelLcn(Session session) { } /** - * This is called when {@link TvIAppService.Session#RequestStreamVolume} is + * This is called when {@link TvInteractiveAppService.Session#RequestStreamVolume} is * called. * - * @param session A {@link TvIAppManager.Session} associated with this callback. + * @param session A {@link TvInteractiveAppManager.Session} associated with this callback. */ public void onRequestStreamVolume(Session session) { } /** - * This is called when {@link TvIAppService.Session#RequestTrackInfoList} is + * This is called when {@link TvInteractiveAppService.Session#RequestTrackInfoList} is * called. * - * @param session A {@link TvIAppManager.Session} associated with this callback. + * @param session A {@link TvInteractiveAppManager.Session} associated with this callback. */ public void onRequestTrackInfoList(Session session) { } /** - * This is called when {@link TvIAppService.Session#RequestCurrentTvInputId} is called. + * This is called when {@link TvInteractiveAppService.Session#RequestCurrentTvInputId} is + * called. * - * @param session A {@link TvIAppManager.Session} associated with this callback. + * @param session A {@link TvInteractiveAppService.Session} associated with this callback. * @hide */ public void onRequestCurrentTvInputId(Session session) { } /** - * This is called when {@link TvIAppService.Session#notifySessionStateChanged} is called. + * This is called when {@link TvInteractiveAppService.Session#notifySessionStateChanged} is + * called. * - * @param session A {@link TvIAppManager.Session} associated with this callback. + * @param session A {@link TvInteractiveAppManager.Session} associated with this callback. * @param state the current state. */ - public void onSessionStateChanged(Session session, int state) { + public void onSessionStateChanged( + Session session, + @InteractiveAppState int state, + @ErrorCode int err) { } /** - * This is called when {@link TvIAppService.Session#notifyBiInteractiveAppCreated} + * This is called when {@link TvInteractiveAppService.Session#notifyBiInteractiveAppCreated} * is called. * - * @param session A {@link TvIAppManager.Session} associated with this callback. + * @param session A {@link TvInteractiveAppManager.Session} associated with this callback. * @param biIAppUri URI associated this BI interactive app. This is the same URI in * {@link Session#createBiInteractiveApp(Uri, Bundle)} * @param biIAppId BI interactive app ID, which can be used to destroy the BI interactive @@ -1709,11 +1811,11 @@ public final class TvIAppManager { * This is called when {@link TvIAppService.Session#notifyTeletextAppStateChanged} is * called. * - * @param session A {@link TvIAppManager.Session} associated with this callback. + * @param session A {@link TvInteractiveAppManager.Session} associated with this callback. * @param state the current state. */ public void onTeletextAppStateChanged( - Session session, @TvIAppManager.TeletextAppState int state) { + Session session, @TvInteractiveAppManager.TeletextAppState int state) { } } } diff --git a/media/java/android/media/tv/interactive/TvIAppService.java b/media/java/android/media/tv/interactive/TvInteractiveAppService.java index c0ec76b10655..d599d0a60995 100755 --- a/media/java/android/media/tv/interactive/TvIAppService.java +++ b/media/java/android/media/tv/interactive/TvInteractiveAppService.java @@ -65,11 +65,11 @@ import java.util.ArrayList; import java.util.List; /** - * The TvIAppService class represents a TV interactive applications RTE. + * The TvInteractiveAppService class represents a TV interactive applications RTE. */ -public abstract class TvIAppService extends Service { +public abstract class TvInteractiveAppService extends Service { private static final boolean DEBUG = false; - private static final String TAG = "TvIAppService"; + private static final String TAG = "TvInteractiveAppService"; private static final int DETACH_MEDIA_VIEW_TIMEOUT_MS = 5000; @@ -83,12 +83,12 @@ public abstract class TvIAppService extends Service { * cannot abuse it. */ public static final String SERVICE_INTERFACE = - "android.media.tv.interactive.TvIAppService"; + "android.media.tv.interactive.TvInteractiveAppService"; /** - * Name under which a TvIAppService component publishes information about itself. This + * Name under which a TvInteractiveAppService component publishes information about itself. This * meta-data must reference an XML resource containing an - * <code><{@link android.R.styleable#TvIAppService tv-interactive-app}></code> + * <code><{@link android.R.styleable#TvInteractiveAppService tv-interactive-app}></code> * tag. */ public static final String SERVICE_META_DATA = "android.media.tv.interactive.app"; @@ -131,6 +131,13 @@ public abstract class TvIAppService extends Service { /** @hide */ public static final String COMMAND_PARAMETER_KEY_TRACK_SELECT_MODE = "command_track_select_mode"; + /** + * Command to quiet channel change. No channel banner or channel info is shown. + * <p>Refer to HbbTV Spec 2.0.4 chapter A.2.4.3. + * @hide + */ + public static final String COMMAND_PARAMETER_KEY_CHANGE_CHANNEL_QUIETLY = + "command_change_channel_quietly"; private final Handler mServiceHandler = new ServiceHandler(); private final RemoteCallbackList<ITvInteractiveAppServiceCallback> mCallbacks = @@ -175,12 +182,12 @@ public abstract class TvIAppService extends Service { } @Override - public void registerAppLinkInfo(Bundle appLinkInfo) { + public void registerAppLinkInfo(AppLinkInfo appLinkInfo) { onRegisterAppLinkInfo(appLinkInfo); } @Override - public void unregisterAppLinkInfo(Bundle appLinkInfo) { + public void unregisterAppLinkInfo(AppLinkInfo appLinkInfo) { onUnregisterAppLinkInfo(appLinkInfo); } @@ -196,15 +203,14 @@ public abstract class TvIAppService extends Service { * Prepares TV Interactive App service for the given type. * @hide */ - public void onPrepare(int type) { - // TODO: make it abstract when unhide + public void onPrepare(@TvInteractiveAppInfo.InteractiveAppType int type) { } /** * Registers App link info. * @hide */ - public void onRegisterAppLinkInfo(Bundle appLinkInfo) { + public void onRegisterAppLinkInfo(AppLinkInfo appLinkInfo) { // TODO: make it abstract when unhide } @@ -212,7 +218,7 @@ public abstract class TvIAppService extends Service { * Unregisters App link info. * @hide */ - public void onUnregisterAppLinkInfo(Bundle appLinkInfo) { + public void onUnregisterAppLinkInfo(AppLinkInfo appLinkInfo) { // TODO: make it abstract when unhide } @@ -236,20 +242,32 @@ public abstract class TvIAppService extends Service { * @hide */ @Nullable - public Session onCreateSession(@NonNull String iAppServiceId, int type) { - // TODO: make it abstract when unhide + public Session onCreateSession( + @NonNull String iAppServiceId, + @TvInteractiveAppInfo.InteractiveAppType int type) { return null; } /** - * Notifies the system when the state of the interactive app has been changed. - * @param state the current state + * Notifies the system when the state of the interactive app RTE has been changed. + * + * @param type the interactive app type + * @param state the current state of the service of the given type + * @param error the error code for error state. {@link TvInteractiveAppManager#ERROR_NONE} is + * used when the state is not + * {@link TvInteractiveAppManager#SERVICE_STATE_ERROR}. * @hide */ public final void notifyStateChanged( - int type, @TvIAppManager.TvInteractiveAppRteState int state) { - mServiceHandler.obtainMessage(ServiceHandler.DO_NOTIFY_RTE_STATE_CHANGED, - type, state).sendToTarget(); + @TvInteractiveAppInfo.InteractiveAppType int type, + @TvInteractiveAppManager.ServiceState int state, + @TvInteractiveAppManager.ErrorCode int error) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = type; + args.arg2 = state; + args.arg3 = error; + mServiceHandler + .obtainMessage(ServiceHandler.DO_NOTIFY_RTE_STATE_CHANGED, args).sendToTarget(); } /** @@ -298,6 +316,7 @@ public abstract class TvIAppService extends Service { * * @param enable {@code true} if you want to enable the media view. {@code false} * otherwise. + * @hide */ public void setMediaViewEnabled(final boolean enable) { mHandler.post(new Runnable() { @@ -319,15 +338,13 @@ public abstract class TvIAppService extends Service { } /** - * Starts TvIAppService session. - * @hide + * Starts TvInteractiveAppService session. */ public void onStartInteractiveApp() { } /** - * Stops TvIAppService session. - * @hide + * Stops TvInteractiveAppService session. */ public void onStopInteractiveApp() { } @@ -364,6 +381,7 @@ public abstract class TvIAppService extends Service { /** * To toggle Digital Teletext Application if there is one in AIT app list. * @param enable + * @hide */ public void onSetTeletextAppEnabled(boolean enable) { } @@ -438,6 +456,7 @@ public abstract class TvIAppService extends Service { * * @param width The width of the media view. * @param height The height of the media view. + * @hide */ public void onMediaViewSizeChanged(int width, int height) { } @@ -447,6 +466,7 @@ public abstract class TvIAppService extends Service { * implementation can override this method and return its own view. * * @return a view attached to the media window + * @hide */ @Nullable public View onCreateMediaView() { @@ -454,7 +474,7 @@ public abstract class TvIAppService extends Service { } /** - * Releases TvIAppService session. + * Releases TvInteractiveAppService session. * @hide */ public void onRelease() { @@ -620,6 +640,7 @@ public abstract class TvIAppService extends Service { /** * Requests broadcast related information from the related TV input. * @param request the request for broadcast info + * @hide */ public void requestBroadcastInfo(@NonNull final BroadcastInfoRequest request) { executeOrPostRunnableOnMainThread(new Runnable() { @@ -644,6 +665,7 @@ public abstract class TvIAppService extends Service { /** * Remove broadcast information request from the related TV input. * @param requestId the ID of the request + * @hide */ public void removeBroadcastInfo(final int requestId) { executeOrPostRunnableOnMainThread(new Runnable() { @@ -669,6 +691,7 @@ public abstract class TvIAppService extends Service { * requests a specific command to be processed by the related TV input. * @param cmdType type of the specific command * @param parameters parameters of the specific command + * @hide */ public void requestCommand( @InteractiveAppServiceCommandType String cmdType, Bundle parameters) { @@ -693,6 +716,7 @@ public abstract class TvIAppService extends Service { /** * Sets broadcast video bounds. + * @hide */ public void setVideoBounds(Rect rect) { executeOrPostRunnableOnMainThread(new Runnable() { @@ -715,6 +739,7 @@ public abstract class TvIAppService extends Service { /** * Requests the URI of the current channel. + * @hide */ public void requestCurrentChannelUri() { executeOrPostRunnableOnMainThread(new Runnable() { @@ -737,6 +762,7 @@ public abstract class TvIAppService extends Service { /** * Requests the logic channel number (LCN) of the current channel. + * @hide */ public void requestCurrentChannelLcn() { executeOrPostRunnableOnMainThread(new Runnable() { @@ -759,6 +785,7 @@ public abstract class TvIAppService extends Service { /** * Requests stream volume. + * @hide */ public void requestStreamVolume() { executeOrPostRunnableOnMainThread(new Runnable() { @@ -781,6 +808,7 @@ public abstract class TvIAppService extends Service { /** * Requests the list of {@link TvTrackInfo}. + * @hide */ public void requestTrackInfoList() { executeOrPostRunnableOnMainThread(new Runnable() { @@ -829,6 +857,7 @@ public abstract class TvIAppService extends Service { /** * requests an advertisement request to be processed by the related TV input. * @param request advertisement request + * @hide */ public void requestAd(@NonNull final AdRequest request) { executeOrPostRunnableOnMainThread(new Runnable() { @@ -987,10 +1016,15 @@ public abstract class TvIAppService extends Service { /** * Notifies when the session state is changed. - * @param state the current state. + * + * @param state the current session state. + * @param err the error code for error state. {@link TvInteractiveAppManager#ERROR_NONE} is + * used when the state is not + * {@link TvInteractiveAppManager#INTERACTIVE_APP_STATE_ERROR}. */ public void notifySessionStateChanged( - @TvIAppManager.TvInteractiveAppRteState int state) { + @TvInteractiveAppManager.InteractiveAppState int state, + @TvInteractiveAppManager.ErrorCode int err) { executeOrPostRunnableOnMainThread(new Runnable() { @MainThread @Override @@ -998,10 +1032,10 @@ public abstract class TvIAppService extends Service { try { if (DEBUG) { Log.d(TAG, "notifySessionStateChanged (state=" - + state + ")"); + + state + "; err=" + err + ")"); } if (mSessionCallback != null) { - mSessionCallback.onSessionStateChanged(state); + mSessionCallback.onSessionStateChanged(state, err); } } catch (RemoteException e) { Log.w(TAG, "error in notifySessionStateChanged", e); @@ -1039,8 +1073,10 @@ public abstract class TvIAppService extends Service { /** * Notifies when the digital teletext app state is changed. * @param state the current state. + * @hide */ - public final void notifyTeletextAppStateChanged(@TvIAppManager.TeletextAppState int state) { + public final void notifyTeletextAppStateChanged( + @TvInteractiveAppManager.TeletextAppState int state) { executeOrPostRunnableOnMainThread(new Runnable() { @MainThread @Override @@ -1068,7 +1104,7 @@ public abstract class TvIAppService extends Service { if (event instanceof KeyEvent) { KeyEvent keyEvent = (KeyEvent) event; if (keyEvent.dispatch(this, mDispatcherState, this)) { - return TvIAppManager.Session.DISPATCH_HANDLED; + return TvInteractiveAppManager.Session.DISPATCH_HANDLED; } // TODO: special handlings of navigation keys and media keys @@ -1077,20 +1113,20 @@ public abstract class TvIAppService extends Service { final int source = motionEvent.getSource(); if (motionEvent.isTouchEvent()) { if (onTouchEvent(motionEvent)) { - return TvIAppManager.Session.DISPATCH_HANDLED; + return TvInteractiveAppManager.Session.DISPATCH_HANDLED; } } else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) { if (onTrackballEvent(motionEvent)) { - return TvIAppManager.Session.DISPATCH_HANDLED; + return TvInteractiveAppManager.Session.DISPATCH_HANDLED; } } else { if (onGenericMotionEvent(motionEvent)) { - return TvIAppManager.Session.DISPATCH_HANDLED; + return TvInteractiveAppManager.Session.DISPATCH_HANDLED; } } } // TODO: handle overlay view - return TvIAppManager.Session.DISPATCH_NOT_HANDLED; + return TvInteractiveAppManager.Session.DISPATCH_NOT_HANDLED; } private void initialize(ITvInteractiveAppSessionCallback callback) { @@ -1443,9 +1479,9 @@ public abstract class TvIAppService extends Service { } int handled = mSessionImpl.dispatchInputEvent(event, this); - if (handled != TvIAppManager.Session.DISPATCH_IN_PROGRESS) { + if (handled != TvInteractiveAppManager.Session.DISPATCH_IN_PROGRESS) { finishInputEvent( - event, handled == TvIAppManager.Session.DISPATCH_HANDLED); + event, handled == TvInteractiveAppManager.Session.DISPATCH_HANDLED); } } } @@ -1457,11 +1493,11 @@ public abstract class TvIAppService extends Service { private static final int DO_NOTIFY_SESSION_CREATED = 2; private static final int DO_NOTIFY_RTE_STATE_CHANGED = 3; - private void broadcastRteStateChanged(int type, int state) { + private void broadcastRteStateChanged(int type, int state, int error) { int n = mCallbacks.beginBroadcast(); for (int i = 0; i < n; ++i) { try { - mCallbacks.getBroadcastItem(i).onStateChanged(type, state); + mCallbacks.getBroadcastItem(i).onStateChanged(type, state, error); } catch (RemoteException e) { Log.e(TAG, "error in broadcastRteStateChanged", e); } @@ -1491,7 +1527,7 @@ public abstract class TvIAppService extends Service { return; } ITvInteractiveAppSession stub = new ITvInteractiveAppSessionWrapper( - android.media.tv.interactive.TvIAppService.this, sessionImpl, channel); + TvInteractiveAppService.this, sessionImpl, channel); SomeArgs someArgs = SomeArgs.obtain(); someArgs.arg1 = sessionImpl; @@ -1519,9 +1555,11 @@ public abstract class TvIAppService extends Service { return; } case DO_NOTIFY_RTE_STATE_CHANGED: { - int type = msg.arg1; - int state = msg.arg2; - broadcastRteStateChanged(type, state); + SomeArgs args = (SomeArgs) msg.obj; + int type = (int) args.arg1; + int state = (int) args.arg2; + int error = (int) args.arg3; + broadcastRteStateChanged(type, state, error); return; } default: { diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppView.java b/media/java/android/media/tv/interactive/TvInteractiveAppView.java index 6f99d515f1e4..12e21998f226 100755 --- a/media/java/android/media/tv/interactive/TvInteractiveAppView.java +++ b/media/java/android/media/tv/interactive/TvInteractiveAppView.java @@ -22,14 +22,15 @@ import android.annotation.Nullable; import android.content.Context; import android.content.res.Resources; import android.content.res.XmlResourceParser; +import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.RectF; import android.media.tv.TvInputManager; import android.media.tv.TvTrackInfo; import android.media.tv.TvView; -import android.media.tv.interactive.TvIAppManager.Session; -import android.media.tv.interactive.TvIAppManager.Session.FinishedInputEventCallback; -import android.media.tv.interactive.TvIAppManager.SessionCallback; +import android.media.tv.interactive.TvInteractiveAppManager.Session; +import android.media.tv.interactive.TvInteractiveAppManager.Session.FinishedInputEventCallback; +import android.media.tv.interactive.TvInteractiveAppManager.SessionCallback; import android.net.Uri; import android.os.Bundle; import android.os.Handler; @@ -50,7 +51,6 @@ import java.util.concurrent.Executor; /** * Displays contents of interactive TV applications. - * @hide */ public class TvInteractiveAppView extends ViewGroup { private static final String TAG = "TvInteractiveAppView"; @@ -61,7 +61,7 @@ public class TvInteractiveAppView extends ViewGroup { private static final int UNSET_TVVIEW_SUCCESS = 3; private static final int UNSET_TVVIEW_FAIL = 4; - private final TvIAppManager mTvInteractiveAppManager; + private final TvInteractiveAppManager mTvInteractiveAppManager; private final Handler mHandler = new Handler(); private final Object mCallbackLock = new Object(); private Session mSession; @@ -141,8 +141,8 @@ public class TvInteractiveAppView extends ViewGroup { } mDefStyleAttr = defStyleAttr; resetSurfaceView(); - mTvInteractiveAppManager = (TvIAppManager) getContext().getSystemService( - Context.TV_IAPP_SERVICE); + mTvInteractiveAppManager = (TvInteractiveAppManager) getContext().getSystemService( + Context.TV_INTERACTIVE_APP_SERVICE); } /** @@ -152,8 +152,8 @@ public class TvInteractiveAppView extends ViewGroup { * callback. */ public void setCallback( - @NonNull TvInteractiveAppCallback callback, - @NonNull @CallbackExecutor Executor executor) { + @NonNull @CallbackExecutor Executor executor, + @NonNull TvInteractiveAppCallback callback) { synchronized (mCallbackLock) { mCallbackExecutor = executor; mCallback = callback; @@ -238,6 +238,7 @@ public class TvInteractiveAppView extends ViewGroup { // The surface view's content should be treated as secure all the time. mSurfaceView.setSecure(true); mSurfaceView.getHolder().addCallback(mSurfaceHolderCallback); + mSurfaceView.getHolder().setFormat(PixelFormat.TRANSLUCENT); addView(mSurfaceView); } @@ -381,9 +382,16 @@ public class TvInteractiveAppView extends ViewGroup { /** * Prepares the interactive application. + * + * @param iAppServiceId the interactive app service ID, which can be found in + * {@link TvInteractiveAppInfo#getId()}. + * + * @see android.media.tv.interactive.TvInteractiveAppManager#getTvInteractiveAppServiceList() * @hide */ - public void prepareInteractiveApp(@NonNull String iAppServiceId, int type) { + public void prepareInteractiveApp( + @NonNull String iAppServiceId, + @TvInteractiveAppInfo.InteractiveAppType int type) { // TODO: document and handle the cases that this method is called multiple times. if (DEBUG) { Log.d(TAG, "prepareInteractiveApp"); @@ -552,10 +560,10 @@ public class TvInteractiveAppView extends ViewGroup { /** * Sets the TvInteractiveAppView to receive events from TIS. This method links the session of - * TvIAppManager to TvInputManager session, so the TIAS can get the TIS events. + * TvInteractiveAppManager to TvInputManager session, so the TIAS can get the TIS events. * * @param tvView the TvView to be linked to this TvInteractiveAppView via linking of Sessions. - * @return to be added + * @return The result of the operation. * @hide */ public int setTvView(@Nullable TvView tvView) { @@ -583,6 +591,7 @@ public class TvInteractiveAppView extends ViewGroup { /** * To toggle Digital Teletext Application if there is one in AIT app list. * @param enable + * @hide */ public void setTeletextAppEnabled(boolean enable) { if (DEBUG) { @@ -609,7 +618,7 @@ public class TvInteractiveAppView extends ViewGroup { */ public void onCommandRequest( @NonNull String iAppServiceId, - @NonNull @TvIAppService.InteractiveAppServiceCommandType String cmdType, + @NonNull @TvInteractiveAppService.InteractiveAppServiceCommandType String cmdType, @Nullable Bundle parameters) { } @@ -617,10 +626,16 @@ public class TvInteractiveAppView extends ViewGroup { * This is called when the session state is changed. * * @param iAppServiceId The ID of the TV interactive app service bound to this view. - * @param state current session state. + * @param state the current state. + * @param err the error code for error state. {@link TvInteractiveAppManager#ERROR_NONE} + * is used when the state is not + * {@link TvInteractiveAppManager#INTERACTIVE_APP_STATE_ERROR}. * @hide */ - public void onSessionStateChanged(@NonNull String iAppServiceId, int state) { + public void onStateChanged( + @NonNull String iAppServiceId, + @TvInteractiveAppManager.InteractiveAppState int state, + @TvInteractiveAppManager.ErrorCode int err) { } /** @@ -642,13 +657,15 @@ public class TvInteractiveAppView extends ViewGroup { * * @param iAppServiceId The ID of the TV interactive app service bound to this view. * @param state digital teletext app current state. + * @hide */ public void onTeletextAppStateChanged( - @NonNull String iAppServiceId, @TvIAppManager.TeletextAppState int state) { + @NonNull String iAppServiceId, + @TvInteractiveAppManager.TeletextAppState int state) { } /** - * This is called when {@link TvIAppService.Session#SetVideoBounds} is called. + * This is called when {@link TvInteractiveAppService.Session#SetVideoBounds} is called. * * @param iAppServiceId The ID of the TV interactive app service bound to this view. * @hide @@ -657,7 +674,7 @@ public class TvInteractiveAppView extends ViewGroup { } /** - * This is called when {@link TvIAppService.Session#RequestCurrentChannelUri} is + * This is called when {@link TvInteractiveAppService.Session#RequestCurrentChannelUri} is * called. * * @param iAppServiceId The ID of the TV interactive app service bound to this view. @@ -667,7 +684,7 @@ public class TvInteractiveAppView extends ViewGroup { } /** - * This is called when {@link TvIAppService.Session#RequestCurrentChannelLcn} is + * This is called when {@link TvInteractiveAppService.Session#RequestCurrentChannelLcn} is * called. * * @param iAppServiceId The ID of the TV interactive app service bound to this view. @@ -677,7 +694,7 @@ public class TvInteractiveAppView extends ViewGroup { } /** - * This is called when {@link TvIAppService.Session#RequestStreamVolume} is + * This is called when {@link TvInteractiveAppService.Session#RequestStreamVolume} is * called. * * @param iAppServiceId The ID of the TV interactive app service bound to this view. @@ -687,7 +704,7 @@ public class TvInteractiveAppView extends ViewGroup { } /** - * This is called when {@link TvIAppService.Session#RequestTrackInfoList} is + * This is called when {@link TvInteractiveAppService.Session#RequestTrackInfoList} is * called. * * @param iAppServiceId The ID of the TV interactive app service bound to this view. @@ -700,6 +717,7 @@ public class TvInteractiveAppView extends ViewGroup { * This is called when {@link TvIAppService.Session#RequestCurrentTvInputId} is called. * * @param iAppServiceId The ID of the TV interactive app service bound to this view. + * @hide */ public void onRequestCurrentTvInputId(@NonNull String iAppServiceId) { } @@ -801,7 +819,7 @@ public class TvInteractiveAppView extends ViewGroup { @Override public void onCommandRequest( Session session, - @TvIAppService.InteractiveAppServiceCommandType String cmdType, + @TvInteractiveAppService.InteractiveAppServiceCommandType String cmdType, Bundle parameters) { if (DEBUG) { Log.d(TAG, "onCommandRequest (cmdType=" + cmdType + ", parameters=" @@ -825,9 +843,12 @@ public class TvInteractiveAppView extends ViewGroup { } @Override - public void onSessionStateChanged(Session session, int state) { + public void onSessionStateChanged( + Session session, + @TvInteractiveAppManager.InteractiveAppState int state, + @TvInteractiveAppManager.ErrorCode int err) { if (DEBUG) { - Log.d(TAG, "onSessionStateChanged (state=" + state + ")"); + Log.d(TAG, "onSessionStateChanged (state=" + state + "; err=" + err + ")"); } if (this != mSessionCallback) { Log.w(TAG, "onSessionStateChanged - session not created"); @@ -838,7 +859,7 @@ public class TvInteractiveAppView extends ViewGroup { mCallbackExecutor.execute(() -> { synchronized (mCallbackLock) { if (mCallback != null) { - mCallback.onSessionStateChanged(mIAppServiceId, state); + mCallback.onStateChanged(mIAppServiceId, state, err); } } }); diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java index 9c4a83a235c0..315737572c19 100644 --- a/media/java/android/media/tv/tuner/Tuner.java +++ b/media/java/android/media/tv/tuner/Tuner.java @@ -1002,7 +1002,7 @@ public class Tuner implements AutoCloseable { private native String nativeGetFrontendHardwareInfo(); private native int nativeSetMaxNumberOfFrontends(int frontendType, int maxNumber); private native int nativeGetMaxNumberOfFrontends(int frontendType); - + private native int nativeRemoveOutputPid(int pid); private native Lnb nativeOpenLnbByHandle(int handle); private native Lnb nativeOpenLnbByName(String name); @@ -1565,6 +1565,36 @@ public class Tuner implements AutoCloseable { } /** + * Filter out unnecessary PID (packet identifier) from frontend output. + * + * <p>It is used by the client to remove some video or audio PIDs of other program to reduce the + * total amount of recorded TS. + * + * <p>This API is only supported by Tuner HAL 2.0 or higher. Unsupported version would cause + * no-op. Use {@link TunerVersionChecker#getTunerVersion()} to check the version. + * + * @return result status of the operation. Unsupported version or if current active frontend + * doesn’t support PID filtering out would return {@link #RESULT_UNAVAILABLE}. + * @throws IllegalStateException if there is no active frontend currently. + */ + @Result + public int removeOutputPid(@IntRange(from = 0) int pid) { + mFrontendLock.lock(); + try { + if (!TunerVersionChecker.checkHigherOrEqualVersionTo( + TunerVersionChecker.TUNER_VERSION_2_0, "Remove output PID")) { + return RESULT_UNAVAILABLE; + } + if (mFrontend == null) { + throw new IllegalStateException("frontend is not initialized"); + } + return nativeRemoveOutputPid(pid); + } finally { + mFrontendLock.unlock(); + } + } + + /** * Gets the currently initialized and activated frontend information. To get all the available * frontend info on the device, use {@link getAvailableFrontendInfos()}. * diff --git a/media/java/android/media/tv/tuner/filter/SectionSettings.java b/media/java/android/media/tv/tuner/filter/SectionSettings.java index f123675a8940..83ed8e84e4da 100644 --- a/media/java/android/media/tv/tuner/filter/SectionSettings.java +++ b/media/java/android/media/tv/tuner/filter/SectionSettings.java @@ -82,7 +82,7 @@ public abstract class SectionSettings extends Settings { * The section filter uses this for CRC (Cyclic redundancy check) checking when * {@link #isCrcEnabled()} is {@code true}. */ - public int getBitWidthOfLengthField() { + public int getLengthFieldBitWidth() { return mBitWidthOfLengthField; } diff --git a/media/java/android/media/tv/tuner/frontend/FrontendStatus.java b/media/java/android/media/tv/tuner/frontend/FrontendStatus.java index 8cedd04a8b89..c1e9b38a13b0 100644 --- a/media/java/android/media/tv/tuner/frontend/FrontendStatus.java +++ b/media/java/android/media/tv/tuner/frontend/FrontendStatus.java @@ -54,7 +54,7 @@ public class FrontendStatus { FRONTEND_STATUS_TYPE_IS_MISO_ENABLED, FRONTEND_STATUS_TYPE_IS_LINEAR, FRONTEND_STATUS_TYPE_IS_SHORT_FRAMES_ENABLED, FRONTEND_STATUS_TYPE_ISDBT_MODE, FRONTEND_STATUS_TYPE_ISDBT_PARTIAL_RECEPTION_FLAG, FRONTEND_STATUS_TYPE_STREAM_IDS, - FRONTEND_STATUS_TYPE_DVBT_CELL_IDS}) + FRONTEND_STATUS_TYPE_DVBT_CELL_IDS, FRONTEND_STATUS_TYPE_ATSC3_ALL_PLP_INFO}) @Retention(RetentionPolicy.SOURCE) public @interface FrontendStatusType {} @@ -165,7 +165,7 @@ public class FrontendStatus { public static final int FRONTEND_STATUS_TYPE_RF_LOCK = android.hardware.tv.tuner.FrontendStatusType.RF_LOCK; /** - * PLP information in a frequency band for ATSC-3.0 frontend. + * Current tuned PLP information in a frequency band for ATSC-3.0 frontend. */ public static final int FRONTEND_STATUS_TYPE_ATSC3_PLP_INFO = android.hardware.tv.tuner.FrontendStatusType.ATSC3_PLP_INFO; @@ -267,6 +267,13 @@ public class FrontendStatus { public static final int FRONTEND_STATUS_TYPE_DVBT_CELL_IDS = android.hardware.tv.tuner.FrontendStatusType.DVBT_CELL_IDS; + /** + * All PLP information in a frequency band for ATSC-3.0 frontend, which includes both tuned and + * not tuned PLPs for currently watching service. + */ + public static final int FRONTEND_STATUS_TYPE_ATSC3_ALL_PLP_INFO = + android.hardware.tv.tuner.FrontendStatusType.ATSC3_ALL_PLP_INFO; + /** @hide */ @IntDef(value = { AtscFrontendSettings.MODULATION_UNDEFINED, @@ -508,6 +515,7 @@ public class FrontendStatus { private Integer mIsdbtPartialReceptionFlag; private int[] mStreamIds; private int[] mDvbtCellIds; + private Atsc3PlpInfo[] mAllPlpInfo; // Constructed and fields set by JNI code. private FrontendStatus() { @@ -1078,6 +1086,25 @@ public class FrontendStatus { } /** + * Gets an array of all PLPs information of ATSC3 frontend, which includes both tuned and not + * tuned PLPs for currently watching service. + * + * <p>This query is only supported by Tuner HAL 2.0 or higher. Unsupported version or if HAL + * doesn't return all PLPs information will throw IllegalStateException. Use + * {@link TunerVersionChecker#getTunerVersion()} to check the version. + */ + @SuppressLint("ArrayReturn") + @NonNull + public Atsc3PlpInfo[] getAllAtsc3PlpInfo() { + TunerVersionChecker.checkHigherOrEqualVersionTo( + TunerVersionChecker.TUNER_VERSION_2_0, "Atsc3PlpInfo all status"); + if (mAllPlpInfo == null) { + throw new IllegalStateException("Atsc3PlpInfo all status is empty"); + } + return mAllPlpInfo; + } + + /** * Information of each tuning Physical Layer Pipes. */ public static class Atsc3PlpTuningInfo { diff --git a/media/jni/android_media_ImageWriter.cpp b/media/jni/android_media_ImageWriter.cpp index 0a5490d33293..2e419a61de91 100644 --- a/media/jni/android_media_ImageWriter.cpp +++ b/media/jni/android_media_ImageWriter.cpp @@ -375,7 +375,8 @@ static void ImageWriter_classInit(JNIEnv* env, jclass clazz) { } static jlong ImageWriter_init(JNIEnv* env, jobject thiz, jobject weakThiz, jobject jsurface, - jint maxImages, jint userFormat, jint userWidth, jint userHeight) { + jint maxImages, jint userWidth, jint userHeight, jboolean useSurfaceImageFormatInfo, + jint hardwareBufferFormat, jlong dataSpace, jlong ndkUsage) { status_t res; ALOGV("%s: maxImages:%d", __FUNCTION__, maxImages); @@ -450,7 +451,7 @@ static jlong ImageWriter_init(JNIEnv* env, jobject thiz, jobject weakThiz, jobje // Query surface format if no valid user format is specified, otherwise, override surface format // with user format. - if (userFormat == IMAGE_FORMAT_UNKNOWN) { + if (useSurfaceImageFormatInfo) { if ((res = anw->query(anw.get(), NATIVE_WINDOW_FORMAT, &surfaceFormat)) != OK) { ALOGE("%s: Query Surface format failed: %s (%d)", __FUNCTION__, strerror(-res), res); jniThrowRuntimeException(env, "Failed to query Surface format"); @@ -458,13 +459,13 @@ static jlong ImageWriter_init(JNIEnv* env, jobject thiz, jobject weakThiz, jobje } } else { // Set consumer buffer format to user specified format - PublicFormat publicFormat = static_cast<PublicFormat>(userFormat); - int nativeFormat = mapPublicFormatToHalFormat(publicFormat); - android_dataspace nativeDataspace = mapPublicFormatToHalDataspace(publicFormat); - res = native_window_set_buffers_format(anw.get(), nativeFormat); + android_dataspace nativeDataspace = static_cast<android_dataspace>(dataSpace); + int userFormat = static_cast<int>(mapHalFormatDataspaceToPublicFormat( + hardwareBufferFormat, nativeDataspace)); + res = native_window_set_buffers_format(anw.get(), hardwareBufferFormat); if (res != OK) { ALOGE("%s: Unable to configure consumer native buffer format to %#x", - __FUNCTION__, nativeFormat); + __FUNCTION__, hardwareBufferFormat); jniThrowRuntimeException(env, "Failed to set Surface format"); return 0; } @@ -484,15 +485,13 @@ static jlong ImageWriter_init(JNIEnv* env, jobject thiz, jobject weakThiz, jobje env->SetIntField(thiz, gImageWriterClassInfo.mWriterFormat, reinterpret_cast<jint>(surfaceFormat)); - if (!isFormatOpaque(surfaceFormat)) { - res = native_window_set_usage(anw.get(), GRALLOC_USAGE_SW_WRITE_OFTEN); - if (res != OK) { - ALOGE("%s: Configure usage %08x for format %08x failed: %s (%d)", - __FUNCTION__, static_cast<unsigned int>(GRALLOC_USAGE_SW_WRITE_OFTEN), - surfaceFormat, strerror(-res), res); - jniThrowRuntimeException(env, "Failed to SW_WRITE_OFTEN configure usage"); - return 0; - } + res = native_window_set_usage(anw.get(), ndkUsage); + if (res != OK) { + ALOGE("%s: Configure usage %08x for format %08x failed: %s (%d)", + __FUNCTION__, static_cast<unsigned int>(ndkUsage), + surfaceFormat, strerror(-res), res); + jniThrowRuntimeException(env, "Failed to SW_WRITE_OFTEN configure usage"); + return 0; } int minUndequeuedBufferCount = 0; @@ -1093,7 +1092,7 @@ static jobjectArray Image_createSurfacePlanes(JNIEnv* env, jobject thiz, static JNINativeMethod gImageWriterMethods[] = { {"nativeClassInit", "()V", (void*)ImageWriter_classInit }, - {"nativeInit", "(Ljava/lang/Object;Landroid/view/Surface;IIII)J", + {"nativeInit", "(Ljava/lang/Object;Landroid/view/Surface;IIIZIJJ)J", (void*)ImageWriter_init }, {"nativeClose", "(J)V", (void*)ImageWriter_close }, {"nativeAttachAndQueueImage", diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp index 1b41494814b7..41f3a678577c 100644 --- a/media/jni/android_media_tv_Tuner.cpp +++ b/media/jni/android_media_tv_Tuner.cpp @@ -1622,6 +1622,15 @@ int32_t JTuner::getMaxNumberOfFrontends(int32_t type) { return mTunerClient->getMaxNumberOfFrontends(static_cast<FrontendType>(type)); } +jint JTuner::removeOutputPid(int32_t pid) { + if (mFeClient == nullptr) { + ALOGE("frontend is not initialized"); + return (jint)Result::INVALID_STATE; + } + + return (jint)mFeClient->removeOutputPid(pid); +} + jobject JTuner::openLnbByHandle(int handle) { if (mTunerClient == nullptr) { return nullptr; @@ -2610,6 +2619,24 @@ jobject JTuner::getFrontendStatus(jintArray types) { env->SetObjectField(statusObj, field, valObj); break; } + case FrontendStatus::Tag::allPlpInfo: { + jfieldID field = env->GetFieldID(clazz, "mAllPlpInfo", + "[Landroid/media/tv/tuner/frontend/Atsc3PlpInfo;"); + jclass plpClazz = env->FindClass("android/media/tv/tuner/frontend/Atsc3PlpInfo"); + jmethodID initPlp = env->GetMethodID(plpClazz, "<init>", "(IZ)V"); + + vector<FrontendScanAtsc3PlpInfo> plpInfos = + s.get<FrontendStatus::Tag::allPlpInfo>(); + jobjectArray valObj = env->NewObjectArray(plpInfos.size(), plpClazz, nullptr); + for (int i = 0; i < plpInfos.size(); i++) { + jobject plpObj = env->NewObject(plpClazz, initPlp, plpInfos[i].plpId, + plpInfos[i].bLlsFlag); + env->SetObjectArrayElement(valObj, i, plpObj); + } + + env->SetObjectField(statusObj, field, valObj); + break; + } } } return statusObj; @@ -4357,6 +4384,11 @@ static jint android_media_tv_Tuner_get_maximum_frontends(JNIEnv *env, jobject th return tuner->getMaxNumberOfFrontends(type); } +static jint android_media_tv_Tuner_remove_output_pid(JNIEnv *env, jobject thiz, jint pid) { + sp<JTuner> tuner = getTuner(env, thiz); + return tuner->removeOutputPid(pid); +} + static jint android_media_tv_Tuner_close_frontend(JNIEnv* env, jobject thiz, jint /* handle */) { sp<JTuner> tuner = getTuner(env, thiz); return tuner->closeFrontend(); @@ -4676,6 +4708,8 @@ static const JNINativeMethod gTunerMethods[] = { (void *)android_media_tv_Tuner_set_maximum_frontends }, { "nativeGetMaxNumberOfFrontends", "(I)I", (void *)android_media_tv_Tuner_get_maximum_frontends }, + { "nativeRemoveOutputPid", "(I)I", + (void *)android_media_tv_Tuner_remove_output_pid }, }; static const JNINativeMethod gFilterMethods[] = { diff --git a/media/jni/android_media_tv_Tuner.h b/media/jni/android_media_tv_Tuner.h index 502bd6b18413..e9475dc3d8ee 100644 --- a/media/jni/android_media_tv_Tuner.h +++ b/media/jni/android_media_tv_Tuner.h @@ -205,6 +205,7 @@ struct JTuner : public RefBase { Result getFrontendHardwareInfo(string& info); jint setMaxNumberOfFrontends(int32_t frontendType, int32_t maxNumber); int32_t getMaxNumberOfFrontends(int32_t frontendType); + jint removeOutputPid(int32_t pid); jweak getObject(); diff --git a/media/jni/tuner/FrontendClient.cpp b/media/jni/tuner/FrontendClient.cpp index 0fdd8d8773f4..bea0342293c0 100644 --- a/media/jni/tuner/FrontendClient.cpp +++ b/media/jni/tuner/FrontendClient.cpp @@ -143,6 +143,15 @@ Result FrontendClient::getHardwareInfo(string& info) { return Result::INVALID_STATE; } +Result FrontendClient::removeOutputPid(int32_t pid) { + if (mTunerFrontend != nullptr) { + Status s = mTunerFrontend->removeOutputPid(pid); + return ClientHelper::getServiceSpecificErrorCode(s); + } + + return Result::INVALID_STATE; +} + shared_ptr<ITunerFrontend> FrontendClient::getAidlFrontend() { return mTunerFrontend; } diff --git a/media/jni/tuner/FrontendClient.h b/media/jni/tuner/FrontendClient.h index 77d909804cf0..c6838c85dfc4 100644 --- a/media/jni/tuner/FrontendClient.h +++ b/media/jni/tuner/FrontendClient.h @@ -120,6 +120,11 @@ public: */ Result getHardwareInfo(string& info); + /** + * Filter out unnecessary PID from frontend output. + */ + Result removeOutputPid(int32_t pid); + int32_t getId(); shared_ptr<ITunerFrontend> getAidlFrontend(); diff --git a/media/native/midi/MidiDeviceInfo.cpp b/media/native/midi/MidiDeviceInfo.cpp index 8a573fba322b..14524883470f 100644 --- a/media/native/midi/MidiDeviceInfo.cpp +++ b/media/native/midi/MidiDeviceInfo.cpp @@ -64,6 +64,7 @@ status_t MidiDeviceInfo::writeToParcel(Parcel* parcel) const { RETURN_IF_FAILED(writeStringVector(parcel, mInputPortNames)); RETURN_IF_FAILED(writeStringVector(parcel, mOutputPortNames)); RETURN_IF_FAILED(parcel->writeInt32(mIsPrivate ? 1 : 0)); + RETURN_IF_FAILED(parcel->writeInt32(mDefaultProtocol)); RETURN_IF_FAILED(mProperties.writeToParcel(parcel)); // This corresponds to "extra" properties written by Java code RETURN_IF_FAILED(mProperties.writeToParcel(parcel)); @@ -83,6 +84,7 @@ status_t MidiDeviceInfo::readFromParcel(const Parcel* parcel) { int32_t isPrivate; RETURN_IF_FAILED(parcel->readInt32(&isPrivate)); mIsPrivate = isPrivate == 1; + RETURN_IF_FAILED(parcel->readInt32(&mDefaultProtocol)); RETURN_IF_FAILED(mProperties.readFromParcel(parcel)); // Ignore "extra" properties as they may contain Java Parcelables return OK; @@ -130,7 +132,8 @@ bool operator==(const MidiDeviceInfo& lhs, const MidiDeviceInfo& rhs) { areVectorsEqual(lhs.mInputPortNames, rhs.mInputPortNames) && areVectorsEqual(lhs.mOutputPortNames, rhs.mOutputPortNames) && lhs.mProperties == rhs.mProperties && - lhs.mIsPrivate == rhs.mIsPrivate); + lhs.mIsPrivate == rhs.mIsPrivate && + lhs.mDefaultProtocol == rhs.mDefaultProtocol); } } // namespace midi diff --git a/media/native/midi/MidiDeviceInfo.h b/media/native/midi/MidiDeviceInfo.h index 5b4a241323d7..23e1cb474168 100644 --- a/media/native/midi/MidiDeviceInfo.h +++ b/media/native/midi/MidiDeviceInfo.h @@ -38,6 +38,7 @@ public: int getType() const { return mType; } int getUid() const { return mId; } bool isPrivate() const { return mIsPrivate; } + int getDefaultProtocol() const { return mDefaultProtocol; } const Vector<String16>& getInputPortNames() const { return mInputPortNames; } const Vector<String16>& getOutputPortNames() const { return mOutputPortNames; } String16 getProperty(const char* propertyName); @@ -48,6 +49,18 @@ public: TYPE_VIRTUAL = 2, TYPE_BLUETOOTH = 3, }; + + enum { + PROTOCOL_UMP_USE_MIDI_CI = 0, + PROTOCOL_UMP_MIDI_1_0_UP_TO_64_BITS = 1, + PROTOCOL_UMP_MIDI_1_0_UP_TO_64_BITS_AND_JRTS = 2, + PROTOCOL_UMP_MIDI_1_0_UP_TO_128_BITS = 3, + PROTOCOL_UMP_MIDI_1_0_UP_TO_128_BITS_AND_JRTS = 4, + PROTOCOL_UMP_MIDI_2_0 = 17, + PROTOCOL_UMP_MIDI_2_0_AND_JRTS = 18, + PROTOCOL_UNKNOWN = -1, + }; + static const char* const PROPERTY_NAME; static const char* const PROPERTY_MANUFACTURER; static const char* const PROPERTY_PRODUCT; @@ -72,6 +85,7 @@ private: Vector<String16> mOutputPortNames; os::PersistableBundle mProperties; bool mIsPrivate; + int32_t mDefaultProtocol; }; } // namespace midi diff --git a/media/native/midi/amidi.cpp b/media/native/midi/amidi.cpp index f90796e415c0..aa076e85e30d 100644 --- a/media/native/midi/amidi.cpp +++ b/media/native/midi/amidi.cpp @@ -138,6 +138,7 @@ static media_status_t AMIDI_getDeviceInfo(const AMidiDevice *device, outDeviceInfoPtr->type = deviceInfo.getType(); outDeviceInfoPtr->inputPortCount = deviceInfo.getInputPortNames().size(); outDeviceInfoPtr->outputPortCount = deviceInfo.getOutputPortNames().size(); + outDeviceInfoPtr->defaultProtocol = deviceInfo.getDefaultProtocol(); return AMEDIA_OK; } @@ -238,6 +239,13 @@ ssize_t AMIDI_API AMidiDevice_getNumOutputPorts(const AMidiDevice *device) { return device->deviceInfo.outputPortCount; } +AMidiDevice_Protocol AMIDI_API AMidiDevice_getDefaultProtocol(const AMidiDevice *device) { + if (device == nullptr) { + return AMIDI_DEVICE_PROTOCOL_UNKNOWN; + } + return static_cast<AMidiDevice_Protocol>(device->deviceInfo.defaultProtocol); +} + /* * Port Helpers */ diff --git a/media/native/midi/amidi_internal.h b/media/native/midi/amidi_internal.h index fce85963d217..023a6f5ec900 100644 --- a/media/native/midi/amidi_internal.h +++ b/media/native/midi/amidi_internal.h @@ -25,6 +25,7 @@ typedef struct { int32_t type; /* one of AMIDI_DEVICE_TYPE_* constants */ int32_t inputPortCount; /* number of input (send) ports associated with the device */ int32_t outputPortCount; /* number of output (received) ports associated with the device */ + int32_t defaultProtocol; /* one of the AMIDI_DEVICE_PROTOCOL_* constants */ } AMidiDeviceInfo; struct AMidiDevice { diff --git a/media/native/midi/include/amidi/AMidi.h b/media/native/midi/include/amidi/AMidi.h index 742db34b74a7..fbb7fb329659 100644 --- a/media/native/midi/include/amidi/AMidi.h +++ b/media/native/midi/include/amidi/AMidi.h @@ -62,6 +62,78 @@ enum { }; /* + * Protocol IDs for various MIDI devices. + * + * Introduced in API 33. + */ +enum AMidiDevice_Protocol : int32_t { + /** + * Constant representing a default protocol with Universal MIDI Packets (UMP). + * UMP is defined in "Universal MIDI Packet (UMP) Format and MIDI 2.0 Protocol" spec. + * All UMP data should be a multiple of 4 bytes. + * Use UMP to negotiate with the device with MIDI-CI. + * MIDI-CI is defined in "MIDI Capability Inquiry (MIDI-CI)" spec. + */ + AMIDI_DEVICE_PROTOCOL_UMP_USE_MIDI_CI = 0, + + /** + * Constant representing a default protocol with Universal MIDI Packets (UMP). + * UMP is defined in "Universal MIDI Packet (UMP) Format and MIDI 2.0 Protocol" spec. + * All UMP data should be a multiple of 4 bytes. + * Use MIDI 1.0 through UMP with packet sizes up to 64 bits. + */ + AMIDI_DEVICE_PROTOCOL_UMP_MIDI_1_0_UP_TO_64_BITS = 1, + + /** + * Constant representing a default protocol with Universal MIDI Packets (UMP). + * UMP is defined in "Universal MIDI Packet (UMP) Format and MIDI 2.0 Protocol" spec. + * All UMP data should be a multiple of 4 bytes. + * Use MIDI 1.0 through UMP with packet sizes up to 64 bits and jitter reduction timestamps. + */ + AMIDI_DEVICE_PROTOCOL_UMP_MIDI_1_0_UP_TO_64_BITS_AND_JRTS = 2, + + /** + * Constant representing a default protocol with Universal MIDI Packets (UMP). + * UMP is defined in "Universal MIDI Packet (UMP) Format and MIDI 2.0 Protocol" spec. + * All UMP data should be a multiple of 4 bytes. + * Use MIDI 1.0 through UMP with packet sizes up to 128 bits. + */ + AMIDI_DEVICE_PROTOCOL_UMP_MIDI_1_0_UP_TO_128_BITS = 3, + + /** + * Constant representing a default protocol with Universal MIDI Packets (UMP). + * UMP is defined in "Universal MIDI Packet (UMP) Format and MIDI 2.0 Protocol" spec. + * All UMP data should be a multiple of 4 bytes. + * Use MIDI 1.0 through UMP with packet sizes up to 128 bits and jitter reduction timestamps. + */ + AMIDI_DEVICE_PROTOCOL_UMP_MIDI_1_0_UP_TO_128_BITS_AND_JRTS = 4, + + /** + * Constant representing a default protocol with Universal MIDI Packets (UMP). + * UMP is defined in "Universal MIDI Packet (UMP) Format and MIDI 2.0 Protocol" spec. + * All UMP data should be a multiple of 4 bytes. + * Use MIDI 2.0 through UMP. + */ + AMIDI_DEVICE_PROTOCOL_UMP_MIDI_2_0 = 17, + + /** + * Constant representing a default protocol with Universal MIDI Packets (UMP). + * UMP is defined in "Universal MIDI Packet (UMP) Format and MIDI 2.0 Protocol" spec. + * All UMP data should be a multiple of 4 bytes. + * Use MIDI 2.0 through UMP and jitter reduction timestamps. + */ + AMIDI_DEVICE_PROTOCOL_UMP_MIDI_2_0_AND_JRTS = 18, + + /** + * Constant representing a device with an unknown default protocol. + * If Universal MIDI Packets (UMP) are needed, use MIDI-CI through MIDI 1.0. + * UMP is defined in "Universal MIDI Packet (UMP) Format and MIDI 2.0 Protocol" spec. + * MIDI-CI is defined in "MIDI Capability Inquiry (MIDI-CI)" spec. + */ + AMIDI_DEVICE_PROTOCOL_UNKNOWN = -1 +}; + +/* * Device API */ /** @@ -134,6 +206,30 @@ ssize_t AMIDI_API AMidiDevice_getNumInputPorts(const AMidiDevice *device) __INTR */ ssize_t AMIDI_API AMidiDevice_getNumOutputPorts(const AMidiDevice *device) __INTRODUCED_IN(29); +/** + * Gets the MIDI device default protocol. + * + * @param device Specifies the MIDI device. + * + * @return The identifier of the MIDI device default protocol: + * AMIDI_DEVICE_PROTOCOL_UMP_USE_MIDI_CI + * AMIDI_DEVICE_PROTOCOL_UMP_MIDI_1_0_UP_TO_64_BITS + * AMIDI_DEVICE_PROTOCOL_UMP_MIDI_1_0_UP_TO_64_BITS_AND_JRTS + * AMIDI_DEVICE_PROTOCOL_UMP_MIDI_1_0_UP_TO_128_BITS + * AMIDI_DEVICE_PROTOCOL_UMP_MIDI_1_0_UP_TO_128_BITS_AND_JRTS + * AMIDI_DEVICE_PROTOCOL_UMP_MIDI_2_0 + * AMIDI_DEVICE_PROTOCOL_UMP_MIDI_2_0_AND_JRTS + * AMIDI_DEVICE_PROTOCOL_UNKNOWN + * + * Most devices should return PROTOCOL_UNKNOWN (-1). This is intentional as devices + * with default UMP support are not backwards compatible. When the device is null, + * return AMIDI_DEVICE_PROTOCOL_UNKNOWN. + * + * Available since API 33. + */ +AMidiDevice_Protocol AMIDI_API AMidiDevice_getDefaultProtocol(const AMidiDevice *device) + __INTRODUCED_IN(33); + /* * API for receiving data from the Output port of a device. */ diff --git a/media/native/midi/libamidi.map.txt b/media/native/midi/libamidi.map.txt index 62627f8c5ef7..f25f97770b50 100644 --- a/media/native/midi/libamidi.map.txt +++ b/media/native/midi/libamidi.map.txt @@ -2,6 +2,7 @@ LIBAMIDI { global: AMidiDevice_fromJava; AMidiDevice_release; + AMidiDevice_getDefaultProtocol; # introduced=Tiramisu AMidiDevice_getType; AMidiDevice_getNumInputPorts; AMidiDevice_getNumOutputPorts; diff --git a/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java index 62c313ace306..4c3b68958c0f 100644 --- a/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java +++ b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java @@ -24,6 +24,7 @@ import android.bluetooth.BluetoothGattDescriptor; import android.bluetooth.BluetoothGattService; import android.bluetooth.BluetoothProfile; import android.content.Context; +import android.media.midi.MidiDevice; import android.media.midi.MidiDeviceInfo; import android.media.midi.MidiDeviceServer; import android.media.midi.MidiDeviceStatus; @@ -63,6 +64,7 @@ public final class BluetoothMidiDevice { "00002902-0000-1000-8000-00805f9b34fb"); private final BluetoothDevice mBluetoothDevice; + private final Context mContext; private final BluetoothMidiService mService; private final MidiManager mMidiManager; private MidiReceiver mOutputReceiver; @@ -136,6 +138,8 @@ public final class BluetoothMidiDevice { // switch to receiving notifications mBluetoothGatt.readCharacteristic(characteristic); } + + openBluetoothDevice(mBluetoothDevice); } } else { Log.e(TAG, "onServicesDiscovered received: " + status); @@ -249,6 +253,7 @@ public final class BluetoothMidiDevice { mBluetoothGatt = mBluetoothDevice.connectGatt(context, false, mGattCallback); + mContext = context; mMidiManager = (MidiManager)context.getSystemService(Context.MIDI_SERVICE); Bundle properties = new Bundle(); @@ -260,7 +265,8 @@ public final class BluetoothMidiDevice { inputPortReceivers[0] = mEventScheduler.getReceiver(); mDeviceServer = mMidiManager.createDeviceServer(inputPortReceivers, 1, - null, null, properties, MidiDeviceInfo.TYPE_BLUETOOTH, mDeviceServerCallback); + null, null, properties, MidiDeviceInfo.TYPE_BLUETOOTH, + MidiDeviceInfo.PROTOCOL_UNKNOWN, mDeviceServerCallback); mOutputReceiver = mDeviceServer.getOutputPortReceivers()[0]; @@ -309,6 +315,18 @@ public final class BluetoothMidiDevice { } } + void openBluetoothDevice(BluetoothDevice btDevice) { + Log.d(TAG, "openBluetoothDevice() device: " + btDevice); + + MidiManager midiManager = mContext.getSystemService(MidiManager.class); + midiManager.openBluetoothDevice(btDevice, + new MidiManager.OnDeviceOpenedListener() { + @Override + public void onDeviceOpened(MidiDevice device) { + } + }, null); + } + public IBinder getBinder() { return mDeviceServer.asBinder(); } diff --git a/native/android/choreographer.cpp b/native/android/choreographer.cpp index fbd4b2ec8736..e22580d6d71a 100644 --- a/native/android/choreographer.cpp +++ b/native/android/choreographer.cpp @@ -67,7 +67,7 @@ size_t AChoreographerFrameCallbackData_getPreferredFrameTimelineIndex( const AChoreographerFrameCallbackData* data) { return AChoreographerFrameCallbackData_routeGetPreferredFrameTimelineIndex(data); } -int64_t AChoreographerFrameCallbackData_getFrameTimelineVsyncId( +AVsyncId AChoreographerFrameCallbackData_getFrameTimelineVsyncId( const AChoreographerFrameCallbackData* data, size_t index) { return AChoreographerFrameCallbackData_routeGetFrameTimelineVsyncId(data, index); } diff --git a/native/android/surface_control.cpp b/native/android/surface_control.cpp index 5b19102334d9..d01a30e52749 100644 --- a/native/android/surface_control.cpp +++ b/native/android/surface_control.cpp @@ -661,7 +661,7 @@ void ASurfaceTransaction_setOnCommit(ASurfaceTransaction* aSurfaceTransaction, v } void ASurfaceTransaction_setFrameTimeline(ASurfaceTransaction* aSurfaceTransaction, - int64_t vsyncId) { + AVsyncId vsyncId) { CHECK_NOT_NULL(aSurfaceTransaction); // TODO(b/210043506): Get start time from platform. ASurfaceTransaction_to_Transaction(aSurfaceTransaction) diff --git a/packages/CompanionDeviceManager/res/layout/data_transfer_confirmation.xml b/packages/CompanionDeviceManager/res/layout/data_transfer_confirmation.xml new file mode 100644 index 000000000000..a1855fdda78d --- /dev/null +++ b/packages/CompanionDeviceManager/res/layout/data_transfer_confirmation.xml @@ -0,0 +1,73 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/activity_confirmation" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="@drawable/dialog_background" + android:elevation="16dp" + android:maxHeight="400dp" + android:orientation="vertical" + android:padding="18dp" + android:layout_gravity="center"> + + <!-- Do NOT change the ID of the root LinearLayout above: it's referenced in CTS tests. --> + + <TextView + android:id="@+id/title" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="center" + android:paddingHorizontal="12dp" + style="@*android:style/TextAppearance.Widget.Toolbar.Title"/> + + <TextView + android:id="@+id/summary" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="12dp" + android:layout_marginBottom="12dp" + android:gravity="center" + android:textColor="?android:attr/textColorSecondary" + android:textSize="14sp" /> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:gravity="end"> + + <!-- Do NOT change the IDs of the buttons: they are referenced in CTS tests. --> + + <Button + android:id="@+id/btn_negative" + style="@android:style/Widget.Material.Button.Borderless.Colored" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/consent_no" + android:textColor="?android:attr/textColorSecondary" /> + + <Button + android:id="@+id/btn_positive" + style="@android:style/Widget.Material.Button.Borderless.Colored" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/consent_yes" /> + + </LinearLayout> + +</LinearLayout>
\ No newline at end of file diff --git a/packages/CompanionDeviceManager/res/values/strings.xml b/packages/CompanionDeviceManager/res/values/strings.xml index cb8b616ec009..25ec96065647 100644 --- a/packages/CompanionDeviceManager/res/values/strings.xml +++ b/packages/CompanionDeviceManager/res/values/strings.xml @@ -73,4 +73,15 @@ <!-- Negative button for the device-app association consent dialog [CHAR LIMIT=30] --> <string name="consent_no">Don\u2019t allow</string> + + <!-- ================== System data transfer ==================== --> + <!-- Title of the permission sync confirmation dialog. [CHAR LIMIT=60] --> + <string name="permission_sync_confirmation_title">Transfer app permissions to your + watch</string> + + <!-- Text of the permission sync explanation in the confirmation dialog. [CHAR LIMIT=400] --> + <string name="permission_sync_summary">To make it easier to set up your watch, + apps installed on your watch during setup will use the same permissions as your phone.\n\n + These permissions may include access to your watch\u2019s microphone and location.</string> + </resources> diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDataTransferActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDataTransferActivity.java new file mode 100644 index 000000000000..67efa03b645f --- /dev/null +++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDataTransferActivity.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.companiondevicemanager; + +import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS; + +import static java.util.Objects.requireNonNull; + +import android.app.Activity; +import android.companion.SystemDataTransferRequest; +import android.content.Intent; +import android.os.Bundle; +import android.os.ResultReceiver; +import android.text.Html; +import android.util.Log; +import android.view.View; +import android.widget.Button; +import android.widget.ListView; +import android.widget.TextView; + +/** + * This activity manages the UI of companion device data transfer. + */ +public class CompanionDeviceDataTransferActivity extends Activity { + + private static final String LOG_TAG = CompanionDeviceDataTransferActivity.class.getSimpleName(); + + // UI -> SystemDataTransferProcessor + private static final int RESULT_CODE_SYSTEM_DATA_TRANSFER_ALLOWED = 0; + private static final int RESULT_CODE_SYSTEM_DATA_TRANSFER_DISALLOWED = 1; + private static final String EXTRA_SYSTEM_DATA_TRANSFER_REQUEST = "system_data_transfer_request"; + private static final String EXTRA_SYSTEM_DATA_TRANSFER_RESULT_RECEIVER = + "system_data_transfer_result_receiver"; + + private SystemDataTransferRequest mRequest; + private ResultReceiver mCdmServiceReceiver; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + Log.i(LOG_TAG, "Creating UI for data transfer confirmation."); + + getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS); + + setContentView(R.layout.data_transfer_confirmation); + + TextView titleView = findViewById(R.id.title); + TextView summaryView = findViewById(R.id.summary); + ListView listView = findViewById(R.id.device_list); + listView.setVisibility(View.GONE); + Button allowButton = findViewById(R.id.btn_positive); + Button disallowButton = findViewById(R.id.btn_negative); + + final Intent intent = getIntent(); + mRequest = intent.getParcelableExtra(EXTRA_SYSTEM_DATA_TRANSFER_REQUEST); + mCdmServiceReceiver = intent.getParcelableExtra(EXTRA_SYSTEM_DATA_TRANSFER_RESULT_RECEIVER); + + requireNonNull(mRequest); + requireNonNull(mCdmServiceReceiver); + + if (mRequest.isPermissionSyncAllPackages() + || !mRequest.getPermissionSyncPackages().isEmpty()) { + titleView.setText(Html.fromHtml(getString( + R.string.permission_sync_confirmation_title), 0)); + summaryView.setText(getString(R.string.permission_sync_summary)); + allowButton.setOnClickListener(v -> allow()); + disallowButton.setOnClickListener(v -> disallow()); + } + } + + private void allow() { + Log.i(LOG_TAG, "allow()"); + + sendDataToReceiver(RESULT_CODE_SYSTEM_DATA_TRANSFER_ALLOWED); + + setResultAndFinish(RESULT_CODE_SYSTEM_DATA_TRANSFER_ALLOWED); + } + + private void disallow() { + Log.i(LOG_TAG, "disallow()"); + + sendDataToReceiver(RESULT_CODE_SYSTEM_DATA_TRANSFER_DISALLOWED); + + setResultAndFinish(RESULT_CODE_SYSTEM_DATA_TRANSFER_DISALLOWED); + } + + private void sendDataToReceiver(int cdmResultCode) { + Bundle data = new Bundle(); + data.putParcelable(EXTRA_SYSTEM_DATA_TRANSFER_REQUEST, mRequest); + mCdmServiceReceiver.send(cdmResultCode, data); + } + + private void setResultAndFinish(int cdmResultCode) { + setResult(cdmResultCode == RESULT_CODE_SYSTEM_DATA_TRANSFER_ALLOWED + ? RESULT_OK : RESULT_CANCELED); + finish(); + } +} diff --git a/packages/ConnectivityT/framework-t/Android.bp b/packages/ConnectivityT/framework-t/Android.bp index d3d8bba16c7c..223bdcdd9c95 100644 --- a/packages/ConnectivityT/framework-t/Android.bp +++ b/packages/ConnectivityT/framework-t/Android.bp @@ -129,6 +129,11 @@ filegroup { "src/android/net/EthernetNetworkSpecifier.java", "src/android/net/IEthernetManager.aidl", "src/android/net/IEthernetServiceListener.aidl", + "src/android/net/IInternalNetworkManagementListener.aidl", + "src/android/net/InternalNetworkUpdateRequest.java", + "src/android/net/InternalNetworkUpdateRequest.aidl", + "src/android/net/InternalNetworkManagementException.java", + "src/android/net/InternalNetworkManagementException.aidl", "src/android/net/ITetheredInterfaceCallback.aidl", ], path: "src", diff --git a/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStats.java b/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStats.java index d33666d744d1..2b6570a6ecb0 100644 --- a/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStats.java +++ b/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStats.java @@ -556,7 +556,7 @@ public final class NetworkStats implements AutoCloseable { /** * Collects history results for uid and resets history enumeration index. */ - void startHistoryEnumeration(int uid, int tag, int state) { + void startHistoryUidEnumeration(int uid, int tag, int state) { mHistory = null; try { mHistory = mSession.getHistoryIntervalForUid(mTemplate, uid, @@ -571,6 +571,20 @@ public final class NetworkStats implements AutoCloseable { } /** + * Collects history results for network and resets history enumeration index. + */ + void startHistoryDeviceEnumeration() { + try { + mHistory = mSession.getHistoryIntervalForNetwork( + mTemplate, NetworkStatsHistory.FIELD_ALL, mStartTimeStamp, mEndTimeStamp); + } catch (RemoteException e) { + Log.w(TAG, e); + mHistory = null; + } + mEnumerationIndex = 0; + } + + /** * Starts uid enumeration for current user. * @throws RemoteException */ diff --git a/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java b/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java index f74edb1a01e5..8813f984519b 100644 --- a/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java +++ b/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java @@ -17,7 +17,10 @@ package android.app.usage; import static android.annotation.SystemApi.Client.MODULE_LIBRARIES; +import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; +import static android.net.NetworkCapabilities.TRANSPORT_WIFI; +import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; @@ -54,7 +57,6 @@ import com.android.net.module.util.NetworkIdentityUtils; import java.util.List; import java.util.Objects; -import java.util.Set; /** * Provides access to network usage history and statistics. Usage data is collected in @@ -124,6 +126,19 @@ public class NetworkStatsManager { private final Context mContext; private final INetworkStatsService mService; + /** + * Type constants for reading different types of Data Usage. + * @hide + */ + // @SystemApi(client = MODULE_LIBRARIES) + public static final String PREFIX_DEV = "dev"; + /** @hide */ + public static final String PREFIX_XT = "xt"; + /** @hide */ + public static final String PREFIX_UID = "uid"; + /** @hide */ + public static final String PREFIX_UID_TAG = "uid_tag"; + /** @hide */ public static final int FLAG_POLL_ON_OPEN = 1 << 0; /** @hide */ @@ -142,6 +157,11 @@ public class NetworkStatsManager { setAugmentWithSubscriptionPlan(true); } + /** @hide */ + public INetworkStatsService getBinder() { + return mService; + } + /** * Set poll on open flag to indicate the poll is needed before service gets statistics * result. This is default enabled. However, for any non-privileged caller, the poll might @@ -150,7 +170,13 @@ public class NetworkStatsManager { * @param pollOnOpen true if poll is needed. * @hide */ - // @SystemApi(client = MODULE_LIBRARIES) + // The system will ignore any non-default values for non-privileged + // processes, so processes that don't hold the appropriate permissions + // can make no use of this API. + @SystemApi(client = MODULE_LIBRARIES) + @RequiresPermission(anyOf = { + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, + android.Manifest.permission.NETWORK_STACK}) public void setPollOnOpen(boolean pollOnOpen) { if (pollOnOpen) { mFlags |= FLAG_POLL_ON_OPEN; @@ -204,9 +230,10 @@ public class NetworkStatsManager { */ @NonNull @WorkerThread - // @SystemApi(client = MODULE_LIBRARIES) + @SystemApi(client = MODULE_LIBRARIES) public Bucket querySummaryForDevice(@NonNull NetworkTemplate template, long startTime, long endTime) { + Objects.requireNonNull(template); try { NetworkStats stats = new NetworkStats(mContext, template, mFlags, startTime, endTime, mService); @@ -378,10 +405,11 @@ public class NetworkStatsManager { * @hide */ @NonNull - // @SystemApi(client = MODULE_LIBRARIES) + @SystemApi(client = MODULE_LIBRARIES) @WorkerThread public NetworkStats querySummary(@NonNull NetworkTemplate template, long startTime, long endTime) throws SecurityException { + Objects.requireNonNull(template); try { NetworkStats result = new NetworkStats(mContext, template, mFlags, startTime, endTime, mService); @@ -411,10 +439,11 @@ public class NetworkStatsManager { * @hide */ @NonNull - // @SystemApi(client = MODULE_LIBRARIES) + @SystemApi(client = MODULE_LIBRARIES) @WorkerThread public NetworkStats queryTaggedSummary(@NonNull NetworkTemplate template, long startTime, long endTime) throws SecurityException { + Objects.requireNonNull(template); try { NetworkStats result = new NetworkStats(mContext, template, mFlags, startTime, endTime, mService); @@ -427,6 +456,43 @@ public class NetworkStatsManager { } /** + * Query usage statistics details for networks matching a given {@link NetworkTemplate}. + * + * Result is not aggregated over time. This means buckets' start and + * end timestamps will be between 'startTime' and 'endTime' parameters. + * <p>Only includes buckets whose entire time period is included between + * startTime and endTime. Doesn't interpolate or return partial buckets. + * Since bucket length is in the order of hours, this + * method cannot be used to measure data usage on a fine grained time scale. + * This may take a long time, and apps should avoid calling this on their main thread. + * + * @param template Template used to match networks. See {@link NetworkTemplate}. + * @param startTime Start of period, in milliseconds since the Unix epoch, see + * {@link java.lang.System#currentTimeMillis}. + * @param endTime End of period, in milliseconds since the Unix epoch, see + * {@link java.lang.System#currentTimeMillis}. + * @return Statistics which is described above. + * @hide + */ + @NonNull + @SystemApi(client = MODULE_LIBRARIES) + @WorkerThread + public NetworkStats queryDetailsForDevice(@NonNull NetworkTemplate template, + long startTime, long endTime) { + Objects.requireNonNull(template); + try { + final NetworkStats result = + new NetworkStats(mContext, template, mFlags, startTime, endTime, mService); + result.startHistoryDeviceEnumeration(); + return result; + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + + return null; // To make the compiler happy. + } + + /** * Query network usage statistics details for a given uid. * This may take a long time, and apps should avoid calling this on their main thread. * @@ -492,7 +558,8 @@ public class NetworkStatsManager { * @param endTime End of period. Defined in terms of "Unix time", see * {@link java.lang.System#currentTimeMillis}. * @param uid UID of app - * @param tag TAG of interest. Use {@link NetworkStats.Bucket#TAG_NONE} for no tags. + * @param tag TAG of interest. Use {@link NetworkStats.Bucket#TAG_NONE} for aggregated data + * across all the tags. * @param state state of interest. Use {@link NetworkStats.Bucket#STATE_ALL} to aggregate * traffic from all states. * @return Statistics object or null if an error happened during statistics collection. @@ -507,21 +574,52 @@ public class NetworkStatsManager { return queryDetailsForUidTagState(template, startTime, endTime, uid, tag, state); } - /** @hide */ - public NetworkStats queryDetailsForUidTagState(NetworkTemplate template, + /** + * Query network usage statistics details for a given template, uid, tag, and state. + * + * Only usable for uids belonging to calling user. Result is not aggregated over time. + * This means buckets' start and end timestamps are going to be between 'startTime' and + * 'endTime' parameters. The uid is going to be the same as the 'uid' parameter, the tag + * the same as the 'tag' parameter, and the state the same as the 'state' parameter. + * defaultNetwork is going to be {@link NetworkStats.Bucket#DEFAULT_NETWORK_ALL}, + * metered is going to be {@link NetworkStats.Bucket#METERED_ALL}, and + * roaming is going to be {@link NetworkStats.Bucket#ROAMING_ALL}. + * <p>Only includes buckets that atomically occur in the inclusive time range. Doesn't + * interpolate across partial buckets. Since bucket length is in the order of hours, this + * method cannot be used to measure data usage on a fine grained time scale. + * This may take a long time, and apps should avoid calling this on their main thread. + * + * @param template Template used to match networks. See {@link NetworkTemplate}. + * @param startTime Start of period, in milliseconds since the Unix epoch, see + * {@link java.lang.System#currentTimeMillis}. + * @param endTime End of period, in milliseconds since the Unix epoch, see + * {@link java.lang.System#currentTimeMillis}. + * @param uid UID of app + * @param tag TAG of interest. Use {@link NetworkStats.Bucket#TAG_NONE} for aggregated data + * across all the tags. + * @param state state of interest. Use {@link NetworkStats.Bucket#STATE_ALL} to aggregate + * traffic from all states. + * @return Statistics which is described above. + * @hide + */ + @NonNull + @SystemApi(client = MODULE_LIBRARIES) + @WorkerThread + public NetworkStats queryDetailsForUidTagState(@NonNull NetworkTemplate template, long startTime, long endTime, int uid, int tag, int state) throws SecurityException { - - NetworkStats result; + Objects.requireNonNull(template); try { - result = new NetworkStats(mContext, template, mFlags, startTime, endTime, mService); - result.startHistoryEnumeration(uid, tag, state); + final NetworkStats result = new NetworkStats( + mContext, template, mFlags, startTime, endTime, mService); + result.startHistoryUidEnumeration(uid, tag, state); + return result; } catch (RemoteException e) { Log.e(TAG, "Error while querying stats for uid=" + uid + " tag=" + tag + " state=" + state, e); - return null; + e.rethrowFromSystemServer(); } - return result; + return null; // To make the compiler happy. } /** @@ -578,26 +676,49 @@ public class NetworkStatsManager { } /** - * Query realtime network usage statistics details with interfaces constrains. - * Return snapshot of current UID statistics, including any {@link TrafficStats#UID_TETHERING}, - * video calling data usage and count of network operations that set by - * {@link TrafficStats#incrementOperationCount}. The returned data doesn't include any - * statistics that is reported by {@link NetworkStatsProvider}. + * Query realtime mobile network usage statistics. + * + * Return a snapshot of current UID network statistics, as it applies + * to the mobile radios of the device. The snapshot will include any + * tethering traffic, video calling data usage and count of + * network operations set by {@link TrafficStats#incrementOperationCount} + * made over a mobile radio. + * The snapshot will not include any statistics that cannot be seen by + * the kernel, e.g. statistics reported by {@link NetworkStatsProvider}s. + * + * @hide + */ + @SystemApi + @RequiresPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) + @NonNull public android.net.NetworkStats getMobileUidStats() { + try { + return mService.getUidStatsForTransport(TRANSPORT_CELLULAR); + } catch (RemoteException e) { + if (DBG) Log.d(TAG, "Remote exception when get Mobile uid stats"); + throw e.rethrowFromSystemServer(); + } + } + + /** + * Query realtime Wi-Fi network usage statistics. * - * @param requiredIfaces A list of interfaces the stats should be restricted to, or - * {@link NetworkStats#INTERFACES_ALL}. + * Return a snapshot of current UID network statistics, as it applies + * to the Wi-Fi radios of the device. The snapshot will include any + * tethering traffic, video calling data usage and count of + * network operations set by {@link TrafficStats#incrementOperationCount} + * made over a Wi-Fi radio. + * The snapshot will not include any statistics that cannot be seen by + * the kernel, e.g. statistics reported by {@link NetworkStatsProvider}s. * * @hide */ - //@SystemApi + @SystemApi @RequiresPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) - @NonNull public android.net.NetworkStats getDetailedUidStats( - @NonNull Set<String> requiredIfaces) { - Objects.requireNonNull(requiredIfaces, "requiredIfaces cannot be null"); + @NonNull public android.net.NetworkStats getWifiUidStats() { try { - return mService.getDetailedUidStats(requiredIfaces.toArray(new String[0])); + return mService.getUidStatsForTransport(TRANSPORT_WIFI); } catch (RemoteException e) { - if (DBG) Log.d(TAG, "Remote exception when get detailed uid stats"); + if (DBG) Log.d(TAG, "Remote exception when get WiFi uid stats"); throw e.rethrowFromSystemServer(); } } @@ -877,7 +998,7 @@ public class NetworkStatsManager { * * @hide */ - // @SystemApi + @SystemApi(client = MODULE_LIBRARIES) @RequiresPermission(anyOf = { NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) @@ -890,17 +1011,18 @@ public class NetworkStatsManager { } /** - * Advise persistence threshold; may be overridden internally. + * Set default value of global alert bytes, the value will be clamped to [128kB, 2MB]. * * @hide */ - // @SystemApi + @SystemApi(client = MODULE_LIBRARIES) @RequiresPermission(anyOf = { NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, - android.Manifest.permission.NETWORK_STACK}) - public void advisePersistThreshold(long thresholdBytes) { + Manifest.permission.NETWORK_STACK}) + public void setDefaultGlobalAlert(long alertBytes) { try { - mService.advisePersistThreshold(thresholdBytes); + // TODO: Sync internal naming with the API surface. + mService.advisePersistThreshold(alertBytes); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -911,7 +1033,7 @@ public class NetworkStatsManager { * * @hide */ - // @SystemApi + @SystemApi(client = MODULE_LIBRARIES) @RequiresPermission(anyOf = { NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) @@ -927,9 +1049,17 @@ public class NetworkStatsManager { * Set the warning and limit to all registered custom network stats providers. * Note that invocation of any interface will be sent to all providers. * + * Asynchronicity notes : because traffic may be happening on the device at the same time, it + * doesn't make sense to wait for the warning and limit to be set – a caller still wouldn't + * know when exactly it was effective. All that can matter is that it's done quickly. Also, + * this method can't fail, so there is no status to return. All providers will see the new + * values soon. + * As such, this method returns immediately and sends the warning and limit to all providers + * as soon as possible through a one-way binder call. + * * @hide */ - // @SystemApi + @SystemApi(client = MODULE_LIBRARIES) @RequiresPermission(anyOf = { NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) diff --git a/packages/ConnectivityT/framework-t/src/android/net/DataUsageRequest.java b/packages/ConnectivityT/framework-t/src/android/net/DataUsageRequest.java index b06d515b3acf..f0ff46522d15 100644 --- a/packages/ConnectivityT/framework-t/src/android/net/DataUsageRequest.java +++ b/packages/ConnectivityT/framework-t/src/android/net/DataUsageRequest.java @@ -75,7 +75,7 @@ public final class DataUsageRequest implements Parcelable { @Override public DataUsageRequest createFromParcel(Parcel in) { int requestId = in.readInt(); - NetworkTemplate template = in.readParcelable(null); + NetworkTemplate template = in.readParcelable(null, android.net.NetworkTemplate.class); long thresholdInBytes = in.readLong(); DataUsageRequest result = new DataUsageRequest(requestId, template, thresholdInBytes); diff --git a/packages/ConnectivityT/framework-t/src/android/net/EthernetNetworkSpecifier.java b/packages/ConnectivityT/framework-t/src/android/net/EthernetNetworkSpecifier.java index 62c576144221..925d12b574a6 100644 --- a/packages/ConnectivityT/framework-t/src/android/net/EthernetNetworkSpecifier.java +++ b/packages/ConnectivityT/framework-t/src/android/net/EthernetNetworkSpecifier.java @@ -23,8 +23,6 @@ import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; -import com.android.internal.util.Preconditions; - import java.util.Objects; /** @@ -47,7 +45,9 @@ public final class EthernetNetworkSpecifier extends NetworkSpecifier implements * @param interfaceName Name of the ethernet interface the specifier refers to. */ public EthernetNetworkSpecifier(@NonNull String interfaceName) { - Preconditions.checkStringNotEmpty(interfaceName); + if (TextUtils.isEmpty(interfaceName)) { + throw new IllegalArgumentException(); + } mInterfaceName = interfaceName; } diff --git a/core/java/android/net/IInternalNetworkManagementListener.aidl b/packages/ConnectivityT/framework-t/src/android/net/IInternalNetworkManagementListener.aidl index 69cde3bd14e8..69cde3bd14e8 100644 --- a/core/java/android/net/IInternalNetworkManagementListener.aidl +++ b/packages/ConnectivityT/framework-t/src/android/net/IInternalNetworkManagementListener.aidl diff --git a/packages/ConnectivityT/framework-t/src/android/net/INetworkStatsService.aidl b/packages/ConnectivityT/framework-t/src/android/net/INetworkStatsService.aidl index a4babb543dbd..da0aa99b9370 100644 --- a/packages/ConnectivityT/framework-t/src/android/net/INetworkStatsService.aidl +++ b/packages/ConnectivityT/framework-t/src/android/net/INetworkStatsService.aidl @@ -49,14 +49,8 @@ interface INetworkStatsService { @UnsupportedAppUsage NetworkStats getDataLayerSnapshotForUid(int uid); - /** Get a detailed snapshot of stats since boot for all UIDs. - * - * <p>Results will not always be limited to stats on requiredIfaces when specified: stats for - * interfaces stacked on the specified interfaces, or for interfaces on which the specified - * interfaces are stacked on, will also be included. - * @param requiredIfaces Interface names to get data for, or {@link NetworkStats#INTERFACES_ALL}. - */ - NetworkStats getDetailedUidStats(in String[] requiredIfaces); + /** Get the transport NetworkStats for all UIDs since boot. */ + NetworkStats getUidStatsForTransport(int transport); /** Return set of any ifaces associated with mobile networks since boot. */ @UnsupportedAppUsage diff --git a/packages/ConnectivityT/framework-t/src/android/net/INetworkStatsSession.aidl b/packages/ConnectivityT/framework-t/src/android/net/INetworkStatsSession.aidl index babe0bfb9760..ab70be826f8e 100644 --- a/packages/ConnectivityT/framework-t/src/android/net/INetworkStatsSession.aidl +++ b/packages/ConnectivityT/framework-t/src/android/net/INetworkStatsSession.aidl @@ -32,6 +32,11 @@ interface INetworkStatsSession { /** Return historical network layer stats for traffic that matches template. */ @UnsupportedAppUsage NetworkStatsHistory getHistoryForNetwork(in NetworkTemplate template, int fields); + /** + * Return historical network layer stats for traffic that matches template, start and end + * timestamp. + */ + NetworkStatsHistory getHistoryIntervalForNetwork(in NetworkTemplate template, int fields, long start, long end); /** * Return network layer usage summary per UID for traffic that matches template. diff --git a/core/java/android/net/InternalNetworkManagementException.aidl b/packages/ConnectivityT/framework-t/src/android/net/InternalNetworkManagementException.aidl index dcce706989f6..dcce706989f6 100644 --- a/core/java/android/net/InternalNetworkManagementException.aidl +++ b/packages/ConnectivityT/framework-t/src/android/net/InternalNetworkManagementException.aidl diff --git a/core/java/android/net/InternalNetworkManagementException.java b/packages/ConnectivityT/framework-t/src/android/net/InternalNetworkManagementException.java index 7f4e403f2259..7f4e403f2259 100644 --- a/core/java/android/net/InternalNetworkManagementException.java +++ b/packages/ConnectivityT/framework-t/src/android/net/InternalNetworkManagementException.java diff --git a/core/java/android/net/InternalNetworkUpdateRequest.aidl b/packages/ConnectivityT/framework-t/src/android/net/InternalNetworkUpdateRequest.aidl index da00cb97afb4..da00cb97afb4 100644 --- a/core/java/android/net/InternalNetworkUpdateRequest.aidl +++ b/packages/ConnectivityT/framework-t/src/android/net/InternalNetworkUpdateRequest.aidl diff --git a/core/java/android/net/InternalNetworkUpdateRequest.java b/packages/ConnectivityT/framework-t/src/android/net/InternalNetworkUpdateRequest.java index f42c4b7c420d..f42c4b7c420d 100644 --- a/core/java/android/net/InternalNetworkUpdateRequest.java +++ b/packages/ConnectivityT/framework-t/src/android/net/InternalNetworkUpdateRequest.java diff --git a/packages/ConnectivityT/framework-t/src/android/net/IpSecConfig.java b/packages/ConnectivityT/framework-t/src/android/net/IpSecConfig.java index 575c5ed968f8..03bb187f119f 100644 --- a/packages/ConnectivityT/framework-t/src/android/net/IpSecConfig.java +++ b/packages/ConnectivityT/framework-t/src/android/net/IpSecConfig.java @@ -267,14 +267,14 @@ public final class IpSecConfig implements Parcelable { mMode = in.readInt(); mSourceAddress = in.readString(); mDestinationAddress = in.readString(); - mNetwork = (Network) in.readParcelable(Network.class.getClassLoader()); + mNetwork = (Network) in.readParcelable(Network.class.getClassLoader(), android.net.Network.class); mSpiResourceId = in.readInt(); mEncryption = - (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader()); + (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader(), android.net.IpSecAlgorithm.class); mAuthentication = - (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader()); + (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader(), android.net.IpSecAlgorithm.class); mAuthenticatedEncryption = - (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader()); + (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader(), android.net.IpSecAlgorithm.class); mEncapType = in.readInt(); mEncapSocketResourceId = in.readInt(); mEncapRemotePort = in.readInt(); diff --git a/packages/ConnectivityT/framework-t/src/android/net/IpSecManager.java b/packages/ConnectivityT/framework-t/src/android/net/IpSecManager.java index 49aa99b0975b..a423783bc1ca 100644 --- a/packages/ConnectivityT/framework-t/src/android/net/IpSecManager.java +++ b/packages/ConnectivityT/framework-t/src/android/net/IpSecManager.java @@ -27,6 +27,7 @@ import android.annotation.TestApi; import android.content.Context; import android.content.pm.PackageManager; import android.os.Binder; +import android.os.IBinder; import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.os.ServiceSpecificException; @@ -988,6 +989,29 @@ public final class IpSecManager { } /** + * @hide + */ + public IpSecTransformResponse createTransform(IpSecConfig config, IBinder binder, + String callingPackage) { + try { + return mService.createTransform(config, binder, callingPackage); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * @hide + */ + public void deleteTransform(int resourceId) { + try { + mService.deleteTransform(resourceId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Construct an instance of IpSecManager within an application context. * * @param context the application context for this manager diff --git a/packages/ConnectivityT/framework-t/src/android/net/IpSecTransform.java b/packages/ConnectivityT/framework-t/src/android/net/IpSecTransform.java index 36199a046cfc..68ae5de4ee70 100644 --- a/packages/ConnectivityT/framework-t/src/android/net/IpSecTransform.java +++ b/packages/ConnectivityT/framework-t/src/android/net/IpSecTransform.java @@ -26,9 +26,6 @@ import android.annotation.SystemApi; import android.content.Context; import android.content.pm.PackageManager; import android.os.Binder; -import android.os.IBinder; -import android.os.RemoteException; -import android.os.ServiceManager; import android.os.ServiceSpecificException; import android.util.Log; @@ -93,16 +90,9 @@ public final class IpSecTransform implements AutoCloseable { mResourceId = INVALID_RESOURCE_ID; } - private IIpSecService getIpSecService() { - IBinder b = ServiceManager.getService(android.content.Context.IPSEC_SERVICE); - if (b == null) { - throw new RemoteException("Failed to connect to IpSecService") - .rethrowAsRuntimeException(); - } - - return IIpSecService.Stub.asInterface(b); + private IpSecManager getIpSecManager(Context context) { + return context.getSystemService(IpSecManager.class); } - /** * Checks the result status and throws an appropriate exception if the status is not Status.OK. */ @@ -130,8 +120,7 @@ public final class IpSecTransform implements AutoCloseable { IpSecManager.SpiUnavailableException { synchronized (this) { try { - IIpSecService svc = getIpSecService(); - IpSecTransformResponse result = svc.createTransform( + IpSecTransformResponse result = getIpSecManager(mContext).createTransform( mConfig, new Binder(), mContext.getOpPackageName()); int status = result.status; checkResultStatus(status); @@ -140,8 +129,6 @@ public final class IpSecTransform implements AutoCloseable { mCloseGuard.open("build"); } catch (ServiceSpecificException e) { throw IpSecManager.rethrowUncheckedExceptionFromServiceSpecificException(e); - } catch (RemoteException e) { - throw e.rethrowAsRuntimeException(); } } @@ -177,10 +164,7 @@ public final class IpSecTransform implements AutoCloseable { return; } try { - IIpSecService svc = getIpSecService(); - svc.deleteTransform(mResourceId); - } catch (RemoteException e) { - throw e.rethrowAsRuntimeException(); + getIpSecManager(mContext).deleteTransform(mResourceId); } catch (Exception e) { // On close we swallow all random exceptions since failure to close is not // actionable by the user. diff --git a/packages/ConnectivityT/framework-t/src/android/net/IpSecUdpEncapResponse.java b/packages/ConnectivityT/framework-t/src/android/net/IpSecUdpEncapResponse.java index 732cf198a9cc..390af8236696 100644 --- a/packages/ConnectivityT/framework-t/src/android/net/IpSecUdpEncapResponse.java +++ b/packages/ConnectivityT/framework-t/src/android/net/IpSecUdpEncapResponse.java @@ -81,7 +81,7 @@ public final class IpSecUdpEncapResponse implements Parcelable { status = in.readInt(); resourceId = in.readInt(); port = in.readInt(); - fileDescriptor = in.readParcelable(ParcelFileDescriptor.class.getClassLoader()); + fileDescriptor = in.readParcelable(ParcelFileDescriptor.class.getClassLoader(), android.os.ParcelFileDescriptor.class); } @android.annotation.NonNull diff --git a/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentity.java b/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentity.java index 8f1115e065dd..d3d5a087ccac 100644 --- a/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentity.java +++ b/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentity.java @@ -16,18 +16,29 @@ package android.net; +import static android.annotation.SystemApi.Client.MODULE_LIBRARIES; +import static android.net.ConnectivityManager.TYPE_MOBILE; import static android.net.ConnectivityManager.TYPE_WIFI; +import static android.net.NetworkTemplate.NETWORK_TYPE_ALL; +import android.annotation.IntDef; +import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SuppressLint; +import android.annotation.SystemApi; import android.content.Context; import android.net.wifi.WifiInfo; import android.service.NetworkIdentityProto; -import android.telephony.Annotation.NetworkType; +import android.telephony.Annotation; +import android.telephony.TelephonyManager; import android.util.proto.ProtoOutputStream; +import com.android.net.module.util.CollectionUtils; import com.android.net.module.util.NetworkCapabilitiesUtils; import com.android.net.module.util.NetworkIdentityUtils; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Objects; @@ -37,11 +48,24 @@ import java.util.Objects; * * @hide */ -public class NetworkIdentity implements Comparable<NetworkIdentity> { +@SystemApi(client = MODULE_LIBRARIES) +public class NetworkIdentity { private static final String TAG = "NetworkIdentity"; + /** @hide */ + // TODO: Remove this after migrating all callers to use + // {@link NetworkTemplate#NETWORK_TYPE_ALL} instead. public static final int SUBTYPE_COMBINED = -1; + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = { "OEM_MANAGED_" }, flag = true, value = { + NetworkTemplate.OEM_MANAGED_NO, + NetworkTemplate.OEM_MANAGED_PAID, + NetworkTemplate.OEM_MANAGED_PRIVATE + }) + public @interface OemManaged{} + /** * Network has no {@code NetworkCapabilities#NET_CAPABILITY_OEM_*}. * @hide @@ -51,29 +75,32 @@ public class NetworkIdentity implements Comparable<NetworkIdentity> { * Network has {@link NetworkCapabilities#NET_CAPABILITY_OEM_PAID}. * @hide */ - public static final int OEM_PAID = 0x1; + public static final int OEM_PAID = 1 << 0; /** * Network has {@link NetworkCapabilities#NET_CAPABILITY_OEM_PRIVATE}. * @hide */ - public static final int OEM_PRIVATE = 0x2; + public static final int OEM_PRIVATE = 1 << 1; + + private static final long SUPPORTED_OEM_MANAGED_TYPES = OEM_PAID | OEM_PRIVATE; final int mType; - final int mSubType; + final int mRatType; final String mSubscriberId; - final String mNetworkId; + final String mWifiNetworkKey; final boolean mRoaming; final boolean mMetered; final boolean mDefaultNetwork; final int mOemManaged; + /** @hide */ public NetworkIdentity( - int type, int subType, String subscriberId, String networkId, boolean roaming, - boolean metered, boolean defaultNetwork, int oemManaged) { + int type, int ratType, @Nullable String subscriberId, @Nullable String wifiNetworkKey, + boolean roaming, boolean metered, boolean defaultNetwork, int oemManaged) { mType = type; - mSubType = subType; + mRatType = ratType; mSubscriberId = subscriberId; - mNetworkId = networkId; + mWifiNetworkKey = wifiNetworkKey; mRoaming = roaming; mMetered = metered; mDefaultNetwork = defaultNetwork; @@ -82,7 +109,7 @@ public class NetworkIdentity implements Comparable<NetworkIdentity> { @Override public int hashCode() { - return Objects.hash(mType, mSubType, mSubscriberId, mNetworkId, mRoaming, mMetered, + return Objects.hash(mType, mRatType, mSubscriberId, mWifiNetworkKey, mRoaming, mMetered, mDefaultNetwork, mOemManaged); } @@ -90,9 +117,9 @@ public class NetworkIdentity implements Comparable<NetworkIdentity> { public boolean equals(@Nullable Object obj) { if (obj instanceof NetworkIdentity) { final NetworkIdentity ident = (NetworkIdentity) obj; - return mType == ident.mType && mSubType == ident.mSubType && mRoaming == ident.mRoaming + return mType == ident.mType && mRatType == ident.mRatType && mRoaming == ident.mRoaming && Objects.equals(mSubscriberId, ident.mSubscriberId) - && Objects.equals(mNetworkId, ident.mNetworkId) + && Objects.equals(mWifiNetworkKey, ident.mWifiNetworkKey) && mMetered == ident.mMetered && mDefaultNetwork == ident.mDefaultNetwork && mOemManaged == ident.mOemManaged; @@ -104,18 +131,18 @@ public class NetworkIdentity implements Comparable<NetworkIdentity> { public String toString() { final StringBuilder builder = new StringBuilder("{"); builder.append("type=").append(mType); - builder.append(", subType="); - if (mSubType == SUBTYPE_COMBINED) { + builder.append(", ratType="); + if (mRatType == NETWORK_TYPE_ALL) { builder.append("COMBINED"); } else { - builder.append(mSubType); + builder.append(mRatType); } if (mSubscriberId != null) { builder.append(", subscriberId=") .append(NetworkIdentityUtils.scrubSubscriberId(mSubscriberId)); } - if (mNetworkId != null) { - builder.append(", networkId=").append(mNetworkId); + if (mWifiNetworkKey != null) { + builder.append(", wifiNetworkKey=").append(mWifiNetworkKey); } if (mRoaming) { builder.append(", ROAMING"); @@ -153,18 +180,14 @@ public class NetworkIdentity implements Comparable<NetworkIdentity> { } } + /** @hide */ public void dumpDebug(ProtoOutputStream proto, long tag) { final long start = proto.start(tag); proto.write(NetworkIdentityProto.TYPE, mType); - // Not dumping mSubType, subtypes are no longer supported. + // TODO: dump mRatType as well. - if (mSubscriberId != null) { - proto.write(NetworkIdentityProto.SUBSCRIBER_ID, - NetworkIdentityUtils.scrubSubscriberId(mSubscriberId)); - } - proto.write(NetworkIdentityProto.NETWORK_ID, mNetworkId); proto.write(NetworkIdentityProto.ROAMING, mRoaming); proto.write(NetworkIdentityProto.METERED, mMetered); proto.write(NetworkIdentityProto.DEFAULT_NETWORK, mDefaultNetwork); @@ -173,77 +196,99 @@ public class NetworkIdentity implements Comparable<NetworkIdentity> { proto.end(start); } + /** Get the network type of this instance. */ public int getType() { return mType; } - public int getSubType() { - return mSubType; + /** Get the Radio Access Technology(RAT) type of this instance. */ + public int getRatType() { + return mRatType; } + /** Get the Subscriber Id of this instance. */ + @Nullable public String getSubscriberId() { return mSubscriberId; } - public String getNetworkId() { - return mNetworkId; + /** Get the Wifi Network Key of this instance. See {@link WifiInfo#getNetworkKey()}. */ + @Nullable + public String getWifiNetworkKey() { + return mWifiNetworkKey; } + /** @hide */ + // TODO: Remove this function after all callers are removed. public boolean getRoaming() { return mRoaming; } + /** Return whether this network is roaming. */ + public boolean isRoaming() { + return mRoaming; + } + + /** @hide */ + // TODO: Remove this function after all callers are removed. public boolean getMetered() { return mMetered; } + /** Return whether this network is metered. */ + public boolean isMetered() { + return mMetered; + } + + /** @hide */ + // TODO: Remove this function after all callers are removed. public boolean getDefaultNetwork() { return mDefaultNetwork; } + /** Return whether this network is the default network. */ + public boolean isDefaultNetwork() { + return mDefaultNetwork; + } + + /** Get the OEM managed type of this instance. */ public int getOemManaged() { return mOemManaged; } /** - * Build a {@link NetworkIdentity} from the given {@link NetworkStateSnapshot} and - * {@code subType}, assuming that any mobile networks are using the current IMSI. - * The subType if applicable, should be set as one of the TelephonyManager.NETWORK_TYPE_* - * constants, or {@link android.telephony.TelephonyManager#NETWORK_TYPE_UNKNOWN} if not. + * Assemble a {@link NetworkIdentity} from the passed arguments. + * + * This methods builds an identity based on the capabilities of the network in the + * snapshot and other passed arguments. The identity is used as a key to record data usage. + * + * @param snapshot the snapshot of network state. See {@link NetworkStateSnapshot}. + * @param defaultNetwork whether the network is a default network. + * @param ratType the Radio Access Technology(RAT) type of the network. Or + * {@link TelephonyManager#NETWORK_TYPE_UNKNOWN} if not applicable. + * See {@code TelephonyManager.NETWORK_TYPE_*}. + * @hide + * @deprecated See {@link NetworkIdentity.Builder}. */ + // TODO: Remove this after all callers are migrated to use new Api. + @Deprecated + @NonNull public static NetworkIdentity buildNetworkIdentity(Context context, - NetworkStateSnapshot snapshot, boolean defaultNetwork, @NetworkType int subType) { - final int legacyType = snapshot.getLegacyType(); - - final String subscriberId = snapshot.getSubscriberId(); - String networkId = null; - boolean roaming = !snapshot.getNetworkCapabilities().hasCapability( - NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING); - boolean metered = !(snapshot.getNetworkCapabilities().hasCapability( - NetworkCapabilities.NET_CAPABILITY_NOT_METERED) - || snapshot.getNetworkCapabilities().hasCapability( - NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED)); - - final int oemManaged = getOemBitfield(snapshot.getNetworkCapabilities()); - - if (legacyType == TYPE_WIFI) { - final TransportInfo transportInfo = snapshot.getNetworkCapabilities() - .getTransportInfo(); - if (transportInfo instanceof WifiInfo) { - final WifiInfo info = (WifiInfo) transportInfo; - networkId = info != null ? info.getCurrentNetworkKey() : null; - } + @NonNull NetworkStateSnapshot snapshot, + boolean defaultNetwork, @Annotation.NetworkType int ratType) { + final NetworkIdentity.Builder builder = new NetworkIdentity.Builder() + .setNetworkStateSnapshot(snapshot).setDefaultNetwork(defaultNetwork); + if (snapshot.getLegacyType() == TYPE_MOBILE && ratType != NETWORK_TYPE_ALL) { + builder.setRatType(ratType); } - - return new NetworkIdentity(legacyType, subType, subscriberId, networkId, roaming, metered, - defaultNetwork, oemManaged); + return builder.build(); } /** * Builds a bitfield of {@code NetworkIdentity.OEM_*} based on {@link NetworkCapabilities}. * @hide */ - public static int getOemBitfield(NetworkCapabilities nc) { + public static int getOemBitfield(@NonNull NetworkCapabilities nc) { int oemManaged = OEM_NONE; if (nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_OEM_PAID)) { @@ -256,30 +301,265 @@ public class NetworkIdentity implements Comparable<NetworkIdentity> { return oemManaged; } - @Override - public int compareTo(NetworkIdentity another) { - int res = Integer.compare(mType, another.mType); + /** @hide */ + public static int compare(@NonNull NetworkIdentity left, @NonNull NetworkIdentity right) { + Objects.requireNonNull(right); + int res = Integer.compare(left.mType, right.mType); if (res == 0) { - res = Integer.compare(mSubType, another.mSubType); + res = Integer.compare(left.mRatType, right.mRatType); } - if (res == 0 && mSubscriberId != null && another.mSubscriberId != null) { - res = mSubscriberId.compareTo(another.mSubscriberId); + if (res == 0 && left.mSubscriberId != null && right.mSubscriberId != null) { + res = left.mSubscriberId.compareTo(right.mSubscriberId); } - if (res == 0 && mNetworkId != null && another.mNetworkId != null) { - res = mNetworkId.compareTo(another.mNetworkId); + if (res == 0 && left.mWifiNetworkKey != null && right.mWifiNetworkKey != null) { + res = left.mWifiNetworkKey.compareTo(right.mWifiNetworkKey); } if (res == 0) { - res = Boolean.compare(mRoaming, another.mRoaming); + res = Boolean.compare(left.mRoaming, right.mRoaming); } if (res == 0) { - res = Boolean.compare(mMetered, another.mMetered); + res = Boolean.compare(left.mMetered, right.mMetered); } if (res == 0) { - res = Boolean.compare(mDefaultNetwork, another.mDefaultNetwork); + res = Boolean.compare(left.mDefaultNetwork, right.mDefaultNetwork); } if (res == 0) { - res = Integer.compare(mOemManaged, another.mOemManaged); + res = Integer.compare(left.mOemManaged, right.mOemManaged); } return res; } + + /** + * Builder class for {@link NetworkIdentity}. + */ + public static final class Builder { + // Need to be synchronized with ConnectivityManager. + // TODO: Use {@link ConnectivityManager#MAX_NETWORK_TYPE} when this file is in the module. + private static final int MAX_NETWORK_TYPE = 18; // TYPE_TEST + private static final int MIN_NETWORK_TYPE = TYPE_MOBILE; + + private int mType; + private int mRatType; + private String mSubscriberId; + private String mWifiNetworkKey; + private boolean mRoaming; + private boolean mMetered; + private boolean mDefaultNetwork; + private int mOemManaged; + + /** + * Creates a new Builder. + */ + public Builder() { + // Initialize with default values. Will be overwritten by setters. + mType = ConnectivityManager.TYPE_NONE; + mRatType = NetworkTemplate.NETWORK_TYPE_ALL; + mSubscriberId = null; + mWifiNetworkKey = null; + mRoaming = false; + mMetered = false; + mDefaultNetwork = false; + mOemManaged = NetworkTemplate.OEM_MANAGED_NO; + } + + /** + * Add an {@link NetworkStateSnapshot} into the {@link NetworkIdentity} instance. + * This is a useful shorthand that will read from the snapshot and set the + * following fields, if they are set in the snapshot : + * - type + * - subscriberId + * - roaming + * - metered + * - oemManaged + * - wifiNetworkKey + * + * @param snapshot The target {@link NetworkStateSnapshot} object. + * @return The builder object. + */ + @SuppressLint("MissingGetterMatchingBuilder") + @NonNull + public Builder setNetworkStateSnapshot(@NonNull NetworkStateSnapshot snapshot) { + setType(snapshot.getLegacyType()); + + setSubscriberId(snapshot.getSubscriberId()); + setRoaming(!snapshot.getNetworkCapabilities().hasCapability( + NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING)); + setMetered(!(snapshot.getNetworkCapabilities().hasCapability( + NetworkCapabilities.NET_CAPABILITY_NOT_METERED) + || snapshot.getNetworkCapabilities().hasCapability( + NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED))); + + setOemManaged(getOemBitfield(snapshot.getNetworkCapabilities())); + + if (mType == TYPE_WIFI) { + final TransportInfo transportInfo = snapshot.getNetworkCapabilities() + .getTransportInfo(); + if (transportInfo instanceof WifiInfo) { + final WifiInfo info = (WifiInfo) transportInfo; + setWifiNetworkKey(info.getNetworkKey()); + } + } + return this; + } + + /** + * Set the network type of the network. + * + * @param type the network type. See {@link ConnectivityManager#TYPE_*}. + * + * @return this builder. + */ + @NonNull + public Builder setType(int type) { + // Include TYPE_NONE for compatibility, type field might not be filled by some + // networks such as test networks. + if ((type < MIN_NETWORK_TYPE || MAX_NETWORK_TYPE < type) + && type != ConnectivityManager.TYPE_NONE) { + throw new IllegalArgumentException("Invalid network type: " + type); + } + mType = type; + return this; + } + + /** + * Set the Radio Access Technology(RAT) type of the network. + * + * No RAT type is specified by default. Call clearRatType to reset. + * + * @param ratType the Radio Access Technology(RAT) type if applicable. See + * {@code TelephonyManager.NETWORK_TYPE_*}. + * + * @return this builder. + */ + @NonNull + public Builder setRatType(@Annotation.NetworkType int ratType) { + if (!CollectionUtils.contains(TelephonyManager.getAllNetworkTypes(), ratType) + && ratType != TelephonyManager.NETWORK_TYPE_UNKNOWN) { + throw new IllegalArgumentException("Invalid ratType " + ratType); + } + mRatType = ratType; + return this; + } + + /** + * Clear the Radio Access Technology(RAT) type of the network. + * + * @return this builder. + */ + @NonNull + public Builder clearRatType() { + mRatType = NetworkTemplate.NETWORK_TYPE_ALL; + return this; + } + + /** + * Set the Subscriber Id. + * + * @param subscriberId the Subscriber Id of the network. Or null if not applicable. + * @return this builder. + */ + @NonNull + public Builder setSubscriberId(@Nullable String subscriberId) { + mSubscriberId = subscriberId; + return this; + } + + /** + * Set the Wifi Network Key. + * + * @param wifiNetworkKey Wifi Network Key of the network, + * see {@link WifiInfo#getNetworkKey()}. + * Or null if not applicable. + * @return this builder. + */ + @NonNull + public Builder setWifiNetworkKey(@Nullable String wifiNetworkKey) { + mWifiNetworkKey = wifiNetworkKey; + return this; + } + + /** + * Set whether this network is roaming. + * + * This field is false by default. Call with false to reset. + * + * @param roaming the roaming status of the network. + * @return this builder. + */ + @NonNull + public Builder setRoaming(boolean roaming) { + mRoaming = roaming; + return this; + } + + /** + * Set whether this network is metered. + * + * This field is false by default. Call with false to reset. + * + * @param metered the meteredness of the network. + * @return this builder. + */ + @NonNull + public Builder setMetered(boolean metered) { + mMetered = metered; + return this; + } + + /** + * Set whether this network is the default network. + * + * This field is false by default. Call with false to reset. + * + * @param defaultNetwork the default network status of the network. + * @return this builder. + */ + @NonNull + public Builder setDefaultNetwork(boolean defaultNetwork) { + mDefaultNetwork = defaultNetwork; + return this; + } + + /** + * Set the OEM managed type. + * + * @param oemManaged Type of OEM managed network or unmanaged networks. + * See {@code NetworkTemplate#OEM_MANAGED_*}. + * @return this builder. + */ + @NonNull + public Builder setOemManaged(@OemManaged int oemManaged) { + // Assert input does not contain illegal oemManage bits. + if ((~SUPPORTED_OEM_MANAGED_TYPES & oemManaged) != 0) { + throw new IllegalArgumentException("Invalid value for OemManaged : " + oemManaged); + } + mOemManaged = oemManaged; + return this; + } + + private void ensureValidParameters() { + // Assert non-mobile network cannot have a ratType. + if (mType != TYPE_MOBILE && mRatType != NetworkTemplate.NETWORK_TYPE_ALL) { + throw new IllegalArgumentException( + "Invalid ratType " + mRatType + " for type " + mType); + } + + // Assert non-wifi network cannot have a wifi network key. + if (mType != TYPE_WIFI && mWifiNetworkKey != null) { + throw new IllegalArgumentException("Invalid wifi network key for type " + mType); + } + } + + /** + * Builds the instance of the {@link NetworkIdentity}. + * + * @return the built instance of {@link NetworkIdentity}. + */ + @NonNull + public NetworkIdentity build() { + ensureValidParameters(); + return new NetworkIdentity(mType, mRatType, mSubscriberId, mWifiNetworkKey, + mRoaming, mMetered, mDefaultNetwork, mOemManaged); + } + } } diff --git a/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentitySet.java b/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentitySet.java index abbebef85c8f..dfa347f6f12b 100644 --- a/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentitySet.java +++ b/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentitySet.java @@ -18,6 +18,7 @@ package android.net; import static android.net.ConnectivityManager.TYPE_MOBILE; +import android.annotation.NonNull; import android.service.NetworkIdentitySetProto; import android.util.proto.ProtoOutputStream; @@ -25,6 +26,8 @@ import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; import java.util.HashSet; +import java.util.Objects; +import java.util.Set; /** * Identity of a {@code iface}, defined by the set of {@link NetworkIdentity} @@ -32,8 +35,7 @@ import java.util.HashSet; * * @hide */ -public class NetworkIdentitySet extends HashSet<NetworkIdentity> implements - Comparable<NetworkIdentitySet> { +public class NetworkIdentitySet extends HashSet<NetworkIdentity> { private static final int VERSION_INIT = 1; private static final int VERSION_ADD_ROAMING = 2; private static final int VERSION_ADD_NETWORK_ID = 3; @@ -41,9 +43,19 @@ public class NetworkIdentitySet extends HashSet<NetworkIdentity> implements private static final int VERSION_ADD_DEFAULT_NETWORK = 5; private static final int VERSION_ADD_OEM_MANAGED_NETWORK = 6; + /** + * Construct a {@link NetworkIdentitySet} object. + */ public NetworkIdentitySet() { + super(); } + /** @hide */ + public NetworkIdentitySet(@NonNull Set<NetworkIdentity> ident) { + super(ident); + } + + /** @hide */ public NetworkIdentitySet(DataInput in) throws IOException { final int version = in.readInt(); final int size = in.readInt(); @@ -52,7 +64,7 @@ public class NetworkIdentitySet extends HashSet<NetworkIdentity> implements final int ignored = in.readInt(); } final int type = in.readInt(); - final int subType = in.readInt(); + final int ratType = in.readInt(); final String subscriberId = readOptionalString(in); final String networkId; if (version >= VERSION_ADD_NETWORK_ID) { @@ -91,63 +103,73 @@ public class NetworkIdentitySet extends HashSet<NetworkIdentity> implements oemNetCapabilities = NetworkIdentity.OEM_NONE; } - add(new NetworkIdentity(type, subType, subscriberId, networkId, roaming, metered, + add(new NetworkIdentity(type, ratType, subscriberId, networkId, roaming, metered, defaultNetwork, oemNetCapabilities)); } } /** * Method to serialize this object into a {@code DataOutput}. + * @hide */ public void writeToStream(DataOutput out) throws IOException { out.writeInt(VERSION_ADD_OEM_MANAGED_NETWORK); out.writeInt(size()); for (NetworkIdentity ident : this) { out.writeInt(ident.getType()); - out.writeInt(ident.getSubType()); + out.writeInt(ident.getRatType()); writeOptionalString(out, ident.getSubscriberId()); - writeOptionalString(out, ident.getNetworkId()); - out.writeBoolean(ident.getRoaming()); - out.writeBoolean(ident.getMetered()); - out.writeBoolean(ident.getDefaultNetwork()); + writeOptionalString(out, ident.getWifiNetworkKey()); + out.writeBoolean(ident.isRoaming()); + out.writeBoolean(ident.isMetered()); + out.writeBoolean(ident.isDefaultNetwork()); out.writeInt(ident.getOemManaged()); } } - /** @return whether any {@link NetworkIdentity} in this set is considered metered. */ + /** + * @return whether any {@link NetworkIdentity} in this set is considered metered. + * @hide + */ public boolean isAnyMemberMetered() { if (isEmpty()) { return false; } for (NetworkIdentity ident : this) { - if (ident.getMetered()) { + if (ident.isMetered()) { return true; } } return false; } - /** @return whether any {@link NetworkIdentity} in this set is considered roaming. */ + /** + * @return whether any {@link NetworkIdentity} in this set is considered roaming. + * @hide + */ public boolean isAnyMemberRoaming() { if (isEmpty()) { return false; } for (NetworkIdentity ident : this) { - if (ident.getRoaming()) { + if (ident.isRoaming()) { return true; } } return false; } - /** @return whether any {@link NetworkIdentity} in this set is considered on the default - network. */ + /** + * @return whether any {@link NetworkIdentity} in this set is considered on the default + * network. + * @hide + */ public boolean areAllMembersOnDefaultNetwork() { if (isEmpty()) { return true; } for (NetworkIdentity ident : this) { - if (!ident.getDefaultNetwork()) { + if (!ident.isDefaultNetwork()) { return false; } } @@ -171,18 +193,20 @@ public class NetworkIdentitySet extends HashSet<NetworkIdentity> implements } } - @Override - public int compareTo(NetworkIdentitySet another) { - if (isEmpty()) return -1; - if (another.isEmpty()) return 1; + public static int compare(@NonNull NetworkIdentitySet left, @NonNull NetworkIdentitySet right) { + Objects.requireNonNull(left); + Objects.requireNonNull(right); + if (left.isEmpty()) return -1; + if (right.isEmpty()) return 1; - final NetworkIdentity ident = iterator().next(); - final NetworkIdentity anotherIdent = another.iterator().next(); - return ident.compareTo(anotherIdent); + final NetworkIdentity leftIdent = left.iterator().next(); + final NetworkIdentity rightIdent = right.iterator().next(); + return NetworkIdentity.compare(leftIdent, rightIdent); } /** * Method to dump this object into proto debug file. + * @hide */ public void dumpDebug(ProtoOutputStream proto, long tag) { final long start = proto.start(tag); diff --git a/packages/ConnectivityT/framework-t/src/android/net/NetworkStateSnapshot.java b/packages/ConnectivityT/framework-t/src/android/net/NetworkStateSnapshot.java index 39156343924d..d577aa8fba54 100644 --- a/packages/ConnectivityT/framework-t/src/android/net/NetworkStateSnapshot.java +++ b/packages/ConnectivityT/framework-t/src/android/net/NetworkStateSnapshot.java @@ -73,9 +73,9 @@ public final class NetworkStateSnapshot implements Parcelable { /** @hide */ public NetworkStateSnapshot(@NonNull Parcel in) { - mNetwork = in.readParcelable(null); - mNetworkCapabilities = in.readParcelable(null); - mLinkProperties = in.readParcelable(null); + mNetwork = in.readParcelable(null, android.net.Network.class); + mNetworkCapabilities = in.readParcelable(null, android.net.NetworkCapabilities.class); + mLinkProperties = in.readParcelable(null, android.net.LinkProperties.class); mSubscriberId = in.readString(); mLegacyType = in.readInt(); } diff --git a/packages/ConnectivityT/framework-t/src/android/net/NetworkStats.java b/packages/ConnectivityT/framework-t/src/android/net/NetworkStats.java index 9d532e7929a6..9175809d9c7c 100644 --- a/packages/ConnectivityT/framework-t/src/android/net/NetworkStats.java +++ b/packages/ConnectivityT/framework-t/src/android/net/NetworkStats.java @@ -41,6 +41,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Arrays; import java.util.HashSet; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Objects; @@ -57,7 +58,7 @@ import java.util.function.Predicate; */ // @NotThreadSafe @SystemApi -public final class NetworkStats implements Parcelable { +public final class NetworkStats implements Parcelable, Iterable<NetworkStats.Entry> { private static final String TAG = "NetworkStats"; /** @@ -678,6 +679,35 @@ public final class NetworkStats implements Parcelable { } /** + * Iterate over Entry objects. + * + * Return an iterator of this object that will iterate through all contained Entry objects. + * + * This iterator does not support concurrent modification and makes no guarantee of fail-fast + * behavior. If any method that can mutate the contents of this object is called while + * iteration is in progress, either inside the loop or in another thread, then behavior is + * undefined. + * The remove() method is not implemented and will throw UnsupportedOperationException. + * @hide + */ + @SystemApi + @NonNull public Iterator<Entry> iterator() { + return new Iterator<Entry>() { + int mIndex = 0; + + @Override + public boolean hasNext() { + return mIndex < size; + } + + @Override + public Entry next() { + return getValues(mIndex++, null); + } + }; + } + + /** * Return specific stats entry. * @hide */ diff --git a/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsCollection.java b/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsCollection.java index 9f9d73f88851..58ca21fdfad0 100644 --- a/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsCollection.java +++ b/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsCollection.java @@ -32,6 +32,8 @@ import static android.text.format.DateUtils.WEEK_IN_MILLIS; import static com.android.net.module.util.NetworkStatsUtils.multiplySafeByRational; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.os.Binder; import android.service.NetworkStatsCollectionKeyProto; import android.service.NetworkStatsCollectionProto; @@ -70,6 +72,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.Objects; +import java.util.Set; /** * Collection of {@link NetworkStatsHistory}, stored based on combined key of @@ -77,6 +80,7 @@ import java.util.Objects; * * @hide */ +// @SystemApi(client = MODULE_LIBRARIES) public class NetworkStatsCollection implements FileRotator.Reader, FileRotator.Writer { private static final String TAG = NetworkStatsCollection.class.getSimpleName(); /** File header magic number: "ANET" */ @@ -100,15 +104,23 @@ public class NetworkStatsCollection implements FileRotator.Reader, FileRotator.W private long mTotalBytes; private boolean mDirty; + /** + * Construct a {@link NetworkStatsCollection} object. + * + * @param bucketDuration duration of the buckets in this object, in milliseconds. + * @hide + */ public NetworkStatsCollection(long bucketDuration) { mBucketDuration = bucketDuration; reset(); } + /** @hide */ public void clear() { reset(); } + /** @hide */ public void reset() { mStats.clear(); mStartMillis = Long.MAX_VALUE; @@ -117,6 +129,7 @@ public class NetworkStatsCollection implements FileRotator.Reader, FileRotator.W mDirty = false; } + /** @hide */ public long getStartMillis() { return mStartMillis; } @@ -124,6 +137,7 @@ public class NetworkStatsCollection implements FileRotator.Reader, FileRotator.W /** * Return first atomic bucket in this collection, which is more conservative * than {@link #mStartMillis}. + * @hide */ public long getFirstAtomicBucketMillis() { if (mStartMillis == Long.MAX_VALUE) { @@ -133,26 +147,32 @@ public class NetworkStatsCollection implements FileRotator.Reader, FileRotator.W } } + /** @hide */ public long getEndMillis() { return mEndMillis; } + /** @hide */ public long getTotalBytes() { return mTotalBytes; } + /** @hide */ public boolean isDirty() { return mDirty; } + /** @hide */ public void clearDirty() { mDirty = false; } + /** @hide */ public boolean isEmpty() { return mStartMillis == Long.MAX_VALUE && mEndMillis == Long.MIN_VALUE; } + /** @hide */ @VisibleForTesting public long roundUp(long time) { if (time == Long.MIN_VALUE || time == Long.MAX_VALUE @@ -168,6 +188,7 @@ public class NetworkStatsCollection implements FileRotator.Reader, FileRotator.W } } + /** @hide */ @VisibleForTesting public long roundDown(long time) { if (time == Long.MIN_VALUE || time == Long.MAX_VALUE @@ -182,10 +203,12 @@ public class NetworkStatsCollection implements FileRotator.Reader, FileRotator.W } } + /** @hide */ public int[] getRelevantUids(@NetworkStatsAccess.Level int accessLevel) { return getRelevantUids(accessLevel, Binder.getCallingUid()); } + /** @hide */ public int[] getRelevantUids(@NetworkStatsAccess.Level int accessLevel, final int callerUid) { final ArrayList<Integer> uids = new ArrayList<>(); @@ -206,6 +229,7 @@ public class NetworkStatsCollection implements FileRotator.Reader, FileRotator.W /** * Combine all {@link NetworkStatsHistory} in this collection which match * the requested parameters. + * @hide */ public NetworkStatsHistory getHistory(NetworkTemplate template, SubscriptionPlan augmentPlan, int uid, int set, int tag, int fields, long start, long end, @@ -331,6 +355,7 @@ public class NetworkStatsCollection implements FileRotator.Reader, FileRotator.W * @param end - end of the range, timestamp in milliseconds since the epoch. * @param accessLevel - caller access level. * @param callerUid - caller UID. + * @hide */ public NetworkStats getSummary(NetworkTemplate template, long start, long end, @NetworkStatsAccess.Level int accessLevel, int callerUid) { @@ -377,6 +402,7 @@ public class NetworkStatsCollection implements FileRotator.Reader, FileRotator.W /** * Record given {@link android.net.NetworkStats.Entry} into this collection. + * @hide */ public void recordData(NetworkIdentitySet ident, int uid, int set, int tag, long start, long end, NetworkStats.Entry entry) { @@ -387,8 +413,12 @@ public class NetworkStatsCollection implements FileRotator.Reader, FileRotator.W /** * Record given {@link NetworkStatsHistory} into this collection. + * + * @hide */ - private void recordHistory(Key key, NetworkStatsHistory history) { + public void recordHistory(@NonNull Key key, @NonNull NetworkStatsHistory history) { + Objects.requireNonNull(key); + Objects.requireNonNull(history); if (history.size() == 0) return; noteRecordedHistory(history.getStart(), history.getEnd(), history.getTotalBytes()); @@ -403,8 +433,11 @@ public class NetworkStatsCollection implements FileRotator.Reader, FileRotator.W /** * Record all {@link NetworkStatsHistory} contained in the given collection * into this collection. + * + * @hide */ - public void recordCollection(NetworkStatsCollection another) { + public void recordCollection(@NonNull NetworkStatsCollection another) { + Objects.requireNonNull(another); for (int i = 0; i < another.mStats.size(); i++) { final Key key = another.mStats.keyAt(i); final NetworkStatsHistory value = another.mStats.valueAt(i); @@ -433,6 +466,7 @@ public class NetworkStatsCollection implements FileRotator.Reader, FileRotator.W } } + /** @hide */ @Override public void read(InputStream in) throws IOException { read((DataInput) new DataInputStream(in)); @@ -472,6 +506,7 @@ public class NetworkStatsCollection implements FileRotator.Reader, FileRotator.W } } + /** @hide */ @Override public void write(OutputStream out) throws IOException { write((DataOutput) new DataOutputStream(out)); @@ -514,6 +549,7 @@ public class NetworkStatsCollection implements FileRotator.Reader, FileRotator.W * See {@code NetworkStatsService#maybeUpgradeLegacyStatsLocked}. * * @deprecated + * @hide */ @Deprecated public void readLegacyNetwork(File file) throws IOException { @@ -559,6 +595,7 @@ public class NetworkStatsCollection implements FileRotator.Reader, FileRotator.W * See {@code NetworkStatsService#maybeUpgradeLegacyStatsLocked}. * * @deprecated + * @hide */ @Deprecated public void readLegacyUid(File file, boolean onlyTags) throws IOException { @@ -629,6 +666,7 @@ public class NetworkStatsCollection implements FileRotator.Reader, FileRotator.W * Remove any {@link NetworkStatsHistory} attributed to the requested UID, * moving any {@link NetworkStats#TAG_NONE} series to * {@link TrafficStats#UID_REMOVED}. + * @hide */ public void removeUids(int[] uids) { final ArrayList<Key> knownKeys = new ArrayList<>(); @@ -665,10 +703,11 @@ public class NetworkStatsCollection implements FileRotator.Reader, FileRotator.W private ArrayList<Key> getSortedKeys() { final ArrayList<Key> keys = new ArrayList<>(); keys.addAll(mStats.keySet()); - Collections.sort(keys); + Collections.sort(keys, (left, right) -> Key.compare(left, right)); return keys; } + /** @hide */ public void dump(IndentingPrintWriter pw) { for (Key key : getSortedKeys()) { pw.print("ident="); pw.print(key.ident.toString()); @@ -683,6 +722,7 @@ public class NetworkStatsCollection implements FileRotator.Reader, FileRotator.W } } + /** @hide */ public void dumpDebug(ProtoOutputStream proto, long tag) { final long start = proto.start(tag); @@ -706,6 +746,7 @@ public class NetworkStatsCollection implements FileRotator.Reader, FileRotator.W proto.end(start); } + /** @hide */ public void dumpCheckin(PrintWriter pw, long start, long end) { dumpCheckin(pw, start, end, NetworkTemplate.buildTemplateMobileWildcard(), "cell"); dumpCheckin(pw, start, end, NetworkTemplate.buildTemplateWifiWildcard(), "wifi"); @@ -768,16 +809,37 @@ public class NetworkStatsCollection implements FileRotator.Reader, FileRotator.W return false; } - private static class Key implements Comparable<Key> { + /** + * the identifier that associate with the {@link NetworkStatsHistory} object to identify + * a certain record in the {@link NetworkStatsCollection} object. + */ + public static class Key { + /** @hide */ public final NetworkIdentitySet ident; + /** @hide */ public final int uid; + /** @hide */ public final int set; + /** @hide */ public final int tag; private final int mHashCode; - Key(NetworkIdentitySet ident, int uid, int set, int tag) { - this.ident = ident; + /** + * Construct a {@link Key} object. + * + * @param ident a Set of {@link NetworkIdentity} that associated with the record. + * @param uid Uid of the record. + * @param set Set of the record, see {@code NetworkStats#SET_*}. + * @param tag Tag of the record, see {@link TrafficStats#setThreadStatsTag(int)}. + */ + public Key(@NonNull Set<NetworkIdentity> ident, int uid, int set, int tag) { + this(new NetworkIdentitySet(Objects.requireNonNull(ident)), uid, set, tag); + } + + /** @hide */ + public Key(@NonNull NetworkIdentitySet ident, int uid, int set, int tag) { + this.ident = Objects.requireNonNull(ident); this.uid = uid; this.set = set; this.tag = tag; @@ -790,7 +852,7 @@ public class NetworkStatsCollection implements FileRotator.Reader, FileRotator.W } @Override - public boolean equals(Object obj) { + public boolean equals(@Nullable Object obj) { if (obj instanceof Key) { final Key key = (Key) obj; return uid == key.uid && set == key.set && tag == key.tag @@ -799,20 +861,22 @@ public class NetworkStatsCollection implements FileRotator.Reader, FileRotator.W return false; } - @Override - public int compareTo(Key another) { + /** @hide */ + public static int compare(@NonNull Key left, @NonNull Key right) { + Objects.requireNonNull(left); + Objects.requireNonNull(right); int res = 0; - if (ident != null && another.ident != null) { - res = ident.compareTo(another.ident); + if (left.ident != null && right.ident != null) { + res = NetworkIdentitySet.compare(left.ident, right.ident); } if (res == 0) { - res = Integer.compare(uid, another.uid); + res = Integer.compare(left.uid, right.uid); } if (res == 0) { - res = Integer.compare(set, another.set); + res = Integer.compare(left.set, right.set); } if (res == 0) { - res = Integer.compare(tag, another.tag); + res = Integer.compare(left.tag, right.tag); } return res; } diff --git a/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsHistory.java b/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsHistory.java index 428bc6df266a..78c137073aaa 100644 --- a/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsHistory.java +++ b/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsHistory.java @@ -16,6 +16,7 @@ package android.net; +import static android.annotation.SystemApi.Client.MODULE_LIBRARIES; import static android.net.NetworkStats.IFACE_ALL; import static android.net.NetworkStats.SET_DEFAULT; import static android.net.NetworkStats.TAG_NONE; @@ -30,6 +31,8 @@ import static android.text.format.DateUtils.SECOND_IN_MILLIS; import static com.android.net.module.util.NetworkStatsUtils.multiplySafeByRational; +import android.annotation.NonNull; +import android.annotation.SystemApi; import android.compat.annotation.UnsupportedAppUsage; import android.os.Build; import android.os.Parcel; @@ -50,7 +53,9 @@ import java.io.DataOutput; import java.io.IOException; import java.io.PrintWriter; import java.net.ProtocolException; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import java.util.Random; /** @@ -64,18 +69,25 @@ import java.util.Random; * * @hide */ -public class NetworkStatsHistory implements Parcelable { +@SystemApi(client = MODULE_LIBRARIES) +public final class NetworkStatsHistory implements Parcelable { private static final int VERSION_INIT = 1; private static final int VERSION_ADD_PACKETS = 2; private static final int VERSION_ADD_ACTIVE = 3; + /** @hide */ public static final int FIELD_ACTIVE_TIME = 0x01; + /** @hide */ public static final int FIELD_RX_BYTES = 0x02; + /** @hide */ public static final int FIELD_RX_PACKETS = 0x04; + /** @hide */ public static final int FIELD_TX_BYTES = 0x08; + /** @hide */ public static final int FIELD_TX_PACKETS = 0x10; + /** @hide */ public static final int FIELD_OPERATIONS = 0x20; - + /** @hide */ public static final int FIELD_ALL = 0xFFFFFFFF; private long bucketDuration; @@ -89,34 +101,171 @@ public class NetworkStatsHistory implements Parcelable { private long[] operations; private long totalBytes; - public static class Entry { + /** @hide */ + public NetworkStatsHistory(long bucketDuration, long[] bucketStart, long[] activeTime, + long[] rxBytes, long[] rxPackets, long[] txBytes, long[] txPackets, + long[] operations, int bucketCount, long totalBytes) { + this.bucketDuration = bucketDuration; + this.bucketStart = bucketStart; + this.activeTime = activeTime; + this.rxBytes = rxBytes; + this.rxPackets = rxPackets; + this.txBytes = txBytes; + this.txPackets = txPackets; + this.operations = operations; + this.bucketCount = bucketCount; + this.totalBytes = totalBytes; + } + + /** + * An instance to represent a single record in a {@link NetworkStatsHistory} object. + */ + public static final class Entry { + /** @hide */ public static final long UNKNOWN = -1; + /** @hide */ + // TODO: Migrate all callers to get duration from the history object and remove this field. @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public long bucketDuration; + /** @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public long bucketStart; + /** @hide */ public long activeTime; + /** @hide */ @UnsupportedAppUsage public long rxBytes; + /** @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public long rxPackets; + /** @hide */ @UnsupportedAppUsage public long txBytes; + /** @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public long txPackets; + /** @hide */ public long operations; + /** @hide */ + Entry() {} + + /** + * Construct a {@link Entry} instance to represent a single record in a + * {@link NetworkStatsHistory} object. + * + * @param bucketStart Start of period for this {@link Entry}, in milliseconds since the + * Unix epoch, see {@link java.lang.System#currentTimeMillis}. + * @param activeTime Active time for this {@link Entry}, in milliseconds. + * @param rxBytes Number of bytes received for this {@link Entry}. Statistics should + * represent the contents of IP packets, including IP headers. + * @param rxPackets Number of packets received for this {@link Entry}. Statistics should + * represent the contents of IP packets, including IP headers. + * @param txBytes Number of bytes transmitted for this {@link Entry}. Statistics should + * represent the contents of IP packets, including IP headers. + * @param txPackets Number of bytes transmitted for this {@link Entry}. Statistics should + * represent the contents of IP packets, including IP headers. + * @param operations count of network operations performed for this {@link Entry}. This can + * be used to derive bytes-per-operation. + */ + public Entry(long bucketStart, long activeTime, long rxBytes, + long rxPackets, long txBytes, long txPackets, long operations) { + this.bucketStart = bucketStart; + this.activeTime = activeTime; + this.rxBytes = rxBytes; + this.rxPackets = rxPackets; + this.txBytes = txBytes; + this.txPackets = txPackets; + this.operations = operations; + } + + /** + * Get start timestamp of the bucket's time interval, in milliseconds since the Unix epoch. + */ + public long getBucketStart() { + return bucketStart; + } + + /** + * Get active time of the bucket's time interval, in milliseconds. + */ + public long getActiveTime() { + return activeTime; + } + + /** Get number of bytes received for this {@link Entry}. */ + public long getRxBytes() { + return rxBytes; + } + + /** Get number of packets received for this {@link Entry}. */ + public long getRxPackets() { + return rxPackets; + } + + /** Get number of bytes transmitted for this {@link Entry}. */ + public long getTxBytes() { + return txBytes; + } + + /** Get number of packets transmitted for this {@link Entry}. */ + public long getTxPackets() { + return txPackets; + } + + /** Get count of network operations performed for this {@link Entry}. */ + public long getOperations() { + return operations; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o.getClass() != getClass()) return false; + Entry entry = (Entry) o; + return bucketStart == entry.bucketStart + && activeTime == entry.activeTime && rxBytes == entry.rxBytes + && rxPackets == entry.rxPackets && txBytes == entry.txBytes + && txPackets == entry.txPackets && operations == entry.operations; + } + + @Override + public int hashCode() { + return (int) (bucketStart * 2 + + activeTime * 3 + + rxBytes * 5 + + rxPackets * 7 + + txBytes * 11 + + txPackets * 13 + + operations * 17); + } + + @Override + public String toString() { + return "Entry{" + + "bucketStart=" + bucketStart + + ", activeTime=" + activeTime + + ", rxBytes=" + rxBytes + + ", rxPackets=" + rxPackets + + ", txBytes=" + txBytes + + ", txPackets=" + txPackets + + ", operations=" + operations + + "}"; + } } + /** @hide */ @UnsupportedAppUsage public NetworkStatsHistory(long bucketDuration) { this(bucketDuration, 10, FIELD_ALL); } + /** @hide */ public NetworkStatsHistory(long bucketDuration, int initialSize) { this(bucketDuration, initialSize, FIELD_ALL); } + /** @hide */ public NetworkStatsHistory(long bucketDuration, int initialSize, int fields) { this.bucketDuration = bucketDuration; bucketStart = new long[initialSize]; @@ -130,11 +279,13 @@ public class NetworkStatsHistory implements Parcelable { totalBytes = 0; } + /** @hide */ public NetworkStatsHistory(NetworkStatsHistory existing, long bucketDuration) { this(bucketDuration, existing.estimateResizeBuckets(bucketDuration)); recordEntireHistory(existing); } + /** @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public NetworkStatsHistory(Parcel in) { bucketDuration = in.readLong(); @@ -150,7 +301,7 @@ public class NetworkStatsHistory implements Parcelable { } @Override - public void writeToParcel(Parcel out, int flags) { + public void writeToParcel(@NonNull Parcel out, int flags) { out.writeLong(bucketDuration); writeLongArray(out, bucketStart, bucketCount); writeLongArray(out, activeTime, bucketCount); @@ -162,6 +313,7 @@ public class NetworkStatsHistory implements Parcelable { out.writeLong(totalBytes); } + /** @hide */ public NetworkStatsHistory(DataInput in) throws IOException { final int version = in.readInt(); switch (version) { @@ -204,6 +356,7 @@ public class NetworkStatsHistory implements Parcelable { } } + /** @hide */ public void writeToStream(DataOutput out) throws IOException { out.writeInt(VERSION_ADD_ACTIVE); out.writeLong(bucketDuration); @@ -221,15 +374,18 @@ public class NetworkStatsHistory implements Parcelable { return 0; } + /** @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public int size() { return bucketCount; } + /** @hide */ public long getBucketDuration() { return bucketDuration; } + /** @hide */ @UnsupportedAppUsage public long getStart() { if (bucketCount > 0) { @@ -239,6 +395,7 @@ public class NetworkStatsHistory implements Parcelable { } } + /** @hide */ @UnsupportedAppUsage public long getEnd() { if (bucketCount > 0) { @@ -250,6 +407,7 @@ public class NetworkStatsHistory implements Parcelable { /** * Return total bytes represented by this history. + * @hide */ public long getTotalBytes() { return totalBytes; @@ -258,6 +416,7 @@ public class NetworkStatsHistory implements Parcelable { /** * Return index of bucket that contains or is immediately before the * requested time. + * @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public int getIndexBefore(long time) { @@ -273,6 +432,7 @@ public class NetworkStatsHistory implements Parcelable { /** * Return index of bucket that contains or is immediately after the * requested time. + * @hide */ public int getIndexAfter(long time) { int index = Arrays.binarySearch(bucketStart, 0, bucketCount, time); @@ -286,6 +446,7 @@ public class NetworkStatsHistory implements Parcelable { /** * Return specific stats entry. + * @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public Entry getValues(int i, Entry recycle) { @@ -301,6 +462,23 @@ public class NetworkStatsHistory implements Parcelable { return entry; } + /** + * Get List of {@link Entry} of the {@link NetworkStatsHistory} instance. + * + * @return + */ + @NonNull + public List<Entry> getEntries() { + // TODO: Return a wrapper that uses this list instead, to prevent the returned result + // from being changed. + final ArrayList<Entry> ret = new ArrayList<>(size()); + for (int i = 0; i < size(); i++) { + ret.add(getValues(i, null /* recycle */)); + } + return ret; + } + + /** @hide */ public void setValues(int i, Entry entry) { // Unwind old values if (rxBytes != null) totalBytes -= rxBytes[i]; @@ -322,6 +500,7 @@ public class NetworkStatsHistory implements Parcelable { /** * Record that data traffic occurred in the given time range. Will * distribute across internal buckets, creating new buckets as needed. + * @hide */ @Deprecated public void recordData(long start, long end, long rxBytes, long txBytes) { @@ -332,6 +511,7 @@ public class NetworkStatsHistory implements Parcelable { /** * Record that data traffic occurred in the given time range. Will * distribute across internal buckets, creating new buckets as needed. + * @hide */ public void recordData(long start, long end, NetworkStats.Entry entry) { long rxBytes = entry.rxBytes; @@ -392,6 +572,7 @@ public class NetworkStatsHistory implements Parcelable { /** * Record an entire {@link NetworkStatsHistory} into this history. Usually * for combining together stats for external reporting. + * @hide */ @UnsupportedAppUsage public void recordEntireHistory(NetworkStatsHistory input) { @@ -402,6 +583,7 @@ public class NetworkStatsHistory implements Parcelable { * Record given {@link NetworkStatsHistory} into this history, copying only * buckets that atomically occur in the inclusive time range. Doesn't * interpolate across partial buckets. + * @hide */ public void recordHistory(NetworkStatsHistory input, long start, long end) { final NetworkStats.Entry entry = new NetworkStats.Entry( @@ -483,6 +665,7 @@ public class NetworkStatsHistory implements Parcelable { /** * Clear all data stored in this object. + * @hide */ public void clear() { bucketStart = EmptyArray.LONG; @@ -498,9 +681,10 @@ public class NetworkStatsHistory implements Parcelable { /** * Remove buckets older than requested cutoff. + * @hide */ - @Deprecated public void removeBucketsBefore(long cutoff) { + // TODO: Consider use getIndexBefore. int i; for (i = 0; i < bucketCount; i++) { final long curStart = bucketStart[i]; @@ -522,7 +706,9 @@ public class NetworkStatsHistory implements Parcelable { if (operations != null) operations = Arrays.copyOfRange(operations, i, length); bucketCount -= i; - // TODO: subtract removed values from totalBytes + totalBytes = 0; + if (rxBytes != null) totalBytes += CollectionUtils.total(rxBytes); + if (txBytes != null) totalBytes += CollectionUtils.total(txBytes); } } @@ -536,6 +722,7 @@ public class NetworkStatsHistory implements Parcelable { * @param start - start of the range, timestamp in milliseconds since the epoch. * @param end - end of the range, timestamp in milliseconds since the epoch. * @param recycle - entry instance for performance, could be null. + * @hide */ @UnsupportedAppUsage public Entry getValues(long start, long end, Entry recycle) { @@ -550,6 +737,7 @@ public class NetworkStatsHistory implements Parcelable { * @param end - end of the range, timestamp in milliseconds since the epoch. * @param now - current timestamp in milliseconds since the epoch (wall clock). * @param recycle - entry instance for performance, could be null. + * @hide */ @UnsupportedAppUsage public Entry getValues(long start, long end, long now, Entry recycle) { @@ -613,6 +801,7 @@ public class NetworkStatsHistory implements Parcelable { /** * @deprecated only for temporary testing + * @hide */ @Deprecated public void generateRandom(long start, long end, long bytes) { @@ -631,6 +820,7 @@ public class NetworkStatsHistory implements Parcelable { /** * @deprecated only for temporary testing + * @hide */ @Deprecated public void generateRandom(long start, long end, long rxBytes, long rxPackets, long txBytes, @@ -660,12 +850,14 @@ public class NetworkStatsHistory implements Parcelable { } } + /** @hide */ public static long randomLong(Random r, long start, long end) { return (long) (start + (r.nextFloat() * (end - start))); } /** * Quickly determine if this history intersects with given window. + * @hide */ public boolean intersects(long start, long end) { final long dataStart = getStart(); @@ -677,6 +869,7 @@ public class NetworkStatsHistory implements Parcelable { return false; } + /** @hide */ public void dump(IndentingPrintWriter pw, boolean fullHistory) { pw.print("NetworkStatsHistory: bucketDuration="); pw.println(bucketDuration / SECOND_IN_MILLIS); @@ -700,6 +893,7 @@ public class NetworkStatsHistory implements Parcelable { pw.decreaseIndent(); } + /** @hide */ public void dumpCheckin(PrintWriter pw) { pw.print("d,"); pw.print(bucketDuration / SECOND_IN_MILLIS); @@ -717,6 +911,7 @@ public class NetworkStatsHistory implements Parcelable { } } + /** @hide */ public void dumpDebug(ProtoOutputStream proto, long tag) { final long start = proto.start(tag); @@ -776,6 +971,7 @@ public class NetworkStatsHistory implements Parcelable { if (array != null) array[i] += value; } + /** @hide */ public int estimateResizeBuckets(long newBucketDuration) { return (int) (size() * getBucketDuration() / newBucketDuration); } @@ -783,6 +979,7 @@ public class NetworkStatsHistory implements Parcelable { /** * Utility methods for interacting with {@link DataInputStream} and * {@link DataOutputStream}, mostly dealing with writing partial arrays. + * @hide */ public static class DataStreamUtils { @Deprecated @@ -857,6 +1054,7 @@ public class NetworkStatsHistory implements Parcelable { /** * Utility methods for interacting with {@link Parcel} structures, mostly * dealing with writing partial arrays. + * @hide */ public static class ParcelUtils { public static long[] readLongArray(Parcel in) { @@ -884,4 +1082,80 @@ public class NetworkStatsHistory implements Parcelable { } } + /** + * Builder class for {@link NetworkStatsHistory}. + */ + public static final class Builder { + private final long mBucketDuration; + private final List<Long> mBucketStart; + private final List<Long> mActiveTime; + private final List<Long> mRxBytes; + private final List<Long> mRxPackets; + private final List<Long> mTxBytes; + private final List<Long> mTxPackets; + private final List<Long> mOperations; + + /** + * Creates a new Builder with given bucket duration and initial capacity to construct + * {@link NetworkStatsHistory} objects. + * + * @param bucketDuration Duration of the buckets of the object, in milliseconds. + * @param initialCapacity Estimated number of records. + */ + public Builder(long bucketDuration, int initialCapacity) { + mBucketDuration = bucketDuration; + mBucketStart = new ArrayList<>(initialCapacity); + mActiveTime = new ArrayList<>(initialCapacity); + mRxBytes = new ArrayList<>(initialCapacity); + mRxPackets = new ArrayList<>(initialCapacity); + mTxBytes = new ArrayList<>(initialCapacity); + mTxPackets = new ArrayList<>(initialCapacity); + mOperations = new ArrayList<>(initialCapacity); + } + + /** + * Add an {@link Entry} into the {@link NetworkStatsHistory} instance. + * + * @param entry The target {@link Entry} object. + * @return The builder object. + */ + @NonNull + public Builder addEntry(@NonNull Entry entry) { + mBucketStart.add(entry.bucketStart); + mActiveTime.add(entry.activeTime); + mRxBytes.add(entry.rxBytes); + mRxPackets.add(entry.rxPackets); + mTxBytes.add(entry.txBytes); + mTxPackets.add(entry.txPackets); + mOperations.add(entry.operations); + return this; + } + + private static long sum(@NonNull List<Long> list) { + long sum = 0; + for (long entry : list) { + sum += entry; + } + return sum; + } + + /** + * Builds the instance of the {@link NetworkStatsHistory}. + * + * @return the built instance of {@link NetworkStatsHistory}. + */ + @NonNull + public NetworkStatsHistory build() { + return new NetworkStatsHistory(mBucketDuration, + CollectionUtils.toLongArray(mBucketStart), + CollectionUtils.toLongArray(mActiveTime), + CollectionUtils.toLongArray(mRxBytes), + CollectionUtils.toLongArray(mRxPackets), + CollectionUtils.toLongArray(mTxBytes), + CollectionUtils.toLongArray(mTxPackets), + CollectionUtils.toLongArray(mOperations), + mBucketStart.size(), + sum(mRxBytes) + sum(mTxBytes)); + } + } } diff --git a/packages/ConnectivityT/framework-t/src/android/net/NetworkTemplate.java b/packages/ConnectivityT/framework-t/src/android/net/NetworkTemplate.java index e9084b019668..cad80752b8e7 100644 --- a/packages/ConnectivityT/framework-t/src/android/net/NetworkTemplate.java +++ b/packages/ConnectivityT/framework-t/src/android/net/NetworkTemplate.java @@ -263,7 +263,7 @@ public final class NetworkTemplate implements Parcelable { * Template to match {@link ConnectivityManager#TYPE_WIFI} networks with the * given key of the wifi network. * - * @param wifiNetworkKey key of the wifi network. see {@link WifiInfo#getCurrentNetworkKey()} + * @param wifiNetworkKey key of the wifi network. see {@link WifiInfo#getNetworkKey()} * to know details about the key. * @hide */ @@ -283,7 +283,7 @@ public final class NetworkTemplate implements Parcelable { * Call with {@link #WIFI_NETWORK_KEY_ALL} for {@code wifiNetworkKey} to get result regardless * of key of the wifi network. * - * @param wifiNetworkKey key of the wifi network. see {@link WifiInfo#getCurrentNetworkKey()} + * @param wifiNetworkKey key of the wifi network. see {@link WifiInfo#getNetworkKey()} * to know details about the key. * @param subscriberId the IMSI associated to this wifi network. * @@ -364,7 +364,7 @@ public final class NetworkTemplate implements Parcelable { private final int mMetered; private final int mRoaming; private final int mDefaultNetwork; - private final int mSubType; + private final int mRatType; /** * The subscriber Id match rule defines how the template should match networks with * specific subscriberId(s). See NetworkTemplate#SUBSCRIBER_ID_MATCH_RULE_* for more detail. @@ -413,18 +413,18 @@ public final class NetworkTemplate implements Parcelable { /** @hide */ // TODO: Remove it after updating all of the caller. public NetworkTemplate(int matchRule, String subscriberId, String[] matchSubscriberIds, - String wifiNetworkKey, int metered, int roaming, int defaultNetwork, int subType, + String wifiNetworkKey, int metered, int roaming, int defaultNetwork, int ratType, int oemManaged) { this(matchRule, subscriberId, matchSubscriberIds, wifiNetworkKey != null ? new String[] { wifiNetworkKey } : new String[0], - metered, roaming, defaultNetwork, subType, oemManaged, + metered, roaming, defaultNetwork, ratType, oemManaged, NetworkStatsUtils.SUBSCRIBER_ID_MATCH_RULE_EXACT); } /** @hide */ public NetworkTemplate(int matchRule, String subscriberId, String[] matchSubscriberIds, String[] matchWifiNetworkKeys, int metered, int roaming, - int defaultNetwork, int subType, int oemManaged, int subscriberIdMatchRule) { + int defaultNetwork, int ratType, int oemManaged, int subscriberIdMatchRule) { Objects.requireNonNull(matchWifiNetworkKeys); mMatchRule = matchRule; mSubscriberId = subscriberId; @@ -435,7 +435,7 @@ public final class NetworkTemplate implements Parcelable { mMetered = metered; mRoaming = roaming; mDefaultNetwork = defaultNetwork; - mSubType = subType; + mRatType = ratType; mOemManaged = oemManaged; mSubscriberIdMatchRule = subscriberIdMatchRule; checkValidSubscriberIdMatchRule(matchRule, subscriberIdMatchRule); @@ -453,7 +453,7 @@ public final class NetworkTemplate implements Parcelable { mMetered = in.readInt(); mRoaming = in.readInt(); mDefaultNetwork = in.readInt(); - mSubType = in.readInt(); + mRatType = in.readInt(); mOemManaged = in.readInt(); mSubscriberIdMatchRule = in.readInt(); } @@ -467,7 +467,7 @@ public final class NetworkTemplate implements Parcelable { dest.writeInt(mMetered); dest.writeInt(mRoaming); dest.writeInt(mDefaultNetwork); - dest.writeInt(mSubType); + dest.writeInt(mRatType); dest.writeInt(mOemManaged); dest.writeInt(mSubscriberIdMatchRule); } @@ -500,8 +500,8 @@ public final class NetworkTemplate implements Parcelable { builder.append(", defaultNetwork=").append(NetworkStats.defaultNetworkToString( mDefaultNetwork)); } - if (mSubType != NETWORK_TYPE_ALL) { - builder.append(", subType=").append(mSubType); + if (mRatType != NETWORK_TYPE_ALL) { + builder.append(", ratType=").append(mRatType); } if (mOemManaged != OEM_MANAGED_ALL) { builder.append(", oemManaged=").append(getOemManagedNames(mOemManaged)); @@ -514,7 +514,7 @@ public final class NetworkTemplate implements Parcelable { @Override public int hashCode() { return Objects.hash(mMatchRule, mSubscriberId, Arrays.hashCode(mMatchWifiNetworkKeys), - mMetered, mRoaming, mDefaultNetwork, mSubType, mOemManaged, mSubscriberIdMatchRule); + mMetered, mRoaming, mDefaultNetwork, mRatType, mOemManaged, mSubscriberIdMatchRule); } @Override @@ -526,7 +526,7 @@ public final class NetworkTemplate implements Parcelable { && mMetered == other.mMetered && mRoaming == other.mRoaming && mDefaultNetwork == other.mDefaultNetwork - && mSubType == other.mSubType + && mRatType == other.mRatType && mOemManaged == other.mOemManaged && mSubscriberIdMatchRule == other.mSubscriberIdMatchRule && Arrays.equals(mMatchWifiNetworkKeys, other.mMatchWifiNetworkKeys); @@ -593,7 +593,7 @@ public final class NetworkTemplate implements Parcelable { /** * Get the set of Wifi Network Keys of the template. - * See {@link WifiInfo#getCurrentNetworkKey()}. + * See {@link WifiInfo#getNetworkKey()}. */ @NonNull public Set<String> getWifiNetworkKeys() { @@ -635,7 +635,7 @@ public final class NetworkTemplate implements Parcelable { * Get the Radio Access Technology(RAT) type filter of the template. */ public int getRatType() { - return mSubType; + return mRatType; } /** @@ -708,8 +708,8 @@ public final class NetworkTemplate implements Parcelable { } private boolean matchesCollapsedRatType(NetworkIdentity ident) { - return mSubType == NETWORK_TYPE_ALL - || getCollapsedRatType(mSubType) == getCollapsedRatType(ident.mSubType); + return mRatType == NETWORK_TYPE_ALL + || getCollapsedRatType(mRatType) == getCollapsedRatType(ident.mRatType); } /** @@ -729,7 +729,7 @@ public final class NetworkTemplate implements Parcelable { * Returns true when the key matches, or when {@code mMatchWifiNetworkKeys} is * empty. * - * @param wifiNetworkKey key of the wifi network. see {@link WifiInfo#getCurrentNetworkKey()} + * @param wifiNetworkKey key of the wifi network. see {@link WifiInfo#getNetworkKey()} * to know details about the key. */ private boolean matchesWifiNetworkKey(@NonNull String wifiNetworkKey) { @@ -837,7 +837,7 @@ public final class NetworkTemplate implements Parcelable { switch (ident.mType) { case TYPE_WIFI: return matchesSubscriberId(ident.mSubscriberId) - && matchesWifiNetworkKey(ident.mNetworkId); + && matchesWifiNetworkKey(ident.mWifiNetworkKey); default: return false; } @@ -1059,9 +1059,9 @@ public final class NetworkTemplate implements Parcelable { * the intention of matching any Wifi Network Key. * * @param wifiNetworkKeys the list of Wifi Network Key, - * see {@link WifiInfo#getCurrentNetworkKey()}. + * see {@link WifiInfo#getNetworkKey()}. * Or an empty list to match all networks. - * Note that {@code getCurrentNetworkKey()} might get null key + * Note that {@code getNetworkKey()} might get null key * when wifi disconnects. However, the caller should never invoke * this function with a null Wifi Network Key since such statistics * never exists. diff --git a/packages/ConnectivityT/framework-t/src/android/net/TrafficStats.java b/packages/ConnectivityT/framework-t/src/android/net/TrafficStats.java index 1af32bf5524c..c803a723ba83 100644 --- a/packages/ConnectivityT/framework-t/src/android/net/TrafficStats.java +++ b/packages/ConnectivityT/framework-t/src/android/net/TrafficStats.java @@ -17,7 +17,6 @@ package android.net; import android.annotation.NonNull; -import android.annotation.Nullable; import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.annotation.TestApi; @@ -27,8 +26,8 @@ import android.app.usage.NetworkStatsManager; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.media.MediaPlayer; +import android.os.Binder; import android.os.Build; -import android.os.IBinder; import android.os.RemoteException; import com.android.server.NetworkManagementSocketTagger; @@ -37,8 +36,6 @@ import dalvik.system.SocketTagger; import java.io.FileDescriptor; import java.io.IOException; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; import java.net.DatagramSocket; import java.net.Socket; import java.net.SocketException; @@ -177,25 +174,12 @@ public class TrafficStats { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562) private synchronized static INetworkStatsService getStatsService() { if (sStatsService == null) { - sStatsService = getStatsBinder(); + throw new IllegalStateException("TrafficStats not initialized, uid=" + + Binder.getCallingUid()); } return sStatsService; } - @Nullable - private static INetworkStatsService getStatsBinder() { - try { - final Method getServiceMethod = Class.forName("android.os.ServiceManager") - .getDeclaredMethod("getService", new Class[]{String.class}); - final IBinder binder = (IBinder) getServiceMethod.invoke( - null, Context.NETWORK_STATS_SERVICE); - return INetworkStatsService.Stub.asInterface(binder); - } catch (NoSuchMethodException | ClassNotFoundException | IllegalAccessException - | InvocationTargetException e) { - throw new NullPointerException("Cannot get INetworkStatsService: " + e); - } - } - /** * Snapshot of {@link NetworkStats} when the currently active profiling * session started, or {@code null} if no session active. @@ -210,6 +194,26 @@ public class TrafficStats { private static final String LOOPBACK_IFACE = "lo"; /** + * Initialization {@link TrafficStats} with the context, to + * allow {@link TrafficStats} to fetch the needed binder. + * + * @param context a long-lived context, such as the application context or system + * server context. + * @hide + */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + @SuppressLint("VisiblySynchronized") + public static synchronized void init(@NonNull final Context context) { + if (sStatsService != null) { + throw new IllegalStateException("TrafficStats is already initialized, uid=" + + Binder.getCallingUid()); + } + final NetworkStatsManager statsManager = + context.getSystemService(NetworkStatsManager.class); + sStatsService = statsManager.getBinder(); + } + + /** * Set active tag to use when accounting {@link Socket} traffic originating * from the current thread. Only one active tag per thread is supported. * <p> diff --git a/packages/ConnectivityT/framework-t/src/android/net/UnderlyingNetworkInfo.java b/packages/ConnectivityT/framework-t/src/android/net/UnderlyingNetworkInfo.java index 33f9375c03bf..7ab53b1da856 100644 --- a/packages/ConnectivityT/framework-t/src/android/net/UnderlyingNetworkInfo.java +++ b/packages/ConnectivityT/framework-t/src/android/net/UnderlyingNetworkInfo.java @@ -60,7 +60,7 @@ public final class UnderlyingNetworkInfo implements Parcelable { mOwnerUid = in.readInt(); mIface = in.readString(); List<String> underlyingIfaces = new ArrayList<>(); - in.readList(underlyingIfaces, null /*classLoader*/); + in.readList(underlyingIfaces, null /*classLoader*/, java.lang.String.class); mUnderlyingIfaces = Collections.unmodifiableList(underlyingIfaces); } diff --git a/packages/ConnectivityT/service/Android.bp b/packages/ConnectivityT/service/Android.bp index b261e165a112..24bc91d91ef7 100644 --- a/packages/ConnectivityT/service/Android.bp +++ b/packages/ConnectivityT/service/Android.bp @@ -26,6 +26,8 @@ filegroup { srcs: [ "src/com/android/server/net/NetworkIdentity*.java", "src/com/android/server/net/NetworkStats*.java", + "src/com/android/server/net/BpfInterfaceMapUpdater.java", + "src/com/android/server/net/InterfaceMapValue.java", ], path: "src", visibility: [ @@ -66,6 +68,7 @@ filegroup { filegroup { name: "services.connectivity-ethernet-sources", srcs: [ + "src/com/android/server/net/DelayedDiskWrite.java", "src/com/android/server/net/IpConfigStore.java", ], path: "src", @@ -97,3 +100,28 @@ filegroup { "//packages/modules/Connectivity:__subpackages__", ], } + +cc_library_shared { + name: "libcom_android_net_module_util_jni", + min_sdk_version: "30", + cflags: [ + "-Wall", + "-Werror", + "-Wno-unused-parameter", + "-Wthread-safety", + ], + srcs: [ + "jni/onload.cpp", + ], + stl: "libc++_static", + static_libs: [ + "libnet_utils_device_common_bpfjni", + ], + shared_libs: [ + "liblog", + "libnativehelper", + ], + apex_available: [ + "//apex_available:platform", + ], +} diff --git a/packages/ConnectivityT/service/jni/onload.cpp b/packages/ConnectivityT/service/jni/onload.cpp new file mode 100644 index 000000000000..bca469756095 --- /dev/null +++ b/packages/ConnectivityT/service/jni/onload.cpp @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <nativehelper/JNIHelp.h> +#include <log/log.h> + +namespace android { + +int register_com_android_net_module_util_BpfMap(JNIEnv* env, char const* class_name); + +extern "C" jint JNI_OnLoad(JavaVM* vm, void*) { + JNIEnv *env; + if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) { + ALOGE("GetEnv failed"); + return JNI_ERR; + } + + if (register_com_android_net_module_util_BpfMap(env, + "com/android/net/module/util/BpfMap") < 0) return JNI_ERR; + + return JNI_VERSION_1_6; +} + +}; + diff --git a/packages/ConnectivityT/service/src/com/android/server/net/BpfInterfaceMapUpdater.java b/packages/ConnectivityT/service/src/com/android/server/net/BpfInterfaceMapUpdater.java new file mode 100644 index 000000000000..25c88eb6bdb2 --- /dev/null +++ b/packages/ConnectivityT/service/src/com/android/server/net/BpfInterfaceMapUpdater.java @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.net; + +import android.content.Context; +import android.net.INetd; +import android.os.Handler; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.ServiceSpecificException; +import android.system.ErrnoException; +import android.util.Log; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.net.module.util.BaseNetdUnsolicitedEventListener; +import com.android.net.module.util.BpfMap; +import com.android.net.module.util.IBpfMap; +import com.android.net.module.util.InterfaceParams; +import com.android.net.module.util.Struct.U32; + +/** + * Monitor interface added (without removed) and right interface name and its index to bpf map. + */ +public class BpfInterfaceMapUpdater { + private static final String TAG = BpfInterfaceMapUpdater.class.getSimpleName(); + // This is current path but may be changed soon. + private static final String IFACE_INDEX_NAME_MAP_PATH = + "/sys/fs/bpf/map_netd_iface_index_name_map"; + private final IBpfMap<U32, InterfaceMapValue> mBpfMap; + private final INetd mNetd; + private final Handler mHandler; + private final Dependencies mDeps; + + public BpfInterfaceMapUpdater(Context ctx, Handler handler) { + this(ctx, handler, new Dependencies()); + } + + @VisibleForTesting + public BpfInterfaceMapUpdater(Context ctx, Handler handler, Dependencies deps) { + mDeps = deps; + mBpfMap = deps.getInterfaceMap(); + mNetd = deps.getINetd(ctx); + mHandler = handler; + } + + /** + * Dependencies of BpfInerfaceMapUpdater, for injection in tests. + */ + @VisibleForTesting + public static class Dependencies { + /** Create BpfMap for updating interface and index mapping. */ + public IBpfMap<U32, InterfaceMapValue> getInterfaceMap() { + try { + return new BpfMap<>(IFACE_INDEX_NAME_MAP_PATH, BpfMap.BPF_F_RDWR, + U32.class, InterfaceMapValue.class); + } catch (ErrnoException e) { + Log.e(TAG, "Cannot create interface map: " + e); + return null; + } + } + + /** Get InterfaceParams for giving interface name. */ + public InterfaceParams getInterfaceParams(String ifaceName) { + return InterfaceParams.getByName(ifaceName); + } + + /** Get INetd binder object. */ + public INetd getINetd(Context ctx) { + return INetd.Stub.asInterface((IBinder) ctx.getSystemService(Context.NETD_SERVICE)); + } + } + + /** + * Start listening interface update event. + * Query current interface names before listening. + */ + public void start() { + mHandler.post(() -> { + if (mBpfMap == null) { + Log.wtf(TAG, "Fail to start: Null bpf map"); + return; + } + + try { + // TODO: use a NetlinkMonitor and listen for RTM_NEWLINK messages instead. + mNetd.registerUnsolicitedEventListener(new InterfaceChangeObserver()); + } catch (RemoteException e) { + Log.wtf(TAG, "Unable to register netd UnsolicitedEventListener, " + e); + } + + final String[] ifaces; + try { + // TODO: use a netlink dump to get the current interface list. + ifaces = mNetd.interfaceGetList(); + } catch (RemoteException | ServiceSpecificException e) { + Log.wtf(TAG, "Unable to query interface names by netd, " + e); + return; + } + + for (String ifaceName : ifaces) { + addInterface(ifaceName); + } + }); + } + + private void addInterface(String ifaceName) { + final InterfaceParams iface = mDeps.getInterfaceParams(ifaceName); + if (iface == null) { + Log.e(TAG, "Unable to get InterfaceParams for " + ifaceName); + return; + } + + try { + mBpfMap.updateEntry(new U32(iface.index), new InterfaceMapValue(ifaceName)); + } catch (ErrnoException e) { + Log.e(TAG, "Unable to update entry for " + ifaceName + ", " + e); + } + } + + private class InterfaceChangeObserver extends BaseNetdUnsolicitedEventListener { + @Override + public void onInterfaceAdded(String ifName) { + mHandler.post(() -> addInterface(ifName)); + } + } +} diff --git a/services/core/java/com/android/server/net/DelayedDiskWrite.java b/packages/ConnectivityT/service/src/com/android/server/net/DelayedDiskWrite.java index 8f09eb7c19ab..35dc4557252c 100644 --- a/services/core/java/com/android/server/net/DelayedDiskWrite.java +++ b/packages/ConnectivityT/service/src/com/android/server/net/DelayedDiskWrite.java @@ -26,21 +26,37 @@ import java.io.DataOutputStream; import java.io.FileOutputStream; import java.io.IOException; +/** + * This class provides APIs to do a delayed data write to a given {@link OutputStream}. + */ public class DelayedDiskWrite { + private static final String TAG = "DelayedDiskWrite"; + private HandlerThread mDiskWriteHandlerThread; private Handler mDiskWriteHandler; /* Tracks multiple writes on the same thread */ private int mWriteSequence = 0; - private final String TAG = "DelayedDiskWrite"; + /** + * Used to do a delayed data write to a given {@link OutputStream}. + */ public interface Writer { - public void onWriteCalled(DataOutputStream out) throws IOException; + /** + * write data to a given {@link OutputStream}. + */ + void onWriteCalled(DataOutputStream out) throws IOException; } + /** + * Do a delayed data write to a given output stream opened from filePath. + */ public void write(final String filePath, final Writer w) { write(filePath, w, true); } + /** + * Do a delayed data write to a given output stream opened from filePath. + */ public void write(final String filePath, final Writer w, final boolean open) { if (TextUtils.isEmpty(filePath)) { throw new IllegalArgumentException("empty file path"); @@ -77,7 +93,7 @@ public class DelayedDiskWrite { if (out != null) { try { out.close(); - } catch (Exception e) {} + } catch (Exception e) { } } // Quit if no more writes sent diff --git a/packages/ConnectivityT/service/src/com/android/server/net/InterfaceMapValue.java b/packages/ConnectivityT/service/src/com/android/server/net/InterfaceMapValue.java new file mode 100644 index 000000000000..061f323447b0 --- /dev/null +++ b/packages/ConnectivityT/service/src/com/android/server/net/InterfaceMapValue.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2020 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.net; + +import com.android.net.module.util.Struct; +import com.android.net.module.util.Struct.Field; +import com.android.net.module.util.Struct.Type; + +/** + * The value of bpf interface index map which is used for NetworkStatsService. + */ +public class InterfaceMapValue extends Struct { + @Field(order = 0, type = Type.ByteArray, arraysize = 16) + public final byte[] interfaceName; + + public InterfaceMapValue(String iface) { + final byte[] ifaceArray = iface.getBytes(); + interfaceName = new byte[16]; + // All array bytes after the interface name, if any, must be 0. + System.arraycopy(ifaceArray, 0, interfaceName, 0, ifaceArray.length); + } +} diff --git a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java index e15acf3ef616..4c78dcb6269e 100644 --- a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java +++ b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java @@ -19,12 +19,16 @@ package com.android.server.net; import static android.Manifest.permission.NETWORK_STATS_PROVIDER; import static android.Manifest.permission.READ_NETWORK_USAGE_HISTORY; import static android.Manifest.permission.UPDATE_DEVICE_STATS; +import static android.app.usage.NetworkStatsManager.PREFIX_DEV; +import static android.app.usage.NetworkStatsManager.PREFIX_UID; +import static android.app.usage.NetworkStatsManager.PREFIX_UID_TAG; +import static android.app.usage.NetworkStatsManager.PREFIX_XT; import static android.content.Intent.ACTION_SHUTDOWN; import static android.content.Intent.ACTION_UID_REMOVED; import static android.content.Intent.ACTION_USER_REMOVED; import static android.content.Intent.EXTRA_UID; import static android.content.pm.PackageManager.PERMISSION_GRANTED; -import static android.net.NetworkIdentity.SUBTYPE_COMBINED; +import static android.net.NetworkCapabilities.TRANSPORT_WIFI; import static android.net.NetworkStats.DEFAULT_NETWORK_ALL; import static android.net.NetworkStats.IFACE_ALL; import static android.net.NetworkStats.IFACE_VT; @@ -47,23 +51,6 @@ import static android.net.TrafficStats.MB_IN_BYTES; import static android.net.TrafficStats.UID_TETHERING; import static android.net.TrafficStats.UNSUPPORTED; import static android.os.Trace.TRACE_TAG_NETWORK; -import static android.provider.Settings.Global.NETSTATS_AUGMENT_ENABLED; -import static android.provider.Settings.Global.NETSTATS_COMBINE_SUBTYPE_ENABLED; -import static android.provider.Settings.Global.NETSTATS_DEV_BUCKET_DURATION; -import static android.provider.Settings.Global.NETSTATS_DEV_DELETE_AGE; -import static android.provider.Settings.Global.NETSTATS_DEV_PERSIST_BYTES; -import static android.provider.Settings.Global.NETSTATS_DEV_ROTATE_AGE; -import static android.provider.Settings.Global.NETSTATS_GLOBAL_ALERT_BYTES; -import static android.provider.Settings.Global.NETSTATS_POLL_INTERVAL; -import static android.provider.Settings.Global.NETSTATS_SAMPLE_ENABLED; -import static android.provider.Settings.Global.NETSTATS_UID_BUCKET_DURATION; -import static android.provider.Settings.Global.NETSTATS_UID_DELETE_AGE; -import static android.provider.Settings.Global.NETSTATS_UID_PERSIST_BYTES; -import static android.provider.Settings.Global.NETSTATS_UID_ROTATE_AGE; -import static android.provider.Settings.Global.NETSTATS_UID_TAG_BUCKET_DURATION; -import static android.provider.Settings.Global.NETSTATS_UID_TAG_DELETE_AGE; -import static android.provider.Settings.Global.NETSTATS_UID_TAG_PERSIST_BYTES; -import static android.provider.Settings.Global.NETSTATS_UID_TAG_ROTATE_AGE; import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID; import static android.text.format.DateUtils.DAY_IN_MILLIS; import static android.text.format.DateUtils.HOUR_IN_MILLIS; @@ -119,7 +106,6 @@ import android.os.Binder; import android.os.DropBoxManager; import android.os.Environment; import android.os.Handler; -import android.os.HandlerExecutor; import android.os.HandlerThread; import android.os.IBinder; import android.os.Looper; @@ -154,9 +140,9 @@ import com.android.net.module.util.BaseNetdUnsolicitedEventListener; import com.android.net.module.util.BestClock; import com.android.net.module.util.BinderUtils; import com.android.net.module.util.CollectionUtils; +import com.android.net.module.util.LocationPermissionChecker; import com.android.net.module.util.NetworkStatsUtils; import com.android.net.module.util.PermissionUtils; -import com.android.server.LocalServices; import java.io.File; import java.io.FileDescriptor; @@ -217,6 +203,10 @@ public class NetworkStatsService extends INetworkStatsService.Stub { private static final int LOG_TAG_NETSTATS_MOBILE_SAMPLE = 51100; private static final int LOG_TAG_NETSTATS_WIFI_SAMPLE = 51101; + // TODO: Replace the hardcoded string and move it into ConnectivitySettingsManager. + private static final String NETSTATS_COMBINE_SUBTYPE_ENABLED = + "netstats_combine_subtype_enabled"; + private final Context mContext; private final NetworkStatsFactory mStatsFactory; private final AlarmManager mAlarmManager; @@ -243,11 +233,6 @@ public class NetworkStatsService extends INetworkStatsService.Stub { private PendingIntent mPollIntent; - private static final String PREFIX_DEV = "dev"; - private static final String PREFIX_XT = "xt"; - private static final String PREFIX_UID = "uid"; - private static final String PREFIX_UID_TAG = "uid_tag"; - /** * Settings that can be changed externally. */ @@ -257,9 +242,9 @@ public class NetworkStatsService extends INetworkStatsService.Stub { boolean getSampleEnabled(); boolean getAugmentEnabled(); /** - * When enabled, all mobile data is reported under {@link NetworkIdentity#SUBTYPE_COMBINED}. - * When disabled, mobile data is broken down by a granular subtype representative of the - * actual subtype. {@see NetworkTemplate#getCollapsedRatType}. + * When enabled, all mobile data is reported under {@link NetworkTemplate#NETWORK_TYPE_ALL}. + * When disabled, mobile data is broken down by a granular ratType representative of the + * actual ratType. {@see NetworkTemplate#getCollapsedRatType}. * Enabling this decreases the level of detail but saves performance, disk space and * amount of data logged. */ @@ -306,6 +291,9 @@ public class NetworkStatsService extends INetworkStatsService.Stub { /** Set of any ifaces associated with mobile networks since boot. */ private volatile String[] mMobileIfaces = new String[0]; + /** Set of any ifaces associated with wifi networks since boot. */ + private volatile String[] mWifiIfaces = new String[0]; + /** Set of all ifaces currently used by traffic that does not explicitly specify a Network. */ @GuardedBy("mStatsLock") private Network[] mDefaultNetworks = new Network[0]; @@ -366,6 +354,12 @@ public class NetworkStatsService extends INetworkStatsService.Stub { @NonNull private final NetworkStatsSubscriptionsMonitor mNetworkStatsSubscriptionsMonitor; + @NonNull + private final LocationPermissionChecker mLocationPermissionChecker; + + @NonNull + private final BpfInterfaceMapUpdater mInterfaceMapUpdater; + private static @NonNull File getDefaultSystemDir() { return new File(Environment.getDataDirectory(), "system"); } @@ -428,7 +422,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { final NetworkStatsService service = new NetworkStatsService(context, INetd.Stub.asInterface((IBinder) context.getSystemService(Context.NETD_SERVICE)), alarmManager, wakeLock, getDefaultClock(), - new DefaultNetworkStatsSettings(context), new NetworkStatsFactory(netd), + new DefaultNetworkStatsSettings(), new NetworkStatsFactory(netd), new NetworkStatsObservers(), getDefaultSystemDir(), getDefaultBaseDir(), new Dependencies()); @@ -458,10 +452,13 @@ public class NetworkStatsService extends INetworkStatsService.Stub { handlerThread.start(); mHandler = new NetworkStatsHandler(handlerThread.getLooper()); mNetworkStatsSubscriptionsMonitor = deps.makeSubscriptionsMonitor(mContext, - new HandlerExecutor(mHandler), this); + (command) -> mHandler.post(command) , this); mContentResolver = mContext.getContentResolver(); mContentObserver = mDeps.makeContentObserver(mHandler, mSettings, mNetworkStatsSubscriptionsMonitor); + mLocationPermissionChecker = mDeps.makeLocationPermissionChecker(mContext); + mInterfaceMapUpdater = mDeps.makeBpfInterfaceMapUpdater(mContext, mHandler); + mInterfaceMapUpdater.start(); } /** @@ -509,6 +506,20 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } }; } + + /** + * @see LocationPermissionChecker + */ + public LocationPermissionChecker makeLocationPermissionChecker(final Context context) { + return new LocationPermissionChecker(context); + } + + /** Create BpfInterfaceMapUpdater to update bpf interface map. */ + @NonNull + public BpfInterfaceMapUpdater makeBpfInterfaceMapUpdater( + @NonNull Context ctx, @NonNull Handler handler) { + return new BpfInterfaceMapUpdater(ctx, handler); + } } /** @@ -557,7 +568,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { // watch for tethering changes final TetheringManager tetheringManager = mContext.getSystemService(TetheringManager.class); tetheringManager.registerTetheringEventCallback( - new HandlerExecutor(mHandler), mTetherListener); + (command) -> mHandler.post(command), mTetherListener); // listen for periodic polling events final IntentFilter pollFilter = new IntentFilter(ACTION_NETWORK_STATS_POLL); @@ -591,13 +602,13 @@ public class NetworkStatsService extends INetworkStatsService.Stub { mSettings.getPollInterval(), pollIntent); mContentResolver.registerContentObserver(Settings.Global - .getUriFor(Settings.Global.NETSTATS_COMBINE_SUBTYPE_ENABLED), + .getUriFor(NETSTATS_COMBINE_SUBTYPE_ENABLED), false /* notifyForDescendants */, mContentObserver); // Post a runnable on handler thread to call onChange(). It's for getting current value of // NETSTATS_COMBINE_SUBTYPE_ENABLED to decide start or stop monitoring RAT type changes. mHandler.post(() -> mContentObserver.onChange(false, Settings.Global - .getUriFor(Settings.Global.NETSTATS_COMBINE_SUBTYPE_ENABLED))); + .getUriFor(NETSTATS_COMBINE_SUBTYPE_ENABLED))); registerGlobalAlert(); } @@ -708,12 +719,25 @@ public class NetworkStatsService extends INetworkStatsService.Stub { return now - lastCallTime < POLL_RATE_LIMIT_MS; } - private INetworkStatsSession openSessionInternal(final int flags, final String callingPackage) { + private int restrictFlagsForCaller(int flags) { + // All non-privileged callers are not allowed to turn off POLL_ON_OPEN. + final boolean isPrivileged = PermissionUtils.checkAnyPermissionOf(mContext, + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, + android.Manifest.permission.NETWORK_STACK); + if (!isPrivileged) { + flags |= NetworkStatsManager.FLAG_POLL_ON_OPEN; + } + // Non-system uids are rate limited for POLL_ON_OPEN. final int callingUid = Binder.getCallingUid(); - final int usedFlags = isRateLimitedForPoll(callingUid) + flags = isRateLimitedForPoll(callingUid) ? flags & (~NetworkStatsManager.FLAG_POLL_ON_OPEN) : flags; - if ((usedFlags & (NetworkStatsManager.FLAG_POLL_ON_OPEN + return flags; + } + + private INetworkStatsSession openSessionInternal(final int flags, final String callingPackage) { + final int restrictedFlags = restrictFlagsForCaller(flags); + if ((restrictedFlags & (NetworkStatsManager.FLAG_POLL_ON_OPEN | NetworkStatsManager.FLAG_POLL_FORCE)) != 0) { final long ident = Binder.clearCallingIdentity(); try { @@ -727,7 +751,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { // for its lifetime; when caller closes only weak references remain. return new INetworkStatsSession.Stub() { - private final int mCallingUid = callingUid; + private final int mCallingUid = Binder.getCallingUid(); private final String mCallingPackage = callingPackage; private final @NetworkStatsAccess.Level int mAccessLevel = checkAccessLevel( callingPackage); @@ -761,26 +785,41 @@ public class NetworkStatsService extends INetworkStatsService.Stub { @Override public NetworkStats getDeviceSummaryForNetwork( NetworkTemplate template, long start, long end) { - return internalGetSummaryForNetwork(template, usedFlags, start, end, mAccessLevel, - mCallingUid); + enforceTemplatePermissions(template, callingPackage); + return internalGetSummaryForNetwork(template, restrictedFlags, start, end, + mAccessLevel, mCallingUid); } @Override public NetworkStats getSummaryForNetwork( NetworkTemplate template, long start, long end) { - return internalGetSummaryForNetwork(template, usedFlags, start, end, mAccessLevel, - mCallingUid); + enforceTemplatePermissions(template, callingPackage); + return internalGetSummaryForNetwork(template, restrictedFlags, start, end, + mAccessLevel, mCallingUid); } + // TODO: Remove this after all callers are removed. @Override public NetworkStatsHistory getHistoryForNetwork(NetworkTemplate template, int fields) { - return internalGetHistoryForNetwork(template, usedFlags, fields, mAccessLevel, - mCallingUid); + enforceTemplatePermissions(template, callingPackage); + return internalGetHistoryForNetwork(template, restrictedFlags, fields, + mAccessLevel, mCallingUid, Long.MIN_VALUE, Long.MAX_VALUE); + } + + @Override + public NetworkStatsHistory getHistoryIntervalForNetwork(NetworkTemplate template, + int fields, long start, long end) { + enforceTemplatePermissions(template, callingPackage); + // TODO(b/200768422): Redact returned history if the template is location + // sensitive but the caller is not privileged. + return internalGetHistoryForNetwork(template, restrictedFlags, fields, + mAccessLevel, mCallingUid, start, end); } @Override public NetworkStats getSummaryForAllUid( NetworkTemplate template, long start, long end, boolean includeTags) { + enforceTemplatePermissions(template, callingPackage); try { final NetworkStats stats = getUidComplete() .getSummary(template, start, end, mAccessLevel, mCallingUid); @@ -798,6 +837,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { @Override public NetworkStats getTaggedSummaryForAllUid( NetworkTemplate template, long start, long end) { + enforceTemplatePermissions(template, callingPackage); try { final NetworkStats tagStats = getUidTagComplete() .getSummary(template, start, end, mAccessLevel, mCallingUid); @@ -810,6 +850,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { @Override public NetworkStatsHistory getHistoryForUid( NetworkTemplate template, int uid, int set, int tag, int fields) { + enforceTemplatePermissions(template, callingPackage); // NOTE: We don't augment UID-level statistics if (tag == TAG_NONE) { return getUidComplete().getHistory(template, null, uid, set, tag, fields, @@ -824,6 +865,9 @@ public class NetworkStatsService extends INetworkStatsService.Stub { public NetworkStatsHistory getHistoryIntervalForUid( NetworkTemplate template, int uid, int set, int tag, int fields, long start, long end) { + enforceTemplatePermissions(template, callingPackage); + // TODO(b/200768422): Redact returned history if the template is location + // sensitive but the caller is not privileged. // NOTE: We don't augment UID-level statistics if (tag == TAG_NONE) { return getUidComplete().getHistory(template, null, uid, set, tag, fields, @@ -845,6 +889,26 @@ public class NetworkStatsService extends INetworkStatsService.Stub { }; } + private void enforceTemplatePermissions(@NonNull NetworkTemplate template, + @NonNull String callingPackage) { + // For a template with wifi network keys, it is possible for a malicious + // client to track the user locations via querying data usage. Thus, enforce + // fine location permission check. + if (!template.getWifiNetworkKeys().isEmpty()) { + final boolean canAccessFineLocation = mLocationPermissionChecker + .checkCallersLocationPermission(callingPackage, + null /* featureId */, + Binder.getCallingUid(), + false /* coarseForTargetSdkLessThanQ */, + null /* message */); + if (!canAccessFineLocation) { + throw new SecurityException("Access fine location is required when querying" + + " with wifi network keys, make sure the app has the necessary" + + "permissions and the location toggle is on."); + } + } + } + private @NetworkStatsAccess.Level int checkAccessLevel(String callingPackage) { return NetworkStatsAccess.checkAccessLevel( mContext, Binder.getCallingPid(), Binder.getCallingUid(), callingPackage); @@ -881,7 +945,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { // We've been using pure XT stats long enough that we no longer need to // splice DEV and XT together. final NetworkStatsHistory history = internalGetHistoryForNetwork(template, flags, FIELD_ALL, - accessLevel, callingUid); + accessLevel, callingUid, start, end); final long now = System.currentTimeMillis(); final NetworkStatsHistory.Entry entry = history.getValues(start, end, now, null); @@ -898,14 +962,14 @@ public class NetworkStatsService extends INetworkStatsService.Stub { * appropriate. */ private NetworkStatsHistory internalGetHistoryForNetwork(NetworkTemplate template, - int flags, int fields, @NetworkStatsAccess.Level int accessLevel, int callingUid) { + int flags, int fields, @NetworkStatsAccess.Level int accessLevel, int callingUid, + long start, long end) { // We've been using pure XT stats long enough that we no longer need to // splice DEV and XT together. final SubscriptionPlan augmentPlan = resolveSubscriptionPlan(template, flags); synchronized (mStatsLock) { return mXtStatsCached.getHistory(template, augmentPlan, - UID_ALL, SET_ALL, TAG_NONE, fields, Long.MIN_VALUE, Long.MAX_VALUE, - accessLevel, callingUid); + UID_ALL, SET_ALL, TAG_NONE, fields, start, end, accessLevel, callingUid); } } @@ -956,11 +1020,15 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } @Override - public NetworkStats getDetailedUidStats(String[] requiredIfaces) { + public NetworkStats getUidStatsForTransport(int transport) { enforceAnyPermissionOf(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK); try { + final String[] relevantIfaces = + transport == TRANSPORT_WIFI ? mWifiIfaces : mMobileIfaces; + // TODO(b/215633405) : mMobileIfaces and mWifiIfaces already contain the stacked + // interfaces, so this is not useful, remove it. final String[] ifacesToQuery = - mStatsFactory.augmentWithStackedInterfaces(requiredIfaces); + mStatsFactory.augmentWithStackedInterfaces(relevantIfaces); return getNetworkStatsUidDetail(ifacesToQuery); } catch (RemoteException e) { Log.wtf(TAG, "Error compiling UID stats", e); @@ -1319,16 +1387,18 @@ public class NetworkStatsService extends INetworkStatsService.Stub { final boolean combineSubtypeEnabled = mSettings.getCombineSubtypeEnabled(); final ArraySet<String> mobileIfaces = new ArraySet<>(); + final ArraySet<String> wifiIfaces = new ArraySet<>(); for (NetworkStateSnapshot snapshot : snapshots) { final int displayTransport = getDisplayTransport(snapshot.getNetworkCapabilities().getTransportTypes()); final boolean isMobile = (NetworkCapabilities.TRANSPORT_CELLULAR == displayTransport); + final boolean isWifi = (NetworkCapabilities.TRANSPORT_WIFI == displayTransport); final boolean isDefault = CollectionUtils.contains( mDefaultNetworks, snapshot.getNetwork()); - final int subType = combineSubtypeEnabled ? SUBTYPE_COMBINED - : getSubTypeForStateSnapshot(snapshot); + final int ratType = combineSubtypeEnabled ? NetworkTemplate.NETWORK_TYPE_ALL + : getRatTypeForStateSnapshot(snapshot); final NetworkIdentity ident = NetworkIdentity.buildNetworkIdentity(mContext, snapshot, - isDefault, subType); + isDefault, ratType); // Traffic occurring on the base interface is always counted for // both total usage and UID details. @@ -1343,12 +1413,12 @@ public class NetworkStatsService extends INetworkStatsService.Stub { // VT is considered always metered in framework's layer. If VT is not metered // per carrier's policy, modem will report 0 usage for VT calls. if (snapshot.getNetworkCapabilities().hasCapability( - NetworkCapabilities.NET_CAPABILITY_IMS) && !ident.getMetered()) { + NetworkCapabilities.NET_CAPABILITY_IMS) && !ident.isMetered()) { // Copy the identify from IMS one but mark it as metered. NetworkIdentity vtIdent = new NetworkIdentity(ident.getType(), - ident.getSubType(), ident.getSubscriberId(), ident.getNetworkId(), - ident.getRoaming(), true /* metered */, + ident.getRatType(), ident.getSubscriberId(), ident.getWifiNetworkKey(), + ident.isRoaming(), true /* metered */, true /* onDefaultNetwork */, ident.getOemManaged()); final String ifaceVt = IFACE_VT + getSubIdForMobile(snapshot); findOrCreateNetworkIdentitySet(mActiveIfaces, ifaceVt).add(vtIdent); @@ -1358,6 +1428,9 @@ public class NetworkStatsService extends INetworkStatsService.Stub { if (isMobile) { mobileIfaces.add(baseIface); } + if (isWifi) { + wifiIfaces.add(baseIface); + } } // Traffic occurring on stacked interfaces is usually clatd. @@ -1399,6 +1472,9 @@ public class NetworkStatsService extends INetworkStatsService.Stub { if (isMobile) { mobileIfaces.add(iface); } + if (isWifi) { + wifiIfaces.add(iface); + } mStatsFactory.noteStackedIface(iface, baseIface); } @@ -1406,11 +1482,16 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } mMobileIfaces = mobileIfaces.toArray(new String[0]); + mWifiIfaces = wifiIfaces.toArray(new String[0]); // TODO (b/192758557): Remove debug log. if (CollectionUtils.contains(mMobileIfaces, null)) { throw new NullPointerException( "null element in mMobileIfaces: " + Arrays.toString(mMobileIfaces)); } + if (CollectionUtils.contains(mWifiIfaces, null)) { + throw new NullPointerException( + "null element in mWifiIfaces: " + Arrays.toString(mWifiIfaces)); + } } private static int getSubIdForMobile(@NonNull NetworkStateSnapshot state) { @@ -1428,11 +1509,11 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } /** - * For networks with {@code TRANSPORT_CELLULAR}, get subType that was obtained through + * For networks with {@code TRANSPORT_CELLULAR}, get ratType that was obtained through * {@link PhoneStateListener}. Otherwise, return 0 given that other networks with different * transport types do not actually fill this value. */ - private int getSubTypeForStateSnapshot(@NonNull NetworkStateSnapshot state) { + private int getRatTypeForStateSnapshot(@NonNull NetworkStateSnapshot state) { if (!state.getNetworkCapabilities().hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) { return 0; } @@ -2120,7 +2201,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { public void notifyWarningOrLimitReached() { Log.d(TAG, mTag + ": notifyWarningOrLimitReached"); BinderUtils.withCleanCallingIdentity(() -> - mNetworkPolicyManager.onStatsProviderWarningOrLimitReached()); + mNetworkPolicyManager.notifyStatsProviderWarningOrLimitReached()); } @Override @@ -2179,24 +2260,11 @@ public class NetworkStatsService extends INetworkStatsService.Stub { * {@link android.provider.Settings.Global}. */ private static class DefaultNetworkStatsSettings implements NetworkStatsSettings { - private final ContentResolver mResolver; - - public DefaultNetworkStatsSettings(Context context) { - mResolver = Objects.requireNonNull(context.getContentResolver()); - // TODO: adjust these timings for production builds - } - - private long getGlobalLong(String name, long def) { - return Settings.Global.getLong(mResolver, name, def); - } - private boolean getGlobalBoolean(String name, boolean def) { - final int defInt = def ? 1 : 0; - return Settings.Global.getInt(mResolver, name, defInt) != 0; - } + DefaultNetworkStatsSettings() {} @Override public long getPollInterval() { - return getGlobalLong(NETSTATS_POLL_INTERVAL, 30 * MINUTE_IN_MILLIS); + return 30 * MINUTE_IN_MILLIS; } @Override public long getPollDelay() { @@ -2204,25 +2272,23 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } @Override public long getGlobalAlertBytes(long def) { - return getGlobalLong(NETSTATS_GLOBAL_ALERT_BYTES, def); + return def; } @Override public boolean getSampleEnabled() { - return getGlobalBoolean(NETSTATS_SAMPLE_ENABLED, true); + return true; } @Override public boolean getAugmentEnabled() { - return getGlobalBoolean(NETSTATS_AUGMENT_ENABLED, true); + return true; } @Override public boolean getCombineSubtypeEnabled() { - return getGlobalBoolean(NETSTATS_COMBINE_SUBTYPE_ENABLED, false); + return false; } @Override public Config getDevConfig() { - return new Config(getGlobalLong(NETSTATS_DEV_BUCKET_DURATION, HOUR_IN_MILLIS), - getGlobalLong(NETSTATS_DEV_ROTATE_AGE, 15 * DAY_IN_MILLIS), - getGlobalLong(NETSTATS_DEV_DELETE_AGE, 90 * DAY_IN_MILLIS)); + return new Config(HOUR_IN_MILLIS, 15 * DAY_IN_MILLIS, 90 * DAY_IN_MILLIS); } @Override public Config getXtConfig() { @@ -2230,31 +2296,27 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } @Override public Config getUidConfig() { - return new Config(getGlobalLong(NETSTATS_UID_BUCKET_DURATION, 2 * HOUR_IN_MILLIS), - getGlobalLong(NETSTATS_UID_ROTATE_AGE, 15 * DAY_IN_MILLIS), - getGlobalLong(NETSTATS_UID_DELETE_AGE, 90 * DAY_IN_MILLIS)); + return new Config(2 * HOUR_IN_MILLIS, 15 * DAY_IN_MILLIS, 90 * DAY_IN_MILLIS); } @Override public Config getUidTagConfig() { - return new Config(getGlobalLong(NETSTATS_UID_TAG_BUCKET_DURATION, 2 * HOUR_IN_MILLIS), - getGlobalLong(NETSTATS_UID_TAG_ROTATE_AGE, 5 * DAY_IN_MILLIS), - getGlobalLong(NETSTATS_UID_TAG_DELETE_AGE, 15 * DAY_IN_MILLIS)); + return new Config(2 * HOUR_IN_MILLIS, 5 * DAY_IN_MILLIS, 15 * DAY_IN_MILLIS); } @Override public long getDevPersistBytes(long def) { - return getGlobalLong(NETSTATS_DEV_PERSIST_BYTES, def); + return def; } @Override public long getXtPersistBytes(long def) { - return getDevPersistBytes(def); + return def; } @Override public long getUidPersistBytes(long def) { - return getGlobalLong(NETSTATS_UID_PERSIST_BYTES, def); + return def; } @Override public long getUidTagPersistBytes(long def) { - return getGlobalLong(NETSTATS_UID_TAG_PERSIST_BYTES, def); + return def; } } diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java index a3eb0eccad9d..ce58ff6fc59d 100644 --- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java +++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java @@ -236,7 +236,8 @@ public class ExternalStorageProvider extends FileSystemProvider { root.flags |= Root.FLAG_REMOVABLE_USB; } - if (volume.getType() != VolumeInfo.TYPE_EMULATED) { + if (volume.getType() != VolumeInfo.TYPE_EMULATED + && volume.getType() != VolumeInfo.TYPE_STUB) { root.flags |= Root.FLAG_SUPPORTS_EJECT; } diff --git a/packages/SettingsLib/res/values/config.xml b/packages/SettingsLib/res/values/config.xml index 45253bb7944a..b150e0169a96 100644 --- a/packages/SettingsLib/res/values/config.xml +++ b/packages/SettingsLib/res/values/config.xml @@ -28,4 +28,9 @@ <!-- Control whether status bar should distinguish HSPA data icon form UMTS data icon on devices --> <bool name="config_hspa_data_distinguishable">false</bool> + + <integer-array name="config_supportedDreamComplications"> + </integer-array> + <integer-array name="config_dreamComplicationsEnabledByDefault"> + </integer-array> </resources>
\ No newline at end of file diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/InterestingConfigChanges.java b/packages/SettingsLib/src/com/android/settingslib/applications/InterestingConfigChanges.java index 5f2bef723cd9..64a0781c4643 100644 --- a/packages/SettingsLib/src/com/android/settingslib/applications/InterestingConfigChanges.java +++ b/packages/SettingsLib/src/com/android/settingslib/applications/InterestingConfigChanges.java @@ -31,9 +31,8 @@ public class InterestingConfigChanges { private int mLastDensity; public InterestingConfigChanges() { - this(ActivityInfo.CONFIG_LOCALE - | ActivityInfo.CONFIG_UI_MODE | ActivityInfo.CONFIG_SCREEN_LAYOUT - | ActivityInfo.CONFIG_ASSETS_PATHS); + this(ActivityInfo.CONFIG_LOCALE | ActivityInfo.CONFIG_LAYOUT_DIRECTION + | ActivityInfo.CONFIG_UI_MODE | ActivityInfo.CONFIG_ASSETS_PATHS); } public InterestingConfigChanges(int flags) { diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java index 8ac4e38677fa..389892ed15e4 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java @@ -170,12 +170,6 @@ public class BluetoothEventManager { } @VisibleForTesting - void registerIntentReceiver() { - mContext.registerReceiverAsUser(mBroadcastReceiver, mUserHandle, mAdapterIntentFilter, - null, mReceiverHandler); - } - - @VisibleForTesting void addProfileHandler(String action, Handler handler) { mHandlerMap.put(action, handler); mProfileIntentFilter.addAction(action); diff --git a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java index aed2ec10e924..7168f3cf1f9c 100644 --- a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java +++ b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java @@ -38,6 +38,8 @@ import android.util.AttributeSet; import android.util.Log; import android.util.Xml; +import com.android.settingslib.R; + import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -45,9 +47,12 @@ import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; public class DreamBackend { private static final String TAG = "DreamBackend"; @@ -78,19 +83,41 @@ public class DreamBackend { @Retention(RetentionPolicy.SOURCE) @IntDef({WHILE_CHARGING, WHILE_DOCKED, EITHER, NEVER}) - public @interface WhenToDream{} + public @interface WhenToDream {} public static final int WHILE_CHARGING = 0; public static final int WHILE_DOCKED = 1; public static final int EITHER = 2; public static final int NEVER = 3; + /** + * The type of dream complications which can be provided by a + * {@link com.android.systemui.dreams.ComplicationProvider}. + */ + @IntDef(prefix = {"COMPLICATION_TYPE_"}, value = { + COMPLICATION_TYPE_TIME, + COMPLICATION_TYPE_DATE, + COMPLICATION_TYPE_WEATHER, + COMPLICATION_TYPE_AIR_QUALITY, + COMPLICATION_TYPE_CAST_INFO + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ComplicationType {} + + public static final int COMPLICATION_TYPE_TIME = 1; + public static final int COMPLICATION_TYPE_DATE = 2; + public static final int COMPLICATION_TYPE_WEATHER = 3; + public static final int COMPLICATION_TYPE_AIR_QUALITY = 4; + public static final int COMPLICATION_TYPE_CAST_INFO = 5; + private final Context mContext; private final IDreamManager mDreamManager; private final DreamInfoComparator mComparator; private final boolean mDreamsEnabledByDefault; private final boolean mDreamsActivatedOnSleepByDefault; private final boolean mDreamsActivatedOnDockByDefault; + private final Set<Integer> mSupportedComplications; + private final Set<Integer> mDefaultEnabledComplications; private static DreamBackend sInstance; @@ -103,17 +130,31 @@ public class DreamBackend { public DreamBackend(Context context) { mContext = context.getApplicationContext(); + final Resources resources = mContext.getResources(); + mDreamManager = IDreamManager.Stub.asInterface( ServiceManager.getService(DreamService.DREAM_SERVICE)); mComparator = new DreamInfoComparator(getDefaultDream()); - mDreamsEnabledByDefault = mContext.getResources() - .getBoolean(com.android.internal.R.bool.config_dreamsEnabledByDefault); - mDreamsActivatedOnSleepByDefault = mContext.getResources() - .getBoolean(com.android.internal.R.bool.config_dreamsActivatedOnSleepByDefault); - mDreamsActivatedOnDockByDefault = mContext.getResources() - .getBoolean(com.android.internal.R.bool.config_dreamsActivatedOnDockByDefault); - mDreamPreviewDefault = mContext.getResources().getDrawable( + mDreamsEnabledByDefault = resources.getBoolean( + com.android.internal.R.bool.config_dreamsEnabledByDefault); + mDreamsActivatedOnSleepByDefault = resources.getBoolean( + com.android.internal.R.bool.config_dreamsActivatedOnSleepByDefault); + mDreamsActivatedOnDockByDefault = resources.getBoolean( + com.android.internal.R.bool.config_dreamsActivatedOnDockByDefault); + mDreamPreviewDefault = resources.getDrawable( com.android.internal.R.drawable.default_dream_preview); + + mSupportedComplications = + Arrays.stream(resources.getIntArray(R.array.config_supportedDreamComplications)) + .boxed() + .collect(Collectors.toSet()); + + mDefaultEnabledComplications = Arrays.stream( + resources.getIntArray(R.array.config_dreamComplicationsEnabledByDefault)) + .boxed() + // A complication can only be enabled by default if it is also supported. + .filter(mSupportedComplications::contains) + .collect(Collectors.toSet()); } public List<DreamInfo> getDreamInfos() { @@ -242,7 +283,57 @@ public class DreamBackend { default: break; } + } + + /** Gets all complications which have been enabled by the user. */ + public Set<Integer> getEnabledComplications() { + final String enabledComplications = Settings.Secure.getString( + mContext.getContentResolver(), + Settings.Secure.SCREENSAVER_ENABLED_COMPLICATIONS); + + if (enabledComplications == null) { + return mDefaultEnabledComplications; + } + + return parseFromString(enabledComplications); + } + + /** Gets all dream complications which are supported on this device. **/ + public Set<Integer> getSupportedComplications() { + return mSupportedComplications; + } + + /** + * Enables or disables a particular dream complication. + * + * @param complicationType The dream complication to be enabled/disabled. + * @param value If true, the complication is enabled. Otherwise it is disabled. + */ + public void setComplicationEnabled(@ComplicationType int complicationType, boolean value) { + if (!mSupportedComplications.contains(complicationType)) return; + + Set<Integer> enabledComplications = getEnabledComplications(); + if (value) { + enabledComplications.add(complicationType); + } else { + enabledComplications.remove(complicationType); + } + + Settings.Secure.putString(mContext.getContentResolver(), + Settings.Secure.SCREENSAVER_ENABLED_COMPLICATIONS, + convertToString(enabledComplications)); + } + + private static String convertToString(Set<Integer> set) { + return set.stream() + .map(String::valueOf) + .collect(Collectors.joining(",")); + } + private static Set<Integer> parseFromString(String string) { + return Arrays.stream(string.split(",")) + .map(Integer::parseInt) + .collect(Collectors.toSet()); } public boolean isEnabled() { @@ -311,7 +402,10 @@ public class DreamBackend { if (dreamInfo == null || dreamInfo.settingsComponentName == null) { return; } - uiContext.startActivity(new Intent().setComponent(dreamInfo.settingsComponentName)); + final Intent intent = new Intent() + .setComponent(dreamInfo.settingsComponentName) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + uiContext.startActivity(intent); } public void preview(DreamInfo dreamInfo) { diff --git a/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleDataLoader.java b/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleDataLoader.java index 3e95b01824cc..5e9ac5a59091 100644 --- a/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleDataLoader.java +++ b/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleDataLoader.java @@ -16,23 +16,16 @@ package com.android.settingslib.net; -import static android.net.NetworkStatsHistory.FIELD_RX_BYTES; -import static android.net.NetworkStatsHistory.FIELD_TX_BYTES; - +import android.annotation.NonNull; import android.app.usage.NetworkStats; import android.app.usage.NetworkStatsManager; import android.content.Context; -import android.net.INetworkStatsService; -import android.net.INetworkStatsSession; import android.net.NetworkPolicy; import android.net.NetworkPolicyManager; -import android.net.NetworkStatsHistory; import android.net.NetworkTemplate; -import android.net.TrafficStats; -import android.os.RemoteException; -import android.os.ServiceManager; import android.text.format.DateUtils; import android.util.Pair; +import android.util.Range; import androidx.annotation.VisibleForTesting; import androidx.loader.content.AsyncTaskLoader; @@ -52,8 +45,6 @@ public abstract class NetworkCycleDataLoader<D> extends AsyncTaskLoader<D> { protected final NetworkTemplate mNetworkTemplate; private final NetworkPolicy mPolicy; private final ArrayList<Long> mCycles; - @VisibleForTesting - final INetworkStatsService mNetworkStatsService; protected NetworkCycleDataLoader(Builder<?> builder) { super(builder.mContext); @@ -61,8 +52,6 @@ public abstract class NetworkCycleDataLoader<D> extends AsyncTaskLoader<D> { mCycles = builder.mCycles; mNetworkStatsManager = (NetworkStatsManager) builder.mContext.getSystemService(Context.NETWORK_STATS_SERVICE); - mNetworkStatsService = INetworkStatsService.Stub.asInterface( - ServiceManager.getService(Context.NETWORK_STATS_SERVICE)); final NetworkPolicyEditor policyEditor = new NetworkPolicyEditor(NetworkPolicyManager.from(builder.mContext)); policyEditor.read(); @@ -112,23 +101,20 @@ public abstract class NetworkCycleDataLoader<D> extends AsyncTaskLoader<D> { @VisibleForTesting void loadFourWeeksData() { + if (mNetworkTemplate == null) return; + final NetworkStats stats = mNetworkStatsManager.queryDetailsForDevice( + mNetworkTemplate, Long.MIN_VALUE, Long.MAX_VALUE); try { - final INetworkStatsSession networkSession = mNetworkStatsService.openSession(); - final NetworkStatsHistory networkHistory = networkSession.getHistoryForNetwork( - mNetworkTemplate, FIELD_RX_BYTES | FIELD_TX_BYTES); - final long historyStart = networkHistory.getStart(); - final long historyEnd = networkHistory.getEnd(); - - long cycleEnd = historyEnd; - while (cycleEnd > historyStart) { + final Range<Long> historyTimeRange = getTimeRangeOf(stats); + + long cycleEnd = historyTimeRange.getUpper(); + while (cycleEnd > historyTimeRange.getLower()) { final long cycleStart = cycleEnd - (DateUtils.WEEK_IN_MILLIS * 4); recordUsage(cycleStart, cycleEnd); cycleEnd = cycleStart; } - - TrafficStats.closeQuietly(networkSession); - } catch (RemoteException e) { - throw new RuntimeException(e); + } catch (IllegalArgumentException e) { + // Empty history, ignore. } } @@ -169,6 +155,32 @@ public abstract class NetworkCycleDataLoader<D> extends AsyncTaskLoader<D> { return bytes; } + @NonNull + @VisibleForTesting + Range getTimeRangeOf(@NonNull NetworkStats stats) { + long start = Long.MAX_VALUE; + long end = Long.MIN_VALUE; + while (hasNextBucket(stats)) { + final NetworkStats.Bucket bucket = getNextBucket(stats); + start = Math.min(start, bucket.getStartTimeStamp()); + end = Math.max(end, bucket.getEndTimeStamp()); + } + return new Range(start, end); + } + + @VisibleForTesting + boolean hasNextBucket(@NonNull NetworkStats stats) { + return stats.hasNextBucket(); + } + + @NonNull + @VisibleForTesting + NetworkStats.Bucket getNextBucket(@NonNull NetworkStats stats) { + NetworkStats.Bucket bucket = new NetworkStats.Bucket(); + stats.getNextBucket(bucket); + return bucket; + } + @VisibleForTesting(otherwise = VisibleForTesting.NONE) public ArrayList<Long> getCycles() { return mCycles; diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java index 63153f89ad99..10ccd22eca83 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java @@ -708,11 +708,11 @@ public class ApplicationsStateRoboTest { throws RemoteException { if (ownerApps != null) { - when(mApplicationsState.mIpm.getInstalledApplications(anyInt(), eq(0))) + when(mApplicationsState.mIpm.getInstalledApplications(anyLong(), eq(0))) .thenReturn(new ParceledListSlice<>(ownerApps)); } if (profileApps != null) { - when(mApplicationsState.mIpm.getInstalledApplications(anyInt(), eq(PROFILE_USERID))) + when(mApplicationsState.mIpm.getInstalledApplications(anyLong(), eq(PROFILE_USERID))) .thenReturn(new ParceledListSlice<>(profileApps)); } final InterestingConfigChanges configChanges = mock(InterestingConfigChanges.class); diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java index bee466d39c23..852ac5ca6abe 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java @@ -129,7 +129,6 @@ public class BluetoothEventManagerTest { @Test public void intentWithExtraState_audioStateChangedShouldDispatchToRegisterCallback() { mBluetoothEventManager.registerCallback(mBluetoothCallback); - mBluetoothEventManager.registerIntentReceiver(); mIntent = new Intent(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED); mContext.sendBroadcast(mIntent); @@ -143,7 +142,6 @@ public class BluetoothEventManagerTest { @Test public void intentWithExtraState_phoneStateChangedShouldDispatchToRegisterCallback() { mBluetoothEventManager.registerCallback(mBluetoothCallback); - mBluetoothEventManager.registerIntentReceiver(); mIntent = new Intent(TelephonyManager.ACTION_PHONE_STATE_CHANGED); mContext.sendBroadcast(mIntent); @@ -169,7 +167,6 @@ public class BluetoothEventManagerTest { @Test public void dispatchAclConnectionStateChanged_aclDisconnected_shouldDispatchCallback() { mBluetoothEventManager.registerCallback(mBluetoothCallback); - mBluetoothEventManager.registerIntentReceiver(); mIntent = new Intent(BluetoothDevice.ACTION_ACL_DISCONNECTED); mIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice); @@ -182,7 +179,6 @@ public class BluetoothEventManagerTest { @Test public void dispatchAclConnectionStateChanged_aclConnected_shouldDispatchCallback() { mBluetoothEventManager.registerCallback(mBluetoothCallback); - mBluetoothEventManager.registerIntentReceiver(); mIntent = new Intent(BluetoothDevice.ACTION_ACL_CONNECTED); mIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice); @@ -196,7 +192,6 @@ public class BluetoothEventManagerTest { public void dispatchAclConnectionStateChanged_aclDisconnected_shouldNotCallbackSubDevice() { when(mCachedDeviceManager.isSubDevice(mBluetoothDevice)).thenReturn(true); mBluetoothEventManager.registerCallback(mBluetoothCallback); - mBluetoothEventManager.registerIntentReceiver(); mIntent = new Intent(BluetoothDevice.ACTION_ACL_DISCONNECTED); mIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice); @@ -210,7 +205,6 @@ public class BluetoothEventManagerTest { public void dispatchAclConnectionStateChanged_aclConnected_shouldNotCallbackSubDevice() { when(mCachedDeviceManager.isSubDevice(mBluetoothDevice)).thenReturn(true); mBluetoothEventManager.registerCallback(mBluetoothCallback); - mBluetoothEventManager.registerIntentReceiver(); mIntent = new Intent(BluetoothDevice.ACTION_ACL_CONNECTED); mIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice); @@ -224,7 +218,6 @@ public class BluetoothEventManagerTest { public void dispatchAclConnectionStateChanged_findDeviceReturnNull_shouldNotDispatchCallback() { when(mCachedDeviceManager.findDevice(mBluetoothDevice)).thenReturn(null); mBluetoothEventManager.registerCallback(mBluetoothCallback); - mBluetoothEventManager.registerIntentReceiver(); mIntent = new Intent(BluetoothDevice.ACTION_ACL_CONNECTED); mIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice); @@ -361,7 +354,6 @@ public class BluetoothEventManagerTest { @Test public void showUnbondMessage_reasonAuthTimeout_showCorrectedErrorCode() { - mBluetoothEventManager.registerIntentReceiver(); mIntent = new Intent(BluetoothDevice.ACTION_BOND_STATE_CHANGED); mIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice); mIntent.putExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_NONE); @@ -377,7 +369,6 @@ public class BluetoothEventManagerTest { @Test public void showUnbondMessage_reasonRemoteDeviceDown_showCorrectedErrorCode() { - mBluetoothEventManager.registerIntentReceiver(); mIntent = new Intent(BluetoothDevice.ACTION_BOND_STATE_CHANGED); mIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice); mIntent.putExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_NONE); @@ -394,7 +385,6 @@ public class BluetoothEventManagerTest { @Test public void showUnbondMessage_reasonAuthRejected_showCorrectedErrorCode() { - mBluetoothEventManager.registerIntentReceiver(); mIntent = new Intent(BluetoothDevice.ACTION_BOND_STATE_CHANGED); mIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice); mIntent.putExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_NONE); @@ -410,7 +400,6 @@ public class BluetoothEventManagerTest { @Test public void showUnbondMessage_reasonAuthFailed_showCorrectedErrorCode() { - mBluetoothEventManager.registerIntentReceiver(); mIntent = new Intent(BluetoothDevice.ACTION_BOND_STATE_CHANGED); mIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice); mIntent.putExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_NONE); diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/ConnectivityPreferenceControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/ConnectivityPreferenceControllerTest.java index 4444e6369b67..c1cc3ae9778a 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/ConnectivityPreferenceControllerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/ConnectivityPreferenceControllerTest.java @@ -33,6 +33,7 @@ import android.os.Handler; import com.android.settingslib.core.lifecycle.Lifecycle; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -54,6 +55,7 @@ public class ConnectivityPreferenceControllerTest { } @Test + @Ignore public void testBroadcastReceiver() { final AbstractConnectivityPreferenceController preferenceController = spy(new ConcreteConnectivityPreferenceController(mContext, mLifecycle)); diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/dream/DreamBackendTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/dream/DreamBackendTest.java new file mode 100644 index 000000000000..53d465305a69 --- /dev/null +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/dream/DreamBackendTest.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.settingslib.dream; + + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.content.res.Resources; + +import com.android.settingslib.R; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; +import org.robolectric.shadows.ShadowSettings; + +@RunWith(RobolectricTestRunner.class) +@Config(shadows = {ShadowSettings.ShadowSecure.class}) +public final class DreamBackendTest { + private static final int[] SUPPORTED_DREAM_COMPLICATIONS = {1, 2, 3}; + private static final int[] DEFAULT_DREAM_COMPLICATIONS = {1, 3, 4}; + + @Mock + private Context mContext; + private DreamBackend mBackend; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + when(mContext.getApplicationContext()).thenReturn(mContext); + + final Resources res = mock(Resources.class); + when(mContext.getResources()).thenReturn(res); + when(res.getIntArray(R.array.config_supportedDreamComplications)).thenReturn( + SUPPORTED_DREAM_COMPLICATIONS); + when(res.getIntArray(R.array.config_dreamComplicationsEnabledByDefault)).thenReturn( + DEFAULT_DREAM_COMPLICATIONS); + mBackend = new DreamBackend(mContext); + } + + @After + public void tearDown() { + ShadowSettings.ShadowSecure.reset(); + } + + @Test + public void testSupportedComplications() { + assertThat(mBackend.getSupportedComplications()).containsExactly(1, 2, 3); + } + + @Test + public void testGetEnabledDreamComplications_default() { + assertThat(mBackend.getEnabledComplications()).containsExactly(1, 3); + } + + @Test + public void testEnableComplication() { + mBackend.setComplicationEnabled(/* complicationType= */ 2, true); + assertThat(mBackend.getEnabledComplications()).containsExactly(1, 2, 3); + } + + @Test + public void testEnableComplication_notSupported() { + mBackend.setComplicationEnabled(/* complicationType= */ 5, true); + assertThat(mBackend.getEnabledComplications()).containsExactly(1, 3); + } + + @Test + public void testDisableComplication() { + mBackend.setComplicationEnabled(/* complicationType= */ 1, false); + assertThat(mBackend.getEnabledComplications()).containsExactly(3); + } +} + diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/BiometricActionDisabledByAdminControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/BiometricActionDisabledByAdminControllerTest.java index 8ec577e8a764..06b6fc8ef73d 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/BiometricActionDisabledByAdminControllerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/BiometricActionDisabledByAdminControllerTest.java @@ -22,7 +22,6 @@ import static com.android.settingslib.enterprise.FakeDeviceAdminStringProvider.D import static junit.framework.Assert.assertNotNull; import static junit.framework.TestCase.assertEquals; -import static junit.framework.TestCase.assertSame; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @@ -67,7 +66,7 @@ public class BiometricActionDisabledByAdminControllerTest { @Test public void buttonClicked() { - ComponentName componentName = mock(ComponentName.class); + ComponentName componentName = new ComponentName("com.android.test", "AThing"); RestrictedLockUtils.EnforcedAdmin enforcedAdmin = new RestrictedLockUtils.EnforcedAdmin( componentName, new UserHandle(UserHandle.myUserId())); @@ -83,6 +82,6 @@ public class BiometricActionDisabledByAdminControllerTest { assertEquals(Settings.SUPERVISOR_VERIFICATION_SETTING_BIOMETRICS, intentCaptor.getValue().getStringExtra( Settings.EXTRA_SUPERVISOR_RESTRICTED_SETTING_KEY)); - assertSame(componentName, intentCaptor.getValue().getComponent()); + assertEquals(componentName.getPackageName(), intentCaptor.getValue().getPackage()); } } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java index 2d53831a30e7..aa0ef91be46b 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java @@ -46,6 +46,7 @@ import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.testutils.shadow.ShadowRouter2Manager; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -731,6 +732,7 @@ public class InfoMediaManagerTest { } @Test + @Ignore public void shouldDisableMediaOutput_infosSizeEqual1_returnsTrue() { final MediaRoute2Info info = mock(MediaRoute2Info.class); final List<MediaRoute2Info> infos = new ArrayList<>(); diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleDataLoaderTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleDataLoaderTest.java index 74b91510cf3f..c79440e58e17 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleDataLoaderTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleDataLoaderTest.java @@ -16,24 +16,24 @@ package com.android.settingslib.net; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.nullable; +import static android.app.usage.NetworkStats.Bucket.UID_ALL; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.annotation.NonNull; +import android.app.usage.NetworkStats; import android.app.usage.NetworkStatsManager; import android.content.Context; import android.net.ConnectivityManager; -import android.net.INetworkStatsService; -import android.net.INetworkStatsSession; import android.net.NetworkPolicy; import android.net.NetworkPolicyManager; -import android.net.NetworkStatsHistory; import android.net.NetworkTemplate; -import android.os.RemoteException; import android.text.format.DateUtils; import android.util.Range; @@ -49,6 +49,8 @@ import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.Iterator; import java.util.List; +import java.util.Queue; +import java.util.concurrent.LinkedBlockingQueue; @RunWith(RobolectricTestRunner.class) public class NetworkCycleDataLoaderTest { @@ -63,8 +65,6 @@ public class NetworkCycleDataLoaderTest { private NetworkPolicy mPolicy; @Mock private Iterator<Range<ZonedDateTime>> mIterator; - @Mock - private INetworkStatsService mNetworkStatsService; private NetworkCycleDataTestLoader mLoader; @@ -132,20 +132,24 @@ public class NetworkCycleDataLoaderTest { verify(mLoader).recordUsage(nowInMs, nowInMs); } + private NetworkStats.Bucket makeMockBucket(int uid, long rxBytes, long txBytes, + long start, long end) { + NetworkStats.Bucket ret = mock(NetworkStats.Bucket.class); + when(ret.getUid()).thenReturn(uid); + when(ret.getRxBytes()).thenReturn(rxBytes); + when(ret.getTxBytes()).thenReturn(txBytes); + when(ret.getStartTimeStamp()).thenReturn(start); + when(ret.getEndTimeStamp()).thenReturn(end); + return ret; + } + @Test - public void loadFourWeeksData_shouldRecordUsageForLast4Weeks() throws RemoteException { + public void loadFourWeeksData_shouldRecordUsageForLast4Weeks() { mLoader = spy(new NetworkCycleDataTestLoader(mContext)); - ReflectionHelpers.setField(mLoader, "mNetworkStatsService", mNetworkStatsService); - final INetworkStatsSession networkSession = mock(INetworkStatsSession.class); - when(mNetworkStatsService.openSession()).thenReturn(networkSession); - final NetworkStatsHistory networkHistory = mock(NetworkStatsHistory.class); - when(networkSession.getHistoryForNetwork(nullable(NetworkTemplate.class), anyInt())) - .thenReturn(networkHistory); final long now = System.currentTimeMillis(); final long fourWeeksAgo = now - (DateUtils.WEEK_IN_MILLIS * 4); final long twoDaysAgo = now - (DateUtils.DAY_IN_MILLIS * 2); - when(networkHistory.getStart()).thenReturn(twoDaysAgo); - when(networkHistory.getEnd()).thenReturn(now); + mLoader.addBucket(makeMockBucket(UID_ALL, 123, 456, twoDaysAgo, now)); mLoader.loadFourWeeksData(); @@ -173,10 +177,31 @@ public class NetworkCycleDataLoaderTest { verify(mLoader).recordUsage(thirtyDaysAgo, twentyDaysAgo); } + @Test + public void getTimeRangeOf() { + mLoader = spy(new NetworkCycleDataTestLoader(mContext)); + // If empty, new Range(MAX_VALUE, MIN_VALUE) will be constructed. Hence, the function + // should throw. + assertThrows(IllegalArgumentException.class, + () -> mLoader.getTimeRangeOf(mock(NetworkStats.class))); + + mLoader.addBucket(makeMockBucket(UID_ALL, 123, 456, 0, 10)); + // Feed the function with unused NetworkStats. The actual data injection is + // done by addBucket. + assertEquals(new Range(0L, 10L), mLoader.getTimeRangeOf(mock(NetworkStats.class))); + + mLoader.addBucket(makeMockBucket(UID_ALL, 123, 456, 0, 10)); + mLoader.addBucket(makeMockBucket(UID_ALL, 123, 456, 30, 40)); + mLoader.addBucket(makeMockBucket(UID_ALL, 123, 456, 10, 25)); + assertEquals(new Range(0L, 40L), mLoader.getTimeRangeOf(mock(NetworkStats.class))); + } + public class NetworkCycleDataTestLoader extends NetworkCycleDataLoader<List<NetworkCycleData>> { + private final Queue<NetworkStats.Bucket> mMockedBuckets = new LinkedBlockingQueue<>(); private NetworkCycleDataTestLoader(Context context) { - super(NetworkCycleDataLoader.builder(mContext)); + super(NetworkCycleDataLoader.builder(mContext) + .setNetworkTemplate(mock(NetworkTemplate.class))); mContext = context; } @@ -188,5 +213,19 @@ public class NetworkCycleDataLoaderTest { List<NetworkCycleData> getCycleUsage() { return null; } + + public void addBucket(NetworkStats.Bucket bucket) { + mMockedBuckets.add(bucket); + } + + @Override + public boolean hasNextBucket(@NonNull NetworkStats unused) { + return !mMockedBuckets.isEmpty(); + } + + @Override + public NetworkStats.Bucket getNextBucket(@NonNull NetworkStats unused) { + return mMockedBuckets.remove(); + } } } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/AccessPointPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/AccessPointPreferenceTest.java index 2bd20a933c58..30267f793cd6 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/AccessPointPreferenceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/AccessPointPreferenceTest.java @@ -19,11 +19,15 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.Context; import android.graphics.drawable.ColorDrawable; +import android.net.wifi.WifiManager; + +import androidx.test.core.app.ApplicationProvider; import org.junit.Before; import org.junit.Test; @@ -36,7 +40,7 @@ import org.robolectric.RuntimeEnvironment; @RunWith(RobolectricTestRunner.class) public class AccessPointPreferenceTest { - private Context mContext = RuntimeEnvironment.application; + private Context mContext; @Mock private AccessPoint mockAccessPoint; @@ -53,6 +57,8 @@ public class AccessPointPreferenceTest { @Before public void setUp() { MockitoAnnotations.initMocks(this); + mContext = spy(ApplicationProvider.getApplicationContext()); + when(mContext.getSystemService(Context.WIFI_SERVICE)).thenReturn(mock(WifiManager.class)); when(mockIconInjector.getIcon(anyInt())).thenReturn(new ColorDrawable()); } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java index 7c2b904fc576..e7b3fe9ab8da 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java @@ -20,6 +20,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.any; 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 static org.mockito.Mockito.when; @@ -32,6 +33,7 @@ import android.net.ScoredNetwork; import android.net.WifiKey; import android.net.wifi.ScanResult; import android.net.wifi.WifiConfiguration; +import android.net.wifi.WifiManager; import android.net.wifi.WifiNetworkScoreCache; import android.os.Bundle; import android.os.Parcelable; @@ -74,6 +76,7 @@ public class WifiUtilsTest { public void setUp() { MockitoAnnotations.initMocks(this); mContext = spy(ApplicationProvider.getApplicationContext()); + when(mContext.getSystemService(Context.WIFI_SERVICE)).thenReturn(mock(WifiManager.class)); } @Test diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java index 231252502937..5f549fd05e1a 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java @@ -296,6 +296,7 @@ public class SecureSettingsValidators { VALIDATORS.put(Secure.CLIPBOARD_SHOW_ACCESS_NOTIFICATIONS, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.NOTIFICATION_BUBBLES, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.LOCATION_TIME_ZONE_DETECTION_ENABLED, BOOLEAN_VALIDATOR); + VALIDATORS.put(Secure.LOCATION_SHOW_SYSTEM_OPS, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.DEVICE_STATE_ROTATION_LOCK, value -> { if (TextUtils.isEmpty(value)) { return true; @@ -324,6 +325,7 @@ public class SecureSettingsValidators { return true; }); VALIDATORS.put(Secure.ODI_CAPTIONS_VOLUME_UI_ENABLED, BOOLEAN_VALIDATOR); + VALIDATORS.put(Secure.FAST_PAIR_SCAN_ENABLED, BOOLEAN_VALIDATOR); } } diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java index c5f027b829d9..7381e05972d4 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java @@ -1378,9 +1378,6 @@ class SettingsProtoDumpUtil { Settings.Global.SYS_STORAGE_CACHE_PERCENTAGE, GlobalSettingsProto.Sys.STORAGE_CACHE_PERCENTAGE); dumpSetting(s, p, - Settings.Global.SYS_STORAGE_CACHE_MAX_BYTES, - GlobalSettingsProto.Sys.STORAGE_CACHE_MAX_BYTES); - dumpSetting(s, p, Settings.Global.SYS_UIDCPUPOWER, GlobalSettingsProto.Sys.UIDCPUPOWER); p.end(sysToken); diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java index a3f39959e7cf..720fb6ceb131 100644 --- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java +++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java @@ -462,7 +462,6 @@ public class SettingsBackupTest { Settings.Global.SYNC_MANAGER_CONSTANTS, Settings.Global.SYNC_MAX_RETRY_DELAY_IN_SECONDS, Settings.Global.SYS_FREE_STORAGE_LOG_INTERVAL, - Settings.Global.SYS_STORAGE_CACHE_MAX_BYTES, Settings.Global.SYS_STORAGE_CACHE_PERCENTAGE, Settings.Global.SYS_STORAGE_FULL_THRESHOLD_BYTES, Settings.Global.SYS_STORAGE_THRESHOLD_MAX_BYTES, diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index c1a812f5ebb8..46e24faed5b9 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -250,6 +250,7 @@ <uses-permission android:name="android.permission.MANAGE_APP_PREDICTIONS" /> <uses-permission android:name="android.permission.MANAGE_SEARCH_UI" /> <uses-permission android:name="android.permission.MANAGE_SMARTSPACE" /> + <uses-permission android:name="android.permission.MANAGE_WALLPAPER_EFFECTS_GENERATION" /> <uses-permission android:name="android.permission.MANAGE_UI_TRANSLATION" /> <uses-permission android:name="android.permission.NETWORK_SETTINGS" /> <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" /> @@ -548,6 +549,10 @@ <!-- Permission required for CTS test - PeopleManagerTest --> <uses-permission android:name="android.permission.READ_PEOPLE_DATA" /> + <!-- Permissions required for CTS test - TrustTestCases --> + <uses-permission android:name="android.permission.PROVIDE_TRUST_AGENT" /> + <uses-permission android:name="android.permission.ACCESS_KEYGUARD_SECURE_STORAGE" /> + <!-- Permission required for CTS test - CtsGameManagerTestCases --> <uses-permission android:name="android.permission.MANAGE_GAME_MODE" /> @@ -623,10 +628,6 @@ <!-- Permission required for CTS test - Notification test suite --> <uses-permission android:name="android.permission.REVOKE_POST_NOTIFICATIONS_WITHOUT_KILL" /> - <!-- Permission required for CTS test - CommunalManagerTest --> - <uses-permission android:name="android.permission.WRITE_COMMUNAL_STATE" /> - <uses-permission android:name="android.permission.READ_COMMUNAL_STATE" /> - <!-- Permission required for CTS test - CaptioningManagerTest --> <uses-permission android:name="android.permission.SET_SYSTEM_AUDIO_CAPTION" /> diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 92ae92e99747..f35f5dd0c3ae 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -153,6 +153,9 @@ <!-- Needed for WallpaperManager.clear in ImageWallpaper.updateWallpaperLocked --> <uses-permission android:name="android.permission.SET_WALLPAPER"/> + <!-- Needed for WallpaperManager.getWallpaperDimAmount in StatusBar.updateTheme --> + <uses-permission android:name="android.permission.SET_WALLPAPER_DIM_AMOUNT"/> + <!-- Wifi Display --> <uses-permission android:name="android.permission.CONFIGURE_WIFI_DISPLAY" /> diff --git a/packages/SystemUI/OWNERS b/packages/SystemUI/OWNERS index e1da74466b55..3ae85e7e2325 100644 --- a/packages/SystemUI/OWNERS +++ b/packages/SystemUI/OWNERS @@ -11,8 +11,10 @@ asc@google.com awickham@google.com beverlyt@google.com brockman@google.com +brzezinski@google.com brycelee@google.com ccassidy@google.com +chrisgollner@google.com cinek@google.com cwren@google.com dupin@google.com @@ -43,6 +45,8 @@ mpietal@google.com mrcasey@google.com mrenouf@google.com nesciosquid@google.com +nickchameyev@google.com +nicomazz@google.com ogunwale@google.com peanutbutter@google.com pinyaoting@google.com diff --git a/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java b/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java index da9a92a1f6b4..68c8c3e02a42 100644 --- a/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java +++ b/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java @@ -122,6 +122,11 @@ public interface BcSmartspaceDataPlugin extends Plugin { * Set or clear device media playing */ void setMediaTarget(@Nullable SmartspaceTarget target); + + /** + * Get the index of the currently selected page. + */ + int getSelectedPage(); } /** Interface for launching Intents, which can differ on the lockscreen */ diff --git a/packages/SystemUI/res-keyguard/layout/footer_actions.xml b/packages/SystemUI/res-keyguard/layout/footer_actions.xml index dfc3e63a4e2b..ecb3cb3961a4 100644 --- a/packages/SystemUI/res-keyguard/layout/footer_actions.xml +++ b/packages/SystemUI/res-keyguard/layout/footer_actions.xml @@ -22,21 +22,6 @@ android:layout_height="48dp" android:gravity="center_vertical"> - <com.android.systemui.statusbar.AlphaOptimizedImageView - android:id="@android:id/edit" - android:layout_width="0dp" - android:layout_height="@dimen/qs_footer_action_button_size" - android:layout_marginEnd="@dimen/qs_tile_margin_horizontal" - android:layout_weight="1" - android:background="@drawable/qs_footer_action_chip_background" - android:clickable="true" - android:clipToPadding="false" - android:contentDescription="@string/accessibility_quick_settings_edit" - android:focusable="true" - android:padding="@dimen/qs_footer_icon_padding" - android:src="@*android:drawable/ic_mode_edit" - android:tint="?android:attr/textColorPrimary" /> - <com.android.systemui.statusbar.phone.MultiUserSwitch android:id="@+id/multi_user_switch" android:layout_width="0dp" diff --git a/packages/SystemUI/res-keyguard/values/bools.xml b/packages/SystemUI/res-keyguard/values/bools.xml index c5bf4ce48188..2b83787172d3 100644 --- a/packages/SystemUI/res-keyguard/values/bools.xml +++ b/packages/SystemUI/res-keyguard/values/bools.xml @@ -17,5 +17,4 @@ <resources> <bool name="kg_show_ime_at_screen_on">true</bool> <bool name="kg_use_all_caps">true</bool> - <bool name="flag_active_unlock">false</bool> </resources> diff --git a/packages/SystemUI/res/drawable/ic_warning.xml b/packages/SystemUI/res/drawable/ic_warning.xml new file mode 100644 index 000000000000..fbed779ec70f --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_warning.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2022 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> + <path android:fillColor="@android:color/white" android:pathData="M12,12.5zM1,21L12,2l11,19zM11,15h2v-5h-2zM12,18q0.425,0 0.713,-0.288Q13,17.425 13,17t-0.287,-0.712Q12.425,16 12,16t-0.713,0.288Q11,16.575 11,17t0.287,0.712Q11.575,18 12,18zM4.45,19h15.1L12,6z"/> +</vector> diff --git a/packages/SystemUI/res/drawable/qs_customizer_background_transition.xml b/packages/SystemUI/res/drawable/qs_customizer_background_transition.xml index ed8f61a97c2a..6fa9eac2fc29 100644 --- a/packages/SystemUI/res/drawable/qs_customizer_background_transition.xml +++ b/packages/SystemUI/res/drawable/qs_customizer_background_transition.xml @@ -15,7 +15,7 @@ --> <inset xmlns:android="http://schemas.android.com/apk/res/android"> <shape> - <solid android:color="@color/qs_detail_transition"/> + <solid android:color="@android:color/transparent"/> <corners android:radius="?android:attr/dialogCornerRadius" /> </shape> </inset> diff --git a/packages/SystemUI/res/drawable/qs_detail_background.xml b/packages/SystemUI/res/drawable/qs_detail_background.xml index e5c7352807f8..c23649d629bd 100644 --- a/packages/SystemUI/res/drawable/qs_detail_background.xml +++ b/packages/SystemUI/res/drawable/qs_detail_background.xml @@ -17,7 +17,7 @@ Copyright (C) 2014 The Android Open Source Project <item> <inset> <shape> - <solid android:color="@color/qs_detail_transition"/> + <solid android:color="@android:color/transparent"/> <corners android:radius="@dimen/qs_footer_action_corner_radius" /> </shape> </inset> diff --git a/packages/SystemUI/res/layout/media_ttt_chip.xml b/packages/SystemUI/res/layout/media_ttt_chip.xml index 2d082dc7d5e2..a5fdcd9e2671 100644 --- a/packages/SystemUI/res/layout/media_ttt_chip.xml +++ b/packages/SystemUI/res/layout/media_ttt_chip.xml @@ -28,8 +28,8 @@ <com.android.internal.widget.CachingIconView android:id="@+id/app_icon" - android:layout_width="@dimen/media_ttt_icon_size" - android:layout_height="@dimen/media_ttt_icon_size" + android:layout_width="@dimen/media_ttt_app_icon_size" + android:layout_height="@dimen/media_ttt_app_icon_size" android:layout_marginEnd="12dp" /> @@ -41,23 +41,34 @@ android:textColor="?android:attr/textColorPrimary" /> + <!-- At most one of [loading, failure_icon, undo] will be visible at a time. --> + <ProgressBar android:id="@+id/loading" android:indeterminate="true" - android:layout_width="@dimen/media_ttt_loading_size" - android:layout_height="@dimen/media_ttt_loading_size" - android:layout_marginStart="12dp" + android:layout_width="@dimen/media_ttt_status_icon_size" + android:layout_height="@dimen/media_ttt_status_icon_size" + android:layout_marginStart="@dimen/media_ttt_last_item_start_margin" android:indeterminateTint="?androidprv:attr/colorAccentPrimaryVariant" style="?android:attr/progressBarStyleSmall" /> + <ImageView + android:id="@+id/failure_icon" + android:layout_width="@dimen/media_ttt_status_icon_size" + android:layout_height="@dimen/media_ttt_status_icon_size" + android:layout_marginStart="@dimen/media_ttt_last_item_start_margin" + android:src="@drawable/ic_warning" + android:tint="@color/GM2_red_500" + /> + <TextView android:id="@+id/undo" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/media_transfer_undo" android:textColor="?androidprv:attr/textColorOnAccent" - android:layout_marginStart="12dp" + android:layout_marginStart="@dimen/media_ttt_last_item_start_margin" android:textSize="@dimen/media_ttt_text_size" android:paddingStart="@dimen/media_ttt_chip_outer_padding" android:paddingEnd="@dimen/media_ttt_chip_outer_padding" diff --git a/packages/SystemUI/res/layout/qs_footer_impl.xml b/packages/SystemUI/res/layout/qs_footer_impl.xml index e70084b80308..5cd9e9485fda 100644 --- a/packages/SystemUI/res/layout/qs_footer_impl.xml +++ b/packages/SystemUI/res/layout/qs_footer_impl.xml @@ -43,7 +43,6 @@ android:id="@+id/build" android:layout_width="0dp" android:layout_height="match_parent" - android:paddingStart="@dimen/qs_tile_margin_horizontal" android:paddingEnd="4dp" android:layout_weight="1" android:clickable="true" @@ -61,10 +60,23 @@ android:layout_gravity="center_vertical" android:visibility="gone" /> - <View + <FrameLayout android:layout_width="0dp" android:layout_height="match_parent" - android:layout_weight="1" /> + android:layout_weight="1"> + <com.android.systemui.statusbar.AlphaOptimizedImageView + android:id="@android:id/edit" + android:layout_width="@dimen/qs_footer_action_button_size" + android:layout_height="@dimen/qs_footer_action_button_size" + android:layout_gravity="center_vertical|end" + android:background="?android:attr/selectableItemBackground" + android:clickable="true" + android:contentDescription="@string/accessibility_quick_settings_edit" + android:focusable="true" + android:padding="@dimen/qs_footer_icon_padding" + android:src="@*android:drawable/ic_mode_edit" + android:tint="?android:attr/textColorPrimary" /> + </FrameLayout> </LinearLayout> diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml index 07f843bb2139..10b8ef414617 100644 --- a/packages/SystemUI/res/values-af/strings.xml +++ b/packages/SystemUI/res/values-af/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi is nie gekoppel nie"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Helderheid"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Kleuromkering"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Kleurregstelling"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"Meer instellings"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Gebruikerinstellings"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Klaar"</string> @@ -450,8 +449,8 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Ontsluit om te gebruik"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Kon nie jou kaarte kry nie; probeer later weer"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Sluitskerminstellings"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"Skandeer QR"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Klik om \'n QR-kode te skandeer"</string> + <string name="qr_code_scanner_title" msgid="5660820608548306581">"QR-kode"</string> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"Tik om te skandeer"</string> <string name="status_bar_work" msgid="5238641949837091056">"Werkprofiel"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Vliegtuigmodus"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"Jy sal nie jou volgende wekker <xliff:g id="WHEN">%1$s</xliff:g> hoor nie"</string> @@ -792,7 +791,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Speel <xliff:g id="SONG_NAME">%1$s</xliff:g> deur <xliff:g id="ARTIST_NAME">%2$s</xliff:g> vanaf <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Speel <xliff:g id="SONG_NAME">%1$s</xliff:g> vanaf <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Ontdoen"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Beweeg nader om op <xliff:g id="DEVICENAME">%1$s</xliff:g> te speel"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"Speel tans op <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Onaktief, gaan program na"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Nie gekry nie"</string> @@ -880,4 +880,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Kies gebruiker"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Programme wat op die agtergrond werk"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Stop"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-af/tiles_states_strings.xml b/packages/SystemUI/res/values-af/tiles_states_strings.xml index 4b1a5b85d1d2..08e60c51ece3 100644 --- a/packages/SystemUI/res/values-af/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-af/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Af"</item> <item msgid="2075645297847971154">"Aan"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"Onbeskikbaar"</item> + <item msgid="1909756493418256167">"Af"</item> + <item msgid="4531508423703413340">"Aan"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"Onbeskikbaar"</item> <item msgid="9103697205127645916">"Af"</item> diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml index c31179c643d2..6fe6f539df3c 100644 --- a/packages/SystemUI/res/values-am/strings.xml +++ b/packages/SystemUI/res/values-am/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi አልተገናኘም"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"ብሩህነት"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"ተቃራኒ ቀለም"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"የቀለም ማስተካከያ"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"ተጨማሪ ቅንብሮች"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"የተጠቃሚ ቅንብሮች"</string> <string name="quick_settings_done" msgid="2163641301648855793">"ተከናውኗል"</string> @@ -450,8 +449,9 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"ለማየት ይክፈቱ"</string> <string name="wallet_error_generic" msgid="257704570182963611">"የእርስዎን ካርዶች ማግኘት ላይ ችግር ነበር፣ እባክዎ ቆይተው እንደገና ይሞክሩ"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"የገጽ መቆለፊያ ቅንብሮች"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"QR ቃኝ"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"QR ኮድን ለመቃኘት ጠቅ ያድርጉ"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"ለመቃኘት መታ ያድርጉ"</string> <string name="status_bar_work" msgid="5238641949837091056">"የስራ መገለጫ"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"የአውሮፕላን ሁነታ"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"የእርስዎን ቀጣይ ማንቂያ <xliff:g id="WHEN">%1$s</xliff:g> አይሰሙም"</string> @@ -792,7 +792,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="SONG_NAME">%1$s</xliff:g> በ<xliff:g id="ARTIST_NAME">%2$s</xliff:g> ከ<xliff:g id="APP_LABEL">%3$s</xliff:g> ያጫውቱ"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g> ከ<xliff:g id="APP_LABEL">%2$s</xliff:g> ያጫውቱ"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"ቀልብስ"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"በ<xliff:g id="DEVICENAME">%1$s</xliff:g> ላይ ለማጫወት ጠጋ ያድርጉ"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"በ<xliff:g id="DEVICENAME">%1$s</xliff:g> ላይ በማጫወት ላይ"</string> <string name="controls_error_timeout" msgid="794197289772728958">"ንቁ ያልኾነ፣ መተግበሪያን ይፈትሹ"</string> <string name="controls_error_removed" msgid="6675638069846014366">"አልተገኘም"</string> @@ -880,4 +881,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"ተጠቃሚን ይምረጡ"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"ከበስተጀርባ የሚሠሩ መተግበሪያዎች"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"መቆሚያ"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-am/tiles_states_strings.xml b/packages/SystemUI/res/values-am/tiles_states_strings.xml index 0f7ee3ecd8d2..c464f9a98cf7 100644 --- a/packages/SystemUI/res/values-am/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-am/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"ጠፍቷል"</item> <item msgid="2075645297847971154">"በርቷል"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"አይገኝም"</item> + <item msgid="1909756493418256167">"አጥፋ"</item> + <item msgid="4531508423703413340">"አብራ"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"አይገኝም"</item> <item msgid="9103697205127645916">"ጠፍቷል"</item> diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml index ec4816fd448a..5a0c7a1969fc 100644 --- a/packages/SystemUI/res/values-ar/strings.xml +++ b/packages/SystemUI/res/values-ar/strings.xml @@ -237,8 +237,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"لم يتم الاتصال بشبكة Wi-Fi."</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"السطوع"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"قلب الألوان"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"تصحيح الألوان"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"المزيد من الإعدادات"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"إعدادات المستخدم"</string> <string name="quick_settings_done" msgid="2163641301648855793">"تم"</string> @@ -462,8 +461,10 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"فتح القفل للاستخدام"</string> <string name="wallet_error_generic" msgid="257704570182963611">"حدثت مشكلة أثناء الحصول على البطاقات، يُرجى إعادة المحاولة لاحقًا."</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"إعدادات شاشة القفل"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"مسح رمز الاستجابة السريعة ضوئيًا"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"انقر لمسح رمز الاستجابة السريعة ضوئيًا."</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <!-- no translation found for qr_code_scanner_description (7937603775306661863) --> + <skip /> <string name="status_bar_work" msgid="5238641949837091056">"الملف الشخصي للعمل"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"وضع الطيران"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"لن تسمع المنبّه القادم في <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -816,7 +817,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"تشغيل <xliff:g id="SONG_NAME">%1$s</xliff:g> للفنان <xliff:g id="ARTIST_NAME">%2$s</xliff:g> من تطبيق <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"تشغيل <xliff:g id="SONG_NAME">%1$s</xliff:g> من تطبيق <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"تراجع"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"عليك الاقتراب لتشغيل الموسيقى على <xliff:g id="DEVICENAME">%1$s</xliff:g>."</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"جارٍ تشغيل الموسيقى على <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"غير نشط، تحقّق من التطبيق."</string> <string name="controls_error_removed" msgid="6675638069846014366">"لم يتم العثور عليه."</string> @@ -904,4 +906,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"اختيار المستخدم"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"التطبيقات التي يتم تشغيلها في الخلفية"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"إيقاف"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-ar/tiles_states_strings.xml b/packages/SystemUI/res/values-ar/tiles_states_strings.xml index 2da87c467177..2bfcf7c57b8b 100644 --- a/packages/SystemUI/res/values-ar/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-ar/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"الميزة غير مفعّلة"</item> <item msgid="2075645297847971154">"الميزة مفعّلة"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"غير متوفّر"</item> + <item msgid="1909756493418256167">"غير مفعّل"</item> + <item msgid="4531508423703413340">"مفعّل"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"الميزة غير متاحة"</item> <item msgid="9103697205127645916">"الميزة غير مفعّلة"</item> diff --git a/packages/SystemUI/res/values-as/strings.xml b/packages/SystemUI/res/values-as/strings.xml index f118633b199b..bf35ad202870 100644 --- a/packages/SystemUI/res/values-as/strings.xml +++ b/packages/SystemUI/res/values-as/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"ৱাই-ফাইৰ সৈতে সংযোগ হৈ থকা নাই"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"উজ্জ্বলতা"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"ৰং বিপৰীতকৰণ"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"ৰং শুধৰণী"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"অধিক ছেটিং"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"ব্যৱহাৰকাৰীৰ ছেটিং"</string> <string name="quick_settings_done" msgid="2163641301648855793">"সম্পন্ন কৰা হ’ল"</string> @@ -450,8 +449,10 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"ব্যৱহাৰ কৰিবলৈ আনলক কৰক"</string> <string name="wallet_error_generic" msgid="257704570182963611">"আপোনাৰ কাৰ্ড লাভ কৰোঁতে এটা সমস্যা হৈছে, অনুগ্ৰহ কৰি পাছত পুনৰ চেষ্টা কৰক"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"লক স্ক্ৰীনৰ ছেটিং"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"কিউআৰ স্কেন কৰক"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"এটা কিউআৰ ক’ড স্কেন কৰিবলৈ ক্লিক কৰক"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <!-- no translation found for qr_code_scanner_description (7937603775306661863) --> + <skip /> <string name="status_bar_work" msgid="5238641949837091056">"কৰ্মস্থানৰ প্ৰ\'ফাইল"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"এয়াৰপ্লেইন ম\'ড"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"আপুনি আপোনাৰ পিছৰটো এলাৰ্ম <xliff:g id="WHEN">%1$s</xliff:g> বজাত শুনা নাপাব"</string> @@ -792,7 +793,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g>ত <xliff:g id="ARTIST_NAME">%2$s</xliff:g>ৰ <xliff:g id="SONG_NAME">%1$s</xliff:g> গীতটো প্লে’ কৰক"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="APP_LABEL">%2$s</xliff:g>ত <xliff:g id="SONG_NAME">%1$s</xliff:g> গীতটো প্লে’ কৰক"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"আনডু কৰক"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"<xliff:g id="DEVICENAME">%1$s</xliff:g>ত প্লে\' কৰিবলৈ ওপৰলৈ যাওক"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"<xliff:g id="DEVICENAME">%1$s</xliff:g>ত প্লে\' কৰি থকা হৈছে"</string> <string name="controls_error_timeout" msgid="794197289772728958">"সক্ৰিয় নহয়, এপ্টো পৰীক্ষা কৰক"</string> <string name="controls_error_removed" msgid="6675638069846014366">"বিচাৰি পোৱা নগ’ল"</string> @@ -880,4 +882,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"ব্যৱহাৰকাৰী বাছনি কৰক"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"নেপথ্যত চলি থকা এপ্"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"বন্ধ কৰক"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-as/tiles_states_strings.xml b/packages/SystemUI/res/values-as/tiles_states_strings.xml index 2ede37473ea5..ba66f8c5f1cd 100644 --- a/packages/SystemUI/res/values-as/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-as/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"অফ আছে"</item> <item msgid="2075645297847971154">"অন কৰা আছে"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"উপলব্ধ নহয়"</item> + <item msgid="1909756493418256167">"অফ আছে"</item> + <item msgid="4531508423703413340">"অন কৰা আছে"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"উপলব্ধ নহয়"</item> <item msgid="9103697205127645916">"অফ আছে"</item> diff --git a/packages/SystemUI/res/values-az/strings.xml b/packages/SystemUI/res/values-az/strings.xml index 9abe0a7a04f7..d4917248de33 100644 --- a/packages/SystemUI/res/values-az/strings.xml +++ b/packages/SystemUI/res/values-az/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi qoşulu deyil"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Parlaqlıq"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Rəng inversiyası"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Rəng korreksiyası"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"Digər ayarlar"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"İstifadəçi ayarları"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Hazır"</string> @@ -450,8 +449,9 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"İstifadə etmək üçün kiliddən çıxarın"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Kartların əldə edilməsində problem oldu, sonra yenidən cəhd edin"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Kilid ekranı ayarları"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"QR kodunu skan edin"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"QR kodu skan etmək üçün tıklayın"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"Skanlamaq üçün toxunun"</string> <string name="status_bar_work" msgid="5238641949837091056">"İş profili"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Təyyarə rejimi"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"<xliff:g id="WHEN">%1$s</xliff:g> zaman növbəti xəbərdarlığınızı eşitməyəcəksiniz"</string> @@ -792,7 +792,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> tərəfindən <xliff:g id="SONG_NAME">%1$s</xliff:g> mahnısını <xliff:g id="APP_LABEL">%3$s</xliff:g> tətbiqindən oxudun"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g> mahnısını <xliff:g id="APP_LABEL">%2$s</xliff:g> tətbiqindən oxudun"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Geri qaytarın"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"<xliff:g id="DEVICENAME">%1$s</xliff:g> cihazında oxutmaq üçün yaxınlaşın"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"<xliff:g id="DEVICENAME">%1$s</xliff:g> cihazında oxudulur"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Aktiv deyil, tətbiqi yoxlayın"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Tapılmadı"</string> @@ -880,4 +881,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"İstifadəçi seçin"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Arxa fonda işləyən tətbiqlər"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Dayandırın"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-az/tiles_states_strings.xml b/packages/SystemUI/res/values-az/tiles_states_strings.xml index f52a9e1bf6fa..368966038b3d 100644 --- a/packages/SystemUI/res/values-az/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-az/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Deaktiv"</item> <item msgid="2075645297847971154">"Aktiv"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"Əlçatmazdır"</item> + <item msgid="1909756493418256167">"Deaktiv"</item> + <item msgid="4531508423703413340">"Aktiv"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"Əlçatan deyil"</item> <item msgid="9103697205127645916">"Deaktiv"</item> diff --git a/packages/SystemUI/res/values-b+sr+Latn/strings.xml b/packages/SystemUI/res/values-b+sr+Latn/strings.xml index 02c4d920bf83..955096e35628 100644 --- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml +++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml @@ -234,8 +234,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"WiFi nije povezan"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Osvetljenost"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inverzija boja"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Korekcija boja"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"Još podešavanja"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Korisnička podešavanja"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Gotovo"</string> @@ -453,8 +452,8 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Otključaj radi korišćenja"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Došlo je do problema pri preuzimanju kartica. Probajte ponovo kasnije"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Podešavanja zaključanog ekrana"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"Skeniraj QR kôd"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Kliknite da biste skenirali QR kôd"</string> + <string name="qr_code_scanner_title" msgid="5660820608548306581">"QR kôd"</string> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"Dodirnite da biste skenirali"</string> <string name="status_bar_work" msgid="5238641949837091056">"Poslovni profil"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Režim rada u avionu"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"Nećete čuti sledeći alarm u <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -798,7 +797,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Pustite <xliff:g id="SONG_NAME">%1$s</xliff:g> izvođača <xliff:g id="ARTIST_NAME">%2$s</xliff:g> iz aplikacije <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Pustite <xliff:g id="SONG_NAME">%1$s</xliff:g> iz aplikacije <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Opozovi"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Približite da biste puštali muziku na: <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"Pušta se na: <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Neaktivno. Vidite aplikaciju"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Nije pronađeno"</string> @@ -886,4 +886,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Izaberite korisnika"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Aplikacije pokrenute u pozadini"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Zaustavi"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-b+sr+Latn/tiles_states_strings.xml b/packages/SystemUI/res/values-b+sr+Latn/tiles_states_strings.xml index d9e0bfca36ff..6e2b7d1bbe57 100644 --- a/packages/SystemUI/res/values-b+sr+Latn/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-b+sr+Latn/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Isključeno"</item> <item msgid="2075645297847971154">"Uključeno"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"Nedostupno"</item> + <item msgid="1909756493418256167">"Isključeno"</item> + <item msgid="4531508423703413340">"Uključeno"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"Nedostupno"</item> <item msgid="9103697205127645916">"Isključeno"</item> diff --git a/packages/SystemUI/res/values-be/strings.xml b/packages/SystemUI/res/values-be/strings.xml index 88f58ac105d3..4d11111832b1 100644 --- a/packages/SystemUI/res/values-be/strings.xml +++ b/packages/SystemUI/res/values-be/strings.xml @@ -235,8 +235,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Няма падключэння да Wi-Fi"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Яркасць"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Інверсія колераў"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Карэкцыя колераў"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"Дадатковыя налады"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Налады карыстальніка"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Гатова"</string> @@ -354,7 +353,7 @@ <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Паказ апавяшчэнняў прыпынены ў рэжыме \"Не турбаваць\""</string> <string name="media_projection_action_text" msgid="3634906766918186440">"Пачаць зараз"</string> <string name="empty_shade_text" msgid="8935967157319717412">"Апавяшчэнняў няма"</string> - <string name="quick_settings_disclosure_parental_controls" msgid="2114102871438223600">"Гэта прылада знаходзіцца пад кантролем вашых бацькоў"</string> + <string name="quick_settings_disclosure_parental_controls" msgid="2114102871438223600">"Гэта прылада знаходзіцца пад кантролем бацькоў"</string> <string name="quick_settings_disclosure_management_monitoring" msgid="8231336875820702180">"Ваша арганізацыя валодае гэтай прыладай і можа кантраляваць сеткавы трафік"</string> <string name="quick_settings_disclosure_named_management_monitoring" msgid="2831423806103479812">"<xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> валодае гэтай прыладай і можа кантраляваць сеткавы трафік"</string> <string name="quick_settings_financed_disclosure_named_management" msgid="2307703784594859524">"Гэта прылада належыць арганізацыі \"<xliff:g id="ORGANIZATION_NAME">%s</xliff:g>\""</string> @@ -393,7 +392,7 @@ <string name="monitoring_description_personal_profile_named_vpn" msgid="8179722332380953673">"Ваш асабісты профіль падключаны да праграмы <xliff:g id="VPN_APP">%1$s</xliff:g>, якая можа сачыць за вашай сеткавай актыўнасцю, уключаючы электронную пошту, праграмы і вэб-сайты."</string> <string name="monitoring_description_vpn_settings_separator" msgid="8292589617720435430">" ,"</string> <string name="monitoring_description_vpn_settings" msgid="5264167033247632071">"Адкрыйце налады VPN"</string> - <string name="monitoring_description_parental_controls" msgid="8184693528917051626">"Гэта прылада знаходзіцца пад кантролем вашых бацькоў. Бацькі могуць праглядаць і кантраляваць вашу інфармацыю, напрыклад пра праграмы, якія вы выкарыстоўваеце, даныя пра ваша месцазнаходжанне і час карыстання прыладай."</string> + <string name="monitoring_description_parental_controls" msgid="8184693528917051626">"Гэта прылада знаходзіцца пад кантролем бацькоў. Бацькі могуць праглядаць і кантраляваць тваю інфармацыю, напрыклад пра праграмы, якія ты выкарыстоўваеш, даныя пра тваё месцазнаходжанне і час карыстання прыладай."</string> <string name="legacy_vpn_name" msgid="4174223520162559145">"VPN"</string> <string name="keyguard_indication_trust_unlocked" msgid="7395154975733744547">"Разблакіравана з дапамогай TrustAgent"</string> <string name="zen_mode_and_condition" msgid="5043165189511223718">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string> @@ -456,8 +455,10 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Разблакіраваць для выкарыстання"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Узнікла праблема з загрузкай вашых карт. Паўтарыце спробу пазней"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Налады экрана блакіроўкі"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"Адсканіраваць QR-код"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Націсніце, каб адсканіраваць QR-код"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <!-- no translation found for qr_code_scanner_description (7937603775306661863) --> + <skip /> <string name="status_bar_work" msgid="5238641949837091056">"Працоўны профіль"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Рэжым палёту"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"Вы не пачуеце наступны будзільнік <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -804,7 +805,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Прайграйце кампазіцыю \"<xliff:g id="SONG_NAME">%1$s</xliff:g>\" (выканаўца – <xliff:g id="ARTIST_NAME">%2$s</xliff:g>) з дапамогай праграмы \"<xliff:g id="APP_LABEL">%3$s</xliff:g>\""</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Прайграйце кампазіцыю \"<xliff:g id="SONG_NAME">%1$s</xliff:g>\" з дапамогай праграмы \"<xliff:g id="APP_LABEL">%2$s</xliff:g>\""</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Адрабіць"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Наблізьцеся, каб прайграць музыку на прыладзе \"<xliff:g id="DEVICENAME">%1$s</xliff:g>\""</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"Прайграецца на прыладзе \"<xliff:g id="DEVICENAME">%1$s</xliff:g>\""</string> <string name="controls_error_timeout" msgid="794197289772728958">"Неактыўна, праверце праграму"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Не знойдзена"</string> @@ -892,4 +894,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Выбар карыстальніка"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Праграмы працуюць у фонавым рэжыме"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Спыніць"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-be/tiles_states_strings.xml b/packages/SystemUI/res/values-be/tiles_states_strings.xml index 5c7c40fed7a7..aef519fe9dcb 100644 --- a/packages/SystemUI/res/values-be/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-be/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Выключана"</item> <item msgid="2075645297847971154">"Уключана"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"Недаступна"</item> + <item msgid="1909756493418256167">"Выключана"</item> + <item msgid="4531508423703413340">"Уключана"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"Недаступна"</item> <item msgid="9103697205127645916">"Выключана"</item> diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml index 8b455daef832..aaa1f3abab11 100644 --- a/packages/SystemUI/res/values-bg/strings.xml +++ b/packages/SystemUI/res/values-bg/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"не е установена връзка с Wi-Fi"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Яркост"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Инвертиране на цветовете"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Корекция на цветове"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"Още настройки"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Потребителски настройки"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Готово"</string> @@ -450,8 +449,10 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Отключване с цел използване"</string> <string name="wallet_error_generic" msgid="257704570182963611">"При извличането на картите ви възникна проблем. Моля, опитайте отново по-късно"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Настройки за заключения екран"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"Сканиране на QR код"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Кликнете, за да сканирате QR код"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <!-- no translation found for qr_code_scanner_description (7937603775306661863) --> + <skip /> <string name="status_bar_work" msgid="5238641949837091056">"Потребителски профил в Work"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Самолетен режим"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"Няма да чуете следващия си будилник в <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -792,7 +793,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Пускане на <xliff:g id="SONG_NAME">%1$s</xliff:g> на <xliff:g id="ARTIST_NAME">%2$s</xliff:g> от <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Пускане на <xliff:g id="SONG_NAME">%1$s</xliff:g> от <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Отмяна"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Преместете се по-близо, за да се възпроизведе на <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"Възпроизвежда се на <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Неактивно, проверете прилож."</string> <string name="controls_error_removed" msgid="6675638069846014366">"Не е намерено"</string> @@ -880,4 +882,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Избор на потребител"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Приложения, които се изпълняват на заден план"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Спиране"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-bg/tiles_states_strings.xml b/packages/SystemUI/res/values-bg/tiles_states_strings.xml index 6839b830283c..0900c521abf1 100644 --- a/packages/SystemUI/res/values-bg/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-bg/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Изкл."</item> <item msgid="2075645297847971154">"Вкл."</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"Не е налице"</item> + <item msgid="1909756493418256167">"Изкл."</item> + <item msgid="4531508423703413340">"Вкл."</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"Не е налице"</item> <item msgid="9103697205127645916">"Изкл."</item> diff --git a/packages/SystemUI/res/values-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml index 958853ec91e4..be290afede2f 100644 --- a/packages/SystemUI/res/values-bn/strings.xml +++ b/packages/SystemUI/res/values-bn/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"ওয়াই-ফাই কানেক্ট করা নেই"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"উজ্জ্বলতা"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"কালার ইনভার্সন"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"রঙ সংশোধন"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"আরও সেটিংস"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"ব্যবহারকারী সেটিংস"</string> <string name="quick_settings_done" msgid="2163641301648855793">"সম্পন্ন হয়েছে"</string> @@ -450,8 +449,9 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"ব্যবহার করতে আনলক করুন"</string> <string name="wallet_error_generic" msgid="257704570182963611">"আপনার কার্ড সংক্রান্ত তথ্য পেতে সমস্যা হয়েছে, পরে আবার চেষ্টা করুন"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"লক স্ক্রিন সেটিংস"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"QR কোড স্ক্যান করুন"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"QR কোড স্ক্যান করতে ক্লিক করুন"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"স্ক্যান করতে ট্যাপ করুন"</string> <string name="status_bar_work" msgid="5238641949837091056">"কাজের প্রোফাইল"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"বিমান মোড"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"আপনি আপনার পরবর্তী <xliff:g id="WHEN">%1$s</xliff:g> অ্যালার্ম শুনতে পাবেন না"</string> @@ -792,7 +792,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g>-এর <xliff:g id="SONG_NAME">%1$s</xliff:g> গানটি <xliff:g id="APP_LABEL">%3$s</xliff:g> অ্যাপে চালান"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g> গানটি <xliff:g id="APP_LABEL">%2$s</xliff:g> অ্যাপে চালান"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"আগের অবস্থায় ফিরুন"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"<xliff:g id="DEVICENAME">%1$s</xliff:g>-এ চালাতে আরও কাছে আনুন"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"<xliff:g id="DEVICENAME">%1$s</xliff:g>-এ ভিডিও চালানো হচ্ছে"</string> <string name="controls_error_timeout" msgid="794197289772728958">"বন্ধ আছে, অ্যাপ চেক করুন"</string> <string name="controls_error_removed" msgid="6675638069846014366">"খুঁজে পাওয়া যায়নি"</string> @@ -880,4 +881,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"ব্যবহারকারী বেছে নিন"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"ব্যাকগ্রাউন্ডে অ্যাপ চলছে"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"বন্ধ করুন"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-bn/tiles_states_strings.xml b/packages/SystemUI/res/values-bn/tiles_states_strings.xml index 7a6b3ac1c371..5358e5d2ec43 100644 --- a/packages/SystemUI/res/values-bn/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-bn/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"বন্ধ আছে"</item> <item msgid="2075645297847971154">"চালু আছে"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"অনুপলভ্য"</item> + <item msgid="1909756493418256167">"বন্ধ আছে"</item> + <item msgid="4531508423703413340">"চালু আছে"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"উপলভ্য নেই"</item> <item msgid="9103697205127645916">"বন্ধ আছে"</item> diff --git a/packages/SystemUI/res/values-bs/strings.xml b/packages/SystemUI/res/values-bs/strings.xml index 092d3df05c7a..eb45708f1caa 100644 --- a/packages/SystemUI/res/values-bs/strings.xml +++ b/packages/SystemUI/res/values-bs/strings.xml @@ -234,8 +234,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"WiFi mreža nije povezana"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Osvjetljenje"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inverzija boja"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Ispravka boje"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"Više postavki"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Korisničke postavke"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Gotovo"</string> @@ -453,8 +452,8 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Otključajte da koristite"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Došlo je do problema prilikom preuzimanja vaših kartica. Pokušajte ponovo kasnije"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Postavke zaključavanja ekrana"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"Skeniraj QR"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Kliknite da skenirate QR kôd"</string> + <string name="qr_code_scanner_title" msgid="5660820608548306581">"QR kôd"</string> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"Dodirnite da skenirate"</string> <string name="status_bar_work" msgid="5238641949837091056">"Profil za posao"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Način rada u avionu"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"Nećete čuti sljedeći alarm u <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -798,7 +797,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Reproducirajte pjesmu <xliff:g id="SONG_NAME">%1$s</xliff:g> izvođača <xliff:g id="ARTIST_NAME">%2$s</xliff:g> pomoću aplikacije <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Reproducirajte pjesmu <xliff:g id="SONG_NAME">%1$s</xliff:g> pomoću aplikacije <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Poništi"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Približite se da reproducirate na uređaju <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"Reproducira se na uređaju <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Neaktivno, vidite aplikaciju"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Nije pronađeno"</string> @@ -886,4 +886,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Odaberite korisnika"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Aplikacije su aktivne u pozadini"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Zaustavi"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-bs/tiles_states_strings.xml b/packages/SystemUI/res/values-bs/tiles_states_strings.xml index d9e0bfca36ff..6e2b7d1bbe57 100644 --- a/packages/SystemUI/res/values-bs/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-bs/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Isključeno"</item> <item msgid="2075645297847971154">"Uključeno"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"Nedostupno"</item> + <item msgid="1909756493418256167">"Isključeno"</item> + <item msgid="4531508423703413340">"Uključeno"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"Nedostupno"</item> <item msgid="9103697205127645916">"Isključeno"</item> diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml index 6ae7d1617df6..06799a21da4c 100644 --- a/packages/SystemUI/res/values-ca/strings.xml +++ b/packages/SystemUI/res/values-ca/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"La Wi‑Fi no està connectada"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Brillantor"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inversió de colors"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Correcció de color"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"Més opcions"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Configuració d\'usuari"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Fet"</string> @@ -450,8 +449,10 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Desbloqueja per utilitzar"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Hi ha hagut un problema en obtenir les teves targetes; torna-ho a provar més tard"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Configuració de la pantalla de bloqueig"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"Escaneja un codi QR"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Fes clic per escanejar un codi QR"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <!-- no translation found for qr_code_scanner_description (7937603775306661863) --> + <skip /> <string name="status_bar_work" msgid="5238641949837091056">"Perfil de treball"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Mode d\'avió"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"<xliff:g id="WHEN">%1$s</xliff:g> no sentiràs la pròxima alarma"</string> @@ -792,7 +793,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Reprodueix <xliff:g id="SONG_NAME">%1$s</xliff:g> (<xliff:g id="ARTIST_NAME">%2$s</xliff:g>) des de l\'aplicació <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Reprodueix <xliff:g id="SONG_NAME">%1$s</xliff:g> des de l\'aplicació <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Desfés"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Mou més a prop per reproduir a <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"S\'està reproduint a <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Inactiu; comprova l\'aplicació"</string> <string name="controls_error_removed" msgid="6675638069846014366">"No s\'ha trobat"</string> @@ -880,4 +882,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Selecciona un usuari"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Aplicacions que s\'executen en segon pla"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Atura"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-ca/tiles_states_strings.xml b/packages/SystemUI/res/values-ca/tiles_states_strings.xml index 28f3da4650eb..2738ecfcbd88 100644 --- a/packages/SystemUI/res/values-ca/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-ca/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Desactivat"</item> <item msgid="2075645297847971154">"Activat"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"No disponible"</item> + <item msgid="1909756493418256167">"Desactivada"</item> + <item msgid="4531508423703413340">"Activada"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"No disponible"</item> <item msgid="9103697205127645916">"Desactivat"</item> diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml index 7e716160939d..4b63cd807084 100644 --- a/packages/SystemUI/res/values-cs/strings.xml +++ b/packages/SystemUI/res/values-cs/strings.xml @@ -235,8 +235,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Není připojena Wi-Fi"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Jas"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Převrácení barev"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Korekce barev"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"Další nastavení"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Uživatelské nastavení"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Hotovo"</string> @@ -456,8 +455,9 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Odemknout a použít"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Při načítání karet došlo k problému, zkuste to později"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Nastavení obrazovky uzamčení"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"Naskenovat QR kód"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Kliknutím naskenujete QR kód"</string> + <string name="qr_code_scanner_title" msgid="5660820608548306581">"QR kód"</string> + <!-- no translation found for qr_code_scanner_description (7937603775306661863) --> + <skip /> <string name="status_bar_work" msgid="5238641949837091056">"Pracovní profil"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Režim Letadlo"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"Svůj další budík <xliff:g id="WHEN">%1$s</xliff:g> neuslyšíte"</string> @@ -804,7 +804,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Přehrát skladbu <xliff:g id="SONG_NAME">%1$s</xliff:g> od interpreta <xliff:g id="ARTIST_NAME">%2$s</xliff:g> z aplikace <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Přehrát skladbu <xliff:g id="SONG_NAME">%1$s</xliff:g> z aplikace <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Vrátit zpět"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Pokud chcete přehrávat na zařízení <xliff:g id="DEVICENAME">%1$s</xliff:g>, přibližte se k němu"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"Přehrává se na zařízení <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Neaktivní, zkontrolujte aplikaci"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Nenalezeno"</string> @@ -892,4 +893,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Zvolte uživatele"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Aplikace běžící na pozadí"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Konec"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-cs/tiles_states_strings.xml b/packages/SystemUI/res/values-cs/tiles_states_strings.xml index ce64e273dc02..cd667cbdc2f0 100644 --- a/packages/SystemUI/res/values-cs/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-cs/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Vyp"</item> <item msgid="2075645297847971154">"Zap"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"Nedostupné"</item> + <item msgid="1909756493418256167">"Vyp"</item> + <item msgid="4531508423703413340">"Zap"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"Nedostupné"</item> <item msgid="9103697205127645916">"Vyp"</item> diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml index 919f57c307ff..ad92cad640c9 100644 --- a/packages/SystemUI/res/values-da/strings.xml +++ b/packages/SystemUI/res/values-da/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Manglende Wi-Fi-forbindelse"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Lysstyrke"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Ombytning af farver"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Farvekorrigering"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"Flere indstillinger"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Brugerindstillinger"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Udfør"</string> @@ -450,8 +449,10 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Lås op for at bruge"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Dine kort kunne ikke hentes. Prøv igen senere."</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Lås skærmindstillinger"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"Scan QR-kode"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Klik for at scanne en QR-kode"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <!-- no translation found for qr_code_scanner_description (7937603775306661863) --> + <skip /> <string name="status_bar_work" msgid="5238641949837091056">"Arbejdsprofil"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Flytilstand"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"Du vil ikke kunne høre din næste alarm <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -792,7 +793,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Afspil <xliff:g id="SONG_NAME">%1$s</xliff:g> af <xliff:g id="ARTIST_NAME">%2$s</xliff:g> via <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Afspil <xliff:g id="SONG_NAME">%1$s</xliff:g> via <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Fortryd"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Flyt enheden tættere på for at afspille på <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"Afspilles på <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Inaktiv. Tjek appen"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Ikke fundet"</string> @@ -880,4 +882,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Vælg bruger"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Apps, der kører i baggrunden"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Stop"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-da/tiles_states_strings.xml b/packages/SystemUI/res/values-da/tiles_states_strings.xml index 9e4c6f4b8459..5ec01fe39c4f 100644 --- a/packages/SystemUI/res/values-da/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-da/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Fra"</item> <item msgid="2075645297847971154">"Til"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"Ikke tilgængelig"</item> + <item msgid="1909756493418256167">"Fra"</item> + <item msgid="4531508423703413340">"Til"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"Ikke tilgængelig"</item> <item msgid="9103697205127645916">"Fra"</item> diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml index ce58c3d080c8..8b514829614f 100644 --- a/packages/SystemUI/res/values-de/strings.xml +++ b/packages/SystemUI/res/values-de/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"WLAN nicht verbunden"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Helligkeit"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Farbumkehr"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Farbkorrektur"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"Weitere Einstellungen"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Nutzereinstellungen"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Fertig"</string> @@ -450,8 +449,9 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Zum Verwenden entsperren"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Beim Abrufen deiner Karten ist ein Fehler aufgetreten – bitte versuch es später noch einmal"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Einstellungen für den Sperrbildschirm"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"QR-Code scannen"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Klicken, um einen QR-Code zu scannen"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"Zum Scannen tippen"</string> <string name="status_bar_work" msgid="5238641949837091056">"Arbeitsprofil"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Flugmodus"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"Lautloser Weckruf <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -792,7 +792,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="SONG_NAME">%1$s</xliff:g> von <xliff:g id="ARTIST_NAME">%2$s</xliff:g> über <xliff:g id="APP_LABEL">%3$s</xliff:g> wiedergeben"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g> über <xliff:g id="APP_LABEL">%2$s</xliff:g> wiedergeben"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Rückgängig machen"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Gehe für die Wiedergabe näher an <xliff:g id="DEVICENAME">%1$s</xliff:g> heran"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"Wird auf <xliff:g id="DEVICENAME">%1$s</xliff:g> abgespielt"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Inaktiv – sieh in der App nach"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Nicht gefunden"</string> @@ -880,4 +881,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Nutzer auswählen"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Apps, die im Hintergrund ausgeführt werden"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Beenden"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-de/tiles_states_strings.xml b/packages/SystemUI/res/values-de/tiles_states_strings.xml index e958670b502e..72476456b248 100644 --- a/packages/SystemUI/res/values-de/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-de/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Aus"</item> <item msgid="2075645297847971154">"An"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"Nicht verfügbar"</item> + <item msgid="1909756493418256167">"Aus"</item> + <item msgid="4531508423703413340">"An"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"Nicht verfügbar"</item> <item msgid="9103697205127645916">"Aus"</item> diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml index 96aed4c7264c..9e61f1a29135 100644 --- a/packages/SystemUI/res/values-el/strings.xml +++ b/packages/SystemUI/res/values-el/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Το Wi-Fi δεν είναι συνδεδεμένο"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Φωτεινότητα"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Αντιστροφή χρωμάτων"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Διόρθωση χρωμάτων"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"Περισσότερες ρυθμίσεις"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Ρυθμίσεις χρήστη"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Τέλος"</string> @@ -450,8 +449,9 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Ξεκλείδωμα για χρήση"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Παρουσιάστηκε πρόβλημα με τη λήψη των καρτών σας. Δοκιμάστε ξανά αργότερα"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Ρυθμίσεις κλειδώματος οθόνης"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"Σάρωση κωδικού QR"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Κάντε κλικ για σάρωση κωδικού QR"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"Πατήστε για σάρωση"</string> <string name="status_bar_work" msgid="5238641949837091056">"Προφίλ εργασίας"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Λειτουργία πτήσης"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"Δεν θα ακούσετε το επόμενο ξυπνητήρι σας <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -792,7 +792,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Αναπαραγωγή του <xliff:g id="SONG_NAME">%1$s</xliff:g> από <xliff:g id="ARTIST_NAME">%2$s</xliff:g> στην εφαρμογή <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Αναπαραγωγή του <xliff:g id="SONG_NAME">%1$s</xliff:g> στην εφαρμογή <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Αναίρεση"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Πλησιάστε για αναπαραγωγή στη συσκευή <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"Αναπαραγωγή στη συσκευή <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Ανενεργό, έλεγχος εφαρμογής"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Δεν βρέθηκε."</string> @@ -880,4 +881,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Επιλογή χρήστη"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Οι εφαρμογές βρίσκονται σε εξέλιξη στο παρασκήνιο"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Διακοπή"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-el/tiles_states_strings.xml b/packages/SystemUI/res/values-el/tiles_states_strings.xml index 1c9583518595..4dca192a877c 100644 --- a/packages/SystemUI/res/values-el/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-el/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Ανενεργό"</item> <item msgid="2075645297847971154">"Ενεργό"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"Μη διαθέσιμη"</item> + <item msgid="1909756493418256167">"Ανενεργή"</item> + <item msgid="4531508423703413340">"Ενεργή"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"Μη διαθέσιμο"</item> <item msgid="9103697205127645916">"Ανενεργό"</item> diff --git a/packages/SystemUI/res/values-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml index 8504cb77cbb3..79c9ca2de1db 100644 --- a/packages/SystemUI/res/values-en-rAU/strings.xml +++ b/packages/SystemUI/res/values-en-rAU/strings.xml @@ -449,8 +449,8 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Unlock to use"</string> <string name="wallet_error_generic" msgid="257704570182963611">"There was a problem getting your cards. Please try again later."</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Lock screen settings"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"Scan QR"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Click to scan a QR code"</string> + <string name="qr_code_scanner_title" msgid="5660820608548306581">"QR code"</string> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"Tap to scan"</string> <string name="status_bar_work" msgid="5238641949837091056">"Work profile"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Aeroplane mode"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"You won\'t hear your next alarm <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -791,7 +791,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Play <xliff:g id="SONG_NAME">%1$s</xliff:g> by <xliff:g id="ARTIST_NAME">%2$s</xliff:g> from <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Play <xliff:g id="SONG_NAME">%1$s</xliff:g> from <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Undo"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Move closer to play on <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"Playing on <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Inactive, check app"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Not found"</string> @@ -879,4 +880,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Select user"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Apps running in the background"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Stop"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-en-rCA/strings.xml b/packages/SystemUI/res/values-en-rCA/strings.xml index 68c5e649c56d..605811d571ee 100644 --- a/packages/SystemUI/res/values-en-rCA/strings.xml +++ b/packages/SystemUI/res/values-en-rCA/strings.xml @@ -449,8 +449,8 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Unlock to use"</string> <string name="wallet_error_generic" msgid="257704570182963611">"There was a problem getting your cards. Please try again later."</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Lock screen settings"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"Scan QR"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Click to scan a QR code"</string> + <string name="qr_code_scanner_title" msgid="5660820608548306581">"QR code"</string> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"Tap to scan"</string> <string name="status_bar_work" msgid="5238641949837091056">"Work profile"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Airplane mode"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"You won\'t hear your next alarm <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -791,7 +791,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Play <xliff:g id="SONG_NAME">%1$s</xliff:g> by <xliff:g id="ARTIST_NAME">%2$s</xliff:g> from <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Play <xliff:g id="SONG_NAME">%1$s</xliff:g> from <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Undo"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Move closer to play on <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"Playing on <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Inactive, check app"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Not found"</string> @@ -879,4 +880,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Select user"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Apps running in the background"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Stop"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml index 8504cb77cbb3..79c9ca2de1db 100644 --- a/packages/SystemUI/res/values-en-rGB/strings.xml +++ b/packages/SystemUI/res/values-en-rGB/strings.xml @@ -449,8 +449,8 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Unlock to use"</string> <string name="wallet_error_generic" msgid="257704570182963611">"There was a problem getting your cards. Please try again later."</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Lock screen settings"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"Scan QR"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Click to scan a QR code"</string> + <string name="qr_code_scanner_title" msgid="5660820608548306581">"QR code"</string> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"Tap to scan"</string> <string name="status_bar_work" msgid="5238641949837091056">"Work profile"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Aeroplane mode"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"You won\'t hear your next alarm <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -791,7 +791,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Play <xliff:g id="SONG_NAME">%1$s</xliff:g> by <xliff:g id="ARTIST_NAME">%2$s</xliff:g> from <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Play <xliff:g id="SONG_NAME">%1$s</xliff:g> from <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Undo"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Move closer to play on <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"Playing on <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Inactive, check app"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Not found"</string> @@ -879,4 +880,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Select user"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Apps running in the background"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Stop"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml index 8504cb77cbb3..79c9ca2de1db 100644 --- a/packages/SystemUI/res/values-en-rIN/strings.xml +++ b/packages/SystemUI/res/values-en-rIN/strings.xml @@ -449,8 +449,8 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Unlock to use"</string> <string name="wallet_error_generic" msgid="257704570182963611">"There was a problem getting your cards. Please try again later."</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Lock screen settings"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"Scan QR"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Click to scan a QR code"</string> + <string name="qr_code_scanner_title" msgid="5660820608548306581">"QR code"</string> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"Tap to scan"</string> <string name="status_bar_work" msgid="5238641949837091056">"Work profile"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Aeroplane mode"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"You won\'t hear your next alarm <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -791,7 +791,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Play <xliff:g id="SONG_NAME">%1$s</xliff:g> by <xliff:g id="ARTIST_NAME">%2$s</xliff:g> from <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Play <xliff:g id="SONG_NAME">%1$s</xliff:g> from <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Undo"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Move closer to play on <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"Playing on <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Inactive, check app"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Not found"</string> @@ -879,4 +880,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Select user"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Apps running in the background"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Stop"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-en-rXC/strings.xml b/packages/SystemUI/res/values-en-rXC/strings.xml index 60f725e6450f..389554855332 100644 --- a/packages/SystemUI/res/values-en-rXC/strings.xml +++ b/packages/SystemUI/res/values-en-rXC/strings.xml @@ -449,8 +449,8 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Unlock to use"</string> <string name="wallet_error_generic" msgid="257704570182963611">"There was a problem getting your cards, please try again later"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Lock screen settings"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"Scan QR"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Click to scan a QR code"</string> + <string name="qr_code_scanner_title" msgid="5660820608548306581">"QR code"</string> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"Tap to scan"</string> <string name="status_bar_work" msgid="5238641949837091056">"Work profile"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Airplane mode"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"You won\'t hear your next alarm <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -791,7 +791,7 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Play <xliff:g id="SONG_NAME">%1$s</xliff:g> by <xliff:g id="ARTIST_NAME">%2$s</xliff:g> from <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Play <xliff:g id="SONG_NAME">%1$s</xliff:g> from <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Undo"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Move closer to play on <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> + <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Move closer to play on <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_playing" msgid="3760048096352107789">"Playing on <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Inactive, check app"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Not found"</string> @@ -879,4 +879,6 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Select user"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Apps running in the background"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Stop"</string> + <string name="clipboard_edit_text_copy" msgid="770856373439969178">"Copy"</string> + <string name="clipboard_overlay_text_copied" msgid="1872624400464891363">"Copied"</string> </resources> diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml index 66bbbbee56dc..f748e9dce65a 100644 --- a/packages/SystemUI/res/values-es-rUS/strings.xml +++ b/packages/SystemUI/res/values-es-rUS/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Red Wi-Fi no conectada"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Brillo"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inversión de color"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Corrección de colores"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"Más configuraciones"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Configuración del usuario"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Listo"</string> @@ -450,8 +449,9 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Desbloquear para usar"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Ocurrió un problema al obtener las tarjetas; vuelve a intentarlo más tarde"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Configuración de pantalla de bloqueo"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"Escanear QR"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Haz clic para escanear un código QR"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"Presiona para escanear"</string> <string name="status_bar_work" msgid="5238641949837091056">"Perfil de trabajo"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Modo de avión"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"No oirás la próxima alarma a la(s) <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -792,7 +792,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Reproduce <xliff:g id="SONG_NAME">%1$s</xliff:g>, de <xliff:g id="ARTIST_NAME">%2$s</xliff:g>, en <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Reproducir <xliff:g id="SONG_NAME">%1$s</xliff:g> en <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Deshacer"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Acércate para reproducir en <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"Reproduciendo en <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Inactivo. Verifica la app"</string> <string name="controls_error_removed" msgid="6675638069846014366">"No se encontró"</string> @@ -880,4 +881,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Seleccionar usuario"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Apps en ejecución en segundo plano"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Detener"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-es-rUS/tiles_states_strings.xml b/packages/SystemUI/res/values-es-rUS/tiles_states_strings.xml index 0d77977fd174..6e425eea6542 100644 --- a/packages/SystemUI/res/values-es-rUS/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-es-rUS/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Desactivado"</item> <item msgid="2075645297847971154">"Activado"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"No disponible"</item> + <item msgid="1909756493418256167">"Desactivada"</item> + <item msgid="4531508423703413340">"Activada"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"No disponible"</item> <item msgid="9103697205127645916">"Desactivado"</item> diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml index 98b45d5162e0..04947803c4bc 100644 --- a/packages/SystemUI/res/values-es/strings.xml +++ b/packages/SystemUI/res/values-es/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi‑Fi no conectado"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Brillo"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Invertir colores"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Corrección de color"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"Más ajustes"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Ajustes de usuario"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Hecho"</string> @@ -450,8 +449,10 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Desbloquear para usar"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Se ha producido un problema al obtener tus tarjetas. Inténtalo de nuevo más tarde."</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Ajustes de pantalla de bloqueo"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"Escanear QR"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Haz clic para escanear un código QR"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <!-- no translation found for qr_code_scanner_description (7937603775306661863) --> + <skip /> <string name="status_bar_work" msgid="5238641949837091056">"Perfil de trabajo"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Modo avión"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"No oirás la próxima alarma (<xliff:g id="WHEN">%1$s</xliff:g>)"</string> @@ -792,7 +793,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Poner <xliff:g id="SONG_NAME">%1$s</xliff:g> de <xliff:g id="ARTIST_NAME">%2$s</xliff:g> en <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Poner <xliff:g id="SONG_NAME">%1$s</xliff:g> en <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Deshacer"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Acércate a <xliff:g id="DEVICENAME">%1$s</xliff:g> para que se reproduzca en ese dispositivo"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"Reproduciendo en <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Inactivo, comprobar aplicación"</string> <string name="controls_error_removed" msgid="6675638069846014366">"No se ha encontrado"</string> @@ -880,4 +882,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Seleccionar usuario"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Aplicaciones ejecutándose en segundo plano"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Detener"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-es/tiles_states_strings.xml b/packages/SystemUI/res/values-es/tiles_states_strings.xml index 080731a2feb1..3ac10ec4a2cf 100644 --- a/packages/SystemUI/res/values-es/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-es/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Desactivado"</item> <item msgid="2075645297847971154">"Activado"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"No disponible"</item> + <item msgid="1909756493418256167">"Desactivada"</item> + <item msgid="4531508423703413340">"Activada"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"No disponible"</item> <item msgid="9103697205127645916">"Desactivado"</item> diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml index 7526b0dea7f8..15c10be74efc 100644 --- a/packages/SystemUI/res/values-et/strings.xml +++ b/packages/SystemUI/res/values-et/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"WiFi-ühendus puudub"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Heledus"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Värvide ümberpööramine"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Värviparandus"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"Rohkem seadeid"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Kasutaja seaded"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Valmis"</string> @@ -450,8 +449,10 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Avage kasutamiseks"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Teie kaartide hankimisel ilmnes probleem, proovige hiljem uuesti"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Lukustuskuva seaded"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"QR-koodi skannimine"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Klõpsake QR-koodi skannimiseks"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <!-- no translation found for qr_code_scanner_description (7937603775306661863) --> + <skip /> <string name="status_bar_work" msgid="5238641949837091056">"Tööprofiil"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Lennukirežiim"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"Te ei kuule järgmist äratust kell <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -792,7 +793,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Esita lugu <xliff:g id="SONG_NAME">%1$s</xliff:g> esitajalt <xliff:g id="ARTIST_NAME">%2$s</xliff:g> rakenduses <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Esita lugu <xliff:g id="SONG_NAME">%1$s</xliff:g> rakenduses <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Võta tagasi"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Minge lähemale, et seadmes <xliff:g id="DEVICENAME">%1$s</xliff:g> esitada"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"Esitatakse seadmes <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Passiivne, vaadake rakendust"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Ei leitud"</string> @@ -880,4 +882,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Kasutaja valimine"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Taustal töötavad rakendused"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Peata"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-et/tiles_states_strings.xml b/packages/SystemUI/res/values-et/tiles_states_strings.xml index 26d71fe07a57..27edd17c9b0d 100644 --- a/packages/SystemUI/res/values-et/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-et/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Väljas"</item> <item msgid="2075645297847971154">"Sees"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"Pole saadaval"</item> + <item msgid="1909756493418256167">"Väljas"</item> + <item msgid="4531508423703413340">"Sees"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"Pole saadaval"</item> <item msgid="9103697205127645916">"Väljas"</item> diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml index 71c3feb8594c..454ad59807bb 100644 --- a/packages/SystemUI/res/values-eu/strings.xml +++ b/packages/SystemUI/res/values-eu/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Ez zaude konektatuta wifi-sarera"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Distira"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Kolore-alderantzikatzea"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Koloreen zuzenketa"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"Ezarpen gehiago"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Erabiltzaile-ezarpenak"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Eginda"</string> @@ -450,8 +449,9 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Desblokeatu erabiltzeko"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Arazo bat izan da txartelak eskuratzean. Saiatu berriro geroago."</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Pantaila blokeatuaren ezarpenak"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"Eskaneatu QR kode bat"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"QR kode bat eskaneatzeko, sakatu hau"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"Sakatu eskaneatzeko"</string> <string name="status_bar_work" msgid="5238641949837091056">"Work profila"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Hegaldi modua"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"Ez duzu entzungo hurrengo alarma (<xliff:g id="WHEN">%1$s</xliff:g>)"</string> @@ -792,7 +792,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Erreproduzitu <xliff:g id="SONG_NAME">%1$s</xliff:g> (<xliff:g id="ARTIST_NAME">%2$s</xliff:g>) <xliff:g id="APP_LABEL">%3$s</xliff:g> bidez"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Erreproduzitu <xliff:g id="SONG_NAME">%1$s</xliff:g> <xliff:g id="APP_LABEL">%2$s</xliff:g> bidez"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Desegin"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Gerturatu <xliff:g id="DEVICENAME">%1$s</xliff:g> gailuan erreproduzitzeko"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"<xliff:g id="DEVICENAME">%1$s</xliff:g> pantailan erreproduzitzen"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Inaktibo; egiaztatu aplikazioa"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Ez da aurkitu"</string> @@ -880,4 +881,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Hautatu erabiltzaile bat"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Atzeko planoan exekutatzen ari diren aplikazioak"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Gelditu"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-eu/tiles_states_strings.xml b/packages/SystemUI/res/values-eu/tiles_states_strings.xml index 11090458f1f5..eb13a1202cc4 100644 --- a/packages/SystemUI/res/values-eu/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-eu/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Desaktibatuta"</item> <item msgid="2075645297847971154">"Aktibatuta"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"Ez dago erabilgarri"</item> + <item msgid="1909756493418256167">"Desaktibatuta"</item> + <item msgid="4531508423703413340">"Aktibatuta"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"Ez dago erabilgarri"</item> <item msgid="9103697205127645916">"Desaktibatuta"</item> diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml index 4efdc8428097..c2c1ffb3ca48 100644 --- a/packages/SystemUI/res/values-fa/strings.xml +++ b/packages/SystemUI/res/values-fa/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi وصل نیست"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"روشنایی"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"وارونگی رنگ"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"تصحیح رنگ"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"تنظیمات بیشتر"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"تنظیمات کاربر"</string> <string name="quick_settings_done" msgid="2163641301648855793">"تمام"</string> @@ -450,8 +449,10 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"برای استفاده، قفل را باز کنید"</string> <string name="wallet_error_generic" msgid="257704570182963611">"هنگام دریافت کارتها مشکلی پیش آمد، لطفاً بعداً دوباره امتحان کنید"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"تنظیمات صفحه قفل"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"اسکن رمزینه پاسخسریع"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"برای اسکن رمزینه پاسخسریع، کلیک کنید"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <!-- no translation found for qr_code_scanner_description (7937603775306661863) --> + <skip /> <string name="status_bar_work" msgid="5238641949837091056">"نمایه کاری"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"حالت هواپیما"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"در ساعت <xliff:g id="WHEN">%1$s</xliff:g>، دیگر صدای زنگ ساعت را نمیشنوید"</string> @@ -792,7 +793,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="SONG_NAME">%1$s</xliff:g> از <xliff:g id="ARTIST_NAME">%2$s</xliff:g> را ازطریق <xliff:g id="APP_LABEL">%3$s</xliff:g> پخش کنید"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g> را ازطریق <xliff:g id="APP_LABEL">%2$s</xliff:g> پخش کنید"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"واگرد"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"برای پخش در <xliff:g id="DEVICENAME">%1$s</xliff:g>، به دستگاه نزدیکتر شوید"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"درحال پخش در <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"غیرفعال، برنامه را بررسی کنید"</string> <string name="controls_error_removed" msgid="6675638069846014366">"پیدا نشد"</string> @@ -880,4 +882,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"انتخاب کاربر"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"برنامههایی که در پسزمینه اجرا میشود"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"توقف"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-fa/tiles_states_strings.xml b/packages/SystemUI/res/values-fa/tiles_states_strings.xml index ab755197d0a5..dcde4d3e18db 100644 --- a/packages/SystemUI/res/values-fa/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-fa/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"خاموش"</item> <item msgid="2075645297847971154">"روشن"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"دردسترس نیست"</item> + <item msgid="1909756493418256167">"خاموش"</item> + <item msgid="4531508423703413340">"روشن"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"دردسترس نیست"</item> <item msgid="9103697205127645916">"خاموش"</item> diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml index 1ab196f09aca..cd3844fad9da 100644 --- a/packages/SystemUI/res/values-fi/strings.xml +++ b/packages/SystemUI/res/values-fi/strings.xml @@ -100,7 +100,7 @@ <string name="accessibility_back" msgid="6530104400086152611">"Takaisin"</string> <string name="accessibility_home" msgid="5430449841237966217">"Aloitus"</string> <string name="accessibility_menu" msgid="2701163794470513040">"Valikko"</string> - <string name="accessibility_accessibility_button" msgid="4089042473497107709">"Esteettömyys"</string> + <string name="accessibility_accessibility_button" msgid="4089042473497107709">"Saavutettavuus"</string> <string name="accessibility_rotate_button" msgid="1238584767612362586">"Näytön kääntäminen"</string> <string name="accessibility_recent" msgid="901641734769533575">"Viimeisimmät"</string> <string name="accessibility_camera_button" msgid="2938898391716647247">"Kamera"</string> @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fiä ei ole yhdistetty"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Kirkkaus"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Käänteiset värit"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Värinkorjaus"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"Lisäasetukset"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Käyttäjäasetukset"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Valmis"</string> @@ -450,8 +449,10 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Avaa lukitus ja käytä"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Korttien noutamisessa oli ongelma, yritä myöhemmin uudelleen"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Lukitusnäytön asetukset"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"Skannaa QR-koodi"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Skannaa QR-koodi klikkaamalla"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <!-- no translation found for qr_code_scanner_description (7937603775306661863) --> + <skip /> <string name="status_bar_work" msgid="5238641949837091056">"Työprofiili"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Lentokonetila"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"Et kuule seuraavaa hälytystäsi (<xliff:g id="WHEN">%1$s</xliff:g>)."</string> @@ -792,7 +793,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Soita <xliff:g id="SONG_NAME">%1$s</xliff:g> (<xliff:g id="ARTIST_NAME">%2$s</xliff:g>) sovelluksessa <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Soita <xliff:g id="SONG_NAME">%1$s</xliff:g> (<xliff:g id="APP_LABEL">%2$s</xliff:g>)"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Kumoa"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Siirry lähemmäs, jotta <xliff:g id="DEVICENAME">%1$s</xliff:g> voi toistaa tämän"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"<xliff:g id="DEVICENAME">%1$s</xliff:g> toistaa tämän"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Epäaktiivinen, tarkista sovellus"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Ei löydy"</string> @@ -880,4 +882,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Valitse käyttäjä"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Sovellukset jotka ovat käynnissä taustalla"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Lopeta"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-fi/tiles_states_strings.xml b/packages/SystemUI/res/values-fi/tiles_states_strings.xml index 6fa9466e1313..d838cf84d409 100644 --- a/packages/SystemUI/res/values-fi/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-fi/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Poissa päältä"</item> <item msgid="2075645297847971154">"Päällä"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"Ei saatavilla"</item> + <item msgid="1909756493418256167">"Pois päältä"</item> + <item msgid="4531508423703413340">"Päällä"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"Ei saatavilla"</item> <item msgid="9103697205127645916">"Poissa päältä"</item> diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml index 4eadcc232a73..440369d54ea1 100644 --- a/packages/SystemUI/res/values-fr-rCA/strings.xml +++ b/packages/SystemUI/res/values-fr-rCA/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Non connecté au Wi-Fi"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Luminosité"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inversion des couleurs"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Correction des couleurs"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"Plus de paramètres"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Paramètres utilisateur"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Terminé"</string> @@ -450,8 +449,10 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Déverrouiller pour utiliser"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Un problème est survenu lors de la récupération de vos cartes, veuillez réessayer plus tard"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Paramètres de l\'écran de verrouillage"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"Numériser le code QR"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Cliquez pour numériser un code QR"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <!-- no translation found for qr_code_scanner_description (7937603775306661863) --> + <skip /> <string name="status_bar_work" msgid="5238641949837091056">"Profil professionnel"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Mode Avion"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"Vous n\'entendrez pas votre prochaine alarme à <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -792,7 +793,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Lecture de <xliff:g id="SONG_NAME">%1$s</xliff:g> par <xliff:g id="ARTIST_NAME">%2$s</xliff:g> à partir de <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Lecture de <xliff:g id="SONG_NAME">%1$s</xliff:g> à partir de <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Annuler"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Rapprochez-vous pour faire jouer le contenu sur <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"Lecture sur <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Délai expiré, vérifiez l\'appli"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Introuvable"</string> @@ -880,4 +882,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Sélect. utilisateur"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Applications exécutées en arrière-plan"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Arrêter"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-fr-rCA/tiles_states_strings.xml b/packages/SystemUI/res/values-fr-rCA/tiles_states_strings.xml index aec8ab87dcfb..0b087ad821c8 100644 --- a/packages/SystemUI/res/values-fr-rCA/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-fr-rCA/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Désactivé"</item> <item msgid="2075645297847971154">"Activé"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"Non disponible"</item> + <item msgid="1909756493418256167">"Désactivée"</item> + <item msgid="4531508423703413340">"Activée"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"Non disponible"</item> <item msgid="9103697205127645916">"Désactivée"</item> diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml index 33fc2945a045..891e85c19724 100644 --- a/packages/SystemUI/res/values-fr/strings.xml +++ b/packages/SystemUI/res/values-fr/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi non connecté"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Luminosité"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inversion des couleurs"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Correction des couleurs"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"Plus de paramètres"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Paramètres utilisateur"</string> <string name="quick_settings_done" msgid="2163641301648855793">"OK"</string> @@ -450,8 +449,9 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Déverrouiller pour utiliser"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Problème de récupération de vos cartes. Réessayez plus tard"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Paramètres de l\'écran de verrouillage"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"Scanner un code QR"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Cliquer pour scanner un code QR"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"Appuyer pour scanner"</string> <string name="status_bar_work" msgid="5238641949837091056">"Profil professionnel"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Mode Avion"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"Vous n\'entendrez pas votre prochaine alarme <xliff:g id="WHEN">%1$s</xliff:g>."</string> @@ -792,7 +792,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Mets <xliff:g id="SONG_NAME">%1$s</xliff:g> par <xliff:g id="ARTIST_NAME">%2$s</xliff:g> depuis <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Mets <xliff:g id="SONG_NAME">%1$s</xliff:g> depuis <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Annuler"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Rapprochez-vous pour lire sur <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"Lecture sur <xliff:g id="DEVICENAME">%1$s</xliff:g>…"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Délai expiré, vérifier l\'appli"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Introuvable"</string> @@ -880,4 +881,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Choisir utilisateur"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Applis exécutées en arrière-plan"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Arrêter"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-fr/tiles_states_strings.xml b/packages/SystemUI/res/values-fr/tiles_states_strings.xml index 01d9c1d74dd0..fbae02afb9b5 100644 --- a/packages/SystemUI/res/values-fr/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-fr/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Désactivé"</item> <item msgid="2075645297847971154">"Activé"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"Indisponible"</item> + <item msgid="1909756493418256167">"Désactivée"</item> + <item msgid="4531508423703413340">"Activée"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"Indisponible"</item> <item msgid="9103697205127645916">"Désactivée"</item> diff --git a/packages/SystemUI/res/values-gl/strings.xml b/packages/SystemUI/res/values-gl/strings.xml index 40fe254b563e..70a3c68ee29f 100644 --- a/packages/SystemUI/res/values-gl/strings.xml +++ b/packages/SystemUI/res/values-gl/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"A wifi non está conectada"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Brillo"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inversión da cor"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Corrección da cor"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"Máis opcións"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Configuración de usuario"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Feito"</string> @@ -450,8 +449,10 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Desbloquear para usar"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Produciuse un problema ao obter as tarxetas. Téntao de novo máis tarde"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Configuración da pantalla de bloqueo"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"Escanear QR"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Fai clic para escanear un código QR"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <!-- no translation found for qr_code_scanner_description (7937603775306661863) --> + <skip /> <string name="status_bar_work" msgid="5238641949837091056">"Perfil de traballo"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Modo avión"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"Non escoitarás a alarma seguinte <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -792,7 +793,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Reproduce <xliff:g id="SONG_NAME">%1$s</xliff:g>, de <xliff:g id="ARTIST_NAME">%2$s</xliff:g>, en <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Reproduce <xliff:g id="SONG_NAME">%1$s</xliff:g> en <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Desfacer"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Achega o dispositivo para reproducir a música en: <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"Estase reproducindo o contido en: <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Inactivo. Comproba a app"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Non se atopou"</string> @@ -880,4 +882,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Seleccionar usuario"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Aplicacións que se están executando en segundo plano"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Deter"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-gl/tiles_states_strings.xml b/packages/SystemUI/res/values-gl/tiles_states_strings.xml index 9045983425df..531e7ff88e4f 100644 --- a/packages/SystemUI/res/values-gl/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-gl/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Non"</item> <item msgid="2075645297847971154">"Si"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"Non dispoñible"</item> + <item msgid="1909756493418256167">"Desactivada"</item> + <item msgid="4531508423703413340">"Activada"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"Non dispoñible"</item> <item msgid="9103697205127645916">"Non"</item> diff --git a/packages/SystemUI/res/values-gu/strings.xml b/packages/SystemUI/res/values-gu/strings.xml index da52c40e505a..5f262b57e67c 100644 --- a/packages/SystemUI/res/values-gu/strings.xml +++ b/packages/SystemUI/res/values-gu/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"વાઇ-ફાઇ કનેક્ટ નથી"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"તેજ"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"વિપરીત રંગમાં બદલવું"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"રંગ સુધારણા"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"વધુ સેટિંગ"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"વપરાશકર્તા સેટિંગ"</string> <string name="quick_settings_done" msgid="2163641301648855793">"થઈ ગયું"</string> @@ -450,8 +449,9 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"ઉપયોગ કરવા માટે અનલૉક કરો"</string> <string name="wallet_error_generic" msgid="257704570182963611">"તમારા કાર્ડની માહિતી મેળવવામાં સમસ્યા આવી હતી, કૃપા કરીને થોડા સમય પછી ફરી પ્રયાસ કરો"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"લૉક સ્ક્રીનના સેટિંગ"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"QR સ્કૅન કરો"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"QR કોડ સ્કૅન કરવા માટે ક્લિક કરો"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"સ્કૅન કરવા માટે ટૅપ કરો"</string> <string name="status_bar_work" msgid="5238641949837091056">"ઑફિસની પ્રોફાઇલ"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"એરપ્લેન મોડ"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"તમે <xliff:g id="WHEN">%1$s</xliff:g> એ તમારો આગલો એલાર્મ સાંભળશો નહીં"</string> @@ -792,7 +792,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g> પર <xliff:g id="ARTIST_NAME">%2$s</xliff:g>નું <xliff:g id="SONG_NAME">%1$s</xliff:g> ગીત ચલાવો"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="APP_LABEL">%2$s</xliff:g> પર <xliff:g id="SONG_NAME">%1$s</xliff:g> ગીત ચલાવો"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"છેલ્લો ફેરફાર રદ કરો"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"<xliff:g id="DEVICENAME">%1$s</xliff:g> પર ચલાવવા માટે વધુ નજીક ખસેડો"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"<xliff:g id="DEVICENAME">%1$s</xliff:g> પર ચલાવવામાં આવી રહ્યું છે"</string> <string name="controls_error_timeout" msgid="794197289772728958">"નિષ્ક્રિય, ઍપને ચેક કરો"</string> <string name="controls_error_removed" msgid="6675638069846014366">"મળ્યું નથી"</string> @@ -880,4 +881,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"વપરાશકર્તા પસંદ કરો"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"બૅકગ્રાઉન્ડમાં ચાલતી ઍપ"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"રોકો"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-gu/tiles_states_strings.xml b/packages/SystemUI/res/values-gu/tiles_states_strings.xml index 15b0f9fe7fbe..10e7ac7b7de0 100644 --- a/packages/SystemUI/res/values-gu/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-gu/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"બંધ છે"</item> <item msgid="2075645297847971154">"ચાલુ છે"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"અનુપલબ્ધ"</item> + <item msgid="1909756493418256167">"બંધ છે"</item> + <item msgid="4531508423703413340">"ચાલુ છે"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"ઉપલબ્ધ નથી"</item> <item msgid="9103697205127645916">"બંધ છે"</item> diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml index 54b73897332d..755c4805be36 100644 --- a/packages/SystemUI/res/values-hi/strings.xml +++ b/packages/SystemUI/res/values-hi/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"वाई-फ़ाई कनेक्ट नहीं है"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"स्क्रीन की रोशनी"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"रंग बदलने की सुविधा"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"रंग में सुधार करने की सुविधा"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"और सेटिंग"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"उपयोगकर्ता सेटिंग"</string> <string name="quick_settings_done" msgid="2163641301648855793">"हो गया"</string> @@ -450,8 +449,10 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"इस्तेमाल करने के लिए, डिवाइस अनलॉक करें"</string> <string name="wallet_error_generic" msgid="257704570182963611">"आपके कार्ड की जानकारी पाने में कोई समस्या हुई है. कृपया बाद में कोशिश करें"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"लॉक स्क्रीन की सेटिंग"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"क्यूआर कोड स्कैन करें"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"क्यूआर कोड स्कैन करने के लिए क्लिक करें"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <!-- no translation found for qr_code_scanner_description (7937603775306661863) --> + <skip /> <string name="status_bar_work" msgid="5238641949837091056">"वर्क प्रोफ़ाइल"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"हवाई जहाज़ मोड"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"आपको <xliff:g id="WHEN">%1$s</xliff:g> पर अपना अगला अलार्म नहीं सुनाई देगा"</string> @@ -792,7 +793,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g> पर, <xliff:g id="ARTIST_NAME">%2$s</xliff:g> का <xliff:g id="SONG_NAME">%1$s</xliff:g> चलाएं"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="APP_LABEL">%2$s</xliff:g> पर, <xliff:g id="SONG_NAME">%1$s</xliff:g> चलाएं"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"पहले जैसा करें"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"<xliff:g id="DEVICENAME">%1$s</xliff:g> पर गाने चलाने के लिए उसके पास जाएं"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"<xliff:g id="DEVICENAME">%1$s</xliff:g> पर चल रहा है"</string> <string name="controls_error_timeout" msgid="794197289772728958">"काम नहीं कर रहा, ऐप जांचें"</string> <string name="controls_error_removed" msgid="6675638069846014366">"कंट्रोल नहीं है"</string> @@ -880,4 +882,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"उपयोगकर्ता चुनें"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"बैकग्राउंड में चल रहे ऐप्लिकेशन"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"बंद करें"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-hi/tiles_states_strings.xml b/packages/SystemUI/res/values-hi/tiles_states_strings.xml index 00fa69936dc6..e52ee17c5100 100644 --- a/packages/SystemUI/res/values-hi/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-hi/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"बंद है"</item> <item msgid="2075645297847971154">"चालू है"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"उपलब्ध नहीं है"</item> + <item msgid="1909756493418256167">"बंद है"</item> + <item msgid="4531508423703413340">"चालू है"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"उपलब्ध नहीं है"</item> <item msgid="9103697205127645916">"बंद है"</item> diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml index 8931d20dda9a..a5be1e360589 100644 --- a/packages/SystemUI/res/values-hr/strings.xml +++ b/packages/SystemUI/res/values-hr/strings.xml @@ -234,8 +234,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi mreža nije povezana"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Svjetlina"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inverzija boja"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Korekcija boja"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"Više postavki"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Korisničke postavke"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Gotovo"</string> @@ -453,8 +452,10 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Otključajte da biste koristili"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Pojavio se problem prilikom dohvaćanja kartica, pokušajte ponovo kasnije"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Postavke zaključanog zaslona"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"Skeniraj QR kôd"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Kliknite da biste skenirali QR kôd"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <!-- no translation found for qr_code_scanner_description (7937603775306661863) --> + <skip /> <string name="status_bar_work" msgid="5238641949837091056">"Poslovni profil"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Način rada u zrakoplovu"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"Nećete čuti sljedeći alarm <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -798,7 +799,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Pustite <xliff:g id="SONG_NAME">%1$s</xliff:g>, <xliff:g id="ARTIST_NAME">%2$s</xliff:g> putem aplikacije <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Pustite <xliff:g id="SONG_NAME">%1$s</xliff:g> putem aplikacije <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Poništi"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Približite se radi reprodukcije na uređaju <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"Reproducira se na uređaju <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Neaktivno, provjerite aplik."</string> <string name="controls_error_removed" msgid="6675638069846014366">"Nije pronađeno"</string> @@ -886,4 +888,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Odabir korisnika"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Aplikacije koje se izvode u pozadini"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Zaustavi"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-hr/tiles_states_strings.xml b/packages/SystemUI/res/values-hr/tiles_states_strings.xml index 730081667113..eb9ae525fa2e 100644 --- a/packages/SystemUI/res/values-hr/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-hr/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Isključeno"</item> <item msgid="2075645297847971154">"Uključeno"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"Nedostupno"</item> + <item msgid="1909756493418256167">"Isključeno"</item> + <item msgid="4531508423703413340">"Uključeno"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"Nedostupno"</item> <item msgid="9103697205127645916">"Isključeno"</item> diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml index eff9e8222e9e..0275b729ad31 100644 --- a/packages/SystemUI/res/values-hu/strings.xml +++ b/packages/SystemUI/res/values-hu/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Nem kapcsolódik Wi‑Fi-hálózathoz"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Fényerő"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Színek invertálása"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Színjavítás"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"További beállítások"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Felhasználói beállítások"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Kész"</string> @@ -450,8 +449,10 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Oldja fel a használathoz"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Probléma merült fel a kártyák lekérésekor, próbálja újra később"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Lezárási képernyő beállításai"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"QR-kód beolvasása"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Kattintson a QR-kód beolvasához"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <!-- no translation found for qr_code_scanner_description (7937603775306661863) --> + <skip /> <string name="status_bar_work" msgid="5238641949837091056">"Munkahelyi profil"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Repülős üzemmód"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"Nem fogja hallani az ébresztést ekkor: <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -792,7 +793,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> <xliff:g id="SONG_NAME">%1$s</xliff:g> című számának lejátszása innen: <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g> lejátszása innen: <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Visszavonás"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Menjen közelebb a következőn való lejátszáshoz: <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"Lejátszás folyamatban a(z) <xliff:g id="DEVICENAME">%1$s</xliff:g> eszközön"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Inaktív, ellenőrizze az appot"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Nem található"</string> @@ -880,4 +882,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Felhasználóválasztás"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Több alkalmazás is fut a háttérben"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Leállítás"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-hu/tiles_states_strings.xml b/packages/SystemUI/res/values-hu/tiles_states_strings.xml index 52450e4b3937..ba92bfd3bf74 100644 --- a/packages/SystemUI/res/values-hu/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-hu/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Ki"</item> <item msgid="2075645297847971154">"Be"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"Nem áll rendelkezésre"</item> + <item msgid="1909756493418256167">"Ki"</item> + <item msgid="4531508423703413340">"Be"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"Nem áll rendelkezésre"</item> <item msgid="9103697205127645916">"Ki"</item> diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml index 084b309e3751..c32b16686eba 100644 --- a/packages/SystemUI/res/values-hy/strings.xml +++ b/packages/SystemUI/res/values-hy/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi-ը միացված չէ"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Պայծառություն"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Գունաշրջում"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Գունաշտկում"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"Հավելյալ կարգավորումներ"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Օգտատիրոջ կարգավորումներ"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Պատրաստ է"</string> @@ -450,8 +449,9 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Ապակողպել՝ օգտագործելու համար"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Չհաջողվեց բեռնել քարտերը։ Նորից փորձեք։"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Կողպէկրանի կարգավորումներ"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"QR կոդերի սկանավորում"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Սեղմեք՝ QR կոդը սկանավորելու համար"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"Հպեք՝ սկանավորելու համար"</string> <string name="status_bar_work" msgid="5238641949837091056">"Android for Work-ի պրոֆիլ"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Ավիառեժիմ"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"Ժամը <xliff:g id="WHEN">%1$s</xliff:g>-ի զարթուցիչը չի զանգի"</string> @@ -792,7 +792,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Նվագարկել <xliff:g id="SONG_NAME">%1$s</xliff:g> երգը <xliff:g id="ARTIST_NAME">%2$s</xliff:g>-ի կատարմամբ <xliff:g id="APP_LABEL">%3$s</xliff:g> հավելվածից"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Նվագարկել <xliff:g id="SONG_NAME">%1$s</xliff:g> երգը <xliff:g id="APP_LABEL">%2$s</xliff:g> հավելվածից"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Հետարկել"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Ավելի մոտ եկեք՝ <xliff:g id="DEVICENAME">%1$s</xliff:g> սարքում նվագարկելու համար"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"Նվագարկվում է <xliff:g id="DEVICENAME">%1$s</xliff:g> սարքում"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Ակտիվ չէ, ստուգեք հավելվածը"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Չի գտնվել"</string> @@ -880,4 +881,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Ընտրեք օգտատեր"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Ֆոնային ռեժիմում աշխատող հավելվածներ"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Դադարեցնել"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-hy/tiles_states_strings.xml b/packages/SystemUI/res/values-hy/tiles_states_strings.xml index 23a096b194af..b52646f352b5 100644 --- a/packages/SystemUI/res/values-hy/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-hy/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Անջատված է"</item> <item msgid="2075645297847971154">"Միացված է"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"Անհասանելի է"</item> + <item msgid="1909756493418256167">"Անջատված է"</item> + <item msgid="4531508423703413340">"Միացված է"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"Հասանելի չէ"</item> <item msgid="9103697205127645916">"Անջատված է"</item> diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml index e595045e913b..24f8698cf7ee 100644 --- a/packages/SystemUI/res/values-in/strings.xml +++ b/packages/SystemUI/res/values-in/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi‑Fi tidak terhubung"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Kecerahan"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inversi warna"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Koreksi warna"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"Setelan lainnya"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Setelan pengguna"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Selesai"</string> @@ -450,8 +449,10 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Buka kunci untuk menggunakan"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Terjadi masalah saat mendapatkan kartu Anda, coba lagi nanti"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Setelan layar kunci"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"Pindai QR"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Klik untuk memindai kode QR"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <!-- no translation found for qr_code_scanner_description (7937603775306661863) --> + <skip /> <string name="status_bar_work" msgid="5238641949837091056">"Profil kerja"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Mode pesawat"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"Anda tidak akan mendengar alarm berikutnya <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -792,7 +793,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Putar <xliff:g id="SONG_NAME">%1$s</xliff:g> oleh <xliff:g id="ARTIST_NAME">%2$s</xliff:g> dari <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Putar <xliff:g id="SONG_NAME">%1$s</xliff:g> dari <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Urungkan"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Dekatkan untuk memutar di <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"Diputar di <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Nonaktif, periksa aplikasi"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Tidak ditemukan"</string> @@ -880,4 +882,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Pilih pengguna"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Aplikasi berjalan di latar belakang"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Berhenti"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-in/tiles_states_strings.xml b/packages/SystemUI/res/values-in/tiles_states_strings.xml index c296e5443190..0007dfc3b581 100644 --- a/packages/SystemUI/res/values-in/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-in/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Nonaktif"</item> <item msgid="2075645297847971154">"Aktif"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"Tidak tersedia"</item> + <item msgid="1909756493418256167">"Nonaktif"</item> + <item msgid="4531508423703413340">"Aktif"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"Tidak tersedia"</item> <item msgid="9103697205127645916">"Nonaktif"</item> diff --git a/packages/SystemUI/res/values-is/strings.xml b/packages/SystemUI/res/values-is/strings.xml index 43ae1b651f0e..a7a43e6f7562 100644 --- a/packages/SystemUI/res/values-is/strings.xml +++ b/packages/SystemUI/res/values-is/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi ekki tengt"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Birtustig"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Umsnúningur lita"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Litaleiðrétting"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"Fleiri stillingar"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Notandastillingar"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Lokið"</string> @@ -450,8 +449,10 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Taktu úr lás til að nota"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Vandamál kom upp við að sækja kortin þín. Reyndu aftur síðar"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Stillingar fyrir læstan skjá"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"Skanna QR-kóða"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Smelltu til að skanna QR-kóða"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <!-- no translation found for qr_code_scanner_description (7937603775306661863) --> + <skip /> <string name="status_bar_work" msgid="5238641949837091056">"Vinnusnið"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Flugstilling"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"Ekki mun heyrast í vekjaranum <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -792,7 +793,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Spila <xliff:g id="SONG_NAME">%1$s</xliff:g> með <xliff:g id="ARTIST_NAME">%2$s</xliff:g> í <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Spila <xliff:g id="SONG_NAME">%1$s</xliff:g> í <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Afturkalla"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Færðu nær til að spila í <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"Spilast í <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Óvirkt, athugaðu forrit"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Fannst ekki"</string> @@ -880,4 +882,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Velja notanda"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Forrit keyra í bakgrunni"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Stöðva"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-is/tiles_states_strings.xml b/packages/SystemUI/res/values-is/tiles_states_strings.xml index 25b3dcc53661..88472ef4b2fc 100644 --- a/packages/SystemUI/res/values-is/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-is/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Slökkt"</item> <item msgid="2075645297847971154">"Kveikt"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"Ekki í boði"</item> + <item msgid="1909756493418256167">"Slökkt"</item> + <item msgid="4531508423703413340">"Kveikt"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"Ekki í boði"</item> <item msgid="9103697205127645916">"Slökkt"</item> diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml index 00d7fb35d15e..2f3d50ff1816 100644 --- a/packages/SystemUI/res/values-it/strings.xml +++ b/packages/SystemUI/res/values-it/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Nessuna connessione Wi-Fi"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Luminosità"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inversione dei colori"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Correzione del colore"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"Altre impostazioni"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Impostazioni utente"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Fine"</string> @@ -450,8 +449,9 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Sblocca per usare"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Si è verificato un problema durante il recupero delle tue carte. Riprova più tardi."</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Impostazioni schermata di blocco"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"Scansiona QR"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Fai clic per scansionare un codice QR"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"Tocca per scansionare"</string> <string name="status_bar_work" msgid="5238641949837091056">"Profilo di lavoro"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Modalità aereo"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"Non sentirai la tua prossima sveglia <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -792,7 +792,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Riproduci <xliff:g id="SONG_NAME">%1$s</xliff:g> di <xliff:g id="ARTIST_NAME">%2$s</xliff:g> da <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Riproduci <xliff:g id="SONG_NAME">%1$s</xliff:g> da <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Annulla"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Avvicinati per riprodurre su <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"In riproduzione su <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Inattivo, controlla l\'app"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Controllo non trovato"</string> @@ -880,4 +881,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Seleziona utente"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"App in esecuzione in background"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Interrompi"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-it/tiles_states_strings.xml b/packages/SystemUI/res/values-it/tiles_states_strings.xml index 7e6827a4d8c2..071a970d2260 100644 --- a/packages/SystemUI/res/values-it/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-it/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Off"</item> <item msgid="2075645297847971154">"On"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"Non disponibile"</item> + <item msgid="1909756493418256167">"Off"</item> + <item msgid="4531508423703413340">"On"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"Non disponibile"</item> <item msgid="9103697205127645916">"Off"</item> diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml index b150eb7e6757..757ff778a018 100644 --- a/packages/SystemUI/res/values-iw/strings.xml +++ b/packages/SystemUI/res/values-iw/strings.xml @@ -235,8 +235,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"אין חיבור ל-Wi-Fi"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"בהירות"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"היפוך צבעים"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"תיקון צבע"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"הגדרות נוספות"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"הגדרות המשתמש"</string> <string name="quick_settings_done" msgid="2163641301648855793">"בוצע"</string> @@ -456,8 +455,10 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"יש לבטל את הנעילה כדי להשתמש"</string> <string name="wallet_error_generic" msgid="257704570182963611">"הייתה בעיה בקבלת הכרטיסים שלך. כדאי לנסות שוב מאוחר יותר"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"הגדרות מסך הנעילה"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"סריקת קוד QR"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"צריך ללחוץ כאן כדי לסרוק קוד QR"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <!-- no translation found for qr_code_scanner_description (7937603775306661863) --> + <skip /> <string name="status_bar_work" msgid="5238641949837091056">"פרופיל עבודה"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"מצב טיסה"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"לא ניתן יהיה לשמוע את ההתראה הבאה שלך <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -804,7 +805,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"הפעלת <xliff:g id="SONG_NAME">%1$s</xliff:g> של <xliff:g id="ARTIST_NAME">%2$s</xliff:g> מ-<xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"הפעלת <xliff:g id="SONG_NAME">%1$s</xliff:g> מ-<xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"ביטול"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"צריך להתקרב כדי להפעיל מוזיקה במכשיר <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"ההפעלה הועברה למכשיר <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"לא פעיל, יש לבדוק את האפליקציה"</string> <string name="controls_error_removed" msgid="6675638069846014366">"לא נמצא"</string> @@ -892,4 +894,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"בחירת משתמש"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"אפליקציות שפועלות ברקע"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"עצירה"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-iw/tiles_states_strings.xml b/packages/SystemUI/res/values-iw/tiles_states_strings.xml index bf3c2b6ad767..49fb4b671546 100644 --- a/packages/SystemUI/res/values-iw/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-iw/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"כבוי"</item> <item msgid="2075645297847971154">"פועל"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"לא זמין"</item> + <item msgid="1909756493418256167">"כבוי"</item> + <item msgid="4531508423703413340">"פועל"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"לא זמין"</item> <item msgid="9103697205127645916">"כבוי"</item> diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml index 1dde2100db8d..f219de3824b8 100644 --- a/packages/SystemUI/res/values-ja/strings.xml +++ b/packages/SystemUI/res/values-ja/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi 未接続"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"画面の明るさ"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"色反転"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"色補正"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"詳細設定"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"ユーザー設定"</string> <string name="quick_settings_done" msgid="2163641301648855793">"完了"</string> @@ -450,8 +449,8 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"ロックを解除して使用"</string> <string name="wallet_error_generic" msgid="257704570182963611">"カードの取得中に問題が発生しました。しばらくしてからもう一度お試しください"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"ロック画面の設定"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"QR のスキャン"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"クリックすると、QR コードをスキャンします"</string> + <string name="qr_code_scanner_title" msgid="5660820608548306581">"QR コード"</string> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"タップしてスキャンします"</string> <string name="status_bar_work" msgid="5238641949837091056">"仕事用プロファイル"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"機内モード"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"次回のアラーム(<xliff:g id="WHEN">%1$s</xliff:g>)は鳴りません"</string> @@ -792,7 +791,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="SONG_NAME">%1$s</xliff:g>(アーティスト名: <xliff:g id="ARTIST_NAME">%2$s</xliff:g>)を <xliff:g id="APP_LABEL">%3$s</xliff:g> で再生"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g> を <xliff:g id="APP_LABEL">%2$s</xliff:g> で再生"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"元に戻す"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"<xliff:g id="DEVICENAME">%1$s</xliff:g>で再生するにはもっと近づけてください"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"<xliff:g id="DEVICENAME">%1$s</xliff:g>で再生しています"</string> <string name="controls_error_timeout" msgid="794197289772728958">"無効: アプリをご確認ください"</string> <string name="controls_error_removed" msgid="6675638069846014366">"見つかりませんでした"</string> @@ -880,4 +880,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"ユーザーの選択"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"バックグラウンドで実行中のアプリ"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"停止"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-ja/tiles_states_strings.xml b/packages/SystemUI/res/values-ja/tiles_states_strings.xml index 9197aab790b0..55cbe8ba868a 100644 --- a/packages/SystemUI/res/values-ja/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-ja/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"OFF"</item> <item msgid="2075645297847971154">"ON"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"使用不可"</item> + <item msgid="1909756493418256167">"OFF"</item> + <item msgid="4531508423703413340">"ON"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"使用不可"</item> <item msgid="9103697205127645916">"OFF"</item> diff --git a/packages/SystemUI/res/values-ka/strings.xml b/packages/SystemUI/res/values-ka/strings.xml index 304c19d2af38..b932c452207a 100644 --- a/packages/SystemUI/res/values-ka/strings.xml +++ b/packages/SystemUI/res/values-ka/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi არ არის დაკავშირებული"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"განათება"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"ფერთა ინვერსია"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"ფერთა კორექცია"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"დამატებითი პარამეტრები"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"მომხმარებლის პარამეტრები"</string> <string name="quick_settings_done" msgid="2163641301648855793">"დასრულდა"</string> @@ -450,8 +449,8 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"გამოსაყენებლად განბლოკვა"</string> <string name="wallet_error_generic" msgid="257704570182963611">"თქვენი ბარათების მიღებისას პრობლემა წარმოიშვა. ცადეთ ხელახლა მოგვიანებით"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"ჩაკეტილი ეკრანის პარამეტრები"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"QR-ის სკანირება"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"დააწკაპუნეთ QR კოდის სკანირებისთვის"</string> + <string name="qr_code_scanner_title" msgid="5660820608548306581">"QR კოდი"</string> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"შეეხეთ დასასკანირებლად"</string> <string name="status_bar_work" msgid="5238641949837091056">"სამსახურის პროფილი"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"თვითმფრინავის რეჟიმი"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"ვერ გაიგონებთ მომდევნო მაღვიძარას <xliff:g id="WHEN">%1$s</xliff:g>-ზე"</string> @@ -792,7 +791,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"დაუკარით <xliff:g id="SONG_NAME">%1$s</xliff:g>, <xliff:g id="ARTIST_NAME">%2$s</xliff:g>, <xliff:g id="APP_LABEL">%3$s</xliff:g>-დან"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"დაუკარით <xliff:g id="SONG_NAME">%1$s</xliff:g> <xliff:g id="APP_LABEL">%2$s</xliff:g>-დან"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"მოქმედების გაუქმება"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"მიიტანეტ უფრო ახლოს, რომ დაუკრათ <xliff:g id="DEVICENAME">%1$s</xliff:g>-ზე"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"მიმდინარეობს დაკვრა <xliff:g id="DEVICENAME">%1$s</xliff:g>-ზე"</string> <string name="controls_error_timeout" msgid="794197289772728958">"არააქტიურია, გადაამოწმეთ აპი"</string> <string name="controls_error_removed" msgid="6675638069846014366">"ვერ მოიძებნა"</string> @@ -880,4 +880,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"მომხმარებლის არჩევა"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"ფონურად მომუშავე აპები"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"შეწყვეტა"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-ka/tiles_states_strings.xml b/packages/SystemUI/res/values-ka/tiles_states_strings.xml index 485c3de7bdcf..34caeff0a9b9 100644 --- a/packages/SystemUI/res/values-ka/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-ka/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"გამორთულია"</item> <item msgid="2075645297847971154">"ჩართულია"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"მიუწვდომელია"</item> + <item msgid="1909756493418256167">"გამორთვა"</item> + <item msgid="4531508423703413340">"ჩართვა"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"მიუწვდომელია"</item> <item msgid="9103697205127645916">"გამორთულია"</item> diff --git a/packages/SystemUI/res/values-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml index 90865f0169b4..1ac6c7ec66af 100644 --- a/packages/SystemUI/res/values-kk/strings.xml +++ b/packages/SystemUI/res/values-kk/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi желісіне жалғанбаған"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Жарықтығы"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Түс инверсиясы"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Түсті түзету"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"Қосымша параметрлер"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Пайдаланушы параметрлері"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Дайын"</string> @@ -450,8 +449,9 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Пайдалану үшін құлыпты ашу"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Карталарыңыз алынбады, кейінірек қайталап көріңіз."</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Экран құлпының параметрлері"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"QR кодын сканерлеу"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"QR кодын сканерлеу үшін басыңыз."</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"Сканерлеу үшін түртіңіз."</string> <string name="status_bar_work" msgid="5238641949837091056">"Жұмыс профилі"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Ұшақ режимі"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"Келесі <xliff:g id="WHEN">%1$s</xliff:g> дабылыңызды есітпейсіз"</string> @@ -792,7 +792,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g> қолданбасында <xliff:g id="ARTIST_NAME">%2$s</xliff:g> орындайтын \"<xliff:g id="SONG_NAME">%1$s</xliff:g>\" әнін ойнату"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="APP_LABEL">%2$s</xliff:g> қолданбасында \"<xliff:g id="SONG_NAME">%1$s</xliff:g>\" әнін ойнату"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Қайтару"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"<xliff:g id="DEVICENAME">%1$s</xliff:g> құрылғысында музыка ойнату үшін оған жақындаңыз."</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"<xliff:g id="DEVICENAME">%1$s</xliff:g> құрылғысында ойнатылуда."</string> <string name="controls_error_timeout" msgid="794197289772728958">"Өшірулі. Қолданба тексеріңіз."</string> <string name="controls_error_removed" msgid="6675638069846014366">"Табылмады"</string> @@ -880,4 +881,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Пайдаланушыны таңдау"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Фондық режимде жұмыс істеп тұрған қолданбалар"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Тоқтату"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-kk/tiles_states_strings.xml b/packages/SystemUI/res/values-kk/tiles_states_strings.xml index b143632803cb..616ad5362e63 100644 --- a/packages/SystemUI/res/values-kk/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-kk/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Өшірулі"</item> <item msgid="2075645297847971154">"Қосулы"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"Қолжетімді емес"</item> + <item msgid="1909756493418256167">"Өшірулі"</item> + <item msgid="4531508423703413340">"Қосулы"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"Қолжетімсіз"</item> <item msgid="9103697205127645916">"Өшірулі"</item> diff --git a/packages/SystemUI/res/values-km/strings.xml b/packages/SystemUI/res/values-km/strings.xml index 4b669998723b..bc69da90b510 100644 --- a/packages/SystemUI/res/values-km/strings.xml +++ b/packages/SystemUI/res/values-km/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"មិនមានការតភ្ជាប់ Wi-Fi ទេ"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"ពន្លឺ"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"ការបញ្ច្រាសពណ៌"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"ការកែតម្រូវពណ៌"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"ការកំណត់ច្រើនទៀត"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"ការកំណត់អ្នកប្រើប្រាស់"</string> <string name="quick_settings_done" msgid="2163641301648855793">"រួចរាល់"</string> @@ -450,8 +449,8 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"ដោះសោដើម្បីប្រើប្រាស់"</string> <string name="wallet_error_generic" msgid="257704570182963611">"មានបញ្ហាក្នុងការទាញយកកាតរបស់អ្នក សូមព្យាយាមម្ដងទៀតនៅពេលក្រោយ"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"ការកំណត់អេក្រង់ចាក់សោ"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"ស្កេនកូដ QR"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"ចុចដើម្បីស្កេនកូដ QR"</string> + <string name="qr_code_scanner_title" msgid="5660820608548306581">"កូដ QR"</string> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"ចុចដើម្បីស្កេន"</string> <string name="status_bar_work" msgid="5238641949837091056">"ប្រវត្តិរូបការងារ"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"ពេលជិះយន្តហោះ"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"អ្នកនឹងមិនលឺម៉ោងរោទ៍ <xliff:g id="WHEN">%1$s</xliff:g> បន្ទាប់របស់អ្នកទេ"</string> @@ -792,7 +791,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"ចាក់ <xliff:g id="SONG_NAME">%1$s</xliff:g> ច្រៀងដោយ <xliff:g id="ARTIST_NAME">%2$s</xliff:g> ពី <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"ចាក់ <xliff:g id="SONG_NAME">%1$s</xliff:g> ពី <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"ត្រឡប់វិញ"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"រំកិលឱ្យកាន់តែជិត ដើម្បីចាក់នៅលើ <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"កំពុងចាក់នៅលើ <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"អសកម្ម ពិនិត្យមើលកម្មវិធី"</string> <string name="controls_error_removed" msgid="6675638069846014366">"រកមិនឃើញទេ"</string> @@ -880,4 +880,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"ជ្រើសរើសអ្នកប្រើប្រាស់"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"កម្មវិធីដែលកំពុងដំណើរការនៅផ្ទៃខាងក្រោយ"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"ឈប់"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-km/tiles_states_strings.xml b/packages/SystemUI/res/values-km/tiles_states_strings.xml index 38d3894d07e1..b1a1a8fbcd44 100644 --- a/packages/SystemUI/res/values-km/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-km/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"បិទ"</item> <item msgid="2075645297847971154">"បើក"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"មិនអាចកែតម្រូវបានទេ"</item> + <item msgid="1909756493418256167">"បិទ"</item> + <item msgid="4531508423703413340">"បើក"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"មិនមានទេ"</item> <item msgid="9103697205127645916">"បិទ"</item> diff --git a/packages/SystemUI/res/values-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml index bbb9ef1bbfc9..c388d82ab962 100644 --- a/packages/SystemUI/res/values-kn/strings.xml +++ b/packages/SystemUI/res/values-kn/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"ವೈ-ಫೈ ಸಂಪರ್ಕಗೊಂಡಿಲ್ಲ"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"ಪ್ರಕಾಶಮಾನ"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"ಕಲರ್ ಇನ್ವರ್ಶನ್"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"ಬಣ್ಣದ ತಿದ್ದುಪಡಿ"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"ಹೆಚ್ಚಿನ ಸೆಟ್ಟಿಂಗ್ಗಳು"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"ಬಳಕೆದಾರರ ಸೆಟ್ಟಿಂಗ್ಗಳು"</string> <string name="quick_settings_done" msgid="2163641301648855793">"ಮುಗಿದಿದೆ"</string> @@ -450,8 +449,10 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"ಬಳಸಲು ಅನ್ಲಾಕ್ ಮಾಡಿ"</string> <string name="wallet_error_generic" msgid="257704570182963611">"ನಿಮ್ಮ ಕಾರ್ಡ್ಗಳನ್ನು ಪಡೆಯುವಾಗ ಸಮಸ್ಯೆ ಉಂಟಾಗಿದೆ, ನಂತರ ಪುನಃ ಪ್ರಯತ್ನಿಸಿ"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"ಲಾಕ್ ಸ್ಕ್ರ್ರೀನ್ ಸೆಟ್ಟಿಂಗ್ಗಳು"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"QR ಕೋಡ್ ಸ್ಕ್ಯಾನ್ ಮಾಡಿ"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"QR ಕೋಡ್ ಅನ್ನು ಸ್ಕ್ಯಾನ್ ಮಾಡಲು ಕ್ಲಿಕ್ ಮಾಡಿ"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <!-- no translation found for qr_code_scanner_description (7937603775306661863) --> + <skip /> <string name="status_bar_work" msgid="5238641949837091056">"ಕೆಲಸದ ಪ್ರೊಫೈಲ್"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"ಏರ್ಪ್ಲೇನ್ ಮೋಡ್"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"ನಿಮ್ಮ ಮುಂದಿನ <xliff:g id="WHEN">%1$s</xliff:g> ಅಲಾರಮ್ ಅನ್ನು ನೀವು ಆಲಿಸುವುದಿಲ್ಲ"</string> @@ -792,7 +793,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> ಅವರ <xliff:g id="SONG_NAME">%1$s</xliff:g> ಹಾಡನ್ನು <xliff:g id="APP_LABEL">%3$s</xliff:g> ನಲ್ಲಿ ಪ್ಲೇ ಮಾಡಿ"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g> ಹಾಡನ್ನು <xliff:g id="APP_LABEL">%2$s</xliff:g> ನಲ್ಲಿ ಪ್ಲೇ ಮಾಡಿ"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"ರದ್ದುಗೊಳಿಸಿ"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"<xliff:g id="DEVICENAME">%1$s</xliff:g> ನಲ್ಲಿ ಪ್ಲೇ ಮಾಡಲು ಅದರ ಹತ್ತಿರಕ್ಕೆ ಸರಿಯಿರಿ"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"<xliff:g id="DEVICENAME">%1$s</xliff:g> ನಲ್ಲಿ ಪ್ಲೇ ಆಗುತ್ತಿದೆ"</string> <string name="controls_error_timeout" msgid="794197289772728958">"ನಿಷ್ಕ್ರಿಯ, ಆ್ಯಪ್ ಪರಿಶೀಲಿಸಿ"</string> <string name="controls_error_removed" msgid="6675638069846014366">"ಕಂಡುಬಂದಿಲ್ಲ"</string> @@ -880,4 +882,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"ಬಳಕೆದಾರ ಆಯ್ಕೆಮಾಡಿ"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"ಹಿನ್ನೆಲೆಯಲ್ಲಿ ರನ್ ಆಗುತ್ತಿರುವ ಆ್ಯಪ್ಗಳು"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"ನಿಲ್ಲಿಸಿ"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-kn/tiles_states_strings.xml b/packages/SystemUI/res/values-kn/tiles_states_strings.xml index 022c5cf35f5c..e5bf6efc4edd 100644 --- a/packages/SystemUI/res/values-kn/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-kn/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"ಆಫ್ ಮಾಡಿ"</item> <item msgid="2075645297847971154">"ಆನ್ ಮಾಡಿ"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"ಲಭ್ಯವಿಲ್ಲ"</item> + <item msgid="1909756493418256167">"ಆಫ್ ಮಾಡಿ"</item> + <item msgid="4531508423703413340">"ಆನ್ ಮಾಡಿ"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"ಲಭ್ಯವಿಲ್ಲ"</item> <item msgid="9103697205127645916">"ಆಫ್ ಮಾಡಿ"</item> diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml index 4275960b8cbc..16b5447df366 100644 --- a/packages/SystemUI/res/values-ko/strings.xml +++ b/packages/SystemUI/res/values-ko/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi가 연결되지 않음"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"밝기"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"색상 반전"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"색상 보정"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"설정 더보기"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"사용자 설정"</string> <string name="quick_settings_done" msgid="2163641301648855793">"완료"</string> @@ -450,8 +449,10 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"잠금 해제하여 사용"</string> <string name="wallet_error_generic" msgid="257704570182963611">"카드를 가져오는 중에 문제가 발생했습니다. 나중에 다시 시도해 보세요."</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"잠금 화면 설정"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"QR 스캔"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"QR 코드를 스캔하려면 클릭하세요."</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <!-- no translation found for qr_code_scanner_description (7937603775306661863) --> + <skip /> <string name="status_bar_work" msgid="5238641949837091056">"직장 프로필"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"비행기 모드"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"<xliff:g id="WHEN">%1$s</xliff:g>에 다음 알람을 들을 수 없습니다."</string> @@ -792,7 +793,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g>에서 <xliff:g id="ARTIST_NAME">%2$s</xliff:g>의 <xliff:g id="SONG_NAME">%1$s</xliff:g> 재생"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="APP_LABEL">%2$s</xliff:g>에서 <xliff:g id="SONG_NAME">%1$s</xliff:g> 재생"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"실행취소"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"<xliff:g id="DEVICENAME">%1$s</xliff:g>에서 재생하려면 기기를 더 가까이로 옮기세요."</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"<xliff:g id="DEVICENAME">%1$s</xliff:g>에서 재생 중"</string> <string name="controls_error_timeout" msgid="794197289772728958">"비활성. 앱을 확인하세요."</string> <string name="controls_error_removed" msgid="6675638069846014366">"찾을 수 없음"</string> @@ -880,4 +882,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"사용자 선택"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"백그라운드에서 실행 중인 앱"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"중지"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-ko/tiles_states_strings.xml b/packages/SystemUI/res/values-ko/tiles_states_strings.xml index ae6f148270c5..595b12c4baa0 100644 --- a/packages/SystemUI/res/values-ko/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-ko/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"꺼짐"</item> <item msgid="2075645297847971154">"켜짐"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"사용할 수 없음"</item> + <item msgid="1909756493418256167">"꺼짐"</item> + <item msgid="4531508423703413340">"켜짐"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"이용 불가"</item> <item msgid="9103697205127645916">"꺼짐"</item> diff --git a/packages/SystemUI/res/values-ky/strings.xml b/packages/SystemUI/res/values-ky/strings.xml index 448fb843ea2c..31c8522de2bc 100644 --- a/packages/SystemUI/res/values-ky/strings.xml +++ b/packages/SystemUI/res/values-ky/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi туташкан жок"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Жарыктыгы"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Түстү инверсиялоо"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Түсүн тууралоо"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"Дагы жөндөөлөр"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Колдонуучунун жөндөөлөрү"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Бүттү"</string> @@ -450,8 +449,9 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Колдонуу үчүн кулпусун ачыңыз"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Кыйытмаларды алууда ката кетти. Бир аздан кийин кайталап көрүңүз."</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Кулпуланган экран жөндөөлөрү"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"QR кодун скандоо"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"QR кодун скандоо үчүн чыкылдатыңыз"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"Скандоо үчүн таптап коюңуз"</string> <string name="status_bar_work" msgid="5238641949837091056">"Жумуш профили"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Учак режими"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"<xliff:g id="WHEN">%1$s</xliff:g> боло турган кийинки эскертмени укпайсыз"</string> @@ -792,7 +792,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="SONG_NAME">%1$s</xliff:g> ырын (аткаруучу: <xliff:g id="ARTIST_NAME">%2$s</xliff:g>) <xliff:g id="APP_LABEL">%3$s</xliff:g> колдонмосунан ойнотуу"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g> ырын <xliff:g id="APP_LABEL">%2$s</xliff:g> колдонмосунан ойнотуу"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Кайтаруу"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"<xliff:g id="DEVICENAME">%1$s</xliff:g> түзмөгүндө ойнотуу үчүн жакыныраак жылдырыңыз"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"<xliff:g id="DEVICENAME">%1$s</xliff:g> аркылуу ойнотулууда"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Жигерсиз. Колдонмону текшериңиз"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Табылган жок"</string> @@ -880,4 +881,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Колдонуучуну тандоо"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Фондо иштеп жаткан колдонмолор"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Токтотуу"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-ky/tiles_states_strings.xml b/packages/SystemUI/res/values-ky/tiles_states_strings.xml index 0eadc34e37ba..3bcbf531d14a 100644 --- a/packages/SystemUI/res/values-ky/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-ky/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Өчүк"</item> <item msgid="2075645297847971154">"Күйүк"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"Жеткиликсиз"</item> + <item msgid="1909756493418256167">"Өчүк"</item> + <item msgid="4531508423703413340">"Күйүк"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"Жеткиликсиз"</item> <item msgid="9103697205127645916">"Өчүк"</item> diff --git a/packages/SystemUI/res/values-lo/strings.xml b/packages/SystemUI/res/values-lo/strings.xml index 702b76e1e7ec..f3884bc110ad 100644 --- a/packages/SystemUI/res/values-lo/strings.xml +++ b/packages/SystemUI/res/values-lo/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"ບໍ່ໄດ້ເຊື່ອມຕໍ່ Wi-Fi"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"ຄວາມແຈ້ງ"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"ການປີ້ນສີ"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"ການແກ້ໄຂສີ"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"ການຕັ້ງຄ່າເພີ່ມເຕີມ"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"ຕັ້ງຄ່າຜູ້ໃຊ້"</string> <string name="quick_settings_done" msgid="2163641301648855793">"ແລ້ວໆ"</string> @@ -450,8 +449,9 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"ປົດລັອກເພື່ອໃຊ້"</string> <string name="wallet_error_generic" msgid="257704570182963611">"ເກີດບັນຫາໃນການໂຫຼດບັດຂອງທ່ານ, ກະລຸນາລອງໃໝ່ໃນພາຍຫຼັງ"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"ການຕັ້ງຄ່າໜ້າຈໍລັອກ"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"ສະແກນ QR"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"ຄລິກເພື່ອສະແກນລະຫັດ QR"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"ແຕະເພື່ອສະແກນ"</string> <string name="status_bar_work" msgid="5238641949837091056">"ໂປຣໄຟລ໌ບ່ອນເຮັດວຽກ"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"ໂໝດເຮືອບິນ"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"ທ່ານຈະບໍ່ໄດ້ຍິນສຽງໂມງປ <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -792,7 +792,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"ຫຼິ້ນ <xliff:g id="SONG_NAME">%1$s</xliff:g> ໂດຍ <xliff:g id="ARTIST_NAME">%2$s</xliff:g> ຈາກ <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"ຫຼິ້ນ <xliff:g id="SONG_NAME">%1$s</xliff:g> ຈາກ <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"ຍົກເລີກ"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"ຍ້າຍໄປໃກ້ຂຶ້ນເພື່ອຫຼິ້ນຢູ່ <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"ກຳລັງຫຼິ້ນຢູ່ <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"ບໍ່ເຮັດວຽກ, ກະລຸນາກວດສອບແອັບ"</string> <string name="controls_error_removed" msgid="6675638069846014366">"ບໍ່ພົບ"</string> @@ -880,4 +881,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"ເລືອກຜູ້ໃຊ້"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"ແອັບທີ່ກຳລັງເອີ້ນໃຊ້ໃນພື້ນຫຼັງ"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"ຢຸດ"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-lo/tiles_states_strings.xml b/packages/SystemUI/res/values-lo/tiles_states_strings.xml index 5fe5cfff03bf..0cb8afd2aca3 100644 --- a/packages/SystemUI/res/values-lo/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-lo/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"ປິດ"</item> <item msgid="2075645297847971154">"ເປີດ"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"ບໍ່ສາມາດໃຊ້ໄດ້"</item> + <item msgid="1909756493418256167">"ປິດ"</item> + <item msgid="4531508423703413340">"ເປີດ"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"ບໍ່ສາມາດໃຊ້ໄດ້"</item> <item msgid="9103697205127645916">"ປິດ"</item> diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml index eb593623d363..f2918d58e837 100644 --- a/packages/SystemUI/res/values-lt/strings.xml +++ b/packages/SystemUI/res/values-lt/strings.xml @@ -235,8 +235,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"„Wi-Fi“ neprijungtas"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Šviesumas"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Spalvų inversija"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Spalvų taisymas"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"Daugiau nustatymų"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Naudotojo nustatymai"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Atlikta"</string> @@ -456,8 +455,9 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Atrakinti, kad būtų galima naudoti"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Gaunant korteles kilo problema, bandykite dar kartą vėliau"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Užrakinimo ekrano nustatymai"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"Nuskaityti QR kodą"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Spustelėkite, kad nuskaitytumėte QR kodą"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"Palieskite, kad nuskaitytumėte"</string> <string name="status_bar_work" msgid="5238641949837091056">"Darbo profilis"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Lėktuvo režimas"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"Negirdėsite kito signalo <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -804,7 +804,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Leisti <xliff:g id="ARTIST_NAME">%2$s</xliff:g> – „<xliff:g id="SONG_NAME">%1$s</xliff:g>“ iš „<xliff:g id="APP_LABEL">%3$s</xliff:g>“"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Leisti „<xliff:g id="SONG_NAME">%1$s</xliff:g>“ iš „<xliff:g id="APP_LABEL">%2$s</xliff:g>“"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Anuliuoti"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Prieikite arčiau, kad galėtumėte leisti įrenginyje „<xliff:g id="DEVICENAME">%1$s</xliff:g>“"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"Leidžiama įrenginyje „<xliff:g id="DEVICENAME">%1$s</xliff:g>“"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Neaktyvu, patikrinkite progr."</string> <string name="controls_error_removed" msgid="6675638069846014366">"Nerasta"</string> @@ -892,4 +893,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Naudotojo pasirinkimas"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Fone veikiančios programos"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Sustabdyti"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-lt/tiles_states_strings.xml b/packages/SystemUI/res/values-lt/tiles_states_strings.xml index 7a0caa9c9afa..44a3fd52a6ef 100644 --- a/packages/SystemUI/res/values-lt/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-lt/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Išjungta"</item> <item msgid="2075645297847971154">"Įjungta"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"Nepasiekiama"</item> + <item msgid="1909756493418256167">"Išjungta"</item> + <item msgid="4531508423703413340">"Įjungta"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"Nepasiekiama"</item> <item msgid="9103697205127645916">"Išjungta"</item> diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml index a27036e7728b..86a5df8ff901 100644 --- a/packages/SystemUI/res/values-lv/strings.xml +++ b/packages/SystemUI/res/values-lv/strings.xml @@ -234,8 +234,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Nav izveidots savienojums ar Wi-Fi"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Spilgtums"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Krāsu inversija"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Krāsu korekcija"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"Vairāk iestatījumu"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Lietotāja iestatījumi"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Gatavs"</string> @@ -453,8 +452,10 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Lai izmantotu, atbloķējiet ekrānu"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Ienesot jūsu kartes, radās problēma. Lūdzu, vēlāk mēģiniet vēlreiz."</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Bloķēšanas ekrāna iestatījumi"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"Ātrās atbildes koda skeneris"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Noklikšķiniet, lai skenētu ātrās atbildes kodu."</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <!-- no translation found for qr_code_scanner_description (7937603775306661863) --> + <skip /> <string name="status_bar_work" msgid="5238641949837091056">"Darba profils"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Lidojuma režīms"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"Nākamais signāls (<xliff:g id="WHEN">%1$s</xliff:g>) netiks atskaņots."</string> @@ -798,7 +799,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Atskaņojiet failu “<xliff:g id="SONG_NAME">%1$s</xliff:g>” (izpildītājs: <xliff:g id="ARTIST_NAME">%2$s</xliff:g>) no lietotnes <xliff:g id="APP_LABEL">%3$s</xliff:g>."</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Atskaņojiet failu “<xliff:g id="SONG_NAME">%1$s</xliff:g>” no lietotnes <xliff:g id="APP_LABEL">%2$s</xliff:g>."</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Atsaukt"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Pārvietojiet savu ierīci tuvāk, lai atskaņotu mūziku ierīcē “<xliff:g id="DEVICENAME">%1$s</xliff:g>”"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"Notiek atskaņošana ierīcē “<xliff:g id="DEVICENAME">%1$s</xliff:g>”"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Neaktīva, pārbaudiet lietotni"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Netika atrasta"</string> @@ -886,4 +888,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Lietotāja atlase"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Lietotnes, kas darbojas fonā"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Apturēt"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-lv/tiles_states_strings.xml b/packages/SystemUI/res/values-lv/tiles_states_strings.xml index 872dba60ca1d..35264ae2459d 100644 --- a/packages/SystemUI/res/values-lv/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-lv/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Izslēgts"</item> <item msgid="2075645297847971154">"Ieslēgts"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"Nav pieejama"</item> + <item msgid="1909756493418256167">"Izslēgta"</item> + <item msgid="4531508423703413340">"Ieslēgta"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"Nav pieejama"</item> <item msgid="9103697205127645916">"Izslēgta"</item> diff --git a/packages/SystemUI/res/values-mk/strings.xml b/packages/SystemUI/res/values-mk/strings.xml index 7f60c25f5bc7..b21f18375222 100644 --- a/packages/SystemUI/res/values-mk/strings.xml +++ b/packages/SystemUI/res/values-mk/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi не е поврзано"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Осветленост"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Инверзија на боите"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Корекција на боите"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"Повеќе поставки"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Поставки на корисникот"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Готово"</string> @@ -450,8 +449,8 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Отклучете за да користите"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Имаше проблем при преземањето на картичките. Обидете се повторно подоцна"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Поставки за заклучен екран"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"Скенирајте QR-код"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Кликнете за да скенирате QR-код"</string> + <string name="qr_code_scanner_title" msgid="5660820608548306581">"QR-код"</string> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"Допрете за скенирање"</string> <string name="status_bar_work" msgid="5238641949837091056">"Работен профил"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Авионски режим"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"Нема да го слушнете следниот аларм <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -792,7 +791,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Пуштете <xliff:g id="SONG_NAME">%1$s</xliff:g> од <xliff:g id="ARTIST_NAME">%2$s</xliff:g> на <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Пуштете <xliff:g id="SONG_NAME">%1$s</xliff:g> на <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Врати"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Приближете се за да пуштите на <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"Се репродуцира на <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Неактивна, провери апликација"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Не е најдено"</string> @@ -880,4 +880,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Изберете корисник"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Апликации се извршуваат во заднина"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Крај"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-mk/tiles_states_strings.xml b/packages/SystemUI/res/values-mk/tiles_states_strings.xml index 65e94f371e7f..c2c6f5dd1f23 100644 --- a/packages/SystemUI/res/values-mk/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-mk/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Исклучено"</item> <item msgid="2075645297847971154">"Вклучено"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"Недостапна"</item> + <item msgid="1909756493418256167">"Исклучена"</item> + <item msgid="4531508423703413340">"Вклучена"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"Недостапно"</item> <item msgid="9103697205127645916">"Исклучено"</item> diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml index a65593cef66a..798ece02b4f7 100644 --- a/packages/SystemUI/res/values-ml/strings.xml +++ b/packages/SystemUI/res/values-ml/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"വൈഫൈ കണക്റ്റ് ചെയ്തിട്ടില്ല"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"തെളിച്ചം"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"നിറം വിപരീതമാക്കൽ"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"നിറം ശരിയാക്കൽ"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"കൂടുതൽ ക്രമീകരണങ്ങൾ"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"ഉപയോക്തൃ ക്രമീകരണം"</string> <string name="quick_settings_done" msgid="2163641301648855793">"പൂർത്തിയാക്കി"</string> @@ -450,8 +449,8 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"ഉപയോഗിക്കാൻ അൺലോക്ക് ചെയ്യുക"</string> <string name="wallet_error_generic" msgid="257704570182963611">"നിങ്ങളുടെ കാർഡുകൾ ലഭ്യമാക്കുന്നതിൽ ഒരു പ്രശ്നമുണ്ടായി, പിന്നീട് വീണ്ടും ശ്രമിക്കുക"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"ലോക്ക് സ്ക്രീൻ ക്രമീകരണം"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"QR സ്കാൻ ചെയ്യുക"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"QR കോഡ് സ്കാൻ ചെയ്യാൻ ക്ലിക്ക് ചെയ്യുക"</string> + <string name="qr_code_scanner_title" msgid="5660820608548306581">"QR കോഡ്"</string> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"സ്കാൻ ചെയ്യാൻ ടാപ്പ് ചെയ്യുക"</string> <string name="status_bar_work" msgid="5238641949837091056">"ഔദ്യോഗിക പ്രൊഫൈൽ"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"ഫ്ലൈറ്റ് മോഡ്"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"<xliff:g id="WHEN">%1$s</xliff:g>-നുള്ള നിങ്ങളുടെ അടുത്ത അലാറം കേൾക്കില്ല"</string> @@ -792,7 +791,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> എന്ന ആർട്ടിസ്റ്റിന്റെ <xliff:g id="SONG_NAME">%1$s</xliff:g> എന്ന ഗാനം <xliff:g id="APP_LABEL">%3$s</xliff:g> ആപ്പിൽ പ്ലേ ചെയ്യുക"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g> എന്ന ഗാനം <xliff:g id="APP_LABEL">%2$s</xliff:g> ആപ്പിൽ പ്ലേ ചെയ്യുക"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"പഴയപടിയാക്കുക"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"<xliff:g id="DEVICENAME">%1$s</xliff:g> എന്നതിൽ പ്ലേ ചെയ്യാൻ അടുത്തേക്ക് നീക്കുക"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"<xliff:g id="DEVICENAME">%1$s</xliff:g> എന്നതിൽ പ്ലേ ചെയ്യുന്നു"</string> <string name="controls_error_timeout" msgid="794197289772728958">"നിഷ്ക്രിയം, ആപ്പ് പരിശോധിക്കൂ"</string> <string name="controls_error_removed" msgid="6675638069846014366">"കണ്ടെത്തിയില്ല"</string> @@ -880,4 +880,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"ഉപയോക്താവിനെ തിരഞ്ഞെടുക്കൂ"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"ആപ്പുകൾ പശ്ചാത്തലത്തിൽ റൺ ചെയ്യുന്നു"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"നിർത്തുക"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-ml/tiles_states_strings.xml b/packages/SystemUI/res/values-ml/tiles_states_strings.xml index 8746c74bd00a..c683c1b14c7d 100644 --- a/packages/SystemUI/res/values-ml/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-ml/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"ഓഫാണ്"</item> <item msgid="2075645297847971154">"ഓണാണ്"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"ലഭ്യമല്ല"</item> + <item msgid="1909756493418256167">"ഓഫാണ്"</item> + <item msgid="4531508423703413340">"ഓണാണ്"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"ലഭ്യമല്ല"</item> <item msgid="9103697205127645916">"ഓഫാണ്"</item> diff --git a/packages/SystemUI/res/values-mn/strings.xml b/packages/SystemUI/res/values-mn/strings.xml index 9cc0b3f0628b..d283916dd0e8 100644 --- a/packages/SystemUI/res/values-mn/strings.xml +++ b/packages/SystemUI/res/values-mn/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi-д холбогдоогүй байна"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Тодрол"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Өнгө урвуулах"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Өнгөний засвар"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"Бусад тохиргоо"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Хэрэглэгчийн тохиргоо"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Дууссан"</string> @@ -450,8 +449,8 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Ашиглахын тулд түгжээг тайлах"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Таны картыг авахад асуудал гарлаа. Дараа дахин оролдоно уу"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Түгжигдсэн дэлгэцийн тохиргоо"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"QR-г скан хийх"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"QR кодыг скан хийхийн тулд товшино уу"</string> + <string name="qr_code_scanner_title" msgid="5660820608548306581">"QR код"</string> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"Скан хийхийн тулд товшино уу"</string> <string name="status_bar_work" msgid="5238641949837091056">"Ажлын профайл"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Нислэгийн горим"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"<xliff:g id="WHEN">%1$s</xliff:g>-т та дараагийн сэрүүлгээ сонсохгүй"</string> @@ -792,7 +791,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g>-н <xliff:g id="SONG_NAME">%1$s</xliff:g>-г <xliff:g id="APP_LABEL">%3$s</xliff:g> дээр тоглуулах"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g>-г <xliff:g id="APP_LABEL">%2$s</xliff:g> дээр тоглуулах"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Болих"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"<xliff:g id="DEVICENAME">%1$s</xliff:g> дээр тоглуулахын тулд төхөөрөмжөө ойртуулна уу"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"<xliff:g id="DEVICENAME">%1$s</xliff:g> дээр тоглуулж байна"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Идэвхгүй байна, аппыг шалгана уу"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Олдсонгүй"</string> @@ -880,4 +880,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Хэрэглэгч сонгох"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Ард ажиллаж байгаа аппууд"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Зогсоох"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-mn/tiles_states_strings.xml b/packages/SystemUI/res/values-mn/tiles_states_strings.xml index 07dde9f76a98..7e01fbd139d3 100644 --- a/packages/SystemUI/res/values-mn/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-mn/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Унтраалттай"</item> <item msgid="2075645297847971154">"Асаалттай"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"Боломжгүй"</item> + <item msgid="1909756493418256167">"Унтраалттай"</item> + <item msgid="4531508423703413340">"Асаалттай"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"Боломжгүй"</item> <item msgid="9103697205127645916">"Унтраалттай"</item> diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml index 4d3258785b60..933a0e406518 100644 --- a/packages/SystemUI/res/values-mr/strings.xml +++ b/packages/SystemUI/res/values-mr/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"वाय-फाय नाही"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"चमक"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"कलर इन्व्हर्जन"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"रंग सुधारणा"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"अधिक सेटिंग्ज"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"वापरकर्ता सेटिंग्ज"</string> <string name="quick_settings_done" msgid="2163641301648855793">"पूर्ण झाले"</string> @@ -450,8 +449,8 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"वापरण्यासाठी अनलॉक करा"</string> <string name="wallet_error_generic" msgid="257704570182963611">"तुमची कार्ड मिळवताना समस्या आली, कृपया नंतर पुन्हा प्रयत्न करा"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"लॉक स्क्रीन सेटिंग्ज"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"QR स्कॅन करा"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"QR कोड स्कॅन करण्यासाठी क्लिक करा"</string> + <string name="qr_code_scanner_title" msgid="5660820608548306581">"QR कोड"</string> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"स्कॅन करण्यासाठी टॅप करा"</string> <string name="status_bar_work" msgid="5238641949837091056">"कार्य प्रोफाईल"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"विमान मोड"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"तुम्ही तुमचा <xliff:g id="WHEN">%1$s</xliff:g> वाजता होणारा पुढील अलार्म ऐकणार नाही"</string> @@ -792,7 +791,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g> मध्ये <xliff:g id="ARTIST_NAME">%2$s</xliff:g> चे <xliff:g id="SONG_NAME">%1$s</xliff:g> प्ले करा"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="APP_LABEL">%2$s</xliff:g> मध्ये <xliff:g id="SONG_NAME">%1$s</xliff:g> प्ले करा"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"पहिल्यासारखे करा"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"<xliff:g id="DEVICENAME">%1$s</xliff:g> वर प्ले करण्यासाठी जवळ जा"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"<xliff:g id="DEVICENAME">%1$s</xliff:g> वर प्ले केला जात आहे"</string> <string name="controls_error_timeout" msgid="794197289772728958">"निष्क्रिय, ॲप तपासा"</string> <string name="controls_error_removed" msgid="6675638069846014366">"आढळले नाही"</string> @@ -880,4 +880,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"वापरकर्ता निवडा"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"ॲप्स बॅकग्राउंडमध्ये रन होत आहेत"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"थांबवा"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-mr/tiles_states_strings.xml b/packages/SystemUI/res/values-mr/tiles_states_strings.xml index f0ca33356bb6..7fd88cceecc9 100644 --- a/packages/SystemUI/res/values-mr/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-mr/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"बंद आहे"</item> <item msgid="2075645297847971154">"सुरू आहे"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"उपलब्ध नाही"</item> + <item msgid="1909756493418256167">"बंद आहे"</item> + <item msgid="4531508423703413340">"सुरू आहे"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"उपलब्ध नाही"</item> <item msgid="9103697205127645916">"बंद आहे"</item> diff --git a/packages/SystemUI/res/values-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml index 355580406b95..4cf476b5db32 100644 --- a/packages/SystemUI/res/values-ms/strings.xml +++ b/packages/SystemUI/res/values-ms/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi tidak disambungkan"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Kecerahan"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Penyongsangan warna"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Pembetulan warna"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"Lagi tetapan"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Tetapan pengguna"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Selesai"</string> @@ -450,8 +449,9 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Buka kunci untuk menggunakan"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Terdapat masalah sewaktu mendapatkan kad anda. Sila cuba sebentar lagi"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Tetapan skrin kunci"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"Imbas QR"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Klik untuk mengimbas kod QR"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"Ketik untuk membuat imbasan"</string> <string name="status_bar_work" msgid="5238641949837091056">"Profil kerja"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Mod pesawat"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"Anda tidak akan mendengar penggera yang seterusnya <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -792,7 +792,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Mainkan <xliff:g id="SONG_NAME">%1$s</xliff:g> oleh <xliff:g id="ARTIST_NAME">%2$s</xliff:g> daripada <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Mainkan <xliff:g id="SONG_NAME">%1$s</xliff:g> daripada <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Buat asal"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Alihkan lebih dekat untuk bermain pada<xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"Dimainkan pada <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Tidak aktif, semak apl"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Tidak ditemukan"</string> @@ -880,4 +881,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Pilih pengguna"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Apl berjalan di latar"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Berhenti"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-ms/tiles_states_strings.xml b/packages/SystemUI/res/values-ms/tiles_states_strings.xml index b682df1ca324..eaafd192506c 100644 --- a/packages/SystemUI/res/values-ms/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-ms/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Mati"</item> <item msgid="2075645297847971154">"Hidup"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"Tidak tersedia"</item> + <item msgid="1909756493418256167">"Mati"</item> + <item msgid="4531508423703413340">"Hidup"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"Tidak tersedia"</item> <item msgid="9103697205127645916">"Mati"</item> diff --git a/packages/SystemUI/res/values-my/strings.xml b/packages/SystemUI/res/values-my/strings.xml index 26333edb77b1..8964ed529cb5 100644 --- a/packages/SystemUI/res/values-my/strings.xml +++ b/packages/SystemUI/res/values-my/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi ချိတ်ဆက်ထားခြင်းမရှိပါ"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"အလင်းတောက်ပမှု"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"အရောင်ပြောင်းပြန်ပြုလုပ်ရန်"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"အရောင် အမှန်ပြင်ခြင်း"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"နောက်ထပ် ဆက်တင်များ"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"အသုံးပြုသူ ဆက်တင်များ"</string> <string name="quick_settings_done" msgid="2163641301648855793">"ပြီးပါပြီ"</string> @@ -450,8 +449,10 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"သုံးရန် လော့ခ်ဖွင့်ပါ"</string> <string name="wallet_error_generic" msgid="257704570182963611">"သင်၏ကတ်များ ရယူရာတွင် ပြဿနာရှိနေသည်၊ နောက်မှ ထပ်စမ်းကြည့်ပါ"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"လော့ခ်မျက်နှာပြင် ဆက်တင်များ"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"QR စကင်ဖတ်ခြင်း"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"QR ကုဒ် စကင်ဖတ်ရန် ကလစ်နှိပ်ပါ"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <!-- no translation found for qr_code_scanner_description (7937603775306661863) --> + <skip /> <string name="status_bar_work" msgid="5238641949837091056">"အလုပ် ပရိုဖိုင်"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"လေယာဉ်ပျံမုဒ်"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"<xliff:g id="WHEN">%1$s</xliff:g> ၌သင့်နောက်ထပ် နှိုးစက်ကို ကြားမည်မဟုတ်ပါ"</string> @@ -792,7 +793,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> ၏ <xliff:g id="SONG_NAME">%1$s</xliff:g> ကို <xliff:g id="APP_LABEL">%3$s</xliff:g> တွင် ဖွင့်ပါ"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g> ကို <xliff:g id="APP_LABEL">%2$s</xliff:g> တွင် ဖွင့်ပါ"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"နောက်ပြန်ရန်"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"<xliff:g id="DEVICENAME">%1$s</xliff:g> တွင်ဖွင့်ရန် အနီးသို့ရွှေ့ပါ"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"<xliff:g id="DEVICENAME">%1$s</xliff:g> တွင်ဖွင့်ထားသည်"</string> <string name="controls_error_timeout" msgid="794197289772728958">"ရပ်နေသည်၊ အက်ပ်ကို စစ်ဆေးပါ"</string> <string name="controls_error_removed" msgid="6675638069846014366">"မတွေ့ပါ"</string> @@ -880,4 +882,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"အသုံးပြုသူ ရွေးခြင်း"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"နောက်ခံတွင် ဖွင့်ထားသောအက်ပ်များ"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"ရပ်ရန်"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-my/tiles_states_strings.xml b/packages/SystemUI/res/values-my/tiles_states_strings.xml index af8d55c8cd7f..dfc8ccca736f 100644 --- a/packages/SystemUI/res/values-my/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-my/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"ပိတ်"</item> <item msgid="2075645297847971154">"ဖွင့်"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"မရနိုင်ပါ"</item> + <item msgid="1909756493418256167">"ပိတ်"</item> + <item msgid="4531508423703413340">"ဖွင့်"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"မရနိုင်ပါ"</item> <item msgid="9103697205127645916">"ပိတ်"</item> diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml index 0102801191bf..6e66156a87ef 100644 --- a/packages/SystemUI/res/values-nb/strings.xml +++ b/packages/SystemUI/res/values-nb/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi er ikke tilkoblet"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Lysstyrke"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Fargeinvertering"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Fargekorrigering"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"Flere innstillinger"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Brukerinnstillinger"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Ferdig"</string> @@ -450,8 +449,10 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Lås opp for å bruke"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Det oppsto et problem med henting av kortene. Prøv igjen senere"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Innstillinger for låseskjermen"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"Skann QR-kode"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Klikk for å skanne en QR-kode"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <!-- no translation found for qr_code_scanner_description (7937603775306661863) --> + <skip /> <string name="status_bar_work" msgid="5238641949837091056">"Work-profil"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Flymodus"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"Du hører ikke neste innstilte alarm <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -792,7 +793,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Spill av <xliff:g id="SONG_NAME">%1$s</xliff:g> av <xliff:g id="ARTIST_NAME">%2$s</xliff:g> fra <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Spill av <xliff:g id="SONG_NAME">%1$s</xliff:g> fra <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Angre"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Flytt nærmere for å spille av på <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"Spilles av på <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Inaktiv. Sjekk appen"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Ikke funnet"</string> @@ -880,4 +882,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Velg bruker"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Apper som kjører i bakgrunnen"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Stopp"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-nb/tiles_states_strings.xml b/packages/SystemUI/res/values-nb/tiles_states_strings.xml index 619f6135d56f..38e10456d612 100644 --- a/packages/SystemUI/res/values-nb/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-nb/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Av"</item> <item msgid="2075645297847971154">"På"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"Utilgjengelig"</item> + <item msgid="1909756493418256167">"Av"</item> + <item msgid="4531508423703413340">"På"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"Utilgjengelig"</item> <item msgid="9103697205127645916">"Av"</item> diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml index dc220c7ccf77..db64a7e4879f 100644 --- a/packages/SystemUI/res/values-ne/strings.xml +++ b/packages/SystemUI/res/values-ne/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi जडान गरिएको छैन"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"उज्यालपन"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"कलर इन्भर्सन"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"रङ सच्याउने कार्य"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"थप सेटिङहरू"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"प्रयोगकर्तासम्बन्धी सेटिङ"</string> <string name="quick_settings_done" msgid="2163641301648855793">"भयो"</string> @@ -450,8 +449,8 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"यो वालेट प्रयोग गर्न डिभाइस अनलक गर्नुहोस्"</string> <string name="wallet_error_generic" msgid="257704570182963611">"तपाईंका कार्डहरू प्राप्त गर्ने क्रममा समस्या भयो, कृपया पछि फेरि प्रयास गर्नुहोस्"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"लक स्क्रिनसम्बन्धी सेटिङ"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"QR स्क्यान गर्नुहोस्"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"QR कोड स्क्यान गर्न क्लिक गर्नुहोस्"</string> + <string name="qr_code_scanner_title" msgid="5660820608548306581">"QR कोड"</string> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"स्क्यान गर्न ट्याप गर्नुहोस्"</string> <string name="status_bar_work" msgid="5238641949837091056">"कार्य प्रोफाइल"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"हवाइजहाज मोड"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"तपाईँले आफ्नो अर्को अलार्म <xliff:g id="WHEN">%1$s</xliff:g> सुन्नुहुने छैन"</string> @@ -792,7 +791,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> को <xliff:g id="SONG_NAME">%1$s</xliff:g> बोलको गीत <xliff:g id="APP_LABEL">%3$s</xliff:g> मा बजाउनुहोस्"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g> बोलको गीत <xliff:g id="APP_LABEL">%2$s</xliff:g> मा बजाउनुहोस्"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"अन्डू गर्नुहोस्"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"<xliff:g id="DEVICENAME">%1$s</xliff:g> मा प्ले गर्न आफ्नो डिभाइस नजिकै लैजानुहोस्"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"<xliff:g id="DEVICENAME">%1$s</xliff:g> मा प्ले गरिँदै छ"</string> <string name="controls_error_timeout" msgid="794197289772728958">"निष्क्रिय छ, एप जाँच गर्नु…"</string> <string name="controls_error_removed" msgid="6675638069846014366">"फेला परेन"</string> @@ -880,4 +880,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"प्रयोगकर्ता चयन गर्नु…"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"अहिले ब्याकग्राउन्डमा चलिरहेका एप"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"रोक्नुहोस्"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-ne/tiles_states_strings.xml b/packages/SystemUI/res/values-ne/tiles_states_strings.xml index 808b58d31ea1..abe94e763481 100644 --- a/packages/SystemUI/res/values-ne/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-ne/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"अफ छ"</item> <item msgid="2075645297847971154">"अन छ"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"उपलब्ध छैन"</item> + <item msgid="1909756493418256167">"अफ छ"</item> + <item msgid="4531508423703413340">"अन छ"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"उपलब्ध छैन"</item> <item msgid="9103697205127645916">"अफ छ"</item> diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml index e7dbd2ecb019..b895d9ebd542 100644 --- a/packages/SystemUI/res/values-nl/strings.xml +++ b/packages/SystemUI/res/values-nl/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wifi niet verbonden"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Helderheid"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Kleurinversie"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Kleurcorrectie"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"Meer instellingen"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Gebruikersinstellingen"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Klaar"</string> @@ -450,8 +449,9 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Ontgrendelen om te gebruiken"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Er is een probleem opgetreden bij het ophalen van je kaarten. Probeer het later opnieuw."</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Instellingen voor vergrendelscherm"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"QR-code scannen"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Klik om een QR-code te scannen"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"Tik om te scannen"</string> <string name="status_bar_work" msgid="5238641949837091056">"Werkprofiel"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Vliegtuigmodus"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"Je hoort je volgende wekker niet <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -792,7 +792,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="SONG_NAME">%1$s</xliff:g> van <xliff:g id="ARTIST_NAME">%2$s</xliff:g> afspelen via <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g> afspelen via <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Ongedaan maken"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Ga dichter naar <xliff:g id="DEVICENAME">%1$s</xliff:g> toe om af te spelen"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"Afspelen op <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Inactief, check de app"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Niet gevonden"</string> @@ -880,4 +881,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Gebruiker selecteren"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Apps die op de achtergrond worden uitgevoerd"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Stoppen"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-nl/tiles_states_strings.xml b/packages/SystemUI/res/values-nl/tiles_states_strings.xml index 92332ca81f4a..ac85f28daa37 100644 --- a/packages/SystemUI/res/values-nl/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-nl/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Uit"</item> <item msgid="2075645297847971154">"Aan"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"Niet beschikbaar"</item> + <item msgid="1909756493418256167">"Uit"</item> + <item msgid="4531508423703413340">"Aan"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"Niet beschikbaar"</item> <item msgid="9103697205127645916">"Uit"</item> diff --git a/packages/SystemUI/res/values-or/strings.xml b/packages/SystemUI/res/values-or/strings.xml index 334b7565be67..e47513c3e8f8 100644 --- a/packages/SystemUI/res/values-or/strings.xml +++ b/packages/SystemUI/res/values-or/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"ୱାଇ-ଫାଇ ସଂଯୋଜିତ ହୋଇନାହିଁ"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"ଉଜ୍ଜ୍ୱଳତା"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"ରଙ୍ଗ ଇନଭାର୍ସନ"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"ରଙ୍ଗ ସଂଶୋଧନ"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"ଅଧିକ ସେଟିଂସ୍"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"ଉପଯୋଗକର୍ତ୍ତା ସେଟିଂସ୍"</string> <string name="quick_settings_done" msgid="2163641301648855793">"ହୋଇଗଲା"</string> @@ -450,8 +449,9 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"ବ୍ୟବହାର କରିବାକୁ ଅନଲକ୍ କରନ୍ତୁ"</string> <string name="wallet_error_generic" msgid="257704570182963611">"ଆପଣଙ୍କ କାର୍ଡଗୁଡ଼ିକ ପାଇବାରେ ଏକ ସମସ୍ୟା ହୋଇଥିଲା। ଦୟାକରି ପରେ ପୁଣି ଚେଷ୍ଟା କରନ୍ତୁ"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"ସ୍କ୍ରିନ୍ ଲକ୍ ସେଟିଂସ୍"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"QR ସ୍କାନ କରନ୍ତୁ"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"ଏକ QR କୋଡ ସ୍କାନ କରିବାକୁ କ୍ଲିକ କରନ୍ତୁ"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"ସ୍କାନ କରିବାକୁ ଟାପ କରନ୍ତୁ"</string> <string name="status_bar_work" msgid="5238641949837091056">"ୱର୍କ ପ୍ରୋଫାଇଲ୍"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"ଏରୋପ୍ଲେନ୍ ମୋଡ୍"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"<xliff:g id="WHEN">%1$s</xliff:g>ବେଳେ ଆପଣ ନିଜର ପରବର୍ତ୍ତୀ ଆଲାର୍ମ ଶୁଣିପାରିବେ ନାହିଁ"</string> @@ -792,7 +792,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g>ରୁ <xliff:g id="ARTIST_NAME">%2$s</xliff:g>ଙ୍କ <xliff:g id="SONG_NAME">%1$s</xliff:g> ଚଲାନ୍ତୁ"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="APP_LABEL">%2$s</xliff:g>ରୁ <xliff:g id="SONG_NAME">%1$s</xliff:g> ଚଲାନ୍ତୁ"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"ପୂର୍ବବତ୍ କରନ୍ତୁ"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"<xliff:g id="DEVICENAME">%1$s</xliff:g>ରେ ଚଲାଇବା ପାଇଁ ନିକଟକୁ ଯାଆନ୍ତୁ"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"<xliff:g id="DEVICENAME">%1$s</xliff:g>ରେ ଚାଲୁଛି"</string> <string name="controls_error_timeout" msgid="794197289772728958">"ନିଷ୍କ୍ରିୟ ଅଛି, ଆପ ଯାଞ୍ଚ କରନ୍ତୁ"</string> <string name="controls_error_removed" msgid="6675638069846014366">"ମିଳିଲା ନାହିଁ"</string> @@ -880,4 +881,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"ଉପଯୋଗକର୍ତ୍ତା ଚୟନ କର"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"ପୃଷ୍ଠପଟରେ ଚାଲୁଥିବା ଆପଗୁଡ଼ିକ"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"ବନ୍ଦ କରନ୍ତୁ"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-or/tiles_states_strings.xml b/packages/SystemUI/res/values-or/tiles_states_strings.xml index 94b012297f49..48ebb63e8ad2 100644 --- a/packages/SystemUI/res/values-or/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-or/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"ବନ୍ଦ ଅଛି"</item> <item msgid="2075645297847971154">"ଚାଲୁ ଅଛି"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"ଉପଲବ୍ଧ ନାହିଁ"</item> + <item msgid="1909756493418256167">"ବନ୍ଦ ଅଛି"</item> + <item msgid="4531508423703413340">"ଚାଲୁ ଅଛି"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"ଉପଲବ୍ଧ ନାହିଁ"</item> <item msgid="9103697205127645916">"ବନ୍ଦ ଅଛି"</item> diff --git a/packages/SystemUI/res/values-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml index 256ed366a087..3eae70aa7b8c 100644 --- a/packages/SystemUI/res/values-pa/strings.xml +++ b/packages/SystemUI/res/values-pa/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"ਵਾਈ-ਫਾਈ ਕਨੈਕਟ ਨਹੀਂ ਕੀਤਾ ਗਿਆ"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"ਚਮਕ"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"ਰੰਗ ਪਲਟਨਾ"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"ਰੰਗ ਸੁਧਾਈ"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"ਹੋਰ ਸੈਟਿੰਗਾਂ"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"ਵਰਤੋਂਕਾਰ ਸੈਟਿੰਗਾਂ"</string> <string name="quick_settings_done" msgid="2163641301648855793">"ਹੋ ਗਿਆ"</string> @@ -450,8 +449,9 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"ਵਰਤਣ ਲਈ ਅਣਲਾਕ ਕਰੋ"</string> <string name="wallet_error_generic" msgid="257704570182963611">"ਤੁਹਾਡੇ ਕਾਰਡ ਪ੍ਰਾਪਤ ਕਰਨ ਵਿੱਚ ਕੋਈ ਸਮੱਸਿਆ ਆਈ, ਕਿਰਪਾ ਕਰਕੇ ਬਾਅਦ ਵਿੱਚ ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"ਲਾਕ ਸਕ੍ਰੀਨ ਸੈਟਿੰਗਾਂ"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"QR ਸਕੈਨ ਕਰੋ"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"QR ਕੋਡ ਨੂੰ ਸਕੈਨ ਕਰਨ ਲਈ ਕਲਿੱਕ ਕਰੋ"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"ਸਕੈਨ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ"</string> <string name="status_bar_work" msgid="5238641949837091056">"ਕਾਰਜ ਪ੍ਰੋਫਾਈਲ"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"ਹਵਾਈ-ਜਹਾਜ਼ ਮੋਡ"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"ਤੁਸੀਂ <xliff:g id="WHEN">%1$s</xliff:g> ਵਜੇ ਆਪਣਾ ਅਗਲਾ ਅਲਾਰਮ ਨਹੀਂ ਸੁਣੋਗੇ"</string> @@ -792,7 +792,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g> ਤੋਂ <xliff:g id="ARTIST_NAME">%2$s</xliff:g> ਦਾ <xliff:g id="SONG_NAME">%1$s</xliff:g> ਚਲਾਓ"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="APP_LABEL">%2$s</xliff:g> ਤੋਂ <xliff:g id="SONG_NAME">%1$s</xliff:g> ਚਲਾਓ"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"ਅਣਕੀਤਾ ਕਰੋ"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"<xliff:g id="DEVICENAME">%1$s</xliff:g> \'ਤੇ ਚਲਾਉਣ ਲਈ ਨੇੜੇ ਲਿਜਾਓ"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"<xliff:g id="DEVICENAME">%1$s</xliff:g> \'ਤੇ ਚਲਾਇਆ ਜਾ ਰਿਹਾ ਹੈ"</string> <string name="controls_error_timeout" msgid="794197289772728958">"ਅਕਿਰਿਆਸ਼ੀਲ, ਐਪ ਦੀ ਜਾਂਚ ਕਰੋ"</string> <string name="controls_error_removed" msgid="6675638069846014366">"ਨਹੀਂ ਮਿਲਿਆ"</string> @@ -880,4 +881,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"ਵਰਤੋਂਕਾਰ ਚੁਣੋ"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"ਬੈਕਗ੍ਰਾਊਂਡ ਵਿੱਚ ਚੱਲ ਰਹੀਆਂ ਐਪਾਂ"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"ਬੰਦ ਕਰੋ"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-pa/tiles_states_strings.xml b/packages/SystemUI/res/values-pa/tiles_states_strings.xml index a7fc06626636..85c5d89eb5a2 100644 --- a/packages/SystemUI/res/values-pa/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-pa/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"ਬੰਦ ਹੈ"</item> <item msgid="2075645297847971154">"ਚਾਲੂ ਹੈ"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"ਉਪਲਬਧ ਨਹੀਂ"</item> + <item msgid="1909756493418256167">"ਬੰਦ ਹੈ"</item> + <item msgid="4531508423703413340">"ਚਾਲੂ ਹੈ"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"ਅਣਉਪਲਬਧ ਹੈ"</item> <item msgid="9103697205127645916">"ਬੰਦ ਹੈ"</item> diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml index 8f57f0e59419..41ad75a3e64b 100644 --- a/packages/SystemUI/res/values-pl/strings.xml +++ b/packages/SystemUI/res/values-pl/strings.xml @@ -235,8 +235,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Brak połączenia z Wi-Fi"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Jasność"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Odwrócenie kolorów"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Korekcja kolorów"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"Więcej ustawień"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Ustawienia użytkownika"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Gotowe"</string> @@ -456,8 +455,10 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Odblokuj, aby użyć"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Podczas pobierania kart wystąpił problem. Spróbuj ponownie później."</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Ustawienia ekranu blokady"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"Skanowanie kodu QR"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Kliknij, aby zeskanować kod QR"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <!-- no translation found for qr_code_scanner_description (7937603775306661863) --> + <skip /> <string name="status_bar_work" msgid="5238641949837091056">"Profil służbowy"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Tryb samolotowy"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"Nie usłyszysz swojego następnego alarmu <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -804,7 +805,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Odtwórz utwór <xliff:g id="SONG_NAME">%1$s</xliff:g> (<xliff:g id="ARTIST_NAME">%2$s</xliff:g>) w aplikacji <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Odtwórz utwór <xliff:g id="SONG_NAME">%1$s</xliff:g> w aplikacji <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Cofnij"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Przysuń się bliżej, aby odtwarzać na urządzeniu <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"Odtwarzam na urządzeniu <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Nieaktywny, sprawdź aplikację"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Nie znaleziono"</string> @@ -892,4 +894,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Wybierz użytkownika"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Aplikacje działające w tle"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Zatrzymaj"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-pl/tiles_states_strings.xml b/packages/SystemUI/res/values-pl/tiles_states_strings.xml index 94fa858a0abb..8b922e53c18f 100644 --- a/packages/SystemUI/res/values-pl/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-pl/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Wyłączony"</item> <item msgid="2075645297847971154">"Włączony"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"Brak dostępu"</item> + <item msgid="1909756493418256167">"Wyłączono"</item> + <item msgid="4531508423703413340">"Włączono"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"Niedostępny"</item> <item msgid="9103697205127645916">"Wyłączony"</item> diff --git a/packages/SystemUI/res/values-pt-rBR/strings.xml b/packages/SystemUI/res/values-pt-rBR/strings.xml index 3b37834f22e4..d21ea5498b4a 100644 --- a/packages/SystemUI/res/values-pt-rBR/strings.xml +++ b/packages/SystemUI/res/values-pt-rBR/strings.xml @@ -449,8 +449,9 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Desbloquear para usar"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Ocorreu um problema ao carregar os cards. Tente novamente mais tarde"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Configurações de tela de bloqueio"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"Ler código QR"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Clique para ler um código QR"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"Toque para fazer a leitura"</string> <string name="status_bar_work" msgid="5238641949837091056">"Perfil de trabalho"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Modo avião"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"Você não ouvirá o próximo alarme às <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -791,7 +792,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Tocar <xliff:g id="SONG_NAME">%1$s</xliff:g> de <xliff:g id="ARTIST_NAME">%2$s</xliff:g> no app <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Tocar <xliff:g id="SONG_NAME">%1$s</xliff:g> no app <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Desfazer"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Aproxime os dispositivos para ouvir música no <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"Reproduzido em <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Inativo, verifique o app"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Não encontrado"</string> @@ -879,4 +881,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Selecionar usuário"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Apps em execução em segundo plano"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Parar"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml index 1195413fd280..556e92a9ff52 100644 --- a/packages/SystemUI/res/values-pt-rPT/strings.xml +++ b/packages/SystemUI/res/values-pt-rPT/strings.xml @@ -449,8 +449,8 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Desbloquear para utilizar"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Ocorreu um problema ao obter os seus cartões. Tente novamente mais tarde."</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Definições do ecrã de bloqueio"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"Leia o QR"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Clique para ler um código QR"</string> + <string name="qr_code_scanner_title" msgid="5660820608548306581">"Código QR"</string> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"Toque para ler"</string> <string name="status_bar_work" msgid="5238641949837091056">"Perfil de trabalho"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Modo de avião"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"Não vai ouvir o próximo alarme às <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -791,7 +791,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Reproduzir <xliff:g id="SONG_NAME">%1$s</xliff:g> de <xliff:g id="ARTIST_NAME">%2$s</xliff:g> a partir da app <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Reproduzir <xliff:g id="SONG_NAME">%1$s</xliff:g> a partir da app <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Anular"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Aproxime-se para reproduzir no dispositivo <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"A reproduzir no dispositivo <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Inativa. Consulte a app."</string> <string name="controls_error_removed" msgid="6675638069846014366">"Não encontrado."</string> @@ -879,4 +880,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Selecione utilizador"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Apps em execução em segundo plano"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Parar"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml index 3b37834f22e4..d21ea5498b4a 100644 --- a/packages/SystemUI/res/values-pt/strings.xml +++ b/packages/SystemUI/res/values-pt/strings.xml @@ -449,8 +449,9 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Desbloquear para usar"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Ocorreu um problema ao carregar os cards. Tente novamente mais tarde"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Configurações de tela de bloqueio"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"Ler código QR"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Clique para ler um código QR"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"Toque para fazer a leitura"</string> <string name="status_bar_work" msgid="5238641949837091056">"Perfil de trabalho"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Modo avião"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"Você não ouvirá o próximo alarme às <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -791,7 +792,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Tocar <xliff:g id="SONG_NAME">%1$s</xliff:g> de <xliff:g id="ARTIST_NAME">%2$s</xliff:g> no app <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Tocar <xliff:g id="SONG_NAME">%1$s</xliff:g> no app <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Desfazer"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Aproxime os dispositivos para ouvir música no <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"Reproduzido em <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Inativo, verifique o app"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Não encontrado"</string> @@ -879,4 +881,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Selecionar usuário"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Apps em execução em segundo plano"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Parar"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml index f8023cf19c3d..ab3c186dbe5b 100644 --- a/packages/SystemUI/res/values-ro/strings.xml +++ b/packages/SystemUI/res/values-ro/strings.xml @@ -234,8 +234,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Rețeaua Wi-Fi nu este conectată"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Luminozitate"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inversarea culorilor"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Corecția culorii"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"Mai multe setări"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Setări de utilizator"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Terminat"</string> @@ -453,8 +452,9 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Deblocați pentru a folosi"</string> <string name="wallet_error_generic" msgid="257704570182963611">"A apărut o problemă la preluarea cardurilor. Încercați din nou mai târziu"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Setările ecranului de blocare"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"Scanați un cod QR"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Dați clic pentru a scana un cod QR"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"Atingeți pentru a scana"</string> <string name="status_bar_work" msgid="5238641949837091056">"Profil de serviciu"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Mod Avion"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"Nu veți auzi următoarea alarmă <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -798,7 +798,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Redați <xliff:g id="SONG_NAME">%1$s</xliff:g> de la <xliff:g id="ARTIST_NAME">%2$s</xliff:g> în <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Redați <xliff:g id="SONG_NAME">%1$s</xliff:g> în <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Anulați"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Apropiați-vă pentru a reda pe <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"Se redă pe <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Inactiv, verificați aplicația"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Nu s-a găsit"</string> @@ -886,4 +887,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Alegeți utilizatorul"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Aplicațiile rulează în fundal"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Opriți"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-ro/tiles_states_strings.xml b/packages/SystemUI/res/values-ro/tiles_states_strings.xml index 708c6f03a1d7..ba936966550e 100644 --- a/packages/SystemUI/res/values-ro/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-ro/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Dezactivat"</item> <item msgid="2075645297847971154">"Activat"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"Indisponibilă"</item> + <item msgid="1909756493418256167">"Dezactivată"</item> + <item msgid="4531508423703413340">"Activată"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"Indisponibilă"</item> <item msgid="9103697205127645916">"Dezactivată"</item> diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml index 9f40eab5e532..e92364c943d9 100644 --- a/packages/SystemUI/res/values-ru/strings.xml +++ b/packages/SystemUI/res/values-ru/strings.xml @@ -235,8 +235,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Нет подключения к сети Wi-Fi."</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Яркость"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Инверсия цветов"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Коррекция цвета"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"Настройки"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Пользовательские настройки"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Готово"</string> @@ -456,8 +455,8 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Разблокировать для использования"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Не удалось получить информацию о картах. Повторите попытку позже."</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Настройки заблокированного экрана"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"Сканер QR-кодов"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Нажмите, чтобы отсканировать QR-код."</string> + <string name="qr_code_scanner_title" msgid="5660820608548306581">"QR-код"</string> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"Нажмите, чтобы отсканировать код"</string> <string name="status_bar_work" msgid="5238641949837091056">"Рабочий профиль"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Режим полета"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"Следующий будильник: <xliff:g id="WHEN">%1$s</xliff:g>. Звук отключен."</string> @@ -804,7 +803,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Воспроизвести медиафайл \"<xliff:g id="SONG_NAME">%1$s</xliff:g>\" (исполнитель: <xliff:g id="ARTIST_NAME">%2$s</xliff:g>) из приложения \"<xliff:g id="APP_LABEL">%3$s</xliff:g>\""</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Воспроизвести медиафайл \"<xliff:g id="SONG_NAME">%1$s</xliff:g>\" из приложения \"<xliff:g id="APP_LABEL">%2$s</xliff:g>\""</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Отменить"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Чтобы включить музыку на устройстве \"<xliff:g id="DEVICENAME">%1$s</xliff:g>\", подойдите к нему ближе."</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"Воспроизводится на устройстве \"<xliff:g id="DEVICENAME">%1$s</xliff:g>\"."</string> <string name="controls_error_timeout" msgid="794197289772728958">"Нет ответа. Проверьте приложение."</string> <string name="controls_error_removed" msgid="6675638069846014366">"Не найдено."</string> @@ -892,4 +892,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Выберите профиль"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Приложения, работающие в фоновом режиме"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Остановить"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-ru/tiles_states_strings.xml b/packages/SystemUI/res/values-ru/tiles_states_strings.xml index 3a51c2e1b2b0..32e6ac978c77 100644 --- a/packages/SystemUI/res/values-ru/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-ru/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Откл."</item> <item msgid="2075645297847971154">"Вкл."</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"Недоступно"</item> + <item msgid="1909756493418256167">"Отключено"</item> + <item msgid="4531508423703413340">"Включено"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"Функция недоступна"</item> <item msgid="9103697205127645916">"Откл."</item> diff --git a/packages/SystemUI/res/values-si/strings.xml b/packages/SystemUI/res/values-si/strings.xml index ce5b12690ff3..327025c488ff 100644 --- a/packages/SystemUI/res/values-si/strings.xml +++ b/packages/SystemUI/res/values-si/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi සම්බන්ධ නොවීය"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"දීප්තිමත් බව"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"වර්ණ අපවර්තනය"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"වර්ණ නිවැරදි කිරීම"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"තව සැකසීම්"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"පරිශීලක සැකසීම්"</string> <string name="quick_settings_done" msgid="2163641301648855793">"නිමයි"</string> @@ -450,8 +449,10 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"භාවිත කිරීමට අගුලු හරින්න"</string> <string name="wallet_error_generic" msgid="257704570182963611">"ඔබගේ කාඩ්පත ලබා ගැනීමේ ගැටලුවක් විය, කරුණාකර පසුව නැවත උත්සාහ කරන්න"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"අගුලු තිර සැකසීම්"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"QR කේතය ස්කෑන් කරන්න"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"QR කේතයක් ස්කෑන් කිරීමට ක්ලික් කරන්න"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <!-- no translation found for qr_code_scanner_description (7937603775306661863) --> + <skip /> <string name="status_bar_work" msgid="5238641949837091056">"කාර්යාල පැතිකඩ"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"ගුවන්යානා ප්රකාරය"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"ඔබට ඔබේ ඊළඟ එලාමය <xliff:g id="WHEN">%1$s</xliff:g> නොඇසෙනු ඇත"</string> @@ -792,7 +793,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g>ගේ <xliff:g id="SONG_NAME">%1$s</xliff:g> <xliff:g id="APP_LABEL">%3$s</xliff:g> වෙතින් වාදනය කරන්න"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g> <xliff:g id="APP_LABEL">%2$s</xliff:g> වෙතින් වාදනය කරන්න"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"පසුගමනය කරන්න"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"<xliff:g id="DEVICENAME">%1$s</xliff:g> හි වාදනය කිරීමට වඩාත් ළං වන්න"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"<xliff:g id="DEVICENAME">%1$s</xliff:g> හි වාදනය කරමින්"</string> <string name="controls_error_timeout" msgid="794197289772728958">"අක්රියයි, යෙදුම පරීක්ෂා කරන්න"</string> <string name="controls_error_removed" msgid="6675638069846014366">"හමු නොවිණි"</string> @@ -880,4 +882,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"පරිශීලක තෝරන්න"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"පසුබිමින් ධාවනය වෙමින් පවතින යෙදුම්"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"නවත්වන්න"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-si/tiles_states_strings.xml b/packages/SystemUI/res/values-si/tiles_states_strings.xml index 909d119f2c09..8929a3c5709d 100644 --- a/packages/SystemUI/res/values-si/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-si/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"අක්රියයි"</item> <item msgid="2075645297847971154">"සක්රියයි"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"නොමැත"</item> + <item msgid="1909756493418256167">"ක්රියාවිරහිතයි"</item> + <item msgid="4531508423703413340">"ක්රියාත්මකයි"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"නොමැත"</item> <item msgid="9103697205127645916">"අක්රියයි"</item> diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml index 209b031bc484..bb8870fb7429 100644 --- a/packages/SystemUI/res/values-sk/strings.xml +++ b/packages/SystemUI/res/values-sk/strings.xml @@ -455,8 +455,9 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Odomknúť a použiť"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Pri načítavaní kariet sa vyskytol problém. Skúste to neskôr."</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Nastavenia uzamknutej obrazovky"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"Skenovanie QR kódu"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Kliknutím naskenujte QR kód"</string> + <string name="qr_code_scanner_title" msgid="5660820608548306581">"QR kód"</string> + <!-- no translation found for qr_code_scanner_description (7937603775306661863) --> + <skip /> <string name="status_bar_work" msgid="5238641949837091056">"Pracovný profil"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Režim v lietadle"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"Váš budík o <xliff:g id="WHEN">%1$s</xliff:g> sa nespustí"</string> @@ -803,7 +804,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Prehrať skladbu <xliff:g id="SONG_NAME">%1$s</xliff:g> od interpreta <xliff:g id="ARTIST_NAME">%2$s</xliff:g> z aplikácie <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Prehrať skladbu <xliff:g id="SONG_NAME">%1$s</xliff:g> z aplikácie <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Späť"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Ak chcete prehrávať v zariadení <xliff:g id="DEVICENAME">%1$s</xliff:g>, priblížte sa"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"Prehráva sa v zariadení <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Neaktívne, preverte aplikáciu"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Nenájdené"</string> @@ -891,4 +893,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Vyberte používateľa"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Aplikácie spustené na pozadí"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Ukončiť"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml index 26d0570b8577..9c2c32c928b3 100644 --- a/packages/SystemUI/res/values-sl/strings.xml +++ b/packages/SystemUI/res/values-sl/strings.xml @@ -455,8 +455,9 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Odklenite za uporabo"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Pri pridobivanju kartic je prišlo do težave. Poskusite znova pozneje."</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Nastavitve zaklepanja zaslona"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"Optično branje kode QR"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Kliknite, če želite optično prebrati kodo QR"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"Dotaknite se za optično branje"</string> <string name="status_bar_work" msgid="5238641949837091056">"Profil za Android Work"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Način za letalo"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"Naslednjega alarma ob <xliff:g id="WHEN">%1$s</xliff:g> ne boste slišali"</string> @@ -803,7 +804,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Predvajaj skladbo <xliff:g id="SONG_NAME">%1$s</xliff:g> izvajalca <xliff:g id="ARTIST_NAME">%2$s</xliff:g> iz aplikacije <xliff:g id="APP_LABEL">%3$s</xliff:g>."</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Predvajaj skladbo <xliff:g id="SONG_NAME">%1$s</xliff:g> iz aplikacije <xliff:g id="APP_LABEL">%2$s</xliff:g>."</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Razveljavi"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Za predvajanje v napravi <xliff:g id="DEVICENAME">%1$s</xliff:g> bolj približajte telefon."</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"Predvajanje v napravi <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Neaktivno, poglejte aplikacijo"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Ni mogoče najti"</string> @@ -891,4 +893,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Izberite uporabnika"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Aplikacije z izvajanjem v ozadju"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Ustavi"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml index 703e681a3762..993ab0cee711 100644 --- a/packages/SystemUI/res/values-sq/strings.xml +++ b/packages/SystemUI/res/values-sq/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi nuk është lidhur"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Ndriçimi"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Anasjellja e ngjyrës"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Korrigjimi i ngjyrës"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"Cilësime të tjera"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Cilësimet e përdoruesit"</string> <string name="quick_settings_done" msgid="2163641301648855793">"U krye"</string> @@ -450,8 +449,10 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Shkyçe për ta përdorur"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Pati një problem me marrjen e kartave të tua. Provo përsëri më vonë"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Cilësimet e ekranit të kyçjes"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"Skano kodin QR"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Kliko për të skanuar një kod QR"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <!-- no translation found for qr_code_scanner_description (7937603775306661863) --> + <skip /> <string name="status_bar_work" msgid="5238641949837091056">"Profili i punës"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Modaliteti i aeroplanit"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"Nuk do ta dëgjosh alarmin e radhës në <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -792,7 +793,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Luaj <xliff:g id="SONG_NAME">%1$s</xliff:g> nga <xliff:g id="ARTIST_NAME">%2$s</xliff:g> nga <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Luaj <xliff:g id="SONG_NAME">%1$s</xliff:g> nga <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Zhbëj"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Afrohu për të luajtur në <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"Po luhet në <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Joaktive, kontrollo aplikacionin"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Nuk u gjet"</string> @@ -880,4 +882,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Zgjidh përdoruesin"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Aplikacionet që ekzekutohen në sfond"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Ndalo"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-sq/tiles_states_strings.xml b/packages/SystemUI/res/values-sq/tiles_states_strings.xml index 461cd93b3ba9..c7e3883aa42e 100644 --- a/packages/SystemUI/res/values-sq/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-sq/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Joaktive"</item> <item msgid="2075645297847971154">"Aktive"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"Nuk ofrohet"</item> + <item msgid="1909756493418256167">"Joaktiv"</item> + <item msgid="4531508423703413340">"Aktiv"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"Nuk ofrohet"</item> <item msgid="9103697205127645916">"Joaktiv"</item> diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml index bf325e2b2db7..f637f2cf3bb7 100644 --- a/packages/SystemUI/res/values-sr/strings.xml +++ b/packages/SystemUI/res/values-sr/strings.xml @@ -234,8 +234,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"WiFi није повезан"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Осветљеност"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Инверзија боја"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Корекција боја"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"Још подешавања"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Корисничка подешавања"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Готово"</string> @@ -453,8 +452,8 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Откључај ради коришћења"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Дошло је до проблема при преузимању картица. Пробајте поново касније"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Подешавања закључаног екрана"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"Скенирај QR кôд"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Кликните да бисте скенирали QR кôд"</string> + <string name="qr_code_scanner_title" msgid="5660820608548306581">"QR кôд"</string> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"Додирните да бисте скенирали"</string> <string name="status_bar_work" msgid="5238641949837091056">"Пословни профил"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Режим рада у авиону"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"Нећете чути следећи аларм у <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -798,7 +797,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Пустите <xliff:g id="SONG_NAME">%1$s</xliff:g> извођача <xliff:g id="ARTIST_NAME">%2$s</xliff:g> из апликације <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Пустите <xliff:g id="SONG_NAME">%1$s</xliff:g> из апликације <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Опозови"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Приближите да бисте пуштали музику на: <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"Пушта се на: <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Неактивно. Видите апликацију"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Није пронађено"</string> @@ -886,4 +886,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Изаберите корисника"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Апликације покренуте у позадини"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Заустави"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-sr/tiles_states_strings.xml b/packages/SystemUI/res/values-sr/tiles_states_strings.xml index ab10ca1f6881..fda7465bcadf 100644 --- a/packages/SystemUI/res/values-sr/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-sr/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Искључено"</item> <item msgid="2075645297847971154">"Укључено"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"Недоступно"</item> + <item msgid="1909756493418256167">"Искључено"</item> + <item msgid="4531508423703413340">"Укључено"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"Недоступно"</item> <item msgid="9103697205127645916">"Искључено"</item> diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml index 36f4ab193bec..04cdcbe30f0c 100644 --- a/packages/SystemUI/res/values-sv/strings.xml +++ b/packages/SystemUI/res/values-sv/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Ej ansluten till wifi"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Ljusstyrka"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Färginvertering"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Färgkorrigering"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"Fler inställningar"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Användarinställningar"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Klart"</string> @@ -450,8 +449,9 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Lås upp för att använda"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Det gick inte att hämta dina kort. Försök igen senare."</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Inställningar för låsskärm"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"Skanna QR"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Klicka för att skanna en QR-kod"</string> + <string name="qr_code_scanner_title" msgid="5660820608548306581">"QR-kod"</string> + <!-- no translation found for qr_code_scanner_description (7937603775306661863) --> + <skip /> <string name="status_bar_work" msgid="5238641949837091056">"Jobbprofil"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Flygplansläge"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"Nästa alarm, kl. <xliff:g id="WHEN">%1$s</xliff:g>, kommer inte att höras"</string> @@ -792,7 +792,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Spela upp <xliff:g id="SONG_NAME">%1$s</xliff:g> med <xliff:g id="ARTIST_NAME">%2$s</xliff:g> från <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Spela upp <xliff:g id="SONG_NAME">%1$s</xliff:g> från <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Ångra"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Flytta närmare för att spela upp på <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"Spelas upp på <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Inaktiv, kolla appen"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Hittades inte"</string> @@ -880,4 +881,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Välj användare"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Appar som körs i bakgrunden"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Stoppa"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-sv/tiles_states_strings.xml b/packages/SystemUI/res/values-sv/tiles_states_strings.xml index cdcf6e6c5f41..11026981c1a0 100644 --- a/packages/SystemUI/res/values-sv/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-sv/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Av"</item> <item msgid="2075645297847971154">"På"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"Inte tillgängligt"</item> + <item msgid="1909756493418256167">"Av"</item> + <item msgid="4531508423703413340">"På"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"Inte tillgängligt"</item> <item msgid="9103697205127645916">"Av"</item> diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml index 6643348c4bdd..2618484925e6 100644 --- a/packages/SystemUI/res/values-sw/strings.xml +++ b/packages/SystemUI/res/values-sw/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi haijaunganishwa"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Ung\'avu"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Ugeuzaji rangi"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Usahihishaji wa rangirangi"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"Mipangilio zaidi"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Mipangilio ya mtumiaji"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Nimemaliza"</string> @@ -450,8 +449,10 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Fungua ili utumie"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Hitilafu imetokea wakati wa kuleta kadi zako, tafadhali jaribu tena baadaye"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Mipangilio ya kufunga skrini"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"Changanua QR"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Bofya ili uchanganue msimbo wa QR"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <!-- no translation found for qr_code_scanner_description (7937603775306661863) --> + <skip /> <string name="status_bar_work" msgid="5238641949837091056">"Wasifu wa kazini"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Hali ya ndegeni"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"Hutasikia kengele yako inayofuata ya saa <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -792,7 +793,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Cheza <xliff:g id="SONG_NAME">%1$s</xliff:g> ulioimbwa na <xliff:g id="ARTIST_NAME">%2$s</xliff:g> katika <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Cheza <xliff:g id="SONG_NAME">%1$s</xliff:g> katika <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Tendua"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Sogea karibu ili ucheze kwenye <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"Inacheza kwenye <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Haitumiki, angalia programu"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Hakipatikani"</string> @@ -880,4 +882,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Chagua mtumiaji"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Programu zinazotumika chinichini"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Simamisha"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-sw/tiles_states_strings.xml b/packages/SystemUI/res/values-sw/tiles_states_strings.xml index 563c07803d12..d186d5192855 100644 --- a/packages/SystemUI/res/values-sw/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-sw/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Kimezimwa"</item> <item msgid="2075645297847971154">"Kimewashwa"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"Haupatikani"</item> + <item msgid="1909756493418256167">"Umezimwa"</item> + <item msgid="4531508423703413340">"Umewashwa"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"Hakipatikani"</item> <item msgid="9103697205127645916">"Kimezimwa"</item> diff --git a/packages/SystemUI/res/values-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml index f721b9848b5e..e45f0721676f 100644 --- a/packages/SystemUI/res/values-ta/strings.xml +++ b/packages/SystemUI/res/values-ta/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"வைஃபை இணைக்கப்படவில்லை"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"ஒளிர்வு"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"கலர் இன்வெர்ஷன்"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"கலர் கரெக்ஷன்"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"அமைப்பில் மாற்று"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"பயனர் அமைப்புகள்"</string> <string name="quick_settings_done" msgid="2163641301648855793">"முடிந்தது"</string> @@ -450,8 +449,8 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"பயன்படுத்துவதற்கு அன்லாக் செய்க"</string> <string name="wallet_error_generic" msgid="257704570182963611">"உங்கள் கார்டுகளின் விவரங்களைப் பெறுவதில் சிக்கல் ஏற்பட்டது, பிறகு முயலவும்"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"பூட்டுத் திரை அமைப்புகள்"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"QR குறியீட்டை ஸ்கேன் செய்யுங்கள்"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"QR குறியீட்டை ஸ்கேன் செய்யக் கிளிக் செய்யவும்"</string> + <string name="qr_code_scanner_title" msgid="5660820608548306581">"QR குறியீடு"</string> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"ஸ்கேன் செய்யத் தட்டவும்"</string> <string name="status_bar_work" msgid="5238641949837091056">"பணிக் கணக்கு"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"விமானப் பயன்முறை"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"அடுத்த அலாரத்தை <xliff:g id="WHEN">%1$s</xliff:g> மணிக்கு கேட்க மாட்டீர்கள்"</string> @@ -792,7 +791,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> இன் <xliff:g id="SONG_NAME">%1$s</xliff:g> பாடலை <xliff:g id="APP_LABEL">%3$s</xliff:g> ஆப்ஸில் பிளேசெய்"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g> பாடலை <xliff:g id="APP_LABEL">%2$s</xliff:g> ஆப்ஸில் பிளேசெய்"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"செயல்தவிர்"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"<xliff:g id="DEVICENAME">%1$s</xliff:g> சாதனத்தில் பிளே செய்ய உங்கள் சாதனத்தை அருகில் எடுத்துச் செல்லவும்"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"<xliff:g id="DEVICENAME">%1$s</xliff:g> சாதனத்தில் பிளே ஆகிறது"</string> <string name="controls_error_timeout" msgid="794197289772728958">"செயலில் இல்லை , சரிபார்க்கவும்"</string> <string name="controls_error_removed" msgid="6675638069846014366">"இல்லை"</string> @@ -880,4 +880,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"பயனரைத் தேர்வுசெய்க"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"பின்னணியில் இயங்கும் ஆப்ஸ்"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"நிறுத்து"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-ta/tiles_states_strings.xml b/packages/SystemUI/res/values-ta/tiles_states_strings.xml index a9e0eabc3d8c..0883d2202dfb 100644 --- a/packages/SystemUI/res/values-ta/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-ta/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"முடக்கப்பட்டுள்ளது"</item> <item msgid="2075645297847971154">"இயக்கப்பட்டுள்ளது"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"இல்லை"</item> + <item msgid="1909756493418256167">"முடக்கப்பட்டுள்ளது"</item> + <item msgid="4531508423703413340">"இயக்கப்பட்டுள்ளது"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"கிடைக்கவில்லை"</item> <item msgid="9103697205127645916">"முடக்கப்பட்டுள்ளது"</item> diff --git a/packages/SystemUI/res/values-te/strings.xml b/packages/SystemUI/res/values-te/strings.xml index 501ee1753815..4e4102358fb1 100644 --- a/packages/SystemUI/res/values-te/strings.xml +++ b/packages/SystemUI/res/values-te/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi‑Fi కనెక్ట్ కాలేదు"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"ప్రకాశం"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"కలర్ మార్పిడి"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"కలర్ కరెక్షన్"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"మరిన్ని సెట్టింగ్లు"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"యూజర్ సెట్టింగ్లు"</string> <string name="quick_settings_done" msgid="2163641301648855793">"పూర్తయింది"</string> @@ -450,8 +449,8 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"ఉపయోగించడానికి అన్లాక్ చేయండి"</string> <string name="wallet_error_generic" msgid="257704570182963611">"మీ కార్డ్లను పొందడంలో సమస్య ఉంది, దయచేసి తర్వాత మళ్లీ ట్రై చేయండి"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"లాక్ స్క్రీన్ సెట్టింగ్లు"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"QRను స్కాన్ చేయండి"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"QR కోడ్ను స్కాన్ చేయడానికి క్లిక్ చేయండి"</string> + <string name="qr_code_scanner_title" msgid="5660820608548306581">"QR కోడ్"</string> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"స్కాన్ చేయడానికి ట్యాప్ చేయండి"</string> <string name="status_bar_work" msgid="5238641949837091056">"ఆఫీస్ ప్రొఫైల్"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"ఎయిర్ప్లేన్ మోడ్"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"మీరు <xliff:g id="WHEN">%1$s</xliff:g> సెట్ చేసిన మీ తర్వాత అలారం మీకు వినిపించదు"</string> @@ -792,7 +791,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g> నుండి <xliff:g id="ARTIST_NAME">%2$s</xliff:g> పాడిన <xliff:g id="SONG_NAME">%1$s</xliff:g>ను ప్లే చేయండి"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="APP_LABEL">%2$s</xliff:g> నుండి <xliff:g id="SONG_NAME">%1$s</xliff:g>ను ప్లే చేయండి"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"చర్య రద్దు"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"<xliff:g id="DEVICENAME">%1$s</xliff:g>లో ప్లే చేయడానికి దగ్గరగా వెళ్లండి"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"<xliff:g id="DEVICENAME">%1$s</xliff:g>లో ప్లే అవుతోంది"</string> <string name="controls_error_timeout" msgid="794197289772728958">"ఇన్యాక్టివ్, యాప్ చెక్ చేయండి"</string> <string name="controls_error_removed" msgid="6675638069846014366">"కనుగొనబడలేదు"</string> @@ -880,4 +880,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"యూజర్ను ఎంచుకోండి"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"యాప్లు బ్యాక్గ్రౌండ్లో రన్ అవుతున్నాయి"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"ఆపివేయండి"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-te/tiles_states_strings.xml b/packages/SystemUI/res/values-te/tiles_states_strings.xml index 4cb7291cafd3..5c8ae3da6d9f 100644 --- a/packages/SystemUI/res/values-te/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-te/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"ఆఫ్లో ఉంది"</item> <item msgid="2075645297847971154">"ఆన్లో ఉంది"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"అందుబాటులో లేదు"</item> + <item msgid="1909756493418256167">"ఆఫ్లో ఉంది"</item> + <item msgid="4531508423703413340">"ఆన్లో ఉంది"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"అందుబాటులో లేదు"</item> <item msgid="9103697205127645916">"ఆఫ్లో ఉంది"</item> diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml index 6b7c05f47d4e..8897186e4a10 100644 --- a/packages/SystemUI/res/values-th/strings.xml +++ b/packages/SystemUI/res/values-th/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"ไม่ได้เชื่อมต่อ Wi-Fi"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"ความสว่าง"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"การกลับสี"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"การแก้สี"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"การตั้งค่าเพิ่มเติม"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"การตั้งค่าของผู้ใช้"</string> <string name="quick_settings_done" msgid="2163641301648855793">"เสร็จสิ้น"</string> @@ -450,8 +449,8 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"ปลดล็อกเพื่อใช้"</string> <string name="wallet_error_generic" msgid="257704570182963611">"เกิดปัญหาในการดึงข้อมูลบัตรของคุณ โปรดลองอีกครั้งในภายหลัง"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"การตั้งค่าหน้าจอล็อก"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"สแกนคิวอาร์"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"คลิกเพื่อสแกนคิวอาร์โค้ด"</string> + <string name="qr_code_scanner_title" msgid="5660820608548306581">"คิวอาร์โค้ด"</string> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"แตะเพื่อสแกน"</string> <string name="status_bar_work" msgid="5238641949837091056">"โปรไฟล์งาน"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"โหมดบนเครื่องบิน"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"คุณจะไม่ได้ยินเสียงปลุกครั้งถัดไปในเวลา <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -792,7 +791,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"เปิดเพลง <xliff:g id="SONG_NAME">%1$s</xliff:g> ของ <xliff:g id="ARTIST_NAME">%2$s</xliff:g> จาก <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"เปิดเพลง <xliff:g id="SONG_NAME">%1$s</xliff:g> จาก <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"เลิกทำ"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"ขยับเข้ามาใกล้ขึ้นเพื่อเล่นใน <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"กำลังเล่นใน <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"ไม่มีการใช้งาน โปรดตรวจสอบแอป"</string> <string name="controls_error_removed" msgid="6675638069846014366">"ไม่พบ"</string> @@ -880,4 +880,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"เลือกผู้ใช้"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"แอปที่ทำงานอยู่เบื้องหลัง"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"หยุด"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-th/tiles_states_strings.xml b/packages/SystemUI/res/values-th/tiles_states_strings.xml index 67768736afd9..e7eae732ba46 100644 --- a/packages/SystemUI/res/values-th/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-th/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"ปิด"</item> <item msgid="2075645297847971154">"เปิด"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"ไม่พร้อมใช้งาน"</item> + <item msgid="1909756493418256167">"ปิด"</item> + <item msgid="4531508423703413340">"เปิด"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"ไม่พร้อมใช้งาน"</item> <item msgid="9103697205127645916">"ปิด"</item> diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml index df546587e5b9..6ab6ef661ab4 100644 --- a/packages/SystemUI/res/values-tl/strings.xml +++ b/packages/SystemUI/res/values-tl/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Hindi nakakonekta sa Wi-Fi"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Brightness"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Pag-invert ng kulay"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Pagtatama ng kulay"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"Higit pang setting"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Mga setting ng user"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Tapos na"</string> @@ -450,8 +449,10 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"I-unlock para magamit"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Nagkaproblema sa pagkuha ng iyong mga card, pakisubukan ulit sa ibang pagkakataon"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Mga setting ng lock screen"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"I-scan ang QR"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Mag-click para mag-scan ng QR code"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <!-- no translation found for qr_code_scanner_description (7937603775306661863) --> + <skip /> <string name="status_bar_work" msgid="5238641949837091056">"Profile sa trabaho"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Airplane mode"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"Hindi mo maririnig ang iyong susunod na alarm ng <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -792,7 +793,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"I-play ang <xliff:g id="SONG_NAME">%1$s</xliff:g> ni/ng <xliff:g id="ARTIST_NAME">%2$s</xliff:g> mula sa <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"I-play ang <xliff:g id="SONG_NAME">%1$s</xliff:g> mula sa <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"I-undo"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Lumapit pa para mag-play sa <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"Nagpe-play sa <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Hindi aktibo, tingnan ang app"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Hindi nahanap"</string> @@ -880,4 +882,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Pumili ng user"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Mga app na tumatakbo sa background"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Ihinto"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-tl/tiles_states_strings.xml b/packages/SystemUI/res/values-tl/tiles_states_strings.xml index 62172a4dc907..f33d8a2c3180 100644 --- a/packages/SystemUI/res/values-tl/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-tl/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Naka-off"</item> <item msgid="2075645297847971154">"Naka-on"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"Hindi available"</item> + <item msgid="1909756493418256167">"Naka-off"</item> + <item msgid="4531508423703413340">"Naka-on"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"Hindi available"</item> <item msgid="9103697205127645916">"Naka-off"</item> diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml index f389bf17de5a..ee44b76da88d 100644 --- a/packages/SystemUI/res/values-tr/strings.xml +++ b/packages/SystemUI/res/values-tr/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Kablosuz ağ bağlı değil"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Parlaklık"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Rengi ters çevirme"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Renk düzeltme"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"Diğer ayarlar"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Kullanıcı ayarları"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Bitti"</string> @@ -450,8 +449,10 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Kullanmak için kilidi aç"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Kartlarınız alınırken bir sorun oluştu. Lütfen daha sonra tekrar deneyin"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Kilit ekranı ayarları"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"QR kodunu tarayın"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"QR kodu taramak için tıklayın"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <!-- no translation found for qr_code_scanner_description (7937603775306661863) --> + <skip /> <string name="status_bar_work" msgid="5238641949837091056">"İş profili"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Uçak modu"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"<xliff:g id="WHEN">%1$s</xliff:g> olarak ayarlanmış bir sonraki alarmınızı duymayacaksınız"</string> @@ -792,7 +793,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g> uygulamasından <xliff:g id="ARTIST_NAME">%2$s</xliff:g>, <xliff:g id="SONG_NAME">%1$s</xliff:g> şarkısını çal"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="APP_LABEL">%2$s</xliff:g> uygulamasından <xliff:g id="SONG_NAME">%1$s</xliff:g> şarkısını çal"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Geri al"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"<xliff:g id="DEVICENAME">%1$s</xliff:g> cihazında çalmak için yaklaşın"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"<xliff:g id="DEVICENAME">%1$s</xliff:g> cihazında çalınıyor"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Devre dışı, uygulamaya bakın"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Bulunamadı"</string> @@ -880,4 +882,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Kullanıcı seçin"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Arka planda çalışan uygulamalar"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Durdur"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-tr/tiles_states_strings.xml b/packages/SystemUI/res/values-tr/tiles_states_strings.xml index 9f836fc6bc2d..17b4bb4e1a44 100644 --- a/packages/SystemUI/res/values-tr/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-tr/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Kapalı"</item> <item msgid="2075645297847971154">"Açık"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"Bilinmiyor"</item> + <item msgid="1909756493418256167">"Kapalı"</item> + <item msgid="4531508423703413340">"Açık"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"Kullanılamıyor"</item> <item msgid="9103697205127645916">"Kapalı"</item> diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml index 3856cc5fbaf3..27aad2edfdb8 100644 --- a/packages/SystemUI/res/values-uk/strings.xml +++ b/packages/SystemUI/res/values-uk/strings.xml @@ -235,8 +235,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi не під’єднано"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Яскравість"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Інверсія кольорів"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Корекція кольору"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"Більше налаштувань"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Налаштування користувача"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Готово"</string> @@ -456,8 +455,10 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Розблокувати, щоб використовувати"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Не вдалось отримати ваші картки. Повторіть спробу пізніше."</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Параметри блокування екрана"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"Сканувати QR-код"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Натисніть, щоб відсканувати QR-код"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <!-- no translation found for qr_code_scanner_description (7937603775306661863) --> + <skip /> <string name="status_bar_work" msgid="5238641949837091056">"Робочий профіль"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Режим польоту"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"Наступний сигнал о <xliff:g id="WHEN">%1$s</xliff:g> не пролунає"</string> @@ -804,7 +805,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Увімкнути пісню \"<xliff:g id="SONG_NAME">%1$s</xliff:g>\", яку виконує <xliff:g id="ARTIST_NAME">%2$s</xliff:g>, у додатку <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Увімкнути пісню \"<xliff:g id="SONG_NAME">%1$s</xliff:g>\" у додатку <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Відмінити"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Щоб відтворити на пристрої <xliff:g id="DEVICENAME">%1$s</xliff:g>, наблизьтеся до нього"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"Відтворюється на пристрої <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Неактивно, перейдіть у додаток"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Не знайдено"</string> @@ -892,4 +894,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Виберіть користувача"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Додатки, що працюють у фоновому режимі"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Зупинити"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-uk/tiles_states_strings.xml b/packages/SystemUI/res/values-uk/tiles_states_strings.xml index 34c40d3d2951..c4ac1949a4c6 100644 --- a/packages/SystemUI/res/values-uk/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-uk/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Вимкнено"</item> <item msgid="2075645297847971154">"Увімкнено"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"Недоступно"</item> + <item msgid="1909756493418256167">"Вимкнено"</item> + <item msgid="4531508423703413340">"Увімкнено"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"Недоступно"</item> <item msgid="9103697205127645916">"Вимкнено"</item> diff --git a/packages/SystemUI/res/values-ur/strings.xml b/packages/SystemUI/res/values-ur/strings.xml index dafaddf1bd66..015d52072f01 100644 --- a/packages/SystemUI/res/values-ur/strings.xml +++ b/packages/SystemUI/res/values-ur/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi سے منسلک نہیں ہے"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"چمکیلا پن"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"رنگوں کی تقلیب"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"رنگ کی اصلاح"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"مزید ترتیبات"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"صارف کی ترتیبات"</string> <string name="quick_settings_done" msgid="2163641301648855793">"ہو گیا"</string> @@ -450,8 +449,8 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"استعمال کرنے کے لیے غیر مقفل کریں"</string> <string name="wallet_error_generic" msgid="257704570182963611">"آپ کے کارڈز حاصل کرنے میں ایک مسئلہ درپیش تھا، براہ کرم بعد میں دوبارہ کوشش کریں"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"مقفل اسکرین کی ترتیبات"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"QR اسکین کریں"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"QR کوڈ اسکین کرنے کے لیے کلک کریں"</string> + <string name="qr_code_scanner_title" msgid="5660820608548306581">"QR کوڈ"</string> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"اسکین کرنے کے لیے تھپتھپائیں"</string> <string name="status_bar_work" msgid="5238641949837091056">"دفتری پروفائل"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"ہوائی جہاز وضع"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"آپ کو <xliff:g id="WHEN">%1$s</xliff:g> بجے اپنا اگلا الارم سنائی نہیں دے گا"</string> @@ -792,7 +791,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g> سے <xliff:g id="ARTIST_NAME">%2$s</xliff:g> کا <xliff:g id="SONG_NAME">%1$s</xliff:g> چلائیں"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="APP_LABEL">%2$s</xliff:g> سے <xliff:g id="SONG_NAME">%1$s</xliff:g> چلائیں"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"کالعدم کریں"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"<xliff:g id="DEVICENAME">%1$s</xliff:g> پر چلانے کے لیے قریب کریں"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"<xliff:g id="DEVICENAME">%1$s</xliff:g> پر چل رہا ہے"</string> <string name="controls_error_timeout" msgid="794197289772728958">"غیر فعال، ایپ چیک کریں"</string> <string name="controls_error_removed" msgid="6675638069846014366">"نہیں ملا"</string> @@ -880,4 +880,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"صارف منتخب کریں"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"ایپس پس منظر میں چل رہی ہیں"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"روکیں"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-ur/tiles_states_strings.xml b/packages/SystemUI/res/values-ur/tiles_states_strings.xml index 02943fa8e272..155403151ed7 100644 --- a/packages/SystemUI/res/values-ur/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-ur/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"آف ہے"</item> <item msgid="2075645297847971154">"آن ہے"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"غیر دستیاب"</item> + <item msgid="1909756493418256167">"آف"</item> + <item msgid="4531508423703413340">"آن"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"دستیاب نہیں ہے"</item> <item msgid="9103697205127645916">"آف ہے"</item> diff --git a/packages/SystemUI/res/values-uz/strings.xml b/packages/SystemUI/res/values-uz/strings.xml index 13971dd28cf2..ce7fc3245925 100644 --- a/packages/SystemUI/res/values-uz/strings.xml +++ b/packages/SystemUI/res/values-uz/strings.xml @@ -449,8 +449,10 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Foydalanish uchun qulfdan chiqarish"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Bildirgilarni yuklashda xatolik yuz berdi, keyinroq qaytadan urining"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Qulflangan ekran sozlamalari"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"QR kodni skanerlash"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"QR kodni skanerlash uchun bosing"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <!-- no translation found for qr_code_scanner_description (7937603775306661863) --> + <skip /> <string name="status_bar_work" msgid="5238641949837091056">"Ish profili"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Parvoz rejimi"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"Keyingi signal (<xliff:g id="WHEN">%1$s</xliff:g>) chalinmaydi"</string> @@ -791,7 +793,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g> ilovasida ijro etish: <xliff:g id="SONG_NAME">%1$s</xliff:g> – <xliff:g id="ARTIST_NAME">%2$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="APP_LABEL">%2$s</xliff:g> ilovasida ijro etilmoqda: <xliff:g id="SONG_NAME">%1$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Qaytarish"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"<xliff:g id="DEVICENAME">%1$s</xliff:g>da ijro etish uchun yaqinroq keling"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"<xliff:g id="DEVICENAME">%1$s</xliff:g> qurilmasida ijro qilinmoqda"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Nofaol. Ilovani tekshiring"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Topilmadi"</string> @@ -879,4 +882,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Foydalanuvchini tanlang"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Orqa fonda ishlayotgan ilovalar"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Stop"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml index 4d2c90c35216..e4066d9ec47e 100644 --- a/packages/SystemUI/res/values-vi/strings.xml +++ b/packages/SystemUI/res/values-vi/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Chưa kết nối với Wi-Fi"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Độ sáng"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Đảo màu"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Chỉnh màu"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"Chế độ cài đặt khác"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Cài đặt người dùng"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Xong"</string> @@ -450,8 +449,10 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Mở khóa để sử dụng"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Đã xảy ra sự cố khi tải thẻ của bạn. Vui lòng thử lại sau"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Cài đặt màn hình khóa"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"Quét mã QR"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Nhấp để quét mã QR"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <!-- no translation found for qr_code_scanner_description (7937603775306661863) --> + <skip /> <string name="status_bar_work" msgid="5238641949837091056">"Hồ sơ công việc"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Chế độ máy bay"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"Bạn sẽ không nghe thấy báo thức tiếp theo lúc <xliff:g id="WHEN">%1$s</xliff:g> của mình"</string> @@ -792,7 +793,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Phát <xliff:g id="SONG_NAME">%1$s</xliff:g> của <xliff:g id="ARTIST_NAME">%2$s</xliff:g> trên <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Phát <xliff:g id="SONG_NAME">%1$s</xliff:g> trên <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Hủy"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Đưa thiết bị đến gần hơn để phát trên <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"Đang phát trên <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Không hoạt động, hãy kiểm tra ứng dụng"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Không tìm thấy"</string> @@ -880,4 +882,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Chọn người dùng"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Các ứng dụng chạy trong nền"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Dừng"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-vi/tiles_states_strings.xml b/packages/SystemUI/res/values-vi/tiles_states_strings.xml index bff64ab6b6d3..94e801278637 100644 --- a/packages/SystemUI/res/values-vi/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-vi/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Đang tắt"</item> <item msgid="2075645297847971154">"Đang bật"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"Không có"</item> + <item msgid="1909756493418256167">"Đang tắt"</item> + <item msgid="4531508423703413340">"Đang bật"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"Không hoạt động"</item> <item msgid="9103697205127645916">"Đang tắt"</item> diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml index 038ada0bec29..4666a882f8a3 100644 --- a/packages/SystemUI/res/values-zh-rCN/strings.xml +++ b/packages/SystemUI/res/values-zh-rCN/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"未连接到 WLAN 网络"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"亮度"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"颜色反转"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"色彩校正"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"更多设置"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"用户设置"</string> <string name="quick_settings_done" msgid="2163641301648855793">"完成"</string> @@ -450,8 +449,8 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"解锁设备即可使用"</string> <string name="wallet_error_generic" msgid="257704570182963611">"获取您的卡片时出现问题,请稍后重试"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"锁定屏幕设置"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"扫描二维码"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"点击即可扫描二维码"</string> + <string name="qr_code_scanner_title" msgid="5660820608548306581">"二维码"</string> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"点按即可扫描"</string> <string name="status_bar_work" msgid="5238641949837091056">"工作资料"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"飞行模式"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"您在<xliff:g id="WHEN">%1$s</xliff:g>将不会听到下次闹钟响铃"</string> @@ -792,7 +791,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"通过<xliff:g id="APP_LABEL">%3$s</xliff:g>播放<xliff:g id="ARTIST_NAME">%2$s</xliff:g>的《<xliff:g id="SONG_NAME">%1$s</xliff:g>》"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"通过<xliff:g id="APP_LABEL">%2$s</xliff:g>播放《<xliff:g id="SONG_NAME">%1$s</xliff:g>》"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"撤消"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"移近一点以在“<xliff:g id="DEVICENAME">%1$s</xliff:g>”上播放"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"正在“<xliff:g id="DEVICENAME">%1$s</xliff:g>”上播放"</string> <string name="controls_error_timeout" msgid="794197289772728958">"无效,请检查应用"</string> <string name="controls_error_removed" msgid="6675638069846014366">"未找到"</string> @@ -880,4 +880,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"选择用户"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"正在在后台运行的应用"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"停止"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-zh-rCN/tiles_states_strings.xml b/packages/SystemUI/res/values-zh-rCN/tiles_states_strings.xml index 0d72f61092d0..a266d929ec3f 100644 --- a/packages/SystemUI/res/values-zh-rCN/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-zh-rCN/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"已关闭"</item> <item msgid="2075645297847971154">"已开启"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"不可用"</item> + <item msgid="1909756493418256167">"关闭"</item> + <item msgid="4531508423703413340">"开启"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"不可用"</item> <item msgid="9103697205127645916">"已关闭"</item> diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml index d4406b4b111b..f64e78cab621 100644 --- a/packages/SystemUI/res/values-zh-rHK/strings.xml +++ b/packages/SystemUI/res/values-zh-rHK/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"未連線至 Wi-Fi"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"亮度"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"色彩反轉"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"色彩校正"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"更多設定"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"使用者設定"</string> <string name="quick_settings_done" msgid="2163641301648855793">"完成"</string> @@ -450,8 +449,8 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"解鎖即可使用"</string> <string name="wallet_error_generic" msgid="257704570182963611">"擷取資訊卡時發生問題,請稍後再試。"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"上鎖畫面設定"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"掃瞄 QR 碼"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"按一下即可掃瞄 QR 碼"</string> + <string name="qr_code_scanner_title" msgid="5660820608548306581">"QR 碼"</string> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"輕按即可掃瞄"</string> <string name="status_bar_work" msgid="5238641949837091056">"工作設定檔"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"飛行模式"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"您不會<xliff:g id="WHEN">%1$s</xliff:g>聽到鬧鐘"</string> @@ -792,7 +791,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"在 <xliff:g id="APP_LABEL">%3$s</xliff:g> 播放 <xliff:g id="ARTIST_NAME">%2$s</xliff:g> 的《<xliff:g id="SONG_NAME">%1$s</xliff:g>》"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"在 <xliff:g id="APP_LABEL">%2$s</xliff:g> 播放《<xliff:g id="SONG_NAME">%1$s</xliff:g>》"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"復原"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"移近一點以在 <xliff:g id="DEVICENAME">%1$s</xliff:g> 上播放"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"正在 <xliff:g id="DEVICENAME">%1$s</xliff:g> 上播放"</string> <string name="controls_error_timeout" msgid="794197289772728958">"已停用,請檢查應用程式"</string> <string name="controls_error_removed" msgid="6675638069846014366">"找不到"</string> @@ -880,4 +880,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"選取使用者"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"正在背景中執行的應用程式"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"停止"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-zh-rHK/tiles_states_strings.xml b/packages/SystemUI/res/values-zh-rHK/tiles_states_strings.xml index 571cfba728a4..d5d092f3067d 100644 --- a/packages/SystemUI/res/values-zh-rHK/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-zh-rHK/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"已關閉"</item> <item msgid="2075645297847971154">"已開啟"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"無法使用"</item> + <item msgid="1909756493418256167">"關閉"</item> + <item msgid="4531508423703413340">"開啟"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"無法使用"</item> <item msgid="9103697205127645916">"已關閉"</item> diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml index 83623d434a27..87a6ae828383 100644 --- a/packages/SystemUI/res/values-zh-rTW/strings.xml +++ b/packages/SystemUI/res/values-zh-rTW/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"未連線至 Wi-Fi"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"亮度"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"色彩反轉"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"色彩校正"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"更多設定"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"使用者設定"</string> <string name="quick_settings_done" msgid="2163641301648855793">"完成"</string> @@ -450,8 +449,9 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"解鎖即可使用"</string> <string name="wallet_error_generic" msgid="257704570182963611">"擷取卡片時發生問題,請稍後再試"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"螢幕鎖定設定"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"掃描 QR 圖碼"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"按一下即可掃描 QR 圖碼"</string> + <string name="qr_code_scanner_title" msgid="5660820608548306581">"QR 圖碼"</string> + <!-- no translation found for qr_code_scanner_description (7937603775306661863) --> + <skip /> <string name="status_bar_work" msgid="5238641949837091056">"工作資料夾"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"飛航模式"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"你不會聽到下一個<xliff:g id="WHEN">%1$s</xliff:g> 的鬧鐘"</string> @@ -792,7 +792,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"透過「<xliff:g id="APP_LABEL">%3$s</xliff:g>」播放<xliff:g id="ARTIST_NAME">%2$s</xliff:g>的〈<xliff:g id="SONG_NAME">%1$s</xliff:g>〉"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"透過「<xliff:g id="APP_LABEL">%2$s</xliff:g>」播放〈<xliff:g id="SONG_NAME">%1$s</xliff:g>〉"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"復原"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"如要在「<xliff:g id="DEVICENAME">%1$s</xliff:g>」上播放,請靠近這部裝置"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"正在「<xliff:g id="DEVICENAME">%1$s</xliff:g>」上播放"</string> <string name="controls_error_timeout" msgid="794197289772728958">"無效,請查看應用程式"</string> <string name="controls_error_removed" msgid="6675638069846014366">"找不到控制項"</string> @@ -880,4 +881,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"選取使用者"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"目前在背景執行的應用程式"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"停止"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-zh-rTW/tiles_states_strings.xml b/packages/SystemUI/res/values-zh-rTW/tiles_states_strings.xml index 48896579101a..ad2441344256 100644 --- a/packages/SystemUI/res/values-zh-rTW/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-zh-rTW/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"已關閉"</item> <item msgid="2075645297847971154">"已開啟"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"無法使用"</item> + <item msgid="1909756493418256167">"已關閉"</item> + <item msgid="4531508423703413340">"已開啟"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"無法使用"</item> <item msgid="9103697205127645916">"已關閉"</item> diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml index fa200dcefaa2..602845b36c3d 100644 --- a/packages/SystemUI/res/values-zu/strings.xml +++ b/packages/SystemUI/res/values-zu/strings.xml @@ -233,8 +233,7 @@ <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"I-Wi-Fi ayixhunyiwe"</string> <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Ukugqama"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Ukuguqulwa kombala"</string> - <!-- no translation found for quick_settings_color_correction_label (5636617913560474664) --> - <skip /> + <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Ukulungiswa kombala"</string> <string name="quick_settings_more_settings" msgid="2878235926753776694">"Izilungiselelo eziningi"</string> <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Amasethingi womsebenzisi"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Kwenziwe"</string> @@ -450,8 +449,9 @@ <string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Vula ukuze usebenzise"</string> <string name="wallet_error_generic" msgid="257704570182963611">"Kube khona inkinga yokuthola amakhadi akho, sicela uzame futhi ngemuva kwesikhathi"</string> <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Amasethingi okukhiya isikrini"</string> - <string name="qr_code_scanner_title" msgid="1598912458255252498">"Skena i-QR"</string> - <string name="qr_code_scanner_description" msgid="7452098243938659945">"Chofoza ukuze uskene ikhodi ye-QR"</string> + <!-- no translation found for qr_code_scanner_title (5660820608548306581) --> + <skip /> + <string name="qr_code_scanner_description" msgid="7937603775306661863">"Thepha ukuze uskene"</string> <string name="status_bar_work" msgid="5238641949837091056">"Iphrofayela yomsebenzi"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Imodi yendiza"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"Ngeke uzwe i-alamu yakho elandelayo ngo-<xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -792,7 +792,8 @@ <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Dlala i-<xliff:g id="SONG_NAME">%1$s</xliff:g> ka-<xliff:g id="ARTIST_NAME">%2$s</xliff:g> kusuka ku-<xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Dlala i-<xliff:g id="SONG_NAME">%1$s</xliff:g> kusuka ku-<xliff:g id="APP_LABEL">%2$s</xliff:g>"</string> <string name="media_transfer_undo" msgid="1895606387620728736">"Hlehlisa"</string> - <string name="media_move_closer_to_transfer" msgid="1628426856415585141">"Sondeza eduze ukudlala ku-<xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> + <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) --> + <skip /> <string name="media_transfer_playing" msgid="3760048096352107789">"Idlala ku-<xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Akusebenzi, hlola uhlelo lokusebenza"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Ayitholakali"</string> @@ -880,4 +881,8 @@ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Khetha umsebenzisi"</string> <string name="fgs_manager_dialog_title" msgid="656091833603337197">"Ama-app ayaqhubeka ngemuva"</string> <string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Misa"</string> + <!-- no translation found for clipboard_edit_text_copy (770856373439969178) --> + <skip /> + <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-zu/tiles_states_strings.xml b/packages/SystemUI/res/values-zu/tiles_states_strings.xml index ea40faf4eb56..92290d6d1a0c 100644 --- a/packages/SystemUI/res/values-zu/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-zu/tiles_states_strings.xml @@ -86,9 +86,11 @@ <item msgid="5715725170633593906">"Valiwe"</item> <item msgid="2075645297847971154">"Vuliwe"</item> </string-array> - <!-- no translation found for tile_states_color_correction:0 (2840507878437297682) --> - <!-- no translation found for tile_states_color_correction:1 (1909756493418256167) --> - <!-- no translation found for tile_states_color_correction:2 (4531508423703413340) --> + <string-array name="tile_states_color_correction"> + <item msgid="2840507878437297682">"Akutholakali"</item> + <item msgid="1909756493418256167">"Valiwe"</item> + <item msgid="4531508423703413340">"Vuliwe"</item> + </string-array> <string-array name="tile_states_inversion"> <item msgid="3638187931191394628">"Akutholakali"</item> <item msgid="9103697205127645916">"Valiwe"</item> diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml index fc28f0976013..461a598e9341 100644 --- a/packages/SystemUI/res/values/colors.xml +++ b/packages/SystemUI/res/values/colors.xml @@ -23,7 +23,6 @@ <color name="system_bar_background_transparent">#00000000</color> <color name="qs_tile_divider">#29ffffff</color><!-- 16% white --> <color name="qs_detail_button_white">#B3FFFFFF</color><!-- 70% white --> - <color name="qs_detail_transition">#66FFFFFF</color> <color name="status_bar_clock_color">#FFFFFFFF</color> <color name="qs_tile_disabled_color">#9E9E9E</color> <!-- 38% black --> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 65f22b805d4e..fc2756ecc8e5 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -716,22 +716,6 @@ <!-- Flag to enable privacy dot views, it shall be true for normal case --> <bool name="config_enablePrivacyDot">true</bool> - <!-- The positions widgets can be in defined as View.Gravity constants --> - <integer-array name="config_dreamComplicationPositions"> - </integer-array> - - <!-- Widget components to show as dream complications --> - <string-array name="config_dreamAppWidgetComplications" translatable="false"> - </string-array> - - <!-- Width percentage of dream complications --> - <item name="config_dreamComplicationWidthPercent" translatable="false" format="float" - type="dimen">0.33</item> - - <!-- Height percentage of dream complications --> - <item name="config_dreamComplicationHeightPercent" translatable="false" format="float" - type="dimen">0.25</item> - <!-- Flag to enable dream overlay service and its registration --> <bool name="config_dreamOverlayServiceEnabled">false</bool> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index b12db5d5f7c2..ceaacfc1271b 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -984,10 +984,11 @@ <!-- Media tap-to-transfer chip for sender device --> <dimen name="media_ttt_chip_outer_padding">16dp</dimen> <dimen name="media_ttt_text_size">16sp</dimen> - <dimen name="media_ttt_icon_size">24dp</dimen> - <dimen name="media_ttt_loading_size">20dp</dimen> + <dimen name="media_ttt_app_icon_size">24dp</dimen> + <dimen name="media_ttt_status_icon_size">20dp</dimen> <dimen name="media_ttt_undo_button_vertical_padding">8dp</dimen> <dimen name="media_ttt_undo_button_vertical_negative_margin">-8dp</dimen> + <dimen name="media_ttt_last_item_start_margin">12dp</dimen> <!-- Media tap-to-transfer chip for receiver device --> <dimen name="media_ttt_chip_size_receiver">100dp</dimen> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 08fb2c66e043..41d5735c8d80 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -670,6 +670,10 @@ <string name="quick_settings_dark_mode_secondary_label_on_at">On at <xliff:g id="time" example="10 pm">%s</xliff:g></string> <!-- QuickSettings: Secondary text for when the Dark theme or some other tile will be on until some user-selected time. [CHAR LIMIT=20] --> <string name="quick_settings_dark_mode_secondary_label_until">Until <xliff:g id="time" example="7 am">%s</xliff:g></string> + <!-- QuickSettings: Secondary text for when the Dark theme will be enabled at bedtime. [CHAR LIMIT=40] --> + <string name="quick_settings_dark_mode_secondary_label_on_at_bedtime">On at bedtime</string> + <!-- QuickSettings: Secondary text for when the Dark theme or some other tile will be on until bedtime ends. [CHAR LIMIT=40] --> + <string name="quick_settings_dark_mode_secondary_label_until_bedtime_ends">Until bedtime ends</string> <!-- QuickSettings: NFC tile [CHAR LIMIT=NONE] --> <string name="quick_settings_nfc_label">NFC</string> @@ -2154,8 +2158,14 @@ <string name="media_transfer_undo">Undo</string> <!-- Text to ask the user to move their device closer to a different device (deviceName) in order to play media on the different device. [CHAR LIMIT=75] --> <string name="media_move_closer_to_start_cast">Move closer to play on <xliff:g id="deviceName" example="My Tablet">%1$s</xliff:g></string> + <!-- Text to ask the user to move their device closer to a different device (deviceName) in order to transfer media from the different device and back onto the current device. [CHAR LIMIT=75] --> + <string name="media_move_closer_to_end_cast">Move closer to <xliff:g id="deviceName" example="My Tablet">%1$s</xliff:g> to play here</string> <!-- Text informing the user that their media is now playing on a different device (deviceName). [CHAR LIMIT=50] --> - <string name="media_transfer_playing">Playing on <xliff:g id="deviceName" example="My Tablet">%1$s</xliff:g></string> + <string name="media_transfer_playing_different_device">Playing on <xliff:g id="deviceName" example="My Tablet">%1$s</xliff:g></string> + <!-- Text informing the user that their media is now playing on this device. [CHAR LIMIT=50] --> + <string name="media_transfer_playing_this_device">Playing on this phone</string> + <!-- Text informing the user that the media transfer has failed because something went wrong. [CHAR LIMIT=50] --> + <string name="media_transfer_failed">Something went wrong</string> <!-- Error message indicating that a control timed out while waiting for an update [CHAR_LIMIT=30] --> <string name="controls_error_timeout">Inactive, check app</string> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/IDeviceSenderCallback.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/IDeviceSenderCallback.aidl deleted file mode 100644 index 484791df053e..000000000000 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/IDeviceSenderCallback.aidl +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.shared.mediattt; - -import android.media.MediaRoute2Info; -import com.android.systemui.shared.mediattt.DeviceInfo; - -/** - * A callback interface that can be invoked to trigger media transfer events on System UI. - * - * This interface is for the *sender* device, which is the device currently playing media. This - * sender device can transfer the media to a different device, called the receiver. - * - * System UI will implement this interface and other services will invoke it. - */ -interface IDeviceSenderCallback { - /** - * Invoke to notify System UI that this device (the sender) is close to a receiver device, so - * the user can potentially *start* a cast to the receiver device if the user moves their device - * a bit closer. - * - * Important notes: - * - When this callback triggers, the device is close enough to inform the user that - * transferring is an option, but the device is *not* close enough to actually initiate a - * transfer yet. - * - This callback is for *starting* a cast. It should be used when this device is currently - * playing media locally and the media should be transferred to be played on the receiver - * device instead. - */ - oneway void closeToReceiverToStartCast( - in MediaRoute2Info mediaInfo, in DeviceInfo otherDeviceInfo); -} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/IDeviceSenderService.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/IDeviceSenderService.aidl new file mode 100644 index 000000000000..eb1c9d058e20 --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/IDeviceSenderService.aidl @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.shared.mediattt; + +import android.media.MediaRoute2Info; +import com.android.systemui.shared.mediattt.DeviceInfo; +import com.android.systemui.shared.mediattt.IUndoTransferCallback; + +/** + * An interface that can be invoked to trigger media transfer events on System UI. + * + * This interface is for the *sender* device, which is the device currently playing media. This + * sender device can transfer the media to a different device, called the receiver. + * + * System UI will implement this interface and other services will invoke it. + */ +interface IDeviceSenderService { + /** + * Invoke to notify System UI that this device (the sender) is close to a receiver device, so + * the user can potentially *start* a cast to the receiver device if the user moves their device + * a bit closer. + * + * Important notes: + * - When this callback triggers, the device is close enough to inform the user that + * transferring is an option, but the device is *not* close enough to actually initiate a + * transfer yet. + * - This callback is for *starting* a cast. It should be used when this device is currently + * playing media locally and the media should be transferred to be played on the receiver + * device instead. + */ + oneway void closeToReceiverToStartCast( + in MediaRoute2Info mediaInfo, in DeviceInfo otherDeviceInfo); + + /** + * Invoke to notify System UI that this device (the sender) is close to a receiver device, so + * the user can potentially *end* a cast on the receiver device if the user moves this device a + * bit closer. + * + * Important notes: + * - When this callback triggers, the device is close enough to inform the user that + * transferring is an option, but the device is *not* close enough to actually initiate a + * transfer yet. + * - This callback is for *ending* a cast. It should be used when media is currently being + * played on the receiver device and the media should be transferred to play locally + * instead. + */ + oneway void closeToReceiverToEndCast( + in MediaRoute2Info mediaInfo, in DeviceInfo otherDeviceInfo); + + /** + * Invoke to notify System UI that a media transfer from this device (the sender) to a receiver + * device has been started. + * + * Important notes: + * - This callback is for *starting* a cast. It should be used when this device is currently + * playing media locally and the media has started being transferred to the receiver device + * instead. + */ + oneway void transferToReceiverTriggered( + in MediaRoute2Info mediaInfo, in DeviceInfo otherDeviceInfo); + + /** + * Invoke to notify System UI that a media transfer from the receiver and back to this device + * (the sender) has been started. + * + * Important notes: + * - This callback is for *ending* a cast. It should be used when media is currently being + * played on the receiver device and the media has started being transferred to play locally + * instead. + */ + oneway void transferToThisDeviceTriggered( + in MediaRoute2Info mediaInfo, in DeviceInfo otherDeviceInfo); + + /** + * Invoke to notify System UI that a media transfer from this device (the sender) to a receiver + * device has finished successfully. + * + * Important notes: + * - This callback is for *starting* a cast. It should be used when this device had previously + * been playing media locally and the media has successfully been transferred to the + * receiver device instead. + * + * @param undoCallback will be invoked if the user chooses to undo this transfer. + */ + oneway void transferToReceiverSucceeded( + in MediaRoute2Info mediaInfo, + in DeviceInfo otherDeviceInfo, + in IUndoTransferCallback undoCallback); + + /** + * Invoke to notify System UI that a media transfer from the receiver and back to this device + * (the sender) has finished successfully. + * + * Important notes: + * - This callback is for *ending* a cast. It should be used when media was previously being + * played on the receiver device and has been successfully transferred to play locally on + * this device instead. + * + * @param undoCallback will be invoked if the user chooses to undo this transfer. + */ + oneway void transferToThisDeviceSucceeded( + in MediaRoute2Info mediaInfo, + in DeviceInfo otherDeviceInfo, + in IUndoTransferCallback undoCallback); + + /** + * Invoke to notify System UI that the attempted transfer has failed. + * + * This callback will be used for both the transfer that should've *started* playing the media + * on the receiver and the transfer that should've *ended* the playing on the receiver. + */ + oneway void transferFailed(in MediaRoute2Info mediaInfo, in DeviceInfo otherDeviceInfo); + + /** + * Invoke to notify System UI that this device is no longer close to the receiver device. + */ + oneway void noLongerCloseToReceiver( + in MediaRoute2Info mediaInfo, in DeviceInfo otherDeviceInfo); +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/IUndoTransferCallback.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/IUndoTransferCallback.aidl new file mode 100644 index 000000000000..b47be8736d23 --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/IUndoTransferCallback.aidl @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.shared.mediattt; + +/** + * An interface that will be invoked by System UI if the user choose to undo a transfer. + * + * Other services will implement this interface and System UI will invoke it. + */ +interface IUndoTransferCallback { + + /** + * Invoked by SystemUI when the user requests to undo the media transfer that just occurred. + * + * Implementors of this method are repsonsible for actually undoing the transfer. + */ + oneway void onUndoTriggered(); +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/navigationbar/RegionSamplingHelper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/navigationbar/RegionSamplingHelper.java index 1d2caf9ab545..6345d113faed 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/navigationbar/RegionSamplingHelper.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/navigationbar/RegionSamplingHelper.java @@ -275,23 +275,27 @@ public class RegionSamplingHelper implements View.OnAttachStateChangeListener, } public void dump(PrintWriter pw) { - pw.println("RegionSamplingHelper:"); - pw.println(" sampleView isAttached: " + mSampledView.isAttachedToWindow()); - pw.println(" sampleView isScValid: " + (mSampledView.isAttachedToWindow() + dump("", pw); + } + + public void dump(String prefix, PrintWriter pw) { + pw.println(prefix + "RegionSamplingHelper:"); + pw.println(prefix + "\tsampleView isAttached: " + mSampledView.isAttachedToWindow()); + pw.println(prefix + "\tsampleView isScValid: " + (mSampledView.isAttachedToWindow() ? mSampledView.getViewRootImpl().getSurfaceControl().isValid() : "notAttached")); - pw.println(" mSamplingEnabled: " + mSamplingEnabled); - pw.println(" mSamplingListenerRegistered: " + mSamplingListenerRegistered); - pw.println(" mSamplingRequestBounds: " + mSamplingRequestBounds); - pw.println(" mRegisteredSamplingBounds: " + mRegisteredSamplingBounds); - pw.println(" mLastMedianLuma: " + mLastMedianLuma); - pw.println(" mCurrentMedianLuma: " + mCurrentMedianLuma); - pw.println(" mWindowVisible: " + mWindowVisible); - pw.println(" mWindowHasBlurs: " + mWindowHasBlurs); - pw.println(" mWaitingOnDraw: " + mWaitingOnDraw); - pw.println(" mRegisteredStopLayer: " + mRegisteredStopLayer); - pw.println(" mWrappedStopLayer: " + mWrappedStopLayer); - pw.println(" mIsDestroyed: " + mIsDestroyed); + pw.println(prefix + "\tmSamplingEnabled: " + mSamplingEnabled); + pw.println(prefix + "\tmSamplingListenerRegistered: " + mSamplingListenerRegistered); + pw.println(prefix + "\tmSamplingRequestBounds: " + mSamplingRequestBounds); + pw.println(prefix + "\tmRegisteredSamplingBounds: " + mRegisteredSamplingBounds); + pw.println(prefix + "\tmLastMedianLuma: " + mLastMedianLuma); + pw.println(prefix + "\tmCurrentMedianLuma: " + mCurrentMedianLuma); + pw.println(prefix + "\tmWindowVisible: " + mWindowVisible); + pw.println(prefix + "\tmWindowHasBlurs: " + mWindowHasBlurs); + pw.println(prefix + "\tmWaitingOnDraw: " + mWaitingOnDraw); + pw.println(prefix + "\tmRegisteredStopLayer: " + mRegisteredStopLayer); + pw.println(prefix + "\tmWrappedStopLayer: " + mWrappedStopLayer); + pw.println(prefix + "\tmIsDestroyed: " + mIsDestroyed); } public interface SamplingCallback { diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java index 605d37628ec7..bb7a0a719a74 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java @@ -16,6 +16,7 @@ package com.android.systemui.shared.rotation; +import static android.content.pm.PackageManager.FEATURE_PC; import static android.view.Display.DEFAULT_DISPLAY; import static com.android.internal.view.RotationPolicy.NATURAL_ROTATION; @@ -51,13 +52,14 @@ import com.android.internal.logging.UiEvent; import com.android.internal.logging.UiEventLogger; import com.android.internal.logging.UiEventLoggerImpl; import com.android.internal.view.RotationPolicy; -import com.android.systemui.shared.rotation.RotationButton.RotationButtonUpdatesCallback; import com.android.systemui.shared.recents.utilities.Utilities; import com.android.systemui.shared.recents.utilities.ViewRippler; +import com.android.systemui.shared.rotation.RotationButton.RotationButtonUpdatesCallback; import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.TaskStackChangeListener; import com.android.systemui.shared.system.TaskStackChangeListeners; +import java.io.PrintWriter; import java.util.Optional; import java.util.function.Consumer; import java.util.function.Supplier; @@ -200,7 +202,7 @@ public class RotationButtonController { } public void registerListeners() { - if (mListenersRegistered) { + if (mListenersRegistered || getContext().getPackageManager().hasSystemFeature(FEATURE_PC)) { return; } @@ -414,6 +416,9 @@ public class RotationButtonController { } public void onTaskbarStateChange(boolean visible, boolean stashed) { + if (getRotationButton() == null) { + return; + } getRotationButton().onTaskbarStateChanged(visible, stashed); } @@ -446,6 +451,30 @@ public class RotationButtonController { return mDarkIconColor; } + public void dumpLogs(String prefix, PrintWriter pw) { + pw.println(prefix + "RotationButtonController:"); + + pw.println(String.format( + "%s\tmIsRecentsAnimationRunning=%b", prefix, mIsRecentsAnimationRunning)); + pw.println(String.format("%s\tmHomeRotationEnabled=%b", prefix, mHomeRotationEnabled)); + pw.println(String.format( + "%s\tmLastRotationSuggestion=%d", prefix, mLastRotationSuggestion)); + pw.println(String.format( + "%s\tmPendingRotationSuggestion=%b", prefix, mPendingRotationSuggestion)); + pw.println(String.format( + "%s\tmHoveringRotationSuggestion=%b", prefix, mHoveringRotationSuggestion)); + pw.println(String.format("%s\tmListenersRegistered=%b", prefix, mListenersRegistered)); + pw.println(String.format( + "%s\tmIsNavigationBarShowing=%b", prefix, mIsNavigationBarShowing)); + pw.println(String.format("%s\tmBehavior=%d", prefix, mBehavior)); + pw.println(String.format( + "%s\tmSkipOverrideUserLockPrefsOnce=%b", prefix, mSkipOverrideUserLockPrefsOnce)); + pw.println(String.format( + "%s\tmLightIconColor=0x%s", prefix, Integer.toHexString(mLightIconColor))); + pw.println(String.format( + "%s\tmDarkIconColor=0x%s", prefix, Integer.toHexString(mDarkIconColor))); + } + public RotationButton getRotationButton() { return mRotationButton; } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java index 54c798c7d95d..5d092d02a835 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java @@ -55,8 +55,8 @@ public class QuickStepContract { // See IStartingWindow.aidl public static final String KEY_EXTRA_SHELL_STARTING_WINDOW = "extra_shell_starting_window"; - // See ISmartspaceTransitionController.aidl - public static final String KEY_EXTRA_SMARTSPACE_TRANSITION_CONTROLLER = "smartspace_transition"; + // See ISysuiUnlockAnimationController.aidl + public static final String KEY_EXTRA_UNLOCK_ANIMATION_CONTROLLER = "unlock_animation"; // See IRecentTasks.aidl public static final String KEY_EXTRA_RECENT_TASKS = "recent_tasks"; diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java index 2ae32c71269a..f2f382dea595 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java @@ -25,6 +25,8 @@ import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.view.WindowManager.TRANSIT_TO_FRONT; import static android.window.TransitionFilter.CONTAINER_ORDER_TOP; +import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.ACTIVITY_TYPE_RECENTS; + import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; @@ -145,6 +147,11 @@ public class RemoteTransitionCompat implements Parcelable { && taskInfo.pictureInPictureParams.isAutoEnterEnabled()) { pipTask = taskInfo.token; } + } else if (change.getTaskInfo() != null + && change.getTaskInfo().topActivityType == ACTIVITY_TYPE_RECENTS) { + // This task is for recents, keep it on top. + t.setLayer(leashMap.get(change.getLeash()), + info.getChanges().size() * 3 - i); } } // Also make all the wallpapers opaque since we want the visible from the start @@ -310,53 +317,48 @@ public class RemoteTransitionCompat implements Parcelable { return; } if (mWrapped != null) mWrapped.finish(toHome, sendUserLeaveHint); - try { - if (!toHome && mPausingTasks != null && mOpeningLeashes == null) { - // The gesture went back to opening the app rather than continuing with - // recents, so end the transition by moving the app back to the top (and also - // re-showing it's task). - final WindowContainerTransaction wct = new WindowContainerTransaction(); - final SurfaceControl.Transaction t = new SurfaceControl.Transaction(); - for (int i = mPausingTasks.size() - 1; i >= 0; --i) { - // reverse order so that index 0 ends up on top - wct.reorder(mPausingTasks.get(i), true /* onTop */); - t.show(mInfo.getChange(mPausingTasks.get(i)).getLeash()); - } - mFinishCB.onTransitionFinished(wct, t); - } else { - if (mOpeningLeashes != null) { - // TODO: the launcher animation should handle this - final SurfaceControl.Transaction t = new SurfaceControl.Transaction(); - for (int i = 0; i < mOpeningLeashes.size(); ++i) { - t.show(mOpeningLeashes.get(i)); - t.setAlpha(mOpeningLeashes.get(i), 1.f); - } - t.apply(); - } - if (mPipTask != null && mPipTransaction != null) { - final SurfaceControl.Transaction t = new SurfaceControl.Transaction(); - t.show(mInfo.getChange(mPipTask).getLeash()); - PictureInPictureSurfaceTransaction.apply(mPipTransaction, - mInfo.getChange(mPipTask).getLeash(), t); - mPipTask = null; - mPipTransaction = null; - mFinishCB.onTransitionFinished(null /* wct */, t); - } else { - mFinishCB.onTransitionFinished(null /* wct */, null /* sct */); + final SurfaceControl.Transaction t = new SurfaceControl.Transaction(); + final WindowContainerTransaction wct; + + if (!toHome && mPausingTasks != null && mOpeningLeashes == null) { + // The gesture went back to opening the app rather than continuing with + // recents, so end the transition by moving the app back to the top (and also + // re-showing it's task). + wct = new WindowContainerTransaction(); + for (int i = mPausingTasks.size() - 1; i >= 0; --i) { + // reverse order so that index 0 ends up on top + wct.reorder(mPausingTasks.get(i), true /* onTop */); + t.show(mInfo.getChange(mPausingTasks.get(i)).getLeash()); + } + } else { + wct = null; + if (mOpeningLeashes != null) { + // TODO: the launcher animation should handle this + for (int i = 0; i < mOpeningLeashes.size(); ++i) { + t.show(mOpeningLeashes.get(i)); + t.setAlpha(mOpeningLeashes.get(i), 1.f); } - } - } catch (RemoteException e) { - Log.e("RemoteTransitionCompat", "Failed to call animation finish callback", e); + if (mPipTask != null && mPipTransaction != null) { + t.show(mInfo.getChange(mPipTask).getLeash()); + PictureInPictureSurfaceTransaction.apply(mPipTransaction, + mInfo.getChange(mPipTask).getLeash(), t); + mPipTask = null; + mPipTransaction = null; + } } // Release surface references now. This is apparently to free GPU // memory while doing quick operations (eg. during CTS). - SurfaceControl.Transaction t = new SurfaceControl.Transaction(); for (int i = 0; i < mLeashMap.size(); ++i) { if (mLeashMap.keyAt(i) == mLeashMap.valueAt(i)) continue; t.remove(mLeashMap.valueAt(i)); } - t.apply(); + try { + mFinishCB.onTransitionFinished(wct, t); + } catch (RemoteException e) { + Log.e("RemoteTransitionCompat", "Failed to call animation finish callback", e); + t.apply(); + } for (int i = 0; i < mInfo.getChanges().size(); ++i) { mInfo.getChanges().get(i).getLeash().release(); } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ILauncherUnlockAnimationController.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ILauncherUnlockAnimationController.aidl new file mode 100644 index 000000000000..366193c2cc41 --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ILauncherUnlockAnimationController.aidl @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.shared.system.smartspace; + +import com.android.systemui.shared.system.smartspace.SmartspaceState; + +// Methods for System UI to interface with Launcher to perform the unlock animation. +interface ILauncherUnlockAnimationController { + // Prepares Launcher for the unlock animation by setting scale/alpha/etc. to their starting + // values. + void prepareForUnlock(boolean willAnimateSmartspace, int selectedPage); + + // Set the unlock percentage. This is used when System UI is controlling each frame of the + // unlock animation, such as during a swipe to unlock touch gesture. + oneway void setUnlockAmount(float amount); + + // Play a full unlock animation from 0f to 1f. This is used when System UI is unlocking from a + // single action, such as biometric auth, and doesn't need to control individual frames. + oneway void playUnlockAnimation(boolean unlocked, long duration); + + // Set the selected page on Launcher's smartspace. + oneway void setSmartspaceSelectedPage(int selectedPage); + + // Set the visibility of Launcher's smartspace. + void setSmartspaceVisibility(int visibility); + + // Tell SystemUI the smartspace's current state. Launcher code should call this whenever the + // smartspace state may have changed. + oneway void dispatchSmartspaceStateToSysui(); +}
\ No newline at end of file diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ISmartspaceCallback.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ISmartspaceCallback.aidl deleted file mode 100644 index 511df4c285b4..000000000000 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ISmartspaceCallback.aidl +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.shared.system.smartspace; - -import com.android.systemui.shared.system.smartspace.SmartspaceState; - -// Methods for getting and setting the state of a SmartSpace. This is used to allow a remote process -// (such as System UI) to sync with and control a SmartSpace view hosted in another process (such as -// Launcher). -interface ISmartspaceCallback { - - // Return information about the state of the SmartSpace, including location on-screen and - // currently selected page. - SmartspaceState getSmartspaceState(); - - // Set the currently selected page of this SmartSpace. - oneway void setSelectedPage(int selectedPage); - - oneway void setVisibility(int visibility); -}
\ No newline at end of file diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ISysuiUnlockAnimationController.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ISysuiUnlockAnimationController.aidl new file mode 100644 index 000000000000..cf83f62af550 --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ISysuiUnlockAnimationController.aidl @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.shared.system.smartspace; + +import com.android.systemui.shared.system.smartspace.ILauncherUnlockAnimationController; +import com.android.systemui.shared.system.smartspace.SmartspaceState; + +// System UI unlock controller. Launcher will provide a LauncherUnlockAnimationController to this +// controller, which System UI will use to control the unlock animation within the Launcher window. +interface ISysuiUnlockAnimationController { + // Provides an implementation of the LauncherUnlockAnimationController to System UI, so that + // SysUI can use it to control the unlock animation in the launcher window. + oneway void setLauncherUnlockController(ILauncherUnlockAnimationController callback); + + // Called by Launcher whenever anything happens to change the state of its smartspace. System UI + // proactively saves this and uses it to perform the unlock animation without needing to make a + // blocking query to Launcher asking about the smartspace state. + oneway void onLauncherSmartspaceStateUpdated(in SmartspaceState state); +}
\ No newline at end of file diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/SmartspaceState.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/SmartspaceState.kt index 2d51c4d13611..d7e61d60aa55 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/SmartspaceState.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/SmartspaceState.kt @@ -28,15 +28,18 @@ import android.os.Parcelable class SmartspaceState() : Parcelable { var boundsOnScreen: Rect = Rect() var selectedPage = 0 + var visibleOnScreen = false constructor(parcel: Parcel) : this() { this.boundsOnScreen = parcel.readParcelable(Rect::javaClass.javaClass.classLoader) this.selectedPage = parcel.readInt() + this.visibleOnScreen = parcel.readBoolean() } override fun writeToParcel(dest: Parcel?, flags: Int) { dest?.writeParcelable(boundsOnScreen, 0) dest?.writeInt(selectedPage) + dest?.writeBoolean(visibleOnScreen) } override fun describeContents(): Int { @@ -44,7 +47,9 @@ class SmartspaceState() : Parcelable { } override fun toString(): String { - return "boundsOnScreen: $boundsOnScreen, selectedPage: $selectedPage" + return "boundsOnScreen: $boundsOnScreen, " + + "selectedPage: $selectedPage, " + + "visibleOnScreen: $visibleOnScreen" } companion object CREATOR : Parcelable.Creator<SmartspaceState> { diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionProgressProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionProgressProvider.kt index e17f43e64b21..409dc95ab131 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionProgressProvider.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionProgressProvider.kt @@ -16,12 +16,14 @@ package com.android.systemui.unfold import android.annotation.FloatRange -import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener import com.android.systemui.statusbar.policy.CallbackController +import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener /** * Interface that allows to receive unfold transition progress updates. + * * It can be used to update view properties based on the current animation progress. + * * onTransitionProgress callback could be called on each frame. * * Use [createUnfoldTransitionProgressProvider] to create instances of this interface diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfig.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfig.kt index 3f027e30b473..d1b06394b818 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfig.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfig.kt @@ -18,9 +18,8 @@ package com.android.systemui.unfold.config import android.content.Context import android.os.SystemProperties -internal class ResourceUnfoldTransitionConfig( - private val context: Context -) : UnfoldTransitionConfig { +internal class ResourceUnfoldTransitionConfig(private val context: Context) : + UnfoldTransitionConfig { override val isEnabled: Boolean get() = readIsEnabledResource() && isPropertyEnabled @@ -29,19 +28,22 @@ internal class ResourceUnfoldTransitionConfig( get() = readIsHingeAngleEnabled() private val isPropertyEnabled: Boolean - get() = SystemProperties.getInt(UNFOLD_TRANSITION_MODE_PROPERTY_NAME, - UNFOLD_TRANSITION_PROPERTY_ENABLED) == UNFOLD_TRANSITION_PROPERTY_ENABLED + get() = + SystemProperties.getInt( + UNFOLD_TRANSITION_MODE_PROPERTY_NAME, UNFOLD_TRANSITION_PROPERTY_ENABLED) == + UNFOLD_TRANSITION_PROPERTY_ENABLED - private fun readIsEnabledResource(): Boolean = context.resources - .getBoolean(com.android.internal.R.bool.config_unfoldTransitionEnabled) + private fun readIsEnabledResource(): Boolean = + context.resources.getBoolean(com.android.internal.R.bool.config_unfoldTransitionEnabled) - private fun readIsHingeAngleEnabled(): Boolean = context.resources - .getBoolean(com.android.internal.R.bool.config_unfoldTransitionHingeAngle) + private fun readIsHingeAngleEnabled(): Boolean = + context.resources.getBoolean(com.android.internal.R.bool.config_unfoldTransitionHingeAngle) } /** - * Temporary persistent property to control unfold transition mode - * See [com.android.unfold.config.AnimationMode] + * Temporary persistent property to control unfold transition mode. + * + * See [com.android.unfold.config.AnimationMode]. */ private const val UNFOLD_TRANSITION_MODE_PROPERTY_NAME = "persist.unfold.transition_enabled" private const val UNFOLD_TRANSITION_PROPERTY_ENABLED = 1 diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/progress/FixedTimingTransitionProgressProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/progress/FixedTimingTransitionProgressProvider.kt index 732882e99038..4c85b055aeae 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/unfold/progress/FixedTimingTransitionProgressProvider.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/progress/FixedTimingTransitionProgressProvider.kt @@ -25,21 +25,17 @@ import com.android.systemui.unfold.updates.FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE import com.android.systemui.unfold.updates.FoldStateProvider import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdate -/** - * Emits animation progress with fixed timing after unfolding - */ +/** Emits animation progress with fixed timing after unfolding */ internal class FixedTimingTransitionProgressProvider( private val foldStateProvider: FoldStateProvider ) : UnfoldTransitionProgressProvider, FoldStateProvider.FoldUpdatesListener { private val animatorListener = AnimatorListener() private val animator = - ObjectAnimator.ofFloat(this, AnimationProgressProperty, 0f, 1f) - .apply { - duration = TRANSITION_TIME_MILLIS - addListener(animatorListener) - } - + ObjectAnimator.ofFloat(this, AnimationProgressProperty, 0f, 1f).apply { + duration = TRANSITION_TIME_MILLIS + addListener(animatorListener) + } private var transitionProgress: Float = 0.0f set(value) { @@ -62,10 +58,8 @@ internal class FixedTimingTransitionProgressProvider( override fun onFoldUpdate(@FoldUpdate update: Int) { when (update) { - FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE -> - animator.start() - FOLD_UPDATE_FINISH_CLOSED -> - animator.cancel() + FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE -> animator.start() + FOLD_UPDATE_FINISH_CLOSED -> animator.cancel() } } @@ -77,16 +71,12 @@ internal class FixedTimingTransitionProgressProvider( listeners.remove(listener) } - override fun onHingeAngleUpdate(angle: Float) { - } + override fun onHingeAngleUpdate(angle: Float) {} private object AnimationProgressProperty : FloatProperty<FixedTimingTransitionProgressProvider>("animation_progress") { - override fun setValue( - provider: FixedTimingTransitionProgressProvider, - value: Float - ) { + override fun setValue(provider: FixedTimingTransitionProgressProvider, value: Float) { provider.transitionProgress = value } @@ -104,11 +94,9 @@ internal class FixedTimingTransitionProgressProvider( listeners.forEach { it.onTransitionFinished() } } - override fun onAnimationRepeat(animator: Animator) { - } + override fun onAnimationRepeat(animator: Animator) {} - override fun onAnimationCancel(animator: Animator) { - } + override fun onAnimationCancel(animator: Animator) {} } private companion object { diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt index a701b44cf916..5266f096e12c 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt @@ -23,10 +23,10 @@ import androidx.dynamicanimation.animation.SpringAnimation import androidx.dynamicanimation.animation.SpringForce import com.android.systemui.unfold.UnfoldTransitionProgressProvider import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener -import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_HALF_OPEN import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_CLOSED -import com.android.systemui.unfold.updates.FOLD_UPDATE_START_CLOSING import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_FULL_OPEN +import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_HALF_OPEN +import com.android.systemui.unfold.updates.FOLD_UPDATE_START_CLOSING import com.android.systemui.unfold.updates.FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE import com.android.systemui.unfold.updates.FoldStateProvider import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdate @@ -35,13 +35,10 @@ import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdatesListener /** Maps fold updates to unfold transition progress using DynamicAnimation. */ internal class PhysicsBasedUnfoldTransitionProgressProvider( private val foldStateProvider: FoldStateProvider -) : - UnfoldTransitionProgressProvider, - FoldUpdatesListener, - DynamicAnimation.OnAnimationEndListener { +) : UnfoldTransitionProgressProvider, FoldUpdatesListener, DynamicAnimation.OnAnimationEndListener { - private val springAnimation = SpringAnimation(this, AnimationProgressProperty) - .apply { + private val springAnimation = + SpringAnimation(this, AnimationProgressProperty).apply { addEndListener(this@PhysicsBasedUnfoldTransitionProgressProvider) } @@ -121,9 +118,7 @@ internal class PhysicsBasedUnfoldTransitionProgressProvider( isTransitionRunning = false springAnimation.cancel() - listeners.forEach { - it.onTransitionFinished() - } + listeners.forEach { it.onTransitionFinished() } if (DEBUG) { Log.d(TAG, "onTransitionFinished") @@ -143,9 +138,7 @@ internal class PhysicsBasedUnfoldTransitionProgressProvider( } private fun onStartTransition() { - listeners.forEach { - it.onTransitionStarted() - } + listeners.forEach { it.onTransitionStarted() } isTransitionRunning = true if (DEBUG) { @@ -157,11 +150,12 @@ internal class PhysicsBasedUnfoldTransitionProgressProvider( if (!isTransitionRunning) onStartTransition() springAnimation.apply { - spring = SpringForce().apply { - finalPosition = startValue - dampingRatio = SpringForce.DAMPING_RATIO_NO_BOUNCY - stiffness = SPRING_STIFFNESS - } + spring = + SpringForce().apply { + finalPosition = startValue + dampingRatio = SpringForce.DAMPING_RATIO_NO_BOUNCY + stiffness = SPRING_STIFFNESS + } minimumVisibleChange = MINIMAL_VISIBLE_CHANGE setStartValue(startValue) setMinValue(0f) diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt index 204ae09b4852..24ecf8781a18 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt @@ -45,11 +45,9 @@ constructor( private val outputListeners: MutableList<FoldUpdatesListener> = mutableListOf() - @FoldUpdate - private var lastFoldUpdate: Int? = null + @FoldUpdate private var lastFoldUpdate: Int? = null - @FloatRange(from = 0.0, to = 180.0) - private var lastHingeAngle: Float = 0f + @FloatRange(from = 0.0, to = 180.0) private var lastHingeAngle: Float = 0f private val hingeAngleListener = HingeAngleListener() private val screenListener = ScreenStatusListener() @@ -60,10 +58,7 @@ constructor( private var isUnfoldHandled = true override fun start() { - deviceStateManager.registerCallback( - mainExecutor, - foldStateListener - ) + deviceStateManager.registerCallback(mainExecutor, foldStateListener) screenStatusProvider.addCallback(screenListener) hingeAngleProvider.addCallback(hingeAngleListener) } @@ -87,11 +82,14 @@ constructor( get() = !isFolded && lastFoldUpdate == FOLD_UPDATE_FINISH_FULL_OPEN private val isTransitionInProgess: Boolean - get() = lastFoldUpdate == FOLD_UPDATE_START_OPENING || + get() = + lastFoldUpdate == FOLD_UPDATE_START_OPENING || lastFoldUpdate == FOLD_UPDATE_START_CLOSING private fun onHingeAngle(angle: Float) { - if (DEBUG) { Log.d(TAG, "Hinge angle: $angle, lastHingeAngle: $lastHingeAngle") } + if (DEBUG) { + Log.d(TAG, "Hinge angle: $angle, lastHingeAngle: $lastHingeAngle") + } val isClosing = angle < lastHingeAngle val isFullyOpened = FULLY_OPEN_DEGREES - angle < FULLY_OPEN_THRESHOLD_DEGREES @@ -116,24 +114,28 @@ constructor( } private inner class FoldStateListener(context: Context) : - DeviceStateManager.FoldStateListener(context, { folded: Boolean -> - isFolded = folded - lastHingeAngle = FULLY_CLOSED_DEGREES - - if (folded) { - hingeAngleProvider.stop() - notifyFoldUpdate(FOLD_UPDATE_FINISH_CLOSED) - cancelTimeout() - isUnfoldHandled = false - } else { - notifyFoldUpdate(FOLD_UPDATE_START_OPENING) - rescheduleAbortAnimationTimeout() - hingeAngleProvider.start() - } - }) + DeviceStateManager.FoldStateListener( + context, + { folded: Boolean -> + isFolded = folded + lastHingeAngle = FULLY_CLOSED_DEGREES + + if (folded) { + hingeAngleProvider.stop() + notifyFoldUpdate(FOLD_UPDATE_FINISH_CLOSED) + cancelTimeout() + isUnfoldHandled = false + } else { + notifyFoldUpdate(FOLD_UPDATE_START_OPENING) + rescheduleAbortAnimationTimeout() + hingeAngleProvider.start() + } + }) private fun notifyFoldUpdate(@FoldUpdate update: Int) { - if (DEBUG) { Log.d(TAG, stateToString(update)) } + if (DEBUG) { + Log.d(TAG, stateToString(update)) + } outputListeners.forEach { it.onFoldUpdate(update) } lastFoldUpdate = update } @@ -149,8 +151,7 @@ constructor( handler.removeCallbacks(timeoutRunnable) } - private inner class ScreenStatusListener : - ScreenStatusProvider.ScreenListener { + private inner class ScreenStatusListener : ScreenStatusProvider.ScreenListener { override fun onScreenTurnedOn() { // Trigger this event only if we are unfolded and this is the first screen @@ -198,9 +199,7 @@ private const val DEBUG = false * Time after which [FOLD_UPDATE_FINISH_HALF_OPEN] is emitted following a * [FOLD_UPDATE_START_CLOSING] or [FOLD_UPDATE_START_OPENING] event, if an end state is not reached. */ -@VisibleForTesting -const val HALF_OPENED_TIMEOUT_MILLIS = 1000L +@VisibleForTesting const val HALF_OPENED_TIMEOUT_MILLIS = 1000L /** Threshold after which we consider the device fully unfolded. */ -@VisibleForTesting -const val FULLY_OPEN_THRESHOLD_DEGREES = 15f
\ No newline at end of file +@VisibleForTesting const val FULLY_OPEN_THRESHOLD_DEGREES = 15f diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/FoldStateProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/FoldStateProvider.kt index df3563df5fc6..5495316cd5b2 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/FoldStateProvider.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/FoldStateProvider.kt @@ -17,8 +17,8 @@ package com.android.systemui.unfold.updates import android.annotation.FloatRange import android.annotation.IntDef -import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdatesListener import com.android.systemui.statusbar.policy.CallbackController +import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdatesListener /** * Allows to subscribe to main events related to fold/unfold process such as hinge angle update, @@ -35,14 +35,16 @@ interface FoldStateProvider : CallbackController<FoldUpdatesListener> { fun onFoldUpdate(@FoldUpdate update: Int) } - @IntDef(prefix = ["FOLD_UPDATE_"], value = [ - FOLD_UPDATE_START_OPENING, - FOLD_UPDATE_START_CLOSING, - FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE, - FOLD_UPDATE_FINISH_HALF_OPEN, - FOLD_UPDATE_FINISH_FULL_OPEN, - FOLD_UPDATE_FINISH_CLOSED - ]) + @IntDef( + prefix = ["FOLD_UPDATE_"], + value = + [ + FOLD_UPDATE_START_OPENING, + FOLD_UPDATE_START_CLOSING, + FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE, + FOLD_UPDATE_FINISH_HALF_OPEN, + FOLD_UPDATE_FINISH_FULL_OPEN, + FOLD_UPDATE_FINISH_CLOSED]) @Retention(AnnotationRetention.SOURCE) annotation class FoldUpdate } diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/hinge/EmptyHingeAngleProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/hinge/EmptyHingeAngleProvider.kt index 4ca1a531fc25..b351585de364 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/hinge/EmptyHingeAngleProvider.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/hinge/EmptyHingeAngleProvider.kt @@ -3,15 +3,11 @@ package com.android.systemui.unfold.updates.hinge import androidx.core.util.Consumer internal object EmptyHingeAngleProvider : HingeAngleProvider { - override fun start() { - } + override fun start() {} - override fun stop() { - } + override fun stop() {} - override fun removeCallback(listener: Consumer<Float>) { - } + override fun removeCallback(listener: Consumer<Float>) {} - override fun addCallback(listener: Consumer<Float>) { - } + override fun addCallback(listener: Consumer<Float>) {} } diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/hinge/HingeAngleProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/hinge/HingeAngleProvider.kt index 6f524560de99..48a5b12c759a 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/hinge/HingeAngleProvider.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/hinge/HingeAngleProvider.kt @@ -5,9 +5,10 @@ import com.android.systemui.statusbar.policy.CallbackController /** * Emits device hinge angle values (angle between two integral parts of the device). - * The hinge angle could be from 0 to 360 degrees inclusive. - * For foldable devices usually 0 corresponds to fully closed (folded) state and - * 180 degrees corresponds to fully open (flat) state + * + * The hinge angle could be from 0 to 360 degrees inclusive. For foldable devices usually 0 + * corresponds to fully closed (folded) state and 180 degrees corresponds to fully open (flat) + * state. */ interface HingeAngleProvider : CallbackController<Consumer<Float>> { fun start() diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/hinge/HingeSensorAngleProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/hinge/HingeSensorAngleProvider.kt index a42ebef04de1..f6fe1ede9ce0 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/hinge/HingeSensorAngleProvider.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/hinge/HingeSensorAngleProvider.kt @@ -6,9 +6,8 @@ import android.hardware.SensorEventListener import android.hardware.SensorManager import androidx.core.util.Consumer -internal class HingeSensorAngleProvider( - private val sensorManager: SensorManager -) : HingeAngleProvider { +internal class HingeSensorAngleProvider(private val sensorManager: SensorManager) : + HingeAngleProvider { private val sensorListener = HingeAngleSensorListener() private val listeners: MutableList<Consumer<Float>> = arrayListOf() @@ -32,8 +31,7 @@ internal class HingeSensorAngleProvider( private inner class HingeAngleSensorListener : SensorEventListener { - override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) { - } + override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {} override fun onSensorChanged(event: SensorEvent) { listeners.forEach { it.accept(event.values[0]) } diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/screen/ScreenStatusProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/screen/ScreenStatusProvider.kt index 1eec8033ac5d..668c69442cac 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/screen/ScreenStatusProvider.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/screen/ScreenStatusProvider.kt @@ -15,8 +15,8 @@ */ package com.android.systemui.unfold.updates.screen -import com.android.systemui.unfold.updates.screen.ScreenStatusProvider.ScreenListener import com.android.systemui.statusbar.policy.CallbackController +import com.android.systemui.unfold.updates.screen.ScreenStatusProvider.ScreenListener interface ScreenStatusProvider : CallbackController<ScreenListener> { diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProvider.kt index 58d7dfb133a5..53c528ff24a8 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProvider.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProvider.kt @@ -10,9 +10,8 @@ import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionPr /** * [UnfoldTransitionProgressProvider] that emits transition progress only when the display has - * default rotation or 180 degrees opposite rotation (ROTATION_0 or ROTATION_180). - * It could be helpful to run the animation only when the display's rotation is perpendicular - * to the fold. + * default rotation or 180 degrees opposite rotation (ROTATION_0 or ROTATION_180). It could be + * helpful to run the animation only when the display's rotation is perpendicular to the fold. */ class NaturalRotationUnfoldProgressProvider( private val context: Context, @@ -21,7 +20,7 @@ class NaturalRotationUnfoldProgressProvider( ) : UnfoldTransitionProgressProvider { private val scopedUnfoldTransitionProgressProvider = - ScopedUnfoldTransitionProgressProvider(unfoldTransitionProgressProvider) + ScopedUnfoldTransitionProgressProvider(unfoldTransitionProgressProvider) private val rotationWatcher = RotationWatcher() private var isNaturalRotation: Boolean = false @@ -37,8 +36,8 @@ class NaturalRotationUnfoldProgressProvider( } private fun onRotationChanged(rotation: Int) { - val isNewRotationNatural = rotation == Surface.ROTATION_0 || - rotation == Surface.ROTATION_180 + val isNewRotationNatural = + rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180 if (isNaturalRotation != isNewRotationNatural) { isNaturalRotation = isNewRotationNatural diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScaleAwareTransitionProgressProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScaleAwareTransitionProgressProvider.kt index ee79b8761059..dfe87921dd42 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScaleAwareTransitionProgressProvider.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScaleAwareTransitionProgressProvider.kt @@ -21,17 +21,18 @@ constructor( private val scopedUnfoldTransitionProgressProvider = ScopedUnfoldTransitionProgressProvider(progressProviderToWrap) - private val animatorDurationScaleObserver = object : ContentObserver(null) { - override fun onChange(selfChange: Boolean) { - onAnimatorScaleChanged() + private val animatorDurationScaleObserver = + object : ContentObserver(null) { + override fun onChange(selfChange: Boolean) { + onAnimatorScaleChanged() + } } - } init { contentResolver.registerContentObserver( - Settings.Global.getUriFor(Settings.Global.ANIMATOR_DURATION_SCALE), - /* notifyForDescendants= */ false, - animatorDurationScaleObserver) + Settings.Global.getUriFor(Settings.Global.ANIMATOR_DURATION_SCALE), + /* notifyForDescendants= */ false, + animatorDurationScaleObserver) onAnimatorScaleChanged() } diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.kt index a274b74f336b..7b6791770295 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.kt @@ -20,16 +20,18 @@ import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionPr /** * Manages progress listeners that can have smaller lifespan than the unfold animation. + * * Allows to limit getting transition updates to only when - * [ScopedUnfoldTransitionProgressProvider.setReadyToHandleTransition] is called - * with readyToHandleTransition = true + * [ScopedUnfoldTransitionProgressProvider.setReadyToHandleTransition] is called with + * readyToHandleTransition = true * - * If the transition has already started by the moment when the clients are ready to play - * the transition then it will report transition started callback and current animation progress. + * If the transition has already started by the moment when the clients are ready to play the + * transition then it will report transition started callback and current animation progress. */ -class ScopedUnfoldTransitionProgressProvider @JvmOverloads constructor( - source: UnfoldTransitionProgressProvider? = null -) : UnfoldTransitionProgressProvider, TransitionProgressListener { +class ScopedUnfoldTransitionProgressProvider +@JvmOverloads +constructor(source: UnfoldTransitionProgressProvider? = null) : + UnfoldTransitionProgressProvider, TransitionProgressListener { private var source: UnfoldTransitionProgressProvider? = null @@ -43,8 +45,8 @@ class ScopedUnfoldTransitionProgressProvider @JvmOverloads constructor( setSourceProvider(source) } /** - * Sets the source for the unfold transition progress updates, - * it replaces current provider if it is already set + * Sets the source for the unfold transition progress updates. Replaces current provider if it + * is already set * @param provider transition provider that emits transition progress updates */ fun setSourceProvider(provider: UnfoldTransitionProgressProvider?) { @@ -60,8 +62,10 @@ class ScopedUnfoldTransitionProgressProvider @JvmOverloads constructor( /** * Allows to notify this provide whether the listeners can play the transition or not. - * Call this method with readyToHandleTransition = true when all listeners - * are ready to consume the transition progress events. + * + * Call this method with readyToHandleTransition = true when all listeners are ready to consume + * the transition progress events. + * * Call it with readyToHandleTransition = false when listeners can't process the events. */ fun setReadyToHandleTransition(isReadyToHandleTransition: Boolean) { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java index cc6df45c598f..d02b8752469a 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java @@ -30,7 +30,6 @@ import com.android.systemui.R; */ public abstract class KeyguardAbsKeyInputView extends KeyguardInputView { protected View mEcaView; - protected boolean mEnableHaptics; // To avoid accidental lockout due to events while the device in in the pocket, ignore // any passwords with length less than or equal to this length. @@ -45,10 +44,6 @@ public abstract class KeyguardAbsKeyInputView extends KeyguardInputView { super(context, attrs); } - void setEnableHaptics(boolean enableHaptics) { - mEnableHaptics = enableHaptics; - } - protected abstract int getPasswordTextViewId(); protected abstract void resetState(); @@ -80,11 +75,9 @@ public abstract class KeyguardAbsKeyInputView extends KeyguardInputView { // Cause a VIRTUAL_KEY vibration public void doHapticKeyClick() { - if (mEnableHaptics) { - performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY, - HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING - | HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING); - } + performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY, + HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING + | HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING); } public void setKeyDownListener(KeyDownListener keyDownListener) { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java index 1c4559eb0364..f86d08de87fc 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java @@ -98,7 +98,6 @@ public abstract class KeyguardAbsKeyInputViewController<T extends KeyguardAbsKey protected void onViewAttached() { super.onViewAttached(); mView.setKeyDownListener(mKeyDownListener); - mView.setEnableHaptics(mLockPatternUtils.isTactileFeedbackEnabled()); mEmergencyButtonController.setEmergencyButtonCallback(mEmergencyButtonCallback); } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java index 25b551139b44..8eb528980a96 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java @@ -20,6 +20,7 @@ import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; import static com.android.keyguard.KeyguardClockSwitch.LARGE; +import static com.android.keyguard.KeyguardClockSwitch.SMALL; import android.app.WallpaperManager; import android.content.res.Resources; @@ -41,7 +42,6 @@ import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import com.android.systemui.plugins.ClockPlugin; import com.android.systemui.plugins.statusbar.StatusBarStateController; -import com.android.systemui.shared.system.smartspace.SmartspaceTransitionController; import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController; import com.android.systemui.statusbar.notification.AnimatableProperty; import com.android.systemui.statusbar.notification.PropertyAnimator; @@ -86,6 +86,9 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS private AnimatableClockController mLargeClockViewController; private FrameLayout mLargeClockFrame; // centered clock + @KeyguardClockSwitch.ClockSize + private int mCurrentClockSize = SMALL; + private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; private final KeyguardBypassController mBypassController; @@ -110,7 +113,6 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS private View mSmartspaceView; private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController; - private SmartspaceTransitionController mSmartspaceTransitionController; private boolean mOnlyClock = false; private Executor mUiExecutor; @@ -136,7 +138,6 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS KeyguardBypassController bypassController, LockscreenSmartspaceController smartspaceController, KeyguardUnlockAnimationController keyguardUnlockAnimationController, - SmartspaceTransitionController smartspaceTransitionController, SecureSettings secureSettings, @Main Executor uiExecutor, @Main Resources resources) { @@ -155,7 +156,22 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS mSecureSettings = secureSettings; mUiExecutor = uiExecutor; mKeyguardUnlockAnimationController = keyguardUnlockAnimationController; - mSmartspaceTransitionController = smartspaceTransitionController; + mKeyguardUnlockAnimationController.addKeyguardUnlockAnimationListener( + new KeyguardUnlockAnimationController.KeyguardUnlockAnimationListener() { + @Override + public void onSmartspaceSharedElementTransitionStarted() { + // The smartspace needs to be able to translate out of bounds in order to + // end up where the launcher's smartspace is, while its container is being + // swiped off the top of the screen. + setClipChildrenForUnlock(false); + } + + @Override + public void onUnlockAnimationFinished() { + // For performance reasons, reset this once the unlock animation ends. + setClipChildrenForUnlock(true); + } + }); } /** @@ -236,7 +252,7 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS mSmartspaceView.setPaddingRelative(startPadding, 0, endPadding, 0); updateClockLayout(); - mSmartspaceTransitionController.setLockscreenSmartspace(mSmartspaceView); + mKeyguardUnlockAnimationController.setLockscreenSmartspace(mSmartspaceView); } mSecureSettings.registerContentObserver( @@ -293,6 +309,8 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS return; } + mCurrentClockSize = clockSize; + boolean appeared = mView.switchToClock(clockSize, animate); if (animate && appeared && clockSize == LARGE) { mLargeClockViewController.animateAppear(); @@ -368,7 +386,14 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS final Set<View> excludedViews = new HashSet<>(); if (mSmartspaceView != null) { - excludedViews.add(mSmartspaceView); + excludedViews.add(mStatusArea); + } + + // Don't change the alpha of the invisible clock. + if (mCurrentClockSize == LARGE) { + excludedViews.add(mClockFrame); + } else { + excludedViews.add(mLargeClockFrame); } setChildrenAlphaExcluding(alpha, excludedViews); @@ -449,4 +474,16 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS mUiExecutor.execute(() -> displayClock(KeyguardClockSwitch.SMALL, /* animate */ true)); } } + + /** + * Sets the clipChildren property on relevant views, to allow the smartspace to draw out of + * bounds during the unlock transition. + */ + private void setClipChildrenForUnlock(boolean clip) { + mView.setClipChildren(clip); + + if (mStatusArea != null) { + mStatusArea.setClipChildren(clip); + } + } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardConstants.java b/packages/SystemUI/src/com/android/keyguard/KeyguardConstants.java index 0340904cbd9d..b2658c9f9bdb 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardConstants.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardConstants.java @@ -16,6 +16,8 @@ package com.android.keyguard; +import android.util.Log; + /** * Defines constants for the Keyguard. */ @@ -25,7 +27,7 @@ public class KeyguardConstants { * Turns on debugging information for the whole Keyguard. This is very verbose and should only * be used temporarily for debugging. */ - public static final boolean DEBUG = false; + public static final boolean DEBUG = Log.isLoggable("Keyguard", Log.DEBUG); public static final boolean DEBUG_SIM_STATES = true; public static final boolean DEBUG_BIOMETRIC_WAKELOCK = true; } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java index 8c7ede26e2e6..848b8ab8ff84 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java @@ -262,7 +262,6 @@ public class KeyguardDisplayManager { private static final int VIDEO_SAFE_REGION = 80; // Percentage of display width & height private static final int MOVE_CLOCK_TIMEOUT = 10000; // 10s private final KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory; - private final Context mContext; private KeyguardClockSwitchController mKeyguardClockSwitchController; private View mClock; private int mUsableWidth; @@ -286,7 +285,6 @@ public class KeyguardDisplayManager { WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); mKeyguardStatusViewComponentFactory = keyguardStatusViewComponentFactory; setCancelable(false); - mContext = context; } @Override @@ -311,7 +309,7 @@ public class KeyguardDisplayManager { updateBounds(); - setContentView(LayoutInflater.from(mContext) + setContentView(LayoutInflater.from(getContext()) .inflate(R.layout.keyguard_presentation, null)); // Logic to make the lock screen fullscreen diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java index 94e07b713915..238acd5db621 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java @@ -224,8 +224,6 @@ public class KeyguardPatternViewController mLockPatternView.setSaveEnabled(false); mLockPatternView.setInStealthMode(!mLockPatternUtils.isVisiblePatternEnabled( KeyguardUpdateMonitor.getCurrentUser())); - // vibrate mode will be the same for the life of this screen - mLockPatternView.setTactileFeedbackEnabled(mLockPatternUtils.isTactileFeedbackEnabled()); mLockPatternView.setOnTouchListener((v, event) -> { if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { mFalsingCollector.avoidGesture(); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java index 8bf890d7df50..a5fe0efc8887 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java @@ -22,7 +22,6 @@ import android.util.Slog; import com.android.keyguard.KeyguardClockSwitch.ClockSize; import com.android.systemui.communal.CommunalStateController; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; -import com.android.systemui.shared.system.smartspace.SmartspaceTransitionController; import com.android.systemui.statusbar.notification.AnimatableProperty; import com.android.systemui.statusbar.notification.PropertyAnimator; import com.android.systemui.statusbar.notification.stack.AnimationProperties; @@ -55,7 +54,6 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV private final KeyguardVisibilityHelper mKeyguardVisibilityHelper; private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController; private final KeyguardStateController mKeyguardStateController; - private SmartspaceTransitionController mSmartspaceTransitionController; private final Rect mClipBounds = new Rect(); @Inject @@ -69,7 +67,6 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV ConfigurationController configurationController, DozeParameters dozeParameters, KeyguardUnlockAnimationController keyguardUnlockAnimationController, - SmartspaceTransitionController smartspaceTransitionController, ScreenOffAnimationController screenOffAnimationController) { super(keyguardStatusView); mKeyguardSliceViewController = keyguardSliceViewController; @@ -82,7 +79,6 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV keyguardStateController, dozeParameters, screenOffAnimationController, /* animateYPos= */ true, /* visibleOnCommunal= */ false); mKeyguardUnlockAnimationController = keyguardUnlockAnimationController; - mSmartspaceTransitionController = smartspaceTransitionController; } @Override diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index a348b423d3c7..f2d0427c39d3 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -37,8 +37,6 @@ import android.app.ActivityManager; import android.app.ActivityTaskManager; import android.app.ActivityTaskManager.RootTaskInfo; import android.app.AlarmManager; -import android.app.Notification; -import android.app.NotificationManager; import android.app.PendingIntent; import android.app.UserSwitchObserver; import android.app.admin.DevicePolicyManager; @@ -104,8 +102,6 @@ import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dump.DumpManager; -import com.android.systemui.flags.FeatureFlags; -import com.android.systemui.flags.Flags; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.shared.system.TaskStackChangeListener; import com.android.systemui.shared.system.TaskStackChangeListeners; @@ -113,7 +109,6 @@ import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.telephony.TelephonyListenerManager; import com.android.systemui.util.Assert; -import com.android.systemui.util.NotificationChannels; import com.android.systemui.util.RingerModeTracker; import com.google.android.collect.Lists; @@ -338,7 +333,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID; private final Executor mBackgroundExecutor; private SensorPrivacyManager mSensorPrivacyManager; - private FeatureFlags mFeatureFlags; private int mFaceAuthUserId; /** @@ -443,7 +437,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } @Override - public void onTrustChanged(boolean enabled, int userId, int flags) { + public void onTrustChanged(boolean enabled, int userId, int flags, + List<String> trustGrantedMessages) { Assert.isMainThread(); boolean wasTrusted = mUserHasTrust.get(userId, false); mUserHasTrust.put(userId, enabled); @@ -465,6 +460,19 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } } } + + if (KeyguardUpdateMonitor.getCurrentUser() == userId && getUserHasTrust(userId)) { + CharSequence message = null; + if (trustGrantedMessages != null && trustGrantedMessages.size() > 0) { + message = trustGrantedMessages.get(0); // for now only shows the first in the list + } + for (int i = 0; i < mCallbacks.size(); i++) { + KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); + if (cb != null) { + cb.showTrustGrantedMessage(message); + } + } + } } @Override @@ -1790,8 +1798,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab AuthController authController, TelephonyListenerManager telephonyListenerManager, InteractionJankMonitor interactionJankMonitor, - LatencyTracker latencyTracker, - FeatureFlags featureFlags) { + LatencyTracker latencyTracker) { mContext = context; mSubscriptionManager = SubscriptionManager.from(context); mTelephonyListenerManager = telephonyListenerManager; @@ -1809,7 +1816,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mAuthController = authController; dumpManager.registerDumpable(getClass().getName(), this); mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class); - mFeatureFlags = featureFlags; mHandler = new Handler(mainLooper) { @Override @@ -2253,34 +2259,12 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab return; } - if (shouldTriggerActiveUnlock() && mFeatureFlags.isEnabled(Flags.ACTIVE_UNLOCK)) { - // TODO (b/192405661): call new TrustManager API - mNumActiveUnlockTriggers++; - Log.d("ActiveUnlock", "would have triggered times=" + mNumActiveUnlockTriggers); - showActiveUnlockNotification(mNumActiveUnlockTriggers); + if (shouldTriggerActiveUnlock()) { + mTrustManager.reportUserRequestedUnlock(KeyguardUpdateMonitor.getCurrentUser()); } } - /** - * TODO (b/192405661): Only for testing. Remove before release. - */ - private void showActiveUnlockNotification(int times) { - final String message = "Active unlock triggered " + times + " times."; - final Notification.Builder nb = - new Notification.Builder(mContext, NotificationChannels.GENERAL) - .setSmallIcon(R.drawable.ic_volume_ringer) - .setContentTitle(message) - .setStyle(new Notification.BigTextStyle().bigText(message)); - mContext.getSystemService(NotificationManager.class).notifyAsUser( - "active_unlock", - 0, - nb.build(), - UserHandle.ALL); - } - private boolean shouldTriggerActiveUnlock() { - // TODO: check if active unlock is ENABLED / AVAILABLE - // Triggers: final boolean triggerActiveUnlockForAssistant = shouldTriggerActiveUnlockForAssistant(); final boolean awakeKeyguard = mKeyguardIsVisible && mDeviceInteractive && !mGoingToSleep @@ -2294,7 +2278,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab final boolean userCanDismissLockScreen = getUserCanSkipBouncer(user) || !mLockPatternUtils.isSecure(user); - // Don't trigger active unlock if fp is locked out TODO: confirm this one + // Don't trigger active unlock if fp is locked out final boolean fpLockedout = mFingerprintLockedOut || mFingerprintLockedOutPermanent; // Don't trigger active unlock if primary auth is required diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java index a74fd15ab11b..47e1035fbfef 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java @@ -23,6 +23,8 @@ import android.os.SystemClock; import android.telephony.TelephonyManager; import android.view.WindowManagerPolicyConstants; +import androidx.annotation.Nullable; + import com.android.settingslib.fuelgauge.BatteryStatus; import com.android.systemui.statusbar.KeyguardIndicationController; @@ -216,6 +218,11 @@ public class KeyguardUpdateMonitorCallback { public void onTrustGrantedWithFlags(int flags, int userId) { } /** + * Called when setting the trust granted message. + */ + public void showTrustGrantedMessage(@Nullable CharSequence message) { } + + /** * Called when a biometric has been acquired. * <p> * It is guaranteed that either {@link #onBiometricAuthenticated} or diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java index e79ea9a44843..a5a3f80d07b9 100644 --- a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java +++ b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java @@ -213,10 +213,8 @@ public class NumPadKey extends ViewGroup { // Cause a VIRTUAL_KEY vibration public void doHapticKeyClick() { - if (mLockPatternUtils.isTactileFeedbackEnabled()) { - performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY, - HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING - | HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING); - } + performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY, + HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING + | HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING); } } diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java index 276790483861..b3be87731fbc 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java @@ -122,7 +122,8 @@ public class SystemUIFactory { .setTaskSurfaceHelper(mWMComponent.getTaskSurfaceHelper()) .setRecentTasks(mWMComponent.getRecentTasks()) .setCompatUI(Optional.of(mWMComponent.getCompatUI())) - .setDragAndDrop(Optional.of(mWMComponent.getDragAndDrop())); + .setDragAndDrop(Optional.of(mWMComponent.getDragAndDrop())) + .setBackAnimation(mWMComponent.getBackAnimation()); } else { // TODO: Call on prepareSysUIComponentBuilder but not with real components. Other option // is separating this logic into newly creating SystemUITestsFactory. @@ -142,7 +143,8 @@ public class SystemUIFactory { .setTaskSurfaceHelper(Optional.ofNullable(null)) .setRecentTasks(Optional.ofNullable(null)) .setCompatUI(Optional.ofNullable(null)) - .setDragAndDrop(Optional.ofNullable(null)); + .setDragAndDrop(Optional.ofNullable(null)) + .setBackAnimation(Optional.ofNullable(null)); } mSysUIComponent = builder.build(); if (mInitializeComponents) { diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java index 052ec86d8398..dbd215d9c713 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java @@ -22,8 +22,10 @@ import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_M import android.annotation.NonNull; import android.annotation.UiContext; +import android.content.ComponentCallbacks; import android.content.Context; import android.content.pm.ActivityInfo; +import android.content.res.Configuration; import android.graphics.Insets; import android.graphics.PixelFormat; import android.graphics.Rect; @@ -54,7 +56,8 @@ import java.util.Collections; * The button icon is movable by dragging and it would not overlap navigation bar window. * And the button UI would automatically be dismissed after displaying for a period of time. */ -class MagnificationModeSwitch implements MagnificationGestureDetector.OnGestureListener { +class MagnificationModeSwitch implements MagnificationGestureDetector.OnGestureListener, + ComponentCallbacks { @VisibleForTesting static final long FADING_ANIMATION_DURATION_MS = 300; @@ -75,6 +78,7 @@ class MagnificationModeSwitch implements MagnificationGestureDetector.OnGestureL private int mMagnificationMode = ACCESSIBILITY_MAGNIFICATION_MODE_NONE; private final LayoutParams mParams; private final SwitchListener mSwitchListener; + private final Configuration mConfiguration; @VisibleForTesting final Rect mDraggableWindowBounds = new Rect(); private boolean mIsVisible = false; @@ -101,6 +105,7 @@ class MagnificationModeSwitch implements MagnificationGestureDetector.OnGestureL MagnificationModeSwitch(Context context, @NonNull ImageView imageView, SfVsyncFrameCallbackProvider sfVsyncFrameProvider, SwitchListener switchListener) { mContext = context; + mConfiguration = new Configuration(context.getResources().getConfiguration()); mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class); mWindowManager = mContext.getSystemService(WindowManager.class); mSfVsyncFrameProvider = sfVsyncFrameProvider; @@ -270,6 +275,7 @@ class MagnificationModeSwitch implements MagnificationGestureDetector.OnGestureL mIsFadeOutAnimating = false; mImageView.setAlpha(0f); mWindowManager.removeView(mImageView); + mContext.unregisterComponentCallbacks(this); mIsVisible = false; } @@ -291,6 +297,8 @@ class MagnificationModeSwitch implements MagnificationGestureDetector.OnGestureL mImageView.setImageResource(getIconResId(mode)); } if (!mIsVisible) { + onConfigurationChanged(mContext.getResources().getConfiguration()); + mContext.registerComponentCallbacks(this); if (resetPosition) { mDraggableWindowBounds.set(getDraggableWindowBounds()); mParams.x = mDraggableWindowBounds.right; @@ -321,7 +329,21 @@ class MagnificationModeSwitch implements MagnificationGestureDetector.OnGestureL } } + @Override + public void onConfigurationChanged(@NonNull Configuration newConfig) { + final int configDiff = newConfig.diff(mConfiguration); + mConfiguration.setTo(newConfig); + onConfigurationChanged(configDiff); + } + + @Override + public void onLowMemory() { + } + void onConfigurationChanged(int configDiff) { + if (configDiff == 0) { + return; + } if ((configDiff & (ActivityInfo.CONFIG_ORIENTATION | ActivityInfo.CONFIG_SCREEN_SIZE)) != 0) { final Rect previousDraggableBounds = new Rect(mDraggableWindowBounds); diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java index 4784bc12099b..885a1777f30b 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java @@ -23,7 +23,6 @@ import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_M import android.annotation.MainThread; import android.annotation.Nullable; import android.content.Context; -import android.content.res.Configuration; import android.graphics.Rect; import android.hardware.display.DisplayManager; import android.os.Handler; @@ -65,7 +64,6 @@ public class WindowMagnification extends CoreStartable implements WindowMagnifie private final OverviewProxyService mOverviewProxyService; private WindowMagnificationConnectionImpl mWindowMagnificationConnectionImpl; - private Configuration mLastConfiguration; private SysUiState mSysUiState; private static class ControllerSupplier extends @@ -107,7 +105,6 @@ public class WindowMagnification extends CoreStartable implements WindowMagnifie SysUiState sysUiState, OverviewProxyService overviewProxyService) { super(context); mHandler = mainHandler; - mLastConfiguration = new Configuration(context.getResources().getConfiguration()); mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class); mCommandQueue = commandQueue; mModeSwitchesController = modeSwitchesController; @@ -118,18 +115,6 @@ public class WindowMagnification extends CoreStartable implements WindowMagnifie } @Override - public void onConfigurationChanged(Configuration newConfig) { - final int configDiff = newConfig.diff(mLastConfiguration); - mLastConfiguration.setTo(newConfig); - mMagnificationControllerSupplier.forEach( - magnificationController -> magnificationController.onConfigurationChanged( - configDiff)); - if (mModeSwitchesController != null) { - mModeSwitchesController.onConfigurationChanged(configDiff); - } - } - - @Override public void start() { mCommandQueue.addCallback(this); mOverviewProxyService.addCallback(new OverviewProxyService.OverviewProxyListener() { diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java index aa1a43397f65..0d20403b08f2 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java @@ -26,6 +26,7 @@ import android.animation.PropertyValuesHolder; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UiContext; +import android.content.ComponentCallbacks; import android.content.Context; import android.content.pm.ActivityInfo; import android.content.res.Configuration; @@ -76,7 +77,8 @@ import java.util.Locale; * Class to handle adding and removing a window magnification. */ class WindowMagnificationController implements View.OnTouchListener, SurfaceHolder.Callback, - MirrorWindowControl.MirrorWindowDelegate, MagnificationGestureDetector.OnGestureListener { + MirrorWindowControl.MirrorWindowDelegate, MagnificationGestureDetector.OnGestureListener, + ComponentCallbacks { private static final String TAG = "WindowMagnificationController"; @SuppressWarnings("isloggabletaglength") @@ -143,6 +145,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold private View mTopDrag; private View mRightDrag; private View mBottomDrag; + private final Configuration mConfiguration; @NonNull private final WindowMagnifierCallback mWindowMagnifierCallback; @@ -191,6 +194,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold mSfVsyncFrameProvider = sfVsyncFrameProvider; mWindowMagnifierCallback = callback; mSysUiState = sysUiState; + mConfiguration = new Configuration(context.getResources().getConfiguration()); final Display display = mContext.getDisplay(); mDisplayId = mContext.getDisplayId(); @@ -339,6 +343,18 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold } mMirrorViewBounds.setEmpty(); updateSystemUIStateIfNeeded(); + mContext.unregisterComponentCallbacks(this); + } + + @Override + public void onConfigurationChanged(@NonNull Configuration newConfig) { + final int configDiff = newConfig.diff(mConfiguration); + mConfiguration.setTo(newConfig); + onConfigurationChanged(configDiff); + } + + @Override + public void onLowMemory() { } /** @@ -351,6 +367,9 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold Log.d(TAG, "onConfigurationChanged = " + Configuration.configurationDiffToString( configDiff)); } + if (configDiff == 0) { + return; + } if ((configDiff & ActivityInfo.CONFIG_ORIENTATION) != 0) { onRotate(); } @@ -390,7 +409,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold if (currentWindowBounds.equals(oldWindowBounds)) { if (DEBUG) { - Log.d(TAG, "updateMagnificationFrame -- window bounds is not changed"); + Log.d(TAG, "handleScreenSizeChanged -- window bounds is not changed"); } return false; } @@ -851,6 +870,10 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold deleteWindowMagnification(); return; } + if (!isWindowVisible()) { + onConfigurationChanged(mResources.getConfiguration()); + mContext.registerComponentCallbacks(this); + } mMagnificationFrameOffsetX = Float.isNaN(magnificationFrameOffsetRatioX) ? mMagnificationFrameOffsetX diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPatternView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPatternView.java index ab8162f9464d..11498dbc0b83 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPatternView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPatternView.java @@ -107,6 +107,5 @@ public class AuthCredentialPatternView extends AuthCredentialView { mLockPatternView.setOnPatternListener(new UnlockPatternListener()); mLockPatternView.setInStealthMode( !mLockPatternUtils.isVisiblePatternEnabled(mUserId)); - mLockPatternView.setTactileFeedbackEnabled(mLockPatternUtils.isTactileFeedbackEnabled()); } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java index e7015115d84c..1317492aefac 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java @@ -29,9 +29,7 @@ import android.graphics.drawable.Drawable; import android.os.Handler; import android.os.Looper; import android.view.animation.AccelerateDecelerateInterpolator; -import android.view.animation.LinearInterpolator; -import androidx.annotation.ColorInt; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -43,18 +41,11 @@ import com.android.systemui.R; public class UdfpsEnrollDrawable extends UdfpsDrawable { private static final String TAG = "UdfpsAnimationEnroll"; - private static final long HINT_COLOR_ANIM_DELAY_MS = 233L; - private static final long HINT_COLOR_ANIM_DURATION_MS = 517L; - private static final long HINT_WIDTH_ANIM_DURATION_MS = 233L; private static final long TARGET_ANIM_DURATION_LONG = 800L; private static final long TARGET_ANIM_DURATION_SHORT = 600L; // 1 + SCALE_MAX is the maximum that the moving target will animate to private static final float SCALE_MAX = 0.25f; - private static final float HINT_PADDING_DP = 10f; - private static final float HINT_MAX_WIDTH_DP = 6f; - private static final float HINT_ANGLE = 40f; - private final Handler mHandler = new Handler(Looper.getMainLooper()); @NonNull private final Drawable mMovingTargetFpIcon; @@ -72,30 +63,10 @@ public class UdfpsEnrollDrawable extends UdfpsDrawable { // Moving target size float mCurrentScale = 1.f; - @ColorInt private final int mHintColorFaded; - @ColorInt private final int mHintColorHighlight; - private final float mHintMaxWidthPx; - private final float mHintPaddingPx; - @NonNull private final Animator.AnimatorListener mTargetAnimListener; private boolean mShouldShowTipHint = false; - @NonNull private final Paint mTipHintPaint; - @Nullable private AnimatorSet mTipHintAnimatorSet; - @Nullable private ValueAnimator mTipHintColorAnimator; - @Nullable private ValueAnimator mTipHintWidthAnimator; - @NonNull private final ValueAnimator.AnimatorUpdateListener mTipHintColorUpdateListener; - @NonNull private final ValueAnimator.AnimatorUpdateListener mTipHintWidthUpdateListener; - @NonNull private final Animator.AnimatorListener mTipHintPulseListener; - private boolean mShouldShowEdgeHint = false; - @NonNull private final Paint mEdgeHintPaint; - @Nullable private AnimatorSet mEdgeHintAnimatorSet; - @Nullable private ValueAnimator mEdgeHintColorAnimator; - @Nullable private ValueAnimator mEdgeHintWidthAnimator; - @NonNull private final ValueAnimator.AnimatorUpdateListener mEdgeHintColorUpdateListener; - @NonNull private final ValueAnimator.AnimatorUpdateListener mEdgeHintWidthUpdateListener; - @NonNull private final Animator.AnimatorListener mEdgeHintPulseListener; UdfpsEnrollDrawable(@NonNull Context context) { super(context); @@ -117,11 +88,6 @@ public class UdfpsEnrollDrawable extends UdfpsDrawable { getFingerprintDrawable().setTint(context.getColor(R.color.udfps_enroll_icon)); - mHintColorFaded = context.getColor(R.color.udfps_moving_target_fill); - mHintColorHighlight = context.getColor(R.color.udfps_enroll_progress); - mHintMaxWidthPx = Utils.dpToPixels(context, HINT_MAX_WIDTH_DP); - mHintPaddingPx = Utils.dpToPixels(context, HINT_PADDING_DP); - mTargetAnimListener = new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) {} @@ -137,80 +103,6 @@ public class UdfpsEnrollDrawable extends UdfpsDrawable { @Override public void onAnimationRepeat(Animator animation) {} }; - - mTipHintPaint = new Paint(0 /* flags */); - mTipHintPaint.setAntiAlias(true); - mTipHintPaint.setColor(mHintColorFaded); - mTipHintPaint.setStyle(Paint.Style.STROKE); - mTipHintPaint.setStrokeCap(Paint.Cap.ROUND); - mTipHintPaint.setStrokeWidth(0f); - mTipHintColorUpdateListener = animation -> { - mTipHintPaint.setColor((int) animation.getAnimatedValue()); - invalidateSelf(); - }; - mTipHintWidthUpdateListener = animation -> { - mTipHintPaint.setStrokeWidth((float) animation.getAnimatedValue()); - invalidateSelf(); - }; - mTipHintPulseListener = new Animator.AnimatorListener() { - @Override - public void onAnimationStart(Animator animation) {} - - @Override - public void onAnimationEnd(Animator animation) { - mHandler.postDelayed(() -> { - mTipHintColorAnimator = - ValueAnimator.ofArgb(mTipHintPaint.getColor(), mHintColorFaded); - mTipHintColorAnimator.setInterpolator(new LinearInterpolator()); - mTipHintColorAnimator.setDuration(HINT_COLOR_ANIM_DURATION_MS); - mTipHintColorAnimator.addUpdateListener(mTipHintColorUpdateListener); - mTipHintColorAnimator.start(); - }, HINT_COLOR_ANIM_DELAY_MS); - } - - @Override - public void onAnimationCancel(Animator animation) {} - - @Override - public void onAnimationRepeat(Animator animation) {} - }; - - mEdgeHintPaint = new Paint(0 /* flags */); - mEdgeHintPaint.setAntiAlias(true); - mEdgeHintPaint.setColor(mHintColorFaded); - mEdgeHintPaint.setStyle(Paint.Style.STROKE); - mEdgeHintPaint.setStrokeCap(Paint.Cap.ROUND); - mEdgeHintPaint.setStrokeWidth(0f); - mEdgeHintColorUpdateListener = animation -> { - mEdgeHintPaint.setColor((int) animation.getAnimatedValue()); - invalidateSelf(); - }; - mEdgeHintWidthUpdateListener = animation -> { - mEdgeHintPaint.setStrokeWidth((float) animation.getAnimatedValue()); - invalidateSelf(); - }; - mEdgeHintPulseListener = new Animator.AnimatorListener() { - @Override - public void onAnimationStart(Animator animation) {} - - @Override - public void onAnimationEnd(Animator animation) { - mHandler.postDelayed(() -> { - mEdgeHintColorAnimator = - ValueAnimator.ofArgb(mEdgeHintPaint.getColor(), mHintColorFaded); - mEdgeHintColorAnimator.setInterpolator(new LinearInterpolator()); - mEdgeHintColorAnimator.setDuration(HINT_COLOR_ANIM_DURATION_MS); - mEdgeHintColorAnimator.addUpdateListener(mEdgeHintColorUpdateListener); - mEdgeHintColorAnimator.start(); - }, HINT_COLOR_ANIM_DELAY_MS); - } - - @Override - public void onAnimationCancel(Animator animation) {} - - @Override - public void onAnimationRepeat(Animator animation) {} - }; } void setEnrollHelper(@NonNull UdfpsEnrollHelper helper) { @@ -287,25 +179,12 @@ public class UdfpsEnrollDrawable extends UdfpsDrawable { private void updateTipHintVisibility() { final boolean shouldShow = mEnrollHelper != null && mEnrollHelper.isTipEnrollmentStage(); + // With the new update, we will git rid of most of this code, and instead + // we will change the fingerprint icon. if (mShouldShowTipHint == shouldShow) { return; } mShouldShowTipHint = shouldShow; - - if (mTipHintWidthAnimator != null && mTipHintWidthAnimator.isRunning()) { - mTipHintWidthAnimator.cancel(); - } - - final float targetWidth = shouldShow ? mHintMaxWidthPx : 0f; - mTipHintWidthAnimator = ValueAnimator.ofFloat(mTipHintPaint.getStrokeWidth(), targetWidth); - mTipHintWidthAnimator.setDuration(HINT_WIDTH_ANIM_DURATION_MS); - mTipHintWidthAnimator.addUpdateListener(mTipHintWidthUpdateListener); - - if (shouldShow) { - startTipHintPulseAnimation(); - } else { - mTipHintWidthAnimator.start(); - } } private void updateEdgeHintVisibility() { @@ -314,71 +193,6 @@ public class UdfpsEnrollDrawable extends UdfpsDrawable { return; } mShouldShowEdgeHint = shouldShow; - - if (mEdgeHintWidthAnimator != null && mEdgeHintWidthAnimator.isRunning()) { - mEdgeHintWidthAnimator.cancel(); - } - - final float targetWidth = shouldShow ? mHintMaxWidthPx : 0f; - mEdgeHintWidthAnimator = - ValueAnimator.ofFloat(mEdgeHintPaint.getStrokeWidth(), targetWidth); - mEdgeHintWidthAnimator.setDuration(HINT_WIDTH_ANIM_DURATION_MS); - mEdgeHintWidthAnimator.addUpdateListener(mEdgeHintWidthUpdateListener); - - if (shouldShow) { - startEdgeHintPulseAnimation(); - } else { - mEdgeHintWidthAnimator.start(); - } - } - - private void startTipHintPulseAnimation() { - mHandler.removeCallbacksAndMessages(null); - if (mTipHintAnimatorSet != null && mTipHintAnimatorSet.isRunning()) { - mTipHintAnimatorSet.cancel(); - } - if (mTipHintColorAnimator != null && mTipHintColorAnimator.isRunning()) { - mTipHintColorAnimator.cancel(); - } - - mTipHintColorAnimator = ValueAnimator.ofArgb(mTipHintPaint.getColor(), mHintColorHighlight); - mTipHintColorAnimator.setDuration(HINT_WIDTH_ANIM_DURATION_MS); - mTipHintColorAnimator.addUpdateListener(mTipHintColorUpdateListener); - mTipHintColorAnimator.addListener(mTipHintPulseListener); - - mTipHintAnimatorSet = new AnimatorSet(); - mTipHintAnimatorSet.setInterpolator(new AccelerateDecelerateInterpolator()); - mTipHintAnimatorSet.playTogether(mTipHintColorAnimator, mTipHintWidthAnimator); - mTipHintAnimatorSet.start(); - } - - private void startEdgeHintPulseAnimation() { - mHandler.removeCallbacksAndMessages(null); - if (mEdgeHintAnimatorSet != null && mEdgeHintAnimatorSet.isRunning()) { - mEdgeHintAnimatorSet.cancel(); - } - if (mEdgeHintColorAnimator != null && mEdgeHintColorAnimator.isRunning()) { - mEdgeHintColorAnimator.cancel(); - } - - mEdgeHintColorAnimator = - ValueAnimator.ofArgb(mEdgeHintPaint.getColor(), mHintColorHighlight); - mEdgeHintColorAnimator.setDuration(HINT_WIDTH_ANIM_DURATION_MS); - mEdgeHintColorAnimator.addUpdateListener(mEdgeHintColorUpdateListener); - mEdgeHintColorAnimator.addListener(mEdgeHintPulseListener); - - mEdgeHintAnimatorSet = new AnimatorSet(); - mEdgeHintAnimatorSet.setInterpolator(new AccelerateDecelerateInterpolator()); - mEdgeHintAnimatorSet.playTogether(mEdgeHintColorAnimator, mEdgeHintWidthAnimator); - mEdgeHintAnimatorSet.start(); - } - - private boolean isTipHintVisible() { - return mTipHintPaint.getStrokeWidth() > 0f; - } - - private boolean isEdgeHintVisible() { - return mEdgeHintPaint.getStrokeWidth() > 0f; } @Override @@ -409,58 +223,6 @@ public class UdfpsEnrollDrawable extends UdfpsDrawable { mSensorOutlinePaint.setAlpha(getAlpha()); } - // Draw the finger tip or edges hint. - if (isTipHintVisible() || isEdgeHintVisible()) { - canvas.save(); - - // Make arcs start from the top, rather than the right. - canvas.rotate(-90f, mSensorRect.centerX(), mSensorRect.centerY()); - - final float halfSensorHeight = Math.abs(mSensorRect.bottom - mSensorRect.top) / 2f; - final float halfSensorWidth = Math.abs(mSensorRect.right - mSensorRect.left) / 2f; - final float hintXOffset = halfSensorWidth + mHintPaddingPx; - final float hintYOffset = halfSensorHeight + mHintPaddingPx; - - if (isTipHintVisible()) { - canvas.drawArc( - mSensorRect.centerX() - hintXOffset, - mSensorRect.centerY() - hintYOffset, - mSensorRect.centerX() + hintXOffset, - mSensorRect.centerY() + hintYOffset, - -HINT_ANGLE / 2f, - HINT_ANGLE, - false /* useCenter */, - mTipHintPaint); - } - - if (isEdgeHintVisible()) { - // Draw right edge hint. - canvas.rotate(-90f, mSensorRect.centerX(), mSensorRect.centerY()); - canvas.drawArc( - mSensorRect.centerX() - hintXOffset, - mSensorRect.centerY() - hintYOffset, - mSensorRect.centerX() + hintXOffset, - mSensorRect.centerY() + hintYOffset, - -HINT_ANGLE / 2f, - HINT_ANGLE, - false /* useCenter */, - mEdgeHintPaint); - - // Draw left edge hint. - canvas.rotate(180f, mSensorRect.centerX(), mSensorRect.centerY()); - canvas.drawArc( - mSensorRect.centerX() - hintXOffset, - mSensorRect.centerY() - hintYOffset, - mSensorRect.centerX() + hintXOffset, - mSensorRect.centerY() + hintYOffset, - -HINT_ANGLE / 2f, - HINT_ANGLE, - false /* useCenter */, - mEdgeHintPaint); - } - - canvas.restore(); - } } @Override diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java index 8b7aa093600c..3ece3ccf38fa 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java @@ -224,7 +224,8 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud if (mUdfpsRequested && !getNotificationShadeVisible() && (!mIsBouncerVisible - || mInputBouncerHiddenAmount != KeyguardBouncer.EXPANSION_VISIBLE)) { + || mInputBouncerHiddenAmount != KeyguardBouncer.EXPANSION_VISIBLE) + && mKeyguardStateController.isShowing()) { return false; } diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt index 25985181364d..8367e1128033 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt @@ -244,7 +244,8 @@ class ControlsControllerImpl @Inject constructor ( restoreFinishedReceiver, IntentFilter(BackupHelper.ACTION_RESTORE_FINISHED), PERMISSION_SELF, - null + null, + Context.RECEIVER_NOT_EXPORTED ) listingController.addCallback(listingCallback) } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java index b23569241f59..bda8e3c2ed63 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java @@ -36,6 +36,7 @@ import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider; import com.android.wm.shell.ShellCommandHandler; import com.android.wm.shell.TaskViewFactory; import com.android.wm.shell.apppairs.AppPairs; +import com.android.wm.shell.back.BackAnimation; import com.android.wm.shell.bubbles.Bubbles; import com.android.wm.shell.compatui.CompatUI; import com.android.wm.shell.displayareahelper.DisplayAreaHelper; @@ -121,6 +122,9 @@ public interface SysUIComponent { @BindsInstance Builder setDragAndDrop(Optional<DragAndDrop> d); + @BindsInstance + Builder setBackAnimation(Optional<BackAnimation> b); + SysUIComponent build(); } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java index 154f6fad5998..bbe9dbd57f53 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java @@ -26,7 +26,6 @@ import com.android.systemui.accessibility.WindowMagnification; import com.android.systemui.biometrics.AuthController; import com.android.systemui.clipboardoverlay.ClipboardListener; import com.android.systemui.dreams.DreamOverlayRegistrant; -import com.android.systemui.dreams.appwidgets.ComplicationPrimer; import com.android.systemui.globalactions.GlobalActionsComponent; import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.keyguard.dagger.KeyguardModule; @@ -212,11 +211,4 @@ public abstract class SystemUIBinder { @ClassKey(DreamOverlayRegistrant.class) public abstract CoreStartable bindDreamOverlayRegistrant( DreamOverlayRegistrant dreamOverlayRegistrant); - - /** Inject into AppWidgetOverlayPrimer. */ - @Binds - @IntoMap - @ClassKey(ComplicationPrimer.class) - public abstract CoreStartable bindAppWidgetOverlayPrimer( - ComplicationPrimer complicationPrimer); } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java index 9bc3f176e91a..b96c5aee4673 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java @@ -48,7 +48,6 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.recents.Recents; import com.android.systemui.screenshot.dagger.ScreenshotModule; import com.android.systemui.settings.dagger.SettingsModule; -import com.android.systemui.shared.system.smartspace.SmartspaceTransitionController; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationShadeWindowController; @@ -188,12 +187,6 @@ public abstract class SystemUIModule { return SystemUIFactory.getInstance(); } - @SysUISingleton - @Provides - static SmartspaceTransitionController provideSmartspaceTransitionController() { - return new SmartspaceTransitionController(); - } - // TODO: This should provided by the WM component /** Provides Optional of BubbleManager */ @SysUISingleton diff --git a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java index b815d4e9884b..b9266923a157 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java @@ -24,6 +24,7 @@ import com.android.wm.shell.ShellCommandHandler; import com.android.wm.shell.ShellInit; import com.android.wm.shell.TaskViewFactory; import com.android.wm.shell.apppairs.AppPairs; +import com.android.wm.shell.back.BackAnimation; import com.android.wm.shell.bubbles.Bubbles; import com.android.wm.shell.compatui.CompatUI; import com.android.wm.shell.dagger.TvWMShellModule; @@ -123,4 +124,7 @@ public interface WMComponent { @WMSingleton DragAndDrop getDragAndDrop(); + + @WMSingleton + Optional<BackAnimation> getBackAnimation(); } diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java index 36319380ad50..63d4d6becc27 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java @@ -163,16 +163,12 @@ public class DozeScreenState implements DozeMachine.Part { // Delay screen state transitions even longer while animations are running. boolean shouldDelayTransitionEnteringDoze = newState == DOZE_AOD - && mParameters.shouldControlScreenOff() && !turningOn; + && mParameters.shouldDelayDisplayDozeTransition() && !turningOn; // Delay screen state transition longer if UDFPS is actively authenticating a fp boolean shouldDelayTransitionForUDFPS = newState == DOZE_AOD && mUdfpsController != null && mUdfpsController.isFingerDown(); - if (shouldDelayTransitionEnteringDoze || shouldDelayTransitionForUDFPS) { - mWakeLock.setAcquired(true); - } - if (!messagePending) { if (DEBUG) { Log.d(TAG, "Display state changed to " + screenState + " delayed by " @@ -180,6 +176,18 @@ public class DozeScreenState implements DozeMachine.Part { } if (shouldDelayTransitionEnteringDoze) { + if (justInitialized) { + // If we are delaying transitioning to doze and the display was not + // turned on we set it to 'on' first to make sure that the animation + // is visible before eventually moving it to doze state. + // The display might be off at this point for example on foldable devices + // when we switch displays and go to doze at the same time. + applyScreenState(Display.STATE_ON); + + // Restore pending screen state as it gets cleared by 'applyScreenState' + mPendingScreenState = screenState; + } + mHandler.postDelayed(mApplyPendingScreenState, ENTER_DOZE_DELAY); } else if (shouldDelayTransitionForUDFPS) { mDozeLog.traceDisplayStateDelayedByUdfps(mPendingScreenState); @@ -190,6 +198,10 @@ public class DozeScreenState implements DozeMachine.Part { } else if (DEBUG) { Log.d(TAG, "Pending display state change to " + screenState); } + + if (shouldDelayTransitionEnteringDoze || shouldDelayTransitionForUDFPS) { + mWakeLock.setAcquired(true); + } } else if (turningOff) { mDozeHost.prepareForGentleSleep(() -> applyScreenState(screenState)); } else { diff --git a/packages/SystemUI/src/com/android/systemui/dreams/ComplicationProvider.java b/packages/SystemUI/src/com/android/systemui/dreams/ComplicationProvider.java index 099e37960ad6..e5d63192f156 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/ComplicationProvider.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/ComplicationProvider.java @@ -16,8 +16,14 @@ package com.android.systemui.dreams; +import android.annotation.IntDef; import android.content.Context; +import com.android.settingslib.dream.DreamBackend; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * {@link ComplicationProvider} is an interface for defining entities that can supply complications * to show over a dream. Presentation components such as the {@link DreamOverlayService} supply @@ -25,6 +31,27 @@ import android.content.Context; */ public interface ComplicationProvider { /** + * The type of dream complications which can be provided by a {@link ComplicationProvider}. + */ + @IntDef(prefix = {"COMPLICATION_TYPE_"}, flag = true, value = { + COMPLICATION_TYPE_NONE, + COMPLICATION_TYPE_TIME, + COMPLICATION_TYPE_DATE, + COMPLICATION_TYPE_WEATHER, + COMPLICATION_TYPE_AIR_QUALITY, + COMPLICATION_TYPE_CAST_INFO + }) + @Retention(RetentionPolicy.SOURCE) + @interface ComplicationType {} + + int COMPLICATION_TYPE_NONE = 0; + int COMPLICATION_TYPE_TIME = 1; + int COMPLICATION_TYPE_DATE = 1 << 1; + int COMPLICATION_TYPE_WEATHER = 1 << 2; + int COMPLICATION_TYPE_AIR_QUALITY = 1 << 3; + int COMPLICATION_TYPE_CAST_INFO = 1 << 4; + + /** * Called when the {@link ComplicationHost} requests the associated complication be produced. * * @param context The {@link Context} used to construct the view. @@ -33,4 +60,26 @@ public interface ComplicationProvider { */ void onCreateComplication(Context context, ComplicationHost.CreationCallback creationCallback, ComplicationHost.InteractionCallback interactionCallback); + + /** + * Converts a {@link com.android.settingslib.dream.DreamBackend.ComplicationType} to + * {@link ComplicationType}. + */ + @ComplicationType + default int convertComplicationType(@DreamBackend.ComplicationType int type) { + switch (type) { + case DreamBackend.COMPLICATION_TYPE_TIME: + return COMPLICATION_TYPE_TIME; + case DreamBackend.COMPLICATION_TYPE_DATE: + return COMPLICATION_TYPE_DATE; + case DreamBackend.COMPLICATION_TYPE_WEATHER: + return COMPLICATION_TYPE_WEATHER; + case DreamBackend.COMPLICATION_TYPE_AIR_QUALITY: + return COMPLICATION_TYPE_AIR_QUALITY; + case DreamBackend.COMPLICATION_TYPE_CAST_INFO: + return COMPLICATION_TYPE_CAST_INFO; + default: + return COMPLICATION_TYPE_NONE; + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/appwidgets/AppWidgetProvider.java b/packages/SystemUI/src/com/android/systemui/dreams/appwidgets/AppWidgetProvider.java deleted file mode 100644 index 687f7a296b1a..000000000000 --- a/packages/SystemUI/src/com/android/systemui/dreams/appwidgets/AppWidgetProvider.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.dreams.appwidgets; - -import android.appwidget.AppWidgetHost; -import android.appwidget.AppWidgetHostView; -import android.appwidget.AppWidgetManager; -import android.appwidget.AppWidgetProviderInfo; -import android.content.ComponentName; -import android.content.Context; -import android.content.res.Resources; -import android.util.Log; - -import com.android.systemui.dagger.SysUISingleton; -import com.android.systemui.dagger.qualifiers.Main; - -import java.util.List; - -import javax.inject.Inject; - -/** - * {@link AppWidgetProvider} is a singleton for accessing app widgets within SystemUI. This - * consolidates resources such as the {@link AppWidgetHost} across potentially multiple - * {@link ComplicationProvider} instances and other usages. - */ -@SysUISingleton -public class AppWidgetProvider { - private static final String TAG = "AppWidgetProvider"; - public static final int APP_WIDGET_HOST_ID = 1025; - - private final Context mContext; - private final AppWidgetManager mAppWidgetManager; - private final AppWidgetHost mAppWidgetHost; - private final Resources mResources; - - @Inject - public AppWidgetProvider(Context context, @Main Resources resources) { - mContext = context; - mResources = resources; - mAppWidgetManager = android.appwidget.AppWidgetManager.getInstance(context); - mAppWidgetHost = new AppWidgetHost(context, APP_WIDGET_HOST_ID); - mAppWidgetHost.startListening(); - } - - /** - * Returns an {@link AppWidgetHostView} associated with a given {@link ComponentName}. - * @param component The {@link ComponentName} of the target {@link AppWidgetHostView}. - * @return The {@link AppWidgetHostView} or {@code null} on error. - */ - public AppWidgetHostView getWidget(ComponentName component) { - final List<AppWidgetProviderInfo> appWidgetInfos = - mAppWidgetManager.getInstalledProviders(); - - for (AppWidgetProviderInfo widgetInfo : appWidgetInfos) { - if (widgetInfo.provider.equals(component)) { - final int widgetId = mAppWidgetHost.allocateAppWidgetId(); - - boolean success = mAppWidgetManager.bindAppWidgetIdIfAllowed(widgetId, - widgetInfo.provider); - - if (!success) { - Log.e(TAG, "could not bind to app widget:" + component); - break; - } - - final AppWidgetHostView appWidgetView = - mAppWidgetHost.createView(mContext, widgetId, widgetInfo); - - if (appWidgetView != null) { - // Register a layout change listener to update the widget on any sizing changes. - appWidgetView.addOnLayoutChangeListener( - (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> { - final float density = mResources.getDisplayMetrics().density; - final int height = Math.round((bottom - top) / density); - final int width = Math.round((right - left) / density); - appWidgetView.updateAppWidgetSize(null, width, height, - width, height); - }); - } - - return appWidgetView; - } - } - - return null; - } -} diff --git a/packages/SystemUI/src/com/android/systemui/dreams/appwidgets/ComplicationPrimer.java b/packages/SystemUI/src/com/android/systemui/dreams/appwidgets/ComplicationPrimer.java deleted file mode 100644 index 7d30fafda8a6..000000000000 --- a/packages/SystemUI/src/com/android/systemui/dreams/appwidgets/ComplicationPrimer.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.dreams.appwidgets; - -import android.content.ComponentName; -import android.content.Context; -import android.content.res.Resources; -import android.view.Gravity; - -import androidx.constraintlayout.widget.ConstraintSet; - -import com.android.systemui.CoreStartable; -import com.android.systemui.R; -import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.dreams.ComplicationHostView; -import com.android.systemui.dreams.DreamOverlayStateController; -import com.android.systemui.dreams.appwidgets.dagger.AppWidgetComponent; - -import javax.inject.Inject; - -/** - * {@link ComplicationPrimer} reads the configured AppWidget Complications from resources on start - * and populates them into the {@link DreamOverlayStateController}. - */ -public class ComplicationPrimer extends CoreStartable { - private final Resources mResources; - private final DreamOverlayStateController mDreamOverlayStateController; - private final AppWidgetComponent.Factory mComponentFactory; - - @Inject - public ComplicationPrimer(Context context, @Main Resources resources, - DreamOverlayStateController overlayStateController, - AppWidgetComponent.Factory appWidgetOverlayFactory) { - super(context); - mResources = resources; - mDreamOverlayStateController = overlayStateController; - mComponentFactory = appWidgetOverlayFactory; - } - - @Override - public void start() { - } - - @Override - protected void onBootCompleted() { - super.onBootCompleted(); - loadDefaultWidgets(); - } - - /** - * Generates the {@link ComplicationHostView.LayoutParams} for a given gravity. Default - * dimension constraints are also included in the params. - * @param gravity The gravity for the layout as defined by {@link Gravity}. - * @param resources The resourcs from which default dimensions will be extracted from. - * @return {@link ComplicationHostView.LayoutParams} representing the provided gravity and - * default parameters. - */ - private static ComplicationHostView.LayoutParams getLayoutParams(int gravity, - Resources resources) { - final ComplicationHostView.LayoutParams params = new ComplicationHostView.LayoutParams( - ComplicationHostView.LayoutParams.MATCH_CONSTRAINT, - ComplicationHostView.LayoutParams.MATCH_CONSTRAINT); - - if ((gravity & Gravity.BOTTOM) == Gravity.BOTTOM) { - params.bottomToBottom = ConstraintSet.PARENT_ID; - } - - if ((gravity & Gravity.TOP) == Gravity.TOP) { - params.topToTop = ConstraintSet.PARENT_ID; - } - - if ((gravity & Gravity.END) == Gravity.END) { - params.endToEnd = ConstraintSet.PARENT_ID; - } - - if ((gravity & Gravity.START) == Gravity.START) { - params.startToStart = ConstraintSet.PARENT_ID; - } - - // For now, apply the same sizing constraints on every widget. - params.matchConstraintPercentHeight = - resources.getFloat(R.dimen.config_dreamComplicationHeightPercent); - params.matchConstraintPercentWidth = - resources.getFloat(R.dimen.config_dreamComplicationWidthPercent); - - return params; - } - - /** - * Helper method for loading widgets based on configuration. - */ - private void loadDefaultWidgets() { - final int[] positions = mResources.getIntArray(R.array.config_dreamComplicationPositions); - final String[] components = - mResources.getStringArray(R.array.config_dreamAppWidgetComplications); - - for (int i = 0; i < Math.min(positions.length, components.length); i++) { - final AppWidgetComponent component = mComponentFactory.build( - ComponentName.unflattenFromString(components[i]), - getLayoutParams(positions[i], mResources)); - - mDreamOverlayStateController.addComplication( - component.getAppWidgetComplicationProvider()); - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/dreams/appwidgets/ComplicationProvider.java b/packages/SystemUI/src/com/android/systemui/dreams/appwidgets/ComplicationProvider.java deleted file mode 100644 index 9188ee54d855..000000000000 --- a/packages/SystemUI/src/com/android/systemui/dreams/appwidgets/ComplicationProvider.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.dreams.appwidgets; - -import android.appwidget.AppWidgetHostView; -import android.content.ComponentName; -import android.content.Context; -import android.util.Log; -import android.widget.RemoteViews; - -import com.android.systemui.dreams.ComplicationHost; -import com.android.systemui.dreams.ComplicationHostView; -import com.android.systemui.plugins.ActivityStarter; - -import javax.inject.Inject; - -/** - * {@link ComplicationProvider} is an implementation of - * {@link com.android.systemui.dreams.ComplicationProvider} for providing app widget-based - * complications. - */ -public class ComplicationProvider implements com.android.systemui.dreams.ComplicationProvider { - private static final String TAG = "AppWidgetCompProvider"; - private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); - - private final ActivityStarter mActivityStarter; - private final AppWidgetProvider mAppWidgetProvider; - private final ComponentName mComponentName; - private final ComplicationHostView.LayoutParams mLayoutParams; - - @Inject - public ComplicationProvider(ActivityStarter activityStarter, - ComponentName componentName, AppWidgetProvider widgetProvider, - ComplicationHostView.LayoutParams layoutParams) { - mActivityStarter = activityStarter; - mComponentName = componentName; - mAppWidgetProvider = widgetProvider; - mLayoutParams = layoutParams; - } - - @Override - public void onCreateComplication(Context context, - ComplicationHost.CreationCallback creationCallback, - ComplicationHost.InteractionCallback interactionCallback) { - final AppWidgetHostView widget = mAppWidgetProvider.getWidget(mComponentName); - - if (widget == null) { - Log.e(TAG, "could not create widget"); - return; - } - - widget.setInteractionHandler((view, pendingIntent, response) -> { - if (pendingIntent.isActivity()) { - if (DEBUG) { - Log.d(TAG, "launching pending intent from app widget:" + mComponentName); - } - interactionCallback.onExit(); - mActivityStarter.startPendingIntentDismissingKeyguard(pendingIntent, - null /*intentSentUiThreadCallback*/, view); - return true; - } else { - return RemoteViews.startPendingIntent(view, pendingIntent, - response.getLaunchOptions(view)); - } - }); - - creationCallback.onCreated(widget, mLayoutParams); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/dreams/appwidgets/dagger/AppWidgetComponent.java b/packages/SystemUI/src/com/android/systemui/dreams/appwidgets/dagger/AppWidgetComponent.java deleted file mode 100644 index 7beed176eeca..000000000000 --- a/packages/SystemUI/src/com/android/systemui/dreams/appwidgets/dagger/AppWidgetComponent.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.dreams.appwidgets.dagger; - -import android.content.ComponentName; - -import com.android.systemui.dreams.ComplicationHostView; -import com.android.systemui.dreams.appwidgets.ComplicationProvider; - -import dagger.BindsInstance; -import dagger.Subcomponent; - -/** */ -@Subcomponent -public interface AppWidgetComponent { - /** */ - @Subcomponent.Factory - interface Factory { - AppWidgetComponent build(@BindsInstance ComponentName component, - @BindsInstance ComplicationHostView.LayoutParams layoutParams); - } - - /** Builds a {@link ComplicationProvider}. */ - ComplicationProvider getAppWidgetComplicationProvider(); -} diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java index 0d4688ec880c..072f50db64f6 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java @@ -16,15 +16,12 @@ package com.android.systemui.dreams.dagger; -import com.android.systemui.dreams.appwidgets.dagger.AppWidgetComponent; - import dagger.Module; /** * Dagger Module providing Communal-related functionality. */ @Module(subcomponents = { - AppWidgetComponent.class, DreamOverlayComponent.class}) public interface DreamModule { -} +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java index 5d6c2a247df3..e24df30bfe34 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java @@ -74,9 +74,6 @@ public class Flags { public static final ResourceBooleanFlag BOUNCER_USER_SWITCHER = new ResourceBooleanFlag(204, R.bool.config_enableBouncerUserSwitcher); - public static final ResourceBooleanFlag ACTIVE_UNLOCK = - new ResourceBooleanFlag(205, R.bool.flag_active_unlock); - /***************************************/ // 300 - power menu public static final BooleanFlag POWER_MENU_LITE = @@ -88,7 +85,7 @@ public class Flags { new BooleanFlag(400, true); public static final BooleanFlag SMARTSPACE_SHARED_ELEMENT_TRANSITION_ENABLED = - new BooleanFlag(401, false); + new BooleanFlag(401, true); public static final ResourceBooleanFlag SMARTSPACE = new ResourceBooleanFlag(402, R.bool.flag_smartspace); diff --git a/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java b/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java index b24d08d3f8bb..3ae11ff28345 100644 --- a/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java +++ b/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java @@ -54,7 +54,7 @@ public class FragmentHostManager { private final View mRootView; private final InterestingConfigChanges mConfigChanges = new InterestingConfigChanges( ActivityInfo.CONFIG_FONT_SCALE | ActivityInfo.CONFIG_LOCALE - | ActivityInfo.CONFIG_SCREEN_LAYOUT | ActivityInfo.CONFIG_ASSETS_PATHS); + | ActivityInfo.CONFIG_LAYOUT_DIRECTION | ActivityInfo.CONFIG_ASSETS_PATHS); private final FragmentService mManager; private final ExtensionFragmentManager mPlugins = new ExtensionFragmentManager(); diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt index 69bcf2ec8b8d..582965a12528 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt @@ -21,6 +21,8 @@ import android.animation.AnimatorListenerAdapter import android.animation.ValueAnimator import android.content.Context import android.graphics.Matrix +import android.graphics.Rect +import android.os.Handler import android.view.RemoteAnimationTarget import android.view.SyncRtSurfaceTransactionApplier import android.view.View @@ -33,11 +35,17 @@ import com.android.systemui.animation.Interpolators import com.android.systemui.dagger.SysUISingleton import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags -import com.android.systemui.shared.system.smartspace.SmartspaceTransitionController +import com.android.systemui.plugins.BcSmartspaceDataPlugin +import com.android.systemui.shared.system.ActivityManagerWrapper +import com.android.systemui.shared.system.QuickStepContract +import com.android.systemui.shared.system.smartspace.ILauncherUnlockAnimationController +import com.android.systemui.shared.system.smartspace.ISysuiUnlockAnimationController +import com.android.systemui.shared.system.smartspace.SmartspaceState import com.android.systemui.statusbar.phone.BiometricUnlockController import com.android.systemui.statusbar.policy.KeyguardStateController import dagger.Lazy import javax.inject.Inject +import kotlin.math.min /** * Starting scale factor for the app/launcher surface behind the keyguard, when it's animating @@ -76,6 +84,25 @@ const val DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD = 0.25f const val DISMISS_AMOUNT_EXIT_KEYGUARD_THRESHOLD = 0.4f /** + * How long the canned unlock animation takes. This is used if we are unlocking from biometric auth, + * from a tap on the unlock icon, or from the bouncer. This is not relevant if the lockscreen is + * swiped away via a touch gesture, or when it's flinging expanded/collapsed after a swipe. + */ +const val UNLOCK_ANIMATION_DURATION_MS = 200L + +/** + * Duration for the alpha animation on the surface behind. This plays to fade in the surface during + * a swipe to unlock (and to fade it back out if the swipe is cancelled). + */ +const val SURFACE_BEHIND_SWIPE_FADE_DURATION_MS = 150L + +/** + * Start delay for the surface behind animation, used so that the lockscreen can get out of the way + * before the surface begins appearing. + */ +const val UNLOCK_ANIMATION_SURFACE_BEHIND_START_DELAY_MS = 75L + +/** * Initiates, controls, and ends the keyguard unlock animation. * * The unlock animation transitions between the keyguard (lock screen) and the app/launcher surface @@ -85,7 +112,7 @@ const val DISMISS_AMOUNT_EXIT_KEYGUARD_THRESHOLD = 0.4f * this controller will play a canned animation on the surface as well. * * The surface behind the keyguard is manipulated via a RemoteAnimation passed to - * [notifyStartKeyguardExitAnimation] by [KeyguardViewMediator]. + * [notifyStartSurfaceBehindRemoteAnimation] by [KeyguardViewMediator]. */ @SysUISingleton class KeyguardUnlockAnimationController @Inject constructor( @@ -94,10 +121,99 @@ class KeyguardUnlockAnimationController @Inject constructor( private val keyguardViewMediator: Lazy<KeyguardViewMediator>, private val keyguardViewController: KeyguardViewController, - private val smartspaceTransitionController: SmartspaceTransitionController, private val featureFlags: FeatureFlags, - private val biometricUnlockController: BiometricUnlockController -) : KeyguardStateController.Callback { + private val biometricUnlockControllerLazy: Lazy<BiometricUnlockController> +) : KeyguardStateController.Callback, ISysuiUnlockAnimationController.Stub() { + + interface KeyguardUnlockAnimationListener { + /** + * Called when the remote unlock animation, controlled by + * [KeyguardUnlockAnimationController], first starts. + * + * [playingCannedAnimation] indicates whether we are playing a canned animation to show the + * app/launcher behind the keyguard, vs. this being a swipe to unlock where the dismiss + * amount drives the animation. + * [fromWakeAndUnlock] tells us whether we are unlocking directly from AOD - in this case, + * the lockscreen is dismissed instantly, so we shouldn't run any animations that rely on it + * being visible. + */ + @JvmDefault + fun onUnlockAnimationStarted(playingCannedAnimation: Boolean, fromWakeAndUnlock: Boolean) {} + + /** + * Called when the remote unlock animation ends, in all cases, canned or swipe-to-unlock. + * The keyguard is no longer visible in this state and the app/launcher behind the keyguard + * is now completely visible. + */ + @JvmDefault + fun onUnlockAnimationFinished() {} + + /** + * Called when we begin the smartspace shared element transition, either due to an unlock + * action (biometric, etc.) or a swipe to unlock. + * + * This transition can begin BEFORE [onUnlockAnimationStarted] is called, if we are swiping + * to unlock and the surface behind the keyguard has not yet been made visible. This is + * because the lockscreen smartspace immediately begins moving towards the launcher + * smartspace location when a swipe begins, even before we start the keyguard exit remote + * animation and show the launcher itself. + */ + @JvmDefault + fun onSmartspaceSharedElementTransitionStarted() {} + } + + /** The SmartSpace view on the lockscreen, provided by [KeyguardClockSwitchController]. */ + var lockscreenSmartspace: View? = null + + /** + * The state of the Launcher's smartspace, delivered via [onLauncherSmartspaceStateUpdated]. + * This is pushed to us from Launcher whenever their smartspace moves or its visibility changes. + * We'll animate the lockscreen smartspace to this location during an unlock. + */ + var launcherSmartspaceState: SmartspaceState? = null + + /** + * Whether a canned unlock animation is playing, vs. currently unlocking in response to a swipe + * gesture or panel fling. If we're swiping/flinging, the unlock animation is driven by the + * dismiss amount, via [onKeyguardDismissAmountChanged]. If we're using a canned animation, it's + * being driven by ValueAnimators started in [playCannedUnlockAnimation]. + */ + var playingCannedUnlockAnimation = false + + /** + * Remote callback provided by Launcher that allows us to control the Launcher's unlock + * animation and smartspace. + * + * If this is null, we will not be animating any Launchers today and should fall back to window + * animations. + */ + private var launcherUnlockController: ILauncherUnlockAnimationController? = null + + private val listeners = ArrayList<KeyguardUnlockAnimationListener>() + + /** + * Called from SystemUiProxy to pass us the launcher's unlock animation controller. If this + * doesn't happen, we won't use in-window animations or the smartspace shared element + * transition, but that's okay! + */ + override fun setLauncherUnlockController(callback: ILauncherUnlockAnimationController?) { + launcherUnlockController = callback + + // If the provided callback dies, set it to null. We'll always check whether it's null + // to avoid DeadObjectExceptions. + callback?.asBinder()?.linkToDeath({ + launcherUnlockController = null + launcherSmartspaceState = null + }, 0 /* flags */) + } + + /** + * Called from SystemUiProxy to pass us the latest state of the Launcher's smartspace. This is + * only done when the state has changed in some way. + */ + override fun onLauncherSmartspaceStateUpdated(state: SmartspaceState?) { + launcherSmartspaceState = state + } /** * Information used to start, run, and finish a RemoteAnimation on the app or launcher surface @@ -105,15 +221,16 @@ class KeyguardUnlockAnimationController @Inject constructor( * * If we're swiping to unlock, the "animation" is controlled via the gesture, tied to the * dismiss amounts received in [onKeyguardDismissAmountChanged]. It does not have a fixed - * duration, and it ends when the gesture reaches a certain threshold or is cancelled. + * duration, and it ends when the gesture reaches a certain threshold or is cancell * * If we're unlocking via biometrics, PIN entry, or from clicking a notification, a canned - * animation is started in [notifyStartKeyguardExitAnimation]. + * animation is started in [playCannedUnlockAnimation]. */ @VisibleForTesting var surfaceTransactionApplier: SyncRtSurfaceTransactionApplier? = null private var surfaceBehindRemoteAnimationTarget: RemoteAnimationTarget? = null private var surfaceBehindRemoteAnimationStartTime: Long = 0 + private var surfaceBehindParams: SyncRtSurfaceTransactionApplier.SurfaceParams? = null /** * Alpha value applied to [surfaceBehindRemoteAnimationTarget], which is the surface of the @@ -125,6 +242,7 @@ class KeyguardUnlockAnimationController @Inject constructor( */ private var surfaceBehindAlpha = 1f private var surfaceBehindAlphaAnimator = ValueAnimator.ofFloat(0f, 1f) + private var smartspaceAnimator = ValueAnimator.ofFloat(0f, 1f) /** * Matrix applied to [surfaceBehindRemoteAnimationTarget], which is the surface of the @@ -144,57 +262,113 @@ class KeyguardUnlockAnimationController @Inject constructor( /** Rounded corner radius to apply to the surface behind the keyguard. */ private var roundedCornerRadius = 0f - /** The SmartSpace view on the lockscreen, provided by [KeyguardClockSwitchController]. */ - public var lockscreenSmartSpace: View? = null + /** + * Whether we tried to start the SmartSpace shared element transition for this unlock swipe. + * It's possible we were unable to do so (if the Launcher SmartSpace is not available), and we + * need to keep track of that so that we don't start doing it halfway through the swipe if + * Launcher becomes available suddenly. + */ + private var attemptedSmartSpaceTransitionForThisSwipe = false /** - * Whether we are currently in the process of unlocking the keyguard, and we are performing the - * shared element SmartSpace transition. + * The original location of the lockscreen smartspace on the screen. */ - private var unlockingWithSmartSpaceTransition: Boolean = false + private val smartspaceOriginBounds = Rect() /** - * Whether we tried to start the SmartSpace shared element transition for this unlock swipe. - * It's possible we're unable to do so (if the Launcher SmartSpace is not available). + * The bounds to which the lockscreen smartspace is moving. This is set to the bounds of the + * launcher's smartspace prior to the transition starting. */ - private var attemptedSmartSpaceTransitionForThisSwipe = false + private val smartspaceDestBounds = Rect() + + /** + * From 0f to 1f, the progress of the smartspace shared element animation. 0f means the + * smartspace is at its normal position within the lock screen hierarchy, and 1f means it has + * fully animated to the location of the Launcher's smartspace. + */ + private var smartspaceUnlockProgress = 0f + + /** + * Whether we're currently unlocking, and we're talking to Launcher to perform in-window + * animations rather than simply animating the Launcher window like any other app. This can be + * true while [unlockingWithSmartspaceTransition] is false, if the smartspace is not available + * or was not ready in time. + */ + private var unlockingToLauncherWithInWindowAnimations: Boolean = false + + /** + * Whether we are currently unlocking, and the smartspace shared element transition is in + * progress. If true, we're also [unlockingToLauncherWithInWindowAnimations]. + */ + private var unlockingWithSmartspaceTransition: Boolean = false + + private val handler = Handler() init { - surfaceBehindAlphaAnimator.duration = 150 - surfaceBehindAlphaAnimator.interpolator = Interpolators.ALPHA_IN - surfaceBehindAlphaAnimator.addUpdateListener { valueAnimator: ValueAnimator -> - surfaceBehindAlpha = valueAnimator.animatedValue as Float - updateSurfaceBehindAppearAmount() - } - surfaceBehindAlphaAnimator.addListener(object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - // If the surface alpha is 0f, it's no longer visible so we can safely be done with - // the animation. - if (surfaceBehindAlpha == 0f) { - keyguardViewMediator.get().finishSurfaceBehindRemoteAnimation( + with(surfaceBehindAlphaAnimator) { + duration = SURFACE_BEHIND_SWIPE_FADE_DURATION_MS + interpolator = Interpolators.TOUCH_RESPONSE + addUpdateListener { valueAnimator: ValueAnimator -> + surfaceBehindAlpha = valueAnimator.animatedValue as Float + updateSurfaceBehindAppearAmount() + } + addListener(object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator) { + // If the surface alpha is 0f, it's no longer visible so we can safely be done + // with the animation even if other properties are still animating. + if (surfaceBehindAlpha == 0f) { + keyguardViewMediator.get().finishSurfaceBehindRemoteAnimation( false /* cancelled */) + } } - } - }) + }) + } - surfaceBehindEntryAnimator.duration = 450 - surfaceBehindEntryAnimator.interpolator = Interpolators.DECELERATE_QUINT - surfaceBehindEntryAnimator.addUpdateListener { valueAnimator: ValueAnimator -> - surfaceBehindAlpha = valueAnimator.animatedValue as Float - setSurfaceBehindAppearAmount(valueAnimator.animatedValue as Float) + with(surfaceBehindEntryAnimator) { + duration = UNLOCK_ANIMATION_DURATION_MS + startDelay = UNLOCK_ANIMATION_SURFACE_BEHIND_START_DELAY_MS + interpolator = Interpolators.TOUCH_RESPONSE + addUpdateListener { valueAnimator: ValueAnimator -> + surfaceBehindAlpha = valueAnimator.animatedValue as Float + setSurfaceBehindAppearAmount(valueAnimator.animatedValue as Float) + } + addListener(object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator) { + playingCannedUnlockAnimation = false + keyguardViewMediator.get().onKeyguardExitRemoteAnimationFinished( + false /* cancelled */ + ) + } + }) } - surfaceBehindEntryAnimator.addListener(object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - keyguardViewMediator.get().onKeyguardExitRemoteAnimationFinished( - false /* cancelled */) + + with(smartspaceAnimator) { + duration = UNLOCK_ANIMATION_DURATION_MS + interpolator = Interpolators.TOUCH_RESPONSE + addUpdateListener { + smartspaceUnlockProgress = it.animatedValue as Float } - }) + addListener(object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator?) { + launcherUnlockController?.setSmartspaceVisibility(View.VISIBLE) + keyguardViewMediator.get().onKeyguardExitRemoteAnimationFinished( + false /* cancelled */) + } + }) + } // Listen for changes in the dismiss amount. keyguardStateController.addCallback(this) roundedCornerRadius = - context.resources.getDimensionPixelSize(R.dimen.rounded_corner_radius).toFloat() + context.resources.getDimensionPixelSize(R.dimen.rounded_corner_radius).toFloat() + } + + /** + * Add a listener to be notified of various stages of the unlock animation. + */ + fun addKeyguardUnlockAnimationListener(listener: KeyguardUnlockAnimationListener) { + listeners.add(listener) } /** @@ -203,78 +377,220 @@ class KeyguardUnlockAnimationController @Inject constructor( * surface for the unlock gesture/animation. * * When we're done with it, we'll call [KeyguardViewMediator.finishSurfaceBehindRemoteAnimation] - * to end the RemoteAnimation. + * to end the RemoteAnimation. The KeyguardViewMediator will then end the animation and let us + * know that it's over by calling [notifyFinishedKeyguardExitAnimation]. * - * [requestedShowSurfaceBehindKeyguard] denotes whether the exit animation started because of a + * [requestedShowSurfaceBehindKeyguard] indicates whether the animation started because of a * call to [KeyguardViewMediator.showSurfaceBehindKeyguard], as happens during a swipe gesture, - * as opposed to the keyguard hiding. + * as opposed to being called because the device was unlocked and the keyguard is going away. */ - fun notifyStartKeyguardExitAnimation( + fun notifyStartSurfaceBehindRemoteAnimation( target: RemoteAnimationTarget, startTime: Long, requestedShowSurfaceBehindKeyguard: Boolean ) { - if (surfaceTransactionApplier == null) { surfaceTransactionApplier = SyncRtSurfaceTransactionApplier( keyguardViewController.viewRootImpl.view) } + // New animation, new params. + surfaceBehindParams = null + surfaceBehindRemoteAnimationTarget = target surfaceBehindRemoteAnimationStartTime = startTime - // If the surface behind wasn't made visible during a swipe, we'll do a canned animation - // to animate it in. Otherwise, the swipe touch events will continue animating it. + // If we specifically requested that the surface behind be made visible, it means we are + // swiping to unlock. In that case, the surface visibility is tied to the dismiss amount, + // and we'll handle that in onKeyguardDismissAmountChanged(). If we didn't request that, the + // keyguard is being dismissed for a different reason (biometric auth, etc.) and we should + // play a canned animation to make the surface fully visible. if (!requestedShowSurfaceBehindKeyguard) { - keyguardViewController.hide(startTime, 350) - - // If we're wake and unlocking, we don't want to animate the surface since we're going - // to do the light reveal scrim from the black AOD screen. Make it visible and end the - // remote aimation. - if (biometricUnlockController.isWakeAndUnlock) { - setSurfaceBehindAppearAmount(1f) - keyguardViewMediator.get().onKeyguardExitRemoteAnimationFinished( - false /* cancelled */) - } else { - // Otherwise, animate it in normally. - surfaceBehindEntryAnimator.start() - } + playCannedUnlockAnimation() } + listeners.forEach { + it.onUnlockAnimationStarted( + playingCannedUnlockAnimation /* playingCannedAnimation */, + biometricUnlockControllerLazy.get().isWakeAndUnlock /* isWakeAndUnlock */) } + // Finish the keyguard remote animation if the dismiss amount has crossed the threshold. // Check it here in case there is no more change to the dismiss amount after the last change // that starts the keyguard animation. @see #updateKeyguardViewMediatorIfThresholdsReached() finishKeyguardExitRemoteAnimationIfReachThreshold() } - fun notifyCancelKeyguardExitAnimation() { + /** + * Called by [KeyguardViewMediator] to let us know that the remote animation has finished, and + * we should clean up all of our state. + */ + fun notifyFinishedKeyguardExitAnimation(cancelled: Boolean) { + // Cancel any pending actions. + handler.removeCallbacksAndMessages(null) + + // Make sure we made the surface behind fully visible, just in case. It should already be + // fully visible. + setSurfaceBehindAppearAmount(1f) + launcherUnlockController?.setUnlockAmount(1f) + smartspaceDestBounds.setEmpty() + + // That target is no longer valid since the animation finished, null it out. surfaceBehindRemoteAnimationTarget = null + surfaceBehindParams = null + + playingCannedUnlockAnimation = false + unlockingToLauncherWithInWindowAnimations = false + unlockingWithSmartspaceTransition = false + resetSmartspaceTransition() + + listeners.forEach { it.onUnlockAnimationFinished() } } - fun notifyFinishedKeyguardExitAnimation() { - surfaceBehindRemoteAnimationTarget = null + /** + * Play a canned unlock animation to unlock the device. This is used when we were *not* swiping + * to unlock using a touch gesture. If we were swiping to unlock, the animation will be driven + * by the dismiss amount via [onKeyguardDismissAmountChanged]. + */ + fun playCannedUnlockAnimation() { + playingCannedUnlockAnimation = true + + if (canPerformInWindowLauncherAnimations()) { + // If possible, use the neat in-window animations to unlock to the launcher. + unlockToLauncherWithInWindowAnimations() + } else if (!biometricUnlockControllerLazy.get().isWakeAndUnlock) { + // If the launcher isn't behind the keyguard, or the launcher unlock controller is not + // available, animate in the entire window. + surfaceBehindEntryAnimator.start() + } else { + setSurfaceBehindAppearAmount(1f) + keyguardViewMediator.get().onKeyguardExitRemoteAnimationFinished(false) + } + + // If this is a wake and unlock, hide the lockscreen immediately. In the future, we should + // animate it out nicely instead, but to the current state of wake and unlock, not hiding it + // causes a lot of issues. + // TODO(b/210016643): Not this, it looks not-ideal! + if (biometricUnlockControllerLazy.get().isWakeAndUnlock) { + keyguardViewController.hide(surfaceBehindRemoteAnimationStartTime, 350) + } } - fun hideKeyguardViewAfterRemoteAnimation() { - keyguardViewController.hide(surfaceBehindRemoteAnimationStartTime, 350) + /** + * Unlock to the launcher, using in-window animations, and the smartspace shared element + * transition if possible. + */ + private fun unlockToLauncherWithInWindowAnimations() { + unlockingToLauncherWithInWindowAnimations = true + + // See if we can do the smartspace transition, and if so, do it! + if (prepareForSmartspaceTransition()) { + animateSmartspaceToDestination() + listeners.forEach { it.onSmartspaceSharedElementTransitionStarted() } + } + + // Tell the launcher to prepare for the animation by setting its views invisible and + // syncing the selected smartspace pages. + launcherUnlockController?.prepareForUnlock( + unlockingWithSmartspaceTransition /* willAnimateSmartspace */, + (lockscreenSmartspace as BcSmartspaceDataPlugin.SmartspaceView?)?.selectedPage ?: -1) + + // Begin the animation. + launcherUnlockController?.playUnlockAnimation( + true /* unlocked */, UNLOCK_ANIMATION_DURATION_MS) + if (!unlockingWithSmartspaceTransition) { + // If we are not unlocking with the smartspace transition, wait for the unlock animation + // to end and then finish the remote animation. If we are using the smartspace + // transition, it will finish the remote animation once it ends. + handler.postDelayed({ + keyguardViewMediator.get().onKeyguardExitRemoteAnimationFinished( + false /* cancelled */) + }, UNLOCK_ANIMATION_DURATION_MS) + } + + // Wait a moment, then show the launcher surface. + setSurfaceBehindAppearAmount(1f) } /** - * Whether we are currently in the process of unlocking the keyguard, and we are performing the - * shared element SmartSpace transition. + * Animates the lockscreen smartspace all the way to the launcher's smartspace location, then + * makes the launcher smartspace visible and ends the remote animation. */ - fun isUnlockingWithSmartSpaceTransition(): Boolean { - return unlockingWithSmartSpaceTransition + private fun animateSmartspaceToDestination() { + smartspaceAnimator.start() + } + + /** + * Reset the lockscreen smartspace's position, and reset all state involving the smartspace + * transition. + */ + public fun resetSmartspaceTransition() { + unlockingWithSmartspaceTransition = false + smartspaceUnlockProgress = 0f + + lockscreenSmartspace?.post { + lockscreenSmartspace!!.translationX = 0f + lockscreenSmartspace!!.translationY = 0f + } + } + + /** + * Moves the lockscreen smartspace towards the launcher smartspace's position. + */ + private fun setSmartspaceProgressToDestinationBounds(progress: Float) { + if (smartspaceDestBounds.isEmpty) { + return + } + + val progressClamped = min(1f, progress) + + // Calculate the distance (relative to the origin) that we need to be for the current + // progress value. + val progressX = + (smartspaceDestBounds.left - smartspaceOriginBounds.left) * progressClamped + val progressY = + (smartspaceDestBounds.top - smartspaceOriginBounds.top) * progressClamped + + val lockscreenSmartspaceCurrentBounds = Rect().also { + lockscreenSmartspace!!.getBoundsOnScreen(it) + } + + // Figure out how far that is from our present location on the screen. This approach + // compensates for the fact that our parent container is also translating to animate out. + val dx = smartspaceOriginBounds.left + progressX - + lockscreenSmartspaceCurrentBounds.left + val dy = smartspaceOriginBounds.top + progressY - + lockscreenSmartspaceCurrentBounds.top + + with(lockscreenSmartspace!!) { + translationX += dx + translationY += dy + } } /** * Update the lockscreen SmartSpace to be positioned according to the current dismiss amount. As * the dismiss amount increases, we will increase our SmartSpace's progress to the destination * bounds (the location of the Launcher SmartSpace). + * + * This is used by [KeyguardClockSwitchController] to keep the smartspace position updated as + * the clock is swiped away. */ fun updateLockscreenSmartSpacePosition() { - smartspaceTransitionController.setProgressToDestinationBounds( - keyguardStateController.dismissAmount / DISMISS_AMOUNT_EXIT_KEYGUARD_THRESHOLD) + setSmartspaceProgressToDestinationBounds(smartspaceUnlockProgress) + } + + /** + * Asks the keyguard view to hide, using the start time from the beginning of the remote + * animation. + */ + fun hideKeyguardViewAfterRemoteAnimation() { + // Hide the keyguard, with no fade out since we animated it away during the unlock. + keyguardViewController.hide(surfaceBehindRemoteAnimationStartTime, 0 /* fadeOutDuration */) + } + + private fun applyParamsToSurface(params: SyncRtSurfaceTransactionApplier.SurfaceParams) { + surfaceTransactionApplier!!.scheduleApply(params) + surfaceBehindParams = params } /** @@ -287,34 +603,56 @@ class KeyguardUnlockAnimationController @Inject constructor( return } - val surfaceHeight: Int = surfaceBehindRemoteAnimationTarget!!.screenSpaceBounds.height() - val scaleFactor = (SURFACE_BEHIND_START_SCALE_FACTOR + - (1f - SURFACE_BEHIND_START_SCALE_FACTOR) * - MathUtils.clamp(amount, 0f, 1f)) + if (unlockingToLauncherWithInWindowAnimations) { + // If we're using the in-window launcher animations, and haven't yet applied alpha = 1f + // to the launcher surface, do that now so we can see the launcher animations. + if (surfaceBehindParams?.alpha?.let { it < 1f } != false) { + applyParamsToSurface( + SyncRtSurfaceTransactionApplier.SurfaceParams.Builder( + surfaceBehindRemoteAnimationTarget!!.leash) + .withAlpha(1f) + .build()) + } - // Scale up from a point at the center-bottom of the surface. - surfaceBehindMatrix.setScale( + // If we aren't using the canned unlock animation (which would be setting the unlock + // amount in its update listener), do it here. + if (!isPlayingCannedUnlockAnimation()) { + launcherUnlockController?.setUnlockAmount(amount) + } + } else { + // Otherwise, animate in the surface's scale/transltion. + val surfaceHeight: Int = surfaceBehindRemoteAnimationTarget!!.screenSpaceBounds.height() + val scaleFactor = (SURFACE_BEHIND_START_SCALE_FACTOR + + (1f - SURFACE_BEHIND_START_SCALE_FACTOR) * + MathUtils.clamp(amount, 0f, 1f)) + + // Scale up from a point at the center-bottom of the surface. + surfaceBehindMatrix.setScale( scaleFactor, scaleFactor, surfaceBehindRemoteAnimationTarget!!.screenSpaceBounds.width() / 2f, - surfaceHeight * SURFACE_BEHIND_SCALE_PIVOT_Y) + surfaceHeight * SURFACE_BEHIND_SCALE_PIVOT_Y + ) - // Translate up from the bottom. - surfaceBehindMatrix.postTranslate(0f, - surfaceHeight * SURFACE_BEHIND_START_TRANSLATION_Y * (1f - amount)) + // Translate up from the bottom. + surfaceBehindMatrix.postTranslate( + 0f, + surfaceHeight * SURFACE_BEHIND_START_TRANSLATION_Y * (1f - amount) + ) - // If we're snapping the keyguard back, immediately begin fading it out. - val animationAlpha = + // If we're snapping the keyguard back, immediately begin fading it out. + val animationAlpha = if (keyguardStateController.isSnappingKeyguardBackAfterSwipe) amount else surfaceBehindAlpha - val params = SyncRtSurfaceTransactionApplier.SurfaceParams.Builder( - surfaceBehindRemoteAnimationTarget!!.leash) + applyParamsToSurface( + SyncRtSurfaceTransactionApplier.SurfaceParams.Builder( + surfaceBehindRemoteAnimationTarget!!.leash) .withMatrix(surfaceBehindMatrix) .withCornerRadius(roundedCornerRadius) .withAlpha(animationAlpha) - .build() - surfaceTransactionApplier!!.scheduleApply(params) + .build()) + } } /** @@ -326,6 +664,10 @@ class KeyguardUnlockAnimationController @Inject constructor( return } + if (playingCannedUnlockAnimation) { + return + } + // For fling animations, we want to animate the surface in over the full distance. If we're // dismissing the keyguard via a swipe gesture (or cancelling the swipe gesture), we want to // bring in the surface behind over a relatively short swipe distance (~15%), to keep the @@ -344,17 +686,19 @@ class KeyguardUnlockAnimationController @Inject constructor( } override fun onKeyguardDismissAmountChanged() { - if (!KeyguardService.sEnableRemoteKeyguardGoingAwayAnimation) { + if (!willHandleUnlockAnimation()) { return } if (keyguardViewController.isShowing) { - updateKeyguardViewMediatorIfThresholdsReached() + showOrHideSurfaceIfDismissAmountThresholdsReached() // If the surface is visible or it's about to be, start updating its appearance to // reflect the new dismiss amount. - if (keyguardViewMediator.get().requestedShowSurfaceBehindKeyguard() || - keyguardViewMediator.get().isAnimatingBetweenKeyguardAndSurfaceBehindOrWillBe) { + if ((keyguardViewMediator.get().requestedShowSurfaceBehindKeyguard() || + keyguardViewMediator.get() + .isAnimatingBetweenKeyguardAndSurfaceBehindOrWillBe) && + !playingCannedUnlockAnimation) { updateSurfaceBehindAppearAmount() } } @@ -362,7 +706,7 @@ class KeyguardUnlockAnimationController @Inject constructor( // The end of the SmartSpace transition can occur after the keyguard is hidden (when we tell // Launcher's SmartSpace to become visible again), so update it even if the keyguard view is // no longer showing. - updateSmartSpaceTransition() + applyDismissAmountToSmartspaceTransition() } /** @@ -370,16 +714,28 @@ class KeyguardUnlockAnimationController @Inject constructor( * such as reaching the point in the dismiss swipe where we need to make the surface behind the * keyguard visible. */ - private fun updateKeyguardViewMediatorIfThresholdsReached() { + private fun showOrHideSurfaceIfDismissAmountThresholdsReached() { if (!featureFlags.isEnabled(Flags.NEW_UNLOCK_SWIPE_ANIMATION)) { return } + // If we are playing the canned unlock animation, we flung away the keyguard to hide it and + // started a canned animation to show the surface behind the keyguard. The fling will cause + // panel height/dismiss amount updates, but we should ignore those updates here since the + // surface behind is already visible and animating. + if (playingCannedUnlockAnimation) { + return + } + val dismissAmount = keyguardStateController.dismissAmount if (dismissAmount >= DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD && !keyguardViewMediator.get().requestedShowSurfaceBehindKeyguard()) { // We passed the threshold, and we're not yet showing the surface behind the // keyguard. Animate it in. + if (canPerformInWindowLauncherAnimations()) { + launcherUnlockController?.setUnlockAmount(0f) + unlockingToLauncherWithInWindowAnimations = true + } keyguardViewMediator.get().showSurfaceBehindKeyguard() fadeInSurfaceBehind() } else if (dismissAmount < DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD && @@ -388,9 +744,9 @@ class KeyguardUnlockAnimationController @Inject constructor( // out. keyguardViewMediator.get().hideSurfaceBehindKeyguard() fadeOutSurfaceBehind() - } else { - finishKeyguardExitRemoteAnimationIfReachThreshold() } + + finishKeyguardExitRemoteAnimationIfReachThreshold() } /** @@ -417,6 +773,7 @@ class KeyguardUnlockAnimationController @Inject constructor( // animating it out. This will be called again after the fling ends. !keyguardStateController.isFlingingToDismissKeyguardDuringSwipeGesture && dismissAmount >= DISMISS_AMOUNT_EXIT_KEYGUARD_THRESHOLD)) { + setSurfaceBehindAppearAmount(1f) keyguardViewMediator.get().onKeyguardExitRemoteAnimationFinished(false /* cancelled */) } } @@ -426,31 +783,53 @@ class KeyguardUnlockAnimationController @Inject constructor( * dismiss amount, and also updates the SmartSpaceTransitionController, which will let Launcher * know if it needs to do something as a result. */ - private fun updateSmartSpaceTransition() { + private fun applyDismissAmountToSmartspaceTransition() { if (!featureFlags.isEnabled(Flags.SMARTSPACE_SHARED_ELEMENT_TRANSITION_ENABLED)) { return } + // If we are playing the canned animation, the smartspace is being animated directly between + // its original location and the location of the launcher smartspace by smartspaceAnimator. + // We can ignore the dismiss amount, which is caused by panel height changes as the panel is + // flung away. + if (playingCannedUnlockAnimation) { + return + } + val dismissAmount = keyguardStateController.dismissAmount - // If we've begun a swipe, and are capable of doing the SmartSpace transition, start it! + // If we've begun a swipe, and haven't yet tried doing the SmartSpace transition, do that + // now. if (!attemptedSmartSpaceTransitionForThisSwipe && - dismissAmount > 0f && - dismissAmount < 1f && - keyguardViewController.isShowing) { + keyguardViewController.isShowing && + dismissAmount > 0f && + dismissAmount < 1f) { attemptedSmartSpaceTransitionForThisSwipe = true - smartspaceTransitionController.prepareForUnlockTransition() - if (keyguardStateController.canPerformSmartSpaceTransition()) { - unlockingWithSmartSpaceTransition = true - smartspaceTransitionController.launcherSmartspace?.setVisibility( - View.INVISIBLE) + if (prepareForSmartspaceTransition()) { + unlockingWithSmartspaceTransition = true + + // Ensure that the smartspace is invisible if we're doing the transition, and + // visible if we aren't. + launcherUnlockController?.setSmartspaceVisibility( + if (unlockingWithSmartspaceTransition) View.INVISIBLE else View.VISIBLE) + + if (unlockingWithSmartspaceTransition) { + listeners.forEach { it.onSmartspaceSharedElementTransitionStarted() } + } } } else if (attemptedSmartSpaceTransitionForThisSwipe && - (dismissAmount == 0f || dismissAmount == 1f)) { + (dismissAmount == 0f || dismissAmount == 1f)) { attemptedSmartSpaceTransitionForThisSwipe = false - unlockingWithSmartSpaceTransition = false - smartspaceTransitionController.launcherSmartspace?.setVisibility(View.VISIBLE) + unlockingWithSmartspaceTransition = false + launcherUnlockController?.setSmartspaceVisibility(View.VISIBLE) + } + + if (unlockingWithSmartspaceTransition) { + val swipedFraction: Float = keyguardStateController.dismissAmount + val progress = swipedFraction / DISMISS_AMOUNT_EXIT_KEYGUARD_THRESHOLD + smartspaceUnlockProgress = progress + setSmartspaceProgressToDestinationBounds(smartspaceUnlockProgress) } } @@ -463,4 +842,121 @@ class KeyguardUnlockAnimationController @Inject constructor( surfaceBehindAlphaAnimator.cancel() surfaceBehindAlphaAnimator.reverse() } + + /** + * Prepare for the smartspace shared element transition, if possible, by figuring out where we + * are animating from/to. + * + * Return true if we'll be able to do the smartspace transition, or false if conditions are not + * right to do it right now. + */ + private fun prepareForSmartspaceTransition(): Boolean { + // Feature is disabled, so we don't want to. + if (!featureFlags.isEnabled(Flags.SMARTSPACE_SHARED_ELEMENT_TRANSITION_ENABLED)) { + return false + } + + // If our controllers are null, or we haven't received a smartspace state from Launcher yet, + // we will not be doing any smartspace transitions today. + if (launcherUnlockController == null || + lockscreenSmartspace == null || + launcherSmartspaceState == null) { + return false + } + + // If the launcher does not have a visible smartspace (either because it's paged off-screen, + // or the smartspace just doesn't exist), we can't do the transition. + if ((launcherSmartspaceState?.visibleOnScreen) != true) { + return false + } + + // If our launcher isn't underneath, then we're unlocking to an app or custom launcher, + // neither of which have a smartspace. + if (!isNexusLauncherUnderneath()) { + return false + } + + // TODO(b/213910911): Unfortunately the keyguard is hidden instantly on wake and unlock, so + // we won't have a lockscreen smartspace to animate. This is sad, and we should fix that! + if (biometricUnlockControllerLazy.get().isWakeAndUnlock) { + return false + } + + // If we can't dismiss the lock screen via a swipe, then the only way we can do the shared + // element transition is if we're doing a biometric unlock. Otherwise, it means the bouncer + // is showing, and you can't see the lockscreen smartspace, so a shared element transition + // would not make sense. + if (!keyguardStateController.canDismissLockScreen() && + !biometricUnlockControllerLazy.get().isBiometricUnlock) { + return false + } + + unlockingWithSmartspaceTransition = true + smartspaceDestBounds.setEmpty() + + // Assuming we were able to retrieve the launcher's state, start the lockscreen + // smartspace at 0, 0, and save its starting bounds. + with(lockscreenSmartspace!!) { + translationX = 0f + translationY = 0f + getBoundsOnScreen(smartspaceOriginBounds) + } + + // Set the destination bounds to the launcher smartspace's bounds, offset by any + // padding on our smartspace. + with(smartspaceDestBounds) { + set(launcherSmartspaceState!!.boundsOnScreen) + offset(-lockscreenSmartspace!!.paddingLeft, -lockscreenSmartspace!!.paddingTop) + } + + return true + } + + /** + * Whether we should be able to do the in-window launcher animations given the current state of + * the device. + */ + fun canPerformInWindowLauncherAnimations(): Boolean { + return isNexusLauncherUnderneath() && launcherUnlockController != null + } + + /** + * Whether we are currently in the process of unlocking the keyguard, and we are performing the + * shared element SmartSpace transition. + */ + fun isUnlockingWithSmartSpaceTransition(): Boolean { + return unlockingWithSmartspaceTransition + } + + /** + * Whether this animation controller will be handling the unlock. We require remote animations + * to be enabled to do this. + * + * If this is not true, nothing in this class is relevant, and the unlock will be handled in + * [KeyguardViewMediator]. + */ + fun willHandleUnlockAnimation(): Boolean { + return KeyguardService.sEnableRemoteKeyguardGoingAwayAnimation + } + + /** + * Whether we are playing a canned unlock animation, vs. unlocking from a touch gesture such as + * a swipe. + */ + fun isPlayingCannedUnlockAnimation(): Boolean { + return playingCannedUnlockAnimation + } + + companion object { + /** + * Return whether the Google Nexus launcher is underneath the keyguard, vs. some other + * launcher or an app. If so, we can communicate with it to perform in-window/shared element + * transitions! + */ + fun isNexusLauncherUnderneath(): Boolean { + return ActivityManagerWrapper.getInstance() + .runningTask?.topActivity?.className?.equals( + QuickStepContract.LAUNCHER_ACTIVITY_CLASS_NAME) ?: false + } + } }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 8376681e7bd4..08e1654c8dbf 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -2235,8 +2235,9 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, createInteractionJankMonitorConf("DismissPanel")); // Pass the surface and metadata to the unlock animation controller. - mKeyguardUnlockAnimationControllerLazy.get().notifyStartKeyguardExitAnimation( - apps[0], startTime, mSurfaceBehindRemoteAnimationRequested); + mKeyguardUnlockAnimationControllerLazy.get() + .notifyStartSurfaceBehindRemoteAnimation( + apps[0], startTime, mSurfaceBehindRemoteAnimationRequested); } else { mInteractionJankMonitor.begin( createInteractionJankMonitorConf("RemoteAnimationDisabled")); @@ -2373,8 +2374,10 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, finishSurfaceBehindRemoteAnimation(cancelled); mSurfaceBehindRemoteAnimationRequested = false; - mKeyguardUnlockAnimationControllerLazy.get().notifyFinishedKeyguardExitAnimation(); }); + + mKeyguardUnlockAnimationControllerLazy.get().notifyFinishedKeyguardExitAnimation( + cancelled); } /** @@ -2412,8 +2415,16 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, return mSurfaceBehindRemoteAnimationRequested; } + public boolean isAnimatingBetweenKeyguardAndSurfaceBehind() { + return mSurfaceBehindRemoteAnimationRunning; + } + /** If it's running, finishes the RemoteAnimation on the surface behind the keyguard. */ public void finishSurfaceBehindRemoteAnimation(boolean cancelled) { + if (!mSurfaceBehindRemoteAnimationRunning) { + return; + } + mSurfaceBehindRemoteAnimationRunning = false; if (mSurfaceBehindRemoteAnimationFinishedCallback != null) { diff --git a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java index d1fe7d449bdd..4baef3aef309 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java +++ b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java @@ -21,8 +21,6 @@ import android.content.Context; import android.view.WindowManager; import com.android.systemui.dagger.SysUISingleton; -import com.android.systemui.dagger.qualifiers.Background; -import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.media.MediaDataManager; import com.android.systemui.media.MediaHierarchyManager; import com.android.systemui.media.MediaHost; @@ -33,10 +31,8 @@ import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerR import com.android.systemui.media.taptotransfer.sender.MediaTttChipControllerSender; import com.android.systemui.media.taptotransfer.sender.MediaTttSenderService; import com.android.systemui.statusbar.commandline.CommandRegistry; -import com.android.systemui.util.concurrency.DelayableExecutor; import java.util.Optional; -import java.util.concurrent.Executor; import javax.inject.Named; @@ -89,14 +85,11 @@ public interface MediaModule { static Optional<MediaTttChipControllerSender> providesMediaTttChipControllerSender( MediaTttFlags mediaTttFlags, Context context, - WindowManager windowManager, - @Main Executor mainExecutor, - @Background Executor backgroundExecutor) { + WindowManager windowManager) { if (!mediaTttFlags.isMediaTttEnabled()) { return Optional.empty(); } - return Optional.of(new MediaTttChipControllerSender( - context, windowManager, mainExecutor, backgroundExecutor)); + return Optional.of(new MediaTttChipControllerSender(context, windowManager)); } /** */ @@ -119,9 +112,7 @@ public interface MediaModule { MediaTttFlags mediaTttFlags, CommandRegistry commandRegistry, Context context, - MediaTttChipControllerSender mediaTttChipControllerSender, - MediaTttChipControllerReceiver mediaTttChipControllerReceiver, - @Main DelayableExecutor mainExecutor) { + MediaTttChipControllerReceiver mediaTttChipControllerReceiver) { if (!mediaTttFlags.isMediaTttEnabled()) { return Optional.empty(); } @@ -129,9 +120,7 @@ public interface MediaModule { new MediaTttCommandLineHelper( commandRegistry, context, - mediaTttChipControllerSender, - mediaTttChipControllerReceiver, - mainExecutor)); + mediaTttChipControllerReceiver)); } /** Inject into MediaTttSenderService. */ diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt index 460d38f45b4d..37208515120a 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt @@ -28,21 +28,22 @@ import android.util.Log import androidx.annotation.VisibleForTesting import com.android.systemui.R import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerReceiver import com.android.systemui.media.taptotransfer.receiver.ChipStateReceiver -import com.android.systemui.media.taptotransfer.sender.MediaTttChipControllerSender import com.android.systemui.media.taptotransfer.sender.MediaTttSenderService +import com.android.systemui.media.taptotransfer.sender.MoveCloserToEndCast import com.android.systemui.media.taptotransfer.sender.MoveCloserToStartCast -import com.android.systemui.media.taptotransfer.sender.TransferInitiated -import com.android.systemui.media.taptotransfer.sender.TransferSucceeded +import com.android.systemui.media.taptotransfer.sender.TransferFailed +import com.android.systemui.media.taptotransfer.sender.TransferToReceiverTriggered +import com.android.systemui.media.taptotransfer.sender.TransferToThisDeviceSucceeded +import com.android.systemui.media.taptotransfer.sender.TransferToThisDeviceTriggered +import com.android.systemui.media.taptotransfer.sender.TransferToReceiverSucceeded import com.android.systemui.shared.mediattt.DeviceInfo -import com.android.systemui.shared.mediattt.IDeviceSenderCallback +import com.android.systemui.shared.mediattt.IDeviceSenderService +import com.android.systemui.shared.mediattt.IUndoTransferCallback import com.android.systemui.statusbar.commandline.Command import com.android.systemui.statusbar.commandline.CommandRegistry -import com.android.systemui.util.concurrency.DelayableExecutor import java.io.PrintWriter -import java.util.concurrent.FutureTask import javax.inject.Inject /** @@ -53,11 +54,9 @@ import javax.inject.Inject class MediaTttCommandLineHelper @Inject constructor( commandRegistry: CommandRegistry, private val context: Context, - private val mediaTttChipControllerSender: MediaTttChipControllerSender, private val mediaTttChipControllerReceiver: MediaTttChipControllerReceiver, - @Main private val mainExecutor: DelayableExecutor, ) { - private var senderCallback: IDeviceSenderCallback? = null + private var senderService: IDeviceSenderService? = null private val senderServiceConnection = SenderServiceConnection() private val appIconDrawable = @@ -66,17 +65,15 @@ class MediaTttCommandLineHelper @Inject constructor( } init { - commandRegistry.registerCommand( - ADD_CHIP_COMMAND_SENDER_TAG) { AddChipCommandSender() } - commandRegistry.registerCommand( - REMOVE_CHIP_COMMAND_SENDER_TAG) { RemoveChipCommandSender() } + commandRegistry.registerCommand(SENDER_COMMAND) { SenderCommand() } commandRegistry.registerCommand( ADD_CHIP_COMMAND_RECEIVER_TAG) { AddChipCommandReceiver() } commandRegistry.registerCommand( REMOVE_CHIP_COMMAND_RECEIVER_TAG) { RemoveChipCommandReceiver() } } - inner class AddChipCommandSender : Command { + /** All commands for the sender device. */ + inner class SenderCommand : Command { override fun execute(pw: PrintWriter, args: List<String>) { val otherDeviceName = args[0] val mediaInfo = MediaRoute2Info.Builder("id", "Test Name") @@ -86,62 +83,99 @@ class MediaTttCommandLineHelper @Inject constructor( when (args[1]) { MOVE_CLOSER_TO_START_CAST_COMMAND_NAME -> { - runOnService { senderCallback -> - senderCallback.closeToReceiverToStartCast(mediaInfo, otherDeviceInfo) + runOnService { senderService -> + senderService.closeToReceiverToStartCast(mediaInfo, otherDeviceInfo) } } - - // TODO(b/203800643): Migrate other commands to invoke the service instead of the - // controller. - TRANSFER_INITIATED_COMMAND_NAME -> { - val futureTask = FutureTask { fakeUndoRunnable } - mediaTttChipControllerSender.displayChip( - TransferInitiated( - appIconDrawable, - APP_ICON_CONTENT_DESCRIPTION, - otherDeviceName, - futureTask - ) - ) - mainExecutor.executeDelayed({ futureTask.run() }, FUTURE_WAIT_TIME) - + MOVE_CLOSER_TO_END_CAST_COMMAND_NAME -> { + runOnService { senderService -> + senderService.closeToReceiverToEndCast(mediaInfo, otherDeviceInfo) + } } - TRANSFER_SUCCEEDED_COMMAND_NAME -> { - mediaTttChipControllerSender.displayChip( - TransferSucceeded( - appIconDrawable, - APP_ICON_CONTENT_DESCRIPTION, - otherDeviceName, - fakeUndoRunnable - ) - ) + TRANSFER_TO_RECEIVER_TRIGGERED_COMMAND_NAME -> { + runOnService { senderService -> + senderService.transferToReceiverTriggered(mediaInfo, otherDeviceInfo) + } + } + TRANSFER_TO_THIS_DEVICE_TRIGGERED_COMMAND_NAME -> { + runOnService { senderService -> + senderService.transferToThisDeviceTriggered(mediaInfo, otherDeviceInfo) + } + } + TRANSFER_TO_RECEIVER_SUCCEEDED_COMMAND_NAME -> { + val undoCallback = object : IUndoTransferCallback.Stub() { + override fun onUndoTriggered() { + Log.i(TAG, "Undo transfer to receiver callback triggered") + // The external services that implement this callback would kick off a + // transfer back to this device, so mimic that here. + runOnService { senderService -> + senderService + .transferToThisDeviceTriggered(mediaInfo, otherDeviceInfo) + } + } + } + runOnService { senderService -> + senderService + .transferToReceiverSucceeded(mediaInfo, otherDeviceInfo, undoCallback) + } + } + TRANSFER_TO_THIS_DEVICE_SUCCEEDED_COMMAND_NAME -> { + val undoCallback = object : IUndoTransferCallback.Stub() { + override fun onUndoTriggered() { + Log.i(TAG, "Undo transfer to this device callback triggered") + // The external services that implement this callback would kick off a + // transfer back to the receiver, so mimic that here. + runOnService { senderService -> + senderService + .transferToReceiverTriggered(mediaInfo, otherDeviceInfo) + } + } + } + runOnService { senderService -> + senderService + .transferToThisDeviceSucceeded(mediaInfo, otherDeviceInfo, undoCallback) + } + } + TRANSFER_FAILED_COMMAND_NAME -> { + runOnService { senderService -> + senderService.transferFailed(mediaInfo, otherDeviceInfo) + } + } + NO_LONGER_CLOSE_TO_RECEIVER_COMMAND_NAME -> { + runOnService { senderService -> + senderService.noLongerCloseToReceiver(mediaInfo, otherDeviceInfo) + context.unbindService(senderServiceConnection) + } } else -> { - pw.println("Chip type must be one of " + + pw.println("Sender command must be one of " + "$MOVE_CLOSER_TO_START_CAST_COMMAND_NAME, " + - "$TRANSFER_INITIATED_COMMAND_NAME, " + - TRANSFER_SUCCEEDED_COMMAND_NAME + "$MOVE_CLOSER_TO_END_CAST_COMMAND_NAME, " + + "$TRANSFER_TO_RECEIVER_TRIGGERED_COMMAND_NAME, " + + "$TRANSFER_TO_THIS_DEVICE_TRIGGERED_COMMAND_NAME, " + + "$TRANSFER_TO_RECEIVER_SUCCEEDED_COMMAND_NAME, " + + "$TRANSFER_TO_THIS_DEVICE_SUCCEEDED_COMMAND_NAME, " + + "$TRANSFER_FAILED_COMMAND_NAME, " + + NO_LONGER_CLOSE_TO_RECEIVER_COMMAND_NAME ) } } } override fun help(pw: PrintWriter) { - pw.println("Usage: adb shell cmd statusbar " + - "$ADD_CHIP_COMMAND_SENDER_TAG <deviceName> <chipStatus>" - ) + pw.println("Usage: adb shell cmd statusbar $SENDER_COMMAND <deviceName> <chipStatus>") } - private fun runOnService(command: SenderCallbackCommand) { - val currentServiceCallback = senderCallback - if (currentServiceCallback != null) { - command.run(currentServiceCallback) + private fun runOnService(command: SenderServiceCommand) { + val currentService = senderService + if (currentService != null) { + command.run(currentService) } else { bindService(command) } } - private fun bindService(command: SenderCallbackCommand) { + private fun bindService(command: SenderServiceCommand) { senderServiceConnection.pendingCommand = command val binding = context.bindService( Intent(context, MediaTttSenderService::class.java), @@ -152,19 +186,6 @@ class MediaTttCommandLineHelper @Inject constructor( } } - /** A command to REMOVE the media ttt chip on the SENDER device. */ - inner class RemoveChipCommandSender : Command { - override fun execute(pw: PrintWriter, args: List<String>) { - mediaTttChipControllerSender.removeChip() - if (senderCallback != null) { - context.unbindService(senderServiceConnection) - } - } - override fun help(pw: PrintWriter) { - pw.println("Usage: adb shell cmd statusbar $REMOVE_CHIP_COMMAND_SENDER_TAG") - } - } - /** A command to DISPLAY the media ttt chip on the RECEIVER device. */ inner class AddChipCommandReceiver : Command { override fun execute(pw: PrintWriter, args: List<String>) { @@ -187,36 +208,32 @@ class MediaTttCommandLineHelper @Inject constructor( } } - /** A service connection for [IDeviceSenderCallback]. */ + /** A service connection for [IDeviceSenderService]. */ private inner class SenderServiceConnection : ServiceConnection { // A command that should be run when the service gets connected. - var pendingCommand: SenderCallbackCommand? = null + var pendingCommand: SenderServiceCommand? = null override fun onServiceConnected(className: ComponentName, service: IBinder) { - val newCallback = IDeviceSenderCallback.Stub.asInterface(service) - senderCallback = newCallback + val newCallback = IDeviceSenderService.Stub.asInterface(service) + senderService = newCallback pendingCommand?.run(newCallback) pendingCommand = null } override fun onServiceDisconnected(className: ComponentName) { - senderCallback = null + senderService = null } } - /** An interface defining a command that should be run on the sender callback. */ - private fun interface SenderCallbackCommand { - /** Runs the command on the provided [senderCallback]. */ - fun run(senderCallback: IDeviceSenderCallback) - } - - private val fakeUndoRunnable = Runnable { - Log.i(TAG, "Undo runnable triggered") + /** An interface defining a command that should be run on the sender service. */ + private fun interface SenderServiceCommand { + /** Runs the command on the provided [senderService]. */ + fun run(senderService: IDeviceSenderService) } } @VisibleForTesting -const val ADD_CHIP_COMMAND_SENDER_TAG = "media-ttt-chip-add-sender" +const val SENDER_COMMAND = "media-ttt-chip-sender" @VisibleForTesting const val REMOVE_CHIP_COMMAND_SENDER_TAG = "media-ttt-chip-remove-sender" @VisibleForTesting @@ -226,10 +243,21 @@ const val REMOVE_CHIP_COMMAND_RECEIVER_TAG = "media-ttt-chip-remove-receiver" @VisibleForTesting val MOVE_CLOSER_TO_START_CAST_COMMAND_NAME = MoveCloserToStartCast::class.simpleName!! @VisibleForTesting -val TRANSFER_INITIATED_COMMAND_NAME = TransferInitiated::class.simpleName!! +val MOVE_CLOSER_TO_END_CAST_COMMAND_NAME = MoveCloserToEndCast::class.simpleName!! +@VisibleForTesting +val TRANSFER_TO_RECEIVER_TRIGGERED_COMMAND_NAME = TransferToReceiverTriggered::class.simpleName!! +@VisibleForTesting +val TRANSFER_TO_THIS_DEVICE_TRIGGERED_COMMAND_NAME = + TransferToThisDeviceTriggered::class.simpleName!! +@VisibleForTesting +val TRANSFER_TO_RECEIVER_SUCCEEDED_COMMAND_NAME = TransferToReceiverSucceeded::class.simpleName!! +@VisibleForTesting +val TRANSFER_TO_THIS_DEVICE_SUCCEEDED_COMMAND_NAME = + TransferToThisDeviceSucceeded::class.simpleName!! +@VisibleForTesting +val TRANSFER_FAILED_COMMAND_NAME = TransferFailed::class.simpleName!! @VisibleForTesting -val TRANSFER_SUCCEEDED_COMMAND_NAME = TransferSucceeded::class.simpleName!! +val NO_LONGER_CLOSE_TO_RECEIVER_COMMAND_NAME = "NoLongerCloseToReceiver" -private const val FUTURE_WAIT_TIME = 2000L private const val APP_ICON_CONTENT_DESCRIPTION = "Fake media app icon" private const val TAG = "MediaTapToTransferCli" diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt index 67721a543427..adae07b58e7c 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt @@ -81,6 +81,9 @@ abstract class MediaTttChipControllerCommon<T : MediaTttChipState>( /** Hides the chip. */ fun removeChip() { + // TODO(b/203800347): We may not want to hide the chip if we're currently in a + // TransferTriggered state: Once the user has initiated the transfer, they should be able + // to move away from the receiver device but still see the status of the transfer. if (chipView == null) { return } windowManager.removeView(chipView) chipView = null diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt index dd434e7756fb..c656df2e0a35 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt @@ -16,11 +16,12 @@ package com.android.systemui.media.taptotransfer.sender +import android.content.Context import android.graphics.drawable.Drawable -import androidx.annotation.StringRes +import android.view.View import com.android.systemui.R import com.android.systemui.media.taptotransfer.common.MediaTttChipState -import java.util.concurrent.Future +import com.android.systemui.shared.mediattt.IUndoTransferCallback /** * A class that stores all the information necessary to display the media tap-to-transfer chip on @@ -28,66 +29,181 @@ import java.util.concurrent.Future * * This is a sealed class where each subclass represents a specific chip state. Each subclass can * contain additional information that is necessary for only that state. - * - * @property chipText a string resource for the text that the chip should display. - * @property otherDeviceName the name of the other device involved in the transfer. */ sealed class ChipStateSender( appIconDrawable: Drawable, - appIconContentDescription: String, - @StringRes internal val chipText: Int, - internal val otherDeviceName: String, -) : MediaTttChipState(appIconDrawable, appIconContentDescription) + appIconContentDescription: String +) : MediaTttChipState(appIconDrawable, appIconContentDescription) { + /** Returns a fully-formed string with the text that the chip should display. */ + abstract fun getChipTextString(context: Context): String + + /** Returns true if the loading icon should be displayed and false otherwise. */ + open fun showLoading(): Boolean = false + + /** + * Returns a click listener for the undo button on the chip. Returns null if this chip state + * doesn't have an undo button. + * + * @param controllerSender passed as a parameter in case we want to display a new chip state + * when undo is clicked. + */ + open fun undoClickListener( + controllerSender: MediaTttChipControllerSender + ): View.OnClickListener? = null +} /** * A state representing that the two devices are close but not close enough to *start* a cast to * the receiver device. The chip will instruct the user to move closer in order to initiate the * transfer to the receiver. + * + * @property otherDeviceName the name of the other device involved in the transfer. */ class MoveCloserToStartCast( appIconDrawable: Drawable, appIconContentDescription: String, - otherDeviceName: String, -) : ChipStateSender( - appIconDrawable, - appIconContentDescription, - R.string.media_move_closer_to_start_cast, - otherDeviceName -) + private val otherDeviceName: String, +) : ChipStateSender(appIconDrawable, appIconContentDescription) { + override fun getChipTextString(context: Context): String { + return context.getString(R.string.media_move_closer_to_start_cast, otherDeviceName) + } +} /** - * A state representing that a transfer has been initiated (but not completed). + * A state representing that the two devices are close but not close enough to *end* a cast that's + * currently occurring the receiver device. The chip will instruct the user to move closer in order + * to initiate the transfer from the receiver and back onto this device (the original sender). * - * @property future a future that will be resolved when the transfer has either succeeded or failed. - * If the transfer succeeded, the future can optionally return an undo runnable (see - * [TransferSucceeded.undoRunnable]). [MediaTttChipControllerSender] is responsible for transitioning - * the chip to the [TransferSucceeded] state if the future resolves successfully. + * @property otherDeviceName the name of the other device involved in the transfer. */ -class TransferInitiated( +class MoveCloserToEndCast( appIconDrawable: Drawable, appIconContentDescription: String, - otherDeviceName: String, - val future: Future<Runnable?> -) : ChipStateSender( - appIconDrawable, - appIconContentDescription, - R.string.media_transfer_playing, - otherDeviceName -) + private val otherDeviceName: String, +) : ChipStateSender(appIconDrawable, appIconContentDescription) { + override fun getChipTextString(context: Context): String { + return context.getString(R.string.media_move_closer_to_end_cast, otherDeviceName) + } +} /** - * A state representing that a transfer has been successfully completed. + * A state representing that a transfer to the receiver device has been initiated (but not + * completed). * - * @property undoRunnable if present, the runnable that should be run to undo the transfer. We will - * show an Undo button on the chip if this runnable is present. + * @property otherDeviceName the name of the other device involved in the transfer. */ -class TransferSucceeded( +class TransferToReceiverTriggered( appIconDrawable: Drawable, appIconContentDescription: String, - otherDeviceName: String, - val undoRunnable: Runnable? = null -) : ChipStateSender(appIconDrawable, - appIconContentDescription, - R.string.media_transfer_playing, - otherDeviceName -) + private val otherDeviceName: String +) : ChipStateSender(appIconDrawable, appIconContentDescription) { + override fun getChipTextString(context: Context): String { + return context.getString(R.string.media_transfer_playing_different_device, otherDeviceName) + } + + override fun showLoading() = true +} + +/** + * A state representing that a transfer from the receiver device and back to this device (the + * sender) has been initiated (but not completed). + */ +class TransferToThisDeviceTriggered( + appIconDrawable: Drawable, + appIconContentDescription: String +) : ChipStateSender(appIconDrawable, appIconContentDescription) { + override fun getChipTextString(context: Context): String { + return context.getString(R.string.media_transfer_playing_this_device) + } + + override fun showLoading() = true +} + +/** + * A state representing that a transfer to the receiver device has been successfully completed. + * + * @property otherDeviceName the name of the other device involved in the transfer. + * @property undoCallback if present, the callback that should be called when the user clicks the + * undo button. The undo button will only be shown if this is non-null. + */ +class TransferToReceiverSucceeded( + appIconDrawable: Drawable, + appIconContentDescription: String, + private val otherDeviceName: String, + val undoCallback: IUndoTransferCallback? = null +) : ChipStateSender(appIconDrawable, appIconContentDescription) { + override fun getChipTextString(context: Context): String { + return context.getString(R.string.media_transfer_playing_different_device, otherDeviceName) + } + + override fun undoClickListener( + controllerSender: MediaTttChipControllerSender + ): View.OnClickListener? { + if (undoCallback == null) { + return null + } + + return View.OnClickListener { + this.undoCallback.onUndoTriggered() + // The external service should eventually send us a TransferToThisDeviceTriggered state, + // but that may take too long to go through the binder and the user may be confused as + // to why the UI hasn't changed yet. So, we immediately change the UI here. + controllerSender.displayChip( + TransferToThisDeviceTriggered( + this.appIconDrawable, + this.appIconContentDescription + ) + ) + } + } +} + +/** + * A state representing that a transfer back to this device has been successfully completed. + * + * @property otherDeviceName the name of the other device involved in the transfer. + * @property undoCallback if present, the callback that should be called when the user clicks the + * undo button. The undo button will only be shown if this is non-null. + */ +class TransferToThisDeviceSucceeded( + appIconDrawable: Drawable, + appIconContentDescription: String, + private val otherDeviceName: String, + val undoCallback: IUndoTransferCallback? = null +) : ChipStateSender(appIconDrawable, appIconContentDescription) { + override fun getChipTextString(context: Context): String { + return context.getString(R.string.media_transfer_playing_this_device) + } + + override fun undoClickListener( + controllerSender: MediaTttChipControllerSender + ): View.OnClickListener? { + if (undoCallback == null) { + return null + } + + return View.OnClickListener { + this.undoCallback.onUndoTriggered() + // The external service should eventually send us a TransferToReceiverTriggered state, + // but that may take too long to go through the binder and the user may be confused as + // to why the UI hasn't changed yet. So, we immediately change the UI here. + controllerSender.displayChip( + TransferToReceiverTriggered( + this.appIconDrawable, + this.appIconContentDescription, + this.otherDeviceName + ) + ) + } + } +} + +/** A state representing that a transfer has failed. */ +class TransferFailed( + appIconDrawable: Drawable, + appIconContentDescription: String +) : ChipStateSender(appIconDrawable, appIconContentDescription) { + override fun getChipTextString(context: Context): String { + return context.getString(R.string.media_transfer_failed) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt index 77d3d70fc98c..453e3d627bc8 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt @@ -23,11 +23,7 @@ import android.view.WindowManager import android.widget.TextView import com.android.systemui.R import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.dagger.qualifiers.Background -import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.media.taptotransfer.common.MediaTttChipControllerCommon -import java.util.concurrent.Executor -import java.util.concurrent.TimeUnit import javax.inject.Inject /** @@ -38,8 +34,6 @@ import javax.inject.Inject class MediaTttChipControllerSender @Inject constructor( context: Context, windowManager: WindowManager, - @Main private val mainExecutor: Executor, - @Background private val backgroundExecutor: Executor, ) : MediaTttChipControllerCommon<ChipStateSender>( context, windowManager, R.layout.media_ttt_chip ) { @@ -51,63 +45,22 @@ class MediaTttChipControllerSender @Inject constructor( // Text currentChipView.requireViewById<TextView>(R.id.text).apply { - text = context.getString(chipState.chipText, chipState.otherDeviceName) + text = chipState.getChipTextString(context) } // Loading - val showLoading = chipState is TransferInitiated currentChipView.requireViewById<View>(R.id.loading).visibility = - if (showLoading) { View.VISIBLE } else { View.GONE } + if (chipState.showLoading()) { View.VISIBLE } else { View.GONE } // Undo - val undoClickListener: View.OnClickListener? = - if (chipState is TransferSucceeded && chipState.undoRunnable != null) - View.OnClickListener { chipState.undoRunnable.run() } - else - null val undoView = currentChipView.requireViewById<View>(R.id.undo) - undoView.visibility = if (undoClickListener != null) { - View.VISIBLE - } else { - View.GONE - } + val undoClickListener = chipState.undoClickListener(this) undoView.setOnClickListener(undoClickListener) + undoView.visibility = if (undoClickListener != null) { View.VISIBLE } else { View.GONE } - // Future handling - if (chipState is TransferInitiated) { - addFutureCallback(chipState) - } - } - - /** - * Adds the appropriate callbacks to [chipState.future] so that we update the chip correctly - * when the future resolves. - */ - private fun addFutureCallback(chipState: TransferInitiated) { - // Listen to the future on a background thread so we don't occupy the main thread while we - // wait for it to complete. - backgroundExecutor.execute { - try { - val undoRunnable = chipState.future.get(TRANSFER_TIMEOUT_SECONDS, TimeUnit.SECONDS) - // Make UI changes on the main thread - mainExecutor.execute { - displayChip( - TransferSucceeded( - chipState.appIconDrawable, - chipState.appIconContentDescription, - chipState.otherDeviceName, - undoRunnable - ) - ) - } - } catch (ex: Exception) { - // TODO(b/203800327): Maybe show a failure chip here if UX decides we need one. - mainExecutor.execute { - removeChip() - } - } - } + // Failure + val showFailure = chipState is TransferFailed + currentChipView.requireViewById<View>(R.id.failure_icon).visibility = + if (showFailure) { View.VISIBLE } else { View.GONE } } } - -private const val TRANSFER_TIMEOUT_SECONDS = 10L diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderService.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderService.kt index b56a69903ea4..717752e536b0 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderService.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderService.kt @@ -25,7 +25,8 @@ import android.media.MediaRoute2Info import android.os.IBinder import com.android.systemui.R import com.android.systemui.shared.mediattt.DeviceInfo -import com.android.systemui.shared.mediattt.IDeviceSenderCallback +import com.android.systemui.shared.mediattt.IUndoTransferCallback +import com.android.systemui.shared.mediattt.IDeviceSenderService import javax.inject.Inject /** @@ -37,12 +38,63 @@ class MediaTttSenderService @Inject constructor( ) : Service() { // TODO(b/203800643): Add logging when callbacks trigger. - private val binder: IBinder = object : IDeviceSenderCallback.Stub() { + private val binder: IBinder = object : IDeviceSenderService.Stub() { override fun closeToReceiverToStartCast( mediaInfo: MediaRoute2Info, otherDeviceInfo: DeviceInfo ) { this@MediaTttSenderService.closeToReceiverToStartCast(mediaInfo, otherDeviceInfo) } + + override fun closeToReceiverToEndCast( + mediaInfo: MediaRoute2Info, otherDeviceInfo: DeviceInfo + ) { + this@MediaTttSenderService.closeToReceiverToEndCast(mediaInfo, otherDeviceInfo) + } + + override fun transferFailed( + mediaInfo: MediaRoute2Info, otherDeviceInfo: DeviceInfo + ) { + this@MediaTttSenderService.transferFailed(mediaInfo) + } + + override fun transferToReceiverTriggered( + mediaInfo: MediaRoute2Info, otherDeviceInfo: DeviceInfo + ) { + this@MediaTttSenderService.transferToReceiverTriggered(mediaInfo, otherDeviceInfo) + } + + override fun transferToThisDeviceTriggered( + mediaInfo: MediaRoute2Info, otherDeviceInfo: DeviceInfo + ) { + this@MediaTttSenderService.transferToThisDeviceTriggered(mediaInfo) + } + + override fun transferToReceiverSucceeded( + mediaInfo: MediaRoute2Info, + otherDeviceInfo: DeviceInfo, + undoCallback: IUndoTransferCallback + ) { + this@MediaTttSenderService.transferToReceiverSucceeded( + mediaInfo, otherDeviceInfo, undoCallback + ) + } + + override fun transferToThisDeviceSucceeded( + mediaInfo: MediaRoute2Info, + otherDeviceInfo: DeviceInfo, + undoCallback: IUndoTransferCallback + ) { + this@MediaTttSenderService.transferToThisDeviceSucceeded( + mediaInfo, otherDeviceInfo, undoCallback + ) + } + + override fun noLongerCloseToReceiver( + mediaInfo: MediaRoute2Info, + otherDeviceInfo: DeviceInfo + ) { + this@MediaTttSenderService.noLongerCloseToReceiver() + } } // TODO(b/203800643): Use the app icon from the media info instead of a fake one. @@ -63,4 +115,68 @@ class MediaTttSenderService @Inject constructor( ) controller.displayChip(chipState) } + + private fun closeToReceiverToEndCast(mediaInfo: MediaRoute2Info, otherDeviceInfo: DeviceInfo) { + val chipState = MoveCloserToEndCast( + appIconDrawable = fakeAppIconDrawable, + appIconContentDescription = mediaInfo.name.toString(), + otherDeviceName = otherDeviceInfo.name + ) + controller.displayChip(chipState) + } + + private fun transferFailed(mediaInfo: MediaRoute2Info) { + val chipState = TransferFailed( + appIconDrawable = fakeAppIconDrawable, + appIconContentDescription = mediaInfo.name.toString() + ) + controller.displayChip(chipState) + } + + private fun transferToReceiverTriggered( + mediaInfo: MediaRoute2Info, otherDeviceInfo: DeviceInfo + ) { + val chipState = TransferToReceiverTriggered( + appIconDrawable = fakeAppIconDrawable, + appIconContentDescription = mediaInfo.name.toString(), + otherDeviceName = otherDeviceInfo.name + ) + controller.displayChip(chipState) + } + + private fun transferToThisDeviceTriggered(mediaInfo: MediaRoute2Info) { + val chipState = TransferToThisDeviceTriggered( + appIconDrawable = fakeAppIconDrawable, + appIconContentDescription = mediaInfo.name.toString() + ) + controller.displayChip(chipState) + } + + private fun transferToReceiverSucceeded( + mediaInfo: MediaRoute2Info, otherDeviceInfo: DeviceInfo, undoCallback: IUndoTransferCallback + ) { + val chipState = TransferToReceiverSucceeded( + appIconDrawable = fakeAppIconDrawable, + appIconContentDescription = mediaInfo.name.toString(), + otherDeviceName = otherDeviceInfo.name, + undoCallback = undoCallback + ) + controller.displayChip(chipState) + } + + private fun transferToThisDeviceSucceeded( + mediaInfo: MediaRoute2Info, otherDeviceInfo: DeviceInfo, undoCallback: IUndoTransferCallback + ) { + val chipState = TransferToThisDeviceSucceeded( + appIconDrawable = fakeAppIconDrawable, + appIconContentDescription = mediaInfo.name.toString(), + otherDeviceName = otherDeviceInfo.name, + undoCallback = undoCallback + ) + controller.displayChip(chipState) + } + + private fun noLongerCloseToReceiver() { + controller.removeChip() + } } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java index d190dcb3ffb8..1bef32ad8caf 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java @@ -141,6 +141,7 @@ import com.android.systemui.statusbar.phone.LightBarController; import com.android.systemui.statusbar.phone.ShadeController; import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.policy.DeviceProvisionedController; +import com.android.wm.shell.back.BackAnimation; import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; import com.android.wm.shell.pip.Pip; @@ -190,6 +191,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, private final Optional<Pip> mPipOptional; private final Optional<LegacySplitScreen> mSplitScreenOptional; private final Optional<Recents> mRecentsOptional; + private final Optional<BackAnimation> mBackAnimation; private final SystemActions mSystemActions; private final Handler mHandler; private final NavigationBarOverlayController mNavbarOverlayController; @@ -504,7 +506,8 @@ public class NavigationBar implements View.OnAttachStateChangeListener, AutoHideController mainAutoHideController, AutoHideController.Factory autoHideControllerFactory, Optional<TelecomManager> telecomManagerOptional, - InputMethodManager inputMethodManager) { + InputMethodManager inputMethodManager, + Optional<BackAnimation> backAnimation) { mContext = context; mWindowManager = windowManager; mAccessibilityManager = accessibilityManager; @@ -524,6 +527,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, mPipOptional = pipOptional; mSplitScreenOptional = splitScreenOptional; mRecentsOptional = recentsOptional; + mBackAnimation = backAnimation; mSystemActions = systemActions; mHandler = mainHandler; mNavbarOverlayController = navbarOverlayController; @@ -629,6 +633,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, mSplitScreenOptional.ifPresent(mNavigationBarView::registerDockedListener); mPipOptional.ifPresent(mNavigationBarView::addPipExclusionBoundsChangeListener); + mBackAnimation.ifPresent(mNavigationBarView::registerBackAnimation); prepareNavigationBarView(); checkNavBarModes(); @@ -1680,6 +1685,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, private final AutoHideController.Factory mAutoHideControllerFactory; private final Optional<TelecomManager> mTelecomManagerOptional; private final InputMethodManager mInputMethodManager; + private final Optional<BackAnimation> mBackAnimation; @Inject public Factory( @@ -1712,7 +1718,8 @@ public class NavigationBar implements View.OnAttachStateChangeListener, AutoHideController mainAutoHideController, AutoHideController.Factory autoHideControllerFactory, Optional<TelecomManager> telecomManagerOptional, - InputMethodManager inputMethodManager) { + InputMethodManager inputMethodManager, + Optional<BackAnimation> backAnimation) { mAssistManagerLazy = assistManagerLazy; mAccessibilityManager = accessibilityManager; mDeviceProvisionedController = deviceProvisionedController; @@ -1743,6 +1750,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, mAutoHideControllerFactory = autoHideControllerFactory; mTelecomManagerOptional = telecomManagerOptional; mInputMethodManager = inputMethodManager; + mBackAnimation = backAnimation; } /** Construct a {@link NavigationBar} */ @@ -1759,7 +1767,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, mNavbarOverlayController, mUiEventLogger, mNavBarHelper, mUserTracker, mMainLightBarController, mLightBarControllerFactory, mMainAutoHideController, mAutoHideControllerFactory, mTelecomManagerOptional, - mInputMethodManager); + mInputMethodManager, mBackAnimation); } } } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java index a984974c6bba..98b49b1c4890 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java @@ -59,6 +59,7 @@ import com.android.systemui.statusbar.phone.AutoHideController; import com.android.systemui.statusbar.phone.BarTransitions.TransitionMode; import com.android.systemui.statusbar.phone.LightBarController; import com.android.systemui.statusbar.policy.ConfigurationController; +import com.android.wm.shell.back.BackAnimation; import com.android.wm.shell.pip.Pip; import java.io.FileDescriptor; @@ -109,7 +110,8 @@ public class NavigationBarController implements DumpManager dumpManager, AutoHideController autoHideController, LightBarController lightBarController, - Optional<Pip> pipOptional) { + Optional<Pip> pipOptional, + Optional<BackAnimation> backAnimation) { mContext = context; mHandler = mainHandler; mNavigationBarFactory = navigationBarFactory; @@ -121,7 +123,8 @@ public class NavigationBarController implements mTaskbarDelegate = taskbarDelegate; mTaskbarDelegate.setDependencies(commandQueue, overviewProxyService, navBarHelper, navigationModeController, sysUiFlagsContainer, - dumpManager, autoHideController, lightBarController, pipOptional); + dumpManager, autoHideController, lightBarController, pipOptional, + backAnimation.orElse(null)); mIsTablet = isTablet(mContext); dumpManager.registerDumpable(this); } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java index ac816ba9e8d5..2dd89f3c4dcd 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java @@ -91,6 +91,7 @@ import com.android.systemui.statusbar.phone.AutoHideController; import com.android.systemui.statusbar.phone.LightBarTransitionsController; import com.android.systemui.statusbar.phone.NotificationPanelViewController; import com.android.systemui.statusbar.phone.StatusBar; +import com.android.wm.shell.back.BackAnimation; import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; import com.android.wm.shell.pip.Pip; @@ -1417,6 +1418,10 @@ public class NavigationBarView extends FrameLayout implements pip.removePipExclusionBoundsChangeListener(mPipListener); } + void registerBackAnimation(BackAnimation backAnimation) { + mEdgeBackGestureHandler.setBackAnimation(backAnimation); + } + private static void dumpButton(PrintWriter pw, String caption, ButtonDispatcher button) { pw.print(" " + caption + ": "); if (button == null) { diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java index 002dd10f7356..441e79a97521 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java @@ -69,6 +69,7 @@ import com.android.systemui.statusbar.phone.AutoHideController; import com.android.systemui.statusbar.phone.BarTransitions; import com.android.systemui.statusbar.phone.LightBarController; import com.android.systemui.statusbar.phone.LightBarTransitionsController; +import com.android.wm.shell.back.BackAnimation; import com.android.wm.shell.pip.Pip; import java.io.FileDescriptor; @@ -150,6 +151,7 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, mAutoHideController.touchAutoHide(); } }; + private BackAnimation mBackAnimation; @Inject public TaskbarDelegate(Context context) { @@ -172,7 +174,8 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, SysUiState sysUiState, DumpManager dumpManager, AutoHideController autoHideController, LightBarController lightBarController, - Optional<Pip> pipOptional) { + Optional<Pip> pipOptional, + BackAnimation backAnimation) { // TODO: adding this in the ctor results in a dagger dependency cycle :( mCommandQueue = commandQueue; mOverviewProxyService = overviewProxyService; @@ -184,6 +187,7 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, mLightBarController = lightBarController; mLightBarTransitionsController = createLightBarTransitionsController(); mPipOptional = pipOptional; + mBackAnimation = backAnimation; } // Separated into a method to keep setDependencies() clean/readable. @@ -233,6 +237,7 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, mAutoHideController.setNavigationBar(mAutoHideUiElement); mLightBarController.setNavigationBar(mLightBarTransitionsController); mPipOptional.ifPresent(this::addPipExclusionBoundsChangeListener); + mEdgeBackGestureHandler.setBackAnimation(mBackAnimation); mInitialized = true; } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java index ab48a28facd0..4f4bd1e86e7c 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java @@ -36,6 +36,7 @@ import android.os.Looper; import android.os.RemoteException; import android.os.SystemClock; import android.os.SystemProperties; +import android.os.Trace; import android.provider.DeviceConfig; import android.util.DisplayMetrics; import android.util.Log; @@ -79,6 +80,7 @@ import com.android.systemui.shared.tracing.ProtoTraceable; import com.android.systemui.tracing.ProtoTracer; import com.android.systemui.tracing.nano.EdgeBackGestureHandlerProto; import com.android.systemui.tracing.nano.SystemUiTraceProto; +import com.android.wm.shell.back.BackAnimation; import java.io.PrintWriter; import java.util.ArrayDeque; @@ -231,6 +233,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker private InputChannelCompat.InputEventReceiver mInputEventReceiver; private NavigationEdgeBackPlugin mEdgeBackPlugin; + private BackAnimation mBackAnimation; private int mLeftInset; private int mRightInset; private int mSysUiFlags; @@ -494,7 +497,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker Choreographer.getInstance(), this::onInputEvent); // Add a nav bar panel window - setEdgeBackPlugin(new NavigationBarEdgePanel(mContext)); + setEdgeBackPlugin(new NavigationBarEdgePanel(mContext, mBackAnimation)); mPluginManager.addPluginListener( this, NavigationEdgeBackPlugin.class, /*allowMultiple=*/ false); } @@ -509,7 +512,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker @Override public void onPluginDisconnected(NavigationEdgeBackPlugin plugin) { - setEdgeBackPlugin(new NavigationBarEdgePanel(mContext)); + setEdgeBackPlugin(new NavigationBarEdgePanel(mContext, mBackAnimation)); } private void setEdgeBackPlugin(NavigationEdgeBackPlugin edgeBackPlugin) { @@ -576,7 +579,9 @@ public class EdgeBackGestureHandler extends CurrentUserTracker mMLModelThreshold = DeviceConfig.getFloat(DeviceConfig.NAMESPACE_SYSTEMUI, SystemUiDeviceConfigFlags.BACK_GESTURE_ML_MODEL_THRESHOLD, 0.9f); if (mBackGestureTfClassifierProvider.isActive()) { + Trace.beginSection("EdgeBackGestureHandler#loadVocab"); mVocab = mBackGestureTfClassifierProvider.loadVocab(mContext.getAssets()); + Trace.endSection(); mUseMLModel = true; return; } @@ -930,6 +935,10 @@ public class EdgeBackGestureHandler extends CurrentUserTracker proto.edgeBackGestureHandler.allowGesture = mAllowGesture; } + public void setBackAnimation(BackAnimation backAnimation) { + mBackAnimation = backAnimation; + } + /** * Injectable instance to create a new EdgeBackGestureHandler. * diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java index 8d1dfc842fba..c18209d9abca 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java @@ -57,6 +57,7 @@ import com.android.systemui.animation.Interpolators; import com.android.systemui.plugins.NavigationEdgeBackPlugin; import com.android.systemui.shared.navigationbar.RegionSamplingHelper; import com.android.systemui.statusbar.VibratorHelper; +import com.android.wm.shell.back.BackAnimation; import java.io.PrintWriter; import java.util.concurrent.Executor; @@ -277,11 +278,14 @@ public class NavigationBarEdgePanel extends View implements NavigationEdgeBackPl } }; private BackCallback mBackCallback; + private final BackAnimation mBackAnimation; - public NavigationBarEdgePanel(Context context) { + public NavigationBarEdgePanel(Context context, + BackAnimation backAnimation) { super(context); mWindowManager = context.getSystemService(WindowManager.class); + mBackAnimation = backAnimation; mVibratorHelper = Dependency.get(VibratorHelper.class); mDensity = context.getResources().getDisplayMetrics().density; @@ -459,6 +463,9 @@ public class NavigationBarEdgePanel extends View implements NavigationEdgeBackPl @Override public void onMotionEvent(MotionEvent event) { + if (mBackAnimation != null) { + mBackAnimation.onBackMotion(event); + } if (mVelocityTracker == null) { mVelocityTracker = VelocityTracker.obtain(); } @@ -866,6 +873,9 @@ public class NavigationBarEdgePanel extends View implements NavigationEdgeBackPl // Whenever the trigger back state changes the existing translation animation should be // cancelled mTranslationAnimation.cancel(); + if (mBackAnimation != null) { + mBackAnimation.setTriggerBack(triggerBack); + } } } diff --git a/packages/SystemUI/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerController.java b/packages/SystemUI/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerController.java index c8e2ca7e7ea8..e26c768a5e80 100644 --- a/packages/SystemUI/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerController.java +++ b/packages/SystemUI/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerController.java @@ -93,6 +93,7 @@ public class QRCodeScannerController implements private final DeviceConfigProxy mDeviceConfigProxy; private final ArrayList<Callback> mCallbacks = new ArrayList<>(); private final UserTracker mUserTracker; + private final boolean mConfigEnableLockScreenButton; private HashMap<Integer, ContentObserver> mQRCodeScannerPreferenceObserver = new HashMap<>(); private DeviceConfig.OnPropertiesChangedListener mOnDefaultQRCodeScannerChangedListener = null; @@ -118,6 +119,9 @@ public class QRCodeScannerController implements mSecureSettings = secureSettings; mDeviceConfigProxy = proxy; mUserTracker = userTracker; + + mConfigEnableLockScreenButton = mContext.getResources().getBoolean( + android.R.bool.config_enableQrCodeScannerOnLockScreen); } /** @@ -156,7 +160,7 @@ public class QRCodeScannerController implements * Returns true if lock screen entry point for QR Code Scanner is to be enabled. */ public boolean isEnabledForLockScreenButton() { - return mQRCodeScannerEnabled && mIntent != null; + return mQRCodeScannerEnabled && mIntent != null && mConfigEnableLockScreenButton; } /** @@ -235,6 +239,11 @@ public class QRCodeScannerController implements } private void updateQRCodeScannerPreferenceDetails(boolean updateSettings) { + if (!mConfigEnableLockScreenButton) { + // Settings only apply to lock screen entry point. + return; + } + boolean prevQRCodeScannerEnabled = mQRCodeScannerEnabled; mQRCodeScannerEnabled = mSecureSettings.getIntForUser(LOCK_SCREEN_SHOW_QR_CODE_SCANNER, 0, mUserTracker.getUserId()) != 0; @@ -251,8 +260,15 @@ public class QRCodeScannerController implements private void updateQRCodeScannerActivityDetails() { String qrCodeScannerActivity = mDeviceConfigProxy.getString( DeviceConfig.NAMESPACE_SYSTEMUI, - SystemUiDeviceConfigFlags.DEFAULT_QR_CODE_SCANNER, - mContext.getResources().getString(R.string.def_qr_code_component)); + SystemUiDeviceConfigFlags.DEFAULT_QR_CODE_SCANNER, ""); + + // "" means either the flags is not available or is set to "", and in both the cases we + // want to use R.string.def_qr_code_component + if (Objects.equals(qrCodeScannerActivity, "")) { + qrCodeScannerActivity = + mContext.getResources().getString(R.string.def_qr_code_component); + } + String prevQrCodeScannerActivity = mQRCodeScannerActivity; ComponentName componentName = null; Intent intent = new Intent(); @@ -281,8 +297,12 @@ public class QRCodeScannerController implements // Our intent should always be explicit and should have a component set if (intent.getComponent() == null) return false; - int flags = PackageManager.MATCH_DEFAULT_ONLY | PackageManager.MATCH_DIRECT_BOOT_AWARE - | PackageManager.MATCH_DIRECT_BOOT_UNAWARE; + int flags = PackageManager.MATCH_DIRECT_BOOT_AWARE + | PackageManager.MATCH_DIRECT_BOOT_UNAWARE + | PackageManager.MATCH_UNINSTALLED_PACKAGES + | PackageManager.MATCH_DISABLED_COMPONENTS + | PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS + | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS; return !mContext.getPackageManager().queryIntentActivities(intent, flags).isEmpty(); } @@ -296,6 +316,11 @@ public class QRCodeScannerController implements } private void unregisterQRCodePreferenceObserver() { + if (!mConfigEnableLockScreenButton) { + // Settings only apply to lock screen entry point. + return; + } + mQRCodeScannerPreferenceObserver.forEach((key, value) -> { mSecureSettings.unregisterContentObserver(value); }); @@ -357,6 +382,11 @@ public class QRCodeScannerController implements } private void registerQRCodePreferenceObserver() { + if (!mConfigEnableLockScreenButton) { + // Settings only apply to lock screen entry point. + return; + } + int userId = mUserTracker.getUserId(); if (mQRCodeScannerPreferenceObserver.getOrDefault(userId, null) != null) return; diff --git a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt index e10e4d8a825c..7ac9205c7922 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt @@ -56,7 +56,6 @@ import javax.inject.Named */ class FooterActionsController @Inject constructor( view: FooterActionsView, - private val qsPanelController: QSPanelController, private val activityStarter: ActivityStarter, private val userManager: UserManager, private val userTracker: UserTracker, @@ -82,7 +81,6 @@ class FooterActionsController @Inject constructor( private val settingsButton: SettingsButton = view.findViewById(R.id.settings_button) private val settingsButtonContainer: View? = view.findViewById(R.id.settings_button_container) - private val editButton: View = view.findViewById(android.R.id.edit) private val powerMenuLite: View = view.findViewById(R.id.pm_lite) private val onUserInfoChangedListener = OnUserInfoChangedListener { _, picture, _ -> @@ -176,13 +174,6 @@ class FooterActionsController @Inject constructor( powerMenuLite.visibility = View.GONE } settingsButton.setOnClickListener(onClickListener) - editButton.setOnClickListener(View.OnClickListener { view: View? -> - if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) { - return@OnClickListener - } - activityStarter.postQSRunnableDismissingKeyguard { qsPanelController.showEdit(view) } - }) - updateView() } diff --git a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsControllerBuilder.kt b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsControllerBuilder.kt index dd4dc87d8a9f..7694be51cba6 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsControllerBuilder.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsControllerBuilder.kt @@ -36,7 +36,6 @@ import javax.inject.Inject import javax.inject.Named class FooterActionsControllerBuilder @Inject constructor( - private val qsPanelController: QSPanelController, private val activityStarter: ActivityStarter, private val userManager: UserManager, private val userTracker: UserTracker, @@ -66,7 +65,7 @@ class FooterActionsControllerBuilder @Inject constructor( } fun build(): FooterActionsController { - return FooterActionsController(view, qsPanelController, activityStarter, userManager, + return FooterActionsController(view, activityStarter, userManager, userTracker, userInfoController, multiUserSwitchControllerFactory.create(view), deviceProvisionedController, falsingManager, metricsLogger, tunerService, globalActionsDialog, uiEventLogger, showPMLiteButton, buttonsVisibleState, diff --git a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsView.kt b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsView.kt index f81f7bf73f64..e6fa2ae8dad1 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsView.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsView.kt @@ -43,7 +43,6 @@ class FooterActionsView(context: Context?, attrs: AttributeSet?) : LinearLayout( private lateinit var multiUserSwitch: MultiUserSwitch private lateinit var multiUserAvatar: ImageView private lateinit var tunerIcon: View - private lateinit var editTilesButton: View private var settingsCogAnimator: TouchAnimator? = null @@ -52,7 +51,6 @@ class FooterActionsView(context: Context?, attrs: AttributeSet?) : LinearLayout( override fun onFinishInflate() { super.onFinishInflate() - editTilesButton = requireViewById(android.R.id.edit) settingsButton = findViewById(R.id.settings_button) settingsContainer = findViewById(R.id.settings_button_container) multiUserSwitch = findViewById(R.id.multi_user_switch) @@ -130,7 +128,6 @@ class FooterActionsView(context: Context?, attrs: AttributeSet?) : LinearLayout( private fun updateClickabilities() { multiUserSwitch.isClickable = multiUserSwitch.visibility == VISIBLE - editTilesButton.isClickable = editTilesButton.visibility == VISIBLE settingsButton.isClickable = settingsButton.visibility == VISIBLE } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java index 066a286b271d..4622660a6c15 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java @@ -47,6 +47,7 @@ public class QSFooterView extends FrameLayout { private PageIndicator mPageIndicator; private TextView mBuildText; private View mActionsContainer; + private View mEditButton; @Nullable protected TouchAnimator mFooterAnimator; @@ -79,6 +80,7 @@ public class QSFooterView extends FrameLayout { mPageIndicator = findViewById(R.id.footer_page_indicator); mActionsContainer = requireViewById(R.id.qs_footer_actions); mBuildText = findViewById(R.id.build); + mEditButton = findViewById(android.R.id.edit); updateResources(); setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); @@ -130,6 +132,7 @@ public class QSFooterView extends FrameLayout { .addFloat(mActionsContainer, "alpha", 0, 1) .addFloat(mPageIndicator, "alpha", 0, 1) .addFloat(mBuildText, "alpha", 0, 1) + .addFloat(mEditButton, "alpha", 0, 1) .setStartDelay(0.9f); return builder.build(); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java index e7c06e3c7ede..5327b7e3ba26 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java @@ -26,6 +26,8 @@ import android.widget.TextView; import android.widget.Toast; import com.android.systemui.R; +import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.plugins.FalsingManager; import com.android.systemui.qs.dagger.QSScope; import com.android.systemui.settings.UserTracker; import com.android.systemui.util.ViewController; @@ -45,10 +47,15 @@ public class QSFooterViewController extends ViewController<QSFooterView> impleme private final FooterActionsController mFooterActionsController; private final TextView mBuildText; private final PageIndicator mPageIndicator; + private final View mEditButton; + private final FalsingManager mFalsingManager; + private final ActivityStarter mActivityStarter; @Inject QSFooterViewController(QSFooterView view, UserTracker userTracker, + FalsingManager falsingManager, + ActivityStarter activityStarter, QSPanelController qsPanelController, QuickQSPanelController quickQSPanelController, @Named(QS_FOOTER) FooterActionsController footerActionsController) { @@ -57,9 +64,12 @@ public class QSFooterViewController extends ViewController<QSFooterView> impleme mQsPanelController = qsPanelController; mQuickQSPanelController = quickQSPanelController; mFooterActionsController = footerActionsController; + mFalsingManager = falsingManager; + mActivityStarter = activityStarter; mBuildText = mView.findViewById(R.id.build); mPageIndicator = mView.findViewById(R.id.footer_page_indicator); + mEditButton = mView.findViewById(android.R.id.edit); } @Override @@ -91,6 +101,14 @@ public class QSFooterViewController extends ViewController<QSFooterView> impleme } return false; }); + + mEditButton.setOnClickListener(view -> { + if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) { + return; + } + mActivityStarter + .postQSRunnableDismissingKeyguard(() -> mQsPanelController.showEdit(view)); + }); mQsPanelController.setFooterPageIndicator(mPageIndicator); mView.updateEverything(); } @@ -103,6 +121,7 @@ public class QSFooterViewController extends ViewController<QSFooterView> impleme @Override public void setVisibility(int visibility) { mView.setVisibility(visibility); + mEditButton.setClickable(visibility == View.VISIBLE); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java index 596d8f01248a..e2964eaf2a23 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java @@ -129,17 +129,27 @@ public class UiModeNightTile extends QSTileImpl<QSTile.BooleanState> implements ? R.string.quick_settings_dark_mode_secondary_label_until_sunrise : R.string.quick_settings_dark_mode_secondary_label_on_at_sunset); } else if (uiMode == UiModeManager.MODE_NIGHT_CUSTOM) { - final boolean use24HourFormat = android.text.format.DateFormat.is24HourFormat(mContext); - final LocalTime time; - if (nightMode) { - time = mUiModeManager.getCustomNightModeEnd(); + int nightModeCustomType = mUiModeManager.getNightModeCustomType(); + if (nightModeCustomType == UiModeManager.MODE_NIGHT_CUSTOM_TYPE_SCHEDULE) { + final boolean use24HourFormat = android.text.format.DateFormat.is24HourFormat( + mContext); + final LocalTime time; + if (nightMode) { + time = mUiModeManager.getCustomNightModeEnd(); + } else { + time = mUiModeManager.getCustomNightModeStart(); + } + state.secondaryLabel = mContext.getResources().getString(nightMode + ? R.string.quick_settings_dark_mode_secondary_label_until + : R.string.quick_settings_dark_mode_secondary_label_on_at, + use24HourFormat ? time.toString() : formatter.format(time)); + } else if (nightModeCustomType == UiModeManager.MODE_NIGHT_CUSTOM_TYPE_BEDTIME) { + state.secondaryLabel = mContext.getResources().getString(nightMode + ? R.string.quick_settings_dark_mode_secondary_label_until_bedtime_ends + : R.string.quick_settings_dark_mode_secondary_label_on_at_bedtime); } else { - time = mUiModeManager.getCustomNightModeStart(); + state.secondaryLabel = null; } - state.secondaryLabel = mContext.getResources().getString(nightMode - ? R.string.quick_settings_dark_mode_secondary_label_until - : R.string.quick_settings_dark_mode_secondary_label_on_at, - use24HourFormat ? time.toString() : formatter.format(time)); } else { state.secondaryLabel = null; } diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java index 60060aaf72da..00a314943f7a 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java @@ -31,9 +31,9 @@ import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHE import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_SHELL_TRANSITIONS; import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_SPLIT_SCREEN; import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_STARTING_WINDOW; -import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SMARTSPACE_TRANSITION_CONTROLLER; import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SUPPORTS_WINDOW_CORNERS; import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY; +import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_UNLOCK_ANIMATION_CONTROLLER; import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_WINDOW_CORNER_RADIUS; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DEVICE_DOZING; @@ -83,6 +83,7 @@ import com.android.systemui.Dumpable; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dump.DumpManager; +import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import com.android.systemui.keyguard.ScreenLifecycle; import com.android.systemui.model.SysUiState; import com.android.systemui.navigationbar.NavigationBar; @@ -97,7 +98,6 @@ import com.android.systemui.shared.recents.ISystemUiProxy; import com.android.systemui.shared.recents.model.Task; import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.QuickStepContract; -import com.android.systemui.shared.system.smartspace.SmartspaceTransitionController; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.phone.NotificationPanelViewController; @@ -161,7 +161,7 @@ public class OverviewProxyService extends CurrentUserTracker implements private final CommandQueue mCommandQueue; private final ShellTransitions mShellTransitions; private final Optional<StartingSurface> mStartingSurface; - private final SmartspaceTransitionController mSmartspaceTransitionController; + private final KeyguardUnlockAnimationController mSysuiUnlockAnimationController; private final Optional<RecentTasks> mRecentTasks; private final UiEventLogger mUiEventLogger; @@ -503,8 +503,8 @@ public class OverviewProxyService extends CurrentUserTracker implements KEY_EXTRA_SHELL_STARTING_WINDOW, startingwindow.createExternalInterface().asBinder())); params.putBinder( - KEY_EXTRA_SMARTSPACE_TRANSITION_CONTROLLER, - mSmartspaceTransitionController.createExternalInterface().asBinder()); + KEY_EXTRA_UNLOCK_ANIMATION_CONTROLLER, + mSysuiUnlockAnimationController.asBinder()); mRecentTasks.ifPresent(recentTasks -> params.putBinder( KEY_EXTRA_RECENT_TASKS, recentTasks.createExternalInterface().asBinder())); @@ -570,8 +570,8 @@ public class OverviewProxyService extends CurrentUserTracker implements BroadcastDispatcher broadcastDispatcher, ShellTransitions shellTransitions, ScreenLifecycle screenLifecycle, - SmartspaceTransitionController smartspaceTransitionController, UiEventLogger uiEventLogger, + KeyguardUnlockAnimationController sysuiUnlockAnimationController, DumpManager dumpManager) { super(broadcastDispatcher); mContext = context; @@ -644,7 +644,7 @@ public class OverviewProxyService extends CurrentUserTracker implements updateEnabledState(); startConnectionToCurrentUser(); mStartingSurface = startingSurface; - mSmartspaceTransitionController = smartspaceTransitionController; + mSysuiUnlockAnimationController = sysuiUnlockAnimationController; } @Override diff --git a/packages/SystemUI/src/com/android/systemui/shared/system/smartspace/SmartspaceTransitionController.kt b/packages/SystemUI/src/com/android/systemui/shared/system/smartspace/SmartspaceTransitionController.kt deleted file mode 100644 index 89b3df0f495f..000000000000 --- a/packages/SystemUI/src/com/android/systemui/shared/system/smartspace/SmartspaceTransitionController.kt +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.shared.system.smartspace - -import android.graphics.Rect -import android.view.View -import com.android.systemui.shared.system.ActivityManagerWrapper -import com.android.systemui.shared.system.QuickStepContract -import kotlin.math.min - -/** - * Controller that keeps track of SmartSpace instances in remote processes (such as Launcher), - * allowing System UI to query or update their state during shared-element transitions. - */ -class SmartspaceTransitionController { - - /** - * Implementation of [ISmartspaceTransitionController] that we provide to Launcher, allowing it - * to provide us with a callback to query and update the state of its Smartspace. - */ - private val ISmartspaceTransitionController = object : ISmartspaceTransitionController.Stub() { - override fun setSmartspace(callback: ISmartspaceCallback?) { - this@SmartspaceTransitionController.launcherSmartspace = callback - updateLauncherSmartSpaceState() - } - } - - /** - * Callback provided by Launcher to allow us to query and update the state of its SmartSpace. - */ - public var launcherSmartspace: ISmartspaceCallback? = null - - public var lockscreenSmartspace: View? = null - - /** - * Cached state of the Launcher SmartSpace. Retrieving the state is an IPC, so we should avoid - * unnecessary - */ - public var mLauncherSmartspaceState: SmartspaceState? = null - - /** - * The bounds of our SmartSpace when the shared element transition began. We'll interpolate - * between this and [smartspaceDestinationBounds] as the dismiss amount changes. - */ - private val smartspaceOriginBounds = Rect() - - /** The bounds of the Launcher's SmartSpace, which is where we are animating our SmartSpace. */ - - private val smartspaceDestinationBounds = Rect() - - fun createExternalInterface(): ISmartspaceTransitionController { - return ISmartspaceTransitionController - } - - /** - * Updates [mLauncherSmartspaceState] and returns it. This will trigger a binder call, so use the - * cached [mLauncherSmartspaceState] if possible. - */ - fun updateLauncherSmartSpaceState(): SmartspaceState? { - return launcherSmartspace?.smartspaceState.also { - mLauncherSmartspaceState = it - } - } - - fun prepareForUnlockTransition() { - updateLauncherSmartSpaceState().also { state -> - if (state?.boundsOnScreen != null && lockscreenSmartspace != null) { - lockscreenSmartspace!!.getBoundsOnScreen(smartspaceOriginBounds) - with(smartspaceDestinationBounds) { - set(state.boundsOnScreen) - offset(-lockscreenSmartspace!!.paddingLeft, - -lockscreenSmartspace!!.paddingTop) - } - } - } - } - - fun setProgressToDestinationBounds(progress: Float) { - if (!isSmartspaceTransitionPossible()) { - return - } - - val progressClamped = min(1f, progress) - - // Calculate the distance (relative to the origin) that we need to be for the current - // progress value. - val progressX = - (smartspaceDestinationBounds.left - smartspaceOriginBounds.left) * progressClamped - val progressY = - (smartspaceDestinationBounds.top - smartspaceOriginBounds.top) * progressClamped - - val lockscreenSmartspaceCurrentBounds = Rect().also { - lockscreenSmartspace!!.getBoundsOnScreen(it) - } - - // Figure out how far that is from our present location on the screen. This approach - // compensates for the fact that our parent container is also translating to animate out. - val dx = smartspaceOriginBounds.left + progressX - - lockscreenSmartspaceCurrentBounds.left - var dy = smartspaceOriginBounds.top + progressY - - lockscreenSmartspaceCurrentBounds.top - - with(lockscreenSmartspace!!) { - translationX = translationX + dx - translationY = translationY + dy - } - } - - /** - * Whether we're capable of performing the Smartspace shared element transition when we unlock. - * This is true if: - * - * - The Launcher registered a Smartspace with us, it's reporting non-empty bounds on screen. - * - Launcher is behind the keyguard, and the Smartspace is visible on the currently selected - * page. - */ - public fun isSmartspaceTransitionPossible(): Boolean { - val smartSpaceNullOrBoundsEmpty = mLauncherSmartspaceState?.boundsOnScreen?.isEmpty ?: true - return isLauncherUnderneath() && !smartSpaceNullOrBoundsEmpty - } - - companion object { - fun isLauncherUnderneath(): Boolean { - return ActivityManagerWrapper.getInstance() - .runningTask?.topActivity?.className?.equals( - QuickStepContract.LAUNCHER_ACTIVITY_CLASS_NAME) ?: false - } - } -}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt index c8115e2c197a..9a932bae833e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt @@ -355,7 +355,8 @@ class LightRevealScrim(context: Context?, attrs: AttributeSet?) : View(context, } override fun onDraw(canvas: Canvas?) { - if (canvas == null || revealGradientWidth <= 0 || revealGradientHeight <= 0) { + if (canvas == null || revealGradientWidth <= 0 || revealGradientHeight <= 0 + || revealAmount == 0f) { if (revealAmount < 1f) { canvas?.drawColor(revealGradientEndColor) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt index 46004db3067a..267ee6d2d177 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt @@ -154,6 +154,16 @@ class NotificationShadeDepthController @Inject constructor( } /** + * We're unlocking, and should not blur as the panel expansion changes. + */ + var blursDisabledForUnlock: Boolean = false + set(value) { + if (field == value) return + field = value + scheduleUpdate() + } + + /** * Force stop blur effect when necessary. */ private var scrimsVisible: Boolean = false @@ -192,7 +202,7 @@ class NotificationShadeDepthController @Inject constructor( combinedBlur = max(combinedBlur, blurUtils.blurRadiusOfRatio(transitionToFullShadeProgress)) var shadeRadius = max(combinedBlur, wakeAndUnlockBlurRadius) - if (blursDisabledForAppLaunch) { + if (blursDisabledForAppLaunch || blursDisabledForUnlock) { shadeRadius = 0f } @@ -309,9 +319,7 @@ class NotificationShadeDepthController @Inject constructor( /** * Update blurs when pulling down the shade */ - override fun onPanelExpansionChanged( - rawFraction: Float, expanded: Boolean, tracking: Boolean - ) { + override fun onPanelExpansionChanged(rawFraction: Float, expanded: Boolean, tracking: Boolean) { val timestamp = SystemClock.elapsedRealtimeNanos() val expansion = MathUtils.saturate( (rawFraction - panelPullDownMinFraction) / (1f - panelPullDownMinFraction)) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java index ea90bdd940a7..6c3a9093fa98 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java @@ -17,14 +17,10 @@ package com.android.systemui.statusbar; import android.content.Context; -import android.database.ContentObserver; -import android.media.AudioAttributes; import android.os.AsyncTask; -import android.os.Handler; -import android.os.UserHandle; +import android.os.VibrationAttributes; import android.os.VibrationEffect; import android.os.Vibrator; -import android.provider.Settings; import com.android.systemui.dagger.SysUISingleton; @@ -37,19 +33,8 @@ public class VibratorHelper { private final Vibrator mVibrator; private final Context mContext; - private boolean mHapticFeedbackEnabled; - private static final AudioAttributes STATUS_BAR_VIBRATION_ATTRIBUTES = - new AudioAttributes.Builder() - .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) - .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION) - .build(); - - final private ContentObserver mVibrationObserver = new ContentObserver(Handler.getMain()) { - @Override - public void onChange(boolean selfChange) { - updateHapticFeedBackEnabled(); - } - }; + private static final VibrationAttributes TOUCH_VIBRATION_ATTRIBUTES = + VibrationAttributes.createForUsage(VibrationAttributes.USAGE_TOUCH); /** */ @@ -57,23 +42,11 @@ public class VibratorHelper { public VibratorHelper(Context context) { mContext = context; mVibrator = context.getSystemService(Vibrator.class); - - mContext.getContentResolver().registerContentObserver( - Settings.System.getUriFor(Settings.System.HAPTIC_FEEDBACK_ENABLED), true, - mVibrationObserver); - mVibrationObserver.onChange(false /* selfChange */); } public void vibrate(final int effectId) { - if (mHapticFeedbackEnabled) { - AsyncTask.execute(() -> - mVibrator.vibrate(VibrationEffect.get(effectId, false /* fallback */), - STATUS_BAR_VIBRATION_ATTRIBUTES)); - } - } - - private void updateHapticFeedBackEnabled() { - mHapticFeedbackEnabled = Settings.System.getIntForUser(mContext.getContentResolver(), - Settings.System.HAPTIC_FEEDBACK_ENABLED, 0, UserHandle.USER_CURRENT) != 0; + AsyncTask.execute(() -> + mVibrator.vibrate(VibrationEffect.get(effectId, false /* fallback */), + TOUCH_VIBRATION_ATTRIBUTES)); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt index 3449bd8e2686..5aeab84b677c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt @@ -23,7 +23,6 @@ import android.graphics.drawable.AnimatedImageDrawable import android.os.Handler import android.service.notification.NotificationListenerService.Ranking import android.service.notification.NotificationListenerService.RankingMap -import com.android.internal.statusbar.NotificationVisibility import com.android.internal.widget.ConversationLayout import com.android.internal.widget.MessagingImageMessage import com.android.internal.widget.MessagingLayout @@ -31,6 +30,7 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.collection.inflation.BindEventManager import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener @@ -72,7 +72,8 @@ class ConversationNotificationProcessor @Inject constructor( */ @SysUISingleton class AnimatedImageNotificationManager @Inject constructor( - private val notificationEntryManager: NotificationEntryManager, + private val notifCollection: CommonNotifCollection, + private val bindEventManager: BindEventManager, private val headsUpManager: HeadsUpManager, private val statusBarStateController: StatusBarStateController ) { @@ -83,33 +84,23 @@ class AnimatedImageNotificationManager @Inject constructor( fun bind() { headsUpManager.addListener(object : OnHeadsUpChangedListener { override fun onHeadsUpStateChanged(entry: NotificationEntry, isHeadsUp: Boolean) { - entry.row?.let { row -> - updateAnimatedImageDrawables(row, animating = isHeadsUp || isStatusBarExpanded) - } + updateAnimatedImageDrawables(entry) } }) statusBarStateController.addCallback(object : StatusBarStateController.StateListener { override fun onExpandedChanged(isExpanded: Boolean) { isStatusBarExpanded = isExpanded - notificationEntryManager.activeNotificationsForCurrentUser.forEach { entry -> - entry.row?.let { row -> - updateAnimatedImageDrawables(row, animating = isExpanded || row.isHeadsUp) - } - } + notifCollection.allNotifs.forEach(::updateAnimatedImageDrawables) } }) - notificationEntryManager.addNotificationEntryListener(object : NotificationEntryListener { - override fun onEntryInflated(entry: NotificationEntry) { - entry.row?.let { row -> - updateAnimatedImageDrawables( - row, - animating = isStatusBarExpanded || row.isHeadsUp) - } - } - override fun onEntryReinflated(entry: NotificationEntry) = onEntryInflated(entry) - }) + bindEventManager.addListener(::updateAnimatedImageDrawables) } + private fun updateAnimatedImageDrawables(entry: NotificationEntry) = + entry.row?.let { row -> + updateAnimatedImageDrawables(row, animating = row.isHeadsUp || isStatusBarExpanded) + } + private fun updateAnimatedImageDrawables(row: ExpandableNotificationRow, animating: Boolean) = (row.layouts?.asSequence() ?: emptySequence()) .flatMap { layout -> layout.allViews.asSequence() } @@ -138,7 +129,7 @@ class AnimatedImageNotificationManager @Inject constructor( */ @SysUISingleton class ConversationNotificationManager @Inject constructor( - private val notificationEntryManager: NotificationEntryManager, + private val bindEventManager: BindEventManager, private val notificationGroupManager: NotificationGroupManagerLegacy, private val context: Context, private val notifCollection: CommonNotifCollection, @@ -151,35 +142,12 @@ class ConversationNotificationManager @Inject constructor( private var notifPanelCollapsed = true - private val entryManagerListener = object : NotificationEntryListener { - override fun onNotificationRankingUpdated(rankingMap: RankingMap) = - updateNotificationRanking(rankingMap) - override fun onEntryInflated(entry: NotificationEntry) = - onEntryViewBound(entry) - override fun onEntryReinflated(entry: NotificationEntry) = onEntryInflated(entry) - override fun onEntryRemoved( - entry: NotificationEntry, - visibility: NotificationVisibility?, - removedByUser: Boolean, - reason: Int - ) = removeTrackedEntry(entry) - } - - private val notifCollectionListener = object : NotifCollectionListener { - override fun onRankingUpdate(ranking: RankingMap) = - updateNotificationRanking(ranking) - - override fun onEntryRemoved(entry: NotificationEntry, reason: Int) { - removeTrackedEntry(entry) - } - } - private fun updateNotificationRanking(rankingMap: RankingMap) { fun getLayouts(view: NotificationContentView) = sequenceOf(view.contractedChild, view.expandedChild, view.headsUpChild) val ranking = Ranking() val activeConversationEntries = states.keys.asSequence() - .mapNotNull { notificationEntryManager.getActiveNotificationUnfiltered(it) } + .mapNotNull { notifCollection.getEntry(it) } for (entry in activeConversationEntries) { if (rankingMap.getRanking(entry.sbn.key, ranking) && ranking.isConversation) { val important = ranking.channel.isImportantConversation @@ -204,7 +172,7 @@ class ConversationNotificationManager @Inject constructor( layout.setIsImportantConversation(important, false) } } - if (changed) { + if (changed && !featureFlags.isNewPipelineEnabled()) { notificationGroupManager.updateIsolation(entry) } } @@ -233,11 +201,14 @@ class ConversationNotificationManager @Inject constructor( } init { - if (featureFlags.isNewPipelineEnabled()) { - notifCollection.addCollectionListener(notifCollectionListener) - } else { - notificationEntryManager.addNotificationEntryListener(entryManagerListener) - } + notifCollection.addCollectionListener(object : NotifCollectionListener { + override fun onRankingUpdate(ranking: RankingMap) = + updateNotificationRanking(ranking) + + override fun onEntryRemoved(entry: NotificationEntry, reason: Int) = + removeTrackedEntry(entry) + }) + bindEventManager.addListener(::onEntryViewBound) } private fun ConversationState.shouldIncrementUnread(newBuilder: Notification.Builder) = @@ -265,11 +236,10 @@ class ConversationNotificationManager @Inject constructor( val expanded = states .asSequence() .mapNotNull { (key, _) -> - notificationEntryManager.getActiveNotificationUnfiltered(key) - ?.let { entry -> - if (entry.row?.isExpanded == true) key to entry - else null - } + notifCollection.getEntry(key)?.let { entry -> + if (entry.row?.isExpanded == true) key to entry + else null + } } .toMap() states.replaceAll { key, state -> diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryListener.java index 97ae83ef3f6a..643deb7463b5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryListener.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryListener.java @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.notification; import android.annotation.Nullable; +import android.app.NotificationChannel; +import android.os.UserHandle; import android.service.notification.NotificationListenerService; import android.service.notification.NotificationListenerService.RankingMap; import android.service.notification.StatusBarNotification; @@ -106,4 +108,20 @@ public interface NotificationEntryListener { */ default void onNotificationRankingUpdated(RankingMap rankingMap) { } + + /** + * Called when a notification channel is modified, in response to + * {@link NotificationListenerService#onNotificationChannelModified}. + * + * @param pkgName the package the notification channel belongs to. + * @param user the user the notification channel belongs to. + * @param channel the channel being modified. + * @param modificationType the type of modification that occurred to the channel. + */ + default void onNotificationChannelModified( + String pkgName, + UserHandle user, + NotificationChannel channel, + int modificationType) { + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java index 062e2395f3dd..c33160858d0f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java @@ -21,8 +21,10 @@ import static com.android.systemui.statusbar.notification.collection.NotifCollec import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationCallback; import android.app.Notification; +import android.app.NotificationChannel; import android.os.RemoteException; import android.os.SystemClock; +import android.os.UserHandle; import android.service.notification.NotificationListenerService; import android.service.notification.NotificationListenerService.Ranking; import android.service.notification.NotificationListenerService.RankingMap; @@ -402,6 +404,15 @@ public class NotificationEntryManager implements @Override public void onNotificationsInitialized() { } + + @Override + public void onNotificationChannelModified( + String pkgName, + UserHandle user, + NotificationChannel channel, + int modificationType) { + notifyChannelModified(pkgName, user, channel, modificationType); + } }; /** @@ -779,6 +790,19 @@ public class NotificationEntryManager implements } } + void notifyChannelModified( + String pkgName, + UserHandle user, + NotificationChannel channel, + int modificationType) { + for (NotifCollectionListener listener : mNotifCollectionListeners) { + listener.onNotificationChannelModified(pkgName, user, channel, modificationType); + } + for (NotificationEntryListener listener : mNotificationEntryListeners) { + listener.onNotificationChannelModified(pkgName, user, channel, modificationType); + } + } + private void updateRankingOfPendingNotifications(@Nullable RankingMap rankingMap) { if (rankingMap == null) { return; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/SectionHeaderVisibilityProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/SectionHeaderVisibilityProvider.kt new file mode 100644 index 000000000000..03b978e7784c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/SectionHeaderVisibilityProvider.kt @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification + +import com.android.systemui.dagger.SysUISingleton +import javax.inject.Inject + +/** + * A class which keeps track of whether section headers should be shown in the notification shade. + * + * (In an ideal world, this would directly monitor the state of the keyguard and invalidate the + * pipeline to show/hide headers, but the KeyguardController already invalidates the pipeline when + * the keyguard's state changes. Instead of having both classes monitor for state changes and ending + * up with duplicate runs of the pipeline, we let the KeyguardController update the header + * visibility when it invalidates, and we just store that state here.) + */ +@SysUISingleton +class SectionHeaderVisibilityProvider @Inject constructor() { + var sectionHeadersVisible = true +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java index 760556131c60..2a2cc81c3223 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java @@ -46,6 +46,7 @@ import android.annotation.IntDef; import android.annotation.MainThread; import android.annotation.UserIdInt; import android.app.Notification; +import android.app.NotificationChannel; import android.os.Handler; import android.os.RemoteException; import android.os.Trace; @@ -72,6 +73,7 @@ import com.android.systemui.statusbar.notification.collection.coalescer.Coalesce import com.android.systemui.statusbar.notification.collection.coalescer.GroupCoalescer; import com.android.systemui.statusbar.notification.collection.coalescer.GroupCoalescer.BatchableNotificationHandler; import com.android.systemui.statusbar.notification.collection.notifcollection.BindEntryEvent; +import com.android.systemui.statusbar.notification.collection.notifcollection.ChannelChangedEvent; import com.android.systemui.statusbar.notification.collection.notifcollection.CleanUpEntryEvent; import com.android.systemui.statusbar.notification.collection.notifcollection.CollectionReadyForBuildListener; import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats; @@ -424,6 +426,16 @@ public class NotifCollection implements Dumpable { dispatchEventsAndRebuildList(); } + private void onNotificationChannelModified( + String pkgName, + UserHandle user, + NotificationChannel channel, + int modificationType) { + Assert.isMainThread(); + mEventQueue.add(new ChannelChangedEvent(pkgName, user, channel, modificationType)); + dispatchEventsAndRebuildList(); + } + private void onNotificationsInitialized() { mInitializedTimestamp = mClock.uptimeMillis(); } @@ -835,6 +847,19 @@ public class NotifCollection implements Dumpable { } @Override + public void onNotificationChannelModified( + String pkgName, + UserHandle user, + NotificationChannel channel, + int modificationType) { + NotifCollection.this.onNotificationChannelModified( + pkgName, + user, + channel, + modificationType); + } + + @Override public void onNotificationsInitialized() { NotifCollection.this.onNotificationsInitialized(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescer.java index 09ae7eb38a06..87e531c01a5f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescer.java @@ -260,7 +260,8 @@ public class GroupCoalescer implements Dumpable { } events.sort(mEventComparator); - mLogger.logEmitBatch(batch.mGroupKey); + long batchAge = mClock.uptimeMillis() - batch.mCreatedTimestamp; + mLogger.logEmitBatch(batch.mGroupKey, batch.mMembers.size(), batchAge); mHandler.onNotificationBatchPosted(events); } @@ -337,6 +338,6 @@ public class GroupCoalescer implements Dumpable { void onNotificationBatchPosted(List<CoalescedEvent> events); } - private static final int MIN_GROUP_LINGER_DURATION = 50; + private static final int MIN_GROUP_LINGER_DURATION = 200; private static final int MAX_GROUP_LINGER_DURATION = 500; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerLogger.kt index d4d5b64240c2..211e37473a70 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerLogger.kt @@ -32,11 +32,13 @@ class GroupCoalescerLogger @Inject constructor( }) } - fun logEmitBatch(groupKey: String) { + fun logEmitBatch(groupKey: String, batchSize: Int, batchAgeMs: Long) { buffer.log(TAG, LogLevel.DEBUG, { str1 = groupKey + int1 = batchSize + long1 = batchAgeMs }, { - "Emitting event batch for group $str1" + "Emitting batch for group $str1 size=$int1 age=${long1}ms" }) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinator.java index 3a39c39cfb20..f04b24ebfb42 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinator.java @@ -101,7 +101,7 @@ public class AppOpsCoordinator implements Coordinator { }; /** - * Puts foreground service notifications into its own section. + * Puts colorized foreground service and call notifications into its own section. */ private final NotifSectioner mNotifSectioner = new NotifSectioner("ForegroundService", NotificationPriorityBucketKt.BUCKET_FOREGROUND_SERVICE) { @@ -109,12 +109,22 @@ public class AppOpsCoordinator implements Coordinator { public boolean isInSection(ListEntry entry) { NotificationEntry notificationEntry = entry.getRepresentativeEntry(); if (notificationEntry != null) { - Notification notification = notificationEntry.getSbn().getNotification(); - return notification.isForegroundService() - && notification.isColorized() - && entry.getRepresentativeEntry().getImportance() > IMPORTANCE_MIN; + return isColorizedForegroundService(notificationEntry) || isCall(notificationEntry); } return false; } + + private boolean isColorizedForegroundService(NotificationEntry entry) { + Notification notification = entry.getSbn().getNotification(); + return notification.isForegroundService() + && notification.isColorized() + && entry.getImportance() > IMPORTANCE_MIN; + } + + private boolean isCall(NotificationEntry entry) { + Notification notification = entry.getSbn().getNotification(); + return entry.getImportance() > IMPORTANCE_MIN + && notification.isStyle(Notification.CallStyle.class); + } }; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt index e59f4a62f9b7..ba88ad7844f1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt @@ -20,11 +20,13 @@ import com.android.systemui.statusbar.notification.collection.ListEntry import com.android.systemui.statusbar.notification.collection.NotifPipeline import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner import com.android.systemui.statusbar.notification.collection.render.NodeController import com.android.systemui.statusbar.notification.dagger.PeopleHeader import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier +import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.PeopleNotificationType import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_NON_PERSON import com.android.systemui.statusbar.notification.stack.BUCKET_PEOPLE import javax.inject.Inject @@ -48,18 +50,36 @@ class ConversationCoordinator @Inject constructor( val sectioner = object : NotifSectioner("People", BUCKET_PEOPLE) { override fun isInSection(entry: ListEntry): Boolean = - isConversation(entry.representativeEntry!!) + isConversation(entry) override fun getHeaderNodeController() = // TODO: remove SHOW_ALL_SECTIONS, this redundant method, and peopleHeaderController if (RankingCoordinator.SHOW_ALL_SECTIONS) peopleHeaderController else null } + val comparator = object : NotifComparator("People") { + override fun compare(entry1: ListEntry, entry2: ListEntry): Int { + assert(entry1.section === entry2.section) + if (entry1.section?.sectioner !== sectioner) { + return 0 + } + val type1 = getPeopleType(entry1) + val type2 = getPeopleType(entry2) + return type2.compareTo(type1) + } + } + override fun attach(pipeline: NotifPipeline) { pipeline.addPromoter(notificationPromoter) } - private fun isConversation(entry: NotificationEntry): Boolean = - peopleNotificationIdentifier.getPeopleNotificationType(entry) != TYPE_NON_PERSON + private fun isConversation(entry: ListEntry): Boolean = + getPeopleType(entry) != TYPE_NON_PERSON + + @PeopleNotificationType + private fun getPeopleType(entry: ListEntry): Int = + entry.representativeEntry?.let { + peopleNotificationIdentifier.getPeopleNotificationType(it) + } ?: TYPE_NON_PERSON companion object { private const val TAG = "ConversationCoordinator" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.java deleted file mode 100644 index 74109120149e..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.java +++ /dev/null @@ -1,259 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.statusbar.notification.collection.coordinator; - -import static com.android.systemui.statusbar.NotificationRemoteInputManager.FORCE_REMOTE_INPUT_HISTORY; -import static com.android.systemui.statusbar.notification.interruption.HeadsUpController.alertAgain; - -import android.util.ArraySet; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.statusbar.NotificationRemoteInputManager; -import com.android.systemui.statusbar.notification.collection.ListEntry; -import com.android.systemui.statusbar.notification.collection.NotifPipeline; -import com.android.systemui.statusbar.notification.collection.NotificationEntry; -import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope; -import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter; -import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner; -import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; -import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender; -import com.android.systemui.statusbar.notification.collection.render.NodeController; -import com.android.systemui.statusbar.notification.dagger.IncomingHeader; -import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder; -import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider; -import com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt; -import com.android.systemui.statusbar.policy.HeadsUpManager; -import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; -import com.android.systemui.util.concurrency.DelayableExecutor; - -import javax.inject.Inject; - -/** - * Coordinates heads up notification (HUN) interactions with the notification pipeline based on - * the HUN state reported by the {@link HeadsUpManager}. In this class we only consider one - * notification, in particular the {@link HeadsUpManager#getTopEntry()}, to be HeadsUpping at a - * time even though other notifications may be queued to heads up next. - * - * The current HUN, but not HUNs that are queued to heads up, will be: - * - Lifetime extended until it's no longer heads upping. - * - Promoted out of its group if it's a child of a group. - * - In the HeadsUpCoordinatorSection. Ordering is configured in {@link NotifCoordinators}. - * - Removed from HeadsUpManager if it's removed from the NotificationCollection. - * - * Note: The inflation callback in {@link PreparationCoordinator} handles showing HUNs. - */ -@CoordinatorScope -public class HeadsUpCoordinator implements Coordinator { - private static final String TAG = "HeadsUpCoordinator"; - - private final HeadsUpManager mHeadsUpManager; - private final HeadsUpViewBinder mHeadsUpViewBinder; - private final NotificationInterruptStateProvider mNotificationInterruptStateProvider; - private final NotificationRemoteInputManager mRemoteInputManager; - private final NodeController mIncomingHeaderController; - private final DelayableExecutor mExecutor; - - private NotifLifetimeExtender.OnEndLifetimeExtensionCallback mEndLifetimeExtension; - // notifs we've extended the lifetime for - private final ArraySet<NotificationEntry> mNotifsExtendingLifetime = new ArraySet<>(); - - @Inject - public HeadsUpCoordinator( - HeadsUpManager headsUpManager, - HeadsUpViewBinder headsUpViewBinder, - NotificationInterruptStateProvider notificationInterruptStateProvider, - NotificationRemoteInputManager remoteInputManager, - @IncomingHeader NodeController incomingHeaderController, - @Main DelayableExecutor executor) { - mHeadsUpManager = headsUpManager; - mHeadsUpViewBinder = headsUpViewBinder; - mNotificationInterruptStateProvider = notificationInterruptStateProvider; - mRemoteInputManager = remoteInputManager; - mIncomingHeaderController = incomingHeaderController; - mExecutor = executor; - } - - @Override - public void attach(NotifPipeline pipeline) { - mHeadsUpManager.addListener(mOnHeadsUpChangedListener); - pipeline.addCollectionListener(mNotifCollectionListener); - pipeline.addPromoter(mNotifPromoter); - pipeline.addNotificationLifetimeExtender(mLifetimeExtender); - } - - public NotifSectioner getSectioner() { - return mNotifSectioner; - } - - private void onHeadsUpViewBound(NotificationEntry entry) { - mHeadsUpManager.showNotification(entry); - } - - private final NotifCollectionListener mNotifCollectionListener = new NotifCollectionListener() { - /** - * Notification was just added and if it should heads up, bind the view and then show it. - */ - @Override - public void onEntryAdded(NotificationEntry entry) { - if (mNotificationInterruptStateProvider.shouldHeadsUp(entry)) { - mHeadsUpViewBinder.bindHeadsUpView( - entry, - HeadsUpCoordinator.this::onHeadsUpViewBound); - } - } - - /** - * Notification could've updated to be heads up or not heads up. Even if it did update to - * heads up, if the notification specified that it only wants to alert once, don't heads - * up again. - */ - @Override - public void onEntryUpdated(NotificationEntry entry) { - boolean hunAgain = alertAgain(entry, entry.getSbn().getNotification()); - // includes check for whether this notification should be filtered: - boolean shouldHeadsUp = mNotificationInterruptStateProvider.shouldHeadsUp(entry); - final boolean wasHeadsUp = mHeadsUpManager.isAlerting(entry.getKey()); - if (wasHeadsUp) { - if (shouldHeadsUp) { - mHeadsUpManager.updateNotification(entry.getKey(), hunAgain); - } else if (!mHeadsUpManager.isEntryAutoHeadsUpped(entry.getKey())) { - // We don't want this to be interrupting anymore, let's remove it - mHeadsUpManager.removeNotification( - entry.getKey(), false /* removeImmediately */); - } - } else if (shouldHeadsUp && hunAgain) { - // This notification was updated to be heads up, show it! - mHeadsUpViewBinder.bindHeadsUpView( - entry, - HeadsUpCoordinator.this::onHeadsUpViewBound); - } - } - - /** - * Stop alerting HUNs that are removed from the notification collection - */ - @Override - public void onEntryRemoved(NotificationEntry entry, int reason) { - final String entryKey = entry.getKey(); - if (mHeadsUpManager.isAlerting(entryKey)) { - boolean removeImmediatelyForRemoteInput = - mRemoteInputManager.isSpinning(entryKey) - && !FORCE_REMOTE_INPUT_HISTORY; - mHeadsUpManager.removeNotification(entry.getKey(), removeImmediatelyForRemoteInput); - } - } - - @Override - public void onEntryCleanUp(NotificationEntry entry) { - mHeadsUpViewBinder.abortBindCallback(entry); - } - }; - - private final NotifLifetimeExtender mLifetimeExtender = new NotifLifetimeExtender() { - @Override - public @NonNull String getName() { - return TAG; - } - - @Override - public void setCallback(@NonNull OnEndLifetimeExtensionCallback callback) { - mEndLifetimeExtension = callback; - } - - @Override - public boolean maybeExtendLifetime(@NonNull NotificationEntry entry, int reason) { - boolean extend = !mHeadsUpManager.canRemoveImmediately(entry.getKey()); - if (extend) { - if (isSticky(entry)) { - long removeAfterMillis = mHeadsUpManager.getEarliestRemovalTime(entry.getKey()); - mExecutor.executeDelayed(() -> { - if (mNotifsExtendingLifetime.contains(entry) - && mHeadsUpManager.canRemoveImmediately(entry.getKey())) { - mHeadsUpManager.removeNotification( - entry.getKey(), /* releaseImmediately */ true); - } - }, removeAfterMillis); - } else { - // remove as early as possible - mExecutor.execute( - () -> mHeadsUpManager.removeNotification( - entry.getKey(), /* releaseImmediately */ false)); - } - mNotifsExtendingLifetime.add(entry); - } - return extend; - } - - @Override - public void cancelLifetimeExtension(@NonNull NotificationEntry entry) { - mNotifsExtendingLifetime.remove(entry); - } - }; - - private final NotifPromoter mNotifPromoter = new NotifPromoter(TAG) { - @Override - public boolean shouldPromoteToTopLevel(NotificationEntry entry) { - return isCurrentlyShowingHun(entry); - } - }; - - private final NotifSectioner mNotifSectioner = new NotifSectioner("HeadsUp", - NotificationPriorityBucketKt.BUCKET_HEADS_UP) { - @Override - public boolean isInSection(ListEntry entry) { - return isCurrentlyShowingHun(entry); - } - - @Nullable - @Override - public NodeController getHeaderNodeController() { - // TODO: remove SHOW_ALL_SECTIONS, this redundant method, and mIncomingHeaderController - if (RankingCoordinator.SHOW_ALL_SECTIONS) { - return mIncomingHeaderController; - } - return null; - } - }; - - private final OnHeadsUpChangedListener mOnHeadsUpChangedListener = - new OnHeadsUpChangedListener() { - @Override - public void onHeadsUpStateChanged(NotificationEntry entry, boolean isHeadsUp) { - if (!isHeadsUp) { - mHeadsUpViewBinder.unbindHeadsUpView(entry); - endNotifLifetimeExtensionIfExtended(entry); - } - } - }; - - private boolean isSticky(NotificationEntry entry) { - return mHeadsUpManager.isSticky(entry.getKey()); - } - - private boolean isCurrentlyShowingHun(ListEntry entry) { - return mHeadsUpManager.isAlerting(entry.getKey()); - } - - private void endNotifLifetimeExtensionIfExtended(NotificationEntry entry) { - if (mNotifsExtendingLifetime.remove(entry)) { - mEndLifetimeExtension.onEndLifetimeExtension(mLifetimeExtender, entry); - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt new file mode 100644 index 000000000000..b84b38233073 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.statusbar.notification.collection.coordinator + +import android.util.ArraySet +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.statusbar.NotificationRemoteInputManager +import com.android.systemui.statusbar.notification.collection.ListEntry +import com.android.systemui.statusbar.notification.collection.NotifPipeline +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender.OnEndLifetimeExtensionCallback +import com.android.systemui.statusbar.notification.collection.render.NodeController +import com.android.systemui.statusbar.notification.dagger.IncomingHeader +import com.android.systemui.statusbar.notification.interruption.HeadsUpController +import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder +import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider +import com.android.systemui.statusbar.notification.stack.BUCKET_HEADS_UP +import com.android.systemui.statusbar.policy.HeadsUpManager +import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener +import com.android.systemui.util.concurrency.DelayableExecutor +import javax.inject.Inject + +/** + * Coordinates heads up notification (HUN) interactions with the notification pipeline based on + * the HUN state reported by the [HeadsUpManager]. In this class we only consider one + * notification, in particular the [HeadsUpManager.getTopEntry], to be HeadsUpping at a + * time even though other notifications may be queued to heads up next. + * + * The current HUN, but not HUNs that are queued to heads up, will be: + * - Lifetime extended until it's no longer heads upping. + * - Promoted out of its group if it's a child of a group. + * - In the HeadsUpCoordinatorSection. Ordering is configured in [NotifCoordinators]. + * - Removed from HeadsUpManager if it's removed from the NotificationCollection. + * + * Note: The inflation callback in [PreparationCoordinator] handles showing HUNs. + */ +@CoordinatorScope +class HeadsUpCoordinator @Inject constructor( + private val mHeadsUpManager: HeadsUpManager, + private val mHeadsUpViewBinder: HeadsUpViewBinder, + private val mNotificationInterruptStateProvider: NotificationInterruptStateProvider, + private val mRemoteInputManager: NotificationRemoteInputManager, + @IncomingHeader private val mIncomingHeaderController: NodeController, + @Main private val mExecutor: DelayableExecutor +) : Coordinator { + private var mEndLifetimeExtension: OnEndLifetimeExtensionCallback? = null + + // notifs we've extended the lifetime for + private val mNotifsExtendingLifetime = ArraySet<NotificationEntry>() + + override fun attach(pipeline: NotifPipeline) { + mHeadsUpManager.addListener(mOnHeadsUpChangedListener) + pipeline.addCollectionListener(mNotifCollectionListener) + pipeline.addPromoter(mNotifPromoter) + pipeline.addNotificationLifetimeExtender(mLifetimeExtender) + } + + private fun onHeadsUpViewBound(entry: NotificationEntry) { + mHeadsUpManager.showNotification(entry) + } + + private val mNotifCollectionListener = object : NotifCollectionListener { + /** + * Notification was just added and if it should heads up, bind the view and then show it. + */ + override fun onEntryAdded(entry: NotificationEntry) { + if (mNotificationInterruptStateProvider.shouldHeadsUp(entry)) { + mHeadsUpViewBinder.bindHeadsUpView(entry) { entry -> onHeadsUpViewBound(entry) } + } + } + + /** + * Notification could've updated to be heads up or not heads up. Even if it did update to + * heads up, if the notification specified that it only wants to alert once, don't heads + * up again. + */ + override fun onEntryUpdated(entry: NotificationEntry) { + val hunAgain = HeadsUpController.alertAgain(entry, entry.sbn.notification) + // includes check for whether this notification should be filtered: + val shouldHeadsUp = mNotificationInterruptStateProvider.shouldHeadsUp(entry) + val wasHeadsUp = mHeadsUpManager.isAlerting(entry.key) + if (wasHeadsUp) { + if (shouldHeadsUp) { + mHeadsUpManager.updateNotification(entry.key, hunAgain) + } else if (!mHeadsUpManager.isEntryAutoHeadsUpped(entry.key)) { + // We don't want this to be interrupting anymore, let's remove it + mHeadsUpManager.removeNotification( + entry.key, false /* removeImmediately */ + ) + } + } else if (shouldHeadsUp && hunAgain) { + // This notification was updated to be heads up, show it! + mHeadsUpViewBinder.bindHeadsUpView(entry) { entry -> onHeadsUpViewBound(entry) } + } + } + + /** + * Stop alerting HUNs that are removed from the notification collection + */ + override fun onEntryRemoved(entry: NotificationEntry, reason: Int) { + val entryKey = entry.key + if (mHeadsUpManager.isAlerting(entryKey)) { + val removeImmediatelyForRemoteInput = (mRemoteInputManager.isSpinning(entryKey) && + !NotificationRemoteInputManager.FORCE_REMOTE_INPUT_HISTORY) + mHeadsUpManager.removeNotification(entry.key, removeImmediatelyForRemoteInput) + } + } + + override fun onEntryCleanUp(entry: NotificationEntry) { + mHeadsUpViewBinder.abortBindCallback(entry) + } + } + + private val mLifetimeExtender = object : NotifLifetimeExtender { + override fun getName() = TAG + + override fun setCallback(callback: OnEndLifetimeExtensionCallback) { + mEndLifetimeExtension = callback + } + + override fun maybeExtendLifetime(entry: NotificationEntry, reason: Int): Boolean { + if (mHeadsUpManager.canRemoveImmediately(entry.key)) { + return false + } + if (isSticky(entry)) { + val removeAfterMillis = mHeadsUpManager.getEarliestRemovalTime(entry.key) + mExecutor.executeDelayed({ + val canStillRemove = mHeadsUpManager.canRemoveImmediately(entry.key) + if (mNotifsExtendingLifetime.contains(entry) && canStillRemove) { + mHeadsUpManager.removeNotification(entry.key, /* releaseImmediately */ true) + } + }, removeAfterMillis) + } else { + mExecutor.execute { + mHeadsUpManager.removeNotification(entry.key, /* releaseImmediately */ false) + } + } + mNotifsExtendingLifetime.add(entry) + return true + } + + override fun cancelLifetimeExtension(entry: NotificationEntry) { + mNotifsExtendingLifetime.remove(entry) + } + } + + private val mNotifPromoter = object : NotifPromoter(TAG) { + override fun shouldPromoteToTopLevel(entry: NotificationEntry): Boolean = + isCurrentlyShowingHun(entry) + } + + val sectioner = object : NotifSectioner("HeadsUp", BUCKET_HEADS_UP) { + override fun isInSection(entry: ListEntry): Boolean = isCurrentlyShowingHun(entry) + + override fun getHeaderNodeController(): NodeController? = + // TODO: remove SHOW_ALL_SECTIONS, this redundant method, and mIncomingHeaderController + if (RankingCoordinator.SHOW_ALL_SECTIONS) mIncomingHeaderController else null + } + + private val mOnHeadsUpChangedListener = object : OnHeadsUpChangedListener { + override fun onHeadsUpStateChanged(entry: NotificationEntry, isHeadsUp: Boolean) { + if (!isHeadsUp) { + mHeadsUpViewBinder.unbindHeadsUpView(entry) + endNotifLifetimeExtensionIfExtended(entry) + } + } + } + + private fun isSticky(entry: NotificationEntry) = mHeadsUpManager.isSticky(entry.key) + + private fun isCurrentlyShowingHun(entry: ListEntry) = mHeadsUpManager.isAlerting(entry.key) + + private fun endNotifLifetimeExtensionIfExtended(entry: NotificationEntry) { + if (mNotifsExtendingLifetime.remove(entry)) { + mEndLifetimeExtension?.onEndLifetimeExtension(mLifetimeExtender, entry) + } + } + + companion object { + private const val TAG = "HeadsUpCoordinator" + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java index 33005b34ff98..733be9c1ca2c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java @@ -36,6 +36,8 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.NotificationLockscreenUserManager; +import com.android.systemui.statusbar.StatusBarState; +import com.android.systemui.statusbar.notification.SectionHeaderVisibilityProvider; import com.android.systemui.statusbar.notification.collection.GroupEntry; import com.android.systemui.statusbar.notification.collection.ListEntry; import com.android.systemui.statusbar.notification.collection.NotifPipeline; @@ -48,7 +50,8 @@ import com.android.systemui.statusbar.policy.KeyguardStateController; import javax.inject.Inject; /** - * Filters low priority and privacy-sensitive notifications from the lockscreen. + * Filters low priority and privacy-sensitive notifications from the lockscreen, and hides section + * headers on the lockscreen. */ @CoordinatorScope public class KeyguardCoordinator implements Coordinator { @@ -62,6 +65,7 @@ public class KeyguardCoordinator implements Coordinator { private final StatusBarStateController mStatusBarStateController; private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; private final HighPriorityProvider mHighPriorityProvider; + private final SectionHeaderVisibilityProvider mSectionHeaderVisibilityProvider; private boolean mHideSilentNotificationsOnLockscreen; @@ -74,7 +78,8 @@ public class KeyguardCoordinator implements Coordinator { BroadcastDispatcher broadcastDispatcher, StatusBarStateController statusBarStateController, KeyguardUpdateMonitor keyguardUpdateMonitor, - HighPriorityProvider highPriorityProvider) { + HighPriorityProvider highPriorityProvider, + SectionHeaderVisibilityProvider sectionHeaderVisibilityProvider) { mContext = context; mMainHandler = mainThreadHandler; mKeyguardStateController = keyguardStateController; @@ -83,6 +88,7 @@ public class KeyguardCoordinator implements Coordinator { mStatusBarStateController = statusBarStateController; mKeyguardUpdateMonitor = keyguardUpdateMonitor; mHighPriorityProvider = highPriorityProvider; + mSectionHeaderVisibilityProvider = sectionHeaderVisibilityProvider; } @Override @@ -214,6 +220,8 @@ public class KeyguardCoordinator implements Coordinator { } private void invalidateListFromFilter(String reason) { + mSectionHeaderVisibilityProvider.setSectionHeadersVisible( + mStatusBarStateController.getState() != StatusBarState.KEYGUARD); mNotifFilter.invalidateList(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt index 757fb5a2fe9a..850cb4b88154 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt @@ -20,6 +20,7 @@ import com.android.systemui.dump.DumpManager import com.android.systemui.statusbar.notification.NotifPipelineFlags import com.android.systemui.statusbar.notification.collection.NotifPipeline import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner import java.io.FileDescriptor import java.io.PrintWriter @@ -63,6 +64,7 @@ class NotifCoordinatorsImpl @Inject constructor( private val mCoordinators: MutableList<Coordinator> = ArrayList() private val mOrderedSections: MutableList<NotifSectioner> = ArrayList() + private val mOrderedComparators: MutableList<NotifComparator> = ArrayList() /** * Creates all the coordinators. @@ -117,6 +119,9 @@ class NotifCoordinatorsImpl @Inject constructor( mOrderedSections.add(rankingCoordinator.alertingSectioner) // Alerting mOrderedSections.add(rankingCoordinator.silentSectioner) // Silent mOrderedSections.add(rankingCoordinator.minimizedSectioner) // Minimized + + // Manually add ordered comparators + mOrderedComparators.add(conversationCoordinator.comparator) } /** @@ -128,6 +133,7 @@ class NotifCoordinatorsImpl @Inject constructor( c.attach(pipeline) } pipeline.setSections(mOrderedSections) + pipeline.setComparators(mOrderedComparators) } override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<String>) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java index 195f3672dc56..35fe0ee7cdb1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java @@ -31,13 +31,13 @@ import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.statusbar.IStatusBarService; -import com.android.systemui.statusbar.notification.ConversationNotificationManager; import com.android.systemui.statusbar.notification.collection.GroupEntry; import com.android.systemui.statusbar.notification.collection.ListEntry; import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.ShadeListBuilder; import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope; +import com.android.systemui.statusbar.notification.collection.inflation.BindEventManagerImpl; import com.android.systemui.statusbar.notification.collection.inflation.NotifInflater; import com.android.systemui.statusbar.notification.collection.inflation.NotifUiAdjustment; import com.android.systemui.statusbar.notification.collection.inflation.NotifUiAdjustmentProvider; @@ -99,7 +99,7 @@ public class PreparationCoordinator implements Coordinator { /** How long we can delay a group while waiting for all children to inflate */ private final long mMaxGroupInflationDelay; - private final ConversationNotificationManager mConversationManager; + private final BindEventManagerImpl mBindEventManager; @Inject public PreparationCoordinator( @@ -109,7 +109,7 @@ public class PreparationCoordinator implements Coordinator { NotifViewBarn viewBarn, NotifUiAdjustmentProvider adjustmentProvider, IStatusBarService service, - ConversationNotificationManager conversationManager) { + BindEventManagerImpl bindEventManager) { this( logger, notifInflater, @@ -117,7 +117,7 @@ public class PreparationCoordinator implements Coordinator { viewBarn, adjustmentProvider, service, - conversationManager, + bindEventManager, CHILD_BIND_CUTOFF, MAX_GROUP_INFLATION_DELAY); } @@ -130,7 +130,7 @@ public class PreparationCoordinator implements Coordinator { NotifViewBarn viewBarn, NotifUiAdjustmentProvider adjustmentProvider, IStatusBarService service, - ConversationNotificationManager conversationManager, + BindEventManagerImpl bindEventManager, int childBindCutoff, long maxGroupInflationDelay) { mLogger = logger; @@ -141,7 +141,7 @@ public class PreparationCoordinator implements Coordinator { mStatusBarService = service; mChildBindCutoff = childBindCutoff; mMaxGroupInflationDelay = maxGroupInflationDelay; - mConversationManager = conversationManager; + mBindEventManager = bindEventManager; } @Override @@ -369,9 +369,7 @@ public class PreparationCoordinator implements Coordinator { mInflatingNotifs.remove(entry); mViewBarn.registerViewForEntry(entry, controller); mInflationStates.put(entry, STATE_INFLATED); - // NOTE: under the new pipeline there's no way to register for an inflation callback, - // so this one method is called by the PreparationCoordinator directly. - mConversationManager.onEntryViewBound(entry); + mBindEventManager.notifyViewBound(entry); mNotifInflatingFilter.invalidateList(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/BindEventManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/BindEventManager.kt new file mode 100644 index 000000000000..51bdd00d09be --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/BindEventManager.kt @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.collection.inflation + +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.util.ListenerSet + +/** + * Helper class that allows distributing bind events regardless of the pipeline. + * + * NOTE: This class isn't ideal; this exposes the concept of view inflation as something that can be + * globally registered for. This is built as it is to provide compatibility with patterns developed + * for the legacy pipeline. Ideally we'd have functionality that needs to know this information be + * handled by events that go through the ViewController itself. + */ +open class BindEventManager { + protected val listeners = ListenerSet<Listener>() + + /** Register a listener */ + fun addListener(listener: Listener) = + listeners.addIfAbsent(listener) + + /** Deregister a listener */ + fun removeListener(listener: Listener) = + listeners.remove(listener) + + /** Listener interface for view bind events */ + fun interface Listener { + fun onViewBound(entry: NotificationEntry) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/BindEventManagerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/BindEventManagerImpl.kt new file mode 100644 index 000000000000..9d5b859ef29c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/BindEventManagerImpl.kt @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.collection.inflation + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.statusbar.notification.NotificationEntryListener +import com.android.systemui.statusbar.notification.NotificationEntryManager +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.collection.inflation.BindEventManager.Listener +import javax.inject.Inject + +/** + * Helper class that allows distributing bind events regardless of the pipeline. + */ +@SysUISingleton +class BindEventManagerImpl @Inject constructor() : BindEventManager() { + /** Emit the [Listener.onViewBound] event to all registered listeners. */ + fun notifyViewBound(entry: NotificationEntry) = + listeners.forEach { listener -> listener.onViewBound(entry) } + + /** Initialize this for the legacy pipeline. */ + fun attachToLegacyPipeline(notificationEntryManager: NotificationEntryManager) { + notificationEntryManager.addNotificationEntryListener(object : NotificationEntryListener { + override fun onEntryInflated(entry: NotificationEntry) = notifyViewBound(entry) + override fun onEntryReinflated(entry: NotificationEntry) = notifyViewBound(entry) + }) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifComparator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifComparator.java index 0d150edee128..f7bbd281ec51 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifComparator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifComparator.java @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.notification.collection.listbuilder.pluggable; +import androidx.annotation.NonNull; + import com.android.systemui.statusbar.notification.collection.ListEntry; import com.android.systemui.statusbar.notification.collection.NotifPipeline; @@ -39,5 +41,5 @@ public abstract class NotifComparator * @return a negative integer, zero, or a positive integer as the first argument is less than * equal to, or greater than the second (same as standard Comparator<> interface). */ - public abstract int compare(ListEntry o1, ListEntry o2); + public abstract int compare(@NonNull ListEntry o1, @NonNull ListEntry o2); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java index 68a346f817e1..9d56a8ede1cc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java @@ -17,6 +17,8 @@ package com.android.systemui.statusbar.notification.collection.notifcollection; import android.annotation.NonNull; +import android.app.NotificationChannel; +import android.os.UserHandle; import android.service.notification.NotificationListenerService; import android.service.notification.StatusBarNotification; @@ -115,4 +117,20 @@ public interface NotifCollectionListener { */ default void onRankingUpdate(NotificationListenerService.RankingMap rankingMap) { } + + /** + * Called when a notification channel is modified, in response to + * {@link NotificationListenerService#onNotificationChannelModified}. + * + * @param pkgName the package the notification channel belongs to. + * @param user the user the notification channel belongs to. + * @param channel the channel being modified. + * @param modificationType the type of modification that occurred to the channel. + */ + default void onNotificationChannelModified( + String pkgName, + UserHandle user, + NotificationChannel channel, + int modificationType) { + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifEvent.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifEvent.kt index 179e95328442..e20f0e50af6d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifEvent.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifEvent.kt @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.notification.collection.notifcollection +import android.app.NotificationChannel +import android.os.UserHandle import android.service.notification.NotificationListenerService.RankingMap import android.service.notification.StatusBarNotification import com.android.systemui.statusbar.notification.collection.NotifCollection @@ -102,3 +104,14 @@ class RankingAppliedEvent() : NotifEvent() { listener.onRankingApplied() } } + +data class ChannelChangedEvent( + val pkgName: String, + val user: UserHandle, + val channel: NotificationChannel, + val modificationType: Int +) : NotifEvent() { + override fun dispatchToListener(listener: NotifCollectionListener) { + listener.onNotificationChannelModified(pkgName, user, channel, modificationType) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/DebugModeFilterProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/DebugModeFilterProvider.kt index d16d76ad2f9a..ab777de21e34 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/DebugModeFilterProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/DebugModeFilterProvider.kt @@ -77,7 +77,7 @@ class DebugModeFilterProvider @Inject constructor( if (needsInitialization) { val filter = IntentFilter().apply { addAction(ACTION_SET_NOTIF_DEBUG_MODE) } val permission = NOTIF_DEBUG_MODE_PERMISSION - context.registerReceiver(mReceiver, filter, permission, null) + context.registerReceiver(mReceiver, filter, permission, null, Context.RECEIVER_EXPORTED) Log.d(TAG, "Registered: $mReceiver") } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeController.kt index 289dacbca69e..26ba12c97575 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeController.kt @@ -41,17 +41,29 @@ interface NodeController { fun getChildCount(): Int = 0 + /** Called to add a child to this view */ fun addChildAt(child: NodeController, index: Int) { throw RuntimeException("Not supported") } + /** Called to move one of this view's current children to a new position */ fun moveChildTo(child: NodeController, index: Int) { throw RuntimeException("Not supported") } + /** Called to remove one of this view's current children */ fun removeChild(child: NodeController, isTransfer: Boolean) { throw RuntimeException("Not supported") } + + /** Called when this view has been added */ + fun onViewAdded() {} + + /** Called when this view has been moved */ + fun onViewMoved() {} + + /** Called when this view has been removed */ + fun onViewRemoved() {} } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilder.kt index f13470ec2c94..607500edfd3a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilder.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.notification.collection.render +import com.android.systemui.statusbar.notification.SectionHeaderVisibilityProvider import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager import com.android.systemui.statusbar.notification.collection.GroupEntry import com.android.systemui.statusbar.notification.collection.ListEntry @@ -35,6 +36,7 @@ import com.android.systemui.util.traceSection class NodeSpecBuilder( private val mediaContainerController: MediaContainerController, private val sectionsFeatureManager: NotificationSectionsFeatureManager, + private val sectionHeaderVisibilityProvider: SectionHeaderVisibilityProvider, private val viewBarn: NotifViewBarn ) { fun buildNodeSpec( @@ -51,6 +53,7 @@ class NodeSpecBuilder( var currentSection: NotifSection? = null val prevSections = mutableSetOf<NotifSection?>() + val showHeaders = sectionHeaderVisibilityProvider.sectionHeadersVisible for (entry in notifList) { val section = entry.section!! @@ -61,7 +64,7 @@ class NodeSpecBuilder( // If this notif begins a new section, first add the section's header view if (section != currentSection) { - if (section.headerController != currentSection?.headerController) { + if (section.headerController != currentSection?.headerController && showHeaders) { section.headerController?.let { headerController -> root.children.add(NodeSpecImpl(root, headerController)) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RootNodeController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RootNodeController.kt index a1800ed12125..4de8e7a5641c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RootNodeController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RootNodeController.kt @@ -40,6 +40,7 @@ class RootNodeController( override fun addChildAt(child: NodeController, index: Int) { listContainer.addContainerViewAt(child.view, index) + listContainer.onNotificationViewUpdateFinished() } override fun moveChildTo(child: NodeController, index: Int) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt index 4e9017e05ecd..2c9508e84aef 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt @@ -94,6 +94,10 @@ internal class SectionHeaderNodeControllerImpl @Inject constructor( _view?.setOnClearAllClickListener(listener) } + override fun onViewAdded() { + headerView?.isContentVisible = true + } + override val view: View get() = _view!! }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt index 6d4ae4b1a869..28cd28594c3e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt @@ -215,13 +215,16 @@ private class ShadeNode( fun addChildAt(child: ShadeNode, index: Int) { controller.addChildAt(child.controller, index) + child.controller.onViewAdded() } fun moveChildTo(child: ShadeNode, index: Int) { controller.moveChildTo(child.controller, index) + child.controller.onViewMoved() } fun removeChild(child: ShadeNode, isTransfer: Boolean) { controller.removeChild(child.controller, isTransfer) + child.controller.onViewRemoved() } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt index ad973927f21e..484707241b4d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt @@ -19,6 +19,7 @@ package com.android.systemui.statusbar.notification.collection.render import android.content.Context import android.view.View import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager +import com.android.systemui.statusbar.notification.SectionHeaderVisibilityProvider import com.android.systemui.statusbar.notification.collection.GroupEntry import com.android.systemui.statusbar.notification.collection.ListEntry import com.android.systemui.statusbar.notification.collection.NotificationEntry @@ -38,13 +39,15 @@ class ShadeViewManager @AssistedInject constructor( @Assisted private val stackController: NotifStackController, mediaContainerController: MediaContainerController, featureManager: NotificationSectionsFeatureManager, + sectionHeaderVisibilityProvider: SectionHeaderVisibilityProvider, logger: ShadeViewDifferLogger, private val viewBarn: NotifViewBarn ) { // We pass a shim view here because the listContainer may not actually have a view associated // with it and the differ never actually cares about the root node's view. private val rootController = RootNodeController(listContainer, View(context)) - private val specBuilder = NodeSpecBuilder(mediaContainerController, featureManager, viewBarn) + private val specBuilder = NodeSpecBuilder(mediaContainerController, featureManager, + sectionHeaderVisibilityProvider, viewBarn) private val viewDiffer = ShadeViewDiffer(rootController, logger) /** Method for attaching this manager to the pipeline. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java index f1cba34158d1..05c40b204c86 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java @@ -50,6 +50,8 @@ import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.coordinator.ShadeEventCoordinator; import com.android.systemui.statusbar.notification.collection.coordinator.VisualStabilityCoordinator; import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorsModule; +import com.android.systemui.statusbar.notification.collection.inflation.BindEventManager; +import com.android.systemui.statusbar.notification.collection.inflation.BindEventManagerImpl; import com.android.systemui.statusbar.notification.collection.inflation.NotifInflater; import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder; import com.android.systemui.statusbar.notification.collection.inflation.OnUserInteractionCallbackImpl; @@ -358,5 +360,9 @@ public interface NotificationsModule { /** */ @Binds + BindEventManager bindBindEventManagerImpl(BindEventManagerImpl bindEventManagerImpl); + + /** */ + @Binds NotifLiveDataStore bindNotifLiveDataStore(NotifLiveDataStoreImpl notifLiveDataStoreImpl); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt index 38f3c39b5b1a..48f2dafedcbb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt @@ -32,6 +32,7 @@ import com.android.systemui.statusbar.notification.collection.NotifLiveDataStore import com.android.systemui.statusbar.notification.collection.NotifPipeline import com.android.systemui.statusbar.notification.collection.NotificationRankingManager import com.android.systemui.statusbar.notification.collection.TargetSdkResolver +import com.android.systemui.statusbar.notification.collection.inflation.BindEventManagerImpl import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl import com.android.systemui.statusbar.notification.collection.init.NotifPipelineInitializer import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy @@ -76,6 +77,7 @@ class NotificationsControllerImpl @Inject constructor( private val notifBindPipelineInitializer: NotifBindPipelineInitializer, private val deviceProvisionedController: DeviceProvisionedController, private val notificationRowBinder: NotificationRowBinderImpl, + private val bindEventManagerImpl: BindEventManagerImpl, private val remoteInputUriController: RemoteInputUriController, private val groupManagerLegacy: Lazy<NotificationGroupManagerLegacy>, private val groupAlertTransferHelper: NotificationGroupAlertTransferHelper, @@ -131,6 +133,7 @@ class NotificationsControllerImpl @Inject constructor( targetSdkResolver.initialize(entryManager) remoteInputUriController.attach(entryManager) groupAlertTransferHelper.bind(entryManager, groupManagerLegacy.get()) + bindEventManagerImpl.attachToLegacyPipeline(entryManager) headsUpManager.addListener(groupManagerLegacy.get()) headsUpManager.addListener(groupAlertTransferHelper) headsUpController.attach(entryManager, headsUpManager) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java index b28fb58967bd..46efef66de43 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java @@ -268,6 +268,18 @@ public class ExpandableNotificationRowController implements NotifViewController } @Override + public void onViewAdded() { + } + + @Override + public void onViewMoved() { + } + + @Override + public void onViewRemoved() { + } + + @Override public int getChildCount() { final List<ExpandableNotificationRow> mChildren = mView.getAttachedChildren(); return mChildren != null ? mChildren.size() : 0; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java index 624e7416d3ee..6eff7993c59c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java @@ -552,14 +552,8 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable { final ViewGroup transientContainer = getTransientContainer(); if (parent == null || parent == newParent) { // If this view's current parent is null or the same as the new parent, the add will - // succeed, so just make sure the tracked transient container is in sync with the - // current parent. - if (transientContainer != null && transientContainer != parent) { - Log.w(TAG, "Expandable view " + this - + " has transient container " + transientContainer - + " but different parent" + parent); - setTransientContainer(null); - } + // succeed as long as it's a true child, so just make sure the view isn't transient. + removeFromTransientContainer(); return; } if (transientContainer == null) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java index 9c755e970a0f..3cdaa9ad5f87 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java @@ -105,6 +105,9 @@ public abstract class StackScrollerDecorView extends ExpandableView { runAfter.run(); }; setViewVisible(mContent, visible, animate, endRunnable); + } else if (runAfter != null) { + // Execute the runAfter runnable immediately if there's no animation to perform. + runAfter.run(); } if (!mContentAnimating) { @@ -228,7 +231,7 @@ public abstract class StackScrollerDecorView extends ExpandableView { Runnable onFinishedRunnable, AnimatorListenerAdapter animationListener) { // TODO: Use duration - setContentVisible(false); + setContentVisible(false, true /* animate */, onFinishedRunnable); return 0; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java index dee1b334182a..8d500fa4e8b0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java @@ -44,6 +44,7 @@ import com.android.systemui.biometrics.AuthController; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dump.DumpManager; +import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.keyguard.ScreenLifecycle; import com.android.systemui.keyguard.WakefulnessLifecycle; @@ -254,6 +255,8 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp ); } + private KeyguardUnlockAnimationController mKeyguardUnlockAnimationController; + @Inject public BiometricUnlockController(Context context, DozeScrimController dozeScrimController, KeyguardViewMediator keyguardViewMediator, ScrimController scrimController, @@ -269,7 +272,8 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp WakefulnessLifecycle wakefulnessLifecycle, ScreenLifecycle screenLifecycle, AuthController authController, - StatusBarStateController statusBarStateController) { + StatusBarStateController statusBarStateController, + KeyguardUnlockAnimationController keyguardUnlockAnimationController) { mContext = context; mPowerManager = powerManager; mShadeController = shadeController; @@ -292,6 +296,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp mMetricsLogger = metricsLogger; mAuthController = authController; mStatusBarStateController = statusBarStateController; + mKeyguardUnlockAnimationController = keyguardUnlockAnimationController; dumpManager.registerDumpable(getClass().getName(), this); } @@ -438,11 +443,15 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp if (!wasDeviceInteractive) { mPendingShowBouncer = true; } else { - mShadeController.animateCollapsePanels( - CommandQueue.FLAG_EXCLUDE_NONE, - true /* force */, - false /* delayed */, - BIOMETRIC_COLLAPSE_SPEEDUP_FACTOR); + // If the keyguard unlock controller is going to handle the unlock animation, it + // will fling the panel collapsed when it's ready. + if (!mKeyguardUnlockAnimationController.willHandleUnlockAnimation()) { + mShadeController.animateCollapsePanels( + CommandQueue.FLAG_EXCLUDE_NONE, + true /* force */, + false /* delayed */, + BIOMETRIC_COLLAPSE_SPEEDUP_FACTOR); + } mPendingShowBouncer = false; mKeyguardViewController.notifyKeyguardAuthenticated( false /* strongAuth */); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java index 1b42b58a55aa..d610b372702d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java @@ -305,6 +305,14 @@ public class DozeParameters implements } /** + * When this method returns true then moving display state to power save mode will be + * delayed for a few seconds. This might be useful to play animations without reducing FPS. + */ + public boolean shouldDelayDisplayDozeTransition() { + return mScreenOffAnimationController.shouldDelayDisplayDozeTransition(); + } + + /** * Whether we're capable of controlling the screen off animation if we want to. This isn't * possible if AOD isn't even enabled or if the flag is disabled. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java index 5d6e807729fa..aff73e456b2e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java @@ -349,6 +349,9 @@ public class NotificationIconAreaController implements } private void updateShelfIcons() { + if (mShelfIcons == null) { + return; + } updateIconsForLayout(entry -> entry.getIcons().getShelfIcon(), mShelfIcons, true /* showAmbient */, true /* showLowPriority */, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java index 0bc633c74fc7..769f68976958 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -46,6 +46,7 @@ import static java.lang.Float.isNaN; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; +import android.annotation.NonNull; import android.app.ActivityManager; import android.app.Fragment; import android.app.StatusBarManager; @@ -137,6 +138,7 @@ import com.android.systemui.fragments.FragmentService; import com.android.systemui.idle.IdleHostView; import com.android.systemui.idle.IdleHostViewController; import com.android.systemui.idle.dagger.IdleViewComponent; +import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import com.android.systemui.media.KeyguardMediaController; import com.android.systemui.media.MediaDataManager; import com.android.systemui.media.MediaHierarchyManager; @@ -211,8 +213,10 @@ import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Optional; +import java.util.Set; import java.util.concurrent.Executor; import java.util.function.Consumer; @@ -788,7 +792,8 @@ public class NotificationPanelViewController extends PanelViewController { Optional<SysUIUnfoldComponent> unfoldComponent, ControlsComponent controlsComponent, InteractionJankMonitor interactionJankMonitor, - QsFrameTranslateController qsFrameTranslateController) { + QsFrameTranslateController qsFrameTranslateController, + KeyguardUnlockAnimationController keyguardUnlockAnimationController) { super(view, featureFlags, falsingManager, @@ -803,7 +808,8 @@ public class NotificationPanelViewController extends PanelViewController { lockscreenGestureLogger, panelExpansionStateManager, ambientState, - interactionJankMonitor); + interactionJankMonitor, + keyguardUnlockAnimationController); mView = view; mVibratorHelper = vibratorHelper; mKeyguardMediaController = keyguardMediaController; @@ -922,8 +928,33 @@ public class NotificationPanelViewController extends PanelViewController { mQsFrameTranslateController = qsFrameTranslateController; updateUserSwitcherFlags(); onFinishInflate(); - mUseCombinedQSHeaders = featureFlags.isEnabled(Flags.COMBINED_QS_HEADERS); + keyguardUnlockAnimationController.addKeyguardUnlockAnimationListener( + new KeyguardUnlockAnimationController.KeyguardUnlockAnimationListener() { + @Override + public void onUnlockAnimationFinished() { + // Make sure the clock is in the correct position after the unlock animation + // so that it's not in the wrong place when we show the keyguard again. + positionClockAndNotifications(true /* forceClockUpdate */); + } + + @Override + public void onUnlockAnimationStarted( + boolean playingCannedAnimation, boolean isWakeAndUnlock) { + // Disable blurs while we're unlocking so that panel expansion does not + // cause blurring. This will eventually be re-enabled by the panel view on + // ACTION_UP, since the user's finger might still be down after a swipe to + // unlock gesture, and we don't want that to cause blurring either. + mDepthController.setBlursDisabledForUnlock(mTracking); + + if (playingCannedAnimation && !isWakeAndUnlock) { + // Fling the panel away so it's not in the way or the surface behind the + // keyguard, which will be appearing. If we're wake and unlocking, the + // lock screen is hidden instantly so should not be flung away. + fling(0f, false, 0.7f, false); + } + } + }); } private void onFinishInflate() { @@ -3321,6 +3352,10 @@ public class NotificationPanelViewController extends PanelViewController { mAffordanceHelper.reset(true); } } + + // If we unlocked from a swipe, the user's finger might still be down after the + // unlock animation ends. We need to wait until ACTION_UP to enable blurs again. + mDepthController.setBlursDisabledForUnlock(false); } private void updateMaxHeadsUpTranslation() { @@ -4596,7 +4631,14 @@ public class NotificationPanelViewController extends PanelViewController { // Can affect multi-user switcher visibility as it depends on screen size by default: // it is enabled only for devices with large screens (see config_keyguardUserSwitcher) - reInflateViews(); + boolean prevKeyguardUserSwitcherEnabled = mKeyguardUserSwitcherEnabled; + boolean prevKeyguardQsUserSwitchEnabled = mKeyguardQsUserSwitchEnabled; + updateUserSwitcherFlags(); + if (prevKeyguardUserSwitcherEnabled != mKeyguardUserSwitcherEnabled + || prevKeyguardQsUserSwitchEnabled != mKeyguardQsUserSwitchEnabled) { + reInflateViews(); + } + Trace.endSection(); } @@ -4913,33 +4955,53 @@ public class NotificationPanelViewController extends PanelViewController { private class DebugDrawable extends Drawable { + private final Set<Integer> mDebugTextUsedYPositions = new HashSet<>(); + private final Paint mDebugPaint = new Paint(); + @Override - public void draw(Canvas canvas) { - Paint p = new Paint(); - p.setColor(Color.RED); - p.setStrokeWidth(2); - p.setStyle(Paint.Style.STROKE); - canvas.drawLine(0, getMaxPanelHeight(), mView.getWidth(), getMaxPanelHeight(), p); - p.setTextSize(24); - if (mHeaderDebugInfo != null) canvas.drawText(mHeaderDebugInfo, 50, 100, p); - p.setColor(Color.BLUE); - canvas.drawLine(0, getExpandedHeight(), mView.getWidth(), getExpandedHeight(), p); - p.setColor(Color.GREEN); - canvas.drawLine(0, calculatePanelHeightQsExpanded(), mView.getWidth(), - calculatePanelHeightQsExpanded(), p); - p.setColor(Color.YELLOW); - canvas.drawLine(0, calculatePanelHeightShade(), mView.getWidth(), - calculatePanelHeightShade(), p); - p.setColor(Color.MAGENTA); - canvas.drawLine( - 0, calculateNotificationsTopPadding(), mView.getWidth(), - calculateNotificationsTopPadding(), p); - p.setColor(Color.CYAN); + public void draw(@NonNull Canvas canvas) { + mDebugTextUsedYPositions.clear(); + + mDebugPaint.setColor(Color.RED); + mDebugPaint.setStrokeWidth(2); + mDebugPaint.setStyle(Paint.Style.STROKE); + mDebugPaint.setTextSize(24); + if (mHeaderDebugInfo != null) canvas.drawText(mHeaderDebugInfo, 50, 100, mDebugPaint); + + drawDebugInfo(canvas, getMaxPanelHeight(), Color.RED, "getMaxPanelHeight()"); + drawDebugInfo(canvas, (int) getExpandedHeight(), Color.BLUE, "getExpandedHeight()"); + drawDebugInfo(canvas, calculatePanelHeightQsExpanded(), Color.GREEN, + "calculatePanelHeightQsExpanded()"); + drawDebugInfo(canvas, calculatePanelHeightShade(), Color.YELLOW, + "calculatePanelHeightShade()"); + drawDebugInfo(canvas, (int) calculateNotificationsTopPadding(), Color.MAGENTA, + "calculateNotificationsTopPadding()"); + drawDebugInfo(canvas, mClockPositionResult.clockY, Color.GRAY, + "mClockPositionResult.clockY"); + + mDebugPaint.setColor(Color.CYAN); canvas.drawLine(0, mClockPositionResult.stackScrollerPadding, mView.getWidth(), - mNotificationStackScrollLayoutController.getTopPadding(), p); - p.setColor(Color.GRAY); - canvas.drawLine(0, mClockPositionResult.clockY, mView.getWidth(), - mClockPositionResult.clockY, p); + mNotificationStackScrollLayoutController.getTopPadding(), mDebugPaint); + } + + private void drawDebugInfo(Canvas canvas, int y, int color, String label) { + mDebugPaint.setColor(color); + canvas.drawLine(/* startX= */ 0, /* startY= */ y, /* stopX= */ mView.getWidth(), + /* stopY= */ y, mDebugPaint); + canvas.drawText(label, /* x= */ 0, /* y= */ computeDebugYTextPosition(y), mDebugPaint); + } + + private int computeDebugYTextPosition(int lineY) { + if (lineY - mDebugPaint.getTextSize() < 0) { + // Avoiding drawing out of bounds + lineY += mDebugPaint.getTextSize(); + } + int textY = lineY; + while (mDebugTextUsedYPositions.contains(textY)) { + textY = (int) (textY + mDebugPaint.getTextSize()); + } + mDebugTextUsedYPositions.add(textY); + return textY; } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java index 53bfd7701335..05ac2a35c777 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java @@ -55,6 +55,7 @@ import com.android.systemui.classifier.Classifier; import com.android.systemui.doze.DozeLog; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; +import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.SysuiStatusBarStateController; @@ -212,6 +213,8 @@ public abstract class PanelViewController { return mAmbientState; } + private KeyguardUnlockAnimationController mKeyguardUnlockAnimationController; + public PanelViewController( PanelView view, FeatureFlags featureFlags, @@ -227,7 +230,15 @@ public abstract class PanelViewController { LockscreenGestureLogger lockscreenGestureLogger, PanelExpansionStateManager panelExpansionStateManager, AmbientState ambientState, - InteractionJankMonitor interactionJankMonitor) { + InteractionJankMonitor interactionJankMonitor, + KeyguardUnlockAnimationController keyguardUnlockAnimationController) { + mKeyguardUnlockAnimationController = keyguardUnlockAnimationController; + keyguardStateController.addCallback(new KeyguardStateController.Callback() { + @Override + public void onKeyguardFadingAwayChanged() { + requestPanelHeightUpdate(); + } + }); mAmbientState = ambientState; mView = view; mStatusBarKeyguardViewManager = statusBarKeyguardViewManager; @@ -437,7 +448,8 @@ public abstract class PanelViewController { mUpdateFlingVelocity = vel; } } else if (!mStatusBar.isBouncerShowing() - && !mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating()) { + && !mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating() + && !mKeyguardStateController.isKeyguardGoingAway()) { boolean expands = onEmptySpaceClick(mInitialTouchX); onTrackingStopped(expands); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenOffAnimationController.kt index e806ca0d9005..091831f36022 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenOffAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenOffAnimationController.kt @@ -180,6 +180,14 @@ class ScreenOffAnimationController @Inject constructor( animations.all { it.shouldAnimateDozingChange() } /** + * Returns true when moving display state to power save mode should be + * delayed for a few seconds. This might be useful to play animations in full quality, + * without reducing FPS. + */ + fun shouldDelayDisplayDozeTransition(): Boolean = + animations.any { it.shouldDelayDisplayDozeTransition() } + + /** * Return true to animate large <-> small clock transition */ fun shouldAnimateClockChange(): Boolean = @@ -207,6 +215,7 @@ interface ScreenOffAnimation { fun shouldHideScrimOnWakeUp(): Boolean = false fun overrideNotificationsDozeAmount(): Boolean = false fun shouldShowAodIconsWhenShade(): Boolean = false + fun shouldDelayDisplayDozeTransition(): Boolean = false fun shouldAnimateAodIcons(): Boolean = true fun shouldAnimateDozingChange(): Boolean = true fun shouldAnimateClockChange(): Boolean = true 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 a23e726e0b6b..48048b49e5b8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java @@ -182,6 +182,9 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump private GradientColors mColors; private boolean mNeedsDrawableColorUpdate; + private float mAdditionalScrimBehindAlphaKeyguard = 0f; + // Combined scrim behind keyguard alpha of default scrim + additional scrim + // (if wallpaper dimming is applied). private float mScrimBehindAlphaKeyguard = KEYGUARD_SCRIM_ALPHA; private final float mDefaultScrimAlpha; @@ -437,7 +440,35 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump return mState; } - protected void setScrimBehindValues(float scrimBehindAlphaKeyguard) { + /** + * Sets the additional scrim behind alpha keyguard that would be blended with the default scrim + * by applying alpha composition on both values. + * + * @param additionalScrimAlpha alpha value of additional scrim behind alpha keyguard. + */ + protected void setAdditionalScrimBehindAlphaKeyguard(float additionalScrimAlpha) { + mAdditionalScrimBehindAlphaKeyguard = additionalScrimAlpha; + } + + /** + * Applies alpha composition to the default scrim behind alpha keyguard and the additional + * scrim alpha, and sets this value to the scrim behind alpha keyguard. + * This is used to apply additional keyguard dimming on top of the default scrim alpha value. + */ + protected void applyCompositeAlphaOnScrimBehindKeyguard() { + int compositeAlpha = ColorUtils.compositeAlpha( + (int) (255 * mAdditionalScrimBehindAlphaKeyguard), + (int) (255 * KEYGUARD_SCRIM_ALPHA)); + float keyguardScrimAlpha = (float) compositeAlpha / 255; + setScrimBehindValues(keyguardScrimAlpha); + } + + /** + * Sets the scrim behind alpha keyguard values. This is how much the keyguard will be dimmed. + * + * @param scrimBehindAlphaKeyguard alpha value of the scrim behind + */ + private void setScrimBehindValues(float scrimBehindAlphaKeyguard) { mScrimBehindAlphaKeyguard = scrimBehindAlphaKeyguard; ScrimState[] states = ScrimState.values(); for (int i = 0; i < states.length; i++) { @@ -676,7 +707,10 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump mNotificationsAlpha = behindFraction * mDefaultScrimAlpha; } else { mBehindAlpha = behindFraction * mDefaultScrimAlpha; - mNotificationsAlpha = mBehindAlpha; + // Delay fade-in of notification scrim a bit further, to coincide with the + // view fade in. Otherwise the empty panel can be quite jarring. + mNotificationsAlpha = MathUtils.constrainedMap(0f, 1f, 0.3f, 0.75f, + mPanelExpansionFraction); } mInFrontAlpha = 0; } @@ -732,7 +766,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump } if (mUnOcclusionAnimationRunning && mState == ScrimState.KEYGUARD) { // We're unoccluding the keyguard and don't want to have a bright flash. - mNotificationsAlpha = KEYGUARD_SCRIM_ALPHA; + mNotificationsAlpha = mScrimBehindAlphaKeyguard; mNotificationsTint = ScrimState.KEYGUARD.getNotifTint(); } } @@ -775,6 +809,12 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump : ScrimState.SHADE_LOCKED.getBehindTint(); behindTint = ColorUtils.blendARGB(behindTint, stateTint, mQsExpansion); } + + // If the keyguard is going away, we should not be opaque. + if (mKeyguardStateController.isKeyguardGoingAway()) { + behindAlpha = 0f; + } + return new Pair<>(behindTint, behindAlpha); } @@ -1299,9 +1339,6 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump public void setExpansionAffectsAlpha(boolean expansionAffectsAlpha) { mExpansionAffectsAlpha = expansionAffectsAlpha; - if (expansionAffectsAlpha) { - applyAndDispatchState(); - } } public void setKeyguardOccluded(boolean keyguardOccluded) { 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 8afa63719564..d2e1650056ac 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java @@ -153,7 +153,7 @@ public enum ScrimState { // to make sure correct color is returned before "prepare" is called @Override public int getBehindTint() { - return Color.BLACK; + return DEBUG_MODE ? DEBUG_BEHIND_TINT : Color.BLACK; } }, @@ -264,6 +264,12 @@ public enum ScrimState { } }; + private static final boolean DEBUG_MODE = false; + + private static final int DEBUG_NOTIFICATIONS_TINT = Color.RED; + private static final int DEBUG_FRONT_TINT = Color.GREEN; + private static final int DEBUG_BEHIND_TINT = Color.BLUE; + boolean mBlankScreen = false; long mAnimationDuration = ScrimController.ANIMATION_DURATION; int mFrontTint = Color.TRANSPARENT; @@ -323,15 +329,15 @@ public enum ScrimState { } public int getFrontTint() { - return mFrontTint; + return DEBUG_MODE ? DEBUG_FRONT_TINT : mFrontTint; } public int getBehindTint() { - return mBehindTint; + return DEBUG_MODE ? DEBUG_BEHIND_TINT : mBehindTint; } public int getNotifTint() { - return mNotifTint; + return DEBUG_MODE ? DEBUG_NOTIFICATIONS_TINT : mNotifTint; } public long getAnimationDuration() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java index f8b0535b7ec7..72237b1ca6c6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java @@ -123,7 +123,8 @@ public class ShadeControllerImpl implements ShadeController { + " canPanelBeCollapsed(): " + getNotificationPanelViewController().canPanelBeCollapsed()); if (getNotificationShadeWindowView() != null - && getNotificationPanelViewController().canPanelBeCollapsed()) { + && getNotificationPanelViewController().canPanelBeCollapsed() + && (flags & CommandQueue.FLAG_EXCLUDE_NOTIFICATION_PANEL) == 0) { // release focus immediately to kick off focus change transition mNotificationShadeWindowController.setNotificationShadeFocusable(false); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index 649aa42f693b..ae4a19e2b212 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -268,6 +268,7 @@ public class StatusBar extends CoreStartable implements // Should match the values in PhoneWindowManager public static final String SYSTEM_DIALOG_REASON_RECENT_APPS = "recentapps"; + public static final String SYSTEM_DIALOG_REASON_DREAM = "dream"; static public final String SYSTEM_DIALOG_REASON_SCREENSHOT = "screenshot"; private static final String BANNER_ACTION_CANCEL = @@ -618,6 +619,8 @@ public class StatusBar extends CoreStartable implements protected boolean mDozing; private boolean mIsFullscreen; + boolean mCloseQsBeforeScreenOff; + private final NotificationMediaManager mMediaManager; private final NotificationLockscreenUserManager mLockscreenUserManager; private final NotificationRemoteInputManager mRemoteInputManager; @@ -1123,6 +1126,15 @@ public class StatusBar extends CoreStartable implements } if (leaveOpen) { mStatusBarStateController.setLeaveOpenOnKeyguardHide(true); + if (mIsKeyguard) { + // When device state changes on keyguard we don't want to keep the state of + // the shade and instead we open clean state of keyguard with shade closed. + // Normally some parts of QS state (like expanded/collapsed) are persisted and + // that causes incorrect UI rendering, especially when changing state with QS + // expanded. To prevent that we can close QS which resets QS and some parts of + // the shade to its default state. Read more in b/201537421 + mCloseQsBeforeScreenOff = true; + } } } @@ -2635,8 +2647,17 @@ public class StatusBar extends CoreStartable implements if (mLockscreenUserManager.isCurrentProfile(getSendingUserId())) { int flags = CommandQueue.FLAG_EXCLUDE_NONE; String reason = intent.getStringExtra("reason"); - if (reason != null && reason.equals(SYSTEM_DIALOG_REASON_RECENT_APPS)) { - flags |= CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL; + if (reason != null) { + if (reason.equals(SYSTEM_DIALOG_REASON_RECENT_APPS)) { + flags |= CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL; + } + // Do not collapse notifications when starting dreaming if the notifications + // shade is used for the screen off animation. It might require expanded + // state for the scrims to be visible + if (reason.equals(SYSTEM_DIALOG_REASON_DREAM) + && mScreenOffAnimationController.shouldExpandNotifications()) { + flags |= CommandQueue.FLAG_EXCLUDE_NOTIFICATION_PANEL; + } } mShadeController.animateCollapsePanels(flags); } @@ -2913,10 +2934,10 @@ public class StatusBar extends CoreStartable implements } boolean updateIsKeyguard() { - return updateIsKeyguard(false /* force */); + return updateIsKeyguard(false /* forceStateChange */); } - boolean updateIsKeyguard(boolean force) { + boolean updateIsKeyguard(boolean forceStateChange) { boolean wakeAndUnlocking = mBiometricUnlockController.getMode() == BiometricUnlockController.MODE_WAKE_AND_UNLOCK; @@ -2949,7 +2970,7 @@ public class StatusBar extends CoreStartable implements // as the animation could prepare 'fake AOD' interface (without actually // transitioning to keyguard state) and this might reset the view states if (!mScreenOffAnimationController.isKeyguardHideDelayed()) { - return hideKeyguardImpl(force); + return hideKeyguardImpl(forceStateChange); } } return false; @@ -2957,6 +2978,8 @@ public class StatusBar extends CoreStartable implements public void showKeyguardImpl() { mIsKeyguard = true; + // In case we're locking while a smartspace transition is in progress, reset it. + mKeyguardUnlockAnimationController.resetSmartspaceTransition(); if (mKeyguardStateController.isLaunchTransitionFadingAway()) { mNotificationPanelViewController.cancelAnimation(); onLaunchTransitionFadingEnded(); @@ -3077,12 +3100,12 @@ public class StatusBar extends CoreStartable implements /** * @return true if we would like to stay in the shade, false if it should go away entirely */ - public boolean hideKeyguardImpl(boolean force) { + public boolean hideKeyguardImpl(boolean forceStateChange) { mIsKeyguard = false; Trace.beginSection("StatusBar#hideKeyguard"); boolean staying = mStatusBarStateController.leaveOpenOnKeyguardHide(); int previousState = mStatusBarStateController.getState(); - if (!(mStatusBarStateController.setState(StatusBarState.SHADE, force))) { + if (!(mStatusBarStateController.setState(StatusBarState.SHADE, forceStateChange))) { //TODO: StatusBarStateController should probably know about hiding the keyguard and // notify listeners. @@ -3135,6 +3158,7 @@ public class StatusBar extends CoreStartable implements // bar. mKeyguardStateController.notifyKeyguardGoingAway(true); mCommandQueue.appTransitionPending(mDisplayId, true /* forced */); + updateScrimController(); } /** @@ -3168,16 +3192,29 @@ public class StatusBar extends CoreStartable implements * Switches theme from light to dark and vice-versa. */ protected void updateTheme() { + // Set additional scrim only if the lock and system wallpaper are different to prevent + // applying the dimming effect twice. + mUiBgExecutor.execute(() -> { + float dimAmount = 0f; + if (mWallpaperManager.lockScreenWallpaperExists()) { + dimAmount = mWallpaperManager.getWallpaperDimAmount(); + } + final float scrimDimAmount = dimAmount; + mMainExecutor.execute(() -> { + mScrimController.setAdditionalScrimBehindAlphaKeyguard(scrimDimAmount); + mScrimController.applyCompositeAlphaOnScrimBehindKeyguard(); + }); + }); + // Lock wallpaper defines the color of the majority of the views, hence we'll use it // to set our default theme. final boolean lockDarkText = mColorExtractor.getNeutralColors().supportsDarkText(); final int themeResId = lockDarkText ? R.style.Theme_SystemUI_LightWallpaper : R.style.Theme_SystemUI; - if (mContext.getThemeResId() == themeResId) { - return; + if (mContext.getThemeResId() != themeResId) { + mContext.setTheme(themeResId); + mConfigurationController.notifyThemeChanged(); } - mContext.setTheme(themeResId); - mConfigurationController.notifyThemeChanged(); } private void updateDozingState() { @@ -3293,7 +3330,10 @@ public class StatusBar extends CoreStartable implements } private void showBouncerOrLockScreenIfKeyguard() { - if (!mKeyguardViewMediator.isHiding()) { + // If the keyguard is animating away, we aren't really the keyguard anymore and should not + // show the bouncer/lockscreen. + if (!mKeyguardViewMediator.isHiding() + && !mKeyguardUnlockAnimationController.isPlayingCannedUnlockAnimation()) { if (mState == StatusBarState.SHADE_LOCKED && mKeyguardUpdateMonitor.isUdfpsEnrolled()) { // shade is showing while locked on the keyguard, so go back to showing the @@ -3631,6 +3671,10 @@ public class StatusBar extends CoreStartable implements public void onScreenTurnedOff() { mFalsingCollector.onScreenOff(); mScrimController.onScreenTurnedOff(); + if (mCloseQsBeforeScreenOff) { + mNotificationPanelViewController.closeQs(); + mCloseQsBeforeScreenOff = false; + } updateIsKeyguard(); } }; @@ -3723,17 +3767,14 @@ public class StatusBar extends CoreStartable implements public void updateScrimController() { Trace.beginSection("StatusBar#updateScrimController"); - // We don't want to end up in KEYGUARD state when we're unlocking with - // fingerprint from doze. We should cross fade directly from black. - boolean unlocking = mBiometricUnlockController.isWakeAndUnlock() - || mKeyguardStateController.isKeyguardFadingAway(); + boolean unlocking = mKeyguardStateController.isShowing() && ( + mBiometricUnlockController.isWakeAndUnlock() + || mKeyguardStateController.isKeyguardFadingAway() + || mKeyguardStateController.isKeyguardGoingAway() + || mKeyguardViewMediator.requestedShowSurfaceBehindKeyguard() + || mKeyguardViewMediator.isAnimatingBetweenKeyguardAndSurfaceBehind()); - // Do not animate the scrim expansion when triggered by the fingerprint sensor. - boolean onKeyguardOrHidingIt = mKeyguardStateController.isShowing() - || mKeyguardStateController.isKeyguardFadingAway() - || mKeyguardStateController.isKeyguardGoingAway(); - mScrimController.setExpansionAffectsAlpha(!(mBiometricUnlockController.isBiometricUnlock() - && onKeyguardOrHidingIt)); + mScrimController.setExpansionAffectsAlpha(!unlocking); boolean launchingAffordanceWithPreview = mNotificationPanelViewController.isLaunchingAffordanceWithPreview(); @@ -3745,7 +3786,7 @@ public class StatusBar extends CoreStartable implements } else { mScrimController.transitionTo(ScrimState.AUTH_SCRIMMED); } - } else if (mBouncerShowing) { + } else if (mBouncerShowing && !unlocking) { // Bouncer needs the front scrim when it's on top of an activity, // tapping on a notification, editing QS or being dismissed by // FLAG_DISMISS_KEYGUARD_ACTIVITY. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt index ea0dd72d673f..cc65ca025139 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt @@ -6,6 +6,7 @@ import android.animation.ValueAnimator import android.content.Context import android.database.ContentObserver import android.os.Handler +import android.os.PowerManager import android.provider.Settings import android.view.Surface import android.view.View @@ -54,9 +55,10 @@ class UnlockedScreenOffAnimationController @Inject constructor( private val keyguardStateController: KeyguardStateController, private val dozeParameters: dagger.Lazy<DozeParameters>, private val globalSettings: GlobalSettings, - private val interactionJankMonitor: InteractionJankMonitor + private val interactionJankMonitor: InteractionJankMonitor, + private val powerManager: PowerManager, + private val handler: Handler = Handler() ) : WakefulnessLifecycle.Observer, ScreenOffAnimation { - private val handler = Handler() private lateinit var statusBar: StatusBar private lateinit var lightRevealScrim: LightRevealScrim @@ -219,7 +221,7 @@ class UnlockedScreenOffAnimationController @Inject constructor( // even if we're going from SHADE to SHADE or KEYGUARD to KEYGUARD, since we might have // changed parts of the UI (such as showing AOD in the shade) without actually changing // the StatusBarState. This ensures that the UI definitely reflects the desired state. - statusBar.updateIsKeyguard(true /* force */) + statusBar.updateIsKeyguard(true /* forceStateChange */) } } @@ -231,10 +233,18 @@ class UnlockedScreenOffAnimationController @Inject constructor( lightRevealAnimationPlaying = true lightRevealAnimator.start() handler.postDelayed({ - aodUiAnimationPlaying = true - - // Show AOD. That'll cause the KeyguardVisibilityHelper to call #animateInKeyguard. - statusBar.notificationPanelViewController.showAodUi() + // Only run this callback if the device is sleeping (not interactive). This callback + // is removed in onStartedWakingUp, but since that event is asynchronously + // dispatched, a race condition could make it possible for this callback to be run + // as the device is waking up. That results in the AOD UI being shown while we wake + // up, with unpredictable consequences. + if (!powerManager.isInteractive) { + aodUiAnimationPlaying = true + + // Show AOD. That'll cause the KeyguardVisibilityHelper to call + // #animateInKeyguard. + statusBar.notificationPanelViewController.showAodUi() + } }, (ANIMATE_IN_KEYGUARD_DELAY * animatorDurationScale).toLong()) return true @@ -290,6 +300,9 @@ class UnlockedScreenOffAnimationController @Inject constructor( return true } + override fun shouldDelayDisplayDozeTransition(): Boolean = + dozeParameters.get().shouldControlUnlockedScreenOff() + fun addCallback(callback: Callback) { callbacks.add(callback) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java index 1030bfdb40fd..33f2140b150e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java @@ -127,10 +127,12 @@ public final class DeviceStateRotationLockSettingController int rotationLockSetting = mDeviceStateRotationLockSettingsManager.getRotationLockSetting(state); if (rotationLockSetting == DEVICE_STATE_ROTATION_LOCK_IGNORED) { + // This should not happen. Device states that have an ignored setting, should also + // specify a fallback device state which is not ignored. // We won't handle this device state. The same rotation lock setting as before should // apply and any changes to the rotation lock setting will be written for the previous // valid device state. - Log.v(TAG, "Ignoring new device state: " + state); + Log.w(TAG, "Missing fallback. Ignoring new device state: " + state); return; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingsManager.java index a418c74848a5..bec5fc8e7b9f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingsManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingsManager.java @@ -52,12 +52,14 @@ public final class DeviceStateRotationLockSettingsManager { private final Handler mMainHandler = Handler.getMain(); private final Set<DeviceStateRotationLockSettingsListener> mListeners = new HashSet<>(); private SparseIntArray mDeviceStateRotationLockSettings; + private SparseIntArray mDeviceStateRotationLockFallbackSettings; private DeviceStateRotationLockSettingsManager(Context context) { mContentResolver = context.getContentResolver(); mDeviceStateRotationLockDefaults = context.getResources() .getStringArray(R.array.config_perDeviceStateRotationLockDefaults); + loadDefaults(); initializeInMemoryMap(); listenForSettingsChange(context); } @@ -114,6 +116,11 @@ public final class DeviceStateRotationLockSettingsManager { /** Updates the rotation lock setting for a specified device state. */ public void updateSetting(int deviceState, boolean rotationLocked) { + if (mDeviceStateRotationLockFallbackSettings.indexOfKey(deviceState) >= 0) { + // The setting for this device state is IGNORED, and has a fallback device state. + // The setting for that fallback device state should be the changed in this case. + deviceState = mDeviceStateRotationLockFallbackSettings.get(deviceState); + } mDeviceStateRotationLockSettings.put( deviceState, rotationLocked @@ -123,16 +130,37 @@ public final class DeviceStateRotationLockSettingsManager { } /** - * Returns the {@link DeviceStateRotationLockSetting} for the given device state. If no setting - * is specified for this device state, it will return {@link + * Returns the {@link Settings.Secure.DeviceStateRotationLockSetting} for the given device + * state. + * + * <p>If the setting for this device state is {@link DEVICE_STATE_ROTATION_LOCK_IGNORED}, it + * will return the setting for the fallback device state. + * + * <p>If no fallback is specified for this device state, it will return {@link * DEVICE_STATE_ROTATION_LOCK_IGNORED}. */ @Settings.Secure.DeviceStateRotationLockSetting public int getRotationLockSetting(int deviceState) { - return mDeviceStateRotationLockSettings.get( - deviceState, DEVICE_STATE_ROTATION_LOCK_IGNORED); + int rotationLockSetting = mDeviceStateRotationLockSettings.get( + deviceState, /* valueIfKeyNotFound= */ DEVICE_STATE_ROTATION_LOCK_IGNORED); + if (rotationLockSetting == DEVICE_STATE_ROTATION_LOCK_IGNORED) { + rotationLockSetting = getFallbackRotationLockSetting(deviceState); + } + return rotationLockSetting; + } + + private int getFallbackRotationLockSetting(int deviceState) { + int indexOfFallbackState = mDeviceStateRotationLockFallbackSettings.indexOfKey(deviceState); + if (indexOfFallbackState < 0) { + Log.w(TAG, "Setting is ignored, but no fallback was specified."); + return DEVICE_STATE_ROTATION_LOCK_IGNORED; + } + int fallbackState = mDeviceStateRotationLockFallbackSettings.valueAt(indexOfFallbackState); + return mDeviceStateRotationLockSettings.get(fallbackState, + /* valueIfKeyNotFound= */ DEVICE_STATE_ROTATION_LOCK_IGNORED); } + /** Returns true if the rotation is locked for the current device state */ public boolean isRotationLocked(int deviceState) { return getRotationLockSetting(deviceState) == DEVICE_STATE_ROTATION_LOCK_LOCKED; @@ -223,21 +251,30 @@ public final class DeviceStateRotationLockSettingsManager { } private void loadDefaults() { - if (mDeviceStateRotationLockDefaults.length == 0) { - Log.w(TAG, "Empty default settings"); - mDeviceStateRotationLockSettings = new SparseIntArray(/* initialCapacity= */ 0); - return; - } - mDeviceStateRotationLockSettings = - new SparseIntArray(mDeviceStateRotationLockDefaults.length); - for (String serializedDefault : mDeviceStateRotationLockDefaults) { - String[] entry = serializedDefault.split(SEPARATOR_REGEX); + mDeviceStateRotationLockSettings = new SparseIntArray( + mDeviceStateRotationLockDefaults.length); + mDeviceStateRotationLockFallbackSettings = new SparseIntArray(1); + for (String entry : mDeviceStateRotationLockDefaults) { + String[] values = entry.split(SEPARATOR_REGEX); try { - int key = Integer.parseInt(entry[0]); - int value = Integer.parseInt(entry[1]); - mDeviceStateRotationLockSettings.put(key, value); + int deviceState = Integer.parseInt(values[0]); + int rotationLockSetting = Integer.parseInt(values[1]); + if (rotationLockSetting == DEVICE_STATE_ROTATION_LOCK_IGNORED) { + if (values.length == 3) { + int fallbackDeviceState = Integer.parseInt(values[2]); + mDeviceStateRotationLockFallbackSettings.put(deviceState, + fallbackDeviceState); + } else { + Log.w(TAG, + "Rotation lock setting is IGNORED, but values have unexpected " + + "size of " + + values.length); + } + } + mDeviceStateRotationLockSettings.put(deviceState, rotationLockSetting); } catch (NumberFormatException e) { - Log.wtf(TAG, "Error deserializing default settings", e); + Log.wtf(TAG, "Error parsing settings entry. Entry was: " + entry, e); + return; } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java index 7bf1601b2fd5..050b67016d09 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java @@ -50,13 +50,6 @@ public interface KeyguardStateController extends CallbackController<Callback> { boolean canDismissLockScreen(); /** - * Whether we can currently perform the shared element SmartSpace transition. This is true if - * we're on the lockscreen, it can be dismissed with a swipe, and the Launcher is underneath the - * keyguard and displaying a SmartSpace that it has registered with System UI. - */ - boolean canPerformSmartSpaceTransition(); - - /** * Whether the keyguard is allowed to rotate, or needs to be locked to the default orientation. */ boolean isKeyguardScreenRotationAllowed(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java index 05a586b1cdc2..978564fc81d0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java @@ -35,7 +35,7 @@ import com.android.systemui.Dumpable; import com.android.systemui.R; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dump.DumpManager; -import com.android.systemui.shared.system.smartspace.SmartspaceTransitionController; +import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -44,6 +44,8 @@ import java.util.Objects; import javax.inject.Inject; +import dagger.Lazy; + /** */ @SysUISingleton @@ -58,7 +60,7 @@ public class KeyguardStateControllerImpl implements KeyguardStateController, Dum private final LockPatternUtils mLockPatternUtils; private final KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback = new UpdateMonitorCallback(); - private final SmartspaceTransitionController mSmartspaceTransitionController; + private final Lazy<KeyguardUnlockAnimationController> mUnlockAnimationControllerLazy; private boolean mCanDismissLockScreen; private boolean mShowing; @@ -105,13 +107,13 @@ public class KeyguardStateControllerImpl implements KeyguardStateController, Dum Context context, KeyguardUpdateMonitor keyguardUpdateMonitor, LockPatternUtils lockPatternUtils, - SmartspaceTransitionController smartspaceTransitionController, + Lazy<KeyguardUnlockAnimationController> keyguardUnlockAnimationController, DumpManager dumpManager) { mContext = context; mKeyguardUpdateMonitor = keyguardUpdateMonitor; mLockPatternUtils = lockPatternUtils; mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback); - mSmartspaceTransitionController = smartspaceTransitionController; + mUnlockAnimationControllerLazy = keyguardUnlockAnimationController; dumpManager.registerDumpable(getClass().getSimpleName(), this); @@ -249,12 +251,6 @@ public class KeyguardStateControllerImpl implements KeyguardStateController, Dum } @Override - public boolean canPerformSmartSpaceTransition() { - return canDismissLockScreen() - && mSmartspaceTransitionController.isSmartspaceTransitionPossible(); - } - - @Override public boolean isKeyguardScreenRotationAllowed() { return SystemProperties.getBoolean("lockscreen.rot_override", false) || mContext.getResources().getBoolean(R.bool.config_enableLockScreenRotation); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java index 3831857c5c8d..59969c0447b3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java @@ -22,10 +22,14 @@ import static android.app.AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION; import static com.android.settingslib.Utils.updateLocationEnabled; +import android.app.AppOpsManager; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.PermissionChecker; +import android.content.pm.PackageManager; +import android.content.pm.UserInfo; import android.location.LocationManager; import android.os.Handler; import android.os.Looper; @@ -68,31 +72,37 @@ public class LocationControllerImpl extends BroadcastReceiver implements Locatio private final BootCompleteCache mBootCompleteCache; private final UserTracker mUserTracker; private final H mHandler; - + private final Handler mBackgroundHandler; + private final PackageManager mPackageManager; private boolean mAreActiveLocationRequests; private boolean mShouldDisplayAllAccesses; + private boolean mShowSystemAccesses; @Inject public LocationControllerImpl(Context context, AppOpsController appOpsController, DeviceConfigProxy deviceConfigProxy, @Main Looper mainLooper, @Background Handler backgroundHandler, BroadcastDispatcher broadcastDispatcher, BootCompleteCache bootCompleteCache, - UserTracker userTracker) { + UserTracker userTracker, PackageManager packageManager) { mContext = context; mAppOpsController = appOpsController; mDeviceConfigProxy = deviceConfigProxy; mBootCompleteCache = bootCompleteCache; mHandler = new H(mainLooper); mUserTracker = userTracker; - mShouldDisplayAllAccesses = getDeviceConfigSetting(); + mBackgroundHandler = backgroundHandler; + mPackageManager = packageManager; + mShouldDisplayAllAccesses = getAllAccessesSetting(); + mShowSystemAccesses = getShowSystemSetting(); // Register to listen for changes in DeviceConfig settings. mDeviceConfigProxy.addOnPropertiesChangedListener( DeviceConfig.NAMESPACE_PRIVACY, backgroundHandler::post, properties -> { - mShouldDisplayAllAccesses = getDeviceConfigSetting(); + mShouldDisplayAllAccesses = getAllAccessesSetting(); + mShowSystemAccesses = getShowSystemSetting(); updateActiveLocationRequests(); }); @@ -176,11 +186,15 @@ public class LocationControllerImpl extends BroadcastReceiver implements Locatio UserHandle.of(userId)); } - private boolean getDeviceConfigSetting() { + private boolean getAllAccessesSetting() { return mDeviceConfigProxy.getBoolean(DeviceConfig.NAMESPACE_PRIVACY, SystemUiDeviceConfigFlags.PROPERTY_LOCATION_INDICATORS_SMALL_ENABLED, false); } + private boolean getShowSystemSetting() { + return mDeviceConfigProxy.getBoolean(DeviceConfig.NAMESPACE_PRIVACY, + SystemUiDeviceConfigFlags.PROPERTY_LOCATION_INDICATORS_SHOW_SYSTEM, false); + } /** * Returns true if there currently exist active high power location requests. */ @@ -202,35 +216,74 @@ public class LocationControllerImpl extends BroadcastReceiver implements Locatio * Returns true if there currently exist active location requests. */ @VisibleForTesting - protected boolean areActiveLocationRequests() { + protected void areActiveLocationRequests() { if (!mShouldDisplayAllAccesses) { - return false; + return; } - List<AppOpItem> appOpsItems = mAppOpsController.getActiveAppOps(); + boolean hadActiveLocationRequests = mAreActiveLocationRequests; + boolean shouldDisplay = false; + List<AppOpItem> appOpsItems = mAppOpsController.getActiveAppOps(); + final List<UserInfo> profiles = mUserTracker.getUserProfiles(); final int numItems = appOpsItems.size(); for (int i = 0; i < numItems; i++) { if (appOpsItems.get(i).getCode() == OP_FINE_LOCATION || appOpsItems.get(i).getCode() == OP_COARSE_LOCATION) { - return true; + if (mShowSystemAccesses) { + shouldDisplay = true; + } else { + shouldDisplay |= !isSystemApp(profiles, appOpsItems.get(i)); + } } } - return false; + mAreActiveLocationRequests = areActiveHighPowerLocationRequests() || shouldDisplay; + if (mAreActiveLocationRequests != hadActiveLocationRequests) { + mHandler.sendEmptyMessage(H.MSG_LOCATION_ACTIVE_CHANGED); + } + } + + private boolean isSystemApp(List<UserInfo> profiles, AppOpItem item) { + final String permission = AppOpsManager.opToPermission(item.getCode()); + UserHandle user = UserHandle.getUserHandleForUid(item.getUid()); + + // Don't show apps belonging to background users except managed users. + boolean foundUser = false; + final int numProfiles = profiles.size(); + for (int i = 0; i < numProfiles; i++) { + if (profiles.get(i).getUserHandle().equals(user)) { + foundUser = true; + } + } + if (!foundUser) { + return true; + } + + final int permissionFlags = mPackageManager.getPermissionFlags( + permission, item.getPackageName(), user); + if (PermissionChecker.checkPermissionForPreflight(mContext, permission, + PermissionChecker.PID_UNKNOWN, item.getUid(), item.getPackageName()) + == PermissionChecker.PERMISSION_GRANTED) { + return (permissionFlags + & PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED) + == 0; + } else { + return (permissionFlags + & PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED) == 0; + } } // Reads the active location requests from either OP_MONITOR_HIGH_POWER_LOCATION, // OP_FINE_LOCATION, or OP_COARSE_LOCATION and updates the status view if necessary. private void updateActiveLocationRequests() { - boolean hadActiveLocationRequests = mAreActiveLocationRequests; if (mShouldDisplayAllAccesses) { - mAreActiveLocationRequests = - areActiveHighPowerLocationRequests() || areActiveLocationRequests(); + mBackgroundHandler.post(this::areActiveLocationRequests); } else { + boolean hadActiveLocationRequests = mAreActiveLocationRequests; mAreActiveLocationRequests = areActiveHighPowerLocationRequests(); - } - if (mAreActiveLocationRequests != hadActiveLocationRequests) { - mHandler.sendEmptyMessage(H.MSG_LOCATION_ACTIVE_CHANGED); + if (mAreActiveLocationRequests != hadActiveLocationRequests) { + mHandler.sendEmptyMessage(H.MSG_LOCATION_ACTIVE_CHANGED); + } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java index 46fa20d094a0..48949f92413d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java @@ -51,6 +51,7 @@ import android.view.ViewAnimationUtils; import android.view.ViewGroup; import android.view.WindowInsets; import android.view.WindowInsetsAnimation; +import android.view.WindowInsetsController; import android.view.accessibility.AccessibilityEvent; import android.view.inputmethod.CompletionInfo; import android.view.inputmethod.EditorInfo; @@ -665,8 +666,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene } // Hide soft-keyboard when the input view became invisible // (i.e. The notification shade collapsed by pressing the home key) - if (visibility != VISIBLE && !mEditText.isVisibleToUser() - && !mController.isRemoteInputActive()) { + if (visibility != VISIBLE && !mController.isRemoteInputActive()) { mEditText.hideIme(); } } @@ -779,8 +779,9 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene } private void hideIme() { - if (mInputMethodManager != null) { - mInputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0); + final WindowInsetsController insetsController = getWindowInsetsController(); + if (insetsController != null) { + insetsController.hide(WindowInsets.Type.ime()); } } diff --git a/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt b/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt index 52c416bad803..aaf35afe936d 100644 --- a/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt @@ -30,17 +30,17 @@ import dagger.Lazy import javax.inject.Inject /** - * Controls folding to AOD animation: when AOD is enabled and foldable device is folded - * we play a special AOD animation on the outer screen + * Controls folding to AOD animation: when AOD is enabled and foldable device is folded we play a + * special AOD animation on the outer screen */ @SysUIUnfoldScope -class FoldAodAnimationController @Inject constructor( +class FoldAodAnimationController +@Inject +constructor( private val keyguardViewMediatorLazy: Lazy<KeyguardViewMediator>, private val wakefulnessLifecycle: WakefulnessLifecycle, private val globalSettings: GlobalSettings -) : CallbackController<FoldAodAnimationStatus>, - ScreenOffAnimation, - WakefulnessLifecycle.Observer { +) : CallbackController<FoldAodAnimationStatus>, ScreenOffAnimation, WakefulnessLifecycle.Observer { private var alwaysOnEnabled: Boolean = false private var isScrimOpaque: Boolean = false @@ -58,17 +58,13 @@ class FoldAodAnimationController @Inject constructor( wakefulnessLifecycle.addObserver(this) } - /** - * Returns true if we should run fold to AOD animation - */ - override fun shouldPlayAnimation(): Boolean = - shouldPlayAnimation + /** Returns true if we should run fold to AOD animation */ + override fun shouldPlayAnimation(): Boolean = shouldPlayAnimation override fun startAnimation(): Boolean = if (alwaysOnEnabled && wakefulnessLifecycle.lastSleepReason == PowerManager.GO_TO_SLEEP_REASON_DEVICE_FOLD && - globalSettings.getString(Settings.Global.ANIMATOR_DURATION_SCALE) != "0" - ) { + globalSettings.getString(Settings.Global.ANIMATOR_DURATION_SCALE) != "0") { shouldPlayAnimation = true isAnimationPlaying = true @@ -107,9 +103,7 @@ class FoldAodAnimationController @Inject constructor( } } - /** - * Called when keyguard scrim opaque changed - */ + /** Called when keyguard scrim opaque changed */ override fun onScrimOpaqueChanged(isOpaque: Boolean) { isScrimOpaque = isOpaque @@ -130,27 +124,21 @@ class FoldAodAnimationController @Inject constructor( } } - override fun isAnimationPlaying(): Boolean = - isAnimationPlaying + override fun isAnimationPlaying(): Boolean = isAnimationPlaying - override fun isKeyguardHideDelayed(): Boolean = - isAnimationPlaying() + override fun isKeyguardHideDelayed(): Boolean = isAnimationPlaying() - override fun shouldShowAodIconsWhenShade(): Boolean = - shouldPlayAnimation() + override fun shouldShowAodIconsWhenShade(): Boolean = shouldPlayAnimation() - override fun shouldAnimateAodIcons(): Boolean = - !shouldPlayAnimation() + override fun shouldAnimateAodIcons(): Boolean = !shouldPlayAnimation() - override fun shouldAnimateDozingChange(): Boolean = - !shouldPlayAnimation() + override fun shouldAnimateDozingChange(): Boolean = !shouldPlayAnimation() - override fun shouldAnimateClockChange(): Boolean = - !isAnimationPlaying() + override fun shouldAnimateClockChange(): Boolean = !isAnimationPlaying() - /** - * Called when AOD status is changed - */ + override fun shouldDelayDisplayDozeTransition(): Boolean = shouldPlayAnimation() + + /** Called when AOD status is changed */ override fun onAlwaysOnChanged(alwaysOn: Boolean) { alwaysOnEnabled = alwaysOn } diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLatencyTracker.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLatencyTracker.kt index 7f63d6c5e778..79b42b8daab1 100644 --- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLatencyTracker.kt +++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLatencyTracker.kt @@ -29,12 +29,16 @@ import javax.inject.Inject * Logs performance metrics regarding time to turn the inner screen on. * * This class assumes that [onFoldEvent] is always called before [onScreenTurnedOn]. + * * This should be used from only one process. + * * For now, the focus is on the time the inner display is visible, but in the future, it is easily * possible to monitor the time to go from the inner screen to the outer. */ @SysUISingleton -class UnfoldLatencyTracker @Inject constructor( +class UnfoldLatencyTracker +@Inject +constructor( private val latencyTracker: LatencyTracker, private val deviceStateManager: DeviceStateManager, @UiBackground private val uiBgExecutor: Executor, @@ -45,8 +49,11 @@ class UnfoldLatencyTracker @Inject constructor( private var folded: Boolean? = null private val foldStateListener = FoldStateListener(context) private val isFoldable: Boolean - get() = context.resources.getIntArray( - com.android.internal.R.array.config_foldedDeviceStates).isNotEmpty() + get() = + context + .resources + .getIntArray(com.android.internal.R.array.config_foldedDeviceStates) + .isNotEmpty() /** Registers for relevant events only if the device is foldable. */ fun init() { diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt index 0b89ef28d227..4b09a583645c 100644 --- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt +++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt @@ -20,8 +20,8 @@ import android.content.Context import android.graphics.PixelFormat import android.hardware.devicestate.DeviceStateManager import android.hardware.devicestate.DeviceStateManager.FoldStateListener -import android.hardware.input.InputManager import android.hardware.display.DisplayManager +import android.hardware.input.InputManager import android.os.Handler import android.os.Trace import android.view.Choreographer @@ -46,7 +46,9 @@ import java.util.function.Consumer import javax.inject.Inject @SysUIUnfoldScope -class UnfoldLightRevealOverlayAnimation @Inject constructor( +class UnfoldLightRevealOverlayAnimation +@Inject +constructor( private val context: Context, private val deviceStateManager: DeviceStateManager, private val displayManager: DisplayManager, @@ -75,12 +77,13 @@ class UnfoldLightRevealOverlayAnimation @Inject constructor( deviceStateManager.registerCallback(executor, FoldListener()) unfoldTransitionProgressProvider.addCallback(transitionListener) - val containerBuilder = SurfaceControl.Builder(SurfaceSession()) - .setContainerLayer() - .setName("unfold-overlay-container") + val containerBuilder = + SurfaceControl.Builder(SurfaceSession()) + .setContainerLayer() + .setName("unfold-overlay-container") - displayAreaHelper.get().attachToRootDisplayArea(Display.DEFAULT_DISPLAY, - containerBuilder) { builder -> + displayAreaHelper.get().attachToRootDisplayArea( + Display.DEFAULT_DISPLAY, containerBuilder) { builder -> executor.execute { overlayContainer = builder.build() @@ -89,13 +92,13 @@ class UnfoldLightRevealOverlayAnimation @Inject constructor( .show(overlayContainer) .apply() - wwm = WindowlessWindowManager(context.resources.configuration, - overlayContainer, null) + wwm = + WindowlessWindowManager(context.resources.configuration, overlayContainer, null) } } - displayManager.registerDisplayListener(displayListener, handler, - DisplayManager.EVENT_FLAG_DISPLAY_CHANGED) + displayManager.registerDisplayListener( + displayListener, handler, DisplayManager.EVENT_FLAG_DISPLAY_CHANGED) // Get unfolded display size immediately as 'current display info' might be // not up-to-date during unfolding @@ -136,8 +139,8 @@ class UnfoldLightRevealOverlayAnimation @Inject constructor( ensureOverlayRemoved() val newRoot = SurfaceControlViewHost(context, context.display!!, wwm, false) - val newView = LightRevealScrim(context, null) - .apply { + val newView = + LightRevealScrim(context, null).apply { revealEffect = createLightRevealEffect() isScrimOpaqueChangedListener = Consumer {} revealAmount = 0f @@ -147,8 +150,7 @@ class UnfoldLightRevealOverlayAnimation @Inject constructor( newRoot.setView(newView, params) onOverlayReady?.let { callback -> - Trace.beginAsyncSection( - "UnfoldLightRevealOverlayAnimation#relayout", 0) + Trace.beginAsyncSection("UnfoldLightRevealOverlayAnimation#relayout", 0) newRoot.relayout(params) { transaction -> val vsyncId = Choreographer.getSfInstance().vsyncId @@ -161,15 +163,11 @@ class UnfoldLightRevealOverlayAnimation @Inject constructor( // (turn on the brightness) only when the content is actually visible as it // might be presented only in the next frame. // See b/197538198 - transaction.setFrameTimelineVsync(vsyncId) - .apply(/* sync */true) + transaction.setFrameTimelineVsync(vsyncId).apply(/* sync */ true) - transaction - .setFrameTimelineVsync(vsyncId + 1) - .apply(/* sync */ true) + transaction.setFrameTimelineVsync(vsyncId + 1).apply(/* sync */ true) - Trace.endAsyncSection( - "UnfoldLightRevealOverlayAnimation#relayout", 0) + Trace.endAsyncSection("UnfoldLightRevealOverlayAnimation#relayout", 0) callback.run() } } @@ -185,10 +183,10 @@ class UnfoldLightRevealOverlayAnimation @Inject constructor( val rotation = context.display!!.rotation val isNatural = rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180 - params.height = if (isNatural) - unfoldedDisplayInfo.naturalHeight else unfoldedDisplayInfo.naturalWidth - params.width = if (isNatural) - unfoldedDisplayInfo.naturalWidth else unfoldedDisplayInfo.naturalHeight + params.height = + if (isNatural) unfoldedDisplayInfo.naturalHeight else unfoldedDisplayInfo.naturalWidth + params.width = + if (isNatural) unfoldedDisplayInfo.naturalWidth else unfoldedDisplayInfo.naturalHeight params.format = PixelFormat.TRANSLUCENT params.type = WindowManager.LayoutParams.TYPE_DISPLAY_OVERLAY @@ -206,8 +204,8 @@ class UnfoldLightRevealOverlayAnimation @Inject constructor( } private fun createLightRevealEffect(): LightRevealEffect { - val isVerticalFold = currentRotation == Surface.ROTATION_0 || - currentRotation == Surface.ROTATION_180 + val isVerticalFold = + currentRotation == Surface.ROTATION_0 || currentRotation == Surface.ROTATION_180 return LinearLightRevealEffect(isVertical = isVerticalFold) } @@ -218,7 +216,8 @@ class UnfoldLightRevealOverlayAnimation @Inject constructor( } private fun getUnfoldedDisplayInfo(): DisplayInfo = - displayManager.displays + displayManager + .displays .asSequence() .map { DisplayInfo().apply { it.getDisplayInfo(this) } } .filter { it.type == Display.TYPE_INTERNAL } @@ -255,18 +254,19 @@ class UnfoldLightRevealOverlayAnimation @Inject constructor( } } - override fun onDisplayAdded(displayId: Int) { - } + override fun onDisplayAdded(displayId: Int) {} - override fun onDisplayRemoved(displayId: Int) { - } + override fun onDisplayRemoved(displayId: Int) {} } - private inner class FoldListener : FoldStateListener(context, Consumer { isFolded -> - if (isFolded) { - ensureOverlayRemoved() - isUnfoldHandled = false - } - this.isFolded = isFolded - }) + private inner class FoldListener : + FoldStateListener( + context, + Consumer { isFolded -> + if (isFolded) { + ensureOverlayRemoved() + isUnfoldHandled = false + } + this.isFolded = isFolded + }) } diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldProgressProvider.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldProgressProvider.kt index bd04ad8385b2..2325acfdcd48 100644 --- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldProgressProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldProgressProvider.kt @@ -21,29 +21,23 @@ import com.android.wm.shell.unfold.ShellUnfoldProgressProvider import com.android.wm.shell.unfold.ShellUnfoldProgressProvider.UnfoldListener import java.util.concurrent.Executor -class UnfoldProgressProvider( - private val unfoldProgressProvider: UnfoldTransitionProgressProvider -) : ShellUnfoldProgressProvider { +class UnfoldProgressProvider(private val unfoldProgressProvider: UnfoldTransitionProgressProvider) : + ShellUnfoldProgressProvider { override fun addListener(executor: Executor, listener: UnfoldListener) { - unfoldProgressProvider.addCallback(object : TransitionProgressListener { - override fun onTransitionStarted() { - executor.execute { - listener.onStateChangeStarted() + unfoldProgressProvider.addCallback( + object : TransitionProgressListener { + override fun onTransitionStarted() { + executor.execute { listener.onStateChangeStarted() } } - } - override fun onTransitionProgress(progress: Float) { - executor.execute { - listener.onStateChangeProgress(progress) + override fun onTransitionProgress(progress: Float) { + executor.execute { listener.onStateChangeProgress(progress) } } - } - override fun onTransitionFinished() { - executor.execute { - listener.onStateChangeFinished() + override fun onTransitionFinished() { + executor.execute { listener.onStateChangeFinished() } } - } - }) + }) } } diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt index 178d01477a09..d2d2361d613d 100644 --- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt +++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt @@ -90,7 +90,7 @@ class UnfoldTransitionModule { config: UnfoldTransitionConfig, provider: Optional<UnfoldTransitionProgressProvider> ): ShellUnfoldProgressProvider = - if (config.isEnabled && provider.isPresent()) { + if (config.isEnabled && provider.isPresent) { UnfoldProgressProvider(provider.get()) } else { ShellUnfoldProgressProvider.NO_PROVIDER diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionWallpaperController.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionWallpaperController.kt index a184315ab75c..d723760fa510 100644 --- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionWallpaperController.kt +++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionWallpaperController.kt @@ -21,7 +21,9 @@ import com.android.systemui.util.WallpaperController import javax.inject.Inject @SysUIUnfoldScope -class UnfoldTransitionWallpaperController @Inject constructor( +class UnfoldTransitionWallpaperController +@Inject +constructor( private val unfoldTransitionProgressProvider: UnfoldTransitionProgressProvider, private val wallpaperController: WallpaperController ) { diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java index 20c8bf38692b..69ebfe8012d1 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java @@ -427,9 +427,11 @@ public class BubblesManager implements Dumpable { } @Override - public void onEntryRemoved(NotificationEntry entry, + public void onEntryRemoved( + NotificationEntry entry, @Nullable NotificationVisibility visibility, - boolean removedByUser, int reason) { + boolean removedByUser, + int reason) { BubblesManager.this.onEntryRemoved(entry); } @@ -437,6 +439,18 @@ public class BubblesManager implements Dumpable { public void onNotificationRankingUpdated(RankingMap rankingMap) { BubblesManager.this.onRankingUpdate(rankingMap); } + + @Override + public void onNotificationChannelModified( + String pkgName, + UserHandle user, + NotificationChannel channel, + int modificationType) { + BubblesManager.this.onNotificationChannelModified(pkgName, + user, + channel, + modificationType); + } }); // The new pipeline takes care of this as a NotifDismissInterceptor BubbleCoordinator @@ -556,6 +570,19 @@ public class BubblesManager implements Dumpable { public void onRankingUpdate(RankingMap rankingMap) { BubblesManager.this.onRankingUpdate(rankingMap); } + + @Override + public void onNotificationChannelModified( + String pkgName, + UserHandle user, + NotificationChannel channel, + int modificationType) { + BubblesManager.this.onNotificationChannelModified( + pkgName, + user, + channel, + modificationType); + } }); } @@ -592,6 +619,14 @@ public class BubblesManager implements Dumpable { mBubbles.onRankingUpdated(rankingMap, pendingOrActiveNotif); } + void onNotificationChannelModified( + String pkg, + UserHandle user, + NotificationChannel channel, + int modificationType) { + mBubbles.onNotificationChannelModified(pkg, user, channel, modificationType); + } + /** * Gets the DismissedByUserStats used by {@link NotificationEntryManager}. * Will not be necessary when using the new notification pipeline's {@link NotifCollection}. diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java index 74e0f4002026..c2439df6624a 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java @@ -48,7 +48,6 @@ import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import com.android.systemui.plugins.ClockPlugin; import com.android.systemui.plugins.statusbar.StatusBarStateController; -import com.android.systemui.shared.system.smartspace.SmartspaceTransitionController; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController; import com.android.systemui.statusbar.phone.KeyguardBypassController; @@ -96,9 +95,8 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase { @Mock Resources mResources; - KeyguardUnlockAnimationController mKeyguardUnlockAnimationController; @Mock - SmartspaceTransitionController mSmartSpaceTransitionController; + KeyguardUnlockAnimationController mKeyguardUnlockAnimationController; @Mock private ClockPlugin mClockPlugin; @Mock @@ -154,7 +152,6 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase { mBypassController, mSmartspaceController, mKeyguardUnlockAnimationController, - mSmartSpaceTransitionController, mSecureSettings, mExecutor, mResources diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java index 80de24868181..217092e6e4e5 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java @@ -24,7 +24,6 @@ import android.testing.AndroidTestingRunner; import com.android.systemui.SysuiTestCase; import com.android.systemui.communal.CommunalStateController; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; -import com.android.systemui.shared.system.smartspace.SmartspaceTransitionController; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.ScreenOffAnimationController; import com.android.systemui.statusbar.policy.ConfigurationController; @@ -61,8 +60,6 @@ public class KeyguardStatusViewControllerTest extends SysuiTestCase { @Mock KeyguardUnlockAnimationController mKeyguardUnlockAnimationController; @Mock - SmartspaceTransitionController mSmartSpaceTransitionController; - @Mock ScreenOffAnimationController mScreenOffAnimationController; @Captor private ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardUpdateMonitorCallbackCaptor; @@ -83,7 +80,6 @@ public class KeyguardStatusViewControllerTest extends SysuiTestCase { mConfigurationController, mDozeParameters, mKeyguardUnlockAnimationController, - mSmartSpaceTransitionController, mScreenOffAnimationController); } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java index 7266e41ad7ca..08d881ff96aa 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -88,7 +88,6 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.biometrics.AuthController; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dump.DumpManager; -import com.android.systemui.flags.FeatureFlags; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.phone.KeyguardBypassController; @@ -108,6 +107,7 @@ import org.mockito.MockitoAnnotations; import org.mockito.MockitoSession; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicBoolean; @@ -174,10 +174,10 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { private InteractionJankMonitor mInteractionJankMonitor; @Mock private LatencyTracker mLatencyTracker; - @Mock - private FeatureFlags mFeatureFlags; @Captor private ArgumentCaptor<StatusBarStateController.StateListener> mStatusBarStateListenerCaptor; + @Mock + private KeyguardUpdateMonitorCallback mTestCallback; // Direct executor private Executor mBackgroundExecutor = Runnable::run; private Executor mMainExecutor = Runnable::run; @@ -255,11 +255,13 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { verify(mStatusBarStateController).addCallback(mStatusBarStateListenerCaptor.capture()); mStatusBarStateListener = mStatusBarStateListenerCaptor.getValue(); + mKeyguardUpdateMonitor.registerCallback(mTestCallback); } @After public void tearDown() { mMockitoSession.finishMocking(); + mKeyguardUpdateMonitor.removeCallback(mTestCallback); mKeyguardUpdateMonitor.destroy(); } @@ -599,7 +601,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { mTestableLooper.processAllMessages(); when(mKeyguardBypassController.canBypass()).thenReturn(true); mKeyguardUpdateMonitor.onTrustChanged(true /* enabled */, - KeyguardUpdateMonitor.getCurrentUser(), 0 /* flags */); + KeyguardUpdateMonitor.getCurrentUser(), 0 /* flags */, + new ArrayList<>()); mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true); verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean()); } @@ -609,7 +612,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { mKeyguardUpdateMonitor.dispatchStartedWakingUp(); mTestableLooper.processAllMessages(); mKeyguardUpdateMonitor.onTrustChanged(true /* enabled */, - KeyguardUpdateMonitor.getCurrentUser(), 0 /* flags */); + KeyguardUpdateMonitor.getCurrentUser(), 0 /* flags */, new ArrayList<>()); mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true); verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean()); @@ -754,7 +757,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @Test public void testGetUserCanSkipBouncer_whenTrust() { int user = KeyguardUpdateMonitor.getCurrentUser(); - mKeyguardUpdateMonitor.onTrustChanged(true /* enabled */, user, 0 /* flags */); + mKeyguardUpdateMonitor.onTrustChanged(true /* enabled */, user, 0 /* flags */, + new ArrayList<>()); assertThat(mKeyguardUpdateMonitor.getUserCanSkipBouncer(user)).isTrue(); } @@ -985,7 +989,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { // WHEN trust is enabled (ie: via smartlock) mKeyguardUpdateMonitor.onTrustChanged(true /* enabled */, - KeyguardUpdateMonitor.getCurrentUser(), 0 /* flags */); + KeyguardUpdateMonitor.getCurrentUser(), 0 /* flags */, new ArrayList<>()); // THEN we shouldn't listen for udfps assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(true)).isEqualTo(false); @@ -1069,6 +1073,17 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { anyBoolean()); } + @Test + public void testShowTrustGrantedMessage_onTrustGranted() { + // WHEN trust is enabled (ie: via some trust agent) with a trustGranted string + mKeyguardUpdateMonitor.onTrustChanged(true /* enabled */, + KeyguardUpdateMonitor.getCurrentUser(), 0 /* flags */, + Arrays.asList("Unlocked by wearable")); + + // THEN the showTrustGrantedMessage should be called with the first message + verify(mTestCallback).showTrustGrantedMessage("Unlocked by wearable"); + } + private void setKeyguardBouncerVisibility(boolean isVisible) { mKeyguardUpdateMonitor.sendKeyguardBouncerChanged(isVisible); mTestableLooper.processAllMessages(); @@ -1108,7 +1123,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { mRingerModeTracker, mBackgroundExecutor, mMainExecutor, mStatusBarStateController, mLockPatternUtils, mAuthController, mTelephonyListenerManager, - mInteractionJankMonitor, mLatencyTracker, mFeatureFlags); + mInteractionJankMonitor, mLatencyTracker); setStrongAuthTracker(KeyguardUpdateMonitorTest.this.mStrongAuthTracker); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java index 6ddfbb2f430f..bc89da7d504c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java @@ -111,6 +111,7 @@ public class MagnificationModeSwitchTest extends SysuiTestCase { @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); + mContext = Mockito.spy(getContext()); final WindowManager wm = mContext.getSystemService(WindowManager.class); mSwitchListener = new SwitchListenerStub(); mWindowManager = spy(new TestableWindowManager(wm)); @@ -139,16 +140,18 @@ public class MagnificationModeSwitchTest extends SysuiTestCase { public void tearDown() { mFadeOutAnimation = null; mMotionEventHelper.recycleEvents(); + mMagnificationModeSwitch.removeButton(); } @Test - public void removeButton_buttonIsShowing_removeView() { + public void removeButton_buttonIsShowing_removeViewAndUnregisterComponentCallbacks() { mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW); mMagnificationModeSwitch.removeButton(); verify(mWindowManager).removeView(mSpyImageView); verify(mViewPropertyAnimator).cancel(); + verify(mContext).unregisterComponentCallbacks(mMagnificationModeSwitch); } @Test @@ -464,6 +467,13 @@ public class MagnificationModeSwitchTest extends SysuiTestCase { } @Test + public void showButton_registerComponentCallbacks() { + mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW); + + verify(mContext).registerComponentCallbacks(mMagnificationModeSwitch); + } + + @Test public void onLocaleChanged_buttonIsShowing_updateA11yWindowTitle() { final String newA11yWindowTitle = "new a11y window title"; mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN); diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/ModeSwitchesControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/ModeSwitchesControllerTest.java index 216f63fce885..a56218b08224 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/ModeSwitchesControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/ModeSwitchesControllerTest.java @@ -91,7 +91,6 @@ public class ModeSwitchesControllerTest extends SysuiTestCase { verify(mModeSwitch).onConfigurationChanged(ActivityInfo.CONFIG_DENSITY); } - @Test public void testOnSwitchClick_showWindowModeButton_invokeListener() { mModeSwitchesController.showButton(Display.DEFAULT_DISPLAY, diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java index 5ad651728c66..1dd5e227a909 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java @@ -16,6 +16,8 @@ package com.android.systemui.accessibility; +import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; +import static android.content.res.Configuration.ORIENTATION_PORTRAIT; import static android.view.Choreographer.FrameCallback; import static android.view.WindowInsets.Type.systemGestures; import static android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; @@ -32,7 +34,6 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.atLeastOnce; @@ -42,9 +43,11 @@ import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.animation.ValueAnimator; import android.app.Instrumentation; import android.content.Context; import android.content.pm.ActivityInfo; +import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Insets; import android.graphics.PointF; @@ -52,6 +55,7 @@ import android.graphics.Rect; import android.os.Handler; import android.os.SystemClock; import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; import android.testing.TestableResources; import android.text.TextUtils; import android.view.Display; @@ -71,6 +75,7 @@ import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.model.SysUiState; import com.android.systemui.util.leak.ReferenceTestUtils; +import com.android.systemui.utils.os.FakeHandler; import org.junit.After; import org.junit.Before; @@ -85,13 +90,12 @@ import org.mockito.MockitoAnnotations; import java.util.List; @LargeTest +@TestableLooper.RunWithLooper @RunWith(AndroidTestingRunner.class) public class WindowMagnificationControllerTest extends SysuiTestCase { private static final int LAYOUT_CHANGE_TIMEOUT_MS = 5000; @Mock - private Handler mHandler; - @Mock private SfVsyncFrameCallbackProvider mSfVsyncFrameProvider; @Mock private MirrorWindowControl mMirrorWindowControl; @@ -99,17 +103,21 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { private WindowMagnifierCallback mWindowMagnifierCallback; @Mock(answer = Answers.RETURNS_DEEP_STUBS) private SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction(); + + private Handler mHandler; private TestableWindowManager mWindowManager; private SysUiState mSysUiState = new SysUiState(); private Resources mResources; private WindowMagnificationAnimationController mWindowMagnificationAnimationController; private WindowMagnificationController mWindowMagnificationController; private Instrumentation mInstrumentation; + private final ValueAnimator mValueAnimator = ValueAnimator.ofFloat(0, 1.0f).setDuration(0); @Before public void setUp() { MockitoAnnotations.initMocks(this); mContext = Mockito.spy(getContext()); + mHandler = new FakeHandler(TestableLooper.get(this).getLooper()); mInstrumentation = InstrumentationRegistry.getInstrumentation(); final WindowManager wm = mContext.getSystemService(WindowManager.class); mWindowManager = spy(new TestableWindowManager(wm)); @@ -121,17 +129,11 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { return null; }).when(mSfVsyncFrameProvider).postFrameCallback( any(FrameCallback.class)); - doAnswer(invocation -> { - final Runnable runnable = invocation.getArgument(0); - runnable.run(); - return null; - }).when(mHandler).post( - any(Runnable.class)); mSysUiState.addCallback(Mockito.mock(SysUiState.SysUiStateCallback.class)); mResources = getContext().getOrCreateTestableResources().getResources(); mWindowMagnificationAnimationController = new WindowMagnificationAnimationController( - mContext); + mContext, mValueAnimator); mWindowMagnificationController = new WindowMagnificationController(mContext, mHandler, mWindowMagnificationAnimationController, mSfVsyncFrameProvider, mMirrorWindowControl, mTransaction, mWindowMagnifierCallback, mSysUiState); @@ -144,6 +146,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { public void tearDown() { mInstrumentation.runOnMainSync( () -> mWindowMagnificationController.deleteWindowMagnification()); + mValueAnimator.cancel(); } @Test @@ -223,13 +226,9 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { final int screenSize = mContext.getResources().getDimensionPixelSize( R.dimen.magnification_max_frame_size) * 10; mWindowManager.setWindowBounds(new Rect(0, 0, screenSize, screenSize)); - //We need to initialize new one because the window size is determined when initialization. - final WindowMagnificationController controller = new WindowMagnificationController(mContext, - mHandler, mWindowMagnificationAnimationController, mSfVsyncFrameProvider, - mMirrorWindowControl, mTransaction, mWindowMagnifierCallback, mSysUiState); mInstrumentation.runOnMainSync(() -> { - controller.enableWindowMagnificationInternal(Float.NaN, Float.NaN, + mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN, Float.NaN); }); @@ -242,17 +241,17 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { } @Test - public void deleteWindowMagnification_destroyControl() { - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN, - Float.NaN); - }); + public void deleteWindowMagnification_destroyControlAndUnregisterComponentCallback() { + mInstrumentation.runOnMainSync( + () -> mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, + Float.NaN, + Float.NaN)); - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.deleteWindowMagnification(); - }); + mInstrumentation.runOnMainSync( + () -> mWindowMagnificationController.deleteWindowMagnification()); verify(mMirrorWindowControl).destroyControl(); + verify(mContext).unregisterComponentCallbacks(mWindowMagnificationController); } @Test @@ -288,12 +287,6 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { @Test public void setScale_enabled_expectedValueAndUpdateStateDescription() { - doAnswer(invocation -> { - final Runnable runnable = invocation.getArgument(0); - runnable.run(); - return null; - }).when(mHandler).postDelayed(any(Runnable.class), anyLong()); - mInstrumentation.runOnMainSync( () -> mWindowMagnificationController.enableWindowMagnificationInternal(2.0f, Float.NaN, Float.NaN)); @@ -322,11 +315,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { @Test public void onOrientationChanged_enabled_updateDisplayRotationAndCenterStayAtSamePosition() { - final Display display = Mockito.spy(mContext.getDisplay()); - final int currentRotation = display.getRotation(); - final int newRotation = (currentRotation + 1) % 4; - when(display.getRotation()).thenReturn(newRotation); - when(mContext.getDisplay()).thenReturn(display); + final int newRotation = simulateRotateTheDevice(); final Rect windowBounds = new Rect(mWindowManager.getCurrentWindowMetrics().getBounds()); final float center = Math.min(windowBounds.exactCenterX(), windowBounds.exactCenterY()); final float displayWidth = windowBounds.width(); @@ -535,6 +524,30 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { } @Test + public void enableWindowMagnification_rotationIsChanged_updateRotationValue() { + final Configuration config = mContext.getResources().getConfiguration(); + config.orientation = config.orientation == ORIENTATION_LANDSCAPE ? ORIENTATION_PORTRAIT + : ORIENTATION_LANDSCAPE; + final int newRotation = simulateRotateTheDevice(); + + mInstrumentation.runOnMainSync( + () -> mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, + Float.NaN, Float.NaN)); + + assertEquals(newRotation, mWindowMagnificationController.mRotation); + } + + @Test + public void enableWindowMagnification_registerComponentCallback() { + mInstrumentation.runOnMainSync( + () -> mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, + Float.NaN, + Float.NaN)); + + verify(mContext).registerComponentCallbacks(mWindowMagnificationController); + } + + @Test public void onLocaleChanged_enabled_updateA11yWindowTitle() { final String newA11yWindowTitle = "new a11y window title"; mInstrumentation.runOnMainSync(() -> { @@ -610,4 +623,14 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { .build(); mWindowManager.setWindowInsets(testInsets); } + + @Surface.Rotation + private int simulateRotateTheDevice() { + final Display display = Mockito.spy(mContext.getDisplay()); + final int currentRotation = display.getRotation(); + final int newRotation = (currentRotation + 1) % 4; + when(display.getRotation()).thenReturn(newRotation); + when(mContext.getDisplay()).thenReturn(display); + return newRotation; + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java index 343658d31272..d3f30c508b8b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java @@ -30,7 +30,6 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.Context; -import android.content.res.Configuration; import android.graphics.Rect; import android.hardware.display.DisplayManager; import android.os.RemoteException; @@ -159,15 +158,6 @@ public class WindowMagnificationTest extends SysuiTestCase { } @Test - public void onConfigurationChanged_updateModeSwitches() { - final Configuration config = new Configuration(); - config.densityDpi = Configuration.DENSITY_DPI_ANY; - mWindowMagnification.onConfigurationChanged(config); - - verify(mModeSwitchesController).onConfigurationChanged(anyInt()); - } - - @Test public void overviewProxyIsConnected_noController_resetFlag() { mOverviewProxyListener.onConnectionChanged(true); diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java index 3e19cc436dca..cdffaecadd77 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java @@ -192,7 +192,7 @@ public class DozeScreenStateTest extends SysuiTestCase { public void test_holdsWakeLockWhenGoingToLowPowerDelayed() { // Transition to low power mode will be delayed to let // animations play at 60 fps. - when(mDozeParameters.shouldControlScreenOff()).thenReturn(true); + when(mDozeParameters.shouldDelayDisplayDozeTransition()).thenReturn(true); mHandlerFake.setMode(QUEUEING); mScreen.transitionTo(UNINITIALIZED, INITIALIZED); @@ -209,7 +209,7 @@ public class DozeScreenStateTest extends SysuiTestCase { public void test_releasesWakeLock_abortingLowPowerDelayed() { // Transition to low power mode will be delayed to let // animations play at 60 fps. - when(mDozeParameters.shouldControlScreenOff()).thenReturn(true); + when(mDozeParameters.shouldDelayDisplayDozeTransition()).thenReturn(true); mHandlerFake.setMode(QUEUEING); mScreen.transitionTo(UNINITIALIZED, INITIALIZED); diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/ComplicationProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/ComplicationProviderTest.java new file mode 100644 index 000000000000..ada7ddbdb287 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/ComplicationProviderTest.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.dreams; + +import static org.junit.Assert.assertEquals; + +import android.content.Context; +import android.testing.AndroidTestingRunner; + +import androidx.test.filters.SmallTest; + +import com.android.settingslib.dream.DreamBackend; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +public class ComplicationProviderTest { + private TestComplicationProvider mComplicationProvider; + + @Before + public void setup() { + mComplicationProvider = new TestComplicationProvider(); + } + + @Test + public void testConvertComplicationType() { + assertEquals(ComplicationProvider.COMPLICATION_TYPE_TIME, + mComplicationProvider.convertComplicationType(DreamBackend.COMPLICATION_TYPE_TIME)); + assertEquals(ComplicationProvider.COMPLICATION_TYPE_DATE, + mComplicationProvider.convertComplicationType(DreamBackend.COMPLICATION_TYPE_DATE)); + assertEquals(ComplicationProvider.COMPLICATION_TYPE_WEATHER, + mComplicationProvider.convertComplicationType( + DreamBackend.COMPLICATION_TYPE_WEATHER)); + assertEquals(ComplicationProvider.COMPLICATION_TYPE_AIR_QUALITY, + mComplicationProvider.convertComplicationType( + DreamBackend.COMPLICATION_TYPE_AIR_QUALITY)); + assertEquals(ComplicationProvider.COMPLICATION_TYPE_CAST_INFO, + mComplicationProvider.convertComplicationType( + DreamBackend.COMPLICATION_TYPE_CAST_INFO)); + } + + private static class TestComplicationProvider implements ComplicationProvider { + @Override + public void onCreateComplication(Context context, + ComplicationHost.CreationCallback creationCallback, + ComplicationHost.InteractionCallback interactionCallback) { + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/appwidgets/ComplicationPrimerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/appwidgets/ComplicationPrimerTest.java deleted file mode 100644 index adf110bb2494..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/appwidgets/ComplicationPrimerTest.java +++ /dev/null @@ -1,191 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.dreams.appwidgets; - -import static org.junit.Assert.assertEquals; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.content.ComponentName; -import android.content.res.Resources; -import android.testing.AndroidTestingRunner; -import android.view.Gravity; - -import androidx.constraintlayout.widget.ConstraintLayout; -import androidx.test.InstrumentationRegistry; -import androidx.test.filters.SmallTest; - -import com.android.systemui.R; -import com.android.systemui.SysuiTestCase; -import com.android.systemui.SysuiTestableContext; -import com.android.systemui.dreams.DreamOverlayStateController; -import com.android.systemui.dreams.appwidgets.dagger.AppWidgetComponent; -import com.android.systemui.utils.leaks.LeakCheckedTest; - -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -@SmallTest -@RunWith(AndroidTestingRunner.class) -public class ComplicationPrimerTest extends SysuiTestCase { - @Rule - public final LeakCheckedTest.SysuiLeakCheck mLeakCheck = new LeakCheckedTest.SysuiLeakCheck(); - - @Rule - public SysuiTestableContext mContext = new SysuiTestableContext( - InstrumentationRegistry.getContext(), mLeakCheck); - - @Mock - Resources mResources; - - @Mock - AppWidgetComponent mAppWidgetComplicationComponent1; - @Mock - AppWidgetComponent mAppWidgetComplicationComponent2; - - @Mock - ComplicationProvider mComplicationProvider1; - - @Mock - ComplicationProvider mComplicationProvider2; - - final ComponentName mAppComplicationComponent1 = - ComponentName.unflattenFromString("com.foo.bar/.Baz"); - final ComponentName mAppComplicationComponent2 = - ComponentName.unflattenFromString("com.foo.bar/.Baz2"); - - final int mAppComplicationGravity1 = Gravity.BOTTOM | Gravity.START; - final int mAppComplicationGravity2 = Gravity.BOTTOM | Gravity.END; - - final String[] mComponents = new String[]{mAppComplicationComponent1.flattenToString(), - mAppComplicationComponent2.flattenToString() }; - final int[] mPositions = new int[]{mAppComplicationGravity1, mAppComplicationGravity2}; - - @Mock - DreamOverlayStateController mDreamOverlayStateController; - - @Mock - AppWidgetComponent.Factory mAppWidgetComplicationProviderFactory; - - @Before - public void setup() { - MockitoAnnotations.initMocks(this); - when(mAppWidgetComplicationProviderFactory.build(eq(mAppComplicationComponent1), any())) - .thenReturn(mAppWidgetComplicationComponent1); - when(mAppWidgetComplicationComponent1.getAppWidgetComplicationProvider()) - .thenReturn(mComplicationProvider1); - when(mAppWidgetComplicationProviderFactory.build(eq(mAppComplicationComponent2), any())) - .thenReturn(mAppWidgetComplicationComponent2); - when(mAppWidgetComplicationComponent2.getAppWidgetComplicationProvider()) - .thenReturn(mComplicationProvider2); - when(mResources.getIntArray(R.array.config_dreamComplicationPositions)) - .thenReturn(mPositions); - when(mResources.getStringArray(R.array.config_dreamAppWidgetComplications)) - .thenReturn(mComponents); - } - - @Test - public void testLoading() { - final ComplicationPrimer primer = new ComplicationPrimer(mContext, - mResources, - mDreamOverlayStateController, - mAppWidgetComplicationProviderFactory); - - // Inform primer to begin. - primer.onBootCompleted(); - - // Verify the first component is added to the state controller with the proper position. - { - final ArgumentCaptor<ConstraintLayout.LayoutParams> layoutParamsArgumentCaptor = - ArgumentCaptor.forClass(ConstraintLayout.LayoutParams.class); - verify(mAppWidgetComplicationProviderFactory, times(1)) - .build(eq(mAppComplicationComponent1), - layoutParamsArgumentCaptor.capture()); - - assertEquals(layoutParamsArgumentCaptor.getValue().startToStart, - ConstraintLayout.LayoutParams.PARENT_ID); - assertEquals(layoutParamsArgumentCaptor.getValue().bottomToBottom, - ConstraintLayout.LayoutParams.PARENT_ID); - - verify(mDreamOverlayStateController, times(1)) - .addComplication(eq(mComplicationProvider1)); - } - - // Verify the second component is added to the state controller with the proper position. - { - final ArgumentCaptor<ConstraintLayout.LayoutParams> layoutParamsArgumentCaptor = - ArgumentCaptor.forClass(ConstraintLayout.LayoutParams.class); - verify(mAppWidgetComplicationProviderFactory, times(1)) - .build(eq(mAppComplicationComponent2), - layoutParamsArgumentCaptor.capture()); - - assertEquals(layoutParamsArgumentCaptor.getValue().endToEnd, - ConstraintLayout.LayoutParams.PARENT_ID); - assertEquals(layoutParamsArgumentCaptor.getValue().bottomToBottom, - ConstraintLayout.LayoutParams.PARENT_ID); - verify(mDreamOverlayStateController, times(1)) - .addComplication(eq(mComplicationProvider1)); - } - } - - @Test - public void testNoComponents() { - when(mResources.getStringArray(R.array.config_dreamAppWidgetComplications)) - .thenReturn(new String[]{}); - - final ComplicationPrimer primer = new ComplicationPrimer(mContext, - mResources, - mDreamOverlayStateController, - mAppWidgetComplicationProviderFactory); - - // Inform primer to begin. - primer.onBootCompleted(); - - - // Make sure there is no request to add a widget if no components are specified by the - // product. - verify(mAppWidgetComplicationProviderFactory, never()).build(any(), any()); - verify(mDreamOverlayStateController, never()).addComplication(any()); - } - - @Test - public void testNoPositions() { - when(mResources.getIntArray(R.array.config_dreamComplicationPositions)) - .thenReturn(new int[]{}); - - final ComplicationPrimer primer = new ComplicationPrimer(mContext, - mResources, - mDreamOverlayStateController, - mAppWidgetComplicationProviderFactory); - - primer.onBootCompleted(); - - // Make sure there is no request to add a widget if no positions are specified by the - // product. - verify(mAppWidgetComplicationProviderFactory, never()).build(any(), any()); - verify(mDreamOverlayStateController, never()).addComplication(any()); - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/appwidgets/ComplicationProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/appwidgets/ComplicationProviderTest.java deleted file mode 100644 index f538112edbda..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/appwidgets/ComplicationProviderTest.java +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.dreams.appwidgets; - -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.isNull; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.app.PendingIntent; -import android.appwidget.AppWidgetHostView; -import android.content.ComponentName; -import android.testing.AndroidTestingRunner; -import android.widget.RemoteViews; - -import androidx.test.InstrumentationRegistry; -import androidx.test.filters.SmallTest; - -import com.android.systemui.SysuiTestCase; -import com.android.systemui.SysuiTestableContext; -import com.android.systemui.dreams.ComplicationHost; -import com.android.systemui.dreams.ComplicationHostView; -import com.android.systemui.plugins.ActivityStarter; -import com.android.systemui.utils.leaks.LeakCheckedTest; - -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -@SmallTest -@RunWith(AndroidTestingRunner.class) -public class ComplicationProviderTest extends SysuiTestCase { - @Mock - ActivityStarter mActivityStarter; - - @Mock - ComponentName mComponentName; - - @Mock - AppWidgetProvider mAppWidgetProvider; - - @Mock - AppWidgetHostView mAppWidgetHostView; - - @Mock - ComplicationHost.CreationCallback mCreationCallback; - - @Mock - ComplicationHost.InteractionCallback mInteractionCallback; - - @Mock - PendingIntent mPendingIntent; - - @Mock - RemoteViews.RemoteResponse mRemoteResponse; - - ComplicationProvider mComplicationProvider; - - RemoteViews.InteractionHandler mInteractionHandler; - - @Rule - public final LeakCheckedTest.SysuiLeakCheck mLeakCheck = new LeakCheckedTest.SysuiLeakCheck(); - - @Rule - public SysuiTestableContext mContext = new SysuiTestableContext( - InstrumentationRegistry.getContext(), mLeakCheck); - - ComplicationHostView.LayoutParams mLayoutParams = new ComplicationHostView.LayoutParams( - ComplicationHostView.LayoutParams.MATCH_PARENT, - ComplicationHostView.LayoutParams.MATCH_PARENT); - - @Before - public void setup() { - MockitoAnnotations.initMocks(this); - when(mPendingIntent.isActivity()).thenReturn(true); - when(mAppWidgetProvider.getWidget(mComponentName)).thenReturn(mAppWidgetHostView); - - mComplicationProvider = new ComplicationProvider( - mActivityStarter, - mComponentName, - mAppWidgetProvider, - mLayoutParams - ); - - final ArgumentCaptor<RemoteViews.InteractionHandler> creationCallbackCapture = - ArgumentCaptor.forClass(RemoteViews.InteractionHandler.class); - - mComplicationProvider.onCreateComplication(mContext, mCreationCallback, - mInteractionCallback); - verify(mAppWidgetHostView, times(1)) - .setInteractionHandler(creationCallbackCapture.capture()); - mInteractionHandler = creationCallbackCapture.getValue(); - } - - @Test - public void testWidgetBringup() { - // Make sure widget was requested. - verify(mAppWidgetProvider, times(1)).getWidget(eq(mComponentName)); - - // Make sure widget was returned to callback. - verify(mCreationCallback, times(1)).onCreated(eq(mAppWidgetHostView), - eq(mLayoutParams)); - } - - @Test - public void testWidgetInteraction() { - // Trigger interaction. - mInteractionHandler.onInteraction(mAppWidgetHostView, mPendingIntent, - mRemoteResponse); - - // Ensure activity is started. - verify(mActivityStarter, times(1)) - .startPendingIntentDismissingKeyguard(eq(mPendingIntent), isNull(), - eq(mAppWidgetHostView)); - // Verify exit is requested. - verify(mInteractionCallback, times(1)).onExit(); - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt index f3043e934c8a..fb1a968acceb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt @@ -14,7 +14,6 @@ import androidx.test.filters.SmallTest import com.android.keyguard.KeyguardViewController import com.android.systemui.SysuiTestCase import com.android.systemui.flags.FeatureFlags -import com.android.systemui.shared.system.smartspace.SmartspaceTransitionController import com.android.systemui.statusbar.phone.BiometricUnlockController import com.android.systemui.statusbar.policy.KeyguardStateController import junit.framework.Assert.assertEquals @@ -44,8 +43,6 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { @Mock private lateinit var keyguardViewController: KeyguardViewController @Mock - private lateinit var smartspaceTransitionController: SmartspaceTransitionController - @Mock private lateinit var featureFlags: FeatureFlags @Mock private lateinit var biometricUnlockController: BiometricUnlockController @@ -59,7 +56,7 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { MockitoAnnotations.initMocks(this) keyguardUnlockAnimationController = KeyguardUnlockAnimationController( context, keyguardStateController, { keyguardViewMediator }, keyguardViewController, - smartspaceTransitionController, featureFlags, biometricUnlockController + featureFlags, { biometricUnlockController } ) `when`(keyguardViewController.viewRootImpl).thenReturn(mock(ViewRootImpl::class.java)) @@ -87,7 +84,7 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { fun noSurfaceAnimation_ifWakeAndUnlocking() { `when`(biometricUnlockController.isWakeAndUnlock).thenReturn(true) - keyguardUnlockAnimationController.notifyStartKeyguardExitAnimation( + keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation( remoteAnimationTarget, 0 /* startTime */, false /* requestedShowSurfaceBehindKeyguard */ @@ -118,15 +115,12 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { fun surfaceAnimation_ifNotWakeAndUnlocking() { `when`(biometricUnlockController.isWakeAndUnlock).thenReturn(false) - keyguardUnlockAnimationController.notifyStartKeyguardExitAnimation( + keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation( remoteAnimationTarget, 0 /* startTime */, false /* requestedShowSurfaceBehindKeyguard */ ) - // Make sure the animator was started. - assertTrue(keyguardUnlockAnimationController.surfaceBehindEntryAnimator.isRunning) - // Since the animation is running, we should not have finished the remote animation. verify(keyguardViewMediator, times(0)).onKeyguardExitRemoteAnimationFinished( false /* cancelled */) diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt index 4839bdea1b70..a1ec38f630ed 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt @@ -21,16 +21,14 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.media.taptotransfer.receiver.ChipStateReceiver import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerReceiver -import com.android.systemui.media.taptotransfer.sender.* +import com.android.systemui.media.taptotransfer.sender.MediaTttSenderService import com.android.systemui.shared.mediattt.DeviceInfo -import com.android.systemui.shared.mediattt.IDeviceSenderCallback +import com.android.systemui.shared.mediattt.IDeviceSenderService import com.android.systemui.statusbar.commandline.Command import com.android.systemui.statusbar.commandline.CommandRegistry -import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.capture -import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test @@ -53,11 +51,9 @@ class MediaTttCommandLineHelperTest : SysuiTestCase() { private lateinit var mediaTttCommandLineHelper: MediaTttCommandLineHelper @Mock - private lateinit var mediaTttChipControllerSender: MediaTttChipControllerSender - @Mock private lateinit var mediaTttChipControllerReceiver: MediaTttChipControllerReceiver @Mock - private lateinit var mediaSenderService: IDeviceSenderCallback.Stub + private lateinit var mediaSenderService: IDeviceSenderService.Stub private lateinit var mediaSenderServiceComponentName: ComponentName @Before @@ -73,28 +69,15 @@ class MediaTttCommandLineHelperTest : SysuiTestCase() { MediaTttCommandLineHelper( commandRegistry, context, - mediaTttChipControllerSender, mediaTttChipControllerReceiver, - FakeExecutor(FakeSystemClock()) ) } @Test(expected = IllegalStateException::class) - fun constructor_addSenderCommandAlreadyRegistered() { - // Since creating the chip controller should automatically register the add command, it + fun constructor_senderCommandAlreadyRegistered() { + // Since creating the chip controller should automatically register the sender command, it // should throw when registering it again. - commandRegistry.registerCommand( - ADD_CHIP_COMMAND_SENDER_TAG - ) { EmptyCommand() } - } - - @Test(expected = IllegalStateException::class) - fun constructor_removeSenderCommandAlreadyRegistered() { - // Since creating the chip controller should automatically register the remove command, it - // should throw when registering it again. - commandRegistry.registerCommand( - REMOVE_CHIP_COMMAND_SENDER_TAG - ) { EmptyCommand() } + commandRegistry.registerCommand(SENDER_COMMAND) { EmptyCommand() } } @Test(expected = IllegalStateException::class) @@ -127,24 +110,74 @@ class MediaTttCommandLineHelperTest : SysuiTestCase() { } @Test - fun sender_transferInitiated_chipDisplayWithCorrectState() { - commandRegistry.onShellCommand(pw, getTransferInitiatedCommand()) + fun sender_moveCloserToEndCast_serviceCallbackCalled() { + commandRegistry.onShellCommand(pw, getMoveCloserToEndCastCommand()) + + assertThat(context.isBound(mediaSenderServiceComponentName)).isTrue() - verify(mediaTttChipControllerSender).displayChip(any(TransferInitiated::class.java)) + val deviceInfoCaptor = argumentCaptor<DeviceInfo>() + verify(mediaSenderService).closeToReceiverToEndCast(any(), capture(deviceInfoCaptor)) + assertThat(deviceInfoCaptor.value!!.name).isEqualTo(DEVICE_NAME) } @Test - fun sender_transferSucceeded_chipDisplayWithCorrectState() { - commandRegistry.onShellCommand(pw, getTransferSucceededCommand()) + fun sender_transferToReceiverTriggered_chipDisplayWithCorrectState() { + commandRegistry.onShellCommand(pw, getTransferToReceiverTriggeredCommand()) + + assertThat(context.isBound(mediaSenderServiceComponentName)).isTrue() - verify(mediaTttChipControllerSender).displayChip(any(TransferSucceeded::class.java)) + val deviceInfoCaptor = argumentCaptor<DeviceInfo>() + verify(mediaSenderService).transferToReceiverTriggered(any(), capture(deviceInfoCaptor)) + assertThat(deviceInfoCaptor.value!!.name).isEqualTo(DEVICE_NAME) + } + + @Test + fun sender_transferToThisDeviceTriggered_chipDisplayWithCorrectState() { + commandRegistry.onShellCommand(pw, getTransferToThisDeviceTriggeredCommand()) + + assertThat(context.isBound(mediaSenderServiceComponentName)).isTrue() + verify(mediaSenderService).transferToThisDeviceTriggered(any(), any()) } @Test - fun sender_removeCommand_chipRemoved() { - commandRegistry.onShellCommand(pw, arrayOf(REMOVE_CHIP_COMMAND_SENDER_TAG)) + fun sender_transferToReceiverSucceeded_chipDisplayWithCorrectState() { + commandRegistry.onShellCommand(pw, getTransferToReceiverSucceededCommand()) + + assertThat(context.isBound(mediaSenderServiceComponentName)).isTrue() - verify(mediaTttChipControllerSender).removeChip() + val deviceInfoCaptor = argumentCaptor<DeviceInfo>() + verify(mediaSenderService) + .transferToReceiverSucceeded(any(), capture(deviceInfoCaptor), any()) + assertThat(deviceInfoCaptor.value!!.name).isEqualTo(DEVICE_NAME) + } + + @Test + fun sender_transferToThisDeviceSucceeded_chipDisplayWithCorrectState() { + commandRegistry.onShellCommand(pw, getTransferToThisDeviceSucceededCommand()) + + assertThat(context.isBound(mediaSenderServiceComponentName)).isTrue() + + val deviceInfoCaptor = argumentCaptor<DeviceInfo>() + verify(mediaSenderService) + .transferToThisDeviceSucceeded(any(), capture(deviceInfoCaptor), any()) + assertThat(deviceInfoCaptor.value!!.name).isEqualTo(DEVICE_NAME) + } + + @Test + fun sender_transferFailed_serviceCallbackCalled() { + commandRegistry.onShellCommand(pw, getTransferFailedCommand()) + + assertThat(context.isBound(mediaSenderServiceComponentName)).isTrue() + verify(mediaSenderService).transferFailed(any(), any()) + } + + @Test + fun sender_noLongerCloseToReceiver_serviceCallbackCalledAndServiceUnbound() { + commandRegistry.onShellCommand(pw, getNoLongerCloseToReceiverCommand()) + + // Once we're no longer close to the receiver, we should unbind the service. + assertThat(context.isBound(mediaSenderServiceComponentName)).isFalse() + verify(mediaSenderService).noLongerCloseToReceiver(any(), any()) } @Test @@ -163,23 +196,58 @@ class MediaTttCommandLineHelperTest : SysuiTestCase() { private fun getMoveCloserToStartCastCommand(): Array<String> = arrayOf( - ADD_CHIP_COMMAND_SENDER_TAG, + SENDER_COMMAND, DEVICE_NAME, MOVE_CLOSER_TO_START_CAST_COMMAND_NAME ) - private fun getTransferInitiatedCommand(): Array<String> = + private fun getMoveCloserToEndCastCommand(): Array<String> = + arrayOf( + SENDER_COMMAND, + DEVICE_NAME, + MOVE_CLOSER_TO_END_CAST_COMMAND_NAME + ) + + private fun getTransferToReceiverTriggeredCommand(): Array<String> = + arrayOf( + SENDER_COMMAND, + DEVICE_NAME, + TRANSFER_TO_RECEIVER_TRIGGERED_COMMAND_NAME + ) + + private fun getTransferToThisDeviceTriggeredCommand(): Array<String> = + arrayOf( + SENDER_COMMAND, + DEVICE_NAME, + TRANSFER_TO_THIS_DEVICE_TRIGGERED_COMMAND_NAME + ) + + private fun getTransferToReceiverSucceededCommand(): Array<String> = + arrayOf( + SENDER_COMMAND, + DEVICE_NAME, + TRANSFER_TO_RECEIVER_SUCCEEDED_COMMAND_NAME + ) + + private fun getTransferToThisDeviceSucceededCommand(): Array<String> = + arrayOf( + SENDER_COMMAND, + DEVICE_NAME, + TRANSFER_TO_THIS_DEVICE_SUCCEEDED_COMMAND_NAME + ) + + private fun getTransferFailedCommand(): Array<String> = arrayOf( - ADD_CHIP_COMMAND_SENDER_TAG, + SENDER_COMMAND, DEVICE_NAME, - TRANSFER_INITIATED_COMMAND_NAME + TRANSFER_FAILED_COMMAND_NAME ) - private fun getTransferSucceededCommand(): Array<String> = + private fun getNoLongerCloseToReceiverCommand(): Array<String> = arrayOf( - ADD_CHIP_COMMAND_SENDER_TAG, + SENDER_COMMAND, DEVICE_NAME, - TRANSFER_SUCCEEDED_COMMAND_NAME + NO_LONGER_CLOSE_TO_RECEIVER_COMMAND_NAME ) class EmptyCommand : Command { diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt index ecc4c46634b9..509ae337abb8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt @@ -26,26 +26,19 @@ import android.widget.TextView import androidx.test.filters.SmallTest import com.android.systemui.R import com.android.systemui.SysuiTestCase -import com.android.systemui.util.concurrency.FakeExecutor +import com.android.systemui.shared.mediattt.IUndoTransferCallback import com.android.systemui.util.mockito.any -import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat -import com.google.common.util.concurrent.SettableFuture import org.junit.Before import org.junit.Test import org.mockito.ArgumentCaptor import org.mockito.Mock import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations -import java.util.concurrent.Future @SmallTest class MediaTttChipControllerSenderTest : SysuiTestCase() { private lateinit var appIconDrawable: Drawable - private lateinit var fakeMainClock: FakeSystemClock - private lateinit var fakeMainExecutor: FakeExecutor - private lateinit var fakeBackgroundClock: FakeSystemClock - private lateinit var fakeBackgroundExecutor: FakeExecutor private lateinit var controllerSender: MediaTttChipControllerSender @@ -56,124 +49,153 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { fun setUp() { MockitoAnnotations.initMocks(this) appIconDrawable = Icon.createWithResource(context, R.drawable.ic_cake).loadDrawable(context) - fakeMainClock = FakeSystemClock() - fakeMainExecutor = FakeExecutor(fakeMainClock) - fakeBackgroundClock = FakeSystemClock() - fakeBackgroundExecutor = FakeExecutor(fakeBackgroundClock) - controllerSender = MediaTttChipControllerSender( - context, windowManager, fakeMainExecutor, fakeBackgroundExecutor - ) + controllerSender = MediaTttChipControllerSender(context, windowManager) } @Test - fun moveCloserToStartCast_appIcon_chipTextContainsDeviceName_noLoadingIcon_noUndo() { - controllerSender.displayChip(moveCloserToStartCast()) + fun moveCloserToStartCast_appIcon_deviceName_noLoadingIcon_noUndo_noFailureIcon() { + val state = moveCloserToStartCast() + controllerSender.displayChip(state) val chipView = getChipView() assertThat(chipView.getAppIconView().drawable).isEqualTo(appIconDrawable) assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_ICON_CONTENT_DESC) - assertThat(chipView.getChipText()).contains(DEVICE_NAME) + assertThat(chipView.getChipText()).isEqualTo(state.getChipTextString(context)) assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE) assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE) + assertThat(chipView.getFailureIcon().visibility).isEqualTo(View.GONE) } @Test - fun transferInitiated_futureNotResolvedYet_appIcon_loadingIcon_noUndo() { - val future: SettableFuture<Runnable?> = SettableFuture.create() - controllerSender.displayChip(transferInitiated(future)) + fun moveCloserToEndCast_appIcon_deviceName_noLoadingIcon_noUndo_noFailureIcon() { + val state = moveCloserToEndCast() + controllerSender.displayChip(state) - // Don't resolve the future in any way and don't run our executors + val chipView = getChipView() + assertThat(chipView.getAppIconView().drawable).isEqualTo(appIconDrawable) + assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_ICON_CONTENT_DESC) + assertThat(chipView.getChipText()).isEqualTo(state.getChipTextString(context)) + assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE) + assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE) + assertThat(chipView.getFailureIcon().visibility).isEqualTo(View.GONE) + } + + @Test + fun transferToReceiverTriggered_appIcon_loadingIcon_noUndo_noFailureIcon() { + val state = transferToReceiverTriggered() + controllerSender.displayChip(state) - // Assert we're still in the loading state val chipView = getChipView() assertThat(chipView.getAppIconView().drawable).isEqualTo(appIconDrawable) assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_ICON_CONTENT_DESC) - assertThat(chipView.getChipText()).contains(DEVICE_NAME) + assertThat(chipView.getChipText()).isEqualTo(state.getChipTextString(context)) assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.VISIBLE) assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE) + assertThat(chipView.getFailureIcon().visibility).isEqualTo(View.GONE) } @Test - fun transferInitiated_futureResolvedSuccessfully_switchesToTransferSucceeded() { - val future: SettableFuture<Runnable?> = SettableFuture.create() - val undoRunnable = Runnable { } + fun transferToThisDeviceTriggered_appIcon_loadingIcon_noUndo_noFailureIcon() { + val state = transferToThisDeviceTriggered() + controllerSender.displayChip(state) - controllerSender.displayChip(transferInitiated(future)) + val chipView = getChipView() + assertThat(chipView.getAppIconView().drawable).isEqualTo(appIconDrawable) + assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_ICON_CONTENT_DESC) + assertThat(chipView.getChipText()).isEqualTo(state.getChipTextString(context)) + assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.VISIBLE) + assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE) + assertThat(chipView.getFailureIcon().visibility).isEqualTo(View.GONE) + } - future.set(undoRunnable) - fakeBackgroundExecutor.advanceClockToLast() - fakeBackgroundExecutor.runAllReady() - fakeMainExecutor.advanceClockToLast() - val numRun = fakeMainExecutor.runAllReady() + @Test + fun transferToReceiverSucceeded_appIcon_deviceName_noLoadingIcon_noFailureIcon() { + val state = transferToReceiverSucceeded() + controllerSender.displayChip(state) - // Assert we ran the future callback - assertThat(numRun).isEqualTo(1) - // Assert that we've moved to the successful state val chipView = getChipView() - assertThat(chipView.getChipText()).contains(DEVICE_NAME) + assertThat(chipView.getAppIconView().drawable).isEqualTo(appIconDrawable) + assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_ICON_CONTENT_DESC) + assertThat(chipView.getChipText()).isEqualTo(state.getChipTextString(context)) assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE) - assertThat(chipView.getUndoButton().visibility).isEqualTo(View.VISIBLE) + assertThat(chipView.getFailureIcon().visibility).isEqualTo(View.GONE) } @Test - fun transferInitiated_futureCancelled_chipRemoved() { - val future: SettableFuture<Runnable?> = SettableFuture.create() + fun transferToReceiverSucceeded_nullUndoRunnable_noUndo() { + controllerSender.displayChip(transferToReceiverSucceeded(undoCallback = null)) - controllerSender.displayChip(transferInitiated(future)) + val chipView = getChipView() + assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE) + } - future.cancel(true) - fakeBackgroundExecutor.advanceClockToLast() - fakeBackgroundExecutor.runAllReady() - fakeMainExecutor.advanceClockToLast() - val numRun = fakeMainExecutor.runAllReady() + @Test + fun transferToReceiverSucceeded_withUndoRunnable_undoWithClick() { + val undoCallback = object : IUndoTransferCallback.Stub() { + override fun onUndoTriggered() {} + } + controllerSender.displayChip(transferToReceiverSucceeded(undoCallback)) - // Assert we ran the future callback - assertThat(numRun).isEqualTo(1) - // Assert that we've hidden the chip - verify(windowManager).removeView(any()) + val chipView = getChipView() + assertThat(chipView.getUndoButton().visibility).isEqualTo(View.VISIBLE) + assertThat(chipView.getUndoButton().hasOnClickListeners()).isTrue() } @Test - fun transferInitiated_futureNotResolvedAfterTimeout_chipRemoved() { - val future: SettableFuture<Runnable?> = SettableFuture.create() - controllerSender.displayChip(transferInitiated(future)) - - // We won't set anything on the future, but we will still run the executors so that we're - // waiting on the future resolving. If we have a bug in our code, then this test will time - // out because we're waiting on the future indefinitely. - fakeBackgroundExecutor.advanceClockToLast() - fakeBackgroundExecutor.runAllReady() - fakeMainExecutor.advanceClockToLast() - val numRun = fakeMainExecutor.runAllReady() - - // Assert we eventually decide to not wait for the future anymore - assertThat(numRun).isEqualTo(1) - // Assert we've hidden the chip - verify(windowManager).removeView(any()) + fun transferToReceiverSucceeded_withUndoRunnable_undoButtonClickRunsRunnable() { + var undoCallbackCalled = false + val undoCallback = object : IUndoTransferCallback.Stub() { + override fun onUndoTriggered() { + undoCallbackCalled = true + } + } + + controllerSender.displayChip(transferToReceiverSucceeded(undoCallback)) + getChipView().getUndoButton().performClick() + + assertThat(undoCallbackCalled).isTrue() } @Test - fun transferSucceeded_appIcon_chipTextContainsDeviceName_noLoadingIcon() { - controllerSender.displayChip(transferSucceeded()) + fun transferToReceiverSucceeded_undoButtonClick_switchesToTransferToThisDeviceTriggered() { + val undoCallback = object : IUndoTransferCallback.Stub() { + override fun onUndoTriggered() {} + } + controllerSender.displayChip(transferToReceiverSucceeded(undoCallback)) + + getChipView().getUndoButton().performClick() + + assertThat(getChipView().getChipText()) + .isEqualTo(transferToThisDeviceTriggered().getChipTextString(context)) + } + + @Test + fun transferToThisDeviceSucceeded_appIcon_deviceName_noLoadingIcon_noFailureIcon() { + val state = transferToThisDeviceSucceeded() + controllerSender.displayChip(state) val chipView = getChipView() assertThat(chipView.getAppIconView().drawable).isEqualTo(appIconDrawable) assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_ICON_CONTENT_DESC) - assertThat(chipView.getChipText()).contains(DEVICE_NAME) + assertThat(chipView.getChipText()).isEqualTo(state.getChipTextString(context)) assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE) + assertThat(chipView.getFailureIcon().visibility).isEqualTo(View.GONE) } @Test - fun transferSucceededNullUndoRunnable_noUndo() { - controllerSender.displayChip(transferSucceeded(undoRunnable = null)) + fun transferToThisDeviceSucceeded_nullUndoRunnable_noUndo() { + controllerSender.displayChip(transferToThisDeviceSucceeded(undoCallback = null)) val chipView = getChipView() assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE) } @Test - fun transferSucceededWithUndoRunnable_undoWithClick() { - controllerSender.displayChip(transferSucceeded { }) + fun transferToThisDeviceSucceeded_withUndoRunnable_undoWithClick() { + val undoCallback = object : IUndoTransferCallback.Stub() { + override fun onUndoTriggered() {} + } + controllerSender.displayChip(transferToThisDeviceSucceeded(undoCallback)) val chipView = getChipView() assertThat(chipView.getUndoButton().visibility).isEqualTo(View.VISIBLE) @@ -181,48 +203,93 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { } @Test - fun transferSucceededWithUndoRunnable_undoButtonClickRunsRunnable() { - var runnableRun = false - val runnable = Runnable { runnableRun = true } + fun transferToThisDeviceSucceeded_withUndoRunnable_undoButtonClickRunsRunnable() { + var undoCallbackCalled = false + val undoCallback = object : IUndoTransferCallback.Stub() { + override fun onUndoTriggered() { + undoCallbackCalled = true + } + } + + controllerSender.displayChip(transferToThisDeviceSucceeded(undoCallback)) + getChipView().getUndoButton().performClick() + + assertThat(undoCallbackCalled).isTrue() + } + + @Test + fun transferToThisDeviceSucceeded_undoButtonClick_switchesToTransferToReceiverTriggered() { + val undoCallback = object : IUndoTransferCallback.Stub() { + override fun onUndoTriggered() {} + } + controllerSender.displayChip(transferToThisDeviceSucceeded(undoCallback)) - controllerSender.displayChip(transferSucceeded(undoRunnable = runnable)) getChipView().getUndoButton().performClick() - assertThat(runnableRun).isTrue() + assertThat(getChipView().getChipText()) + .isEqualTo(transferToReceiverTriggered().getChipTextString(context)) + } + + @Test + fun transferFailed_appIcon_noDeviceName_noLoadingIcon_noUndo_failureIcon() { + val state = transferFailed() + controllerSender.displayChip(state) + + val chipView = getChipView() + assertThat(chipView.getAppIconView().drawable).isEqualTo(appIconDrawable) + assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_ICON_CONTENT_DESC) + assertThat(chipView.getChipText()).isEqualTo(state.getChipTextString(context)) + assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE) + assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE) + assertThat(chipView.getFailureIcon().visibility).isEqualTo(View.VISIBLE) } @Test - fun changeFromCloserToStartToTransferInitiated_loadingIconAppears() { + fun changeFromCloserToStartToTransferTriggered_loadingIconAppears() { controllerSender.displayChip(moveCloserToStartCast()) - controllerSender.displayChip(transferInitiated()) + controllerSender.displayChip(transferToReceiverTriggered()) assertThat(getChipView().getLoadingIconVisibility()).isEqualTo(View.VISIBLE) } @Test - fun changeFromTransferInitiatedToTransferSucceeded_loadingIconDisappears() { - controllerSender.displayChip(transferInitiated()) - controllerSender.displayChip(transferSucceeded()) + fun changeFromTransferTriggeredToTransferSucceeded_loadingIconDisappears() { + controllerSender.displayChip(transferToReceiverTriggered()) + controllerSender.displayChip(transferToReceiverSucceeded()) assertThat(getChipView().getLoadingIconVisibility()).isEqualTo(View.GONE) } @Test - fun changeFromTransferInitiatedToTransferSucceeded_undoButtonAppears() { - controllerSender.displayChip(transferInitiated()) - controllerSender.displayChip(transferSucceeded { }) + fun changeFromTransferTriggeredToTransferSucceeded_undoButtonAppears() { + controllerSender.displayChip(transferToReceiverTriggered()) + controllerSender.displayChip( + transferToReceiverSucceeded( + object : IUndoTransferCallback.Stub() { + override fun onUndoTriggered() {} + } + ) + ) assertThat(getChipView().getUndoButton().visibility).isEqualTo(View.VISIBLE) } @Test fun changeFromTransferSucceededToMoveCloserToStart_undoButtonDisappears() { - controllerSender.displayChip(transferSucceeded()) + controllerSender.displayChip(transferToReceiverSucceeded()) controllerSender.displayChip(moveCloserToStartCast()) assertThat(getChipView().getUndoButton().visibility).isEqualTo(View.GONE) } + @Test + fun changeFromTransferTriggeredToTransferFailed_failureIconAppears() { + controllerSender.displayChip(transferToReceiverTriggered()) + controllerSender.displayChip(transferFailed()) + + assertThat(getChipView().getFailureIcon().visibility).isEqualTo(View.VISIBLE) + } + private fun LinearLayout.getAppIconView() = this.requireViewById<ImageView>(R.id.app_icon) private fun LinearLayout.getChipText(): String = @@ -233,6 +300,8 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { private fun LinearLayout.getUndoButton(): View = this.requireViewById(R.id.undo) + private fun LinearLayout.getFailureIcon(): View = this.requireViewById(R.id.failure_icon) + private fun getChipView(): LinearLayout { val viewCaptor = ArgumentCaptor.forClass(View::class.java) verify(windowManager).addView(viewCaptor.capture(), any()) @@ -244,18 +313,32 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { MoveCloserToStartCast(appIconDrawable, APP_ICON_CONTENT_DESC, DEVICE_NAME) /** Helper method providing default parameters to not clutter up the tests. */ - private fun transferInitiated( - future: Future<Runnable?> = TEST_FUTURE - ) = TransferInitiated(appIconDrawable, APP_ICON_CONTENT_DESC, DEVICE_NAME, future) + private fun moveCloserToEndCast() = + MoveCloserToEndCast(appIconDrawable, APP_ICON_CONTENT_DESC, DEVICE_NAME) + + /** Helper method providing default parameters to not clutter up the tests. */ + private fun transferToReceiverTriggered() = + TransferToReceiverTriggered(appIconDrawable, APP_ICON_CONTENT_DESC, DEVICE_NAME) + + /** Helper method providing default parameters to not clutter up the tests. */ + private fun transferToThisDeviceTriggered() = + TransferToThisDeviceTriggered(appIconDrawable, APP_ICON_CONTENT_DESC) + + /** Helper method providing default parameters to not clutter up the tests. */ + private fun transferToReceiverSucceeded(undoCallback: IUndoTransferCallback? = null) = + TransferToReceiverSucceeded( + appIconDrawable, APP_ICON_CONTENT_DESC, DEVICE_NAME, undoCallback + ) + + /** Helper method providing default parameters to not clutter up the tests. */ + private fun transferToThisDeviceSucceeded(undoCallback: IUndoTransferCallback? = null) = + TransferToThisDeviceSucceeded( + appIconDrawable, APP_ICON_CONTENT_DESC, DEVICE_NAME, undoCallback + ) /** Helper method providing default parameters to not clutter up the tests. */ - private fun transferSucceeded( - undoRunnable: Runnable? = null - ) = TransferSucceeded(appIconDrawable, APP_ICON_CONTENT_DESC, DEVICE_NAME, undoRunnable) + private fun transferFailed() = TransferFailed(appIconDrawable, APP_ICON_CONTENT_DESC) } private const val DEVICE_NAME = "My Tablet" private const val APP_ICON_CONTENT_DESC = "Content description" -// Use a settable future that hasn't yet been set so that we don't immediately switch to the success -// state. -private val TEST_FUTURE: SettableFuture<Runnable?> = SettableFuture.create() diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderServiceTest.kt index 8f64698a5a6c..11b727ec507c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderServiceTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderServiceTest.kt @@ -4,7 +4,9 @@ import android.media.MediaRoute2Info import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.shared.mediattt.DeviceInfo -import com.android.systemui.shared.mediattt.IDeviceSenderCallback +import com.android.systemui.shared.mediattt.IDeviceSenderService +import com.android.systemui.shared.mediattt.IUndoTransferCallback +import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.capture import com.google.common.truth.Truth.assertThat @@ -17,8 +19,7 @@ import org.mockito.MockitoAnnotations @SmallTest class MediaTttSenderServiceTest : SysuiTestCase() { - private lateinit var service: MediaTttSenderService - private lateinit var callback: IDeviceSenderCallback + private lateinit var service: IDeviceSenderService @Mock private lateinit var controller: MediaTttChipControllerSender @@ -30,19 +31,94 @@ class MediaTttSenderServiceTest : SysuiTestCase() { @Before fun setUp() { MockitoAnnotations.initMocks(this) - service = MediaTttSenderService(context, controller) - callback = IDeviceSenderCallback.Stub.asInterface(service.onBind(null)) + val mediaTttSenderService = MediaTttSenderService(context, controller) + service = IDeviceSenderService.Stub.asInterface(mediaTttSenderService.onBind(null)) } @Test - fun closeToReceiverToStartCast_controllerTriggeredWithMoveCloserToStartCastState() { + fun closeToReceiverToStartCast_controllerTriggeredWithCorrectState() { val name = "Fake name" - callback.closeToReceiverToStartCast(mediaInfo, DeviceInfo(name)) + service.closeToReceiverToStartCast(mediaInfo, DeviceInfo(name)) val chipStateCaptor = argumentCaptor<MoveCloserToStartCast>() verify(controller).displayChip(capture(chipStateCaptor)) val chipState = chipStateCaptor.value!! - assertThat(chipState.otherDeviceName).isEqualTo(name) + assertThat(chipState.getChipTextString(context)).contains(name) + } + + @Test + fun closeToReceiverToEndCast_controllerTriggeredWithCorrectState() { + val name = "Fake name" + service.closeToReceiverToEndCast(mediaInfo, DeviceInfo(name)) + + val chipStateCaptor = argumentCaptor<MoveCloserToEndCast>() + verify(controller).displayChip(capture(chipStateCaptor)) + + val chipState = chipStateCaptor.value!! + assertThat(chipState.getChipTextString(context)).contains(name) + } + + @Test + fun transferToThisDeviceTriggered_controllerTriggeredWithCorrectState() { + service.transferToThisDeviceTriggered(mediaInfo, DeviceInfo("Fake name")) + + verify(controller).displayChip(any<TransferToThisDeviceTriggered>()) + } + + @Test + fun transferToReceiverTriggered_controllerTriggeredWithCorrectState() { + val name = "Fake name" + service.transferToReceiverTriggered(mediaInfo, DeviceInfo(name)) + + val chipStateCaptor = argumentCaptor<TransferToReceiverTriggered>() + verify(controller).displayChip(capture(chipStateCaptor)) + + val chipState = chipStateCaptor.value!! + assertThat(chipState.getChipTextString(context)).contains(name) + } + + @Test + fun transferToReceiverSucceeded_controllerTriggeredWithCorrectState() { + val name = "Fake name" + val undoCallback = object : IUndoTransferCallback.Stub() { + override fun onUndoTriggered() {} + } + service.transferToReceiverSucceeded(mediaInfo, DeviceInfo(name), undoCallback) + + val chipStateCaptor = argumentCaptor<TransferToReceiverSucceeded>() + verify(controller).displayChip(capture(chipStateCaptor)) + + val chipState = chipStateCaptor.value!! + assertThat(chipState.getChipTextString(context)).contains(name) + assertThat(chipState.undoCallback).isEqualTo(undoCallback) + } + + @Test + fun transferToThisDeviceSucceeded_controllerTriggeredWithCorrectState() { + val undoCallback = object : IUndoTransferCallback.Stub() { + override fun onUndoTriggered() {} + } + service.transferToThisDeviceSucceeded(mediaInfo, DeviceInfo("name"), undoCallback) + + val chipStateCaptor = argumentCaptor<TransferToThisDeviceSucceeded>() + verify(controller).displayChip(capture(chipStateCaptor)) + + val chipState = chipStateCaptor.value!! + assertThat(chipState.undoCallback).isEqualTo(undoCallback) + } + + @Test + fun transferFailed_controllerTriggeredWithTransferFailedState() { + service.transferFailed(mediaInfo, DeviceInfo("Fake name")) + + verify(controller).displayChip(any<TransferFailed>()) + } + + @Test + fun noLongerCloseToReceiver_controllerRemoveChipTriggered() { + service.noLongerCloseToReceiver(mediaInfo, DeviceInfo("Fake name")) + + verify(controller).removeChip() } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java index 3e8e8748a679..73d2b0bf1a0f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java @@ -47,6 +47,7 @@ import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.phone.AutoHideController; import com.android.systemui.statusbar.phone.LightBarController; import com.android.systemui.statusbar.policy.ConfigurationController; +import com.android.wm.shell.back.BackAnimation; import com.android.wm.shell.pip.Pip; import org.junit.After; @@ -92,7 +93,8 @@ public class NavigationBarControllerTest extends SysuiTestCase { mock(DumpManager.class), mock(AutoHideController.class), mock(LightBarController.class), - Optional.of(mock(Pip.class)))); + Optional.of(mock(Pip.class)), + Optional.of(mock(BackAnimation.class)))); initializeNavigationBars(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java index 5003013358be..9ca898b9dea9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java @@ -95,6 +95,7 @@ import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.utils.leaks.LeakCheckedTest; +import com.android.wm.shell.back.BackAnimation; import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; import com.android.wm.shell.pip.Pip; @@ -105,7 +106,6 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import org.mockito.Spy; import java.util.Optional; @@ -383,7 +383,8 @@ public class NavigationBarTest extends SysuiTestCase { mAutoHideController, mAutoHideControllerFactory, Optional.of(mTelecomManager), - mInputMethodManager); + mInputMethodManager, + Optional.of(mock(BackAnimation.class))); return spy(factory.create(context)); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerControllerTest.java index 03a0da7d91fb..4a6bbbcf1d6b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerControllerTest.java @@ -24,6 +24,7 @@ import static com.android.systemui.qrcodescanner.controller.QRCodeScannerControl import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -73,7 +74,7 @@ public class QRCodeScannerControllerTest extends SysuiTestCase { private DeviceConfigProxyFake mProxyFake; private void setUpLocal(String deviceConfigActivity, String defaultActivity, - boolean validateActivity, boolean enableSetting) { + boolean validateActivity, boolean enableSetting, boolean enableOnLockScreen) { MockitoAnnotations.initMocks(this); int enableSettingInt = enableSetting ? 1 : 0; @@ -91,6 +92,8 @@ public class QRCodeScannerControllerTest extends SysuiTestCase { when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA)).thenReturn(true); mContext.getOrCreateTestableResources().addOverride(R.string.def_qr_code_component, defaultActivity); + mContext.getOrCreateTestableResources().addOverride( + android.R.bool.config_enableQrCodeScannerOnLockScreen, enableOnLockScreen); mProxyFake = new DeviceConfigProxyFake(); mProxyFake.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, @@ -126,7 +129,8 @@ public class QRCodeScannerControllerTest extends SysuiTestCase { @Test public void qrCodeScannerInit_withoutDefaultValue() { setUpLocal(/* deviceConfigActivity */ null, /* defaultActivity */ - "", /* validateActivity */ true, /* enableSetting */true); + "", /* validateActivity */ true, /* enableSetting */ true, + /* enableOnLockScreen */ true); verifyActivityDetails(null); assertThat(mController.isEnabledForLockScreenButton()).isFalse(); assertThat(mController.isEnabledForQuickSettings()).isFalse(); @@ -135,7 +139,8 @@ public class QRCodeScannerControllerTest extends SysuiTestCase { @Test public void qrCodeScannerInit_withIncorrectDefaultValue() { setUpLocal(/* deviceConfigActivity */ null, /* defaultActivity */ - "abc/.def", /* validateActivity */ false, /* enableSetting */ true); + "abc/.def", /* validateActivity */ false, /* enableSetting */ true, + /* enableOnLockScreen */ true); verifyActivityDetails(null); assertThat(mController.isEnabledForLockScreenButton()).isFalse(); } @@ -143,7 +148,8 @@ public class QRCodeScannerControllerTest extends SysuiTestCase { @Test public void qrCodeScannerInit_withCorrectDefaultValue() { setUpLocal(/* deviceConfigActivity */ null, /* defaultActivity */ - "abc/.def", /* validateActivity */ true, /* enableSetting */true); + "abc/.def", /* validateActivity */ true, /* enableSetting */true, + /* enableOnLockScreen */ true); verifyActivityDetails("abc/.def"); assertThat(mController.isEnabledForLockScreenButton()).isTrue(); assertThat(mController.isEnabledForQuickSettings()).isTrue(); @@ -152,7 +158,8 @@ public class QRCodeScannerControllerTest extends SysuiTestCase { @Test public void qrCodeScannerInit_withCorrectDeviceConfig() { setUpLocal(/* deviceConfigActivity */ "abc/.def", /* defaultActivity */ - "", /* validateActivity */ true, /* enableSetting */true); + "", /* validateActivity */ true, /* enableSetting */true, + /* enableOnLockScreen */ true); verifyActivityDetails("abc/.def"); assertThat(mController.isEnabledForLockScreenButton()).isTrue(); assertThat(mController.isEnabledForQuickSettings()).isTrue(); @@ -161,7 +168,8 @@ public class QRCodeScannerControllerTest extends SysuiTestCase { @Test public void qrCodeScannerInit_withCorrectDeviceConfig_withCorrectDefaultValue() { setUpLocal(/* deviceConfigActivity */ "abc/.def", /* defaultActivity */ - "xyz/.qrs", /* validateActivity */true, /* enableSetting */ true); + "xyz/.qrs", /* validateActivity */true, /* enableSetting */ true, + /* enableOnLockScreen */ true); verifyActivityDetails("abc/.def"); assertThat(mController.isEnabledForLockScreenButton()).isTrue(); assertThat(mController.isEnabledForQuickSettings()).isTrue(); @@ -170,7 +178,8 @@ public class QRCodeScannerControllerTest extends SysuiTestCase { @Test public void qrCodeScannerInit_withCorrectDeviceConfig_fullActivity() { setUpLocal(/* deviceConfigActivity */ "abc/abc.def", /* defaultActivity */ - "", /* validateActivity */ true, /* enableSetting */ true); + "", /* validateActivity */ true, /* enableSetting */ true, + /* enableOnLockScreen */ true); verifyActivityDetails("abc/abc.def"); assertThat(mController.isEnabledForLockScreenButton()).isTrue(); assertThat(mController.isEnabledForQuickSettings()).isTrue(); @@ -179,7 +188,8 @@ public class QRCodeScannerControllerTest extends SysuiTestCase { @Test public void qrCodeScannerInit_withIncorrectDeviceConfig() { setUpLocal(/* deviceConfigActivity */ "def/.efg", /* defaultActivity */ - "", /* validateActivity */ false, /* enableSetting */ true); + "", /* validateActivity */ false, /* enableSetting */ true, + /* enableOnLockScreen */ true); verifyActivityDetails(null); assertThat(mController.isEnabledForLockScreenButton()).isFalse(); assertThat(mController.isEnabledForQuickSettings()).isFalse(); @@ -188,7 +198,8 @@ public class QRCodeScannerControllerTest extends SysuiTestCase { @Test public void verifyDeviceConfigChange_withDefaultActivity() { setUpLocal(/* deviceConfigActivity */ null, /* defaultActivity */ - "abc/.def", /* validateActivity */ true, /* enableSetting */true); + "abc/.def", /* validateActivity */ true, /* enableSetting */true, + /* enableOnLockScreen */ true); verifyActivityDetails("abc/.def"); assertThat(mController.isEnabledForLockScreenButton()).isTrue(); assertThat(mController.isEnabledForQuickSettings()).isTrue(); @@ -214,7 +225,8 @@ public class QRCodeScannerControllerTest extends SysuiTestCase { @Test public void verifyDeviceConfigChange_withoutDefaultActivity() { setUpLocal(/* deviceConfigActivity */ null, /* defaultActivity */ - "", /* validateActivity */ true, /* enableSetting */ true); + "", /* validateActivity */ true, /* enableSetting */ true, + /* enableOnLockScreen */ true); verifyActivityDetails(null); assertThat(mController.isEnabledForLockScreenButton()).isFalse(); assertThat(mController.isEnabledForQuickSettings()).isFalse(); @@ -239,7 +251,8 @@ public class QRCodeScannerControllerTest extends SysuiTestCase { @Test public void verifyDeviceConfigChangeToSameValue() { setUpLocal(/* deviceConfigActivity */ null, /* defaultActivity */ - "", /* validateActivity */ true, /* enableSetting */true); + "", /* validateActivity */ true, /* enableSetting */true, + /* enableOnLockScreen */ true); mProxyFake.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, SystemUiDeviceConfigFlags.DEFAULT_QR_CODE_SCANNER, @@ -261,7 +274,8 @@ public class QRCodeScannerControllerTest extends SysuiTestCase { @Test public void verifyPreferenceChange() { setUpLocal(/* deviceConfigActivity */ null, /* defaultActivity */ - "abc/.def", /* validateActivity */ true, /* enableSetting */true); + "abc/.def", /* validateActivity */ true, /* enableSetting */true, + /* enableOnLockScreen */ true); mSecureSettings.putStringForUser(LOCK_SCREEN_SHOW_QR_CODE_SCANNER, "0", UserHandle.USER_CURRENT); mSecureSettings.putStringForUser(LOCK_SCREEN_SHOW_QR_CODE_SCANNER, "0", @@ -278,7 +292,8 @@ public class QRCodeScannerControllerTest extends SysuiTestCase { @Test public void verifyPreferenceChangeToSameValue() { setUpLocal(/* deviceConfigActivity */ null, /* defaultActivity */ - "abc/.def", /* validateActivity */ true, /* enableSetting */true); + "abc/.def", /* validateActivity */ true, /* enableSetting */true, + /* enableOnLockScreen */ true); verifyActivityDetails("abc/.def"); assertThat(mController.isEnabledForLockScreenButton()).isTrue(); assertThat(mController.isEnabledForQuickSettings()).isTrue(); @@ -301,7 +316,8 @@ public class QRCodeScannerControllerTest extends SysuiTestCase { @Test public void verifyUnregisterRegisterChangeObservers() { setUpLocal(/* deviceConfigActivity */ null, /* defaultActivity */ - "abc/.def", /* validateActivity */ true, /* enableSetting */true); + "abc/.def", /* validateActivity */ true, /* enableSetting */true, + /* enableOnLockScreen */ true); verifyActivityDetails("abc/.def"); assertThat(mController.isEnabledForLockScreenButton()).isTrue(); assertThat(mController.isEnabledForQuickSettings()).isTrue(); @@ -312,7 +328,7 @@ public class QRCodeScannerControllerTest extends SysuiTestCase { assertThat(mController.isEnabledForLockScreenButton()).isFalse(); assertThat(mController.isEnabledForQuickSettings()).isFalse(); - // Unregister once again and make sure, it affect affect the next register event + // Unregister once again and make sure it affects the next register event mController.unregisterQRCodeScannerChangeObservers(DEFAULT_QR_CODE_SCANNER_CHANGE, QR_CODE_SCANNER_PREFERENCE_CHANGE); mController.registerQRCodeScannerChangeObservers(DEFAULT_QR_CODE_SCANNER_CHANGE, @@ -321,4 +337,15 @@ public class QRCodeScannerControllerTest extends SysuiTestCase { assertThat(mController.isEnabledForLockScreenButton()).isTrue(); assertThat(mController.isEnabledForQuickSettings()).isTrue(); } + + @Test + public void verifyDisableLockscreenButton() { + setUpLocal(/* deviceConfigActivity */ null, /* defaultActivity */ + "abc/.def", /* validateActivity */ true, /* enableSetting */true, + /* enableOnLockScreen */ false); + assertThat(mController.getIntent()).isNotNull(); + assertThat(mController.isEnabledForLockScreenButton()).isFalse(); + assertThat(mController.isEnabledForQuickSettings()).isTrue(); + assertThat(getSettingsQRCodeDefaultComponent()).isNull(); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt index 26f04fc4e7b5..354bb5192251 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt @@ -54,8 +54,6 @@ class FooterActionsControllerTest : LeakCheckedTest() { @Mock private lateinit var userInfoController: UserInfoController @Mock - private lateinit var qsPanelController: QSPanelController - @Mock private lateinit var multiUserSwitchController: MultiUserSwitchController @Mock private lateinit var globalActionsDialog: GlobalActionsDialogLite @@ -81,7 +79,7 @@ class FooterActionsControllerTest : LeakCheckedTest() { view = LayoutInflater.from(context) .inflate(R.layout.footer_actions, null) as FooterActionsView - controller = FooterActionsController(view, qsPanelController, activityStarter, + controller = FooterActionsController(view, activityStarter, userManager, userTracker, userInfoController, multiUserSwitchController, deviceProvisionedController, falsingManager, metricsLogger, fakeTunerService, globalActionsDialog, uiEventLogger, showPMLiteButton = true, diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterViewControllerTest.java index 8b19c50f915e..f43e68f3e575 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterViewControllerTest.java @@ -18,7 +18,11 @@ package com.android.systemui.qs; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; import android.content.ClipData; @@ -31,6 +35,8 @@ import android.widget.TextView; import androidx.test.filters.SmallTest; import com.android.systemui.R; +import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.plugins.FalsingManager; import com.android.systemui.settings.UserTracker; import com.android.systemui.utils.leaks.LeakCheckedTest; @@ -60,13 +66,20 @@ public class QSFooterViewControllerTest extends LeakCheckedTest { private TextView mBuildText; @Mock private FooterActionsController mFooterActionsController; + @Mock + private FalsingManager mFalsingManager; + @Mock + private ActivityStarter mActivityStarter; private QSFooterViewController mController; + private View mEditButton; @Before public void setup() throws Exception { MockitoAnnotations.initMocks(this); + mEditButton = new View(mContext); + injectLeakCheckedDependencies(ALL_SUPPORTED_CLASSES); mContext.addMockSystemService(ClipboardManager.class, mClipboardManager); @@ -77,9 +90,11 @@ public class QSFooterViewControllerTest extends LeakCheckedTest { when(mView.isAttachedToWindow()).thenReturn(true); when(mView.findViewById(R.id.build)).thenReturn(mBuildText); + when(mView.findViewById(android.R.id.edit)).thenReturn(mEditButton); - mController = new QSFooterViewController(mView, mUserTracker, mQSPanelController, - mQuickQSPanelController, mFooterActionsController); + mController = new QSFooterViewController(mView, mUserTracker, mFalsingManager, + mActivityStarter, mQSPanelController, mQuickQSPanelController, + mFooterActionsController); mController.init(); } @@ -99,4 +114,27 @@ public class QSFooterViewControllerTest extends LeakCheckedTest { verify(mClipboardManager).setPrimaryClip(captor.capture()); assertThat(captor.getValue().getItemAt(0).getText()).isEqualTo(text); } + + @Test + public void testEditButton_falseTap() { + when(mFalsingManager.isFalseTap(anyInt())).thenReturn(true); + + mEditButton.performClick(); + + verify(mQSPanelController, never()).showEdit(any()); + verifyZeroInteractions(mActivityStarter); + } + + @Test + public void testEditButton_realTap() { + when(mFalsingManager.isFalseTap(anyInt())).thenReturn(false); + + mEditButton.performClick(); + + ArgumentCaptor<Runnable> captor = ArgumentCaptor.forClass(Runnable.class); + + verify(mActivityStarter).postQSRunnableDismissingKeyguard(captor.capture()); + captor.getValue().run(); + verify(mQSPanelController).showEdit(mEditButton); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt index 0bce621c3b02..91e5d33d9c9e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt @@ -302,6 +302,40 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() { } @Test + fun ignoreBlurForUnlock_ignores() { + notificationShadeDepthController.onPanelExpansionChanged( + rawFraction = 1f, expanded = true, tracking = false + ) + `when`(shadeAnimation.radius).thenReturn(maxBlur.toFloat()) + + notificationShadeDepthController.blursDisabledForAppLaunch = false + notificationShadeDepthController.blursDisabledForUnlock = true + + notificationShadeDepthController.updateBlurCallback.doFrame(0) + + // Since we are ignoring blurs for unlock, we should be applying blur = 0 despite setting it + // to maxBlur above. + verify(blurUtils).applyBlur(any(), eq(0), eq(false)) + } + + @Test + fun ignoreBlurForUnlock_doesNotIgnore() { + notificationShadeDepthController.onPanelExpansionChanged( + rawFraction = 1f, expanded = true, tracking = false + ) + `when`(shadeAnimation.radius).thenReturn(maxBlur.toFloat()) + + notificationShadeDepthController.blursDisabledForAppLaunch = false + notificationShadeDepthController.blursDisabledForUnlock = false + + notificationShadeDepthController.updateBlurCallback.doFrame(0) + + // Since we are not ignoring blurs for unlock (or app launch), we should apply the blur we + // returned above (maxBlur). + verify(blurUtils).applyBlur(any(), eq(maxBlur), eq(false)) + } + + @Test fun brightnessMirrorVisible_whenVisible() { notificationShadeDepthController.brightnessMirrorVisible = true verify(brightnessSpring).animateTo(eq(maxBlur), any()) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt index 1961ab269267..188baaf682b0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt @@ -561,6 +561,10 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { override fun setMediaTarget(target: SmartspaceTarget?) { } + + override fun getSelectedPage(): Int { + return -1 + } }) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java index dc83c0d08291..f2b7bf515c45 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.notification; import static android.app.NotificationManager.IMPORTANCE_DEFAULT; +import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_UPDATED; import static android.service.notification.NotificationListenerService.REASON_CANCEL; import static com.android.systemui.statusbar.notification.NotificationEntryManager.UNDEFINED_DISMISS_REASON; @@ -428,6 +429,18 @@ public class NotificationEntryManagerTest extends SysuiTestCase { } @Test + public void testNotifyChannelModified_notifiesListeners() { + NotificationChannel channel = mock(NotificationChannel.class); + String pkg = "PKG"; + mEntryManager.notifyChannelModified(pkg, UserHandle.CURRENT, channel, + NOTIFICATION_CHANNEL_OR_GROUP_UPDATED); + verify(mNotifCollectionListener).onNotificationChannelModified(eq(pkg), + eq(UserHandle.CURRENT), eq(channel), eq(NOTIFICATION_CHANNEL_OR_GROUP_UPDATED)); + verify(mEntryListener).onNotificationChannelModified(eq(pkg), + eq(UserHandle.CURRENT), eq(channel), eq(NOTIFICATION_CHANNEL_OR_GROUP_UPDATED)); + } + + @Test public void testLifetimeExtenders_ifNotificationIsRetainedItIsntRemoved() { // GIVEN an entry manager with a notification mEntryManager.addActiveNotificationForTest(mEntry); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java index f08180dd3b9b..706800940fd1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java @@ -19,6 +19,7 @@ package com.android.systemui.statusbar.notification.collection; import static android.app.Notification.FLAG_FOREGROUND_SERVICE; import static android.app.Notification.FLAG_NO_CLEAR; import static android.app.Notification.FLAG_ONGOING_EVENT; +import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_UPDATED; import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL; import static android.service.notification.NotificationListenerService.REASON_CANCEL; import static android.service.notification.NotificationListenerService.REASON_CLICK; @@ -53,6 +54,8 @@ import static java.util.Objects.requireNonNull; import android.annotation.Nullable; import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationManager; import android.os.Handler; import android.os.RemoteException; import android.service.notification.NotificationListenerService.Ranking; @@ -336,6 +339,37 @@ public class NotifCollectionTest extends SysuiTestCase { } @Test + public void testEventDispatchedWhenChannelChanged() { + // GIVEN a collection with one notif that has a channel + NotificationEntryBuilder neb = buildNotif(TEST_PACKAGE, 48); + NotificationChannel channel = new NotificationChannel( + "channelId", + "channelName", + NotificationManager.IMPORTANCE_DEFAULT); + neb.setChannel(channel); + + NotifEvent notif = mNoMan.postNotif(neb); + NotificationEntry entry = mCollectionListener.getEntry(notif.key); + clearInvocations(mCollectionListener); + + + // WHEN a notif channel is modified + channel.setAllowBubbles(true); + mNoMan.issueChannelModification( + TEST_PACKAGE, + entry.getSbn().getUser(), + channel, + NOTIFICATION_CHANNEL_OR_GROUP_UPDATED); + + // THEN the listener is notified + mListenerInOrder.verify(mCollectionListener).onNotificationChannelModified( + TEST_PACKAGE, + entry.getSbn().getUser(), + channel, + NOTIFICATION_CHANNEL_OR_GROUP_UPDATED); + } + + @Test public void testRankingsAreUpdatedForOtherNotifs() { // GIVEN a collection with one notif NotifEvent notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 3) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java index b832577c16ac..25dd23a955e6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java @@ -1968,7 +1968,7 @@ public class ShadeListBuilderTest extends SysuiTestCase { } @Override - public int compare(ListEntry o1, ListEntry o2) { + public int compare(@NonNull ListEntry o1, @NonNull ListEntry o2) { boolean contains1 = mPreferredPackages.contains( o1.getRepresentativeEntry().getSbn().getPackageName()); boolean contains2 = mPreferredPackages.contains( diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinatorTest.java index f2e7081e096b..bc32759a9938 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinatorTest.java @@ -28,6 +28,10 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.app.Notification; +import android.app.PendingIntent; +import android.app.Person; +import android.content.Intent; import android.graphics.Color; import android.os.UserHandle; import android.service.notification.StatusBarNotification; @@ -151,4 +155,35 @@ public class AppOpsCoordinatorTest extends SysuiTestCase { // THEN the entry is NOT in the fgs section assertFalse(mFgsSection.isInSection(mEntryBuilder.build())); } + + @Test + public void testIncludeCallInSection_importanceDefault() { + // GIVEN the notification represents a call with > min importance + mEntryBuilder + .setImportance(IMPORTANCE_DEFAULT) + .modifyNotification(mContext) + .setStyle(makeCallStyle()); + + // THEN the entry is in the fgs section + assertTrue(mFgsSection.isInSection(mEntryBuilder.build())); + } + + @Test + public void testDiscludeCallInSection_importanceMin() { + // GIVEN the notification represents a call with min importance + mEntryBuilder + .setImportance(IMPORTANCE_MIN) + .modifyNotification(mContext) + .setStyle(makeCallStyle()); + + // THEN the entry is NOT in the fgs section + assertFalse(mFgsSection.isInSection(mEntryBuilder.build())); + } + + private Notification.CallStyle makeCallStyle() { + final PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0, + new Intent("action"), PendingIntent.FLAG_IMMUTABLE); + final Person person = new Person.Builder().setName("person").build(); + return Notification.CallStyle.forIncomingCall(person, pendingIntent, pendingIntent); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt index a46b44002812..8deac94214bd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt @@ -24,18 +24,24 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.notification.collection.NotifPipeline import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder +import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner import com.android.systemui.statusbar.notification.collection.render.NodeController import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier +import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_IMPORTANT_PERSON import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_PERSON +import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.withArgCaptor +import com.google.common.truth.Truth.assertThat import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock +import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations import org.mockito.Mockito.`when` as whenever @@ -47,12 +53,15 @@ class ConversationCoordinatorTest : SysuiTestCase() { // captured listeners and pluggables: private lateinit var promoter: NotifPromoter private lateinit var peopleSectioner: NotifSectioner + private lateinit var peopleComparator: NotifComparator @Mock private lateinit var pipeline: NotifPipeline @Mock private lateinit var peopleNotificationIdentifier: PeopleNotificationIdentifier @Mock private lateinit var channel: NotificationChannel @Mock private lateinit var headerController: NodeController private lateinit var entry: NotificationEntry + private lateinit var entryA: NotificationEntry + private lateinit var entryB: NotificationEntry private lateinit var coordinator: ConversationCoordinator @@ -70,8 +79,15 @@ class ConversationCoordinatorTest : SysuiTestCase() { } peopleSectioner = coordinator.sectioner + peopleComparator = coordinator.comparator entry = NotificationEntryBuilder().setChannel(channel).build() + + val section = NotifSection(peopleSectioner, 0) + entryA = NotificationEntryBuilder().setChannel(channel) + .setSection(section).setTag("A").build() + entryB = NotificationEntryBuilder().setChannel(channel) + .setSection(section).setTag("B").build() } @Test @@ -90,4 +106,36 @@ class ConversationCoordinatorTest : SysuiTestCase() { assertTrue(peopleSectioner.isInSection(entry)) assertFalse(peopleSectioner.isInSection(NotificationEntryBuilder().build())) } + + @Test + fun testComparatorIgnoresFromOtherSection() { + val e1 = NotificationEntryBuilder().setId(1).setChannel(channel).build() + val e2 = NotificationEntryBuilder().setId(2).setChannel(channel).build() + + // wrong section -- never classify + assertThat(peopleComparator.compare(e1, e2)).isEqualTo(0) + verify(peopleNotificationIdentifier, never()).getPeopleNotificationType(any()) + } + + @Test + fun testComparatorPutsImportantPeopleFirst() { + whenever(peopleNotificationIdentifier.getPeopleNotificationType(entryA)) + .thenReturn(TYPE_IMPORTANT_PERSON) + whenever(peopleNotificationIdentifier.getPeopleNotificationType(entryB)) + .thenReturn(TYPE_PERSON) + + // only put people notifications in this section + assertThat(peopleComparator.compare(entryA, entryB)).isEqualTo(-1) + } + + @Test + fun testComparatorEquatesPeopleWithSameType() { + whenever(peopleNotificationIdentifier.getPeopleNotificationType(entryA)) + .thenReturn(TYPE_PERSON) + whenever(peopleNotificationIdentifier.getPeopleNotificationType(entryB)) + .thenReturn(TYPE_PERSON) + + // only put people notifications in this section + assertThat(peopleComparator.compare(entryA, entryB)).isEqualTo(0) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.java deleted file mode 100644 index 8ee892c4be58..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.java +++ /dev/null @@ -1,274 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.statusbar.notification.collection.coordinator; - -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.testing.AndroidTestingRunner; -import android.testing.TestableLooper; - -import androidx.test.filters.SmallTest; - -import com.android.systemui.SysuiTestCase; -import com.android.systemui.statusbar.NotificationRemoteInputManager; -import com.android.systemui.statusbar.notification.collection.NotifPipeline; -import com.android.systemui.statusbar.notification.collection.NotificationEntry; -import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; -import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter; -import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner; -import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; -import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender; -import com.android.systemui.statusbar.notification.collection.render.NodeController; -import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder; -import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider; -import com.android.systemui.statusbar.notification.row.NotifBindPipeline.BindCallback; -import com.android.systemui.statusbar.policy.HeadsUpManager; -import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; -import com.android.systemui.util.concurrency.FakeExecutor; -import com.android.systemui.util.time.FakeSystemClock; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import java.util.ArrayList; - -@SmallTest -@RunWith(AndroidTestingRunner.class) -@TestableLooper.RunWithLooper -public class HeadsUpCoordinatorTest extends SysuiTestCase { - - private HeadsUpCoordinator mCoordinator; - - // captured listeners and pluggables: - private NotifCollectionListener mCollectionListener; - private NotifPromoter mNotifPromoter; - private NotifLifetimeExtender mNotifLifetimeExtender; - private OnHeadsUpChangedListener mOnHeadsUpChangedListener; - private NotifSectioner mNotifSectioner; - - @Mock private NotifPipeline mNotifPipeline; - @Mock private HeadsUpManager mHeadsUpManager; - @Mock private HeadsUpViewBinder mHeadsUpViewBinder; - @Mock private NotificationInterruptStateProvider mNotificationInterruptStateProvider; - @Mock private NotificationRemoteInputManager mRemoteInputManager; - @Mock private NotifLifetimeExtender.OnEndLifetimeExtensionCallback mEndLifetimeExtension; - @Mock private NodeController mHeaderController; - - private NotificationEntry mEntry; - private final FakeSystemClock mClock = new FakeSystemClock(); - private final FakeExecutor mExecutor = new FakeExecutor(mClock); - private final ArrayList<NotificationEntry> mHuns = new ArrayList(); - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - - mCoordinator = new HeadsUpCoordinator( - mHeadsUpManager, - mHeadsUpViewBinder, - mNotificationInterruptStateProvider, - mRemoteInputManager, - mHeaderController, - mExecutor); - - mCoordinator.attach(mNotifPipeline); - - // capture arguments: - ArgumentCaptor<NotifCollectionListener> notifCollectionCaptor = - ArgumentCaptor.forClass(NotifCollectionListener.class); - ArgumentCaptor<NotifPromoter> notifPromoterCaptor = - ArgumentCaptor.forClass(NotifPromoter.class); - ArgumentCaptor<NotifLifetimeExtender> notifLifetimeExtenderCaptor = - ArgumentCaptor.forClass(NotifLifetimeExtender.class); - ArgumentCaptor<OnHeadsUpChangedListener> headsUpChangedListenerCaptor = - ArgumentCaptor.forClass(OnHeadsUpChangedListener.class); - - verify(mNotifPipeline).addCollectionListener(notifCollectionCaptor.capture()); - verify(mNotifPipeline).addPromoter(notifPromoterCaptor.capture()); - verify(mNotifPipeline).addNotificationLifetimeExtender( - notifLifetimeExtenderCaptor.capture()); - verify(mHeadsUpManager).addListener(headsUpChangedListenerCaptor.capture()); - - given(mHeadsUpManager.getAllEntries()).willAnswer(i -> mHuns.stream()); - given(mHeadsUpManager.isAlerting(anyString())).willAnswer(i -> { - String key = i.getArgument(0); - for (NotificationEntry entry : mHuns) { - if (entry.getKey().equals(key)) return true; - } - return false; - }); - when(mHeadsUpManager.getEarliestRemovalTime(anyString())).thenReturn(1000L); - - mCollectionListener = notifCollectionCaptor.getValue(); - mNotifPromoter = notifPromoterCaptor.getValue(); - mNotifLifetimeExtender = notifLifetimeExtenderCaptor.getValue(); - mOnHeadsUpChangedListener = headsUpChangedListenerCaptor.getValue(); - - mNotifSectioner = mCoordinator.getSectioner(); - mNotifLifetimeExtender.setCallback(mEndLifetimeExtension); - mEntry = new NotificationEntryBuilder().build(); - } - - @Test - public void testCancelStickyNotification() { - when(mHeadsUpManager.isSticky(anyString())).thenReturn(true); - addHUN(mEntry); - when(mHeadsUpManager.canRemoveImmediately(anyString())).thenReturn(false, true); - when(mHeadsUpManager.getEarliestRemovalTime(anyString())).thenReturn(1000L, 0L); - assertTrue(mNotifLifetimeExtender.maybeExtendLifetime(mEntry, 0)); - mClock.advanceTime(1000L); - mExecutor.runAllReady(); - verify(mHeadsUpManager, times(0)) - .removeNotification(anyString(), eq(false)); - verify(mHeadsUpManager, times(1)) - .removeNotification(anyString(), eq(true)); - } - - @Test - public void testCancelUpdatedStickyNotification() { - when(mHeadsUpManager.isSticky(anyString())).thenReturn(true); - addHUN(mEntry); - when(mHeadsUpManager.getEarliestRemovalTime(anyString())).thenReturn(1000L, 500L); - assertTrue(mNotifLifetimeExtender.maybeExtendLifetime(mEntry, 0)); - mClock.advanceTime(1000L); - mExecutor.runAllReady(); - verify(mHeadsUpManager, times(0)) - .removeNotification(anyString(), eq(false)); - verify(mHeadsUpManager, times(0)) - .removeNotification(anyString(), eq(true)); - } - - @Test - public void testCancelNotification() { - when(mHeadsUpManager.isSticky(anyString())).thenReturn(false); - addHUN(mEntry); - when(mHeadsUpManager.getEarliestRemovalTime(anyString())).thenReturn(1000L, 500L); - assertTrue(mNotifLifetimeExtender.maybeExtendLifetime(mEntry, 0)); - mClock.advanceTime(1000L); - mExecutor.runAllReady(); - verify(mHeadsUpManager, times(1)) - .removeNotification(anyString(), eq(false)); - verify(mHeadsUpManager, times(0)) - .removeNotification(anyString(), eq(true)); - } - - @Test - public void testPromotesCurrentHUN() { - // GIVEN the current HUN is set to mEntry - addHUN(mEntry); - - // THEN only promote the current HUN, mEntry - assertTrue(mNotifPromoter.shouldPromoteToTopLevel(mEntry)); - assertFalse(mNotifPromoter.shouldPromoteToTopLevel(new NotificationEntryBuilder() - .setPkg("test-package2") - .build())); - } - - @Test - public void testIncludeInSectionCurrentHUN() { - // GIVEN the current HUN is set to mEntry - addHUN(mEntry); - - // THEN only section the current HUN, mEntry - assertTrue(mNotifSectioner.isInSection(mEntry)); - assertFalse(mNotifSectioner.isInSection(new NotificationEntryBuilder() - .setPkg("test-package") - .build())); - } - - @Test - public void testLifetimeExtendsCurrentHUN() { - // GIVEN there is a HUN, mEntry - addHUN(mEntry); - - given(mHeadsUpManager.canRemoveImmediately(anyString())).willAnswer(i -> { - String key = i.getArgument(0); - for (NotificationEntry entry : mHuns) { - if (entry.getKey().equals(key)) return false; - } - return true; - }); - // THEN only the current HUN, mEntry, should be lifetimeExtended - assertTrue(mNotifLifetimeExtender.maybeExtendLifetime(mEntry, /* cancellationReason */ 0)); - assertFalse(mNotifLifetimeExtender.maybeExtendLifetime( - new NotificationEntryBuilder() - .setPkg("test-package") - .build(), /* cancellationReason */ 0)); - } - - @Test - public void testShowHUNOnInflationFinished() { - // WHEN a notification should HUN and its inflation is finished - when(mNotificationInterruptStateProvider.shouldHeadsUp(mEntry)).thenReturn(true); - - ArgumentCaptor<BindCallback> bindCallbackCaptor = - ArgumentCaptor.forClass(BindCallback.class); - mCollectionListener.onEntryAdded(mEntry); - verify(mHeadsUpViewBinder).bindHeadsUpView(eq(mEntry), bindCallbackCaptor.capture()); - - bindCallbackCaptor.getValue().onBindFinished(mEntry); - - // THEN we tell the HeadsUpManager to show the notification - verify(mHeadsUpManager).showNotification(mEntry); - } - - @Test - public void testNoHUNOnInflationFinished() { - // WHEN a notification shouldn't HUN and its inflation is finished - when(mNotificationInterruptStateProvider.shouldHeadsUp(mEntry)).thenReturn(false); - ArgumentCaptor<BindCallback> bindCallbackCaptor = - ArgumentCaptor.forClass(BindCallback.class); - mCollectionListener.onEntryAdded(mEntry); - - // THEN we never bind the heads up view or tell HeadsUpManager to show the notification - verify(mHeadsUpViewBinder, never()).bindHeadsUpView( - eq(mEntry), bindCallbackCaptor.capture()); - verify(mHeadsUpManager, never()).showNotification(mEntry); - } - - @Test - public void testOnEntryRemovedRemovesHeadsUpNotification() { - // GIVEN the current HUN is mEntry - addHUN(mEntry); - - // WHEN mEntry is removed from the notification collection - mCollectionListener.onEntryRemoved(mEntry, /* cancellation reason */ 0); - when(mRemoteInputManager.isSpinning(any())).thenReturn(false); - - // THEN heads up manager should remove the entry - verify(mHeadsUpManager).removeNotification(mEntry.getKey(), false); - } - - private void addHUN(NotificationEntry entry) { - mHuns.add(entry); - when(mHeadsUpManager.getTopEntry()).thenReturn(entry); - mOnHeadsUpChangedListener.onHeadsUpStateChanged(entry, entry != null); - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt new file mode 100644 index 000000000000..c67a2331b023 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt @@ -0,0 +1,238 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.statusbar.notification.collection.coordinator + +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper.RunWithLooper +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.NotificationRemoteInputManager +import com.android.systemui.statusbar.notification.collection.NotifPipeline +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender.OnEndLifetimeExtensionCallback +import com.android.systemui.statusbar.notification.collection.render.NodeController +import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder +import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider +import com.android.systemui.statusbar.notification.row.NotifBindPipeline.BindCallback +import com.android.systemui.statusbar.policy.HeadsUpManager +import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener +import com.android.systemui.util.concurrency.FakeExecutor +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.eq +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.withArgCaptor +import com.android.systemui.util.time.FakeSystemClock +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.anyString +import org.mockito.BDDMockito.given +import org.mockito.Mockito.never +import org.mockito.Mockito.times +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations +import java.util.ArrayList +import org.mockito.Mockito.`when` as whenever + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@RunWithLooper +class HeadsUpCoordinatorTest : SysuiTestCase() { + private lateinit var mCoordinator: HeadsUpCoordinator + + // captured listeners and pluggables: + private lateinit var mCollectionListener: NotifCollectionListener + private lateinit var mNotifPromoter: NotifPromoter + private lateinit var mNotifLifetimeExtender: NotifLifetimeExtender + private lateinit var mOnHeadsUpChangedListener: OnHeadsUpChangedListener + private lateinit var mNotifSectioner: NotifSectioner + + private val mNotifPipeline: NotifPipeline = mock() + private val mHeadsUpManager: HeadsUpManager = mock() + private val mHeadsUpViewBinder: HeadsUpViewBinder = mock() + private val mNotificationInterruptStateProvider: NotificationInterruptStateProvider = mock() + private val mRemoteInputManager: NotificationRemoteInputManager = mock() + private val mEndLifetimeExtension: OnEndLifetimeExtensionCallback = mock() + private val mHeaderController: NodeController = mock() + + private lateinit var mEntry: NotificationEntry + private val mExecutor = FakeExecutor(FakeSystemClock()) + private val mHuns: ArrayList<NotificationEntry> = ArrayList() + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + mCoordinator = HeadsUpCoordinator( + mHeadsUpManager, + mHeadsUpViewBinder, + mNotificationInterruptStateProvider, + mRemoteInputManager, + mHeaderController, + mExecutor) + mCoordinator.attach(mNotifPipeline) + + // capture arguments: + mCollectionListener = withArgCaptor { + verify(mNotifPipeline).addCollectionListener(capture()) + } + mNotifPromoter = withArgCaptor { + verify(mNotifPipeline).addPromoter(capture()) + } + mNotifLifetimeExtender = withArgCaptor { + verify(mNotifPipeline).addNotificationLifetimeExtender(capture()) + } + mOnHeadsUpChangedListener = withArgCaptor { + verify(mHeadsUpManager).addListener(capture()) + } + given(mHeadsUpManager.allEntries).willAnswer { mHuns.stream() } + given(mHeadsUpManager.isAlerting(anyString())).willAnswer { invocation -> + val key = invocation.getArgument<String>(0) + mHuns.any { entry -> entry.key == key } + } + given(mHeadsUpManager.canRemoveImmediately(anyString())).willAnswer { invocation -> + val key = invocation.getArgument<String>(0) + !mHuns.any { entry -> entry.key == key } + } + whenever(mHeadsUpManager.getEarliestRemovalTime(anyString())).thenReturn(1000L) + mNotifSectioner = mCoordinator.sectioner + mNotifLifetimeExtender.setCallback(mEndLifetimeExtension) + mEntry = NotificationEntryBuilder().build() + } + + @Test + fun testCancelStickyNotification() { + whenever(mHeadsUpManager.isSticky(anyString())).thenReturn(true) + addHUN(mEntry) + whenever(mHeadsUpManager.canRemoveImmediately(anyString())).thenReturn(false, true) + whenever(mHeadsUpManager.getEarliestRemovalTime(anyString())).thenReturn(1000L, 0L) + assertTrue(mNotifLifetimeExtender.maybeExtendLifetime(mEntry, 0)) + mExecutor.advanceClockToLast() + mExecutor.runAllReady() + verify(mHeadsUpManager, times(0)).removeNotification(anyString(), eq(false)) + verify(mHeadsUpManager, times(1)).removeNotification(anyString(), eq(true)) + } + + @Test + fun testCancelUpdatedStickyNotification() { + whenever(mHeadsUpManager.isSticky(anyString())).thenReturn(true) + addHUN(mEntry) + whenever(mHeadsUpManager.getEarliestRemovalTime(anyString())).thenReturn(1000L, 500L) + assertTrue(mNotifLifetimeExtender.maybeExtendLifetime(mEntry, 0)) + mExecutor.advanceClockToLast() + mExecutor.runAllReady() + verify(mHeadsUpManager, times(0)).removeNotification(anyString(), eq(false)) + verify(mHeadsUpManager, times(0)).removeNotification(anyString(), eq(true)) + } + + @Test + fun testCancelNotification() { + whenever(mHeadsUpManager.isSticky(anyString())).thenReturn(false) + addHUN(mEntry) + whenever(mHeadsUpManager.getEarliestRemovalTime(anyString())).thenReturn(1000L, 500L) + assertTrue(mNotifLifetimeExtender.maybeExtendLifetime(mEntry, 0)) + mExecutor.advanceClockToLast() + mExecutor.runAllReady() + verify(mHeadsUpManager, times(1)).removeNotification(anyString(), eq(false)) + verify(mHeadsUpManager, times(0)).removeNotification(anyString(), eq(true)) + } + + @Test + fun testPromotesCurrentHUN() { + // GIVEN the current HUN is set to mEntry + addHUN(mEntry) + + // THEN only promote the current HUN, mEntry + assertTrue(mNotifPromoter.shouldPromoteToTopLevel(mEntry)) + assertFalse(mNotifPromoter.shouldPromoteToTopLevel(NotificationEntryBuilder() + .setPkg("test-package2") + .build())) + } + + @Test + fun testIncludeInSectionCurrentHUN() { + // GIVEN the current HUN is set to mEntry + addHUN(mEntry) + + // THEN only section the current HUN, mEntry + assertTrue(mNotifSectioner.isInSection(mEntry)) + assertFalse(mNotifSectioner.isInSection(NotificationEntryBuilder() + .setPkg("test-package") + .build())) + } + + @Test + fun testLifetimeExtendsCurrentHUN() { + // GIVEN there is a HUN, mEntry + addHUN(mEntry) + + // THEN only the current HUN, mEntry, should be lifetimeExtended + assertTrue(mNotifLifetimeExtender.maybeExtendLifetime(mEntry, /* cancellationReason */ 0)) + assertFalse(mNotifLifetimeExtender.maybeExtendLifetime( + NotificationEntryBuilder() + .setPkg("test-package") + .build(), /* cancellationReason */ 0)) + } + + @Test + fun testShowHUNOnInflationFinished() { + // WHEN a notification should HUN and its inflation is finished + whenever(mNotificationInterruptStateProvider.shouldHeadsUp(mEntry)).thenReturn(true) + + mCollectionListener.onEntryAdded(mEntry) + withArgCaptor<BindCallback> { + verify(mHeadsUpViewBinder).bindHeadsUpView(eq(mEntry), capture()) + }.onBindFinished(mEntry) + + // THEN we tell the HeadsUpManager to show the notification + verify(mHeadsUpManager).showNotification(mEntry) + } + + @Test + fun testNoHUNOnInflationFinished() { + // WHEN a notification shouldn't HUN and its inflation is finished + whenever(mNotificationInterruptStateProvider.shouldHeadsUp(mEntry)).thenReturn(false) + mCollectionListener.onEntryAdded(mEntry) + + // THEN we never bind the heads up view or tell HeadsUpManager to show the notification + verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mEntry), any()) + verify(mHeadsUpManager, never()).showNotification(mEntry) + } + + @Test + fun testOnEntryRemovedRemovesHeadsUpNotification() { + // GIVEN the current HUN is mEntry + addHUN(mEntry) + + // WHEN mEntry is removed from the notification collection + mCollectionListener.onEntryRemoved(mEntry, /* cancellation reason */ 0) + whenever(mRemoteInputManager.isSpinning(any())).thenReturn(false) + + // THEN heads up manager should remove the entry + verify(mHeadsUpManager).removeNotification(mEntry.key, false) + } + + private fun addHUN(entry: NotificationEntry) { + mHuns.add(entry) + whenever(mHeadsUpManager.topEntry).thenReturn(entry) + mOnHeadsUpChangedListener.onHeadsUpStateChanged(entry, true) + } +}
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java index 917c049fd578..d0947497f0ec 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java @@ -41,6 +41,7 @@ import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.RankingBuilder; +import com.android.systemui.statusbar.notification.SectionHeaderVisibilityProvider; import com.android.systemui.statusbar.notification.collection.GroupEntry; import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder; import com.android.systemui.statusbar.notification.collection.NotifPipeline; @@ -70,6 +71,7 @@ public class KeyguardCoordinatorTest extends SysuiTestCase { @Mock private StatusBarStateController mStatusBarStateController; @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor; @Mock private HighPriorityProvider mHighPriorityProvider; + @Mock private SectionHeaderVisibilityProvider mSectionHeaderVisibilityProvider; @Mock private NotifPipeline mNotifPipeline; private NotificationEntry mEntry; @@ -81,7 +83,7 @@ public class KeyguardCoordinatorTest extends SysuiTestCase { KeyguardCoordinator keyguardCoordinator = new KeyguardCoordinator( mContext, mMainHandler, mKeyguardStateController, mLockscreenUserManager, mBroadcastDispatcher, mStatusBarStateController, - mKeyguardUpdateMonitor, mHighPriorityProvider); + mKeyguardUpdateMonitor, mHighPriorityProvider, mSectionHeaderVisibilityProvider); mEntry = new NotificationEntryBuilder() .setUser(new UserHandle(NOTIF_USER_ID)) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java index bde6734bbf92..3b034f7af9a6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java @@ -26,6 +26,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import static java.util.Objects.requireNonNull; @@ -40,7 +41,6 @@ import androidx.test.filters.SmallTest; import com.android.internal.statusbar.IStatusBarService; import com.android.systemui.SysuiTestCase; import com.android.systemui.statusbar.RankingBuilder; -import com.android.systemui.statusbar.notification.ConversationNotificationManager; import com.android.systemui.statusbar.notification.SectionClassifier; import com.android.systemui.statusbar.notification.collection.GroupEntry; import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder; @@ -48,6 +48,7 @@ import com.android.systemui.statusbar.notification.collection.ListEntry; import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; +import com.android.systemui.statusbar.notification.collection.inflation.BindEventManagerImpl; import com.android.systemui.statusbar.notification.collection.inflation.NotifInflater; import com.android.systemui.statusbar.notification.collection.inflation.NotifUiAdjustmentProvider; import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection; @@ -93,7 +94,7 @@ public class PreparationCoordinatorTest extends SysuiTestCase { @Mock private NotifSection mNotifSection; @Mock private NotifPipeline mNotifPipeline; @Mock private IStatusBarService mService; - @Mock private ConversationNotificationManager mConvoManager; + @Mock private BindEventManagerImpl mBindEventManagerImpl; @Spy private FakeNotifInflater mNotifInflater = new FakeNotifInflater(); private final SectionClassifier mSectionClassifier = new SectionClassifier(); private final NotifUiAdjustmentProvider mAdjustmentProvider = @@ -121,7 +122,7 @@ public class PreparationCoordinatorTest extends SysuiTestCase { mock(NotifViewBarn.class), mAdjustmentProvider, mService, - mConvoManager, + mBindEventManagerImpl, TEST_CHILD_BIND_CUTOFF, TEST_MAX_GROUP_DELAY); @@ -411,7 +412,8 @@ public class PreparationCoordinatorTest extends SysuiTestCase { public void testCallConversationManagerBindWhenInflated() { mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry)); mNotifInflater.getInflateCallback(mEntry).onInflationFinished(mEntry, null); - verify(mConvoManager, times(1)).onEntryViewBound(eq(mEntry)); + verify(mBindEventManagerImpl, times(1)).notifyViewBound(eq(mEntry)); + verifyNoMoreInteractions(mBindEventManagerImpl); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt index f77381000ae2..4e309d49c6d2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt @@ -19,6 +19,7 @@ package com.android.systemui.statusbar.notification.collection.render import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager +import com.android.systemui.statusbar.notification.SectionHeaderVisibilityProvider import com.android.systemui.statusbar.notification.collection.GroupEntry import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder import com.android.systemui.statusbar.notification.collection.ListEntry @@ -43,6 +44,7 @@ class NodeSpecBuilderTest : SysuiTestCase() { private val mediaContainerController: MediaContainerController = mock() private val sectionsFeatureManager: NotificationSectionsFeatureManager = mock() + private val sectionHeaderVisibilityProvider: SectionHeaderVisibilityProvider = mock() private val viewBarn: NotifViewBarn = mock() private var rootController: NodeController = buildFakeController("rootController") @@ -72,11 +74,13 @@ class NodeSpecBuilderTest : SysuiTestCase() { fakeViewBarn.getViewByEntry(it.getArgument(0)) } - specBuilder = NodeSpecBuilder(mediaContainerController, sectionsFeatureManager, viewBarn) + specBuilder = NodeSpecBuilder(mediaContainerController, sectionsFeatureManager, + sectionHeaderVisibilityProvider, viewBarn) } @Test fun testMultipleSectionsWithSameController() { + whenever(sectionHeaderVisibilityProvider.sectionHeadersVisible).thenReturn(true) checkOutput( listOf( notif(0, section0), @@ -95,6 +99,7 @@ class NodeSpecBuilderTest : SysuiTestCase() { @Test(expected = RuntimeException::class) fun testMultipleSectionsWithSameControllerNonConsecutive() { + whenever(sectionHeaderVisibilityProvider.sectionHeadersVisible).thenReturn(true) checkOutput( listOf( notif(0, section0), @@ -108,6 +113,7 @@ class NodeSpecBuilderTest : SysuiTestCase() { @Test fun testSimpleMapping() { + whenever(sectionHeaderVisibilityProvider.sectionHeadersVisible).thenReturn(true) checkOutput( // GIVEN a simple flat list of notifications all in the same headerless section listOf( @@ -129,6 +135,7 @@ class NodeSpecBuilderTest : SysuiTestCase() { @Test fun testSimpleMappingWithMedia() { + whenever(sectionHeaderVisibilityProvider.sectionHeadersVisible).thenReturn(true) // WHEN media controls are enabled whenever(sectionsFeatureManager.isMediaControlsEnabled()).thenReturn(true) @@ -154,6 +161,8 @@ class NodeSpecBuilderTest : SysuiTestCase() { @Test fun testHeaderInjection() { + // WHEN section headers are supposed to be visible + whenever(sectionHeaderVisibilityProvider.sectionHeadersVisible).thenReturn(true) checkOutput( // GIVEN a flat list of notifications, spread across three sections listOf( @@ -177,7 +186,31 @@ class NodeSpecBuilderTest : SysuiTestCase() { } @Test + fun testHeaderSuppression() { + // WHEN section headers are supposed to be hidden + whenever(sectionHeaderVisibilityProvider.sectionHeadersVisible).thenReturn(false) + checkOutput( + // GIVEN a flat list of notifications, spread across three sections + listOf( + notif(0, section0), + notif(1, section0), + notif(2, section1), + notif(3, section2) + ), + + // THEN each section has its header injected + tree( + notifNode(0), + notifNode(1), + notifNode(2), + notifNode(3) + ) + ) + } + + @Test fun testGroups() { + whenever(sectionHeaderVisibilityProvider.sectionHeadersVisible).thenReturn(true) checkOutput( // GIVEN a mixed list of top-level notifications and groups listOf( @@ -218,6 +251,7 @@ class NodeSpecBuilderTest : SysuiTestCase() { @Test fun testSecondSectionWithNoHeader() { + whenever(sectionHeaderVisibilityProvider.sectionHeadersVisible).thenReturn(true) checkOutput( // GIVEN a middle section with no associated header view listOf( @@ -247,6 +281,7 @@ class NodeSpecBuilderTest : SysuiTestCase() { @Test(expected = RuntimeException::class) fun testRepeatedSectionsThrow() { + whenever(sectionHeaderVisibilityProvider.sectionHeadersVisible).thenReturn(true) checkOutput( // GIVEN a malformed list where sections are not contiguous listOf( diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.java index bbe92f67ca2e..15ff5551703b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.java @@ -274,6 +274,18 @@ public class ShadeViewDifferTest extends SysuiTestCase { public void removeChild(@NonNull NodeController child, boolean isTransfer) { view.removeView(child.getView()); } + + @Override + public void onViewAdded() { + } + + @Override + public void onViewMoved() { + } + + @Override + public void onViewRemoved() { + } } private static class SpecBuilder { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java index c3349f1d70f4..5ca1f21eb021 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java @@ -42,6 +42,7 @@ import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.SysuiTestCase; import com.android.systemui.biometrics.AuthController; import com.android.systemui.dump.DumpManager; +import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.keyguard.ScreenLifecycle; import com.android.systemui.keyguard.WakefulnessLifecycle; @@ -104,6 +105,8 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase { private ScreenLifecycle mScreenLifecycle; @Mock private StatusBarStateController mStatusBarStateController; + @Mock + private KeyguardUnlockAnimationController mKeyguardUnlockAnimationController; private BiometricUnlockController mBiometricUnlockController; @Before @@ -126,7 +129,7 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase { mUpdateMonitor, res.getResources(), mKeyguardBypassController, mDozeParameters, mMetricsLogger, mDumpManager, mPowerManager, mNotificationMediaManager, mWakefulnessLifecycle, mScreenLifecycle, - mAuthController, mStatusBarStateController); + mAuthController, mStatusBarStateController, mKeyguardUnlockAnimationController); mBiometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager); mBiometricUnlockController.setBiometricModeListener(mBiometricModeListener); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java index 35f671bf8298..1c8b35aeaeaf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java @@ -104,6 +104,7 @@ import com.android.systemui.fragments.FragmentHostManager; import com.android.systemui.fragments.FragmentService; import com.android.systemui.idle.IdleHostViewController; import com.android.systemui.idle.dagger.IdleViewComponent; +import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import com.android.systemui.media.KeyguardMediaController; import com.android.systemui.media.MediaDataManager; import com.android.systemui.media.MediaHierarchyManager; @@ -362,6 +363,8 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { private QsFrameTranslateController mQsFrameTranslateController; @Mock private StatusBarWindowStateController mStatusBarWindowStateController; + @Mock + private KeyguardUnlockAnimationController mKeyguardUnlockAnimationController; private Optional<SysUIUnfoldComponent> mSysUIUnfoldComponent = Optional.empty(); private SysuiStatusBarStateController mStatusBarStateController; private NotificationPanelViewController mNotificationPanelViewController; @@ -546,7 +549,8 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { mSysUIUnfoldComponent, mControlsComponent, mInteractionJankMonitor, - mQsFrameTranslateController); + mQsFrameTranslateController, + mKeyguardUnlockAnimationController); mNotificationPanelViewController.initDependencies( mStatusBar, () -> {}, 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 f21fca23df5b..10f4435d3f97 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 @@ -647,8 +647,15 @@ public class ScrimControllerTest extends SysuiTestCase { mScrimController.setRawPanelExpansionFraction(0.3f); assertScrimAlpha(Map.of( mScrimInFront, TRANSPARENT, - mNotificationsScrim, SEMI_TRANSPARENT, + mNotificationsScrim, TRANSPARENT, mScrimBehind, SEMI_TRANSPARENT)); + + // Then, notification scrim should fade in + mScrimController.setRawPanelExpansionFraction(0.7f); + assertScrimAlpha(Map.of( + mScrimInFront, TRANSPARENT, + mNotificationsScrim, SEMI_TRANSPARENT, + mScrimBehind, OPAQUE)); } @@ -1132,6 +1139,7 @@ public class ScrimControllerTest extends SysuiTestCase { @Test public void testScrimsVisible_whenShadeVisible() { + mScrimController.setClipsQsScrim(true); mScrimController.transitionTo(ScrimState.UNLOCKED); mScrimController.setRawPanelExpansionFraction(0.3f); // notifications scrim alpha change require calling setQsPosition diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java index bb8bad39ab31..77065b2d4380 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java @@ -819,31 +819,27 @@ public class StatusBarTest extends SysuiTestCase { } @Test - public void testSetExpansionAffectsAlpha_onlyWhenHidingKeyguard() { + public void testSetExpansionAffectsAlpha_whenKeyguardShowingButGoingAwayForAnyReason() { mStatusBar.updateScrimController(); verify(mScrimController).setExpansionAffectsAlpha(eq(true)); clearInvocations(mScrimController); - when(mBiometricUnlockController.isBiometricUnlock()).thenReturn(true); + when(mKeyguardStateController.isShowing()).thenReturn(true); + when(mKeyguardStateController.isKeyguardGoingAway()).thenReturn(false); mStatusBar.updateScrimController(); verify(mScrimController).setExpansionAffectsAlpha(eq(true)); clearInvocations(mScrimController); when(mKeyguardStateController.isShowing()).thenReturn(true); + when(mKeyguardStateController.isKeyguardGoingAway()).thenReturn(true); mStatusBar.updateScrimController(); verify(mScrimController).setExpansionAffectsAlpha(eq(false)); clearInvocations(mScrimController); - reset(mKeyguardStateController); + when(mKeyguardStateController.isShowing()).thenReturn(true); when(mKeyguardStateController.isKeyguardFadingAway()).thenReturn(true); mStatusBar.updateScrimController(); verify(mScrimController).setExpansionAffectsAlpha(eq(false)); - - clearInvocations(mScrimController); - reset(mKeyguardStateController); - when(mKeyguardStateController.isKeyguardGoingAway()).thenReturn(true); - mStatusBar.updateScrimController(); - verify(mScrimController).setExpansionAffectsAlpha(eq(false)); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt index 24a56bc76fae..71b32c0bd106 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt @@ -17,6 +17,8 @@ package com.android.systemui.statusbar.phone import android.animation.Animator +import android.os.Handler +import android.os.PowerManager import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import android.view.View @@ -29,13 +31,19 @@ import com.android.systemui.statusbar.LightRevealScrim import com.android.systemui.statusbar.StatusBarStateControllerImpl import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.settings.GlobalSettings +import org.junit.After import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor import org.mockito.Mock import org.mockito.Mockito +import org.mockito.Mockito.`when` +import org.mockito.Mockito.anyLong +import org.mockito.Mockito.never import org.mockito.Mockito.spy +import org.mockito.Mockito.times +import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @SmallTest @@ -53,7 +61,9 @@ class UnlockedScreenOffAnimationControllerTest : SysuiTestCase() { @Mock private lateinit var globalSettings: GlobalSettings @Mock - private lateinit var statusbar: StatusBar + private lateinit var statusBar: StatusBar + @Mock + private lateinit var notificationPanelViewController: NotificationPanelViewController @Mock private lateinit var lightRevealScrim: LightRevealScrim @Mock @@ -62,6 +72,10 @@ class UnlockedScreenOffAnimationControllerTest : SysuiTestCase() { private lateinit var statusBarStateController: StatusBarStateControllerImpl @Mock private lateinit var interactionJankMonitor: InteractionJankMonitor + @Mock + private lateinit var powerManager: PowerManager + @Mock + private lateinit var handler: Handler @Before fun setUp() { @@ -75,9 +89,24 @@ class UnlockedScreenOffAnimationControllerTest : SysuiTestCase() { keyguardStateController, dagger.Lazy<DozeParameters> { dozeParameters }, globalSettings, - interactionJankMonitor + interactionJankMonitor, + powerManager, + handler = handler ) - controller.initialize(statusbar, lightRevealScrim) + controller.initialize(statusBar, lightRevealScrim) + `when`(statusBar.notificationPanelViewController).thenReturn( + notificationPanelViewController) + + // Screen off does not run if the panel is expanded, so we should say it's collapsed to test + // screen off. + `when`(notificationPanelViewController.isFullyCollapsed).thenReturn(true) + } + + @After + fun cleanUp() { + // Tell the screen off controller to cancel the animations and clean up its state, or + // subsequent tests will act unpredictably as the animator continues running. + controller.onStartedWakingUp() } @Test @@ -93,4 +122,49 @@ class UnlockedScreenOffAnimationControllerTest : SysuiTestCase() { listener.value.onAnimationEnd(null) Mockito.verify(animator).setListener(null) } + + /** + * The AOD UI is shown during the screen off animation, after a delay to allow the light reveal + * animation to start. If the device is woken up during the screen off, we should *never* do + * this. + * + * This test confirms that we do show the AOD UI when the device is not woken up + * (PowerManager#isInteractive = false). + */ + @Test + fun testAodUiShownIfNotInteractive() { + `when`(dozeParameters.shouldControlUnlockedScreenOff()).thenReturn(true) + `when`(powerManager.isInteractive).thenReturn(false) + + val callbackCaptor = ArgumentCaptor.forClass(Runnable::class.java) + controller.startAnimation() + + verify(handler).postDelayed(callbackCaptor.capture(), anyLong()) + + callbackCaptor.value.run() + + verify(notificationPanelViewController, times(1)).showAodUi() + } + + /** + * The AOD UI is shown during the screen off animation, after a delay to allow the light reveal + * animation to start. If the device is woken up during the screen off, we should *never* do + * this. + * + * This test confirms that we do not show the AOD UI when the device is woken up during screen + * off (PowerManager#isInteractive = true). + */ + @Test + fun testAodUiNotShownIfInteractive() { + `when`(dozeParameters.shouldControlUnlockedScreenOff()).thenReturn(true) + `when`(powerManager.isInteractive).thenReturn(true) + + val callbackCaptor = ArgumentCaptor.forClass(Runnable::class.java) + controller.startAnimation() + + verify(handler).postDelayed(callbackCaptor.capture(), anyLong()) + callbackCaptor.value.run() + + verify(notificationPanelViewController, never()).showAodUi() + } }
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java index a8522c787029..6364d2f23299 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java @@ -52,13 +52,14 @@ import org.mockito.MockitoAnnotations; @SmallTest public class DeviceStateRotationLockSettingControllerTest extends SysuiTestCase { - private static final String[] DEFAULT_SETTINGS = new String[] {"0:0", "1:2"}; + private static final String[] DEFAULT_SETTINGS = new String[]{"0:1", "2:0:1", "1:2"}; private final FakeSystemClock mFakeSystemClock = new FakeSystemClock(); private final FakeExecutor mFakeExecutor = new FakeExecutor(mFakeSystemClock); - @Mock DeviceStateManager mDeviceStateManager; - RotationPolicyWrapper mFakeRotationPolicy = new FakeRotationPolicy(); - DeviceStateRotationLockSettingController mDeviceStateRotationLockSettingController; + @Mock + private DeviceStateManager mDeviceStateManager; + private final RotationPolicyWrapper mFakeRotationPolicy = new FakeRotationPolicy(); + private DeviceStateRotationLockSettingController mDeviceStateRotationLockSettingController; private DeviceStateManager.DeviceStateCallback mDeviceStateCallback; private DeviceStateRotationLockSettingsManager mSettingsManager; private TestableContentResolver mContentResolver; @@ -93,7 +94,7 @@ public class DeviceStateRotationLockSettingControllerTest extends SysuiTestCase mContentResolver, Settings.Secure.DEVICE_STATE_ROTATION_LOCK, UserHandle.USER_CURRENT)) - .isEqualTo("0:0:1:2"); + .isEqualTo("0:1:1:2:2:0"); } @Test @@ -125,6 +126,31 @@ public class DeviceStateRotationLockSettingControllerTest extends SysuiTestCase } @Test + public void whenDeviceStateSwitched_settingIsIgnored_loadsDefaultFallbackSetting() { + initializeSettingsWith(); + mFakeRotationPolicy.setRotationLock(true); + + // State 2 -> Ignored -> Fall back to state 1 which is unlocked + mDeviceStateCallback.onStateChanged(2); + + assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse(); + } + + @Test + public void whenDeviceStateSwitched_ignoredSetting_fallbackValueChanges_usesFallbackValue() { + initializeSettingsWith( + 0, DEVICE_STATE_ROTATION_LOCK_UNLOCKED, + 1, DEVICE_STATE_ROTATION_LOCK_LOCKED, + 2, DEVICE_STATE_ROTATION_LOCK_IGNORED); + mFakeRotationPolicy.setRotationLock(false); + + // State 2 -> Ignored -> Fall back to state 1 which is locked + mDeviceStateCallback.onStateChanged(2); + + assertThat(mFakeRotationPolicy.isRotationLocked()).isTrue(); + } + + @Test public void whenUserChangesSetting_saveSettingForCurrentState() { initializeSettingsWith( 0, DEVICE_STATE_ROTATION_LOCK_LOCKED, 1, DEVICE_STATE_ROTATION_LOCK_UNLOCKED); @@ -159,15 +185,15 @@ public class DeviceStateRotationLockSettingControllerTest extends SysuiTestCase } @Test - public void whenDeviceStateSwitchedToIgnoredState_newSettingsSaveForPreviousState() { + public void whenDeviceStateSwitchedToIgnoredState_noFallback_newSettingsSaveForPreviousState() { initializeSettingsWith( - 0, DEVICE_STATE_ROTATION_LOCK_IGNORED, 1, DEVICE_STATE_ROTATION_LOCK_UNLOCKED); + 8, DEVICE_STATE_ROTATION_LOCK_IGNORED, 1, DEVICE_STATE_ROTATION_LOCK_UNLOCKED); mFakeRotationPolicy.setRotationLock(true); mDeviceStateCallback.onStateChanged(1); assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse(); - mDeviceStateCallback.onStateChanged(0); + mDeviceStateCallback.onStateChanged(8); assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse(); mDeviceStateRotationLockSettingController.onRotationLockStateChanged( @@ -178,7 +204,7 @@ public class DeviceStateRotationLockSettingControllerTest extends SysuiTestCase mContentResolver, Settings.Secure.DEVICE_STATE_ROTATION_LOCK, UserHandle.USER_CURRENT)) - .isEqualTo("0:0:1:1"); + .isEqualTo("1:1:8:0"); } @Test @@ -198,12 +224,78 @@ public class DeviceStateRotationLockSettingControllerTest extends SysuiTestCase assertThat(mFakeRotationPolicy.isRotationLocked()).isTrue(); } + @Test + public void onRotationLockStateChanged_newSettingIsPersisted() { + initializeSettingsWith( + 0, DEVICE_STATE_ROTATION_LOCK_LOCKED, + 1, DEVICE_STATE_ROTATION_LOCK_UNLOCKED); + mDeviceStateCallback.onStateChanged(0); + + mDeviceStateRotationLockSettingController.onRotationLockStateChanged( + /* rotationLocked= */ false, + /* affordanceVisible= */ true + ); + + assertThat( + Settings.Secure.getStringForUser( + mContentResolver, + Settings.Secure.DEVICE_STATE_ROTATION_LOCK, + UserHandle.USER_CURRENT)) + .isEqualTo("0:2:1:2"); + } + + @Test + public void onRotationLockStateChanged_deviceStateIsIgnored_newSettingIsPersistedToFallback() { + initializeSettingsWith( + 0, DEVICE_STATE_ROTATION_LOCK_LOCKED, + 1, DEVICE_STATE_ROTATION_LOCK_UNLOCKED, + 2, DEVICE_STATE_ROTATION_LOCK_IGNORED); + mDeviceStateCallback.onStateChanged(2); + + mDeviceStateRotationLockSettingController.onRotationLockStateChanged( + /* rotationLocked= */ true, + /* affordanceVisible= */ true + ); + + assertThat( + Settings.Secure.getStringForUser( + mContentResolver, + Settings.Secure.DEVICE_STATE_ROTATION_LOCK, + UserHandle.USER_CURRENT)) + .isEqualTo("0:1:1:1:2:0"); + } + + @Test + public void onRotationLockStateChange_stateIgnored_noFallback_settingIsPersistedToPrevious() { + initializeSettingsWith( + 0, DEVICE_STATE_ROTATION_LOCK_LOCKED, + 1, DEVICE_STATE_ROTATION_LOCK_UNLOCKED, + 8, DEVICE_STATE_ROTATION_LOCK_IGNORED); + mDeviceStateCallback.onStateChanged(1); + mDeviceStateCallback.onStateChanged(8); + + mDeviceStateRotationLockSettingController.onRotationLockStateChanged( + /* rotationLocked= */ true, + /* affordanceVisible= */ true + ); + + assertThat( + Settings.Secure.getStringForUser( + mContentResolver, + Settings.Secure.DEVICE_STATE_ROTATION_LOCK, + UserHandle.USER_CURRENT)) + .isEqualTo("0:1:1:1:8:0"); + } + private void initializeSettingsWith(int... values) { if (values.length % 2 != 0) { throw new IllegalArgumentException("Expecting key-value pairs"); } StringBuilder sb = new StringBuilder(); - for (int i = 0; i < values.length; sb.append(":")) { + for (int i = 0; i < values.length; ) { + if (i > 0) { + sb.append(":"); + } sb.append(values[i++]).append(":").append(values[i++]); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java index 8ccaf9362454..4a8170fc2955 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java @@ -33,7 +33,7 @@ import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.SysuiTestCase; import com.android.systemui.dump.DumpManager; -import com.android.systemui.shared.system.smartspace.SmartspaceTransitionController; +import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import org.junit.Before; import org.junit.Test; @@ -41,6 +41,8 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import dagger.Lazy; + @SmallTest @TestableLooper.RunWithLooper @RunWith(AndroidTestingRunner.class) @@ -52,9 +54,9 @@ public class KeyguardStateControllerTest extends SysuiTestCase { private LockPatternUtils mLockPatternUtils; private KeyguardStateController mKeyguardStateController; @Mock - private SmartspaceTransitionController mSmartSpaceTransitionController; - @Mock private DumpManager mDumpManager; + @Mock + private Lazy<KeyguardUnlockAnimationController> mKeyguardUnlockAnimationControllerLazy; @Before public void setup() { @@ -63,7 +65,7 @@ public class KeyguardStateControllerTest extends SysuiTestCase { mContext, mKeyguardUpdateMonitor, mLockPatternUtils, - mSmartSpaceTransitionController, + mKeyguardUnlockAnimationControllerLazy, mDumpManager); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java index 087f2e6006cf..2126dda7b310 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java @@ -22,6 +22,7 @@ import static org.mockito.Mockito.when; import android.app.AppOpsManager; import android.content.Intent; +import android.content.pm.UserInfo; import android.location.LocationManager; import android.os.Handler; import android.os.UserHandle; @@ -68,6 +69,8 @@ public class LocationControllerImplTest extends SysuiTestCase { MockitoAnnotations.initMocks(this); when(mUserTracker.getUserId()).thenReturn(UserHandle.USER_SYSTEM); when(mUserTracker.getUserHandle()).thenReturn(UserHandle.SYSTEM); + when(mUserTracker.getUserProfiles()) + .thenReturn(ImmutableList.of(new UserInfo(0, "name", 0))); mDeviceConfigProxy = new DeviceConfigProxyFake(); mTestableLooper = TestableLooper.get(this); @@ -78,7 +81,8 @@ public class LocationControllerImplTest extends SysuiTestCase { new Handler(mTestableLooper.getLooper()), mock(BroadcastDispatcher.class), mock(BootCompleteCache.class), - mUserTracker); + mUserTracker, + mContext.getPackageManager()); mTestableLooper.processAllMessages(); } @@ -161,17 +165,38 @@ public class LocationControllerImplTest extends SysuiTestCase { @Test public void testCallbackNotified_additionalOps() { LocationChangeCallback callback = mock(LocationChangeCallback.class); - mLocationController.addCallback(callback); + mDeviceConfigProxy.setProperty( + DeviceConfig.NAMESPACE_PRIVACY, + SystemUiDeviceConfigFlags.PROPERTY_LOCATION_INDICATORS_SMALL_ENABLED, + "true", + true); + mTestableLooper.processAllMessages(); + + when(mAppOpsController.getActiveAppOps()) + .thenReturn(ImmutableList.of( + new AppOpItem(AppOpsManager.OP_FINE_LOCATION, 0, + "com.google.android.googlequicksearchbox", + System.currentTimeMillis()))); + mLocationController.onActiveStateChanged(AppOpsManager.OP_FINE_LOCATION, 0, + "com.google.android.googlequicksearchbox", true); mTestableLooper.processAllMessages(); - mLocationController.onReceive(mContext, new Intent(LocationManager.MODE_CHANGED_ACTION)); + verify(callback, times(1)).onLocationActiveChanged(true); + when(mAppOpsController.getActiveAppOps()).thenReturn(ImmutableList.of()); + mLocationController.onActiveStateChanged(AppOpsManager.OP_FINE_LOCATION, 0, + "com.google.android.googlequicksearchbox", false); mTestableLooper.processAllMessages(); - verify(callback, times(2)).onLocationSettingsChanged(anyBoolean()); + verify(callback, times(1)).onLocationActiveChanged(false); + } + @Test + public void testCallbackNotified_additionalOps_shouldShowSystem() { + LocationChangeCallback callback = mock(LocationChangeCallback.class); + mLocationController.addCallback(callback); mDeviceConfigProxy.setProperty( DeviceConfig.NAMESPACE_PRIVACY, SystemUiDeviceConfigFlags.PROPERTY_LOCATION_INDICATORS_SMALL_ENABLED, @@ -181,10 +206,40 @@ public class LocationControllerImplTest extends SysuiTestCase { when(mAppOpsController.getActiveAppOps()) .thenReturn(ImmutableList.of( - new AppOpItem(AppOpsManager.OP_FINE_LOCATION, 0, "", + new AppOpItem(AppOpsManager.OP_FINE_LOCATION, 0, + "com.google.android.gms", System.currentTimeMillis()))); mLocationController.onActiveStateChanged(AppOpsManager.OP_FINE_LOCATION, 0, - "", true); + "com.google.android.gms", true); + + mTestableLooper.processAllMessages(); + + verify(callback, times(0)).onLocationActiveChanged(true); + } + + + @Test + public void testCallbackNotified_additionalOps_shouldNotShowSystem() { + LocationChangeCallback callback = mock(LocationChangeCallback.class); + mLocationController.addCallback(callback); + mDeviceConfigProxy.setProperty( + DeviceConfig.NAMESPACE_PRIVACY, + SystemUiDeviceConfigFlags.PROPERTY_LOCATION_INDICATORS_SMALL_ENABLED, + "true", + true); + mDeviceConfigProxy.setProperty( + DeviceConfig.NAMESPACE_PRIVACY, + SystemUiDeviceConfigFlags.PROPERTY_LOCATION_INDICATORS_SHOW_SYSTEM, + "true", + true); + mTestableLooper.processAllMessages(); + + when(mAppOpsController.getActiveAppOps()) + .thenReturn(ImmutableList.of( + new AppOpItem(AppOpsManager.OP_FINE_LOCATION, 0, "com.google.android.gms", + System.currentTimeMillis()))); + mLocationController.onActiveStateChanged(AppOpsManager.OP_FINE_LOCATION, 0, + "com.google.android.gms", true); mTestableLooper.processAllMessages(); @@ -192,7 +247,7 @@ public class LocationControllerImplTest extends SysuiTestCase { when(mAppOpsController.getActiveAppOps()).thenReturn(ImmutableList.of()); mLocationController.onActiveStateChanged(AppOpsManager.OP_FINE_LOCATION, 0, - "", false); + "com.google.android.gms", false); mTestableLooper.processAllMessages(); verify(callback, times(1)).onLocationActiveChanged(false); diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldLatencyTrackerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldLatencyTrackerTest.kt index f600b12993b9..4e2736c74007 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldLatencyTrackerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldLatencyTrackerTest.kt @@ -68,7 +68,7 @@ class UnfoldLatencyTrackerTest : SysuiTestCase() { context.mainExecutor, context, screenLifecycle - ) + ).apply { init() } deviceStates = FoldableTestUtils.findDeviceStates(context) verify(deviceStateManager).registerCallback(any(), foldStateListenerCaptor.capture()) diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeKeyguardStateController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeKeyguardStateController.java index e136d00b86f8..aaea4ecdc08d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeKeyguardStateController.java +++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeKeyguardStateController.java @@ -129,11 +129,6 @@ public class FakeKeyguardStateController implements KeyguardStateController { } @Override - public boolean canPerformSmartSpaceTransition() { - return false; - } - - @Override public boolean isKeyguardScreenRotationAllowed() { return false; } diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java index 07351cf386ac..9c49e98b9e36 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java @@ -18,6 +18,8 @@ package com.android.systemui.wmshell; import static android.app.Notification.FLAG_BUBBLE; import static android.app.PendingIntent.FLAG_MUTABLE; +import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_DELETED; +import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_UPDATED; import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL; import static android.service.notification.NotificationListenerService.REASON_CANCEL; import static android.service.notification.NotificationListenerService.REASON_CANCEL_ALL; @@ -1285,6 +1287,58 @@ public class BubblesTest extends SysuiTestCase { assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */); } + @Test + public void testNotificationChannelModified_channelUpdated_removesOverflowBubble() + throws Exception { + // Setup + ExpandableNotificationRow row = mNotificationTestHelper.createShortcutBubble("shortcutId"); + NotificationEntry entry = row.getEntry(); + entry.getChannel().setConversationId( + row.getEntry().getChannel().getParentChannelId(), + "shortcutId"); + mBubbleController.updateBubble(BubblesManager.notifToBubbleEntry(row.getEntry())); + assertTrue(mBubbleController.hasBubbles()); + + // Overflow it + mBubbleData.dismissBubbleWithKey(entry.getKey(), + Bubbles.DISMISS_USER_GESTURE); + assertThat(mBubbleData.hasOverflowBubbleWithKey(entry.getKey())).isTrue(); + + // Test + entry.getChannel().setDeleted(true); + mBubbleController.onNotificationChannelModified(entry.getSbn().getPackageName(), + entry.getSbn().getUser(), + entry.getChannel(), + NOTIFICATION_CHANNEL_OR_GROUP_UPDATED); + assertThat(mBubbleData.hasOverflowBubbleWithKey(entry.getKey())).isFalse(); + } + + @Test + public void testNotificationChannelModified_channelDeleted_removesOverflowBubble() + throws Exception { + // Setup + ExpandableNotificationRow row = mNotificationTestHelper.createShortcutBubble("shortcutId"); + NotificationEntry entry = row.getEntry(); + entry.getChannel().setConversationId( + row.getEntry().getChannel().getParentChannelId(), + "shortcutId"); + mBubbleController.updateBubble(BubblesManager.notifToBubbleEntry(row.getEntry())); + assertTrue(mBubbleController.hasBubbles()); + + // Overflow it + mBubbleData.dismissBubbleWithKey(entry.getKey(), + Bubbles.DISMISS_USER_GESTURE); + assertThat(mBubbleData.hasOverflowBubbleWithKey(entry.getKey())).isTrue(); + + // Test + entry.getChannel().setDeleted(true); + mBubbleController.onNotificationChannelModified(entry.getSbn().getPackageName(), + entry.getSbn().getUser(), + entry.getChannel(), + NOTIFICATION_CHANNEL_OR_GROUP_DELETED); + assertThat(mBubbleData.hasOverflowBubbleWithKey(entry.getKey())).isFalse(); + } + /** Creates a bubble using the userId and package. */ private Bubble createBubble(int userId, String pkg) { final UserHandle userHandle = new UserHandle(userId); diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java index 9eee83ccb6e2..e12a82a1c62b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java @@ -17,6 +17,8 @@ package com.android.systemui.wmshell; import static android.app.Notification.FLAG_BUBBLE; +import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_DELETED; +import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_UPDATED; import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL; import static android.service.notification.NotificationListenerService.REASON_GROUP_SUMMARY_CANCELED; @@ -1104,6 +1106,58 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase { assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */); } + @Test + public void testNotificationChannelModified_channelUpdated_removesOverflowBubble() + throws Exception { + // Setup + ExpandableNotificationRow row = mNotificationTestHelper.createShortcutBubble("shortcutId"); + NotificationEntry entry = row.getEntry(); + entry.getChannel().setConversationId( + row.getEntry().getChannel().getParentChannelId(), + "shortcutId"); + mBubbleController.updateBubble(BubblesManager.notifToBubbleEntry(row.getEntry())); + assertTrue(mBubbleController.hasBubbles()); + + // Overflow it + mBubbleData.dismissBubbleWithKey(entry.getKey(), + Bubbles.DISMISS_USER_GESTURE); + assertThat(mBubbleData.hasOverflowBubbleWithKey(entry.getKey())).isTrue(); + + // Test + entry.getChannel().setDeleted(true); + mBubbleController.onNotificationChannelModified(entry.getSbn().getPackageName(), + entry.getSbn().getUser(), + entry.getChannel(), + NOTIFICATION_CHANNEL_OR_GROUP_UPDATED); + assertThat(mBubbleData.hasOverflowBubbleWithKey(entry.getKey())).isFalse(); + } + + @Test + public void testNotificationChannelModified_channelDeleted_removesOverflowBubble() + throws Exception { + // Setup + ExpandableNotificationRow row = mNotificationTestHelper.createShortcutBubble("shortcutId"); + NotificationEntry entry = row.getEntry(); + entry.getChannel().setConversationId( + row.getEntry().getChannel().getParentChannelId(), + "shortcutId"); + mBubbleController.updateBubble(BubblesManager.notifToBubbleEntry(row.getEntry())); + assertTrue(mBubbleController.hasBubbles()); + + // Overflow it + mBubbleData.dismissBubbleWithKey(entry.getKey(), + Bubbles.DISMISS_USER_GESTURE); + assertThat(mBubbleData.hasOverflowBubbleWithKey(entry.getKey())).isTrue(); + + // Test + entry.getChannel().setDeleted(true); + mBubbleController.onNotificationChannelModified(entry.getSbn().getPackageName(), + entry.getSbn().getUser(), + entry.getChannel(), + NOTIFICATION_CHANNEL_OR_GROUP_DELETED); + assertThat(mBubbleData.hasOverflowBubbleWithKey(entry.getKey())).isFalse(); + } + /** * Sets the bubble metadata flags for this entry. These flags are normally set by * NotificationManagerService when the notification is sent, however, these tests do not diff --git a/proto/src/camera.proto b/proto/src/camera.proto index d07a525fdffa..0338b93c8842 100644 --- a/proto/src/camera.proto +++ b/proto/src/camera.proto @@ -62,4 +62,7 @@ message CameraStreamProto { // The frame counts for each histogram bins // Expected number of fields: 10 repeated int64 histogram_counts = 13; + + // The dynamic range profile of the stream + optional int32 dynamic_range_profile = 14; } diff --git a/services/Android.bp b/services/Android.bp index f33c8c0dae15..26760aa3765a 100644 --- a/services/Android.bp +++ b/services/Android.bp @@ -179,6 +179,10 @@ cc_library_shared { name: "libandroid_servers", defaults: ["libservices.core-libs"], whole_static_libs: ["libservices.core"], + required: [ + // TODO: remove after NetworkStatsService is moved to the mainline module. + "libcom_android_net_module_util_jni", + ], } platform_compat_config { diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 91e909357bc4..5b580d9d829c 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -3497,6 +3497,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub pw.append("hasWindowMagnificationConnection=").append( String.valueOf(getWindowMagnificationMgr().isConnected())); pw.println(); + mMagnificationProcessor.dump(pw, getValidDisplayList()); final int userCount = mUserStates.size(); for (int i = 0; i < userCount; i++) { mUserStates.valueAt(i).dump(fd, pw, args); diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java index 8f15d5cf3e3d..77e3ee51125e 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java @@ -26,6 +26,10 @@ import static android.view.accessibility.MagnificationAnimationCallback.STUB_ANI import android.accessibilityservice.MagnificationConfig; import android.annotation.NonNull; import android.graphics.Region; +import android.view.Display; + +import java.io.PrintWriter; +import java.util.ArrayList; /** * Processor class for AccessibilityService connection to control magnification on the specified @@ -360,4 +364,29 @@ public class MagnificationProcessor { private void unregister(int displayId) { mController.getFullScreenMagnificationController().unregister(displayId); } + + /** Dumps {@link MagnificationConfig} and magnification region of magnifiers on the displays. */ + public void dump(final PrintWriter pw, ArrayList<Display> displaysList) { + for (int i = 0; i < displaysList.size(); i++) { + final int displayId = displaysList.get(i).getDisplayId(); + final MagnificationConfig config = getMagnificationConfig(displayId); + pw.println("Magnifier on display#" + displayId); + pw.append(" " + config).println(); + final Region region = new Region(); + getCurrentMagnificationRegion(displayId, region, true); + if (!region.isEmpty()) { + pw.append(" Magnification region=").append(region.toString()).println(); + } + pw.append(" IdOfLastServiceToMagnify=" + + getIdOfLastServiceToMagnify(config.getMode(), displayId)).println(); + } + } + + private int getIdOfLastServiceToMagnify(int mode, int displayId) { + return (mode == MAGNIFICATION_MODE_FULLSCREEN) + ? mController.getFullScreenMagnificationController() + .getIdOfLastServiceToMagnify(displayId) + : mController.getWindowMagnificationMgr().getIdOfLastServiceToMagnify( + displayId); + } } diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java index 95c7d446d75e..2fbefa4f14c9 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java @@ -319,6 +319,22 @@ public class WindowMagnificationManager implements } /** + * Get the ID of the last service that changed the magnification config. + * + * @param displayId The logical display id. + * @return The id + */ + public int getIdOfLastServiceToMagnify(int displayId) { + synchronized (mLock) { + final WindowMagnifier magnifier = mWindowMagnifiers.get(displayId); + if (magnifier != null) { + return magnifier.mIdOfLastServiceToControl; + } + } + return INVALID_SERVICE_ID; + } + + /** * Enable or disable tracking typing focus for the specific magnification window. * * The tracking typing focus should be set to enabled with the following conditions: @@ -466,6 +482,7 @@ public class WindowMagnificationManager implements boolean previousEnabled; synchronized (mLock) { if (mConnectionWrapper == null) { + Slog.w(TAG, "enableWindowMagnification failed: connection null"); return false; } WindowMagnifier magnifier = mWindowMagnifiers.get(displayId); @@ -508,6 +525,7 @@ public class WindowMagnificationManager implements synchronized (mLock) { WindowMagnifier magnifier = mWindowMagnifiers.get(displayId); if (magnifier == null || mConnectionWrapper == null) { + Slog.w(TAG, "disableWindowMagnification failed: connection " + mConnectionWrapper); return false; } disabled = magnifier.disableWindowMagnificationInternal(animationCallback); diff --git a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java b/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java index 93fc0e7262aa..2b7b97737e0f 100644 --- a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java +++ b/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java @@ -254,9 +254,14 @@ class AssociationRequestsProcessor { private AssociationInfo createAssociationAndNotifyApplication( @NonNull AssociationRequest request, @NonNull String packageName, @UserIdInt int userId, @Nullable MacAddress macAddress, @NonNull IAssociationRequestCallback callback) { - final AssociationInfo association = mService.createAssociation(userId, packageName, - macAddress, request.getDisplayName(), request.getDeviceProfile(), - request.isSelfManaged()); + final AssociationInfo association; + final long callingIdentity = Binder.clearCallingIdentity(); + try { + association = mService.createAssociation(userId, packageName, macAddress, + request.getDisplayName(), request.getDeviceProfile(), request.isSelfManaged()); + } finally { + Binder.restoreCallingIdentity(callingIdentity); + } try { callback.onAssociationCreated(association); diff --git a/services/companion/java/com/android/server/companion/AssociationStore.java b/services/companion/java/com/android/server/companion/AssociationStore.java index 58fc8f7fe5b6..01905bb2297f 100644 --- a/services/companion/java/com/android/server/companion/AssociationStore.java +++ b/services/companion/java/com/android/server/companion/AssociationStore.java @@ -49,7 +49,25 @@ public interface AssociationStore { /** Listener for any changes to {@link AssociationInfo}-s. */ interface OnChangeListener { default void onAssociationChanged( - @ChangeType int changeType, AssociationInfo association) {} + @ChangeType int changeType, AssociationInfo association) { + switch (changeType) { + case CHANGE_TYPE_ADDED: + onAssociationAdded(association); + break; + + case CHANGE_TYPE_REMOVED: + onAssociationRemoved(association); + break; + + case CHANGE_TYPE_UPDATED_ADDRESS_CHANGED: + onAssociationUpdated(association, true); + break; + + case CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED: + onAssociationUpdated(association, false); + break; + } + } default void onAssociationAdded(AssociationInfo association) {} diff --git a/services/companion/java/com/android/server/companion/AssociationStoreImpl.java b/services/companion/java/com/android/server/companion/AssociationStoreImpl.java index dbcdd0f877a1..cda554e9d0cf 100644 --- a/services/companion/java/com/android/server/companion/AssociationStoreImpl.java +++ b/services/companion/java/com/android/server/companion/AssociationStoreImpl.java @@ -125,8 +125,7 @@ class AssociationStoreImpl implements AssociationStore { // Update the MacAddress-to-List<Association> map if needed. final MacAddress updatedAddress = updated.getDeviceMacAddress(); final MacAddress currentAddress = current.getDeviceMacAddress(); - macAddressChanged = Objects.equals( - current.getDeviceMacAddress(), updated.getDeviceMacAddress()); + macAddressChanged = Objects.equals(currentAddress, updatedAddress); if (macAddressChanged) { if (currentAddress != null) { mAddressMap.get(currentAddress).remove(id); @@ -213,11 +212,9 @@ class AssociationStoreImpl implements AssociationStore { final Set<Integer> ids = mAddressMap.get(address); if (ids == null) return Collections.emptyList(); - final List<AssociationInfo> associations = new ArrayList<>(); - for (AssociationInfo association : mIdMap.values()) { - if (address.equals(association.getDeviceMacAddress())) { - associations.add(association); - } + final List<AssociationInfo> associations = new ArrayList<>(ids.size()); + for (Integer id : ids) { + associations.add(mIdMap.get(id)); } return Collections.unmodifiableList(associations); @@ -263,24 +260,6 @@ class AssociationStoreImpl implements AssociationStore { synchronized (mListeners) { for (OnChangeListener listener : mListeners) { listener.onAssociationChanged(changeType, association); - - switch (changeType) { - case CHANGE_TYPE_ADDED: - listener.onAssociationAdded(association); - break; - - case CHANGE_TYPE_REMOVED: - listener.onAssociationRemoved(association); - break; - - case CHANGE_TYPE_UPDATED_ADDRESS_CHANGED: - listener.onAssociationUpdated(association, true); - break; - - case CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED: - listener.onAssociationUpdated(association, false); - break; - } } } } diff --git a/services/companion/java/com/android/server/companion/CompanionApplicationController.java b/services/companion/java/com/android/server/companion/CompanionApplicationController.java new file mode 100644 index 000000000000..be1bc7907cd5 --- /dev/null +++ b/services/companion/java/com/android/server/companion/CompanionApplicationController.java @@ -0,0 +1,317 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.companion; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SuppressLint; +import android.annotation.UserIdInt; +import android.companion.AssociationInfo; +import android.companion.CompanionDeviceService; +import android.content.ComponentName; +import android.content.Context; +import android.os.Handler; +import android.util.Log; +import android.util.SparseArray; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.infra.PerUser; +import com.android.internal.util.CollectionUtils; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Manages communication with companion applications via + * {@link android.companion.ICompanionDeviceService} interface, including "connecting" (binding) to + * the services, maintaining the connection (the binding), and invoking callback methods such as + * {@link CompanionDeviceService#onDeviceAppeared(AssociationInfo)} and + * {@link CompanionDeviceService#onDeviceDisappeared(AssociationInfo)} in the application process. + * + * <p> + * The following is the list of the APIs provided by {@link CompanionApplicationController} (to be + * utilized by {@link CompanionDeviceManagerService}): + * <ul> + * <li> {@link #bindCompanionApplication(int, String)} + * <li> {@link #unbindCompanionApplication(int, String)} + * <li> {@link #notifyCompanionApplicationDeviceAppeared(AssociationInfo)} + * <li> {@link #notifyCompanionApplicationDeviceDisappeared(AssociationInfo)} + * <li> {@link #isCompanionApplicationBound(int, String)} + * <li> {@link #isRebindingCompanionApplicationScheduled(int, String)} + * </ul> + * + * @see CompanionDeviceService + * @see android.companion.ICompanionDeviceService + * @see CompanionDeviceServiceConnector + */ +@SuppressLint("LongLogTag") +class CompanionApplicationController { + static final boolean DEBUG = false; + private static final String TAG = "CompanionDevice_ApplicationController"; + + private static final long REBIND_TIMEOUT = 10 * 1000; // 10 sec + + interface Callback { + /** + * @return {@code true} if should schedule rebinding. + * {@code false} if we do not need to rebind. + */ + boolean onCompanionApplicationBindingDied( + @UserIdInt int userId, @NonNull String packageName); + + /** + * Callback after timeout for previously scheduled rebind has passed. + */ + void onRebindCompanionApplicationTimeout( + @UserIdInt int userId, @NonNull String packageName); + } + + private final @NonNull Context mContext; + private final @NonNull Callback mCallback; + private final @NonNull CompanionServicesRegister mCompanionServicesRegister; + @GuardedBy("mBoundCompanionApplications") + private final @NonNull AndroidPackageMap<List<CompanionDeviceServiceConnector>> + mBoundCompanionApplications; + private final @NonNull AndroidPackageMap<Boolean> mScheduledForRebindingCompanionApplications; + + CompanionApplicationController(Context context, Callback callback) { + mContext = context; + mCallback = callback; + mCompanionServicesRegister = new CompanionServicesRegister(); + mBoundCompanionApplications = new AndroidPackageMap<>(); + mScheduledForRebindingCompanionApplications = new AndroidPackageMap<>(); + } + + void onPackagesChanged(@UserIdInt int userId) { + mCompanionServicesRegister.invalidate(userId); + } + + void bindCompanionApplication(@UserIdInt int userId, @NonNull String packageName) { + if (DEBUG) Log.i(TAG, "bind() u" + userId + "/" + packageName); + + final List<ComponentName> companionServices = + mCompanionServicesRegister.forPackage(userId, packageName); + final List<CompanionDeviceServiceConnector> serviceConnectors; + + synchronized (mBoundCompanionApplications) { + if (mBoundCompanionApplications.containsValueForPackage(userId, packageName)) { + if (DEBUG) Log.e(TAG, "u" + userId + "/" + packageName + " is ALREADY bound."); + return; + } + + serviceConnectors = CollectionUtils.map(companionServices, componentName -> + new CompanionDeviceServiceConnector(mContext, userId, componentName)); + mBoundCompanionApplications.setValueForPackage(userId, packageName, serviceConnectors); + } + + // The first connector in the list is always the primary connector: set a listener to it. + serviceConnectors.get(0).setListener(this::onPrimaryServiceBindingDied); + + // Now "bind" all the connectors: the primary one and the rest of them. + for (CompanionDeviceServiceConnector serviceConnector : serviceConnectors) { + serviceConnector.connect(); + } + } + + void unbindCompanionApplication(@UserIdInt int userId, @NonNull String packageName) { + if (DEBUG) Log.i(TAG, "unbind() u" + userId + "/" + packageName); + + final List<CompanionDeviceServiceConnector> serviceConnectors; + synchronized (mBoundCompanionApplications) { + serviceConnectors = mBoundCompanionApplications.removePackage(userId, packageName); + } + if (serviceConnectors == null) { + if (DEBUG) Log.e(TAG, "u" + userId + "/" + packageName + " is NOT bound"); + return; + } + + for (CompanionDeviceServiceConnector serviceConnector : serviceConnectors) { + serviceConnector.postUnbind(); + } + } + + boolean isCompanionApplicationBound(@UserIdInt int userId, @NonNull String packageName) { + synchronized (mBoundCompanionApplications) { + return mBoundCompanionApplications.containsValueForPackage(userId, packageName); + } + } + + private void scheduleRebinding(@UserIdInt int userId, @NonNull String packageName) { + mScheduledForRebindingCompanionApplications.setValueForPackage(userId, packageName, true); + + Handler.getMain().postDelayed(() -> + onRebindingCompanionApplicationTimeout(userId, packageName), REBIND_TIMEOUT); + } + + boolean isRebindingCompanionApplicationScheduled( + @UserIdInt int userId, @NonNull String packageName) { + return mScheduledForRebindingCompanionApplications + .containsValueForPackage(userId, packageName); + } + + private void onRebindingCompanionApplicationTimeout( + @UserIdInt int userId, @NonNull String packageName) { + mScheduledForRebindingCompanionApplications.removePackage(userId, packageName); + + mCallback.onRebindCompanionApplicationTimeout(userId, packageName); + } + + void notifyCompanionApplicationDeviceAppeared(AssociationInfo association) { + final int userId = association.getUserId(); + final String packageName = association.getPackageName(); + if (DEBUG) { + Log.i(TAG, "notifyDevice_Appeared() id=" + association.getId() + " u" + userId + + "/" + packageName); + } + + final CompanionDeviceServiceConnector primaryServiceConnector = + getPrimaryServiceConnector(userId, packageName); + if (primaryServiceConnector == null) { + if (DEBUG) Log.e(TAG, "u" + userId + "/" + packageName + " is NOT bound."); + return; + } + + primaryServiceConnector.postOnDeviceAppeared(association); + } + + void notifyCompanionApplicationDeviceDisappeared(AssociationInfo association) { + final int userId = association.getUserId(); + final String packageName = association.getPackageName(); + if (DEBUG) { + Log.i(TAG, "notifyDevice_Disappeared() id=" + association.getId() + " u" + userId + + "/" + packageName); + } + + final CompanionDeviceServiceConnector primaryServiceConnector = + getPrimaryServiceConnector(userId, packageName); + if (primaryServiceConnector == null) { + if (DEBUG) Log.e(TAG, "u" + userId + "/" + packageName + " is NOT bound."); + return; + } + + primaryServiceConnector.postOnDeviceDisappeared(association); + } + + private void onPrimaryServiceBindingDied(@UserIdInt int userId, @NonNull String packageName) { + if (DEBUG) Log.i(TAG, "onPrimaryServiceBindingDied() u" + userId + "/" + packageName); + + // First: mark as NOT bound. + synchronized (mBoundCompanionApplications) { + mBoundCompanionApplications.removePackage(userId, packageName); + } + + // Second: invoke callback, schedule rebinding if needed. + final boolean shouldScheduleRebind = + mCallback.onCompanionApplicationBindingDied(userId, packageName); + if (shouldScheduleRebind) { + scheduleRebinding(userId, packageName); + } + } + + private @Nullable CompanionDeviceServiceConnector getPrimaryServiceConnector( + @UserIdInt int userId, @NonNull String packageName) { + final List<CompanionDeviceServiceConnector> connectors; + synchronized (mBoundCompanionApplications) { + connectors = mBoundCompanionApplications.getValueForPackage(userId, packageName); + } + return connectors != null ? connectors.get(0) : null; + } + + private class CompanionServicesRegister extends PerUser<Map<String, List<ComponentName>>> { + @Override + public synchronized @NonNull Map<String, List<ComponentName>> forUser( + @UserIdInt int userId) { + return super.forUser(userId); + } + + synchronized @NonNull List<ComponentName> forPackage( + @UserIdInt int userId, @NonNull String packageName) { + return forUser(userId).getOrDefault(packageName, Collections.emptyList()); + } + + synchronized @NonNull ComponentName primaryForPackage( + @UserIdInt int userId, @NonNull String packageName) { + // The primary service is always at the head of the list. + return forPackage(userId, packageName).get(0); + } + + synchronized void invalidate(@UserIdInt int userId) { + remove(userId); + } + + @Override + protected final @NonNull Map<String, List<ComponentName>> create(@UserIdInt int userId) { + return PackageUtils.getCompanionServicesForUser(mContext, userId); + } + } + + /** + * Associates an Android package (defined by userId + packageName) with a value of type T. + */ + private static class AndroidPackageMap<T> extends SparseArray<Map<String, T>> { + + void setValueForPackage( + @UserIdInt int userId, @NonNull String packageName, @NonNull T value) { + Map<String, T> forUser = get(userId); + if (forUser == null) { + forUser = /* Map<String, T> */ new HashMap(); + put(userId, forUser); + } + + forUser.put(packageName, value); + } + + boolean containsValueForPackage(@UserIdInt int userId, @NonNull String packageName) { + final Map<String, ?> forUser = get(userId); + return forUser != null && forUser.containsKey(packageName); + } + + T getValueForPackage(@UserIdInt int userId, @NonNull String packageName) { + final Map<String, T> forUser = get(userId); + return forUser != null ? forUser.get(packageName) : null; + } + + T removePackage(@UserIdInt int userId, @NonNull String packageName) { + final Map<String, T> forUser = get(userId); + if (forUser == null) return null; + return forUser.remove(packageName); + } + + void dump() { + if (size() == 0) { + Log.d(TAG, "<empty>"); + return; + } + + for (int i = 0; i < size(); i++) { + final int userId = keyAt(i); + final Map<String, T> forUser = get(userId); + if (forUser.isEmpty()) { + Log.d(TAG, "u" + userId + ": <empty>"); + } + + for (Map.Entry<String, T> packageValue : forUser.entrySet()) { + final String packageName = packageValue.getKey(); + final T value = packageValue.getValue(); + Log.d(TAG, "u" + userId + "\\" + packageName + " -> " + value); + } + } + } + } +} diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java index 1e50c80f0e71..cfd37988d234 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java @@ -77,11 +77,13 @@ import android.content.pm.PackageInfo; import android.content.pm.PackageItemInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; +import android.content.pm.UserInfo; import android.net.MacAddress; import android.net.NetworkPolicyManager; import android.os.Binder; import android.os.Environment; import android.os.Handler; +import android.os.Message; import android.os.Parcel; import android.os.PowerWhitelistManager; import android.os.RemoteCallbackList; @@ -185,6 +187,7 @@ public class CompanionDeviceManagerService extends SystemService private final CompanionDeviceManagerServiceInternal mLocalService = new LocalService(this); final Handler mMainHandler = Handler.getMain(); + private final PersistUserStateHandler mUserPersistenceHandler = new PersistUserStateHandler(); private CompanionDevicePresenceController mCompanionDevicePresenceController; /** @@ -366,9 +369,8 @@ public class CompanionDeviceManagerService extends SystemService final List<AssociationInfo> updatedAssociations = mAssociationStore.getAssociationsForUser(userId); - final Map<String, Set<Integer>> usedIdsForUser = getPreviouslyUsedIdsForUser(userId); - BackgroundThread.getHandler().post(() -> - mPersistentStore.persistStateForUser(userId, updatedAssociations, usedIdsForUser)); + + mUserPersistenceHandler.postPersistUserState(userId); // Notify listeners if ADDED, REMOVED or UPDATED_ADDRESS_CHANGED. // Do NOT notify when UPDATED_ADDRESS_UNCHANGED, which means a minor tweak in association's @@ -381,6 +383,13 @@ public class CompanionDeviceManagerService extends SystemService restartBleScan(); } + private void persistStateForUser(@UserIdInt int userId) { + final List<AssociationInfo> updatedAssociations = + mAssociationStore.getAssociationsForUser(userId); + final Map<String, Set<Integer>> usedIdsForUser = getPreviouslyUsedIdsForUser(userId); + mPersistentStore.persistStateForUser(userId, updatedAssociations, usedIdsForUser); + } + private void notifyListeners( @UserIdInt int userId, @NonNull List<AssociationInfo> associations) { mListeners.broadcast((listener, callbackUserId) -> { @@ -778,6 +787,12 @@ public class CompanionDeviceManagerService extends SystemService Slog.i(LOG_TAG, "New CDM association created=" + association); mAssociationStore.addAssociation(association); + // If the "Device Profile" is specified, make the companion application a holder of the + // corresponding role. + if (deviceProfile != null) { + addRoleHolderForAssociation(getContext(), association); + } + updateSpecialAccessPermissionForAssociatedPackage(association); return association; @@ -921,14 +936,8 @@ public class CompanionDeviceManagerService extends SystemService exemptFromAutoRevoke(packageInfo.packageName, packageInfo.applicationInfo.uid); - if (!association.isSelfManaged()) { - if (mCurrentlyConnectedDevices.contains(association.getDeviceMacAddressAsString())) { - addRoleHolderForAssociation(getContext(), association); - } - - if (association.isNotifyOnDeviceNearby()) { - restartBleScan(); - } + if (association.isNotifyOnDeviceNearby()) { + restartBleScan(); } } @@ -974,19 +983,7 @@ public class CompanionDeviceManagerService extends SystemService void onDeviceConnected(String address) { Slog.d(LOG_TAG, "onDeviceConnected(address = " + address + ")"); - mCurrentlyConnectedDevices.add(address); - - for (AssociationInfo association : mAssociationStore.getAssociationsByAddress(address)) { - if (association.getDeviceProfile() != null) { - Slog.i(LOG_TAG, "Granting role " + association.getDeviceProfile() - + " to " + association.getPackageName() - + " due to device connected: " + association.getDeviceMacAddress()); - - addRoleHolderForAssociation(getContext(), association); - } - } - onDeviceNearby(address); } @@ -1349,4 +1346,47 @@ public class CompanionDeviceManagerService extends SystemService mService.associationCleanUp(profile); } } + + /** + * This method must only be called from {@link CompanionDeviceShellCommand} for testing + * purposes only! + */ + void persistState() { + mUserPersistenceHandler.clearMessages(); + for (UserInfo user : mUserManager.getAliveUsers()) { + persistStateForUser(user.id); + } + } + + /** + * This class is dedicated to handling requests to persist user state. + */ + @SuppressLint("HandlerLeak") + private class PersistUserStateHandler extends Handler { + PersistUserStateHandler() { + super(BackgroundThread.get().getLooper()); + } + + /** + * Persists user state unless there is already an outstanding request for the given user. + */ + synchronized void postPersistUserState(@UserIdInt int userId) { + if (!hasMessages(userId)) { + sendMessage(obtainMessage(userId)); + } + } + + /** + * Clears *ALL* outstanding persist requests for *ALL* users. + */ + synchronized void clearMessages() { + removeCallbacksAndMessages(null); + } + + @Override + public void handleMessage(@NonNull Message msg) { + final int userId = msg.what; + persistStateForUser(userId); + } + } } diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java index 5f46d5c4c4bf..f2e660779e9e 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java @@ -73,19 +73,12 @@ class CompanionDeviceShellCommand extends android.os.ShellCommand { } break; - case "simulate_connect": { - mService.onDeviceConnected(getNextArgRequired()); - } - break; - - case "simulate_disconnect": { - mService.onDeviceDisconnected(getNextArgRequired()); - } - break; case "clear-association-memory-cache": { + mService.persistState(); mService.loadAssociationsFromDisk(); } break; + default: return handleDefaultCommands(cmd); } diff --git a/services/companion/java/com/android/server/companion/DataStoreUtils.java b/services/companion/java/com/android/server/companion/DataStoreUtils.java new file mode 100644 index 000000000000..6055a81c7ca7 --- /dev/null +++ b/services/companion/java/com/android/server/companion/DataStoreUtils.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.companion; + +import static org.xmlpull.v1.XmlPullParser.END_TAG; +import static org.xmlpull.v1.XmlPullParser.START_TAG; + +import android.annotation.NonNull; +import android.annotation.UserIdInt; +import android.os.Environment; +import android.util.AtomicFile; +import android.util.Slog; + +import com.android.internal.util.FunctionalUtils; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.File; +import java.io.FileOutputStream; + +final class DataStoreUtils { + + private static final String LOG_TAG = DataStoreUtils.class.getSimpleName(); + + static boolean isStartOfTag(@NonNull XmlPullParser parser, @NonNull String tag) + throws XmlPullParserException { + return parser.getEventType() == START_TAG && tag.equals(parser.getName()); + } + + static boolean isEndOfTag(@NonNull XmlPullParser parser, @NonNull String tag) + throws XmlPullParserException { + return parser.getEventType() == END_TAG && tag.equals(parser.getName()); + } + + /** + * Creates {@link AtomicFile} object that represents the back-up for the given user. + * + * IMPORTANT: the method will ALWAYS return the same {@link AtomicFile} object, which makes it + * possible to synchronize reads and writes to the file using the returned object. + * + * @param userId the userId to retrieve the storage file + * @param fileName the storage file name + * @return an AtomicFile for the user + */ + @NonNull + static AtomicFile createStorageFileForUser(@UserIdInt int userId, String fileName) { + return new AtomicFile(getBaseStorageFileForUser(userId, fileName)); + } + + @NonNull + private static File getBaseStorageFileForUser(@UserIdInt int userId, String fileName) { + return new File(Environment.getDataSystemDeDirectory(userId), fileName); + } + + /** + * Writing to file could fail, for example, if the user has been recently removed and so was + * their DE (/data/system_de/<user-id>/) directory. + */ + static void writeToFileSafely(@NonNull AtomicFile file, + @NonNull FunctionalUtils.ThrowingConsumer<FileOutputStream> consumer) { + try { + file.write(consumer); + } catch (Exception e) { + Slog.e(LOG_TAG, "Error while writing to file " + file, e); + } + } + + private DataStoreUtils() { + } +} diff --git a/services/companion/java/com/android/server/companion/PersistentDataStore.java b/services/companion/java/com/android/server/companion/PersistentDataStore.java index 3c8c3cb8f842..da33b4446840 100644 --- a/services/companion/java/com/android/server/companion/PersistentDataStore.java +++ b/services/companion/java/com/android/server/companion/PersistentDataStore.java @@ -25,9 +25,10 @@ import static com.android.internal.util.XmlUtils.writeBooleanAttribute; import static com.android.internal.util.XmlUtils.writeIntAttribute; import static com.android.internal.util.XmlUtils.writeLongAttribute; import static com.android.internal.util.XmlUtils.writeStringAttribute; - -import static org.xmlpull.v1.XmlPullParser.END_TAG; -import static org.xmlpull.v1.XmlPullParser.START_TAG; +import static com.android.server.companion.DataStoreUtils.createStorageFileForUser; +import static com.android.server.companion.DataStoreUtils.isEndOfTag; +import static com.android.server.companion.DataStoreUtils.isStartOfTag; +import static com.android.server.companion.DataStoreUtils.writeToFileSafely; import android.annotation.NonNull; import android.annotation.Nullable; @@ -44,7 +45,6 @@ import android.util.TypedXmlPullParser; import android.util.TypedXmlSerializer; import android.util.Xml; -import com.android.internal.util.FunctionalUtils.ThrowingConsumer; import com.android.internal.util.XmlUtils; import org.xmlpull.v1.XmlPullParser; @@ -53,7 +53,6 @@ import org.xmlpull.v1.XmlSerializer; import java.io.File; import java.io.FileInputStream; -import java.io.FileOutputStream; import java.io.IOException; import java.util.Collection; import java.util.HashSet; @@ -210,6 +209,7 @@ final class PersistentDataStore { @NonNull Collection<AssociationInfo> associationsOut, @NonNull Map<String, Set<Integer>> previouslyUsedIdsPerPackageOut) { Slog.i(LOG_TAG, "Reading associations for user " + userId + " from disk"); + final AtomicFile file = getStorageFileForUser(userId); if (DEBUG) Slog.d(LOG_TAG, " > File=" + file.getBaseFile().getPath()); @@ -349,11 +349,7 @@ final class PersistentDataStore { */ private @NonNull AtomicFile getStorageFileForUser(@UserIdInt int userId) { return mUserIdToStorageFile.computeIfAbsent(userId, - u -> new AtomicFile(getBaseStorageFileForUser(userId))); - } - - private static @NonNull File getBaseStorageFileForUser(@UserIdInt int userId) { - return new File(Environment.getDataSystemDeDirectory(userId), FILE_NAME); + u -> createStorageFileForUser(userId, FILE_NAME)); } private static @NonNull File getBaseLegacyStorageFileForUser(@UserIdInt int userId) { @@ -512,16 +508,6 @@ final class PersistentDataStore { serializer.endTag(null, XML_TAG_PACKAGE); } - private static boolean isStartOfTag(@NonNull XmlPullParser parser, @NonNull String tag) - throws XmlPullParserException { - return parser.getEventType() == START_TAG && tag.equals(parser.getName()); - } - - private static boolean isEndOfTag(@NonNull XmlPullParser parser, @NonNull String tag) - throws XmlPullParserException { - return parser.getEventType() == END_TAG && tag.equals(parser.getName()); - } - private static void requireStartOfTag(@NonNull XmlPullParser parser, @NonNull String tag) throws XmlPullParserException { if (isStartOfTag(parser, tag)) return; @@ -546,13 +532,4 @@ final class PersistentDataStore { } return associationInfo; } - - private static void writeToFileSafely(@NonNull AtomicFile file, - @NonNull ThrowingConsumer<FileOutputStream> consumer) { - try { - file.write(consumer); - } catch (Exception e) { - Slog.e(LOG_TAG, "Error while writing to file " + file, e); - } - } } diff --git a/services/companion/java/com/android/server/companion/SystemDataTransferRequestDataStore.java b/services/companion/java/com/android/server/companion/SystemDataTransferRequestDataStore.java new file mode 100644 index 000000000000..38e5d16842b9 --- /dev/null +++ b/services/companion/java/com/android/server/companion/SystemDataTransferRequestDataStore.java @@ -0,0 +1,226 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.companion; + +import static com.android.internal.util.XmlUtils.readBooleanAttribute; +import static com.android.internal.util.XmlUtils.readIntAttribute; +import static com.android.internal.util.XmlUtils.readThisListXml; +import static com.android.internal.util.XmlUtils.writeBooleanAttribute; +import static com.android.internal.util.XmlUtils.writeIntAttribute; +import static com.android.internal.util.XmlUtils.writeListXml; +import static com.android.server.companion.DataStoreUtils.createStorageFileForUser; +import static com.android.server.companion.DataStoreUtils.isEndOfTag; +import static com.android.server.companion.DataStoreUtils.isStartOfTag; +import static com.android.server.companion.DataStoreUtils.writeToFileSafely; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.UserIdInt; +import android.companion.SystemDataTransferRequest; +import android.util.AtomicFile; +import android.util.Slog; +import android.util.TypedXmlPullParser; +import android.util.TypedXmlSerializer; +import android.util.Xml; + +import com.android.internal.util.XmlUtils; + +import org.xmlpull.v1.XmlPullParserException; + +import java.io.FileInputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * The class is responsible for reading/writing SystemDataTransferRequest records from/to the disk. + * + * The following snippet is a sample XML file stored in the disk. + * <pre>{@code + * <requests> + * <request + * association_id="1" + * is_permission_sync_all_packages="false"> + * <list name="permission_sync_packages"> + * <string>com.sample.app1</string> + * <string>com.sample.app2</string> + * </list> + * </request> + * </requests> + * }</pre> + */ +public class SystemDataTransferRequestDataStore { + + private static final String LOG_TAG = SystemDataTransferRequestDataStore.class.getSimpleName(); + + private static final String FILE_NAME = "companion_device_system_data_transfer_requests.xml"; + + private static final String XML_TAG_REQUESTS = "requests"; + private static final String XML_TAG_REQUEST = "request"; + private static final String XML_TAG_LIST = "list"; + + private static final String XML_ATTR_ASSOCIATION_ID = "association_id"; + private static final String XML_ATTR_IS_PERMISSION_SYNC_ALL_PACKAGES = + "is_permission_sync_all_packages"; + private static final String XML_ATTR_PERMISSION_SYNC_PACKAGES = "permission_sync_packages"; + + private final ConcurrentMap<Integer, AtomicFile> mUserIdToStorageFile = + new ConcurrentHashMap<>(); + + /** + * Reads previously persisted data for the given user + * + * @param userId Android UserID + * @return a list of SystemDataTransferRequest + */ + @NonNull + List<SystemDataTransferRequest> readRequestsForUser(@UserIdInt int userId) { + final AtomicFile file = getStorageFileForUser(userId); + Slog.i(LOG_TAG, "Reading SystemDataTransferRequests for user " + userId + " from " + + "file=" + file.getBaseFile().getPath()); + + // getStorageFileForUser() ALWAYS returns the SAME OBJECT, which allows us to synchronize + // accesses to the file on the file system using this AtomicFile object. + synchronized (file) { + if (!file.getBaseFile().exists()) { + Slog.d(LOG_TAG, "File does not exist -> Abort"); + return Collections.emptyList(); + } + try (FileInputStream in = file.openRead()) { + final TypedXmlPullParser parser = Xml.resolvePullParser(in); + XmlUtils.beginDocument(parser, XML_TAG_REQUESTS); + + return readRequests(parser); + } catch (XmlPullParserException | IOException e) { + Slog.e(LOG_TAG, "Error while reading requests file", e); + return Collections.emptyList(); + } + } + } + + @NonNull + private List<SystemDataTransferRequest> readRequests(@NonNull TypedXmlPullParser parser) + throws XmlPullParserException, IOException { + if (!isStartOfTag(parser, XML_TAG_REQUESTS)) { + throw new XmlPullParserException("The XML doesn't have start tag: " + XML_TAG_REQUESTS); + } + + List<SystemDataTransferRequest> requests = new ArrayList<>(); + + while (true) { + parser.nextTag(); + if (isEndOfTag(parser, XML_TAG_REQUESTS)) break; + if (isStartOfTag(parser, XML_TAG_REQUEST)) { + requests.add(readRequest(parser)); + } + } + + return requests; + } + + private SystemDataTransferRequest readRequest(@NonNull TypedXmlPullParser parser) + throws XmlPullParserException, IOException { + if (!isStartOfTag(parser, XML_TAG_REQUEST)) { + throw new XmlPullParserException("XML doesn't have start tag: " + XML_TAG_REQUEST); + } + + final int associationId = readIntAttribute(parser, XML_ATTR_ASSOCIATION_ID); + final boolean isPermissionSyncAllPackages = readBooleanAttribute(parser, + XML_ATTR_IS_PERMISSION_SYNC_ALL_PACKAGES); + parser.nextTag(); + List<String> permissionSyncPackages = new ArrayList<>(); + if (isStartOfTag(parser, XML_TAG_LIST)) { + parser.nextTag(); + permissionSyncPackages = readThisListXml(parser, XML_TAG_LIST, + new String[1]); + } + + return new SystemDataTransferRequest(associationId, isPermissionSyncAllPackages, + permissionSyncPackages); + } + + /** + * Persisted user's SystemDataTransferRequest data to the disk. + * + * @param userId Android UserID + * @param requests a list of user's SystemDataTransferRequest. + */ + void writeRequestsForUser(@UserIdInt int userId, + @NonNull List<SystemDataTransferRequest> requests) { + final AtomicFile file = getStorageFileForUser(userId); + Slog.i(LOG_TAG, "Writing SystemDataTransferRequests for user " + userId + " to file=" + + file.getBaseFile().getPath()); + + // getStorageFileForUser() ALWAYS returns the SAME OBJECT, which allows us to synchronize + // accesses to the file on the file system using this AtomicFile object. + synchronized (file) { + writeToFileSafely(file, out -> { + final TypedXmlSerializer serializer = Xml.resolveSerializer(out); + serializer.setFeature( + "http://xmlpull.org/v1/doc/features.html#indent-output", true); + serializer.startDocument(null, true); + writeRequests(serializer, requests); + serializer.endDocument(); + }); + } + } + + private void writeRequests(@NonNull TypedXmlSerializer serializer, + @Nullable Collection<SystemDataTransferRequest> requests) throws IOException { + serializer.startTag(null, XML_TAG_REQUESTS); + + for (SystemDataTransferRequest request : requests) { + writeRequest(serializer, request); + } + + serializer.endTag(null, XML_TAG_REQUESTS); + } + + private void writeRequest(@NonNull TypedXmlSerializer serializer, + @NonNull SystemDataTransferRequest request) throws IOException { + serializer.startTag(null, XML_TAG_REQUEST); + + writeIntAttribute(serializer, XML_ATTR_ASSOCIATION_ID, request.getAssociationId()); + writeBooleanAttribute(serializer, XML_ATTR_IS_PERMISSION_SYNC_ALL_PACKAGES, + request.isPermissionSyncAllPackages()); + try { + writeListXml(request.getPermissionSyncPackages(), XML_ATTR_PERMISSION_SYNC_PACKAGES, + serializer); + } catch (XmlPullParserException e) { + Slog.e(LOG_TAG, "Error writing permission sync packages into XML. " + + request.getPermissionSyncPackages().toString()); + } + + serializer.endTag(null, XML_TAG_REQUEST); + } + + /** + * Creates and caches {@link AtomicFile} object that represents the back-up file for the given + * user. + * + * IMPORTANT: the method will ALWAYS return the same {@link AtomicFile} object, which makes it + * possible to synchronize reads and writes to the file using the returned object. + */ + private @NonNull AtomicFile getStorageFileForUser(@UserIdInt int userId) { + return mUserIdToStorageFile.computeIfAbsent(userId, + u -> createStorageFileForUser(userId, FILE_NAME)); + } +} diff --git a/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java b/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java new file mode 100644 index 000000000000..0eb6b8d24768 --- /dev/null +++ b/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java @@ -0,0 +1,436 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package com.android.server.companion.presence; + +import static android.bluetooth.BluetoothAdapter.ACTION_BLE_STATE_CHANGED; +import static android.bluetooth.BluetoothAdapter.ACTION_STATE_CHANGED; +import static android.bluetooth.BluetoothAdapter.EXTRA_PREVIOUS_STATE; +import static android.bluetooth.BluetoothAdapter.EXTRA_STATE; +import static android.bluetooth.BluetoothAdapter.STATE_BLE_ON; +import static android.bluetooth.BluetoothAdapter.STATE_ON; +import static android.bluetooth.BluetoothAdapter.nameForState; +import static android.bluetooth.le.ScanCallback.SCAN_FAILED_ALREADY_STARTED; +import static android.bluetooth.le.ScanCallback.SCAN_FAILED_APPLICATION_REGISTRATION_FAILED; +import static android.bluetooth.le.ScanCallback.SCAN_FAILED_FEATURE_UNSUPPORTED; +import static android.bluetooth.le.ScanCallback.SCAN_FAILED_INTERNAL_ERROR; +import static android.bluetooth.le.ScanCallback.SCAN_FAILED_OUT_OF_HARDWARE_RESOURCES; +import static android.bluetooth.le.ScanCallback.SCAN_FAILED_SCANNING_TOO_FREQUENTLY; +import static android.bluetooth.le.ScanSettings.CALLBACK_TYPE_ALL_MATCHES; +import static android.bluetooth.le.ScanSettings.CALLBACK_TYPE_FIRST_MATCH; +import static android.bluetooth.le.ScanSettings.CALLBACK_TYPE_MATCH_LOST; +import static android.bluetooth.le.ScanSettings.SCAN_MODE_LOW_POWER; + +import static com.android.server.companion.presence.Utils.btDeviceToString; + +import static java.util.Objects.requireNonNull; + +import android.annotation.MainThread; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SuppressLint; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.le.BluetoothLeScanner; +import android.bluetooth.le.ScanCallback; +import android.bluetooth.le.ScanFilter; +import android.bluetooth.le.ScanResult; +import android.bluetooth.le.ScanSettings; +import android.companion.AssociationInfo; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.util.Log; +import android.util.Slog; + +import com.android.server.companion.AssociationStore; +import com.android.server.companion.AssociationStore.ChangeType; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +@SuppressLint("LongLogTag") +class BleCompanionDeviceScanner implements AssociationStore.OnChangeListener { + private static final boolean DEBUG = false; + private static final String TAG = "CompanionDevice_PresenceMonitor_BLE"; + + /** + * When using {@link ScanSettings#SCAN_MODE_LOW_POWER}, it usually takes from 20 seconds to + * 2 minutes for the BLE scanner to find advertisements sent from the same device. + * On the other hand, {@link android.bluetooth.BluetoothAdapter.LeScanCallback} will report + * {@link ScanSettings#CALLBACK_TYPE_MATCH_LOST MATCH_LOST} 10 sec after it finds the + * advertisement for the first time (add reports + * {@link ScanSettings#CALLBACK_TYPE_FIRST_MATCH FIRST_MATCH}). + * To avoid constantly reporting {@link Callback#onBleCompanionDeviceFound(int) onDeviceFound()} + * and {@link Callback#onBleCompanionDeviceLost(int) onDeviceLost()} (while the device is + * actually present) to its clients, {@link BleCompanionDeviceScanner}, will add 1-minute delay + * after it receives {@link ScanSettings#CALLBACK_TYPE_MATCH_LOST MATCH_LOST}. + */ + private static final int NOTIFY_DEVICE_LOST_DELAY = 2 * 60 * 1000; // 2 Min. + + interface Callback { + void onBleCompanionDeviceFound(int associationId); + + void onBleCompanionDeviceLost(int associationId); + } + + private final @NonNull AssociationStore mAssociationStore; + private final @NonNull Callback mCallback; + private final @NonNull MainThreadHandler mMainThreadHandler; + + // Non-null after init(). + private @Nullable BluetoothAdapter mBtAdapter; + // Non-null after init() and when BLE is available. Otherwise - null. + private @Nullable BluetoothLeScanner mBleScanner; + // Only accessed from the Main thread. + private boolean mScanning = false; + + BleCompanionDeviceScanner( + @NonNull AssociationStore associationStore, @NonNull Callback callback) { + mAssociationStore = associationStore; + mCallback = callback; + mMainThreadHandler = new MainThreadHandler(); + } + + @MainThread + void init(@NonNull Context context, @NonNull BluetoothAdapter btAdapter) { + if (DEBUG) Log.i(TAG, "init()"); + + if (mBtAdapter != null) { + throw new IllegalStateException(getClass().getSimpleName() + " is already initialized"); + } + mBtAdapter = requireNonNull(btAdapter); + + checkBleState(); + registerBluetoothStateBroadcastReceiver(context); + + mAssociationStore.registerListener(this); + } + + @MainThread + final void restartScan() { + enforceInitialized(); + + if (DEBUG) Log.i(TAG , "restartScan()"); + if (mBleScanner == null) { + if (DEBUG) Log.d(TAG, " > BLE is not available"); + return; + } + + stopScanIfNeeded(); + startScan(); + } + + @Override + public void onAssociationChanged(@ChangeType int changeType, AssociationInfo association) { + // Simply restart scanning. + if (Looper.getMainLooper().isCurrentThread()) { + restartScan(); + } else { + mMainThreadHandler.post(this::restartScan); + } + } + + @MainThread + private void checkBleState() { + enforceInitialized(); + + final boolean bleAvailable = isBleAvailable(); + if (DEBUG) { + Log.i(TAG, "checkBleState() bleAvailable=" + bleAvailable); + } + if ((bleAvailable && mBleScanner != null) || (!bleAvailable && mBleScanner == null)) { + // Nothing changed. + if (DEBUG) Log.i(TAG, " > BLE status did not change"); + return; + } + + if (bleAvailable) { + mBleScanner = mBtAdapter.getBluetoothLeScanner(); + if (mBleScanner == null) { + // Oops, that's a race condition. Can return. + return; + } + if (DEBUG) Log.i(TAG, " > BLE is now available"); + + startScan(); + } else { + if (DEBUG) Log.i(TAG, " > BLE is now unavailable"); + + stopScanIfNeeded(); + mBleScanner = null; + } + } + + /** + * A duplicate of {@code BluetoothAdapter.getLeAccess()} method which has the package-private + * access level, so it's not accessible from here. + */ + private boolean isBleAvailable() { + final int state = mBtAdapter.getLeState(); + if (DEBUG) Log.d(TAG, "getLeAccess() state=" + nameForBtState(state)); + return state == STATE_ON || state == STATE_BLE_ON; + } + + @MainThread + private void startScan() { + enforceInitialized(); + // This method should not be called if scan is already in progress. + if (mScanning) throw new IllegalStateException("Scan is already in progress."); + // Neither should this method be called if the adapter is not available. + if (mBleScanner == null) throw new IllegalStateException("BLE is not available."); + + if (DEBUG) Log.i(TAG, "startScan()"); + + // Collect MAC addresses from all associations. + final Set<String> macAddresses = new HashSet<>(); + for (AssociationInfo association : mAssociationStore.getAssociations()) { + // Beware that BT stack does not consider low-case MAC addresses valid, while + // MacAddress.toString() return a low-case String. + final String macAddress = association.getDeviceMacAddressAsString(); + if (macAddress != null) { + macAddresses.add(macAddress); + } + } + if (macAddresses.isEmpty()) { + if (DEBUG) Log.i(TAG, " > there are no (associated) devices to Scan for."); + return; + } else { + if (DEBUG) { + Log.d(TAG, " > addresses=(n=" + macAddresses.size() + ")" + + "[" + String.join(", ", macAddresses) + "]"); + } + } + + final List<ScanFilter> filters = new ArrayList<>(macAddresses.size()); + for (String macAddress : macAddresses) { + final ScanFilter filter = new ScanFilter.Builder() + .setDeviceAddress(macAddress) + .build(); + filters.add(filter); + } + + mBleScanner.startScan(filters, SCAN_SETTINGS, mScanCallback); + mScanning = true; + } + + private void stopScanIfNeeded() { + enforceInitialized(); + + if (DEBUG) Log.i(TAG, "stopScan()"); + if (!mScanning) { + Log.d(TAG, " > not scanning."); + return; + } + + mBleScanner.stopScan(mScanCallback); + mScanning = false; + } + + @MainThread + private void notifyDeviceFound(@NonNull BluetoothDevice device) { + if (DEBUG) Log.i(TAG, "notifyDevice_Found()" + btDeviceToString(device)); + + final List<AssociationInfo> associations = + mAssociationStore.getAssociationsByAddress(device.getAddress()); + if (DEBUG) Log.d(TAG, " > associations=" + Arrays.toString(associations.toArray())); + + for (AssociationInfo association : associations) { + mCallback.onBleCompanionDeviceFound(association.getId()); + } + } + + @MainThread + private void notifyDeviceLost(@NonNull BluetoothDevice device) { + if (DEBUG) Log.i(TAG, "notifyDevice_Lost()" + btDeviceToString(device)); + + final List<AssociationInfo> associations = + mAssociationStore.getAssociationsByAddress(device.getAddress()); + if (DEBUG) Log.d(TAG, " > associations=" + Arrays.toString(associations.toArray())); + + for (AssociationInfo association : associations) { + mCallback.onBleCompanionDeviceLost(association.getId()); + } + } + + private void registerBluetoothStateBroadcastReceiver(Context context) { + final BroadcastReceiver receiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + final int prevState = intent.getIntExtra(EXTRA_PREVIOUS_STATE, -1); + final int state = intent.getIntExtra(EXTRA_STATE, -1); + + if (DEBUG) { + // The action is either STATE_CHANGED or BLE_STATE_CHANGED. + final String action = + intent.getAction().replace("android.bluetooth.adapter.", "bt."); + Log.d(TAG, "on(Broadcast)Receive() " + action + ": " + + nameForBtState(prevState) + "->" + nameForBtState(state)); + } + + checkBleState(); + } + }; + + final IntentFilter filter = new IntentFilter(); + filter.addAction(ACTION_STATE_CHANGED); + filter.addAction(ACTION_BLE_STATE_CHANGED); + + context.registerReceiver(receiver, filter); + } + + private void enforceInitialized() { + if (mBtAdapter != null) return; + throw new IllegalStateException(getClass().getSimpleName() + " is not initialized"); + } + + private final ScanCallback mScanCallback = new ScanCallback() { + @MainThread + @Override + public void onScanResult(int callbackType, ScanResult result) { + final BluetoothDevice device = result.getDevice(); + + if (DEBUG) { + Log.d(TAG, "onScanResult() " + nameForBleScanCallbackType(callbackType) + + " device=" + btDeviceToString(device)); + Log.v(TAG, " > scanResult=" + result); + + final List<AssociationInfo> associations = + mAssociationStore.getAssociationsByAddress(device.getAddress()); + Log.v(TAG, " > associations=" + Arrays.toString(associations.toArray())); + } + + switch (callbackType) { + case CALLBACK_TYPE_FIRST_MATCH: + if (mMainThreadHandler.hasNotifyDeviceLostMessages(device)) { + mMainThreadHandler.removeNotifyDeviceLostMessages(device); + return; + } + + notifyDeviceFound(device); + break; + + case CALLBACK_TYPE_MATCH_LOST: + mMainThreadHandler.sendNotifyDeviceLostDelayedMessage(device); + break; + + default: + Slog.wtf(TAG, "Unexpected callback " + + nameForBleScanCallbackType(callbackType)); + break; + } + } + + @MainThread + @Override + public void onScanFailed(int errorCode) { + if (DEBUG) Log.w(TAG, "onScanFailed() " + nameForBleScanErrorCode(errorCode)); + mScanning = false; + } + }; + + @SuppressLint("HandlerLeak") + private class MainThreadHandler extends Handler { + private static final int NOTIFY_DEVICE_LOST = 1; + + MainThreadHandler() { + super(Looper.getMainLooper()); + } + + @Override + public void handleMessage(@NonNull Message message) { + if (message.what != NOTIFY_DEVICE_LOST) return; + + final BluetoothDevice device = (BluetoothDevice) message.obj; + notifyDeviceLost(device); + } + + void sendNotifyDeviceLostDelayedMessage(BluetoothDevice device) { + final Message message = obtainMessage(NOTIFY_DEVICE_LOST, device); + sendMessageDelayed(message, NOTIFY_DEVICE_LOST_DELAY); + } + + boolean hasNotifyDeviceLostMessages(BluetoothDevice device) { + return hasEqualMessages(NOTIFY_DEVICE_LOST, device); + } + + void removeNotifyDeviceLostMessages(BluetoothDevice device) { + removeEqualMessages(NOTIFY_DEVICE_LOST, device); + } + } + + private static String nameForBtState(int state) { + return nameForState(state) + "(" + state + ")"; + } + + private static String nameForBleScanCallbackType(int callbackType) { + final String name; + switch (callbackType) { + case CALLBACK_TYPE_ALL_MATCHES: + name = "ALL_MATCHES"; + break; + case CALLBACK_TYPE_FIRST_MATCH: + name = "FIRST_MATCH"; + break; + case CALLBACK_TYPE_MATCH_LOST: + name = "MATCH_LOST"; + break; + default: + name = "Unknown"; + } + return name + "(" + callbackType + ")"; + } + + private static String nameForBleScanErrorCode(int errorCode) { + final String name; + switch (errorCode) { + case SCAN_FAILED_ALREADY_STARTED: + name = "ALREADY_STARTED"; + break; + case SCAN_FAILED_APPLICATION_REGISTRATION_FAILED: + name = "APPLICATION_REGISTRATION_FAILED"; + break; + case SCAN_FAILED_INTERNAL_ERROR: + name = "INTERNAL_ERROR"; + break; + case SCAN_FAILED_FEATURE_UNSUPPORTED: + name = "FEATURE_UNSUPPORTED"; + break; + case SCAN_FAILED_OUT_OF_HARDWARE_RESOURCES: + name = "OUT_OF_HARDWARE_RESOURCES"; + break; + case SCAN_FAILED_SCANNING_TOO_FREQUENTLY: + name = "SCANNING_TOO_FREQUENTLY"; + break; + default: + name = "Unknown"; + } + return name + "(" + errorCode + ")"; + } + + private static final ScanSettings SCAN_SETTINGS = new ScanSettings.Builder() + .setCallbackType(CALLBACK_TYPE_FIRST_MATCH | CALLBACK_TYPE_MATCH_LOST) + .setScanMode(SCAN_MODE_LOW_POWER) + .build(); +} diff --git a/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java b/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java new file mode 100644 index 000000000000..dbe866b374f1 --- /dev/null +++ b/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.companion.presence; + +import static com.android.server.companion.presence.Utils.btDeviceToString; + +import android.annotation.NonNull; +import android.annotation.SuppressLint; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.companion.AssociationInfo; +import android.net.MacAddress; +import android.os.Handler; +import android.os.HandlerExecutor; +import android.util.Log; + +import com.android.server.companion.AssociationStore; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@SuppressLint("LongLogTag") +class BluetoothCompanionDeviceConnectionListener + extends BluetoothAdapter.BluetoothConnectionCallback + implements AssociationStore.OnChangeListener { + private static final boolean DEBUG = false; + private static final String TAG = "CompanionDevice_PresenceMonitor_BT"; + + interface Callback { + void onBluetoothCompanionDeviceConnected(int associationId); + + void onBluetoothCompanionDeviceDisconnected(int associationId); + } + + private final @NonNull AssociationStore mAssociationStore; + private final @NonNull Callback mCallback; + /** A set of ALL connected BT device (not only companion.) */ + private final @NonNull Map<MacAddress, BluetoothDevice> mAllConnectedDevices = new HashMap<>(); + + BluetoothCompanionDeviceConnectionListener(@NonNull AssociationStore associationStore, + @NonNull Callback callback) { + mAssociationStore = associationStore; + mCallback = callback; + } + + public void init(@NonNull BluetoothAdapter btAdapter) { + if (DEBUG) Log.i(TAG, "init()"); + + btAdapter.registerBluetoothConnectionCallback( + new HandlerExecutor(Handler.getMain()), /* callback */this); + mAssociationStore.registerListener(this); + } + + /** + * Overrides + * {@link BluetoothAdapter.BluetoothConnectionCallback#onDeviceConnected(BluetoothDevice)}. + */ + @Override + public void onDeviceConnected(@NonNull BluetoothDevice device) { + if (DEBUG) Log.i(TAG, "onDevice_Connected() " + btDeviceToString(device)); + + final MacAddress macAddress = MacAddress.fromString(device.getAddress()); + if (mAllConnectedDevices.put(macAddress, device) != null) { + if (DEBUG) Log.w(TAG, "Device " + btDeviceToString(device) + " is already connected."); + return; + } + + onDeviceConnectivityChanged(device, true); + } + + /** + * Overrides + * {@link BluetoothAdapter.BluetoothConnectionCallback#onDeviceConnected(BluetoothDevice)}. + * Also invoked when user turns BT off while the device is connected. + */ + @Override + public void onDeviceDisconnected(@NonNull BluetoothDevice device, + @DisconnectReason int reason) { + if (DEBUG) { + Log.i(TAG, "onDevice_Disconnected() " + btDeviceToString(device)); + Log.d(TAG, " reason=" + disconnectReasonText(reason)); + } + + final MacAddress macAddress = MacAddress.fromString(device.getAddress()); + if (mAllConnectedDevices.remove(macAddress) == null) { + if (DEBUG) { + Log.w(TAG, "The device wasn't tracked as connected " + btDeviceToString(device)); + } + return; + } + + onDeviceConnectivityChanged(device, false); + } + + private void onDeviceConnectivityChanged(@NonNull BluetoothDevice device, boolean connected) { + final List<AssociationInfo> associations = + mAssociationStore.getAssociationsByAddress(device.getAddress()); + + if (DEBUG) { + Log.d(TAG, "onDevice_ConnectivityChanged() " + btDeviceToString(device) + + " connected=" + connected); + if (associations.isEmpty()) { + Log.d(TAG, " > No CDM associations"); + } else { + Log.d(TAG, " > associations=" + Arrays.toString(associations.toArray())); + } + } + + for (AssociationInfo association : associations) { + final int id = association.getId(); + if (connected) { + mCallback.onBluetoothCompanionDeviceConnected(id); + } else { + mCallback.onBluetoothCompanionDeviceDisconnected(id); + } + } + } + + @Override + public void onAssociationAdded(AssociationInfo association) { + if (DEBUG) Log.d(TAG, "onAssociation_Added() " + association); + + if (mAllConnectedDevices.containsKey(association.getDeviceMacAddress())) { + mCallback.onBluetoothCompanionDeviceConnected(association.getId()); + } + } + + @Override + public void onAssociationRemoved(AssociationInfo association) { + // Intentionally do nothing: CompanionDevicePresenceMonitor will do all the bookkeeping + // required. + } + + @Override + public void onAssociationUpdated(AssociationInfo association, boolean addressChanged) { + if (DEBUG) { + Log.d(TAG, "onAssociation_Updated() addrChange=" + addressChanged + + " " + association); + } + + if (!addressChanged) { + // Don't need to do anything. + return; + } + + // At the moment CDM does allow changing association addresses, so we will never come here. + // This will be implemented when CDM support updating addresses. + throw new IllegalArgumentException("Address changes are not supported."); + } +} diff --git a/services/companion/java/com/android/server/companion/presence/Utils.java b/services/companion/java/com/android/server/companion/presence/Utils.java new file mode 100644 index 000000000000..583b443c8cb7 --- /dev/null +++ b/services/companion/java/com/android/server/companion/presence/Utils.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.companion.presence; + +import android.annotation.NonNull; +import android.bluetooth.BluetoothDevice; + +/** Utilities for working with Bluetooth and BLE devices. */ +class Utils { + + /** + * @return short String representation of {@link BluetoothDevice}. + */ + static String btDeviceToString(@NonNull BluetoothDevice btDevice) { + final StringBuilder sb = new StringBuilder(btDevice.getAddress()); + + sb.append(" [name="); + final String name = btDevice.getName(); + if (name != null) { + sb.append('\'').append(name).append('\''); + } else { + sb.append("null"); + } + + final String alias = btDevice.getAlias(); + if (alias != null) { + sb.append(", alias='").append(alias).append("'"); + } + + return sb.append(']').toString(); + } + + private Utils() { + } +} diff --git a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java index 734e5c3f6f8b..75acf81a4a3c 100644 --- a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java +++ b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java @@ -21,18 +21,24 @@ import static android.view.WindowManager.LayoutParams.FLAG_SECURE; import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS; import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.compat.CompatChanges; +import android.companion.virtual.VirtualDeviceManager.ActivityListener; import android.compat.annotation.ChangeId; import android.compat.annotation.EnabledSince; import android.content.ComponentName; import android.content.pm.ActivityInfo; import android.os.Build; +import android.os.Handler; +import android.os.Looper; import android.os.UserHandle; import android.util.ArraySet; import android.util.Slog; +import android.view.Display; import android.window.DisplayWindowPolicyController; import java.util.List; +import java.util.Set; /** @@ -49,14 +55,38 @@ class GenericWindowPolicyController extends DisplayWindowPolicyController { @ChangeId @EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU) public static final long ALLOW_SECURE_ACTIVITY_DISPLAY_ON_REMOTE_DEVICE = 201712607L; - @NonNull private final ArraySet<UserHandle> mAllowedUsers; - - @NonNull final ArraySet<Integer> mRunningUids = new ArraySet<>(); + @NonNull + private final ArraySet<UserHandle> mAllowedUsers; + @Nullable + private final ArraySet<ComponentName> mAllowedActivities; + @Nullable + private final ArraySet<ComponentName> mBlockedActivities; + + @NonNull + final ArraySet<Integer> mRunningUids = new ArraySet<>(); + @Nullable private final ActivityListener mActivityListener; + private final Handler mHandler = new Handler(Looper.getMainLooper()); + /** + * Creates a window policy controller that is generic to the different use cases of virtual + * device. + * + * @param windowFlags The window flags that this controller is interested in. + * @param systemWindowFlags The system window flags that this controller is interested in. + * @param allowedUsers The set of users that are allowed to stream in this display. + * @param activityListener Activity listener to listen for activity changes. The display ID + * is not populated in this callback and is always {@link Display#INVALID_DISPLAY}. + */ GenericWindowPolicyController(int windowFlags, int systemWindowFlags, - @NonNull ArraySet<UserHandle> allowedUsers) { + @NonNull ArraySet<UserHandle> allowedUsers, + @Nullable Set<ComponentName> allowedActivities, + @Nullable Set<ComponentName> blockedActivities, + @NonNull ActivityListener activityListener) { mAllowedUsers = allowedUsers; + mAllowedActivities = allowedActivities == null ? null : new ArraySet<>(allowedActivities); + mBlockedActivities = blockedActivities == null ? null : new ArraySet<>(blockedActivities); setInterestedWindowFlags(windowFlags, systemWindowFlags); + mActivityListener = activityListener; } @Override @@ -80,13 +110,21 @@ class GenericWindowPolicyController extends DisplayWindowPolicyController { @Override public void onTopActivityChanged(ComponentName topActivity, int uid) { - + if (mActivityListener != null) { + // Post callback on the main thread so it doesn't block activity launching + mHandler.post(() -> + mActivityListener.onTopActivityChanged(Display.INVALID_DISPLAY, topActivity)); + } } @Override public void onRunningAppsChanged(ArraySet<Integer> runningUids) { mRunningUids.clear(); mRunningUids.addAll(runningUids); + if (mActivityListener != null && mRunningUids.isEmpty()) { + // Post callback on the main thread so it doesn't block activity launching + mHandler.post(() -> mActivityListener.onDisplayEmpty(Display.INVALID_DISPLAY)); + } } /** @@ -108,6 +146,18 @@ class GenericWindowPolicyController extends DisplayWindowPolicyController { Slog.d(TAG, "Virtual device activity not allowed from user " + activityUser); return false; } + if (mBlockedActivities != null + && mBlockedActivities.contains(activityInfo.getComponentName())) { + Slog.d(TAG, + "Virtual device blocking launch of " + activityInfo.getComponentName()); + return false; + } + if (mAllowedActivities != null + && !mAllowedActivities.contains(activityInfo.getComponentName())) { + Slog.d(TAG, + activityInfo.getComponentName() + " is not in the allowed list."); + return false; + } if (!CompatChanges.isChangeEnabled(ALLOW_SECURE_ACTIVITY_DISPLAY_ON_REMOTE_DEVICE, activityInfo.packageName, activityUser)) { // TODO(b/201712607): Add checks for the apps that use SurfaceView#setSecure. diff --git a/services/companion/java/com/android/server/companion/virtual/InputController.java b/services/companion/java/com/android/server/companion/virtual/InputController.java index 067edcc0b08d..ae39d7ef0b83 100644 --- a/services/companion/java/com/android/server/companion/virtual/InputController.java +++ b/services/companion/java/com/android/server/companion/virtual/InputController.java @@ -16,8 +16,11 @@ package com.android.server.companion.virtual; +import android.annotation.IntDef; import android.annotation.NonNull; import android.graphics.Point; +import android.graphics.PointF; +import android.hardware.input.InputManagerInternal; import android.hardware.input.VirtualKeyEvent; import android.hardware.input.VirtualMouseButtonEvent; import android.hardware.input.VirtualMouseRelativeEvent; @@ -26,25 +29,40 @@ import android.hardware.input.VirtualTouchEvent; import android.os.IBinder; import android.os.RemoteException; import android.util.ArrayMap; +import android.util.Slog; +import android.view.Display; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.server.LocalServices; import java.io.PrintWriter; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Map; +import java.util.concurrent.atomic.AtomicLong; /** Controls virtual input devices, including device lifecycle and event dispatch. */ class InputController { + private static final String TAG = "VirtualInputController"; + private final Object mLock; /* Token -> file descriptor associations. */ @VisibleForTesting @GuardedBy("mLock") - final Map<IBinder, Integer> mInputDeviceFds = new ArrayMap<>(); + final Map<IBinder, InputDeviceDescriptor> mInputDeviceDescriptors = new ArrayMap<>(); private final NativeWrapper mNativeWrapper; + /** + * Because the pointer is a singleton, it can only be targeted at one display at a time. Because + * multiple mice could be concurrently registered, mice that are associated with a different + * display than the current target display should not be allowed to affect the current target. + */ + @VisibleForTesting int mActivePointerDisplayId; + InputController(@NonNull Object lock) { this(lock, new NativeWrapper()); } @@ -53,32 +71,39 @@ class InputController { InputController(@NonNull Object lock, @NonNull NativeWrapper nativeWrapper) { mLock = lock; mNativeWrapper = nativeWrapper; + mActivePointerDisplayId = Display.INVALID_DISPLAY; } void close() { synchronized (mLock) { - for (int fd : mInputDeviceFds.values()) { - mNativeWrapper.closeUinput(fd); + for (InputDeviceDescriptor inputDeviceDescriptor : mInputDeviceDescriptors.values()) { + mNativeWrapper.closeUinput(inputDeviceDescriptor.getFileDescriptor()); } - mInputDeviceFds.clear(); + mInputDeviceDescriptors.clear(); + resetMouseValuesLocked(); } } void createKeyboard(@NonNull String deviceName, int vendorId, int productId, - @NonNull IBinder deviceToken) { + @NonNull IBinder deviceToken, + int displayId) { final int fd = mNativeWrapper.openUinputKeyboard(deviceName, vendorId, productId); if (fd < 0) { throw new RuntimeException( "A native error occurred when creating keyboard: " + -fd); } + final BinderDeathRecipient binderDeathRecipient = new BinderDeathRecipient(deviceToken); synchronized (mLock) { - mInputDeviceFds.put(deviceToken, fd); + mInputDeviceDescriptors.put(deviceToken, + new InputDeviceDescriptor(fd, binderDeathRecipient, + InputDeviceDescriptor.TYPE_KEYBOARD, displayId)); } try { - deviceToken.linkToDeath(new BinderDeathRecipient(deviceToken), /* flags= */ 0); + deviceToken.linkToDeath(binderDeathRecipient, /* flags= */ 0); } catch (RemoteException e) { + // TODO(b/215608394): remove and close InputDeviceDescriptor throw new RuntimeException("Could not create virtual keyboard", e); } } @@ -86,18 +111,27 @@ class InputController { void createMouse(@NonNull String deviceName, int vendorId, int productId, - @NonNull IBinder deviceToken) { + @NonNull IBinder deviceToken, + int displayId) { final int fd = mNativeWrapper.openUinputMouse(deviceName, vendorId, productId); if (fd < 0) { throw new RuntimeException( "A native error occurred when creating mouse: " + -fd); } + final BinderDeathRecipient binderDeathRecipient = new BinderDeathRecipient(deviceToken); synchronized (mLock) { - mInputDeviceFds.put(deviceToken, fd); + mInputDeviceDescriptors.put(deviceToken, + new InputDeviceDescriptor(fd, binderDeathRecipient, + InputDeviceDescriptor.TYPE_MOUSE, displayId)); + final InputManagerInternal inputManagerInternal = + LocalServices.getService(InputManagerInternal.class); + inputManagerInternal.setVirtualMousePointerDisplayId(displayId); + mActivePointerDisplayId = displayId; } try { - deviceToken.linkToDeath(new BinderDeathRecipient(deviceToken), /* flags= */ 0); + deviceToken.linkToDeath(binderDeathRecipient, /* flags= */ 0); } catch (RemoteException e) { + // TODO(b/215608394): remove and close InputDeviceDescriptor throw new RuntimeException("Could not create virtual mouse", e); } } @@ -106,6 +140,7 @@ class InputController { int vendorId, int productId, @NonNull IBinder deviceToken, + int displayId, @NonNull Point screenSize) { final int fd = mNativeWrapper.openUinputTouchscreen(deviceName, vendorId, productId, screenSize.y, screenSize.x); @@ -113,93 +148,177 @@ class InputController { throw new RuntimeException( "A native error occurred when creating touchscreen: " + -fd); } + final BinderDeathRecipient binderDeathRecipient = new BinderDeathRecipient(deviceToken); synchronized (mLock) { - mInputDeviceFds.put(deviceToken, fd); + mInputDeviceDescriptors.put(deviceToken, + new InputDeviceDescriptor(fd, binderDeathRecipient, + InputDeviceDescriptor.TYPE_TOUCHSCREEN, displayId)); } try { - deviceToken.linkToDeath(new BinderDeathRecipient(deviceToken), /* flags= */ 0); + deviceToken.linkToDeath(binderDeathRecipient, /* flags= */ 0); } catch (RemoteException e) { + // TODO(b/215608394): remove and close InputDeviceDescriptor throw new RuntimeException("Could not create virtual touchscreen", e); } } void unregisterInputDevice(@NonNull IBinder token) { synchronized (mLock) { - final Integer fd = mInputDeviceFds.remove(token); - if (fd == null) { + final InputDeviceDescriptor inputDeviceDescriptor = mInputDeviceDescriptors.remove( + token); + if (inputDeviceDescriptor == null) { throw new IllegalArgumentException( "Could not unregister input device for given token"); } - mNativeWrapper.closeUinput(fd); + token.unlinkToDeath(inputDeviceDescriptor.getDeathRecipient(), /* flags= */ 0); + mNativeWrapper.closeUinput(inputDeviceDescriptor.getFileDescriptor()); + + // Reset values to the default if all virtual mice are unregistered, or set display + // id if there's another mouse (choose the most recent). + if (inputDeviceDescriptor.isMouse()) { + updateMouseValuesLocked(); + } } } + @GuardedBy("mLock") + private void updateMouseValuesLocked() { + InputDeviceDescriptor mostRecentlyCreatedMouse = null; + for (InputDeviceDescriptor otherInputDeviceDescriptor : + mInputDeviceDescriptors.values()) { + if (otherInputDeviceDescriptor.isMouse()) { + if (mostRecentlyCreatedMouse == null + || (otherInputDeviceDescriptor.getCreationOrderNumber() + > mostRecentlyCreatedMouse.getCreationOrderNumber())) { + mostRecentlyCreatedMouse = otherInputDeviceDescriptor; + } + } + } + if (mostRecentlyCreatedMouse != null) { + final InputManagerInternal inputManagerInternal = + LocalServices.getService(InputManagerInternal.class); + inputManagerInternal.setVirtualMousePointerDisplayId( + mostRecentlyCreatedMouse.getDisplayId()); + mActivePointerDisplayId = mostRecentlyCreatedMouse.getDisplayId(); + } else { + // All mice have been unregistered; reset all values. + resetMouseValuesLocked(); + } + } + + private void resetMouseValuesLocked() { + final InputManagerInternal inputManagerInternal = + LocalServices.getService(InputManagerInternal.class); + inputManagerInternal.setVirtualMousePointerDisplayId(Display.INVALID_DISPLAY); + mActivePointerDisplayId = Display.INVALID_DISPLAY; + } + boolean sendKeyEvent(@NonNull IBinder token, @NonNull VirtualKeyEvent event) { synchronized (mLock) { - final Integer fd = mInputDeviceFds.get(token); - if (fd == null) { + final InputDeviceDescriptor inputDeviceDescriptor = mInputDeviceDescriptors.get( + token); + if (inputDeviceDescriptor == null) { throw new IllegalArgumentException( "Could not send key event to input device for given token"); } - return mNativeWrapper.writeKeyEvent(fd, event.getKeyCode(), event.getAction()); + return mNativeWrapper.writeKeyEvent(inputDeviceDescriptor.getFileDescriptor(), + event.getKeyCode(), event.getAction()); } } boolean sendButtonEvent(@NonNull IBinder token, @NonNull VirtualMouseButtonEvent event) { synchronized (mLock) { - final Integer fd = mInputDeviceFds.get(token); - if (fd == null) { + final InputDeviceDescriptor inputDeviceDescriptor = mInputDeviceDescriptors.get( + token); + if (inputDeviceDescriptor == null) { throw new IllegalArgumentException( "Could not send button event to input device for given token"); } - return mNativeWrapper.writeButtonEvent(fd, event.getButtonCode(), event.getAction()); + if (inputDeviceDescriptor.getDisplayId() != mActivePointerDisplayId) { + throw new IllegalStateException( + "Display id associated with this mouse is not currently targetable"); + } + return mNativeWrapper.writeButtonEvent(inputDeviceDescriptor.getFileDescriptor(), + event.getButtonCode(), event.getAction()); } } boolean sendTouchEvent(@NonNull IBinder token, @NonNull VirtualTouchEvent event) { synchronized (mLock) { - final Integer fd = mInputDeviceFds.get(token); - if (fd == null) { + final InputDeviceDescriptor inputDeviceDescriptor = mInputDeviceDescriptors.get( + token); + if (inputDeviceDescriptor == null) { throw new IllegalArgumentException( "Could not send touch event to input device for given token"); } - return mNativeWrapper.writeTouchEvent(fd, event.getPointerId(), event.getToolType(), - event.getAction(), event.getX(), event.getY(), event.getPressure(), - event.getMajorAxisSize()); + return mNativeWrapper.writeTouchEvent(inputDeviceDescriptor.getFileDescriptor(), + event.getPointerId(), event.getToolType(), event.getAction(), event.getX(), + event.getY(), event.getPressure(), event.getMajorAxisSize()); } } boolean sendRelativeEvent(@NonNull IBinder token, @NonNull VirtualMouseRelativeEvent event) { synchronized (mLock) { - final Integer fd = mInputDeviceFds.get(token); - if (fd == null) { + final InputDeviceDescriptor inputDeviceDescriptor = mInputDeviceDescriptors.get( + token); + if (inputDeviceDescriptor == null) { throw new IllegalArgumentException( "Could not send relative event to input device for given token"); } - return mNativeWrapper.writeRelativeEvent(fd, event.getRelativeX(), - event.getRelativeY()); + if (inputDeviceDescriptor.getDisplayId() != mActivePointerDisplayId) { + throw new IllegalStateException( + "Display id associated with this mouse is not currently targetable"); + } + return mNativeWrapper.writeRelativeEvent(inputDeviceDescriptor.getFileDescriptor(), + event.getRelativeX(), event.getRelativeY()); } } boolean sendScrollEvent(@NonNull IBinder token, @NonNull VirtualMouseScrollEvent event) { synchronized (mLock) { - final Integer fd = mInputDeviceFds.get(token); - if (fd == null) { + final InputDeviceDescriptor inputDeviceDescriptor = mInputDeviceDescriptors.get( + token); + if (inputDeviceDescriptor == null) { throw new IllegalArgumentException( "Could not send scroll event to input device for given token"); } - return mNativeWrapper.writeScrollEvent(fd, event.getXAxisMovement(), - event.getYAxisMovement()); + if (inputDeviceDescriptor.getDisplayId() != mActivePointerDisplayId) { + throw new IllegalStateException( + "Display id associated with this mouse is not currently targetable"); + } + return mNativeWrapper.writeScrollEvent(inputDeviceDescriptor.getFileDescriptor(), + event.getXAxisMovement(), event.getYAxisMovement()); + } + } + + public PointF getCursorPosition(@NonNull IBinder token) { + synchronized (mLock) { + final InputDeviceDescriptor inputDeviceDescriptor = mInputDeviceDescriptors.get( + token); + if (inputDeviceDescriptor == null) { + throw new IllegalArgumentException( + "Could not get cursor position for input device for given token"); + } + if (inputDeviceDescriptor.getDisplayId() != mActivePointerDisplayId) { + throw new IllegalStateException( + "Display id associated with this mouse is not currently targetable"); + } + return LocalServices.getService(InputManagerInternal.class).getCursorPosition(); } } public void dump(@NonNull PrintWriter fout) { fout.println(" InputController: "); synchronized (mLock) { - fout.println(" Active file descriptors: "); - for (int inputDeviceFd : mInputDeviceFds.values()) { - fout.println(inputDeviceFd); + fout.println(" Active descriptors: "); + for (InputDeviceDescriptor inputDeviceDescriptor : mInputDeviceDescriptors.values()) { + fout.println(" fd: " + inputDeviceDescriptor.getFileDescriptor()); + fout.println(" displayId: " + inputDeviceDescriptor.getDisplayId()); + fout.println(" creationOrder: " + + inputDeviceDescriptor.getCreationOrderNumber()); + fout.println(" type: " + inputDeviceDescriptor.getType()); } + fout.println(" Active mouse display id: " + mActivePointerDisplayId); } } @@ -267,6 +386,63 @@ class InputController { } } + @VisibleForTesting static final class InputDeviceDescriptor { + + static final int TYPE_KEYBOARD = 1; + static final int TYPE_MOUSE = 2; + static final int TYPE_TOUCHSCREEN = 3; + @IntDef(prefix = { "TYPE_" }, value = { + TYPE_KEYBOARD, + TYPE_MOUSE, + TYPE_TOUCHSCREEN, + }) + @Retention(RetentionPolicy.SOURCE) + @interface Type { + } + + private static final AtomicLong sNextCreationOrderNumber = new AtomicLong(1); + + private final int mFd; + private final IBinder.DeathRecipient mDeathRecipient; + private final @Type int mType; + private final int mDisplayId; + // Monotonically increasing number; devices with lower numbers were created earlier. + private final long mCreationOrderNumber; + + InputDeviceDescriptor(int fd, IBinder.DeathRecipient deathRecipient, + @Type int type, int displayId) { + mFd = fd; + mDeathRecipient = deathRecipient; + mType = type; + mDisplayId = displayId; + mCreationOrderNumber = sNextCreationOrderNumber.getAndIncrement(); + } + + public int getFileDescriptor() { + return mFd; + } + + public int getType() { + return mType; + } + + public boolean isMouse() { + return mType == TYPE_MOUSE; + } + + public IBinder.DeathRecipient getDeathRecipient() { + return mDeathRecipient; + } + + public int getDisplayId() { + return mDisplayId; + } + + public long getCreationOrderNumber() { + return mCreationOrderNumber; + } + } + private final class BinderDeathRecipient implements IBinder.DeathRecipient { private final IBinder mDeviceToken; @@ -277,6 +453,10 @@ class InputController { @Override public void binderDied() { + // All callers are expected to call {@link VirtualDevice#unregisterInputDevice} before + // quitting, which removes this death recipient. If this is invoked, the remote end + // died, or they disposed of the object without properly unregistering. + Slog.e(TAG, "Virtual input controller binder died"); unregisterInputDevice(mDeviceToken); } } diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java index 0c0ee521af0f..5c8fb2ecec0f 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java @@ -29,10 +29,15 @@ import android.app.PendingIntent; import android.app.admin.DevicePolicyManager; import android.companion.AssociationInfo; import android.companion.virtual.IVirtualDevice; +import android.companion.virtual.IVirtualDeviceActivityListener; +import android.companion.virtual.VirtualDeviceManager.ActivityListener; import android.companion.virtual.VirtualDeviceParams; +import android.content.ComponentName; import android.content.Context; import android.graphics.Point; +import android.graphics.PointF; import android.hardware.display.DisplayManager; +import android.hardware.input.InputManagerInternal; import android.hardware.input.VirtualKeyEvent; import android.hardware.input.VirtualMouseButtonEvent; import android.hardware.input.VirtualMouseRelativeEvent; @@ -50,11 +55,11 @@ import android.util.SparseArray; import android.window.DisplayWindowPolicyController; import com.android.internal.annotations.VisibleForTesting; +import com.android.server.LocalServices; import java.io.FileDescriptor; import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.List; +import java.util.Set; final class VirtualDeviceImpl extends IVirtualDevice.Stub @@ -69,10 +74,34 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub private final int mOwnerUid; private final InputController mInputController; @VisibleForTesting - final List<Integer> mVirtualDisplayIds = new ArrayList<>(); + final Set<Integer> mVirtualDisplayIds = new ArraySet<>(); private final OnDeviceCloseListener mListener; private final IBinder mAppToken; private final VirtualDeviceParams mParams; + private final IVirtualDeviceActivityListener mActivityListener; + + private ActivityListener createListenerAdapter(int displayId) { + return new ActivityListener() { + + @Override + public void onTopActivityChanged(int unusedDisplayId, ComponentName topActivity) { + try { + mActivityListener.onTopActivityChanged(displayId, topActivity); + } catch (RemoteException e) { + Slog.w(TAG, "Unable to call mActivityListener", e); + } + } + + @Override + public void onDisplayEmpty(int unusedDisplayId) { + try { + mActivityListener.onDisplayEmpty(displayId); + } catch (RemoteException e) { + Slog.w(TAG, "Unable to call mActivityListener", e); + } + } + }; + } /** * A mapping from the virtual display ID to its corresponding @@ -83,18 +112,22 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub VirtualDeviceImpl(Context context, AssociationInfo associationInfo, IBinder token, int ownerUid, OnDeviceCloseListener listener, - PendingTrampolineCallback pendingTrampolineCallback, VirtualDeviceParams params) { + PendingTrampolineCallback pendingTrampolineCallback, + IVirtualDeviceActivityListener activityListener, + VirtualDeviceParams params) { this(context, associationInfo, token, ownerUid, /* inputController= */ null, listener, - pendingTrampolineCallback, params); + pendingTrampolineCallback, activityListener, params); } @VisibleForTesting VirtualDeviceImpl(Context context, AssociationInfo associationInfo, IBinder token, int ownerUid, InputController inputController, OnDeviceCloseListener listener, - PendingTrampolineCallback pendingTrampolineCallback, VirtualDeviceParams params) { + PendingTrampolineCallback pendingTrampolineCallback, + IVirtualDeviceActivityListener activityListener, VirtualDeviceParams params) { mContext = context; mAssociationInfo = associationInfo; mPendingTrampolineCallback = pendingTrampolineCallback; + mActivityListener = activityListener; mOwnerUid = ownerUid; mAppToken = token; mParams = params; @@ -202,7 +235,8 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub } final long token = Binder.clearCallingIdentity(); try { - mInputController.createKeyboard(deviceName, vendorId, productId, deviceToken); + mInputController.createKeyboard(deviceName, vendorId, productId, deviceToken, + displayId); } finally { Binder.restoreCallingIdentity(token); } @@ -227,7 +261,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub } final long token = Binder.clearCallingIdentity(); try { - mInputController.createMouse(deviceName, vendorId, productId, deviceToken); + mInputController.createMouse(deviceName, vendorId, productId, deviceToken, displayId); } finally { Binder.restoreCallingIdentity(token); } @@ -254,7 +288,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub final long token = Binder.clearCallingIdentity(); try { mInputController.createTouchscreen(deviceName, vendorId, productId, - deviceToken, screenSize); + deviceToken, displayId, screenSize); } finally { Binder.restoreCallingIdentity(token); } @@ -324,10 +358,21 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub } } + @Override // Binder call + public PointF getCursorPosition(IBinder token) { + final long binderToken = Binder.clearCallingIdentity(); + try { + return mInputController.getCursorPosition(token); + } finally { + Binder.restoreCallingIdentity(binderToken); + } + } + @Override protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) { fout.println(" VirtualDevice: "); fout.println(" mAssociationId: " + mAssociationInfo.getId()); + fout.println(" mParams: " + mParams); fout.println(" mVirtualDisplayIds: "); synchronized (mVirtualDeviceLock) { for (int id : mVirtualDisplayIds) { @@ -343,9 +388,15 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub "Virtual device already have a virtual display with ID " + displayId); } mVirtualDisplayIds.add(displayId); + LocalServices.getService(InputManagerInternal.class).setDisplayEligibilityForPointerCapture( + displayId, false); final GenericWindowPolicyController dwpc = new GenericWindowPolicyController(FLAG_SECURE, - SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS, getAllowedUserHandles()); + SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS, + getAllowedUserHandles(), + mParams.getAllowedActivities(), + mParams.getBlockedActivities(), + createListenerAdapter(displayId)); mWindowPolicyControllers.put(displayId, dwpc); return dwpc; } @@ -374,6 +425,8 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub "Virtual device doesn't have a virtual display with ID " + displayId); } mVirtualDisplayIds.remove(displayId); + LocalServices.getService(InputManagerInternal.class).setDisplayEligibilityForPointerCapture( + displayId, true); mWindowPolicyControllers.remove(displayId); } diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java index 7e0c2fc37da6..b507110d5473 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java @@ -27,6 +27,7 @@ import android.companion.AssociationInfo; import android.companion.CompanionDeviceManager; import android.companion.CompanionDeviceManager.OnAssociationsChangedListener; import android.companion.virtual.IVirtualDevice; +import android.companion.virtual.IVirtualDeviceActivityListener; import android.companion.virtual.IVirtualDeviceManager; import android.companion.virtual.VirtualDeviceParams; import android.content.Context; @@ -176,7 +177,8 @@ public class VirtualDeviceManagerService extends SystemService { IBinder token, String packageName, int associationId, - @NonNull VirtualDeviceParams params) { + @NonNull VirtualDeviceParams params, + @NonNull IVirtualDeviceActivityListener activityListener) { getContext().enforceCallingOrSelfPermission( android.Manifest.permission.CREATE_VIRTUAL_DEVICE, "createVirtualDevice"); @@ -206,7 +208,7 @@ public class VirtualDeviceManagerService extends SystemService { } } }, - this, params); + this, activityListener, params); mVirtualDevices.put(associationInfo.getId(), virtualDevice); return virtualDevice; } diff --git a/services/core/Android.bp b/services/core/Android.bp index 094ed375325e..1106fe7270e6 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -176,6 +176,9 @@ java_library_static { "overlayable_policy_aidl-java", "SurfaceFlingerProperties", "com.android.sysprop.watchdog", + // This is used for services.connectivity-tiramisu-sources. + // TODO: delete when NetworkStatsService is moved to the mainline module. + "net-utils-device-common-bpf", ], javac_shard_size: 50, } diff --git a/services/core/java/android/app/usage/OWNERS b/services/core/java/android/app/usage/OWNERS new file mode 100644 index 000000000000..3a555143b11d --- /dev/null +++ b/services/core/java/android/app/usage/OWNERS @@ -0,0 +1 @@ +include platform/frameworks/base:/core/java/android/app/usage/OWNERS diff --git a/services/core/java/android/app/usage/UsageStatsManagerInternal.java b/services/core/java/android/app/usage/UsageStatsManagerInternal.java index 21fc19ec3079..435d294a3e8e 100644 --- a/services/core/java/android/app/usage/UsageStatsManagerInternal.java +++ b/services/core/java/android/app/usage/UsageStatsManagerInternal.java @@ -17,6 +17,7 @@ package android.app.usage; import android.annotation.CurrentTimeMillisLong; +import android.annotation.ElapsedRealtimeLong; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; @@ -25,6 +26,7 @@ import android.content.ComponentName; import android.content.LocusId; import android.content.res.Configuration; import android.os.IBinder; +import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; @@ -362,4 +364,52 @@ public abstract class UsageStatsManagerInternal { /** Unregister a listener from being notified of every estimated launch time change. */ public abstract void unregisterLaunchTimeChangedListener( @NonNull EstimatedLaunchTimeChangedListener listener); + + /** + * Reports a broadcast dispatched event to the UsageStatsManager. + * + * @param sourceUid uid of the package that sent the broadcast. + * @param targetPackage name of the package that the broadcast is targeted to. + * @param targetUser user that {@code targetPackage} belongs to. + * @param idForResponseEvent ID to be used for recording any response events corresponding + * to this broadcast. + * @param timestampMs time (in millis) when the broadcast was dispatched, in + * {@link SystemClock#elapsedRealtime()} timebase. + */ + public abstract void reportBroadcastDispatched(int sourceUid, @NonNull String targetPackage, + @NonNull UserHandle targetUser, long idForResponseEvent, + @ElapsedRealtimeLong long timestampMs); + + /** + * Reports a notification posted event to the UsageStatsManager. + * + * @param packageName name of the package which posted the notification. + * @param user user that {@code packageName} belongs to. + * @param timestampMs time (in millis) when the notification was posted, in + * {@link SystemClock#elapsedRealtime()} timebase. + */ + public abstract void reportNotificationPosted(@NonNull String packageName, + @NonNull UserHandle user, @ElapsedRealtimeLong long timestampMs); + + /** + * Reports a notification updated event to the UsageStatsManager. + * + * @param packageName name of the package which updated the notification. + * @param user user that {@code packageName} belongs to. + * @param timestampMs time (in millis) when the notification was updated, in + * {@link SystemClock#elapsedRealtime()} timebase. + */ + public abstract void reportNotificationUpdated(@NonNull String packageName, + @NonNull UserHandle user, @ElapsedRealtimeLong long timestampMs); + + /** + * Reports a notification removed event to the UsageStatsManager. + * + * @param packageName name of the package which removed the notification. + * @param user user that {@code packageName} belongs to. + * @param timestampMs time (in millis) when the notification was removed, in + * {@link SystemClock#elapsedRealtime()} timebase. + */ + public abstract void reportNotificationRemoved(@NonNull String packageName, + @NonNull UserHandle user, @ElapsedRealtimeLong long timestampMs); } diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java index 60cae4d67f5a..f56bfab7055f 100644 --- a/services/core/java/android/content/pm/PackageManagerInternal.java +++ b/services/core/java/android/content/pm/PackageManagerInternal.java @@ -70,6 +70,7 @@ public abstract class PackageManagerInternal implements PackageSettingsSnapshotP PACKAGE_SYSTEM, PACKAGE_SETUP_WIZARD, PACKAGE_INSTALLER, + PACKAGE_UNINSTALLER, PACKAGE_VERIFIER, PACKAGE_BROWSER, PACKAGE_SYSTEM_TEXT_CLASSIFIER, @@ -89,23 +90,25 @@ public abstract class PackageManagerInternal implements PackageSettingsSnapshotP public static final int PACKAGE_SYSTEM = 0; public static final int PACKAGE_SETUP_WIZARD = 1; public static final int PACKAGE_INSTALLER = 2; - public static final int PACKAGE_VERIFIER = 3; - public static final int PACKAGE_BROWSER = 4; - public static final int PACKAGE_SYSTEM_TEXT_CLASSIFIER = 5; - public static final int PACKAGE_PERMISSION_CONTROLLER = 6; - public static final int PACKAGE_WELLBEING = 7; - public static final int PACKAGE_DOCUMENTER = 8; - public static final int PACKAGE_CONFIGURATOR = 9; - public static final int PACKAGE_INCIDENT_REPORT_APPROVER = 10; - public static final int PACKAGE_APP_PREDICTOR = 11; - public static final int PACKAGE_OVERLAY_CONFIG_SIGNATURE = 12; - public static final int PACKAGE_WIFI = 13; - public static final int PACKAGE_COMPANION = 14; - public static final int PACKAGE_RETAIL_DEMO = 15; - public static final int PACKAGE_RECENTS = 16; + public static final int PACKAGE_UNINSTALLER = 3; + public static final int PACKAGE_VERIFIER = 4; + public static final int PACKAGE_BROWSER = 5; + public static final int PACKAGE_SYSTEM_TEXT_CLASSIFIER = 6; + public static final int PACKAGE_PERMISSION_CONTROLLER = 7; + public static final int PACKAGE_WELLBEING = 8; + public static final int PACKAGE_DOCUMENTER = 9; + public static final int PACKAGE_CONFIGURATOR = 10; + public static final int PACKAGE_INCIDENT_REPORT_APPROVER = 11; + public static final int PACKAGE_APP_PREDICTOR = 12; + public static final int PACKAGE_OVERLAY_CONFIG_SIGNATURE = 13; + public static final int PACKAGE_WIFI = 14; + public static final int PACKAGE_COMPANION = 15; + public static final int PACKAGE_RETAIL_DEMO = 16; + public static final int PACKAGE_RECENTS = 17; + public static final int PACKAGE_AMBIENT_CONTEXT_DETECTION = 18; // Integer value of the last known package ID. Increases as new ID is added to KnownPackage. // Please note the numbers should be continuous. - public static final int LAST_KNOWN_PACKAGE = PACKAGE_RECENTS; + public static final int LAST_KNOWN_PACKAGE = PACKAGE_AMBIENT_CONTEXT_DETECTION; @LongDef(flag = true, prefix = "RESOLVE_", value = { RESOLVE_NON_BROWSER_ONLY, @@ -1141,6 +1144,8 @@ public abstract class PackageManagerInternal implements PackageSettingsSnapshotP return "Setup Wizard"; case PACKAGE_INSTALLER: return "Installer"; + case PACKAGE_UNINSTALLER: + return "Uninstaller"; case PACKAGE_VERIFIER: return "Verifier"; case PACKAGE_BROWSER: diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java index 844ac86e8eb5..5d48d7821cad 100644 --- a/services/core/java/com/android/server/BatteryService.java +++ b/services/core/java/com/android/server/BatteryService.java @@ -853,7 +853,9 @@ public final class BatteryService extends SystemService { pw.println("Battery service (battery) commands:"); pw.println(" help"); pw.println(" Print this help text."); - pw.println(" set [-f] [ac|usb|wireless|status|level|temp|present|invalid] <value>"); + pw.println(" get [-f] [ac|usb|wireless|status|level|temp|present|counter|invalid]"); + pw.println( + " set [-f] [ac|usb|wireless|status|level|temp|present|counter|invalid] <value>"); pw.println(" Force a battery property value, freezing battery state."); pw.println(" -f: force a battery change broadcast be sent, prints new sequence."); pw.println(" unplug [-f]"); @@ -863,7 +865,7 @@ public final class BatteryService extends SystemService { pw.println(" Unfreeze battery state, returning to current hardware values."); pw.println(" -f: force a battery change broadcast be sent, prints new sequence."); if (Build.IS_DEBUGGABLE) { - pw.println(" disable_charge"); + pw.println(" suspend_input"); pw.println(" Suspend charging even if plugged in. "); } } @@ -893,6 +895,46 @@ public final class BatteryService extends SystemService { android.Manifest.permission.DEVICE_POWER, null); unplugBattery(/* forceUpdate= */ (opts & OPTION_FORCE_UPDATE) != 0, pw); } break; + case "get": { + final String key = shell.getNextArg(); + if (key == null) { + pw.println("No property specified"); + return -1; + + } + switch (key) { + case "present": + pw.println(mHealthInfo.batteryPresent); + break; + case "ac": + pw.println(mHealthInfo.chargerAcOnline); + break; + case "usb": + pw.println(mHealthInfo.chargerUsbOnline); + break; + case "wireless": + pw.println(mHealthInfo.chargerWirelessOnline); + break; + case "status": + pw.println(mHealthInfo.batteryStatus); + break; + case "level": + pw.println(mHealthInfo.batteryLevel); + break; + case "counter": + pw.println(mHealthInfo.batteryChargeCounterUah); + break; + case "temp": + pw.println(mHealthInfo.batteryTemperatureTenthsCelsius); + break; + case "invalid": + pw.println(mInvalidCharger); + break; + default: + pw.println("Unknown get option: " + key); + break; + } + } break; case "set": { int opts = parseOptions(shell); getContext().enforceCallingOrSelfPermission( diff --git a/services/core/java/com/android/server/MasterClearReceiver.java b/services/core/java/com/android/server/MasterClearReceiver.java index 62dc27330f87..be2b7f72bd4f 100644 --- a/services/core/java/com/android/server/MasterClearReceiver.java +++ b/services/core/java/com/android/server/MasterClearReceiver.java @@ -16,11 +16,14 @@ package com.android.server; +import static android.app.admin.DevicePolicyResources.Strings.Core.WORK_PROFILE_DELETED_TITLE; + import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.Notification; import android.app.NotificationManager; import android.app.ProgressDialog; +import android.app.admin.DevicePolicyManager; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -155,7 +158,7 @@ public class MasterClearReceiver extends BroadcastReceiver { final Notification notification = new Notification.Builder(context, SystemNotificationChannels.DEVICE_ADMIN) .setSmallIcon(android.R.drawable.stat_sys_warning) - .setContentTitle(context.getString(R.string.work_profile_deleted)) + .setContentTitle(getWorkProfileDeletedTitle(context)) .setContentText(wipeReason) .setColor(context.getColor(R.color.system_notification_accent_color)) .setStyle(new Notification.BigTextStyle().bigText(wipeReason)) @@ -164,6 +167,12 @@ public class MasterClearReceiver extends BroadcastReceiver { SystemMessageProto.SystemMessage.NOTE_PROFILE_WIPED, notification); } + private String getWorkProfileDeletedTitle(Context context) { + final DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class); + return dpm.getString(WORK_PROFILE_DELETED_TITLE, + () -> context.getString(R.string.work_profile_deleted)); + } + private @UserIdInt int getCurrentForegroundUserId() { try { return ActivityManager.getCurrentUser(); diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index 98764e02d803..f71f02a6ec4e 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -160,8 +160,6 @@ import com.android.server.storage.StorageSessionController.ExternalStorageServic import com.android.server.wm.ActivityTaskManagerInternal; import com.android.server.wm.ActivityTaskManagerInternal.ScreenObserver; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; import libcore.io.IoUtils; import libcore.util.EmptyArray; @@ -173,6 +171,8 @@ import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; import java.io.PrintWriter; import java.math.BigInteger; import java.security.GeneralSecurityException; @@ -3698,16 +3698,29 @@ class StorageManagerService extends IStorageManager.Stub @Nullable public PendingIntent getManageSpaceActivityIntent( @NonNull String packageName, int requestCode) { - // Only Apps with MANAGE_EXTERNAL_STORAGE permission should be able to call this API. - enforcePermission(android.Manifest.permission.MANAGE_EXTERNAL_STORAGE); - - // We want to call the manageSpaceActivity as a SystemService and clear identity - // of the calling App + // Only Apps with MANAGE_EXTERNAL_STORAGE permission which have package visibility for + // packageName should be able to call this API. int originalUid = Binder.getCallingUidOrThrow(); - final long token = Binder.clearCallingIdentity(); + try { + // Get package name for calling app and verify it has MANAGE_EXTERNAL_STORAGE permission + final String[] packagesFromUid = mIPackageManager.getPackagesForUid(originalUid); + if (packagesFromUid == null) { + throw new SecurityException("Unknown uid " + originalUid); + } + // Checking first entry in packagesFromUid is enough as using "sharedUserId" + // mechanism is rare and discouraged. Also, Apps that share same UID share the same + // permissions. + if (!mStorageManagerInternal.hasExternalStorageAccess(originalUid, + packagesFromUid[0])) { + throw new SecurityException("Only File Manager Apps permitted"); + } + } catch (RemoteException re) { + throw new SecurityException("Unknown uid " + originalUid, re); + } + ApplicationInfo appInfo; try { - ApplicationInfo appInfo = mIPackageManager.getApplicationInfo(packageName, 0, + appInfo = mIPackageManager.getApplicationInfo(packageName, 0, UserHandle.getUserId(originalUid)); if (appInfo == null) { throw new IllegalArgumentException( @@ -3717,8 +3730,15 @@ class StorageManagerService extends IStorageManager.Stub Log.i(TAG, packageName + " doesn't have a manageSpaceActivity"); return null; } - Context targetAppContext = mContext.createPackageContext(packageName, 0); + } catch (RemoteException e) { + throw new SecurityException("Only File Manager Apps permitted"); + } + // We want to call the manageSpaceActivity as a SystemService and clear identity + // of the calling App + final long token = Binder.clearCallingIdentity(); + try { + Context targetAppContext = mContext.createPackageContext(packageName, 0); Intent intent = new Intent(Intent.ACTION_DEFAULT); intent.setClassName(packageName, appInfo.manageSpaceActivityName); @@ -3728,8 +3748,6 @@ class StorageManagerService extends IStorageManager.Stub intent, FLAG_ONE_SHOT | FLAG_CANCEL_CURRENT | FLAG_IMMUTABLE); return activity; - } catch (RemoteException e) { - throw e.rethrowAsRuntimeException(); } catch (PackageManager.NameNotFoundException e) { throw new IllegalArgumentException( "packageName not found"); @@ -4955,19 +4973,17 @@ class StorageManagerService extends IStorageManager.Stub @Override public boolean hasExternalStorageAccess(int uid, String packageName) { try { - if (mIPackageManager.checkUidPermission( - MANAGE_EXTERNAL_STORAGE, uid) == PERMISSION_GRANTED) { - return true; + final int opMode = mIAppOpsService.checkOperation( + OP_MANAGE_EXTERNAL_STORAGE, uid, packageName); + if (opMode == AppOpsManager.MODE_DEFAULT) { + return mIPackageManager.checkUidPermission( + MANAGE_EXTERNAL_STORAGE, uid) == PERMISSION_GRANTED; } - if (mIAppOpsService.checkOperation( - OP_MANAGE_EXTERNAL_STORAGE, uid, packageName) == MODE_ALLOWED) { - return true; - } + return opMode == AppOpsManager.MODE_ALLOWED; } catch (RemoteException e) { Slog.w("Failed to check MANAGE_EXTERNAL_STORAGE access for " + packageName, e); } - return false; } diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java index 81627a05c9a4..c236a7f80000 100644 --- a/services/core/java/com/android/server/UiModeManagerService.java +++ b/services/core/java/com/android/server/UiModeManagerService.java @@ -19,6 +19,9 @@ package com.android.server; import static android.app.UiModeManager.DEFAULT_PRIORITY; import static android.app.UiModeManager.MODE_NIGHT_AUTO; import static android.app.UiModeManager.MODE_NIGHT_CUSTOM; +import static android.app.UiModeManager.MODE_NIGHT_CUSTOM_TYPE_BEDTIME; +import static android.app.UiModeManager.MODE_NIGHT_CUSTOM_TYPE_SCHEDULE; +import static android.app.UiModeManager.MODE_NIGHT_CUSTOM_TYPE_UNKNOWN; import static android.app.UiModeManager.MODE_NIGHT_NO; import static android.app.UiModeManager.MODE_NIGHT_YES; import static android.app.UiModeManager.PROJECTION_TYPE_AUTOMOTIVE; @@ -40,6 +43,7 @@ import android.app.NotificationManager; import android.app.PendingIntent; import android.app.StatusBarManager; import android.app.UiModeManager; +import android.app.UiModeManager.NightModeCustomType; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -115,6 +119,7 @@ final class UiModeManagerService extends SystemService { private int mLastBroadcastState = Intent.EXTRA_DOCK_STATE_UNDOCKED; private int mNightMode = UiModeManager.MODE_NIGHT_NO; + private int mNightModeCustomType = UiModeManager.MODE_NIGHT_CUSTOM_TYPE_UNKNOWN; private final LocalTime DEFAULT_CUSTOM_NIGHT_START_TIME = LocalTime.of(22, 0); private final LocalTime DEFAULT_CUSTOM_NIGHT_END_TIME = LocalTime.of(6, 0); private LocalTime mCustomAutoNightModeStartMilliseconds = DEFAULT_CUSTOM_NIGHT_START_TIME; @@ -136,6 +141,7 @@ final class UiModeManagerService extends SystemService { private boolean mWatch; private boolean mVrHeadset; private boolean mComputedNightMode; + private boolean mLastBedtimeRequestedNightMode = false; private int mCarModeEnableFlags; private boolean mSetupWizardComplete; @@ -541,7 +547,9 @@ final class UiModeManagerService extends SystemService { mNightMode = Secure.getIntForUser(context.getContentResolver(), Secure.UI_NIGHT_MODE, res.getInteger( com.android.internal.R.integer.config_defaultNightMode), userId); - mOverrideNightModeOn = Secure.getIntForUser(context.getContentResolver(), + mNightModeCustomType = Secure.getIntForUser(context.getContentResolver(), + Secure.UI_NIGHT_MODE_CUSTOM_TYPE, MODE_NIGHT_CUSTOM_TYPE_UNKNOWN, userId); + mOverrideNightModeOn = Secure.getIntForUser(context.getContentResolver(), Secure.UI_NIGHT_MODE_OVERRIDE_ON, 0, userId) != 0; mOverrideNightModeOff = Secure.getIntForUser(context.getContentResolver(), Secure.UI_NIGHT_MODE_OVERRIDE_OFF, 0, userId) != 0; @@ -702,6 +710,14 @@ final class UiModeManagerService extends SystemService { @Override public void setNightMode(int mode) { + // MODE_NIGHT_CUSTOM_TYPE_SCHEDULE is the default for MODE_NIGHT_CUSTOM. + int customModeType = mode == MODE_NIGHT_CUSTOM + ? MODE_NIGHT_CUSTOM_TYPE_SCHEDULE + : MODE_NIGHT_CUSTOM_TYPE_UNKNOWN; + setNightModeInternal(mode, customModeType); + } + + private void setNightModeInternal(int mode, int customModeType) { if (isNightModeLocked() && (getContext().checkCallingOrSelfPermission( android.Manifest.permission.MODIFY_DAY_NIGHT_MODE) != PackageManager.PERMISSION_GRANTED)) { @@ -722,12 +738,14 @@ final class UiModeManagerService extends SystemService { final long ident = Binder.clearCallingIdentity(); try { synchronized (mLock) { - if (mNightMode != mode) { + if (mNightMode != mode || mNightModeCustomType != customModeType) { if (mNightMode == MODE_NIGHT_AUTO || mNightMode == MODE_NIGHT_CUSTOM) { unregisterScreenOffEventLocked(); cancelCustomAlarm(); } - + mNightModeCustomType = mode == MODE_NIGHT_CUSTOM + ? customModeType + : MODE_NIGHT_CUSTOM_TYPE_UNKNOWN; mNightMode = mode; resetNightModeOverrideLocked(); persistNightMode(user); @@ -754,6 +772,30 @@ final class UiModeManagerService extends SystemService { } @Override + public void setNightModeCustomType(@NightModeCustomType int nightModeCustomType) { + if (getContext().checkCallingOrSelfPermission( + android.Manifest.permission.MODIFY_DAY_NIGHT_MODE) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException( + "setNightModeCustomType requires MODIFY_DAY_NIGHT_MODE permission"); + } + setNightModeInternal(MODE_NIGHT_CUSTOM, nightModeCustomType); + } + + @Override + public int getNightModeCustomType() { + if (getContext().checkCallingOrSelfPermission( + android.Manifest.permission.MODIFY_DAY_NIGHT_MODE) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException( + "getNightModeCustomType requires MODIFY_DAY_NIGHT_MODE permission"); + } + synchronized (mLock) { + return mNightModeCustomType; + } + } + + @Override public void setApplicationNightMode(@UiModeManager.NightMode int mode) { switch (mode) { case UiModeManager.MODE_NIGHT_NO: @@ -808,10 +850,19 @@ final class UiModeManagerService extends SystemService { } @Override + public boolean setNightModeActivatedForCustomMode(int modeNightCustomType, boolean active) { + return setNightModeActivatedForModeInternal(modeNightCustomType, active); + } + + @Override public boolean setNightModeActivated(boolean active) { - if (isNightModeLocked() && (getContext().checkCallingOrSelfPermission( + return setNightModeActivatedForModeInternal(mNightModeCustomType, active); + } + + private boolean setNightModeActivatedForModeInternal(int modeCustomType, boolean active) { + if (getContext().checkCallingOrSelfPermission( android.Manifest.permission.MODIFY_DAY_NIGHT_MODE) - != PackageManager.PERMISSION_GRANTED)) { + != PackageManager.PERMISSION_GRANTED) { Slog.e(TAG, "Night mode locked, requires MODIFY_DAY_NIGHT_MODE permission"); return false; } @@ -824,6 +875,14 @@ final class UiModeManagerService extends SystemService { return false; } + // Store the last requested bedtime night mode state so that we don't need to notify + // anyone if the user decides to switch to the night mode to bedtime. + if (modeCustomType == MODE_NIGHT_CUSTOM_TYPE_BEDTIME) { + mLastBedtimeRequestedNightMode = active; + } + if (modeCustomType != mNightModeCustomType) { + return false; + } synchronized (mLock) { final long ident = Binder.clearCallingIdentity(); try { @@ -1422,6 +1481,8 @@ final class UiModeManagerService extends SystemService { Secure.putIntForUser(getContext().getContentResolver(), Secure.UI_NIGHT_MODE, mNightMode, user); Secure.putLongForUser(getContext().getContentResolver(), + Secure.UI_NIGHT_MODE_CUSTOM_TYPE, mNightModeCustomType, user); + Secure.putLongForUser(getContext().getContentResolver(), Secure.DARK_THEME_CUSTOM_START_TIME, mCustomAutoNightModeStartMilliseconds.toNanoOfDay() / 1000, user); Secure.putLongForUser(getContext().getContentResolver(), @@ -1473,10 +1534,14 @@ final class UiModeManagerService extends SystemService { } if (mNightMode == MODE_NIGHT_CUSTOM) { - registerTimeChangeEvent(); - final boolean activate = computeCustomNightMode(); - updateComputedNightModeLocked(activate); - scheduleNextCustomTimeListener(); + if (mNightModeCustomType == MODE_NIGHT_CUSTOM_TYPE_BEDTIME) { + updateComputedNightModeLocked(mLastBedtimeRequestedNightMode); + } else { + registerTimeChangeEvent(); + final boolean activate = computeCustomNightMode(); + updateComputedNightModeLocked(activate); + scheduleNextCustomTimeListener(); + } } else { unregisterTimeChangeEvent(); } @@ -1494,6 +1559,7 @@ final class UiModeManagerService extends SystemService { "updateConfigurationLocked: mDockState=" + mDockState + "; mCarMode=" + mCarModeEnabled + "; mNightMode=" + mNightMode + + "; mNightModeCustomType=" + mNightModeCustomType + "; uiMode=" + uiMode); } @@ -1534,7 +1600,8 @@ final class UiModeManagerService extends SystemService { } private boolean shouldApplyAutomaticChangesImmediately() { - return mCar || !mPowerManager.isInteractive(); + return mCar || !mPowerManager.isInteractive() + || mNightModeCustomType == MODE_NIGHT_CUSTOM_TYPE_BEDTIME; } private void scheduleNextCustomTimeListener() { diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index b92556bf9a20..888710857706 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -4138,7 +4138,7 @@ public final class ActiveServices { // for a previous process to come up. To deal with this, we store // in the service any current isolated process it is running in or // waiting to have come up. - app = r.isolatedProc; + app = r.isolationHostProc; if (WebViewZygote.isMultiprocessEnabled() && r.serviceInfo.packageName.equals(WebViewZygote.getPackageName())) { hostingRecord = HostingRecord.byWebviewZygote(r.instanceName); @@ -4165,7 +4165,7 @@ public final class ActiveServices { return msg; } if (isolated) { - r.isolatedProc = app; + r.isolationHostProc = app; } } @@ -4976,7 +4976,7 @@ public final class ActiveServices { try { for (int i=0; i<mPendingServices.size(); i++) { sr = mPendingServices.get(i); - if (proc != sr.isolatedProc && (proc.uid != sr.appInfo.uid + if (proc != sr.isolationHostProc && (proc.uid != sr.appInfo.uid || !processName.equals(sr.processName))) { continue; } @@ -5016,7 +5016,7 @@ public final class ActiveServices { boolean didImmediateRestart = false; for (int i=0; i<mRestartingServices.size(); i++) { sr = mRestartingServices.get(i); - if (proc != sr.isolatedProc && (proc.uid != sr.appInfo.uid + if (proc != sr.isolationHostProc && (proc.uid != sr.appInfo.uid || !processName.equals(sr.processName))) { continue; } @@ -5048,9 +5048,9 @@ public final class ActiveServices { ServiceRecord sr = mPendingServices.get(i); if ((proc.uid == sr.appInfo.uid && proc.processName.equals(sr.processName)) - || sr.isolatedProc == proc) { + || sr.isolationHostProc == proc) { Slog.w(TAG, "Forcing bringing down service: " + sr); - sr.isolatedProc = null; + sr.isolationHostProc = null; mPendingServices.remove(i); size = mPendingServices.size(); i--; @@ -5083,7 +5083,7 @@ public final class ActiveServices { stopServiceAndUpdateAllowlistManagerLocked(service); } service.setProcess(null, null, 0, null); - service.isolatedProc = null; + service.isolationHostProc = null; if (mTmpCollectionResults == null) { mTmpCollectionResults = new ArrayList<>(); } @@ -5321,7 +5321,7 @@ public final class ActiveServices { sr.app.mServices.updateBoundClientUids(); } sr.setProcess(null, null, 0, null); - sr.isolatedProc = null; + sr.isolationHostProc = null; sr.executeNesting = 0; synchronized (mAm.mProcessStats.mLock) { sr.forceClearTracker(); @@ -6792,7 +6792,8 @@ public final class ActiveServices { r.mFgsNotificationShown, durationMs, r.mStartForegroundCount, - ActivityManagerUtils.hashComponentNameForAtom(r.shortInstanceName)); + ActivityManagerUtils.hashComponentNameForAtom(r.shortInstanceName), + r.mFgsHasNotificationPermission); } boolean canAllowWhileInUsePermissionInFgsLocked(int callingPid, int callingUid, diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index f67e732b47dd..b1b4c4447ec8 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -2871,13 +2871,51 @@ public class ActivityManagerService extends IActivityManager.Stub return mode == AppOpsManager.MODE_ALLOWED; } - @Override - public int getPackageProcessState(String packageName, String callingPackage) { + /** + * Checks whether the calling package is trusted. + * + * The calling package is trusted if it's from system or the supposed package name matches the + * UID making the call. + * + * @throws SecurityException if the package name and UID don't match. + */ + private void verifyCallingPackage(String callingPackage) { + final int callingUid = Binder.getCallingUid(); + // The caller is System or Shell. + if (callingUid == SYSTEM_UID || isCallerShell()) { + return; + } + + // Handle the special UIDs that don't have real package (audioserver, cameraserver, etc). + final String resolvedPackage = AppOpsManager.resolvePackageName(callingUid, + null /* packageName */); + if (resolvedPackage != null && resolvedPackage.equals(callingPackage)) { + return; + } + + final int claimedUid = getPackageManagerInternal().getPackageUid(callingPackage, + 0 /* flags */, UserHandle.getUserId(callingUid)); + if (callingUid == claimedUid) { + return; + } + + throw new SecurityException( + "Claimed calling package " + callingPackage + " does not match the calling UID " + + Binder.getCallingUid()); + } + + private void enforceUsageStatsPermission(String callingPackage, String func) { + verifyCallingPackage(callingPackage); + // Since the protection level of PACKAGE_USAGE_STATS has 'appop', apps may grant this + // permission via that way. We need to check both app-ops and permission. if (!hasUsageStatsPermission(callingPackage)) { - enforceCallingPermission(android.Manifest.permission.PACKAGE_USAGE_STATS, - "getPackageProcessState"); + enforceCallingPermission(android.Manifest.permission.PACKAGE_USAGE_STATS, func); } + } + @Override + public int getPackageProcessState(String packageName, String callingPackage) { + enforceUsageStatsPermission(callingPackage, "getPackageProcessState"); final int[] procState = {PROCESS_STATE_NONEXISTENT}; synchronized (mProcLock) { mProcessList.forEachLruProcessesLOSP(false, proc -> { @@ -6938,11 +6976,7 @@ public class ActivityManagerService extends IActivityManager.Stub @Override public int getUidProcessState(int uid, String callingPackage) { - if (!hasUsageStatsPermission(callingPackage)) { - enforceCallingPermission(android.Manifest.permission.PACKAGE_USAGE_STATS, - "getUidProcessState"); - } - + enforceUsageStatsPermission(callingPackage, "getUidProcessState"); synchronized (mProcLock) { return mProcessList.getUidProcStateLOSP(uid); } @@ -6950,11 +6984,7 @@ public class ActivityManagerService extends IActivityManager.Stub @Override public @ProcessCapability int getUidProcessCapabilities(int uid, String callingPackage) { - if (!hasUsageStatsPermission(callingPackage)) { - enforceCallingPermission(android.Manifest.permission.PACKAGE_USAGE_STATS, - "getUidProcessState"); - } - + enforceUsageStatsPermission(callingPackage, "getUidProcessCapabilities"); synchronized (mProcLock) { return mProcessList.getUidProcessCapabilityLOSP(uid); } @@ -6963,10 +6993,7 @@ public class ActivityManagerService extends IActivityManager.Stub @Override public void registerUidObserver(IUidObserver observer, int which, int cutpoint, String callingPackage) { - if (!hasUsageStatsPermission(callingPackage)) { - enforceCallingPermission(android.Manifest.permission.PACKAGE_USAGE_STATS, - "registerUidObserver"); - } + enforceUsageStatsPermission(callingPackage, "registerUidObserver"); mUidObserverController.register(observer, which, cutpoint, callingPackage, Binder.getCallingUid()); } @@ -6978,10 +7005,7 @@ public class ActivityManagerService extends IActivityManager.Stub @Override public boolean isUidActive(int uid, String callingPackage) { - if (!hasUsageStatsPermission(callingPackage)) { - enforceCallingPermission(android.Manifest.permission.PACKAGE_USAGE_STATS, - "isUidActive"); - } + enforceUsageStatsPermission(callingPackage, "isUidActive"); synchronized (mProcLock) { if (isUidActiveLOSP(uid)) { return true; diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index 9ffafe256033..0b92954e7932 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -42,6 +42,7 @@ import android.os.BatteryStatsInternal; import android.os.BatteryUsageStats; import android.os.BatteryUsageStatsQuery; import android.os.Binder; +import android.os.BluetoothBatteryStats; import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; @@ -2233,6 +2234,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub pw.println(" --read-daily: read-load last written daily stats."); pw.println(" --settings: dump the settings key/values related to batterystats"); pw.println(" --cpu: dump cpu stats for debugging purpose"); + pw.println(" --power-profile: dump the power profile constants"); pw.println(" <package.name>: optional name of package to filter output by."); pw.println(" -h: print this help text."); pw.println("Battery stats (batterystats) commands:"); @@ -2270,6 +2272,12 @@ public final class BatteryStatsService extends IBatteryStats.Stub } } + private void dumpPowerProfile(PrintWriter pw) { + synchronized (mStats) { + mStats.dumpPowerProfileLocked(pw); + } + } + private int doEnableOrDisable(PrintWriter pw, int i, String[] args, boolean enable) { i++; if (i >= args.length) { @@ -2412,6 +2420,9 @@ public final class BatteryStatsService extends IBatteryStats.Stub } else if ("--measured-energy".equals(arg)) { dumpMeasuredEnergyStats(pw); return; + } else if ("--power-profile".equals(arg)) { + dumpPowerProfile(pw); + return; } else if ("-a".equals(arg)) { flags |= BatteryStats.DUMP_VERBOSE; } else if (arg.length() > 0 && arg.charAt(0) == '-'){ @@ -2617,6 +2628,20 @@ public final class BatteryStatsService extends IBatteryStats.Stub } /** + * Gets a snapshot of Bluetooth stats + * @hide + */ + public BluetoothBatteryStats getBluetoothBatteryStats() { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BATTERY_STATS, null); + + // Wait for the completion of pending works if there is any + awaitCompletion(); + synchronized (mStats) { + return mStats.getBluetoothBatteryStats(); + } + } + + /** * Gets a snapshot of the system health for a particular uid. */ @Override diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java index eb8a4e9508da..e36ea20dea48 100644 --- a/services/core/java/com/android/server/am/BroadcastQueue.java +++ b/services/core/java/com/android/server/am/BroadcastQueue.java @@ -781,6 +781,54 @@ public final class BroadcastQueue { skip = true; } } + // Check that the receiver does *not* have any excluded permissions + if (!skip && r.excludedPermissions != null && r.excludedPermissions.length > 0) { + for (int i = 0; i < r.excludedPermissions.length; i++) { + String excludedPermission = r.excludedPermissions[i]; + final int perm = mService.checkComponentPermission(excludedPermission, + filter.receiverList.pid, filter.receiverList.uid, -1, true); + + int appOp = AppOpsManager.permissionToOpCode(excludedPermission); + if (appOp != AppOpsManager.OP_NONE) { + // When there is an app op associated with the permission, + // skip when both the permission and the app op are + // granted. + if ((perm == PackageManager.PERMISSION_GRANTED) && ( + mService.getAppOpsManager().checkOpNoThrow(appOp, + filter.receiverList.uid, + filter.packageName) + == AppOpsManager.MODE_ALLOWED)) { + Slog.w(TAG, "Appop Denial: receiving " + + r.intent.toString() + + " to " + filter.receiverList.app + + " (pid=" + filter.receiverList.pid + + ", uid=" + filter.receiverList.uid + ")" + + " excludes appop " + AppOpsManager.permissionToOp( + excludedPermission) + + " due to sender " + r.callerPackage + + " (uid " + r.callingUid + ")"); + skip = true; + break; + } + } else { + // When there is no app op associated with the permission, + // skip when permission is granted. + if (perm == PackageManager.PERMISSION_GRANTED) { + Slog.w(TAG, "Permission Denial: receiving " + + r.intent.toString() + + " to " + filter.receiverList.app + + " (pid=" + filter.receiverList.pid + + ", uid=" + filter.receiverList.uid + ")" + + " excludes " + excludedPermission + + " due to sender " + r.callerPackage + + " (uid " + r.callingUid + ")"); + skip = true; + break; + } + } + } + } + // If the broadcast also requires an app op check that as well. if (!skip && r.appOp != AppOpsManager.OP_NONE && mService.getAppOpsManager().noteOpNoThrow(r.appOp, diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java index c08cf6485855..6f22c61d2b2a 100644 --- a/services/core/java/com/android/server/am/CachedAppOptimizer.java +++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java @@ -962,7 +962,7 @@ public final class CachedAppOptimizer { } opt.setFreezerOverride(false); - if (!opt.isFrozen()) { + if (pid == 0 || !opt.isFrozen()) { return; } diff --git a/services/core/java/com/android/server/am/ContentProviderHelper.java b/services/core/java/com/android/server/am/ContentProviderHelper.java index 625dd636b24e..3c5b872ec717 100644 --- a/services/core/java/com/android/server/am/ContentProviderHelper.java +++ b/services/core/java/com/android/server/am/ContentProviderHelper.java @@ -15,6 +15,7 @@ */ package com.android.server.am; +import static android.content.ContentProvider.isAuthorityRedirectedForCloneProfile; import static android.os.Process.PROC_CHAR; import static android.os.Process.PROC_OUT_LONG; import static android.os.Process.PROC_PARENS; @@ -46,6 +47,7 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.PathPermission; import android.content.pm.ProviderInfo; +import android.content.pm.UserInfo; import android.database.ContentObserver; import android.net.Uri; import android.os.Binder; @@ -72,6 +74,7 @@ import com.android.internal.os.BackgroundThread; import com.android.internal.util.ArrayUtils; import com.android.server.LocalServices; import com.android.server.RescueParty; +import com.android.server.pm.UserManagerInternal; import com.android.server.pm.parsing.pkg.AndroidPackage; import java.io.FileDescriptor; @@ -177,12 +180,22 @@ public class ContentProviderHelper { cpr = mProviderMap.getProviderByName(name, UserHandle.USER_SYSTEM); if (cpr != null) { cpi = cpr.info; + if (mService.isSingleton( cpi.processName, cpi.applicationInfo, cpi.name, cpi.flags) && mService.isValidSingletonCall( r == null ? callingUid : r.uid, cpi.applicationInfo.uid)) { userId = UserHandle.USER_SYSTEM; checkCrossUser = false; + } else if (isAuthorityRedirectedForCloneProfile(name)) { + UserManagerInternal umInternal = LocalServices.getService( + UserManagerInternal.class); + UserInfo userInfo = umInternal.getUserInfo(userId); + + if (userInfo != null && userInfo.isCloneProfile()) { + userId = umInternal.getProfileParentId(userId); + checkCrossUser = false; + } } else { cpr = null; cpi = null; @@ -1026,6 +1039,7 @@ public class ContentProviderHelper { * at the given authority and user. */ String checkContentProviderAccess(String authority, int userId) { + boolean checkUser = true; if (userId == UserHandle.USER_ALL) { mService.mContext.enforceCallingOrSelfPermission( android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, TAG); @@ -1041,6 +1055,17 @@ public class ContentProviderHelper { | PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userId); + if (cpi == null && isAuthorityRedirectedForCloneProfile(authority)) { + // This might be a provider that's running only in the system user that's + // also serving clone profiles + cpi = AppGlobals.getPackageManager().resolveContentProvider(authority, + ActivityManagerService.STOCK_PM_FLAGS + | PackageManager.GET_URI_PERMISSION_PATTERNS + | PackageManager.MATCH_DISABLED_COMPONENTS + | PackageManager.MATCH_DIRECT_BOOT_AWARE + | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, + UserHandle.USER_SYSTEM); + } } catch (RemoteException ignored) { } if (cpi == null) { @@ -1048,6 +1073,16 @@ public class ContentProviderHelper { + "; expected to find a valid ContentProvider for this authority"; } + if (isAuthorityRedirectedForCloneProfile(authority)) { + UserManagerInternal umInternal = LocalServices.getService(UserManagerInternal.class); + UserInfo userInfo = umInternal.getUserInfo(userId); + + if (userInfo != null && userInfo.isCloneProfile()) { + userId = umInternal.getProfileParentId(userId); + checkUser = false; + } + } + final int callingPid = Binder.getCallingPid(); ProcessRecord r; final String appName; @@ -1060,7 +1095,7 @@ public class ContentProviderHelper { } return checkContentProviderPermission(cpi, callingPid, Binder.getCallingUid(), - userId, true, appName); + userId, checkUser, appName); } int checkContentProviderUriPermission(Uri uri, int userId, int callingUid, int modeFlags) { diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java index b1234962efc2..bdfd02e9c25a 100644 --- a/services/core/java/com/android/server/am/OomAdjuster.java +++ b/services/core/java/com/android/server/am/OomAdjuster.java @@ -598,7 +598,7 @@ public class OomAdjuster { for (int i = psr.numberOfConnections() - 1; i >= 0; i--) { ConnectionRecord cr = psr.getConnectionAt(i); ProcessRecord service = (cr.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) != 0 - ? cr.binding.service.isolatedProc : cr.binding.service.app; + ? cr.binding.service.isolationHostProc : cr.binding.service.app; if (service == null || service == pr) { continue; } diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java index c830554c398b..be187e21db47 100644 --- a/services/core/java/com/android/server/am/ProcessRecord.java +++ b/services/core/java/com/android/server/am/ProcessRecord.java @@ -513,7 +513,7 @@ class ProcessRecord implements WindowProcessListener { } } processInfo = procInfo; - isolated = _info.uid != _uid; + isolated = Process.isIsolated(_uid); appZygote = (UserHandle.getAppId(_uid) >= Process.FIRST_APP_ZYGOTE_ISOLATED_UID && UserHandle.getAppId(_uid) <= Process.LAST_APP_ZYGOTE_ISOLATED_UID); uid = _uid; diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java index b3e46cd0b526..24e7ba4a32b9 100644 --- a/services/core/java/com/android/server/am/ServiceRecord.java +++ b/services/core/java/com/android/server/am/ServiceRecord.java @@ -102,7 +102,8 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN // IBinder -> ConnectionRecord of all bound clients ProcessRecord app; // where this service is running or null. - ProcessRecord isolatedProc; // keep track of isolated process, if requested + ProcessRecord isolationHostProc; // process which we've started for this service (used for + // isolated and supplemental processes) ServiceState tracker; // tracking service execution, may be null ServiceState restartTracker; // tracking service restart boolean allowlistManager; // any bindings to this service have BIND_ALLOW_WHITELIST_MANAGEMENT? @@ -175,7 +176,6 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN // FGS notification was shown before the FGS finishes, or it wasn't deferred in the first place. boolean mFgsNotificationShown; // Whether FGS package has permissions to show notifications. - // TODO(b/194833441): Output this field to logs in ActiveServices#logFGSStateChangeLocked. boolean mFgsHasNotificationPermission; // allow the service becomes foreground service? Service started from background may not be @@ -353,8 +353,8 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN if (app != null) { app.dumpDebug(proto, ServiceRecordProto.APP); } - if (isolatedProc != null) { - isolatedProc.dumpDebug(proto, ServiceRecordProto.ISOLATED_PROC); + if (isolationHostProc != null) { + isolationHostProc.dumpDebug(proto, ServiceRecordProto.ISOLATED_PROC); } proto.write(ServiceRecordProto.WHITELIST_MANAGER, allowlistManager); proto.write(ServiceRecordProto.DELAYED, delayed); @@ -456,8 +456,8 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN pw.print(prefix); pw.print("dataDir="); pw.println(appInfo.dataDir); } pw.print(prefix); pw.print("app="); pw.println(app); - if (isolatedProc != null) { - pw.print(prefix); pw.print("isolatedProc="); pw.println(isolatedProc); + if (isolationHostProc != null) { + pw.print(prefix); pw.print("isolationHostProc="); pw.println(isolationHostProc); } if (allowlistManager) { pw.print(prefix); pw.print("allowlistManager="); pw.println(allowlistManager); @@ -593,6 +593,10 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN userId = UserHandle.getUserId(appInfo.uid); createdFromFg = callerIsFg; updateKeepWarmLocked(); + // initialize notification permission state; this'll be updated whenever there's an attempt + // to post or update a notification, but that doesn't cover the time before the first + // notification + updateFgsHasNotificationPermission(); } public ServiceState getTracker() { @@ -950,6 +954,25 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN return lastStartId; } + private void updateFgsHasNotificationPermission() { + // Do asynchronous communication with notification manager to avoid deadlocks. + final String localPackageName = packageName; + final int appUid = appInfo.uid; + + ams.mHandler.post(new Runnable() { + public void run() { + NotificationManagerInternal nm = LocalServices.getService( + NotificationManagerInternal.class); + if (nm == null) { + return; + } + // Record whether the package has permission to notify the user + mFgsHasNotificationPermission = nm.areNotificationsEnabledForPackage( + localPackageName, appUid); + } + }); + } + public void postNotification() { if (isForeground && foregroundNoti != null && app != null) { final int appUid = appInfo.uid; diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index 028a0ec8648a..252584c76696 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -2903,7 +2903,7 @@ class UserController implements Handler.Callback { */ mHandler.removeMessages(CLEAR_USER_JOURNEY_SESSION_MSG); mHandler.sendMessageDelayed(mHandler.obtainMessage(CLEAR_USER_JOURNEY_SESSION_MSG, - target.id), USER_JOURNEY_TIMEOUT_MS); + target.id, /* arg2= */ 0), USER_JOURNEY_TIMEOUT_MS); } FrameworkStatsLog.write(FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED, newSessionId, diff --git a/services/core/java/com/android/server/ambientcontext/AmbientContextManagerPerUserService.java b/services/core/java/com/android/server/ambientcontext/AmbientContextManagerPerUserService.java new file mode 100644 index 000000000000..6982513f8f2f --- /dev/null +++ b/services/core/java/com/android/server/ambientcontext/AmbientContextManagerPerUserService.java @@ -0,0 +1,294 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.ambientcontext; + +import android.Manifest; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.UserIdInt; +import android.app.AppGlobals; +import android.app.BroadcastOptions; +import android.app.PendingIntent; +import android.app.ambientcontext.AmbientContextEvent; +import android.app.ambientcontext.AmbientContextEventRequest; +import android.app.ambientcontext.AmbientContextEventResponse; +import android.app.ambientcontext.AmbientContextManager; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ServiceInfo; +import android.os.Binder; +import android.os.RemoteCallback; +import android.os.RemoteException; +import android.service.ambientcontext.AmbientContextDetectionService; +import android.util.IndentingPrintWriter; +import android.util.Slog; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.infra.AbstractPerUserSystemService; + +import java.io.PrintWriter; +import java.util.HashSet; +import java.util.Set; + +/** + * Per-user manager service for {@link AmbientContextEvent}s. + */ +final class AmbientContextManagerPerUserService extends + AbstractPerUserSystemService<AmbientContextManagerPerUserService, + AmbientContextManagerService> { + private static final String TAG = AmbientContextManagerPerUserService.class.getSimpleName(); + + @Nullable + @VisibleForTesting + RemoteAmbientContextDetectionService mRemoteService; + + private ComponentName mComponentName; + private Context mContext; + private Set<PendingIntent> mExistingPendingIntents; + + AmbientContextManagerPerUserService( + @NonNull AmbientContextManagerService master, Object lock, @UserIdInt int userId) { + super(master, lock, userId); + mContext = master.getContext(); + mExistingPendingIntents = new HashSet<>(); + } + + void destroyLocked() { + if (isVerbose()) { + Slog.v(TAG, "destroyLocked()"); + } + + Slog.d(TAG, "Trying to cancel the remote request. Reason: Service destroyed."); + if (mRemoteService != null) { + synchronized (mLock) { + mRemoteService.unbind(); + mRemoteService = null; + } + } + } + + @GuardedBy("mLock") + private void ensureRemoteServiceInitiated() { + if (mRemoteService == null) { + mRemoteService = new RemoteAmbientContextDetectionService( + getContext(), mComponentName, getUserId()); + } + } + + /** + * get the currently bound component name. + */ + @VisibleForTesting + ComponentName getComponentName() { + return mComponentName; + } + + + /** + * Resolves and sets up the service if it had not been done yet. Returns true if the service + * is available. + */ + @GuardedBy("mLock") + @VisibleForTesting + boolean setUpServiceIfNeeded() { + if (mComponentName == null) { + mComponentName = updateServiceInfoLocked(); + } + return mComponentName != null; + } + + @Override + protected ServiceInfo newServiceInfoLocked(@NonNull ComponentName serviceComponent) + throws PackageManager.NameNotFoundException { + ServiceInfo serviceInfo; + try { + serviceInfo = AppGlobals.getPackageManager().getServiceInfo(serviceComponent, + 0, mUserId); + if (serviceInfo != null) { + final String permission = serviceInfo.permission; + if (!Manifest.permission.BIND_AMBIENT_CONTEXT_DETECTION_SERVICE.equals( + permission)) { + throw new SecurityException(String.format( + "Service %s requires %s permission. Found %s permission", + serviceInfo.getComponentName(), + Manifest.permission.BIND_AMBIENT_CONTEXT_DETECTION_SERVICE, + serviceInfo.permission)); + } + } + } catch (RemoteException e) { + throw new PackageManager.NameNotFoundException( + "Could not get service for " + serviceComponent); + } + return serviceInfo; + } + + @Override + protected void dumpLocked(@NonNull String prefix, @NonNull PrintWriter pw) { + synchronized (super.mLock) { + super.dumpLocked(prefix, pw); + } + if (mRemoteService != null) { + mRemoteService.dump("", new IndentingPrintWriter(pw, " ")); + } + } + + /** + * Handles client registering as an observer. Only one registration is supported per app + * package. A new registration from the same package will overwrite the previous registration. + */ + public void onRegisterObserver(AmbientContextEventRequest request, + PendingIntent pendingIntent) { + synchronized (mLock) { + if (!setUpServiceIfNeeded()) { + Slog.w(TAG, "Service is not available at this moment."); + sendStatusUpdateIntent( + pendingIntent, AmbientContextEventResponse.STATUS_SERVICE_UNAVAILABLE); + return; + } + + // Remove any existing intent and unregister for this package before adding a new one. + String callingPackage = pendingIntent.getCreatorPackage(); + PendingIntent duplicatePendingIntent = findExistingRequestByPackage(callingPackage); + if (duplicatePendingIntent != null) { + Slog.d(TAG, "Unregister duplicate request from " + callingPackage); + onUnregisterObserver(callingPackage); + mExistingPendingIntents.remove(duplicatePendingIntent); + } + + // Register new package and add request to mExistingRequests + startDetection(request, callingPackage, createRemoteCallback()); + mExistingPendingIntents.add(pendingIntent); + } + } + + @VisibleForTesting + void startDetection(AmbientContextEventRequest request, String callingPackage, + RemoteCallback callback) { + Slog.d(TAG, "Requested detection of " + request.getEventTypes()); + synchronized (mLock) { + ensureRemoteServiceInitiated(); + mRemoteService.startDetection(request, callingPackage, callback); + } + } + + /** + * Sends an intent with a status code and empty events. + */ + void sendStatusUpdateIntent(PendingIntent pendingIntent, int statusCode) { + AmbientContextEventResponse response = new AmbientContextEventResponse.Builder() + .setStatusCode(statusCode) + .build(); + sendResponseIntent(pendingIntent, response); + } + + /** + * Unregisters the client from all previously registered events by removing from the + * mExistingRequests map, and unregister events from the service if those events are not + * requested by other apps. + */ + public void onUnregisterObserver(String callingPackage) { + synchronized (mLock) { + PendingIntent pendingIntent = findExistingRequestByPackage(callingPackage); + if (pendingIntent == null) { + Slog.d(TAG, "No registration found for " + callingPackage); + return; + } + + // Remove from existing requests + mExistingPendingIntents.remove(pendingIntent); + stopDetection(pendingIntent.getCreatorPackage()); + } + } + + @VisibleForTesting + void stopDetection(String packageName) { + Slog.d(TAG, "Stop detection for " + packageName); + synchronized (mLock) { + ensureRemoteServiceInitiated(); + mRemoteService.stopDetection(packageName); + } + } + + @Nullable + private PendingIntent findExistingRequestByPackage(String callingPackage) { + for (PendingIntent pendingIntent : mExistingPendingIntents) { + if (pendingIntent.getCreatorPackage().equals(callingPackage)) { + return pendingIntent; + } + } + return null; + } + + /** + * Sends out the Intent to the client after the event is detected. + * + * @param pendingIntent Client's PendingIntent for callback + * @param response Response with status code and detection result + */ + private void sendResponseIntent(PendingIntent pendingIntent, + AmbientContextEventResponse response) { + Intent intent = new Intent(); + intent.putExtra(AmbientContextManager.EXTRA_AMBIENT_CONTEXT_EVENT_RESPONSE, response); + // Explicitly disallow the receiver from starting activities, to prevent apps from utilizing + // the PendingIntent as a backdoor to do this. + BroadcastOptions options = BroadcastOptions.makeBasic(); + options.setPendingIntentBackgroundActivityLaunchAllowed(false); + try { + pendingIntent.send(getContext(), 0, intent, null, null, null, + options.toBundle()); + Slog.i(TAG, "Sending PendingIntent to " + pendingIntent.getCreatorPackage() + ": " + + response); + } catch (PendingIntent.CanceledException e) { + Slog.w(TAG, "Couldn't deliver pendingIntent:" + pendingIntent); + } + } + + @NonNull + private RemoteCallback createRemoteCallback() { + return new RemoteCallback(result -> { + AmbientContextEventResponse response = (AmbientContextEventResponse) result.get( + AmbientContextDetectionService.RESPONSE_BUNDLE_KEY); + final long token = Binder.clearCallingIdentity(); + try { + Set<PendingIntent> pendingIntentForFailedRequests = new HashSet<>(); + for (PendingIntent pendingIntent : mExistingPendingIntents) { + // Send PendingIntent if a requesting package matches the response packages. + if (response.getPackageName().equals(pendingIntent.getCreatorPackage())) { + sendResponseIntent(pendingIntent, response); + + int statusCode = response.getStatusCode(); + if (statusCode != AmbientContextEventResponse.STATUS_SUCCESS) { + pendingIntentForFailedRequests.add(pendingIntent); + } + Slog.i(TAG, "Got response of " + response.getEvents() + " for " + + pendingIntent.getCreatorPackage() + ". Status: " + statusCode); + } + } + + // Removes the failed requests from the existing requests. + for (PendingIntent pendingIntent : pendingIntentForFailedRequests) { + mExistingPendingIntents.remove(pendingIntent); + } + } finally { + Binder.restoreCallingIdentity(token); + } + }); + } +} diff --git a/services/core/java/com/android/server/ambientcontext/AmbientContextManagerService.java b/services/core/java/com/android/server/ambientcontext/AmbientContextManagerService.java new file mode 100644 index 000000000000..33905f2d1aa3 --- /dev/null +++ b/services/core/java/com/android/server/ambientcontext/AmbientContextManagerService.java @@ -0,0 +1,220 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.ambientcontext; + +import static android.provider.DeviceConfig.NAMESPACE_AMBIENT_CONTEXT_MANAGER_SERVICE; + +import android.Manifest; +import android.annotation.NonNull; +import android.annotation.UserIdInt; +import android.app.PendingIntent; +import android.app.ambientcontext.AmbientContextEvent; +import android.app.ambientcontext.AmbientContextEventRequest; +import android.app.ambientcontext.AmbientContextEventResponse; +import android.app.ambientcontext.IAmbientContextEventObserver; +import android.content.ComponentName; +import android.content.Context; +import android.content.pm.PackageManagerInternal; +import android.os.RemoteCallback; +import android.os.ResultReceiver; +import android.os.ShellCallback; +import android.os.UserHandle; +import android.provider.DeviceConfig; +import android.util.Slog; + +import com.android.internal.R; +import com.android.internal.util.DumpUtils; +import com.android.server.LocalServices; +import com.android.server.SystemService; +import com.android.server.infra.AbstractMasterSystemService; +import com.android.server.infra.FrameworkResourcesServiceNameResolver; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.Objects; +import java.util.Set; + +/** + * System service for managing {@link AmbientContextEvent}s. + */ +public class AmbientContextManagerService extends + AbstractMasterSystemService<AmbientContextManagerService, + AmbientContextManagerPerUserService> { + private static final String TAG = AmbientContextManagerService.class.getSimpleName(); + private static final String KEY_SERVICE_ENABLED = "service_enabled"; + + /** Default value in absence of {@link DeviceConfig} override. */ + private static final boolean DEFAULT_SERVICE_ENABLED = true; + + private final Context mContext; + boolean mIsServiceEnabled; + + public AmbientContextManagerService(Context context) { + super(context, + new FrameworkResourcesServiceNameResolver( + context, + R.string.config_defaultAmbientContextDetectionService), + /*disallowProperty=*/null, + PACKAGE_UPDATE_POLICY_REFRESH_EAGER + | /*To avoid high latency*/ PACKAGE_RESTART_POLICY_REFRESH_EAGER); + mContext = context; + } + + @Override + public void onStart() { + publishBinderService(Context.AMBIENT_CONTEXT_SERVICE, new AmbientContextEventObserver()); + } + + @Override + public void onBootPhase(int phase) { + if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) { + DeviceConfig.addOnPropertiesChangedListener( + NAMESPACE_AMBIENT_CONTEXT_MANAGER_SERVICE, + getContext().getMainExecutor(), + (properties) -> onDeviceConfigChange(properties.getKeyset())); + + mIsServiceEnabled = DeviceConfig.getBoolean( + NAMESPACE_AMBIENT_CONTEXT_MANAGER_SERVICE, + KEY_SERVICE_ENABLED, DEFAULT_SERVICE_ENABLED); + } + } + + private void onDeviceConfigChange(@NonNull Set<String> keys) { + if (keys.contains(KEY_SERVICE_ENABLED)) { + mIsServiceEnabled = DeviceConfig.getBoolean( + NAMESPACE_AMBIENT_CONTEXT_MANAGER_SERVICE, + KEY_SERVICE_ENABLED, DEFAULT_SERVICE_ENABLED); + } + } + + @Override + protected AmbientContextManagerPerUserService newServiceLocked(int resolvedUserId, + boolean disabled) { + return new AmbientContextManagerPerUserService(this, mLock, resolvedUserId); + } + + @Override + protected void onServiceRemoved( + AmbientContextManagerPerUserService service, @UserIdInt int userId) { + service.destroyLocked(); + } + + /** Returns {@code true} if the detection service is configured on this device. */ + public static boolean isDetectionServiceConfigured() { + final PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class); + final String[] packageNames = pmi.getKnownPackageNames( + PackageManagerInternal.PACKAGE_AMBIENT_CONTEXT_DETECTION, UserHandle.USER_SYSTEM); + boolean isServiceConfigured = (packageNames.length != 0); + Slog.i(TAG, "Detection service configured: " + isServiceConfigured); + return isServiceConfigured; + } + + /** + * Send request to the remote AmbientContextDetectionService impl to start detecting the + * specified events. Intended for use by shell command for testing. + * Requires ACCESS_AMBIENT_CONTEXT_EVENT permission. + */ + void startAmbientContextEvent(@UserIdInt int userId, AmbientContextEventRequest request, + String packageName, RemoteCallback callback) { + mContext.enforceCallingOrSelfPermission( + Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT, TAG); + synchronized (mLock) { + final AmbientContextManagerPerUserService service = getServiceForUserLocked(userId); + if (service != null) { + service.startDetection(request, packageName, callback); + } else { + Slog.i(TAG, "service not available for user_id: " + userId); + } + } + } + + /** + * Send request to the remote AmbientContextDetectionService impl to stop detecting the + * specified events. Intended for use by shell command for testing. + * Requires ACCESS_AMBIENT_CONTEXT_EVENT permission. + */ + void stopAmbientContextEvent(@UserIdInt int userId, String packageName) { + mContext.enforceCallingOrSelfPermission( + Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT, TAG); + synchronized (mLock) { + final AmbientContextManagerPerUserService service = getServiceForUserLocked(userId); + if (service != null) { + service.stopDetection(packageName); + } else { + Slog.i(TAG, "service not available for user_id: " + userId); + } + } + } + + /** + * Returns the AmbientContextManagerPerUserService component for this user. + */ + public ComponentName getComponentName(@UserIdInt int userId) { + synchronized (mLock) { + final AmbientContextManagerPerUserService service = getServiceForUserLocked(userId); + if (service != null) { + return service.getComponentName(); + } + } + return null; + } + + private final class AmbientContextEventObserver extends IAmbientContextEventObserver.Stub { + final AmbientContextManagerPerUserService mService = getServiceForUserLocked( + UserHandle.getCallingUserId()); + + @Override + public void registerObserver( + AmbientContextEventRequest request, PendingIntent pendingIntent) { + Objects.requireNonNull(request); + Objects.requireNonNull(pendingIntent); + mContext.enforceCallingOrSelfPermission( + Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT, TAG); + if (!mIsServiceEnabled) { + Slog.w(TAG, "Service not available."); + mService.sendStatusUpdateIntent(pendingIntent, + AmbientContextEventResponse.STATUS_SERVICE_UNAVAILABLE); + return; + } + mService.onRegisterObserver(request, pendingIntent); + } + + @Override + public void unregisterObserver(String callingPackage) { + mContext.enforceCallingOrSelfPermission( + Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT, TAG); + mService.onUnregisterObserver(callingPackage); + } + + @Override + protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) { + return; + } + synchronized (mLock) { + dumpLocked("", pw); + } + } + + @Override + public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, + String[] args, ShellCallback callback, ResultReceiver resultReceiver) { + new AmbientContextShellCommand(AmbientContextManagerService.this).exec( + this, in, out, err, args, callback, resultReceiver); + } + } +} diff --git a/services/core/java/com/android/server/ambientcontext/AmbientContextShellCommand.java b/services/core/java/com/android/server/ambientcontext/AmbientContextShellCommand.java new file mode 100644 index 000000000000..b5cd985fa097 --- /dev/null +++ b/services/core/java/com/android/server/ambientcontext/AmbientContextShellCommand.java @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.ambientcontext; + +import static java.lang.System.out; + +import android.annotation.NonNull; +import android.app.ambientcontext.AmbientContextEvent; +import android.app.ambientcontext.AmbientContextEventRequest; +import android.app.ambientcontext.AmbientContextEventResponse; +import android.content.ComponentName; +import android.os.Binder; +import android.os.RemoteCallback; +import android.os.ShellCommand; +import android.service.ambientcontext.AmbientContextDetectionService; + +import java.io.PrintWriter; + +/** + * Shell command for {@link AmbientContextManagerService}. + */ +final class AmbientContextShellCommand extends ShellCommand { + + @NonNull + private final AmbientContextManagerService mService; + + AmbientContextShellCommand(@NonNull AmbientContextManagerService service) { + mService = service; + } + + /** Callbacks for AmbientContextEventService results used internally for testing. */ + static class TestableCallbackInternal { + private AmbientContextEventResponse mLastResponse; + + public AmbientContextEventResponse getLastResponse() { + return mLastResponse; + } + + @NonNull + private RemoteCallback createRemoteCallback() { + return new RemoteCallback(result -> { + AmbientContextEventResponse response = + (AmbientContextEventResponse) result.get( + AmbientContextDetectionService.RESPONSE_BUNDLE_KEY); + final long token = Binder.clearCallingIdentity(); + try { + mLastResponse = response; + out.println("Response available: " + response); + } finally { + Binder.restoreCallingIdentity(token); + } + }); + } + } + + static final TestableCallbackInternal sTestableCallbackInternal = + new TestableCallbackInternal(); + + @Override + public int onCommand(String cmd) { + if (cmd == null) { + return handleDefaultCommands(cmd); + } + + switch (cmd) { + case "start-detection": + return runStartDetection(); + case "stop-detection": + return runStopDetection(); + case "get-last-status-code": + return getLastStatusCode(); + case "get-bound-package": + return getBoundPackageName(); + case "set-temporary-service": + return setTemporaryService(); + default: + return handleDefaultCommands(cmd); + } + } + + private int runStartDetection() { + final int userId = Integer.parseInt(getNextArgRequired()); + final String packageName = getNextArgRequired(); + AmbientContextEventRequest request = new AmbientContextEventRequest.Builder() + .addEventType(AmbientContextEvent.EVENT_COUGH) + .addEventType(AmbientContextEvent.EVENT_SNORE) + .build(); + + mService.startAmbientContextEvent(userId, request, packageName, + sTestableCallbackInternal.createRemoteCallback()); + return 0; + } + + private int runStopDetection() { + final int userId = Integer.parseInt(getNextArgRequired()); + final String packageName = getNextArgRequired(); + mService.stopAmbientContextEvent(userId, packageName); + return 0; + } + + private int getLastStatusCode() { + AmbientContextEventResponse lastResponse = sTestableCallbackInternal.getLastResponse(); + if (lastResponse == null) { + return -1; + } + return lastResponse.getStatusCode(); + } + + @Override + public void onHelp() { + PrintWriter pw = getOutPrintWriter(); + pw.println("AmbientContextEvent commands: "); + pw.println(" help"); + pw.println(" Print this help text."); + pw.println(); + pw.println(" start-detection USER_ID PACKAGE_NAME: Starts AmbientContextEvent detection."); + pw.println(" stop-detection USER_ID: Stops AmbientContextEvent detection."); + pw.println(" get-last-status-code: Prints the latest request status code."); + pw.println(" get-bound-package USER_ID:" + + " Print the bound package that implements the service."); + pw.println(" set-temporary-service USER_ID [COMPONENT_NAME DURATION]"); + pw.println(" Temporarily (for DURATION ms) changes the service implementation."); + pw.println(" To reset, call with just the USER_ID argument."); + } + + private int getBoundPackageName() { + final PrintWriter out = getOutPrintWriter(); + final int userId = Integer.parseInt(getNextArgRequired()); + final ComponentName componentName = mService.getComponentName(userId); + out.println(componentName == null ? "" : componentName.getPackageName()); + return 0; + } + + private int setTemporaryService() { + final PrintWriter out = getOutPrintWriter(); + final int userId = Integer.parseInt(getNextArgRequired()); + final String serviceName = getNextArg(); + if (serviceName == null) { + mService.resetTemporaryService(userId); + out.println("AmbientContextDetectionService temporary reset. "); + return 0; + } + + final int duration = Integer.parseInt(getNextArgRequired()); + mService.setTemporaryService(userId, serviceName, duration); + out.println("AmbientContextDetectionService temporarily set to " + serviceName + + " for " + duration + "ms"); + return 0; + } +} diff --git a/services/core/java/com/android/server/ambientcontext/OWNERS b/services/core/java/com/android/server/ambientcontext/OWNERS new file mode 100644 index 000000000000..a863297b84e8 --- /dev/null +++ b/services/core/java/com/android/server/ambientcontext/OWNERS @@ -0,0 +1,3 @@ +enxun@google.com +kxchen@google.com +tgadh@google.com diff --git a/services/core/java/com/android/server/ambientcontext/RemoteAmbientContextDetectionService.java b/services/core/java/com/android/server/ambientcontext/RemoteAmbientContextDetectionService.java new file mode 100644 index 000000000000..5cc29b3c34c0 --- /dev/null +++ b/services/core/java/com/android/server/ambientcontext/RemoteAmbientContextDetectionService.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.ambientcontext; + +import static android.content.Context.BIND_FOREGROUND_SERVICE; +import static android.content.Context.BIND_INCLUDE_CAPABILITIES; + +import android.annotation.NonNull; +import android.app.ambientcontext.AmbientContextEventRequest; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.os.RemoteCallback; +import android.service.ambientcontext.AmbientContextDetectionService; +import android.service.ambientcontext.IAmbientContextDetectionService; +import android.util.Slog; + +import com.android.internal.infra.ServiceConnector; + +/** Manages the connection to the remote service. */ +final class RemoteAmbientContextDetectionService + extends ServiceConnector.Impl<IAmbientContextDetectionService> { + private static final String TAG = + RemoteAmbientContextDetectionService.class.getSimpleName(); + + RemoteAmbientContextDetectionService(Context context, ComponentName serviceName, + int userId) { + super(context, new Intent( + AmbientContextDetectionService.SERVICE_INTERFACE).setComponent(serviceName), + BIND_FOREGROUND_SERVICE | BIND_INCLUDE_CAPABILITIES, userId, + IAmbientContextDetectionService.Stub::asInterface); + + // Bind right away + connect(); + } + + /** + * Asks the implementation to start detection. + * + * @param request The request with events to detect, and optional detection options. + * @param packageName The app package that requested the detection + * @param callback callback for detection results + */ + public void startDetection( + @NonNull AmbientContextEventRequest request, String packageName, + RemoteCallback callback) { + Slog.i(TAG, "Start detection for " + request.getEventTypes()); + post(service -> service.startDetection(request, packageName, callback)); + } + + /** + * Asks the implementation to stop detection. + * + * @param packageName stop detection for the given package + */ + public void stopDetection(String packageName) { + Slog.i(TAG, "Stop detection for " + packageName); + post(service -> service.stopDetection(packageName)); + } +} diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java index 0980f4077489..f5f7bb3428bf 100644 --- a/services/core/java/com/android/server/app/GameManagerService.java +++ b/services/core/java/com/android/server/app/GameManagerService.java @@ -39,9 +39,11 @@ import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; +import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.GameManager; import android.app.GameManager.GameMode; +import android.app.GameModeInfo; import android.app.GameState; import android.app.IGameManagerService; import android.app.compat.PackageOverride; @@ -81,6 +83,7 @@ import com.android.server.LocalServices; import com.android.server.ServiceThread; import com.android.server.SystemService; import com.android.server.SystemService.TargetUser; +import com.android.server.app.GameManagerService.GamePackageConfiguration.GameModeConfiguration; import java.io.FileDescriptor; import java.util.List; @@ -113,6 +116,7 @@ public final class GameManagerService extends IGameManagerService.Stub { private final Context mContext; private final Object mLock = new Object(); private final Object mDeviceConfigLock = new Object(); + private final Object mOverrideConfigLock = new Object(); private final Handler mHandler; private final PackageManager mPackageManager; private final IPlatformCompat mPlatformCompat; @@ -122,6 +126,8 @@ public final class GameManagerService extends IGameManagerService.Stub { private final ArrayMap<Integer, GameManagerSettings> mSettings = new ArrayMap<>(); @GuardedBy("mDeviceConfigLock") private final ArrayMap<String, GamePackageConfiguration> mConfigs = new ArrayMap<>(); + @GuardedBy("mOverrideConfigLock") + private final ArrayMap<String, GamePackageConfiguration> mOverrideConfigs = new ArrayMap<>(); public GameManagerService(Context context) { this(context, createServiceThread().getLooper()); @@ -281,13 +287,51 @@ public final class GameManagerService extends IGameManagerService.Stub { return 0; } + public enum FrameRate { + FPS_DEFAULT(0), + FPS_30(30), + FPS_45(45), + FPS_60(60), + FPS_90(90), + FPS_120(120), + FPS_INVALID(-1); + + public final int fps; + + FrameRate(int fps) { + this.fps = fps; + } + } + + // Turn the raw string to the corresponding fps int. + // Return 0 when disabling, -1 for invalid fps. + static int getFpsInt(String raw) { + switch (raw) { + case "30": + return FrameRate.FPS_30.fps; + case "45": + return FrameRate.FPS_45.fps; + case "60": + return FrameRate.FPS_60.fps; + case "90": + return FrameRate.FPS_90.fps; + case "120": + return FrameRate.FPS_120.fps; + case "disable": + case "": + return FrameRate.FPS_DEFAULT.fps; + } + return FrameRate.FPS_INVALID.fps; + } + /** * Called by games to communicate the current state to the platform. * @param packageName The client package name. * @param gameState An object set to the current state. * @param userId The user associated with this state. */ - public void setGameState(String packageName, @NonNull GameState gameState, int userId) { + public void setGameState(String packageName, @NonNull GameState gameState, + @UserIdInt int userId) { if (!isPackageGame(packageName, userId)) { // Restrict to games only. return; @@ -399,11 +443,14 @@ public final class GameManagerService extends IGameManagerService.Stub { public static final String TAG = "GameManagerService_GameModeConfiguration"; public static final String MODE_KEY = "mode"; public static final String SCALING_KEY = "downscaleFactor"; + public static final String FPS_KEY = "fps"; public static final String DEFAULT_SCALING = "1.0"; + public static final String DEFAULT_FPS = ""; public static final String ANGLE_KEY = "useAngle"; private final @GameMode int mGameMode; - private final String mScaling; + private String mScaling; + private String mFps; private final boolean mUseAngle; GameModeConfiguration(KeyValueListParser parser) { @@ -414,6 +461,8 @@ public final class GameManagerService extends IGameManagerService.Stub { // using ANGLE). mScaling = !mAllowDownscale || willGamePerformOptimizations(mGameMode) ? DEFAULT_SCALING : parser.getString(SCALING_KEY, DEFAULT_SCALING); + + mFps = parser.getString(FPS_KEY, DEFAULT_FPS); // We only want to use ANGLE if: // - We're allowed to use ANGLE (the app hasn't opted out via the manifest) AND // - The app has not opted in to performing the work itself AND @@ -430,14 +479,27 @@ public final class GameManagerService extends IGameManagerService.Stub { return mScaling; } + public int getFps() { + return GameManagerService.getFpsInt(mFps); + } + public boolean getUseAngle() { return mUseAngle; } + public void setScaling(String scaling) { + mScaling = scaling; + } + + public void setFpsStr(String fpsStr) { + mFps = fpsStr; + } + public boolean isValid() { - return mGameMode == GameManager.GAME_MODE_STANDARD + return (mGameMode == GameManager.GAME_MODE_STANDARD || mGameMode == GameManager.GAME_MODE_PERFORMANCE - || mGameMode == GameManager.GAME_MODE_BATTERY; + || mGameMode == GameManager.GAME_MODE_BATTERY) + && !willGamePerformOptimizations(mGameMode); } /** @@ -445,7 +507,7 @@ public final class GameManagerService extends IGameManagerService.Stub { */ public String toString() { return "[Game Mode:" + mGameMode + ",Scaling:" + mScaling + ",Use Angle:" - + mUseAngle + "]"; + + mUseAngle + ",Fps:" + mFps + "]"; } /** @@ -633,6 +695,32 @@ public final class GameManagerService extends IGameManagerService.Stub { } } + private @GameMode int[] getAvailableGameModesUnchecked(String packageName) { + GamePackageConfiguration config = null; + synchronized (mOverrideConfigLock) { + config = mOverrideConfigs.get(packageName); + } + if (config == null) { + synchronized (mDeviceConfigLock) { + config = mConfigs.get(packageName); + } + } + if (config == null) { + return new int[]{}; + } + return config.getAvailableGameModes(); + } + + private boolean isPackageGame(String packageName, @UserIdInt int userId) { + try { + final ApplicationInfo applicationInfo = mPackageManager + .getApplicationInfoAsUser(packageName, PackageManager.MATCH_ALL, userId); + return applicationInfo.category == ApplicationInfo.CATEGORY_GAME; + } catch (PackageManager.NameNotFoundException e) { + return false; + } + } + /** * Get an array of game modes available for a given package. * Checks that the caller has {@link android.Manifest.permission#MANAGE_GAME_MODE}. @@ -641,16 +729,10 @@ public final class GameManagerService extends IGameManagerService.Stub { @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE) public @GameMode int[] getAvailableGameModes(String packageName) throws SecurityException { checkPermission(Manifest.permission.MANAGE_GAME_MODE); - synchronized (mDeviceConfigLock) { - final GamePackageConfiguration config = mConfigs.get(packageName); - if (config == null) { - return new int[]{GameManager.GAME_MODE_UNSUPPORTED}; - } - return config.getAvailableGameModes(); - } + return getAvailableGameModesUnchecked(packageName); } - private @GameMode int getGameModeFromSettings(String packageName, int userId) { + private @GameMode int getGameModeFromSettings(String packageName, @UserIdInt int userId) { synchronized (mLock) { if (!mSettings.containsKey(userId)) { Slog.w(TAG, "User ID '" + userId + "' does not have a Game Mode" @@ -668,28 +750,22 @@ public final class GameManagerService extends IGameManagerService.Stub { * {@link android.Manifest.permission#MANAGE_GAME_MODE}. */ @Override - public @GameMode int getGameMode(String packageName, int userId) + public @GameMode int getGameMode(@NonNull String packageName, @UserIdInt int userId) throws SecurityException { userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId, false, true, "getGameMode", "com.android.server.app.GameManagerService"); // Restrict to games only. - try { - final ApplicationInfo applicationInfo = mPackageManager - .getApplicationInfoAsUser(packageName, PackageManager.MATCH_ALL, userId); - if (applicationInfo.category != ApplicationInfo.CATEGORY_GAME) { - // The game mode for applications that are not identified as game is always - // UNSUPPORTED. See {@link PackageManager#setApplicationCategoryHint(String, int)} - return GameManager.GAME_MODE_UNSUPPORTED; - } - } catch (PackageManager.NameNotFoundException e) { + if (!isPackageGame(packageName, userId)) { + // The game mode for applications that are not identified as game is always + // UNSUPPORTED. See {@link PackageManager#setApplicationCategoryHint(String, int)} return GameManager.GAME_MODE_UNSUPPORTED; } // This function handles two types of queries: - // 1.) A normal, non-privileged app querying its own Game Mode. - // 2.) A privileged system service querying the Game Mode of another package. + // 1) A normal, non-privileged app querying its own Game Mode. + // 2) A privileged system service querying the Game Mode of another package. // The least privileged case is a normal app performing a query, so check that first and // return a value if the package name is valid. Next, check if the caller has the necessary // permission and return a value. Do this check last, since it can throw an exception. @@ -702,14 +778,32 @@ public final class GameManagerService extends IGameManagerService.Stub { return getGameModeFromSettings(packageName, userId); } - private boolean isPackageGame(String packageName, int userId) { - try { - final ApplicationInfo applicationInfo = mPackageManager - .getApplicationInfoAsUser(packageName, PackageManager.MATCH_ALL, userId); - return applicationInfo.category == ApplicationInfo.CATEGORY_GAME; - } catch (PackageManager.NameNotFoundException e) { - return false; + /** + * Get the GameModeInfo for the package name. + * Verifies that the calling process is for the matching package UID or has + * {@link android.Manifest.permission#MANAGE_GAME_MODE}. If the package is not a game, + * null is always returned. + */ + @Override + @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE) + @Nullable + public GameModeInfo getGameModeInfo(@NonNull String packageName, @UserIdInt int userId) { + userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), + Binder.getCallingUid(), userId, false, true, "getGameModeInfo", + "com.android.server.app.GameManagerService"); + + // Check the caller has the necessary permission. + checkPermission(Manifest.permission.MANAGE_GAME_MODE); + + // Restrict to games only. + if (!isPackageGame(packageName, userId)) { + return null; } + + final @GameMode int activeGameMode = getGameModeFromSettings(packageName, userId); + final @GameMode int[] availableGameModes = getAvailableGameModesUnchecked(packageName); + + return new GameModeInfo(activeGameMode, availableGameModes); } /** @@ -743,7 +837,7 @@ public final class GameManagerService extends IGameManagerService.Stub { mHandler.sendMessageDelayed(msg, WRITE_SETTINGS_DELAY); } } - updateInterventions(packageName, gameMode); + updateInterventions(packageName, gameMode, userId); } /** @@ -876,41 +970,26 @@ public final class GameManagerService extends IGameManagerService.Stub { } } - private void updateCompatModeDownscale(String packageName, @GameMode int gameMode) { - synchronized (mDeviceConfigLock) { - if (gameMode == GameManager.GAME_MODE_STANDARD - || gameMode == GameManager.GAME_MODE_UNSUPPORTED) { - disableCompatScale(packageName); - return; - } - final GamePackageConfiguration packageConfig = mConfigs.get(packageName); - if (packageConfig == null) { - disableCompatScale(packageName); - Slog.v(TAG, "Package configuration not found for " + packageName); - return; - } - if (DEBUG) { - Slog.v(TAG, dumpDeviceConfigs()); - } - if (packageConfig.willGamePerformOptimizations(gameMode)) { - disableCompatScale(packageName); - return; - } - final GamePackageConfiguration.GameModeConfiguration modeConfig = - packageConfig.getGameModeConfiguration(gameMode); - if (modeConfig == null) { - Slog.i(TAG, "Game mode " + gameMode + " not found for " + packageName); - return; - } - long scaleId = modeConfig.getCompatChangeId(); - if (scaleId == 0) { - Slog.i(TAG, "Invalid downscaling change id " + scaleId + " for " - + packageName); - return; - } + private void updateCompatModeDownscale(GamePackageConfiguration packageConfig, + String packageName, @GameMode int gameMode) { - enableCompatScale(packageName, scaleId); + if (DEBUG) { + Slog.v(TAG, dumpDeviceConfigs()); + } + final GamePackageConfiguration.GameModeConfiguration modeConfig = + packageConfig.getGameModeConfiguration(gameMode); + if (modeConfig == null) { + Slog.i(TAG, "Game mode " + gameMode + " not found for " + packageName); + return; } + long scaleId = modeConfig.getCompatChangeId(); + if (scaleId == 0) { + Slog.w(TAG, "Invalid downscaling change id " + scaleId + " for " + + packageName); + return; + } + + enableCompatScale(packageName, scaleId); } private int modeToBitmask(@GameMode int gameMode) { @@ -927,16 +1006,233 @@ public final class GameManagerService extends IGameManagerService.Stub { // ship. } - private void updateInterventions(String packageName, @GameMode int gameMode) { - updateCompatModeDownscale(packageName, gameMode); + + private void updateFps(GamePackageConfiguration packageConfig, String packageName, + @GameMode int gameMode, @UserIdInt int userId) { + final GamePackageConfiguration.GameModeConfiguration modeConfig = + packageConfig.getGameModeConfiguration(gameMode); + if (modeConfig == null) { + Slog.d(TAG, "Game mode " + gameMode + " not found for " + packageName); + return; + } + try { + final float fps = modeConfig.getFps(); + final int uid = mPackageManager.getPackageUidAsUser(packageName, userId); + nativeSetOverrideFrameRate(uid, fps); + } catch (PackageManager.NameNotFoundException e) { + return; + } + } + + + private void updateInterventions(String packageName, + @GameMode int gameMode, @UserIdInt int userId) { + if (gameMode == GameManager.GAME_MODE_STANDARD + || gameMode == GameManager.GAME_MODE_UNSUPPORTED) { + disableCompatScale(packageName); + return; + } + GamePackageConfiguration packageConfig = null; + + synchronized (mOverrideConfigLock) { + packageConfig = mOverrideConfigs.get(packageName); + } + + if (packageConfig == null) { + synchronized (mDeviceConfigLock) { + packageConfig = mConfigs.get(packageName); + } + } + + if (packageConfig == null) { + disableCompatScale(packageName); + Slog.v(TAG, "Package configuration not found for " + packageName); + return; + } + if (packageConfig.willGamePerformOptimizations(gameMode)) { + return; + } + updateCompatModeDownscale(packageConfig, packageName, gameMode); + updateFps(packageConfig, packageName, gameMode, userId); updateUseAngle(packageName, gameMode); } /** + * Set the override Game Mode Configuration. + * Update the config if exists, create one if not. + */ + @VisibleForTesting + @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE) + public void setGameModeConfigOverride(String packageName, @UserIdInt int userId, + @GameMode int gameMode, String fpsStr, String scaling) throws SecurityException { + checkPermission(Manifest.permission.MANAGE_GAME_MODE); + synchronized (mLock) { + if (!mSettings.containsKey(userId)) { + return; + } + } + // Adding override game mode configuration of the given package name + synchronized (mOverrideConfigLock) { + // look for the existing override GamePackageConfiguration + GamePackageConfiguration overrideConfig = mOverrideConfigs.get(packageName); + if (overrideConfig == null) { + overrideConfig = new GamePackageConfiguration(packageName, userId); + mOverrideConfigs.put(packageName, overrideConfig); + } + + // modify GameModeConfiguration intervention settings + GamePackageConfiguration.GameModeConfiguration overrideModeConfig = + overrideConfig.getGameModeConfiguration(gameMode); + + if (fpsStr != null) { + overrideModeConfig.setFpsStr(fpsStr); + } else { + overrideModeConfig.setFpsStr( + GamePackageConfiguration.GameModeConfiguration.DEFAULT_FPS); + } + if (scaling != null) { + overrideModeConfig.setScaling(scaling); + } else { + overrideModeConfig.setScaling( + GamePackageConfiguration.GameModeConfiguration.DEFAULT_SCALING); + } + Slog.i(TAG, "Package Name: " + packageName + + " FPS: " + String.valueOf(overrideModeConfig.getFps()) + + " Scaling: " + overrideModeConfig.getScaling()); + } + setGameMode(packageName, gameMode, userId); + } + + /** + * Reset the overridden gameModeConfiguration of the given mode. + * Remove the override config if game mode is not specified. + */ + @VisibleForTesting + @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE) + public void resetGameModeConfigOverride(String packageName, @UserIdInt int userId, + @GameMode int gameModeToReset) throws SecurityException { + checkPermission(Manifest.permission.MANAGE_GAME_MODE); + synchronized (mLock) { + if (!mSettings.containsKey(userId)) { + return; + } + } + + // resets GamePackageConfiguration of a given packageName. + // If a gameMode is specified, only reset the GameModeConfiguration of the gameMode. + if (gameModeToReset != -1) { + GamePackageConfiguration overrideConfig = null; + synchronized (mOverrideConfigLock) { + overrideConfig = mOverrideConfigs.get(packageName); + } + + GamePackageConfiguration config = null; + synchronized (mDeviceConfigLock) { + config = mConfigs.get(packageName); + } + + int[] modes = overrideConfig.getAvailableGameModes(); + + // First check if the mode to reset exists + boolean isGameModeExist = false; + for (int mode : modes) { + if (gameModeToReset == mode) { + isGameModeExist = true; + } + } + if (!isGameModeExist) { + return; + } + + // If the game mode to reset is the only mode other than standard mode, + // The override config is removed. + if (modes.length <= 2) { + synchronized (mOverrideConfigLock) { + mOverrideConfigs.remove(packageName); + } + } else { + // otherwise we reset the mode by copying the original config. + overrideConfig.addModeConfig(config.getGameModeConfiguration(gameModeToReset)); + } + } else { + synchronized (mOverrideConfigLock) { + // remove override config if there is one + mOverrideConfigs.remove(packageName); + } + } + + // Make sure after resetting the game mode is still supported. + // If not, set the game mode to standard + int gameMode = getGameMode(packageName, userId); + int newGameMode = gameMode; + + GamePackageConfiguration config = null; + synchronized (mOverrideConfigLock) { + config = mOverrideConfigs.get(packageName); + } + synchronized (mDeviceConfigLock) { + config = mConfigs.get(packageName); + } + if (config != null) { + int modesBitfield = config.getAvailableGameModesBitfield(); + // Remove UNSUPPORTED to simplify the logic here, since we really just + // want to check if we support selectable game modes + modesBitfield &= ~modeToBitmask(GameManager.GAME_MODE_UNSUPPORTED); + if (!bitFieldContainsModeBitmask(modesBitfield, gameMode)) { + if (bitFieldContainsModeBitmask(modesBitfield, + GameManager.GAME_MODE_STANDARD)) { + // If the current set mode isn't supported, + // but we support STANDARD, then set the mode to STANDARD. + newGameMode = GameManager.GAME_MODE_STANDARD; + } else { + // If we don't support any game modes, then set to UNSUPPORTED + newGameMode = GameManager.GAME_MODE_UNSUPPORTED; + } + } + } else if (gameMode != GameManager.GAME_MODE_UNSUPPORTED) { + // If we have no config for the package, but the configured mode is not + // UNSUPPORTED, then set to UNSUPPORTED + newGameMode = GameManager.GAME_MODE_UNSUPPORTED; + } + if (gameMode != newGameMode) { + setGameMode(packageName, GameManager.GAME_MODE_STANDARD, userId); + return; + } + setGameMode(packageName, gameMode, userId); + } + + /** + * Returns the string listing all the interventions currently set to a game. + */ + public String getInterventionList(String packageName) { + GamePackageConfiguration packageConfig = null; + synchronized (mOverrideConfigLock) { + packageConfig = mOverrideConfigs.get(packageName); + } + + if (packageConfig == null) { + synchronized (mDeviceConfigLock) { + packageConfig = mConfigs.get(packageName); + } + } + + StringBuilder listStrSb = new StringBuilder(); + if (packageConfig == null) { + listStrSb.append("\n No intervention found for package ") + .append(packageName); + return listStrSb.toString(); + } + listStrSb.append("\nPackage name: ") + .append(packageName) + .append(packageConfig.toString()); + return listStrSb.toString(); + } + + /** * @hide */ @VisibleForTesting - void updateConfigsForUser(int userId, String ...packageNames) { + void updateConfigsForUser(@UserIdInt int userId, String ...packageNames) { try { synchronized (mDeviceConfigLock) { for (final String packageName : packageNames) { @@ -954,43 +1250,47 @@ public final class GameManagerService extends IGameManagerService.Stub { } } } + synchronized (mLock) { + if (!mSettings.containsKey(userId)) { + return; + } + } for (final String packageName : packageNames) { - if (mSettings.containsKey(userId)) { - int gameMode = getGameMode(packageName, userId); - int newGameMode = gameMode; - // Make sure the user settings and package configs don't conflict. I.e. the - // user setting is set to a mode that no longer available due to config/manifest - // changes. Most of the time we won't have to change anything. - GamePackageConfiguration config; - synchronized (mDeviceConfigLock) { - config = mConfigs.get(packageName); - } - if (config != null) { - int modesBitfield = config.getAvailableGameModesBitfield(); - // Remove UNSUPPORTED to simplify the logic here, since we really just - // want to check if we support selectable game modes - modesBitfield &= ~modeToBitmask(GameManager.GAME_MODE_UNSUPPORTED); - if (!bitFieldContainsModeBitmask(modesBitfield, gameMode)) { - if (bitFieldContainsModeBitmask(modesBitfield, - GameManager.GAME_MODE_STANDARD)) { - // If the current set mode isn't supported, but we support STANDARD, - // then set the mode to STANDARD. - newGameMode = GameManager.GAME_MODE_STANDARD; - } else { - // If we don't support any game modes, then set to UNSUPPORTED - newGameMode = GameManager.GAME_MODE_UNSUPPORTED; - } + int gameMode = getGameMode(packageName, userId); + int newGameMode = gameMode; + // Make sure the user settings and package configs don't conflict. + // I.e. the user setting is set to a mode that no longer available due to + // config/manifest changes. + // Most of the time we won't have to change anything. + GamePackageConfiguration config = null; + synchronized (mDeviceConfigLock) { + config = mConfigs.get(packageName); + } + if (config != null) { + int modesBitfield = config.getAvailableGameModesBitfield(); + // Remove UNSUPPORTED to simplify the logic here, since we really just + // want to check if we support selectable game modes + modesBitfield &= ~modeToBitmask(GameManager.GAME_MODE_UNSUPPORTED); + if (!bitFieldContainsModeBitmask(modesBitfield, gameMode)) { + if (bitFieldContainsModeBitmask(modesBitfield, + GameManager.GAME_MODE_STANDARD)) { + // If the current set mode isn't supported, + // but we support STANDARD, then set the mode to STANDARD. + newGameMode = GameManager.GAME_MODE_STANDARD; + } else { + // If we don't support any game modes, then set to UNSUPPORTED + newGameMode = GameManager.GAME_MODE_UNSUPPORTED; } - } else if (gameMode != GameManager.GAME_MODE_UNSUPPORTED) { - // If we have no config for the package, but the configured mode is not - // UNSUPPORTED, then set to UNSUPPORTED - newGameMode = GameManager.GAME_MODE_UNSUPPORTED; - } - if (newGameMode != gameMode) { - setGameMode(packageName, newGameMode, userId); } - updateInterventions(packageName, gameMode); + } else if (gameMode != GameManager.GAME_MODE_UNSUPPORTED) { + // If we have no config for the package, but the configured mode is not + // UNSUPPORTED, then set to UNSUPPORTED + newGameMode = GameManager.GAME_MODE_UNSUPPORTED; + } + if (newGameMode != gameMode) { + setGameMode(packageName, newGameMode, userId); } + updateInterventions(packageName, gameMode, userId); } } catch (Exception e) { Slog.e(TAG, "Failed to update compat modes for user " + userId + ": " + e); @@ -1011,7 +1311,16 @@ public final class GameManagerService extends IGameManagerService.Stub { */ @VisibleForTesting public GamePackageConfiguration getConfig(String packageName) { - return mConfigs.get(packageName); + GamePackageConfiguration packageConfig = null; + synchronized (mOverrideConfigLock) { + packageConfig = mOverrideConfigs.get(packageName); + } + if (packageConfig == null) { + synchronized (mDeviceConfigLock) { + packageConfig = mConfigs.get(packageName); + } + } + return packageConfig; } private void registerPackageReceiver() { @@ -1047,6 +1356,9 @@ public final class GameManagerService extends IGameManagerService.Stub { break; case ACTION_PACKAGE_REMOVED: disableCompatScale(packageName); + synchronized (mOverrideConfigLock) { + mOverrideConfigs.remove(packageName); + } synchronized (mDeviceConfigLock) { mConfigs.remove(packageName); } @@ -1083,4 +1395,9 @@ public final class GameManagerService extends IGameManagerService.Stub { handlerThread.start(); return handlerThread; } + + /** + * load dynamic library for frame rate overriding JNI calls + */ + private static native void nativeSetOverrideFrameRate(int uid, float frameRate); } diff --git a/services/core/java/com/android/server/app/GameManagerShellCommand.java b/services/core/java/com/android/server/app/GameManagerShellCommand.java index f07d207e7d06..470c320dbd7d 100644 --- a/services/core/java/com/android/server/app/GameManagerShellCommand.java +++ b/services/core/java/com/android/server/app/GameManagerShellCommand.java @@ -34,7 +34,6 @@ import static com.android.server.wm.CompatModePackages.DOWNSCALE_90; import android.app.ActivityManager; import android.app.GameManager; import android.app.IGameManagerService; -import android.compat.Compatibility; import android.content.Context; import android.os.RemoteException; import android.os.ServiceManager; @@ -42,12 +41,8 @@ import android.os.ServiceManager.ServiceNotFoundException; import android.os.ShellCommand; import android.util.ArraySet; -import com.android.internal.compat.CompatibilityChangeConfig; -import com.android.server.compat.PlatformCompat; - import java.io.PrintWriter; -import java.util.Set; -import java.util.stream.Collectors; +import java.util.Locale; /** * ShellCommands for GameManagerService. @@ -83,42 +78,11 @@ public class GameManagerShellCommand extends ShellCommand { final PrintWriter pw = getOutPrintWriter(); try { switch (cmd) { - case "downscale": { - final String ratio = getNextArgRequired(); - final String packageName = getNextArgRequired(); - - final long changeId = GameManagerService.getCompatChangeId(ratio); - if (changeId == 0 && !ratio.equals("disable")) { - pw.println("Invalid scaling ratio '" + ratio + "'"); - break; - } - - Set<Long> enabled = new ArraySet<>(); - Set<Long> disabled; - if (changeId == 0) { - disabled = DOWNSCALE_CHANGE_IDS; - } else { - enabled.add(DOWNSCALED); - enabled.add(changeId); - disabled = DOWNSCALE_CHANGE_IDS.stream() - .filter(it -> it != DOWNSCALED && it != changeId) - .collect(Collectors.toSet()); - } - - final PlatformCompat platformCompat = (PlatformCompat) - ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE); - final CompatibilityChangeConfig overrides = - new CompatibilityChangeConfig( - new Compatibility.ChangeConfig(enabled, disabled)); - - platformCompat.setOverrides(overrides, packageName); - if (changeId == 0) { - pw.println("Disable downscaling for " + packageName + "."); - } else { - pw.println("Enable downscaling ratio for " + packageName + " to " + ratio); - } - - return 0; + case "set": { + return runSetGameMode(pw); + } + case "reset": { + return runResetGameMode(pw); } case "mode": { /** The "mode" command allows setting a package's current game mode outside of @@ -132,6 +96,9 @@ public class GameManagerShellCommand extends ShellCommand { */ return runGameMode(pw); } + case "list": { + return runGameList(pw); + } default: return handleDefaultCommands(cmd); } @@ -141,6 +108,22 @@ public class GameManagerShellCommand extends ShellCommand { return -1; } + private int runGameList(PrintWriter pw) throws ServiceNotFoundException, RemoteException { + final String packageName = getNextArgRequired(); + + final GameManagerService gameManagerService = (GameManagerService) + ServiceManager.getService(Context.GAME_SERVICE); + + final String listStr = gameManagerService.getInterventionList(packageName); + + if (listStr == null) { + pw.println("No interventions found for " + packageName); + } else { + pw.println(packageName + " interventions: " + listStr); + } + return 0; + } + private int runGameMode(PrintWriter pw) throws ServiceNotFoundException, RemoteException { final String option = getNextOption(); String userIdStr = null; @@ -200,6 +183,172 @@ public class GameManagerShellCommand extends ShellCommand { return 0; } + private int runSetGameMode(PrintWriter pw) throws ServiceNotFoundException, RemoteException { + String option = getNextArgRequired(); + if (!option.equals("--mode")) { + pw.println("Invalid option '" + option + "'"); + return -1; + } + + final String gameMode = getNextArgRequired(); + + /** + * handling optional input + * "--user", "--downscale" and "--fps" can come in any order + */ + String userIdStr = null; + String fpsStr = null; + String downscaleRatio = null; + while ((option = getNextOption()) != null) { + switch (option) { + case "--user": + if (userIdStr == null) { + userIdStr = getNextArgRequired(); + } else { + pw.println("Duplicate option '" + option + "'"); + return -1; + } + break; + case "--downscale": + if (downscaleRatio == null) { + downscaleRatio = getNextArgRequired(); + if (downscaleRatio != null + && GameManagerService.getCompatChangeId(downscaleRatio) == 0 + && !downscaleRatio.equals("disable")) { + pw.println("Invalid scaling ratio '" + downscaleRatio + "'"); + return -1; + } + } else { + pw.println("Duplicate option '" + option + "'"); + return -1; + } + break; + case "--fps": + if (fpsStr == null) { + fpsStr = getNextArgRequired(); + if (fpsStr != null && GameManagerService.getFpsInt(fpsStr) == -1) { + pw.println("Invalid frame rate '" + fpsStr + "'"); + return -1; + } + } else { + pw.println("Duplicate option '" + option + "'"); + return -1; + } + break; + default: + pw.println("Invalid option '" + option + "'"); + return -1; + } + } + + final String packageName = getNextArgRequired(); + + int userId = userIdStr != null ? Integer.parseInt(userIdStr) + : ActivityManager.getCurrentUser(); + + final GameManagerService gameManagerService = (GameManagerService) + ServiceManager.getService(Context.GAME_SERVICE); + + boolean batteryModeSupported = false; + boolean perfModeSupported = false; + int [] modes = gameManagerService.getAvailableGameModes(packageName); + + for (int mode : modes) { + if (mode == GameManager.GAME_MODE_PERFORMANCE) { + perfModeSupported = true; + } else if (mode == GameManager.GAME_MODE_BATTERY) { + batteryModeSupported = true; + } + } + + switch (gameMode.toLowerCase(Locale.getDefault())) { + case "2": + case "performance": + if (perfModeSupported) { + gameManagerService.setGameModeConfigOverride(packageName, userId, + GameManager.GAME_MODE_PERFORMANCE, fpsStr, downscaleRatio); + } else { + pw.println("Game mode: " + gameMode + " not supported by " + + packageName); + return -1; + } + break; + case "3": + case "battery": + if (batteryModeSupported) { + gameManagerService.setGameModeConfigOverride(packageName, userId, + GameManager.GAME_MODE_BATTERY, fpsStr, downscaleRatio); + } else { + pw.println("Game mode: " + gameMode + " not supported by " + + packageName); + return -1; + } + break; + default: + pw.println("Invalid game mode: " + gameMode); + return -1; + } + return 0; + } + + private int runResetGameMode(PrintWriter pw) throws ServiceNotFoundException, RemoteException { + String option = null; + String gameMode = null; + String userIdStr = null; + while ((option = getNextOption()) != null) { + switch (option) { + case "--user": + if (userIdStr == null) { + userIdStr = getNextArgRequired(); + } else { + pw.println("Duplicate option '" + option + "'"); + return -1; + } + break; + case "--mode": + if (gameMode == null) { + gameMode = getNextArgRequired(); + } else { + pw.println("Duplicate option '" + option + "'"); + return -1; + } + break; + default: + pw.println("Invalid option '" + option + "'"); + return -1; + } + } + + final String packageName = getNextArgRequired(); + + final GameManagerService gameManagerService = (GameManagerService) + ServiceManager.getService(Context.GAME_SERVICE); + + int userId = userIdStr != null ? Integer.parseInt(userIdStr) + : ActivityManager.getCurrentUser(); + + if (gameMode == null) { + gameManagerService.resetGameModeConfigOverride(packageName, userId, -1); + return 0; + } + + switch (gameMode.toLowerCase(Locale.getDefault())) { + case "2": + case "performance": + gameManagerService.resetGameModeConfigOverride(packageName, userId, + GameManager.GAME_MODE_PERFORMANCE); + break; + case "3": + case "battery": + gameManagerService.resetGameModeConfigOverride(packageName, userId, + GameManager.GAME_MODE_BATTERY); + break; + default: + pw.println("Invalid game mode: " + gameMode); + return -1; + } + return 0; + } @Override public void onHelp() { @@ -207,10 +356,25 @@ public class GameManagerShellCommand extends ShellCommand { pw.println("Game manager (game) commands:"); pw.println(" help"); pw.println(" Print this help text."); - pw.println(" downscale [0.3|0.35|0.4|0.45|0.5|0.55|0.6|0.65|0.7|0.75|0.8|0.85|0.9|disable] <PACKAGE_NAME>"); - pw.println(" Force app to run at the specified scaling ratio."); + pw.println(" downscale"); + pw.println(" Deprecated. Please use `set` command."); pw.println(" mode [--user <USER_ID>] [1|2|3|standard|performance|battery] <PACKAGE_NAME>"); - pw.println(" Force app to run in the specified game mode, if supported."); - pw.println(" --user <USER_ID>: apply for the given user, the current user is used when unspecified."); + pw.println(" Set app to run in the specified game mode, if supported."); + pw.println(" --user <USER_ID>: apply for the given user,"); + pw.println(" the current user is used when unspecified."); + pw.println(" set --mode [2|3|performance|battery] [intervention configs] <PACKAGE_NAME>"); + pw.println(" Set app to run at given game mode with configs, if supported."); + pw.println(" Intervention configs consists of:"); + pw.println(" --downscale [0.3|0.35|0.4|0.45|0.5|0.55|0.6|0.65"); + pw.println(" |0.7|0.75|0.8|0.85|0.9|disable]"); + pw.println(" Set app to run at the specified scaling ratio."); + pw.println(" --fps [30|45|60|90|120|disable]"); + pw.println(" Set app to run at the specified fps, if supported."); + pw.println(" reset [--mode [2|3|performance|battery] --user <USER_ID>] <PACKAGE_NAME>"); + pw.println(" Resets the game mode of the app to device configuration."); + pw.println(" --mode [2|3|performance|battery]: apply for the given mode,"); + pw.println(" resets all modes when unspecified."); + pw.println(" --user <USER_ID>: apply for the given user,"); + pw.println(" the current user is used when unspecified."); } } diff --git a/services/core/java/com/android/server/app/GameServiceProviderInstanceFactoryImpl.java b/services/core/java/com/android/server/app/GameServiceProviderInstanceFactoryImpl.java index d5ac03ab7c0d..b4c43f6f1b93 100644 --- a/services/core/java/com/android/server/app/GameServiceProviderInstanceFactoryImpl.java +++ b/services/core/java/com/android/server/app/GameServiceProviderInstanceFactoryImpl.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.app.ActivityTaskManager; import android.content.Context; import android.content.Intent; +import android.os.ServiceManager; import android.service.games.GameService; import android.service.games.GameSessionService; import android.service.games.IGameService; @@ -27,6 +28,9 @@ import android.service.games.IGameSessionService; import com.android.internal.infra.ServiceConnector; import com.android.internal.os.BackgroundThread; +import com.android.server.LocalServices; +import com.android.server.wm.WindowManagerInternal; +import com.android.server.wm.WindowManagerService; final class GameServiceProviderInstanceFactoryImpl implements GameServiceProviderInstanceFactory { private final Context mContext; @@ -44,6 +48,8 @@ final class GameServiceProviderInstanceFactoryImpl implements GameServiceProvide BackgroundThread.getExecutor(), new GameClassifierImpl(mContext.getPackageManager()), ActivityTaskManager.getService(), + (WindowManagerService) ServiceManager.getService(Context.WINDOW_SERVICE), + LocalServices.getService(WindowManagerInternal.class), new GameServiceConnector(mContext, gameServiceProviderConfiguration), new GameSessionServiceConnector(mContext, gameServiceProviderConfiguration)); } diff --git a/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java b/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java index cc060e94a52a..43c9839a04d8 100644 --- a/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java +++ b/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java @@ -17,24 +17,37 @@ package com.android.server.app; import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.ActivityManager.RunningTaskInfo; +import android.app.ActivityTaskManager; import android.app.IActivityTaskManager; import android.app.TaskStackListener; import android.content.ComponentName; -import android.os.IBinder; +import android.graphics.Bitmap; +import android.graphics.Rect; import android.os.RemoteException; import android.os.UserHandle; import android.service.games.CreateGameSessionRequest; +import android.service.games.CreateGameSessionResult; +import android.service.games.GameScreenshotResult; +import android.service.games.GameSessionViewHostConfiguration; import android.service.games.GameStartedEvent; import android.service.games.IGameService; import android.service.games.IGameServiceController; import android.service.games.IGameSession; +import android.service.games.IGameSessionController; import android.service.games.IGameSessionService; import android.util.Slog; +import android.view.SurfaceControlViewHost.SurfacePackage; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.infra.AndroidFuture; import com.android.internal.infra.ServiceConnector; +import com.android.server.wm.WindowManagerInternal; +import com.android.server.wm.WindowManagerService; +import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; @@ -62,6 +75,19 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan GameServiceProviderInstanceImpl.this.onTaskRemoved(taskId); }); } + + @Override + public void onTaskFocusChanged(int taskId, boolean focused) { + mBackgroundExecutor.execute(() -> { + GameServiceProviderInstanceImpl.this.onTaskFocusChanged(taskId, focused); + }); + } + + // TODO(b/204503192): Limit the lifespan of the game session in the Game Service provider + // to only when the associated task is running. Right now it is possible for a task to + // move into the background and for all associated processes to die and for the Game Session + // provider's GameSessionService to continue to be running. Ideally we could unbind the + // service when this happens. }; private final IGameServiceController mGameServiceController = @@ -74,11 +100,25 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan } }; + private final IGameSessionController mGameSessionController = + new IGameSessionController.Stub() { + @Override + public void takeScreenshot(int taskId, + @NonNull AndroidFuture gameScreenshotResultFuture) { + mBackgroundExecutor.execute(() -> { + GameServiceProviderInstanceImpl.this.takeScreenshot(taskId, + gameScreenshotResultFuture); + }); + } + }; + private final Object mLock = new Object(); private final UserHandle mUserHandle; private final Executor mBackgroundExecutor; private final GameClassifier mGameClassifier; private final IActivityTaskManager mActivityTaskManager; + private final WindowManagerService mWindowManagerService; + private final WindowManagerInternal mWindowManagerInternal; private final ServiceConnector<IGameService> mGameServiceConnector; private final ServiceConnector<IGameSessionService> mGameSessionServiceConnector; @@ -89,16 +129,20 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan private volatile boolean mIsRunning; GameServiceProviderInstanceImpl( - UserHandle userHandle, + @NonNull UserHandle userHandle, @NonNull Executor backgroundExecutor, @NonNull GameClassifier gameClassifier, @NonNull IActivityTaskManager activityTaskManager, + @NonNull WindowManagerService windowManagerService, + @NonNull WindowManagerInternal windowManagerInternal, @NonNull ServiceConnector<IGameService> gameServiceConnector, @NonNull ServiceConnector<IGameSessionService> gameSessionServiceConnector) { mUserHandle = userHandle; mBackgroundExecutor = backgroundExecutor; mGameClassifier = gameClassifier; mActivityTaskManager = activityTaskManager; + mWindowManagerService = windowManagerService; + mWindowManagerInternal = windowManagerInternal; mGameServiceConnector = gameServiceConnector; mGameSessionServiceConnector = gameSessionServiceConnector; } @@ -151,16 +195,7 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan } for (GameSessionRecord gameSessionRecord : mGameSessions.values()) { - IGameSession gameSession = gameSessionRecord.getGameSession(); - if (gameSession == null) { - continue; - } - - try { - gameSession.destroy(); - } catch (RemoteException ex) { - Slog.w(TAG, "Failed to destroy session: " + gameSessionRecord, ex); - } + destroyGameSessionFromRecord(gameSessionRecord); } mGameSessions.clear(); @@ -185,31 +220,55 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan } } + private void onTaskFocusChanged(int taskId, boolean focused) { + synchronized (mLock) { + onTaskFocusChangedLocked(taskId, focused); + } + } + + @GuardedBy("mLock") + private void onTaskFocusChangedLocked(int taskId, boolean focused) { + if (DEBUG) { + Slog.d(TAG, "onTaskFocusChangedLocked() id: " + taskId + " focused: " + focused); + } + + final GameSessionRecord gameSessionRecord = mGameSessions.get(taskId); + if (gameSessionRecord == null || gameSessionRecord.getGameSession() == null) { + return; + } + + try { + gameSessionRecord.getGameSession().onTaskFocusChanged(focused); + } catch (RemoteException ex) { + Slog.w(TAG, "Failed to notify session of task focus change: " + gameSessionRecord); + } + } + @GuardedBy("mLock") - private void gameTaskStartedLocked(int sessionId, @NonNull ComponentName componentName) { + private void gameTaskStartedLocked(int taskId, @NonNull ComponentName componentName) { if (DEBUG) { - Slog.i(TAG, "gameStartedLocked() id: " + sessionId + " component: " + componentName); + Slog.i(TAG, "gameStartedLocked() id: " + taskId + " component: " + componentName); } if (!mIsRunning) { return; } - GameSessionRecord existingGameSessionRecord = mGameSessions.get(sessionId); + GameSessionRecord existingGameSessionRecord = mGameSessions.get(taskId); if (existingGameSessionRecord != null) { - Slog.w(TAG, "Existing game session found for task (id: " + sessionId + Slog.w(TAG, "Existing game session found for task (id: " + taskId + ") creation. Ignoring."); return; } GameSessionRecord gameSessionRecord = GameSessionRecord.awaitingGameSessionRequest( - sessionId, componentName); - mGameSessions.put(sessionId, gameSessionRecord); + taskId, componentName); + mGameSessions.put(taskId, gameSessionRecord); AndroidFuture<Void> unusedPostGameStartedFuture = mGameServiceConnector.post( gameService -> { gameService.gameStarted( - new GameStartedEvent(sessionId, componentName.getPackageName())); + new GameStartedEvent(taskId, componentName.getPackageName())); }); } @@ -220,7 +279,7 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan return; } - destroyGameSessionIfNecessaryLocked(taskId); + removeAndDestroyGameSessionIfNecessaryLocked(taskId); } } @@ -231,112 +290,175 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan } @GuardedBy("mLock") - private void createGameSessionLocked(int sessionId) { + private void createGameSessionLocked(int taskId) { if (DEBUG) { - Slog.i(TAG, "createGameSessionLocked() id: " + sessionId); + Slog.i(TAG, "createGameSessionLocked() id: " + taskId); } if (!mIsRunning) { return; } - GameSessionRecord existingGameSessionRecord = mGameSessions.get(sessionId); + GameSessionRecord existingGameSessionRecord = mGameSessions.get(taskId); if (existingGameSessionRecord == null) { - Slog.w(TAG, "No existing game session record found for task (id: " + sessionId + Slog.w(TAG, "No existing game session record found for task (id: " + taskId + ") creation. Ignoring."); return; } if (!existingGameSessionRecord.isAwaitingGameSessionRequest()) { - Slog.w(TAG, "Existing game session for task (id: " + sessionId + Slog.w(TAG, "Existing game session for task (id: " + taskId + ") is not awaiting game session request. Ignoring."); return; } - mGameSessions.put(sessionId, existingGameSessionRecord.withGameSessionRequested()); - - ComponentName componentName = existingGameSessionRecord.getComponentName(); - - // TODO(b/207035150): Allow the game service provider to determine if a game session - // should be created. For now we will assume all games should have a session. - AndroidFuture<IBinder> gameSessionFuture = new AndroidFuture<IBinder>() - .orTimeout(CREATE_GAME_SESSION_TIMEOUT_MS, TimeUnit.MILLISECONDS) - .whenCompleteAsync((gameSessionIBinder, exception) -> { - IGameSession gameSession = IGameSession.Stub.asInterface(gameSessionIBinder); - if (exception != null || gameSession == null) { - Slog.w(TAG, "Failed to create GameSession: " + existingGameSessionRecord, - exception); - synchronized (mLock) { - destroyGameSessionIfNecessaryLocked(sessionId); - } - return; - } - - synchronized (mLock) { - attachGameSessionLocked(sessionId, gameSession); - } - }, mBackgroundExecutor); + + GameSessionViewHostConfiguration gameSessionViewHostConfiguration = + createViewHostConfigurationForTask(taskId); + if (gameSessionViewHostConfiguration == null) { + Slog.w(TAG, "Failed to create view host configuration for task (id" + taskId + + ") creation. Ignoring."); + return; + } + + if (DEBUG) { + Slog.d(TAG, "Determined initial view host configuration for task (id: " + taskId + "): " + + gameSessionViewHostConfiguration); + } + + mGameSessions.put(taskId, existingGameSessionRecord.withGameSessionRequested()); + + AndroidFuture<CreateGameSessionResult> createGameSessionResultFuture = + new AndroidFuture<CreateGameSessionResult>() + .orTimeout(CREATE_GAME_SESSION_TIMEOUT_MS, TimeUnit.MILLISECONDS) + .whenCompleteAsync((createGameSessionResult, exception) -> { + if (exception != null || createGameSessionResult == null) { + Slog.w(TAG, "Failed to create GameSession: " + + existingGameSessionRecord, + exception); + synchronized (mLock) { + removeAndDestroyGameSessionIfNecessaryLocked(taskId); + } + return; + } + + synchronized (mLock) { + attachGameSessionLocked(taskId, createGameSessionResult); + } + + // The TaskStackListener may have made its task focused call for the + // game session's task before the game session was created, so check if + // the task is already focused so that the game session can be notified. + setGameSessionFocusedIfNecessary(taskId, + createGameSessionResult.getGameSession()); + }, mBackgroundExecutor); AndroidFuture<Void> unusedPostCreateGameSessionFuture = mGameSessionServiceConnector.post(gameService -> { CreateGameSessionRequest createGameSessionRequest = - new CreateGameSessionRequest(sessionId, componentName.getPackageName()); - gameService.create(createGameSessionRequest, gameSessionFuture); + new CreateGameSessionRequest( + taskId, + existingGameSessionRecord.getComponentName().getPackageName()); + gameService.create( + mGameSessionController, + createGameSessionRequest, + gameSessionViewHostConfiguration, + createGameSessionResultFuture); }); } + private void setGameSessionFocusedIfNecessary(int taskId, IGameSession gameSession) { + try { + final ActivityTaskManager.RootTaskInfo rootTaskInfo = + mActivityTaskManager.getFocusedRootTaskInfo(); + if (rootTaskInfo != null && rootTaskInfo.taskId == taskId) { + gameSession.onTaskFocusChanged(true); + } + } catch (RemoteException ex) { + Slog.w(TAG, "Failed to set task focused for ID: " + taskId); + } + } + @GuardedBy("mLock") - private void attachGameSessionLocked(int sessionId, @NonNull IGameSession gameSession) { + private void attachGameSessionLocked( + int taskId, + @NonNull CreateGameSessionResult createGameSessionResult) { if (DEBUG) { - Slog.i(TAG, "attachGameSession() id: " + sessionId); + Slog.d(TAG, "attachGameSession() id: " + taskId); } - GameSessionRecord gameSessionRecord = mGameSessions.get(sessionId); - boolean isValidAttachRequest = true; + GameSessionRecord gameSessionRecord = mGameSessions.get(taskId); + if (gameSessionRecord == null) { - Slog.w(TAG, "No associated game session record. Destroying id: " + sessionId); - isValidAttachRequest = false; + Slog.w(TAG, "No associated game session record. Destroying id: " + taskId); + destroyGameSessionDuringAttach(taskId, createGameSessionResult); + return; } - if (gameSessionRecord != null && !gameSessionRecord.isGameSessionRequested()) { - Slog.w(TAG, - "Game session not requested for existing game session record. Destroying id: " - + sessionId); - isValidAttachRequest = false; + + if (!gameSessionRecord.isGameSessionRequested()) { + destroyGameSessionDuringAttach(taskId, createGameSessionResult); + return; } - if (!isValidAttachRequest) { - try { - gameSession.destroy(); - } catch (RemoteException ex) { - Slog.w(TAG, "Failed to destroy session: " + gameSessionRecord, ex); - } + try { + mWindowManagerInternal.addTaskOverlay( + taskId, + createGameSessionResult.getSurfacePackage()); + } catch (IllegalArgumentException ex) { + Slog.w(TAG, "Failed to add task overlay. Destroying id: " + taskId); + destroyGameSessionDuringAttach(taskId, createGameSessionResult); return; } - mGameSessions.put(sessionId, gameSessionRecord.withGameSession(gameSession)); + mGameSessions.put(taskId, + gameSessionRecord.withGameSession( + createGameSessionResult.getGameSession(), + createGameSessionResult.getSurfacePackage())); + } + + private void destroyGameSessionDuringAttach( + int taskId, + CreateGameSessionResult createGameSessionResult) { + try { + createGameSessionResult.getGameSession().onDestroyed(); + } catch (RemoteException ex) { + Slog.w(TAG, "Failed to destroy session: " + taskId); + } } @GuardedBy("mLock") - private void destroyGameSessionIfNecessaryLocked(int sessionId) { - // TODO(b/204503192): Limit the lifespan of the game session in the Game Service provider - // to only when the associated task is running. Right now it is possible for a task to - // move into the background and for all associated processes to die and for the Game Session - // provider's GameSessionService to continue to be running. Ideally we could unbind the - // service when this happens. + private void removeAndDestroyGameSessionIfNecessaryLocked(int taskId) { if (DEBUG) { - Slog.i(TAG, "destroyGameSession() id: " + sessionId); + Slog.d(TAG, "destroyGameSession() id: " + taskId); } - GameSessionRecord gameSessionRecord = mGameSessions.remove(sessionId); + GameSessionRecord gameSessionRecord = mGameSessions.remove(taskId); if (gameSessionRecord == null) { if (DEBUG) { - Slog.w(TAG, "No game session found for id: " + sessionId); + Slog.w(TAG, "No game session found for id: " + taskId); } return; } + destroyGameSessionFromRecord(gameSessionRecord); + } + + private void destroyGameSessionFromRecord(@NonNull GameSessionRecord gameSessionRecord) { + SurfacePackage surfacePackage = gameSessionRecord.getSurfacePackage(); + if (surfacePackage != null) { + try { + mWindowManagerInternal.removeTaskOverlay( + gameSessionRecord.getTaskId(), + surfacePackage); + } catch (IllegalArgumentException ex) { + Slog.i(TAG, + "Failed to remove task overlay. This is expected if the task is already " + + "destroyed: " + + gameSessionRecord); + } + } IGameSession gameSession = gameSessionRecord.getGameSession(); if (gameSession != null) { try { - gameSession.destroy(); + gameSession.onDestroyed(); } catch (RemoteException ex) { Slog.w(TAG, "Failed to destroy session: " + gameSessionRecord, ex); } @@ -344,7 +466,7 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan if (mGameSessions.isEmpty()) { if (DEBUG) { - Slog.i(TAG, "No active game sessions. Disconnecting GameSessionService"); + Slog.d(TAG, "No active game sessions. Disconnecting GameSessionService"); } if (mGameSessionServiceConnector != null) { @@ -352,4 +474,62 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan } } } + + @Nullable + private GameSessionViewHostConfiguration createViewHostConfigurationForTask(int taskId) { + RunningTaskInfo runningTaskInfo = getRunningTaskInfoForTask(taskId); + if (runningTaskInfo == null) { + return null; + } + + Rect bounds = runningTaskInfo.configuration.windowConfiguration.getBounds(); + return new GameSessionViewHostConfiguration( + runningTaskInfo.displayId, + bounds.width(), + bounds.height()); + } + + @Nullable + private RunningTaskInfo getRunningTaskInfoForTask(int taskId) { + List<RunningTaskInfo> runningTaskInfos; + try { + runningTaskInfos = mActivityTaskManager.getTasks( + /* maxNum= */ Integer.MAX_VALUE, + /* filterOnlyVisibleRecents= */ true, + /* keepIntentExtra= */ false); + } catch (RemoteException ex) { + Slog.w(TAG, "Failed to fetch running tasks"); + return null; + } + + for (RunningTaskInfo taskInfo : runningTaskInfos) { + if (taskInfo.taskId == taskId) { + return taskInfo; + } + } + + return null; + } + + @VisibleForTesting + void takeScreenshot(int taskId, @NonNull AndroidFuture callback) { + synchronized (mLock) { + boolean isTaskAssociatedWithGameSession = mGameSessions.containsKey(taskId); + if (!isTaskAssociatedWithGameSession) { + Slog.w(TAG, "No game session found for id: " + taskId); + callback.complete(GameScreenshotResult.createInternalErrorResult()); + return; + } + } + + mBackgroundExecutor.execute(() -> { + final Bitmap bitmap = mWindowManagerService.captureTaskBitmap(taskId); + if (bitmap == null) { + Slog.w(TAG, "Could not get bitmap for id: " + taskId); + callback.complete(GameScreenshotResult.createInternalErrorResult()); + } else { + callback.complete(GameScreenshotResult.createSuccessResult(bitmap)); + } + }); + } } diff --git a/services/core/java/com/android/server/app/GameSessionRecord.java b/services/core/java/com/android/server/app/GameSessionRecord.java index e9daceb0cd39..a241812f7868 100644 --- a/services/core/java/com/android/server/app/GameSessionRecord.java +++ b/services/core/java/com/android/server/app/GameSessionRecord.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ComponentName; import android.service.games.IGameSession; +import android.view.SurfaceControlViewHost.SurfacePackage; import java.util.Objects; @@ -37,26 +38,34 @@ final class GameSessionRecord { } private final int mTaskId; + private final State mState; private final ComponentName mRootComponentName; @Nullable private final IGameSession mIGameSession; - private final State mState; + @Nullable + private final SurfacePackage mSurfacePackage; static GameSessionRecord awaitingGameSessionRequest(int taskId, ComponentName rootComponentName) { - return new GameSessionRecord(taskId, rootComponentName, /* gameSession= */ null, - State.NO_GAME_SESSION_REQUESTED); + return new GameSessionRecord( + taskId, + State.NO_GAME_SESSION_REQUESTED, + rootComponentName, + /* gameSession= */ null, + /* surfacePackage= */ null); } private GameSessionRecord( int taskId, + @NonNull State state, @NonNull ComponentName rootComponentName, @Nullable IGameSession gameSession, - @NonNull State state) { + @Nullable SurfacePackage surfacePackage) { this.mTaskId = taskId; + this.mState = state; this.mRootComponentName = rootComponentName; this.mIGameSession = gameSession; - this.mState = state; + this.mSurfacePackage = surfacePackage; } public boolean isAwaitingGameSessionRequest() { @@ -65,8 +74,12 @@ final class GameSessionRecord { @NonNull public GameSessionRecord withGameSessionRequested() { - return new GameSessionRecord(mTaskId, mRootComponentName, /* gameSession=*/ null, - State.GAME_SESSION_REQUESTED); + return new GameSessionRecord( + mTaskId, + State.GAME_SESSION_REQUESTED, + mRootComponentName, + /* gameSession=*/ null, + /* surfacePackage=*/ null); } public boolean isGameSessionRequested() { @@ -74,15 +87,20 @@ final class GameSessionRecord { } @NonNull - public GameSessionRecord withGameSession(@NonNull IGameSession gameSession) { + public GameSessionRecord withGameSession( + @NonNull IGameSession gameSession, + @NonNull SurfacePackage surfacePackage) { Objects.requireNonNull(gameSession); - return new GameSessionRecord(mTaskId, mRootComponentName, gameSession, - State.GAME_SESSION_ATTACHED); + return new GameSessionRecord(mTaskId, + State.GAME_SESSION_ATTACHED, + mRootComponentName, + gameSession, + surfacePackage); } - @Nullable - public IGameSession getGameSession() { - return mIGameSession; + @NonNull + public int getTaskId() { + return mTaskId; } @NonNull @@ -90,17 +108,29 @@ final class GameSessionRecord { return mRootComponentName; } + @Nullable + public IGameSession getGameSession() { + return mIGameSession; + } + + @Nullable + public SurfacePackage getSurfacePackage() { + return mSurfacePackage; + } + @Override public String toString() { return "GameSessionRecord{" + "mTaskId=" + mTaskId + + ", mState=" + + mState + ", mRootComponentName=" + mRootComponentName + ", mIGameSession=" + mIGameSession - + ", mState=" - + mState + + ", mSurfacePackage=" + + mSurfacePackage + '}'; } @@ -115,12 +145,16 @@ final class GameSessionRecord { } GameSessionRecord that = (GameSessionRecord) o; - return mTaskId == that.mTaskId && mRootComponentName.equals(that.mRootComponentName) - && Objects.equals(mIGameSession, that.mIGameSession) && mState == that.mState; + return mTaskId == that.mTaskId + && mState == that.mState + && mRootComponentName.equals(that.mRootComponentName) + && Objects.equals(mIGameSession, that.mIGameSession) + && Objects.equals(mSurfacePackage, that.mSurfacePackage); } @Override public int hashCode() { - return Objects.hash(mTaskId, mRootComponentName, mIGameSession, mState); + return Objects.hash( + mTaskId, mState, mRootComponentName, mIGameSession, mState, mSurfacePackage); } } diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java index 0961fcb31ace..2dd6bf575579 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java +++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java @@ -1360,6 +1360,9 @@ public class AudioDeviceInventory { case AudioSystem.DEVICE_OUT_USB_HEADSET: connType = AudioRoutesInfo.MAIN_USB; break; + case AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET: + connType = AudioRoutesInfo.MAIN_DOCK_SPEAKERS; + break; } synchronized (mCurAudioRoutes) { diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 57ae36ed906b..0ea936e35a37 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -4384,12 +4384,6 @@ public class AudioService extends IAudioService.Stub if (!mHasVibrator) { return false; } - final boolean hapticsDisabled = Settings.System.getIntForUser(mContext.getContentResolver(), - Settings.System.HAPTIC_FEEDBACK_ENABLED, 0, UserHandle.USER_CURRENT) == 0; - if (hapticsDisabled) { - return false; - } - if (effect == null) { return false; } diff --git a/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java b/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java new file mode 100644 index 000000000000..c3471bd1d771 --- /dev/null +++ b/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics.log; + +import android.hardware.biometrics.BiometricsProtoEnums; +import android.util.Slog; + +import com.android.internal.util.FrameworkStatsLog; + +/** + * Wrapper for {@link FrameworkStatsLog} to isolate the testable parts. + */ +public class BiometricFrameworkStatsLogger { + + private static final String TAG = "BiometricFrameworkStatsLogger"; + + private static final BiometricFrameworkStatsLogger sInstance = + new BiometricFrameworkStatsLogger(); + + private BiometricFrameworkStatsLogger() {} + + public static BiometricFrameworkStatsLogger getInstance() { + return sInstance; + } + + /** {@see FrameworkStatsLog.BIOMETRIC_ACQUIRED}. */ + public void acquired( + int statsModality, int statsAction, int statsClient, boolean isDebug, + int acquiredInfo, int vendorCode, boolean isCrypto, int targetUserId) { + FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_ACQUIRED, + statsModality, + targetUserId, + isCrypto, + statsAction, + statsClient, + acquiredInfo, + vendorCode, + isDebug, + -1 /* sensorId */); + } + + /** {@see FrameworkStatsLog.BIOMETRIC_AUTHENTICATED}. */ + public void authenticate( + int statsModality, int statsAction, int statsClient, boolean isDebug, long latency, + boolean authenticated, int authState, boolean requireConfirmation, boolean isCrypto, + int targetUserId, boolean isBiometricPrompt, float ambientLightLux) { + FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_AUTHENTICATED, + statsModality, + targetUserId, + isCrypto, + statsClient, + requireConfirmation, + authState, + sanitizeLatency(latency), + isDebug, + -1 /* sensorId */, + ambientLightLux); + } + + /** {@see FrameworkStatsLog.BIOMETRIC_ENROLLED}. */ + public void enroll(int statsModality, int statsAction, int statsClient, + int targetUserId, long latency, boolean enrollSuccessful, float ambientLightLux) { + FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_ENROLLED, + statsModality, + targetUserId, + sanitizeLatency(latency), + enrollSuccessful, + -1, /* sensorId */ + ambientLightLux); + } + + /** {@see FrameworkStatsLog.BIOMETRIC_ERROR_OCCURRED}. */ + public void error( + int statsModality, int statsAction, int statsClient, boolean isDebug, long latency, + int error, int vendorCode, boolean isCrypto, int targetUserId) { + FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_ERROR_OCCURRED, + statsModality, + targetUserId, + isCrypto, + statsAction, + statsClient, + error, + vendorCode, + isDebug, + sanitizeLatency(latency), + -1 /* sensorId */); + } + + /** {@see FrameworkStatsLog.BIOMETRIC_SYSTEM_HEALTH_ISSUE_DETECTED}. */ + public void reportUnknownTemplateEnrolledHal(int statsModality) { + FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_SYSTEM_HEALTH_ISSUE_DETECTED, + statsModality, + BiometricsProtoEnums.ISSUE_UNKNOWN_TEMPLATE_ENROLLED_HAL, + -1 /* sensorId */); + } + + /** {@see FrameworkStatsLog.BIOMETRIC_SYSTEM_HEALTH_ISSUE_DETECTED}. */ + public void reportUnknownTemplateEnrolledFramework(int statsModality) { + FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_SYSTEM_HEALTH_ISSUE_DETECTED, + statsModality, + BiometricsProtoEnums.ISSUE_UNKNOWN_TEMPLATE_ENROLLED_FRAMEWORK, + -1 /* sensorId */); + } + + private long sanitizeLatency(long latency) { + if (latency < 0) { + Slog.w(TAG, "found a negative latency : " + latency); + return -1; + } + return latency; + } +} diff --git a/services/core/java/com/android/server/biometrics/sensors/LoggableMonitor.java b/services/core/java/com/android/server/biometrics/log/BiometricLogger.java index b4c82f2ed799..d029af38c683 100644 --- a/services/core/java/com/android/server/biometrics/sensors/LoggableMonitor.java +++ b/services/core/java/com/android/server/biometrics/log/BiometricLogger.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.biometrics.sensors; +package com.android.server.biometrics.log; import android.annotation.NonNull; import android.annotation.Nullable; @@ -29,71 +29,28 @@ import android.hardware.face.FaceManager; import android.hardware.fingerprint.FingerprintManager; import android.util.Slog; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.FrameworkStatsLog; import com.android.server.biometrics.Utils; /** - * Abstract class that adds logging functionality to the ClientMonitor classes. + * Logger for all reported Biometric framework events. */ -public abstract class LoggableMonitor { +public class BiometricLogger { - public static final String TAG = "Biometrics/LoggableMonitor"; + public static final String TAG = "BiometricLogger"; public static final boolean DEBUG = false; - final int mStatsModality; + private final int mStatsModality; private final int mStatsAction; private final int mStatsClient; + private final BiometricFrameworkStatsLogger mSink; @NonNull private final SensorManager mSensorManager; + private long mFirstAcquireTimeMs; private boolean mLightSensorEnabled = false; private boolean mShouldLogMetrics = true; - /** - * Probe for loggable attributes that can be continuously monitored, such as ambient light. - * - * Disable probes when the sensors are in states that are not interesting for monitoring - * purposes to save power. - */ - protected interface Probe { - /** Ensure the probe is actively sampling for new data. */ - void enable(); - /** Stop sampling data. */ - void disable(); - } - - /** - * Client monitor callback that exposes a probe. - * - * Disables the probe when the operation completes. - */ - protected static class CallbackWithProbe<T extends Probe> - implements BaseClientMonitor.Callback { - private final boolean mStartWithClient; - private final T mProbe; - - public CallbackWithProbe(@NonNull T probe, boolean startWithClient) { - mProbe = probe; - mStartWithClient = startWithClient; - } - - @Override - public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) { - if (mStartWithClient) { - mProbe.enable(); - } - } - - @Override - public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, boolean success) { - mProbe.disable(); - } - - @NonNull - public T getProbe() { - return mProbe; - } - } - private class ALSProbe implements Probe { @Override public void enable() { @@ -128,26 +85,30 @@ public abstract class LoggableMonitor { * @param statsAction One of {@link BiometricsProtoEnums} ACTION_* constants. * @param statsClient One of {@link BiometricsProtoEnums} CLIENT_* constants. */ - public LoggableMonitor(@NonNull Context context, int statsModality, int statsAction, - int statsClient) { + public BiometricLogger( + @NonNull Context context, int statsModality, int statsAction, int statsClient) { + this(statsModality, statsAction, statsClient, + BiometricFrameworkStatsLogger.getInstance(), + context.getSystemService(SensorManager.class)); + } + + @VisibleForTesting + BiometricLogger( + int statsModality, int statsAction, int statsClient, + BiometricFrameworkStatsLogger logSink, SensorManager sensorManager) { mStatsModality = statsModality; mStatsAction = statsAction; mStatsClient = statsClient; - mSensorManager = context.getSystemService(SensorManager.class); + mSink = logSink; + mSensorManager = sensorManager; } - /** - * Only valid for AuthenticationClient. - * @return true if the client is authenticating for a crypto operation. - */ - protected boolean isCryptoOperation() { - return false; - } - - protected void setShouldLog(boolean shouldLog) { - mShouldLogMetrics = shouldLog; + /** Disable logging metrics and only log critical events, such as system health issues. */ + public void disableMetrics() { + mShouldLogMetrics = false; } + /** {@link BiometricsProtoEnums} CLIENT_* constants */ public int getStatsClient() { return mStatsClient; } @@ -171,8 +132,9 @@ public abstract class LoggableMonitor { return shouldSkipLogging; } - protected final void logOnAcquired(Context context, int acquiredInfo, int vendorCode, - int targetUserId) { + /** Log an acquisition event. */ + public void logOnAcquired(Context context, + int acquiredInfo, int vendorCode, boolean isCrypto, int targetUserId) { if (!mShouldLogMetrics) { return; } @@ -192,7 +154,7 @@ public abstract class LoggableMonitor { if (DEBUG) { Slog.v(TAG, "Acquired! Modality: " + mStatsModality + ", User: " + targetUserId - + ", IsCrypto: " + isCryptoOperation() + + ", IsCrypto: " + isCrypto + ", Action: " + mStatsAction + ", Client: " + mStatsClient + ", AcquiredInfo: " + acquiredInfo @@ -203,19 +165,14 @@ public abstract class LoggableMonitor { return; } - FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_ACQUIRED, - mStatsModality, - targetUserId, - isCryptoOperation(), - mStatsAction, - mStatsClient, - acquiredInfo, - vendorCode, + mSink.acquired(mStatsModality, mStatsAction, mStatsClient, Utils.isDebugEnabled(context, targetUserId), - -1 /* sensorId */); + acquiredInfo, vendorCode, isCrypto, targetUserId); } - protected final void logOnError(Context context, int error, int vendorCode, int targetUserId) { + /** Log an error during an operation. */ + public void logOnError(Context context, + int error, int vendorCode, boolean isCrypto, int targetUserId) { if (!mShouldLogMetrics) { return; } @@ -226,7 +183,7 @@ public abstract class LoggableMonitor { if (DEBUG) { Slog.v(TAG, "Error! Modality: " + mStatsModality + ", User: " + targetUserId - + ", IsCrypto: " + isCryptoOperation() + + ", IsCrypto: " + isCrypto + ", Action: " + mStatsAction + ", Client: " + mStatsClient + ", Error: " + error @@ -240,21 +197,15 @@ public abstract class LoggableMonitor { return; } - FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_ERROR_OCCURRED, - mStatsModality, - targetUserId, - isCryptoOperation(), - mStatsAction, - mStatsClient, - error, - vendorCode, - Utils.isDebugEnabled(context, targetUserId), - sanitizeLatency(latency), - -1 /* sensorId */); + mSink.error(mStatsModality, mStatsAction, mStatsClient, + Utils.isDebugEnabled(context, targetUserId), latency, + error, vendorCode, isCrypto, targetUserId); } - protected final void logOnAuthenticated(Context context, boolean authenticated, - boolean requireConfirmation, int targetUserId, boolean isBiometricPrompt) { + /** Log authentication attempt. */ + public void logOnAuthenticated(Context context, + boolean authenticated, boolean requireConfirmation, boolean isCrypto, + int targetUserId, boolean isBiometricPrompt) { if (!mShouldLogMetrics) { return; } @@ -279,7 +230,7 @@ public abstract class LoggableMonitor { if (DEBUG) { Slog.v(TAG, "Authenticated! Modality: " + mStatsModality + ", User: " + targetUserId - + ", IsCrypto: " + isCryptoOperation() + + ", IsCrypto: " + isCrypto + ", Client: " + mStatsClient + ", RequireConfirmation: " + requireConfirmation + ", State: " + authState @@ -293,20 +244,14 @@ public abstract class LoggableMonitor { return; } - FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_AUTHENTICATED, - mStatsModality, - targetUserId, - isCryptoOperation(), - mStatsClient, - requireConfirmation, - authState, - sanitizeLatency(latency), + mSink.authenticate(mStatsModality, mStatsAction, mStatsClient, Utils.isDebugEnabled(context, targetUserId), - -1 /* sensorId */, - mLastAmbientLux /* ambientLightLux */); + latency, authenticated, authState, requireConfirmation, isCrypto, + targetUserId, isBiometricPrompt, mLastAmbientLux); } - protected final void logOnEnrolled(int targetUserId, long latency, boolean enrollSuccessful) { + /** Log enrollment outcome. */ + public void logOnEnrolled(int targetUserId, long latency, boolean enrollSuccessful) { if (!mShouldLogMetrics) { return; } @@ -326,25 +271,30 @@ public abstract class LoggableMonitor { return; } - FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_ENROLLED, - mStatsModality, - targetUserId, - sanitizeLatency(latency), - enrollSuccessful, - -1, /* sensorId */ - mLastAmbientLux /* ambientLightLux */); + mSink.enroll(mStatsModality, mStatsAction, mStatsClient, + targetUserId, latency, enrollSuccessful, mLastAmbientLux); } - private long sanitizeLatency(long latency) { - if (latency < 0) { - Slog.w(TAG, "found a negative latency : " + latency); - return -1; + /** Report unexpected enrollment reported by the HAL. */ + public void logUnknownEnrollmentInHal() { + if (shouldSkipLogging()) { + return; } - return latency; + + mSink.reportUnknownTemplateEnrolledHal(mStatsModality); + } + + /** Report unknown enrollment in framework settings */ + public void logUnknownEnrollmentInFramework() { + if (shouldSkipLogging()) { + return; + } + + mSink.reportUnknownTemplateEnrolledFramework(mStatsModality); } /** - * Get a callback to start/stop ALS capture when client runs. + * Get a callback to start/stop ALS capture when a client runs. * * If the probe should not run for the entire operation, do not set startWithClient and * start/stop the problem when needed. @@ -352,7 +302,7 @@ public abstract class LoggableMonitor { * @param startWithClient if probe should start automatically when the operation starts. */ @NonNull - protected CallbackWithProbe<Probe> createALSCallback(boolean startWithClient) { + public CallbackWithProbe<Probe> createALSCallback(boolean startWithClient) { return new CallbackWithProbe<>(new ALSProbe(), startWithClient); } diff --git a/services/core/java/com/android/server/biometrics/log/CallbackWithProbe.java b/services/core/java/com/android/server/biometrics/log/CallbackWithProbe.java new file mode 100644 index 000000000000..f7b736885f71 --- /dev/null +++ b/services/core/java/com/android/server/biometrics/log/CallbackWithProbe.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics.log; + +import android.annotation.NonNull; + +import com.android.server.biometrics.sensors.BaseClientMonitor; +import com.android.server.biometrics.sensors.ClientMonitorCallback; + +/** + * Client monitor callback that exposes a probe. + * + * Disables the probe when the operation completes. + * + * @param <T> probe type + */ +public class CallbackWithProbe<T extends Probe> implements ClientMonitorCallback { + private final boolean mStartWithClient; + private final T mProbe; + + public CallbackWithProbe(@NonNull T probe, boolean startWithClient) { + mProbe = probe; + mStartWithClient = startWithClient; + } + + @Override + public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) { + if (mStartWithClient) { + mProbe.enable(); + } + } + + @Override + public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, boolean success) { + mProbe.disable(); + } + + @NonNull + public T getProbe() { + return mProbe; + } +} diff --git a/services/core/java/com/android/server/biometrics/log/Probe.java b/services/core/java/com/android/server/biometrics/log/Probe.java new file mode 100644 index 000000000000..9e6fc6b8b8b2 --- /dev/null +++ b/services/core/java/com/android/server/biometrics/log/Probe.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics.log; + +/** + * Probe for loggable attributes that can be continuously monitored, such as ambient light. + * + * Disable probes when the sensors are in states that are not interesting for monitoring + * purposes to save power. + */ +public interface Probe { + /** Ensure the probe is actively sampling for new data. */ + void enable(); + /** Stop sampling data. */ + void disable(); +} diff --git a/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java b/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java index 6f7176816ddb..86d72ba1c06f 100644 --- a/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java @@ -105,7 +105,8 @@ public abstract class AcquisitionClient<T> extends HalClientMonitor<T> implement // that do not handle lockout under the HAL. In these cases, ensure that the framework only // sends errors once per ClientMonitor. if (mShouldSendErrorToClient) { - logOnError(getContext(), errorCode, vendorCode, getTargetUserId()); + getLogger().logOnError(getContext(), errorCode, vendorCode, + isCryptoOperation(), getTargetUserId()); try { if (getListener() != null) { mShouldSendErrorToClient = false; @@ -137,7 +138,7 @@ public abstract class AcquisitionClient<T> extends HalClientMonitor<T> implement } @Override - public void cancelWithoutStarting(@NonNull Callback callback) { + public void cancelWithoutStarting(@NonNull ClientMonitorCallback callback) { Slog.d(TAG, "cancelWithoutStarting: " + this); final int errorCode = BiometricConstants.BIOMETRIC_ERROR_CANCELED; @@ -163,7 +164,8 @@ public abstract class AcquisitionClient<T> extends HalClientMonitor<T> implement protected final void onAcquiredInternal(int acquiredInfo, int vendorCode, boolean shouldSend) { - super.logOnAcquired(getContext(), acquiredInfo, vendorCode, getTargetUserId()); + getLogger().logOnAcquired(getContext(), acquiredInfo, vendorCode, + isCryptoOperation(), getTargetUserId()); if (DEBUG) { Slog.v(TAG, "Acquired: " + acquiredInfo + " " + vendorCode + ", shouldSend: " + shouldSend); diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java index 358263df916b..35a0f575ec97 100644 --- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java @@ -91,7 +91,7 @@ public abstract class AuthenticationClient<T> extends AcquisitionClient<T> /** * Handles lifecycle, e.g. {@link BiometricScheduler}, - * {@link com.android.server.biometrics.sensors.BaseClientMonitor.Callback} after authentication + * {@link ClientMonitorCallback} after authentication * results are known. Note that this happens asynchronously from (but shortly after) * {@link #onAuthenticated(BiometricAuthenticator.Identifier, boolean, ArrayList)} and allows * {@link CoexCoordinator} a chance to invoke/delay this event. @@ -180,8 +180,8 @@ public abstract class AuthenticationClient<T> extends AcquisitionClient<T> @Override public void onAuthenticated(BiometricAuthenticator.Identifier identifier, boolean authenticated, ArrayList<Byte> hardwareAuthToken) { - super.logOnAuthenticated(getContext(), authenticated, mRequireConfirmation, - getTargetUserId(), isBiometricPrompt()); + getLogger().logOnAuthenticated(getContext(), authenticated, mRequireConfirmation, + isCryptoOperation(), getTargetUserId(), isBiometricPrompt()); final ClientMonitorCallbackConverter listener = getListener(); @@ -440,7 +440,7 @@ public abstract class AuthenticationClient<T> extends AcquisitionClient<T> * Start authentication */ @Override - public void start(@NonNull Callback callback) { + public void start(@NonNull ClientMonitorCallback callback) { super.start(callback); final @LockoutTracker.LockoutMode int lockoutMode = diff --git a/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java b/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java index 26bbb403f39f..e1f7e2ab5461 100644 --- a/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java +++ b/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java @@ -27,18 +27,16 @@ import android.os.RemoteException; import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; +import com.android.server.biometrics.log.BiometricLogger; -import java.util.ArrayList; -import java.util.List; import java.util.NoSuchElementException; /** - * Abstract base class for keeping track and dispatching events from the biometric's HAL to the + * Abstract base class for keeping track and dispatching events from the biometric's HAL to * the current client. Subclasses are responsible for coordinating the interaction with * the biometric's HAL for the specific action (e.g. authenticate, enroll, enumerate, etc.). */ -public abstract class BaseClientMonitor extends LoggableMonitor - implements IBinder.DeathRecipient { +public abstract class BaseClientMonitor implements IBinder.DeathRecipient { private static final String TAG = "Biometrics/ClientMonitor"; protected static final boolean DEBUG = true; @@ -46,68 +44,12 @@ public abstract class BaseClientMonitor extends LoggableMonitor // Counter used to distinguish between ClientMonitor instances to help debugging. private static int sCount = 0; - /** - * Interface that ClientMonitor holders should use to receive callbacks. - */ - public interface Callback { - /** - * Invoked when the ClientMonitor operation has been started (e.g. reached the head of - * the queue and becomes the current operation). - * - * @param clientMonitor Reference of the ClientMonitor that is starting. - */ - default void onClientStarted(@NonNull BaseClientMonitor clientMonitor) { - } - - /** - * Invoked when the ClientMonitor operation is complete. This abstracts away asynchronous - * (i.e. Authenticate, Enroll, Enumerate, Remove) and synchronous (i.e. generateChallenge, - * revokeChallenge) so that a scheduler can process ClientMonitors regardless of their - * implementation. - * - * @param clientMonitor Reference of the ClientMonitor that finished. - * @param success True if the operation completed successfully. - */ - default void onClientFinished(@NonNull BaseClientMonitor clientMonitor, boolean success) { - } - } - - /** Holder for wrapping multiple handlers into a single Callback. */ - public static class CompositeCallback implements Callback { - @NonNull - private final List<Callback> mCallbacks; - - public CompositeCallback(@NonNull Callback... callbacks) { - mCallbacks = new ArrayList<>(); - - for (Callback callback : callbacks) { - if (callback != null) { - mCallbacks.add(callback); - } - } - } - - @Override - public final void onClientStarted(@NonNull BaseClientMonitor clientMonitor) { - for (int i = 0; i < mCallbacks.size(); i++) { - mCallbacks.get(i).onClientStarted(clientMonitor); - } - } - - @Override - public final void onClientFinished(@NonNull BaseClientMonitor clientMonitor, - boolean success) { - for (int i = mCallbacks.size() - 1; i >= 0; i--) { - mCallbacks.get(i).onClientFinished(clientMonitor, success); - } - } - } - private final int mSequentialId; @NonNull private final Context mContext; private final int mTargetUserId; @NonNull private final String mOwner; private final int mSensorId; // sensorId as configured by the framework + @NonNull private final BiometricLogger mLogger; @Nullable private IBinder mToken; private long mRequestId; @@ -119,7 +61,7 @@ public abstract class BaseClientMonitor extends LoggableMonitor // Use an empty callback by default since delayed operations can receive events // before they are started and cause NPE in subclasses that access this field directly. - @NonNull protected Callback mCallback = new Callback() { + @NonNull protected ClientMonitorCallback mCallback = new ClientMonitorCallback() { @Override public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) { Slog.e(TAG, "mCallback onClientStarted: called before set (should not happen)"); @@ -133,18 +75,6 @@ public abstract class BaseClientMonitor extends LoggableMonitor }; /** - * @return A ClientMonitorEnum constant defined in biometrics.proto - */ - public abstract int getProtoEnum(); - - /** - * @return True if the ClientMonitor should cancel any current and pending interruptable clients - */ - public boolean interruptsPrecedingClients() { - return false; - } - - /** * @param context system_server context * @param token a unique token for the client * @param listener recipient of related events (e.g. authentication) @@ -160,7 +90,14 @@ public abstract class BaseClientMonitor extends LoggableMonitor @Nullable IBinder token, @Nullable ClientMonitorCallbackConverter listener, int userId, @NonNull String owner, int cookie, int sensorId, int statsModality, int statsAction, int statsClient) { - super(context, statsModality, statsAction, statsClient); + this(context, token, listener, userId, owner, cookie, sensorId, + new BiometricLogger(context, statsModality, statsAction, statsClient)); + } + + @VisibleForTesting + BaseClientMonitor(@NonNull Context context, + @Nullable IBinder token, @Nullable ClientMonitorCallbackConverter listener, int userId, + @NonNull String owner, int cookie, int sensorId, @NonNull BiometricLogger logger) { mSequentialId = sCount++; mContext = context; mToken = token; @@ -170,6 +107,7 @@ public abstract class BaseClientMonitor extends LoggableMonitor mOwner = owner; mCookie = cookie; mSensorId = sensorId; + mLogger = logger; try { if (token != null) { @@ -180,15 +118,19 @@ public abstract class BaseClientMonitor extends LoggableMonitor } } - public int getCookie() { - return mCookie; + /** A ClientMonitorEnum constant defined in biometrics.proto */ + public abstract int getProtoEnum(); + + /** True if the ClientMonitor should cancel any current and pending interruptable clients. */ + public boolean interruptsPrecedingClients() { + return false; } /** * Starts the ClientMonitor's lifecycle. * @param callback invoked when the operation is complete (succeeds, fails, etc) */ - public void start(@NonNull Callback callback) { + public void start(@NonNull ClientMonitorCallback callback) { mCallback = wrapCallbackForStart(callback); mCallback.onClientStarted(this); } @@ -199,7 +141,7 @@ public abstract class BaseClientMonitor extends LoggableMonitor * Returns the original callback unless overridden. */ @NonNull - protected Callback wrapCallbackForStart(@NonNull Callback callback) { + protected ClientMonitorCallback wrapCallbackForStart(@NonNull ClientMonitorCallback callback) { return callback; } @@ -257,6 +199,20 @@ public abstract class BaseClientMonitor extends LoggableMonitor } } + /** + * Only valid for AuthenticationClient. + * @return true if the client is authenticating for a crypto operation. + */ + protected boolean isCryptoOperation() { + return false; + } + + /** Logger for this client */ + @NonNull + public BiometricLogger getLogger() { + return mLogger; + } + public final Context getContext() { return mContext; } @@ -281,6 +237,11 @@ public abstract class BaseClientMonitor extends LoggableMonitor return mSensorId; } + /** Cookie set when this monitor was created. */ + public int getCookie() { + return mCookie; + } + /** Unique request id. */ public final long getRequestId() { return mRequestId; @@ -305,7 +266,7 @@ public abstract class BaseClientMonitor extends LoggableMonitor } @VisibleForTesting - public Callback getCallback() { + public ClientMonitorCallback getCallback() { return mCallback; } diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java index 39c5944d65c7..1a6da94f683e 100644 --- a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java +++ b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java @@ -160,7 +160,7 @@ public class BiometricScheduler { // Internal callback, notified when an operation is complete. Notifies the requester // that the operation is complete, before performing internal scheduler work (such as // starting the next client). - private final BaseClientMonitor.Callback mInternalCallback = new BaseClientMonitor.Callback() { + private final ClientMonitorCallback mInternalCallback = new ClientMonitorCallback() { @Override public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) { Slog.d(getTag(), "[Started] " + clientMonitor); @@ -247,7 +247,7 @@ public class BiometricScheduler { } @VisibleForTesting - public BaseClientMonitor.Callback getInternalCallback() { + public ClientMonitorCallback getInternalCallback() { return mInternalCallback; } @@ -368,7 +368,7 @@ public class BiometricScheduler { * @param clientCallback optional callback, invoked when the client state changes. */ public void scheduleClientMonitor(@NonNull BaseClientMonitor clientMonitor, - @Nullable BaseClientMonitor.Callback clientCallback) { + @Nullable ClientMonitorCallback clientCallback) { // If the incoming operation should interrupt preceding clients, mark any interruptable // pending clients as canceling. Once they reach the head of the queue, the scheduler will // send ERROR_CANCELED and skip the operation. diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java b/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java index e8b50d90b586..812ca8ac62fe 100644 --- a/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java +++ b/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java @@ -65,7 +65,7 @@ public class BiometricSchedulerOperation { protected static final int STATE_WAITING_FOR_COOKIE = 4; /** - * The {@link BaseClientMonitor.Callback} has been invoked and the client is finished. + * The {@link ClientMonitorCallback} has been invoked and the client is finished. */ protected static final int STATE_FINISHED = 5; @@ -83,7 +83,7 @@ public class BiometricSchedulerOperation { @NonNull private final BaseClientMonitor mClientMonitor; @Nullable - private final BaseClientMonitor.Callback mClientCallback; + private final ClientMonitorCallback mClientCallback; @OperationState private int mState; @VisibleForTesting @@ -92,14 +92,14 @@ public class BiometricSchedulerOperation { BiometricSchedulerOperation( @NonNull BaseClientMonitor clientMonitor, - @Nullable BaseClientMonitor.Callback callback + @Nullable ClientMonitorCallback callback ) { this(clientMonitor, callback, STATE_WAITING_IN_QUEUE); } protected BiometricSchedulerOperation( @NonNull BaseClientMonitor clientMonitor, - @Nullable BaseClientMonitor.Callback callback, + @Nullable ClientMonitorCallback callback, @OperationState int state ) { mClientMonitor = clientMonitor; @@ -139,7 +139,7 @@ public class BiometricSchedulerOperation { * @param callback lifecycle callback * @return if this operation started */ - public boolean start(@NonNull BaseClientMonitor.Callback callback) { + public boolean start(@NonNull ClientMonitorCallback callback) { checkInState("start", STATE_WAITING_IN_QUEUE, STATE_WAITING_FOR_COOKIE, @@ -159,7 +159,7 @@ public class BiometricSchedulerOperation { * @param cookie cookie indicting the operation should begin * @return if this operation started */ - public boolean startWithCookie(@NonNull BaseClientMonitor.Callback callback, int cookie) { + public boolean startWithCookie(@NonNull ClientMonitorCallback callback, int cookie) { checkInState("start", STATE_WAITING_IN_QUEUE, STATE_WAITING_FOR_COOKIE, @@ -173,8 +173,8 @@ public class BiometricSchedulerOperation { return doStart(callback); } - private boolean doStart(@NonNull BaseClientMonitor.Callback callback) { - final BaseClientMonitor.Callback cb = getWrappedCallback(callback); + private boolean doStart(@NonNull ClientMonitorCallback callback) { + final ClientMonitorCallback cb = getWrappedCallback(callback); if (mState == STATE_WAITING_IN_QUEUE_CANCELING) { Slog.d(TAG, "Operation marked for cancellation, cancelling now: " + this); @@ -239,9 +239,9 @@ public class BiometricSchedulerOperation { * * @param handler handler to use for the cancellation watchdog * @param callback lifecycle callback (only used if this operation hasn't started, otherwise - * the callback used from {@link #start(BaseClientMonitor.Callback)} is used) + * the callback used from {@link #start(ClientMonitorCallback)} is used) */ - public void cancel(@NonNull Handler handler, @NonNull BaseClientMonitor.Callback callback) { + public void cancel(@NonNull Handler handler, @NonNull ClientMonitorCallback callback) { checkNotInState("cancel", STATE_FINISHED); final int currentState = mState; @@ -270,14 +270,14 @@ public class BiometricSchedulerOperation { } @NonNull - private BaseClientMonitor.Callback getWrappedCallback() { + private ClientMonitorCallback getWrappedCallback() { return getWrappedCallback(null); } @NonNull - private BaseClientMonitor.Callback getWrappedCallback( - @Nullable BaseClientMonitor.Callback callback) { - final BaseClientMonitor.Callback destroyCallback = new BaseClientMonitor.Callback() { + private ClientMonitorCallback getWrappedCallback( + @Nullable ClientMonitorCallback callback) { + final ClientMonitorCallback destroyCallback = new ClientMonitorCallback() { @Override public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, boolean success) { @@ -286,7 +286,7 @@ public class BiometricSchedulerOperation { mState = STATE_FINISHED; } }; - return new BaseClientMonitor.CompositeCallback(destroyCallback, callback, mClientCallback); + return new ClientMonitorCompositeCallback(destroyCallback, callback, mClientCallback); } /** {@link BaseClientMonitor#getSensorId()}. */ diff --git a/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallback.java b/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallback.java new file mode 100644 index 000000000000..8ea4ee911cb1 --- /dev/null +++ b/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallback.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics.sensors; + +import android.annotation.NonNull; + +/** + * Interface that ClientMonitor holders should use to receive callbacks. + */ +public interface ClientMonitorCallback { + /** + * Invoked when the ClientMonitor operation has been started (e.g. reached the head of + * the queue and becomes the current operation). + * + * @param clientMonitor Reference of the ClientMonitor that is starting. + */ + default void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {} + + /** + * Invoked when the ClientMonitor operation is complete. This abstracts away asynchronous + * (i.e. Authenticate, Enroll, Enumerate, Remove) and synchronous (i.e. generateChallenge, + * revokeChallenge) so that a scheduler can process ClientMonitors regardless of their + * implementation. + * + * @param clientMonitor Reference of the ClientMonitor that finished. + * @param success True if the operation completed successfully. + */ + default void onClientFinished(@NonNull BaseClientMonitor clientMonitor, boolean success) {} +} diff --git a/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCompositeCallback.java b/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCompositeCallback.java new file mode 100644 index 000000000000..b82f5fa129d6 --- /dev/null +++ b/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCompositeCallback.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics.sensors; + +import android.annotation.NonNull; + +import java.util.ArrayList; +import java.util.List; + +/** Holder for wrapping multiple handlers into a single Callback. */ +public class ClientMonitorCompositeCallback implements ClientMonitorCallback { + @NonNull + private final List<ClientMonitorCallback> mCallbacks; + + public ClientMonitorCompositeCallback(@NonNull ClientMonitorCallback... callbacks) { + mCallbacks = new ArrayList<>(); + + for (ClientMonitorCallback callback : callbacks) { + if (callback != null) { + mCallbacks.add(callback); + } + } + } + + @Override + public final void onClientStarted(@NonNull BaseClientMonitor clientMonitor) { + for (int i = 0; i < mCallbacks.size(); i++) { + mCallbacks.get(i).onClientStarted(clientMonitor); + } + } + + @Override + public final void onClientFinished(@NonNull BaseClientMonitor clientMonitor, + boolean success) { + for (int i = mCallbacks.size() - 1; i >= 0; i--) { + mCallbacks.get(i).onClientFinished(clientMonitor, success); + } + } +} diff --git a/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java index 2826e0c97305..3b7adc1a6176 100644 --- a/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java @@ -89,7 +89,8 @@ public abstract class EnrollClient<T> extends AcquisitionClient<T> implements En if (remaining == 0) { mBiometricUtils.addBiometricForUser(getContext(), getTargetUserId(), identifier); - logOnEnrolled(getTargetUserId(), System.currentTimeMillis() - mEnrollmentStartTimeMs, + getLogger().logOnEnrolled(getTargetUserId(), + System.currentTimeMillis() - mEnrollmentStartTimeMs, true /* enrollSuccessful */); mCallback.onClientFinished(this, true /* success */); } @@ -97,7 +98,7 @@ public abstract class EnrollClient<T> extends AcquisitionClient<T> implements En } @Override - public void start(@NonNull Callback callback) { + public void start(@NonNull ClientMonitorCallback callback) { super.start(callback); if (hasReachedEnrollmentLimit()) { @@ -116,7 +117,8 @@ public abstract class EnrollClient<T> extends AcquisitionClient<T> implements En */ @Override public void onError(int error, int vendorCode) { - logOnEnrolled(getTargetUserId(), System.currentTimeMillis() - mEnrollmentStartTimeMs, + getLogger().logOnEnrolled(getTargetUserId(), + System.currentTimeMillis() - mEnrollmentStartTimeMs, false /* enrollSuccessful */); super.onError(error, vendorCode); } diff --git a/services/core/java/com/android/server/biometrics/sensors/EnrollmentModifier.java b/services/core/java/com/android/server/biometrics/sensors/EnrollmentModifier.java index c2f909b08bb6..3060f30bc084 100644 --- a/services/core/java/com/android/server/biometrics/sensors/EnrollmentModifier.java +++ b/services/core/java/com/android/server/biometrics/sensors/EnrollmentModifier.java @@ -23,7 +23,7 @@ public interface EnrollmentModifier { /** * Callers should typically check this after - * {@link BaseClientMonitor.Callback#onClientFinished(BaseClientMonitor, boolean)} + * {@link ClientMonitorCallback#onClientFinished(BaseClientMonitor, boolean)} * * @return true if the user has gone from: * 1) none-enrolled --> enrolled diff --git a/services/core/java/com/android/server/biometrics/sensors/GenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/GenerateChallengeClient.java index 3d74f369efde..6fb6d08cd602 100644 --- a/services/core/java/com/android/server/biometrics/sensors/GenerateChallengeClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/GenerateChallengeClient.java @@ -47,7 +47,7 @@ public abstract class GenerateChallengeClient<T> extends HalClientMonitor<T> { } @Override - public void start(@NonNull Callback callback) { + public void start(@NonNull ClientMonitorCallback callback) { super.start(callback); startHalOperation(); diff --git a/services/core/java/com/android/server/biometrics/sensors/HalClientMonitor.java b/services/core/java/com/android/server/biometrics/sensors/HalClientMonitor.java index 63cd4125262d..c8830f8049a2 100644 --- a/services/core/java/com/android/server/biometrics/sensors/HalClientMonitor.java +++ b/services/core/java/com/android/server/biometrics/sensors/HalClientMonitor.java @@ -45,7 +45,7 @@ public abstract class HalClientMonitor<T> extends BaseClientMonitor { /** * Invoked if the scheduler is unable to start the ClientMonitor (for example the HAL is null). * If such a problem is detected, the scheduler will not invoke - * {@link #start(Callback)}. + * {@link #start(ClientMonitorCallback)}. */ public abstract void unableToStart(); diff --git a/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java index 579dfd69ec66..0636893eabf7 100644 --- a/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java @@ -23,7 +23,6 @@ import android.hardware.biometrics.BiometricsProtoEnums; import android.os.IBinder; import android.util.Slog; -import com.android.internal.util.FrameworkStatsLog; import com.android.server.biometrics.BiometricsProto; import java.util.ArrayList; @@ -65,7 +64,7 @@ public abstract class InternalCleanupClient<S extends BiometricAuthenticator.Ide private final boolean mHasEnrollmentsBeforeStarting; private BaseClientMonitor mCurrentTask; - private final Callback mEnumerateCallback = new Callback() { + private final ClientMonitorCallback mEnumerateCallback = new ClientMonitorCallback() { @Override public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, boolean success) { final List<BiometricAuthenticator.Identifier> unknownHALTemplates = @@ -91,7 +90,7 @@ public abstract class InternalCleanupClient<S extends BiometricAuthenticator.Ide } }; - private final Callback mRemoveCallback = new Callback() { + private final ClientMonitorCallback mRemoveCallback = new ClientMonitorCallback() { @Override public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, boolean success) { Slog.d(TAG, "Remove onClientFinished: " + clientMonitor + ", success: " + success); @@ -128,10 +127,9 @@ public abstract class InternalCleanupClient<S extends BiometricAuthenticator.Ide mCurrentTask = getRemovalClient(getContext(), mLazyDaemon, getToken(), template.mIdentifier.getBiometricId(), template.mUserId, getContext().getPackageName(), mBiometricUtils, getSensorId(), mAuthenticatorIds); - FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_SYSTEM_HEALTH_ISSUE_DETECTED, - mStatsModality, - BiometricsProtoEnums.ISSUE_UNKNOWN_TEMPLATE_ENROLLED_HAL, - -1 /* sensorId */); + + getLogger().logUnknownEnrollmentInHal(); + mCurrentTask.start(mRemoveCallback); } @@ -141,7 +139,7 @@ public abstract class InternalCleanupClient<S extends BiometricAuthenticator.Ide } @Override - public void start(@NonNull Callback callback) { + public void start(@NonNull ClientMonitorCallback callback) { super.start(callback); // Start enumeration. Removal will start if necessary, when enumeration is completed. diff --git a/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java b/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java index 7f6903a17b32..05ea19a3aa14 100644 --- a/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java @@ -23,7 +23,6 @@ import android.hardware.biometrics.BiometricsProtoEnums; import android.os.IBinder; import android.util.Slog; -import com.android.internal.util.FrameworkStatsLog; import com.android.server.biometrics.BiometricsProto; import java.util.ArrayList; @@ -73,7 +72,7 @@ public abstract class InternalEnumerateClient<T> extends HalClientMonitor<T> } @Override - public void start(@NonNull Callback callback) { + public void start(@NonNull ClientMonitorCallback callback) { super.start(callback); // The biometric template ids will be removed when we get confirmation from the HAL @@ -116,10 +115,8 @@ public abstract class InternalEnumerateClient<T> extends HalClientMonitor<T> + identifier.getBiometricId() + " " + identifier.getName()); mUtils.removeBiometricForUser(getContext(), getTargetUserId(), identifier.getBiometricId()); - FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_SYSTEM_HEALTH_ISSUE_DETECTED, - mStatsModality, - BiometricsProtoEnums.ISSUE_UNKNOWN_TEMPLATE_ENROLLED_FRAMEWORK, - -1 /* sensorId */); + + getLogger().logUnknownEnrollmentInFramework(); } mEnrolledList.clear(); } diff --git a/services/core/java/com/android/server/biometrics/sensors/Interruptable.java b/services/core/java/com/android/server/biometrics/sensors/Interruptable.java index d5093c756415..4f645efcccf0 100644 --- a/services/core/java/com/android/server/biometrics/sensors/Interruptable.java +++ b/services/core/java/com/android/server/biometrics/sensors/Interruptable.java @@ -29,15 +29,15 @@ public interface Interruptable { /** * Notifies the client that it needs to finish before - * {@link BaseClientMonitor#start(BaseClientMonitor.Callback)} was invoked. This usually happens + * {@link BaseClientMonitor#start(ClientMonitorCallback)} was invoked. This usually happens * if the client is still waiting in the pending queue and got notified that a subsequent * operation is preempting it. * * This method must invoke - * {@link BaseClientMonitor.Callback#onClientFinished(BaseClientMonitor, boolean)} on the + * {@link ClientMonitorCallback#onClientFinished(BaseClientMonitor, boolean)} on the * given callback (with success). * * @param callback invoked when the operation is completed. */ - void cancelWithoutStarting(@NonNull BaseClientMonitor.Callback callback); + void cancelWithoutStarting(@NonNull ClientMonitorCallback callback); } diff --git a/services/core/java/com/android/server/biometrics/sensors/InvalidationClient.java b/services/core/java/com/android/server/biometrics/sensors/InvalidationClient.java index cede4a725246..ee6bb0f0886a 100644 --- a/services/core/java/com/android/server/biometrics/sensors/InvalidationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/InvalidationClient.java @@ -62,7 +62,7 @@ public abstract class InvalidationClient<S extends BiometricAuthenticator.Identi } @Override - public void start(@NonNull Callback callback) { + public void start(@NonNull ClientMonitorCallback callback) { super.start(callback); startHalOperation(); diff --git a/services/core/java/com/android/server/biometrics/sensors/InvalidationRequesterClient.java b/services/core/java/com/android/server/biometrics/sensors/InvalidationRequesterClient.java index 5ba1b0000a7c..b2661a28012d 100644 --- a/services/core/java/com/android/server/biometrics/sensors/InvalidationRequesterClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/InvalidationRequesterClient.java @@ -84,7 +84,7 @@ public class InvalidationRequesterClient<S extends BiometricAuthenticator.Identi } @Override - public void start(@NonNull Callback callback) { + public void start(@NonNull ClientMonitorCallback callback) { super.start(callback); mUtils.setInvalidationInProgress(getContext(), getTargetUserId(), true /* inProgress */); diff --git a/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java index 2a6677e55d60..e79819b401ea 100644 --- a/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java @@ -17,7 +17,6 @@ package com.android.server.biometrics.sensors; import android.annotation.NonNull; -import android.annotation.Nullable; import android.content.Context; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricsProtoEnums; @@ -59,7 +58,7 @@ public abstract class RemovalClient<S extends BiometricAuthenticator.Identifier, } @Override - public void start(@NonNull Callback callback) { + public void start(@NonNull ClientMonitorCallback callback) { super.start(callback); // The biometric template ids will be removed when we get confirmation from the HAL diff --git a/services/core/java/com/android/server/biometrics/sensors/RevokeChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/RevokeChallengeClient.java index 1edf5afef3b2..21a6ddfcde66 100644 --- a/services/core/java/com/android/server/biometrics/sensors/RevokeChallengeClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/RevokeChallengeClient.java @@ -38,7 +38,7 @@ public abstract class RevokeChallengeClient<T> extends HalClientMonitor<T> { } @Override - public void start(@NonNull Callback callback) { + public void start(@NonNull ClientMonitorCallback callback) { super.start(callback); startHalOperation(); diff --git a/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java index 603cc22968a9..4f900208841e 100644 --- a/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java +++ b/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java @@ -56,7 +56,7 @@ public class UserAwareBiometricScheduler extends BiometricScheduler { @NonNull private final UserSwitchCallback mUserSwitchCallback; @Nullable private StopUserClient<?> mStopUserClient; - private class ClientFinishedCallback implements BaseClientMonitor.Callback { + private class ClientFinishedCallback implements ClientMonitorCallback { private final BaseClientMonitor mOwner; ClientFinishedCallback(BaseClientMonitor owner) { diff --git a/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java index 77e431c81192..1e9b72b6f4d5 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java @@ -29,7 +29,7 @@ import android.os.IBinder; import android.util.proto.ProtoOutputStream; import android.view.Surface; -import com.android.server.biometrics.sensors.BaseClientMonitor; +import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; import com.android.server.biometrics.sensors.LockoutTracker; @@ -137,7 +137,7 @@ public interface ServiceProvider { void startPreparedClient(int sensorId, int cookie); void scheduleInternalCleanup(int sensorId, int userId, - @Nullable BaseClientMonitor.Callback callback); + @Nullable ClientMonitorCallback callback); void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto, boolean clearSchedulerBuffer); diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java index 66b942b085a4..89982691ae38 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java @@ -35,6 +35,7 @@ import android.util.Slog; import com.android.server.biometrics.HardwareAuthTokenUtils; import com.android.server.biometrics.Utils; import com.android.server.biometrics.sensors.BaseClientMonitor; +import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.face.FaceUtils; import java.util.HashSet; @@ -221,7 +222,7 @@ public class BiometricTestSessionImpl extends ITestSession.Stub { Utils.checkPermission(mContext, TEST_BIOMETRIC); Slog.d(TAG, "cleanupInternalState: " + userId); - mProvider.scheduleInternalCleanup(mSensorId, userId, new BaseClientMonitor.Callback() { + mProvider.scheduleInternalCleanup(mSensorId, userId, new ClientMonitorCallback() { @Override public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) { try { diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java index 4131ae127ab2..dc21a04fb446 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java @@ -39,7 +39,9 @@ import com.android.internal.R; import com.android.server.biometrics.Utils; import com.android.server.biometrics.sensors.AuthenticationClient; import com.android.server.biometrics.sensors.BiometricNotificationUtils; +import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; +import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback; import com.android.server.biometrics.sensors.LockoutCache; import com.android.server.biometrics.sensors.LockoutConsumer; import com.android.server.biometrics.sensors.LockoutTracker; @@ -97,15 +99,16 @@ class FaceAuthenticationClient extends AuthenticationClient<ISession> implements } @Override - public void start(@NonNull Callback callback) { + public void start(@NonNull ClientMonitorCallback callback) { super.start(callback); mState = STATE_STARTED; } @NonNull @Override - protected Callback wrapCallbackForStart(@NonNull Callback callback) { - return new CompositeCallback(createALSCallback(true /* startWithClient */), callback); + protected ClientMonitorCallback wrapCallbackForStart(@NonNull ClientMonitorCallback callback) { + return new ClientMonitorCompositeCallback( + getLogger().createALSCallback(true /* startWithClient */), callback); } @Override @@ -241,7 +244,8 @@ class FaceAuthenticationClient extends AuthenticationClient<ISession> implements mLockoutCache.setLockoutModeForUser(getTargetUserId(), LockoutTracker.LOCKOUT_TIMED); // Lockout metrics are logged as an error code. final int error = BiometricFaceConstants.FACE_ERROR_LOCKOUT; - logOnError(getContext(), error, 0 /* vendorCode */, getTargetUserId()); + getLogger().logOnError(getContext(), error, 0 /* vendorCode */, + isCryptoOperation(), getTargetUserId()); try { getListener().onError(getSensorId(), getCookie(), error, 0 /* vendorCode */); @@ -256,7 +260,8 @@ class FaceAuthenticationClient extends AuthenticationClient<ISession> implements mLockoutCache.setLockoutModeForUser(getTargetUserId(), LockoutTracker.LOCKOUT_PERMANENT); // Lockout metrics are logged as an error code. final int error = BiometricFaceConstants.FACE_ERROR_LOCKOUT_PERMANENT; - logOnError(getContext(), error, 0 /* vendorCode */, getTargetUserId()); + getLogger().logOnError(getContext(), error, 0 /* vendorCode */, + isCryptoOperation(), getTargetUserId()); try { getListener().onError(getSensorId(), getCookie(), error, 0 /* vendorCode */); diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java index 2158dfe7bde5..72a20db077dd 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java @@ -30,6 +30,7 @@ import android.util.Slog; import com.android.server.biometrics.BiometricsProto; import com.android.server.biometrics.sensors.AcquisitionClient; +import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; import com.android.server.biometrics.sensors.DetectionConsumer; @@ -58,7 +59,7 @@ public class FaceDetectClient extends AcquisitionClient<ISession> implements Det } @Override - public void start(@NonNull Callback callback) { + public void start(@NonNull ClientMonitorCallback callback) { super.start(callback); startHalOperation(); } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java index aae4fbe9b0d7..5c57dbbffedb 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java @@ -41,7 +41,9 @@ import com.android.server.biometrics.Utils; import com.android.server.biometrics.sensors.BaseClientMonitor; import com.android.server.biometrics.sensors.BiometricNotificationUtils; import com.android.server.biometrics.sensors.BiometricUtils; +import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; +import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback; import com.android.server.biometrics.sensors.EnrollClient; import com.android.server.biometrics.sensors.face.FaceService; import com.android.server.biometrics.sensors.face.FaceUtils; @@ -67,8 +69,8 @@ public class FaceEnrollClient extends EnrollClient<ISession> { private final int mMaxTemplatesPerUser; private final boolean mDebugConsent; - private final BaseClientMonitor.Callback mPreviewHandleDeleterCallback = - new BaseClientMonitor.Callback() { + private final ClientMonitorCallback mPreviewHandleDeleterCallback = + new ClientMonitorCallback() { @Override public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) { } @@ -101,7 +103,7 @@ public class FaceEnrollClient extends EnrollClient<ISession> { } @Override - public void start(@NonNull Callback callback) { + public void start(@NonNull ClientMonitorCallback callback) { super.start(callback); BiometricNotificationUtils.cancelReEnrollNotification(getContext()); @@ -109,9 +111,9 @@ public class FaceEnrollClient extends EnrollClient<ISession> { @NonNull @Override - protected Callback wrapCallbackForStart(@NonNull Callback callback) { - return new CompositeCallback(mPreviewHandleDeleterCallback, - createALSCallback(true /* startWithClient */), callback); + protected ClientMonitorCallback wrapCallbackForStart(@NonNull ClientMonitorCallback callback) { + return new ClientMonitorCompositeCallback(mPreviewHandleDeleterCallback, + getLogger().createALSCallback(true /* startWithClient */), callback); } @Override diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetAuthenticatorIdClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetAuthenticatorIdClient.java index af826c2f68ba..584b58cdd7c7 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetAuthenticatorIdClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetAuthenticatorIdClient.java @@ -24,6 +24,7 @@ import android.os.RemoteException; import android.util.Slog; import com.android.server.biometrics.BiometricsProto; +import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.HalClientMonitor; import java.util.Map; @@ -48,7 +49,7 @@ class FaceGetAuthenticatorIdClient extends HalClientMonitor<ISession> { // Nothing to do here } - public void start(@NonNull Callback callback) { + public void start(@NonNull ClientMonitorCallback callback) { super.start(callback); startHalOperation(); } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClient.java index 315ede8bc5df..acf5720cd0cf 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClient.java @@ -29,6 +29,7 @@ import android.provider.Settings; import android.util.Slog; import com.android.server.biometrics.BiometricsProto; +import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; import com.android.server.biometrics.sensors.ErrorConsumer; import com.android.server.biometrics.sensors.HalClientMonitor; @@ -60,7 +61,7 @@ public class FaceGetFeatureClient extends HalClientMonitor<ISession> implements } @Override - public void start(@NonNull Callback callback) { + public void start(@NonNull ClientMonitorCallback callback) { super.start(callback); startHalOperation(); } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java index ae507abea537..9d7a5529f473 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java @@ -49,6 +49,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.server.biometrics.Utils; import com.android.server.biometrics.sensors.AuthenticationClient; import com.android.server.biometrics.sensors.BaseClientMonitor; +import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; import com.android.server.biometrics.sensors.InvalidationRequesterClient; import com.android.server.biometrics.sensors.LockoutResetDispatcher; @@ -217,7 +218,7 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { } private void scheduleForSensor(int sensorId, @NonNull BaseClientMonitor client, - BaseClientMonitor.Callback callback) { + ClientMonitorCallback callback) { if (!mSensors.contains(sensorId)) { throw new IllegalStateException("Unable to schedule client: " + client + " for sensor: " + sensorId); @@ -341,7 +342,7 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { opPackageName, id, FaceUtils.getInstance(sensorId), disabledFeatures, ENROLL_TIMEOUT_SEC, previewSurface, sensorId, maxTemplatesPerUser, debugConsent); - scheduleForSensor(sensorId, client, new BaseClientMonitor.Callback() { + scheduleForSensor(sensorId, client, new ClientMonitorCallback() { @Override public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, boolean success) { @@ -511,7 +512,7 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { @Override public void scheduleInternalCleanup(int sensorId, int userId, - @Nullable BaseClientMonitor.Callback callback) { + @Nullable ClientMonitorCallback callback) { mHandler.post(() -> { final List<Face> enrolledList = getEnrolledFaces(sensorId, userId); final FaceInternalCleanupClient client = diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java index 1e1b532961df..fd44c5cf4afc 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java @@ -27,6 +27,7 @@ import android.util.Slog; import com.android.server.biometrics.BiometricsProto; import com.android.server.biometrics.HardwareAuthTokenUtils; +import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.ErrorConsumer; import com.android.server.biometrics.sensors.HalClientMonitor; import com.android.server.biometrics.sensors.LockoutCache; @@ -64,7 +65,7 @@ public class FaceResetLockoutClient extends HalClientMonitor<ISession> implement } @Override - public void start(@NonNull Callback callback) { + public void start(@NonNull ClientMonitorCallback callback) { super.start(callback); startHalOperation(); } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClient.java index 4515d0421a58..ee6982abd9ed 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClient.java @@ -28,6 +28,7 @@ import android.util.Slog; import com.android.server.biometrics.BiometricsProto; import com.android.server.biometrics.HardwareAuthTokenUtils; +import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; import com.android.server.biometrics.sensors.ErrorConsumer; import com.android.server.biometrics.sensors.HalClientMonitor; @@ -65,7 +66,7 @@ public class FaceSetFeatureClient extends HalClientMonitor<ISession> implements } @Override - public void start(@NonNull Callback callback) { + public void start(@NonNull ClientMonitorCallback callback) { super.start(callback); startHalOperation(); } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStartUserClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStartUserClient.java index 2b5f49546d69..4a3da0d929dc 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStartUserClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStartUserClient.java @@ -27,6 +27,7 @@ import android.os.IBinder; import android.os.RemoteException; import android.util.Slog; +import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.StartUserClient; public class FaceStartUserClient extends StartUserClient<IFace, ISession> { @@ -43,7 +44,7 @@ public class FaceStartUserClient extends StartUserClient<IFace, ISession> { } @Override - public void start(@NonNull Callback callback) { + public void start(@NonNull ClientMonitorCallback callback) { super.start(callback); startHalOperation(); } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStopUserClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStopUserClient.java index 06328e311b06..88b92359268c 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStopUserClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStopUserClient.java @@ -24,6 +24,7 @@ import android.os.IBinder; import android.os.RemoteException; import android.util.Slog; +import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.StopUserClient; public class FaceStopUserClient extends StopUserClient<ISession> { @@ -36,7 +37,7 @@ public class FaceStopUserClient extends StopUserClient<ISession> { } @Override - public void start(@NonNull Callback callback) { + public void start(@NonNull ClientMonitorCallback callback) { super.start(callback); startHalOperation(); } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/BiometricTestSessionImpl.java index b45578b4d447..e7483b3eeae3 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/BiometricTestSessionImpl.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/BiometricTestSessionImpl.java @@ -32,6 +32,7 @@ import android.util.Slog; import com.android.server.biometrics.Utils; import com.android.server.biometrics.sensors.BaseClientMonitor; +import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.face.FaceUtils; import java.util.ArrayList; @@ -197,7 +198,7 @@ public class BiometricTestSessionImpl extends ITestSession.Stub { public void cleanupInternalState(int userId) { Utils.checkPermission(mContext, TEST_BIOMETRIC); - mFace10.scheduleInternalCleanup(mSensorId, userId, new BaseClientMonitor.Callback() { + mFace10.scheduleInternalCleanup(mSensorId, userId, new ClientMonitorCallback() { @Override public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) { try { diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java index e957794372aa..9a52db19ecda 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java @@ -60,6 +60,7 @@ import com.android.server.biometrics.sensors.AuthenticationConsumer; import com.android.server.biometrics.sensors.BaseClientMonitor; import com.android.server.biometrics.sensors.BiometricNotificationUtils; import com.android.server.biometrics.sensors.BiometricScheduler; +import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; import com.android.server.biometrics.sensors.EnumerateConsumer; import com.android.server.biometrics.sensors.ErrorConsumer; @@ -534,7 +535,7 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId, opPackageName, mSensorId, sSystemClock.millis()); mGeneratedChallengeCache = client; - mScheduler.scheduleClientMonitor(client, new BaseClientMonitor.Callback() { + mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() { @Override public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) { if (client != clientMonitor) { @@ -562,7 +563,7 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { final FaceRevokeChallengeClient client = new FaceRevokeChallengeClient(mContext, mLazyDaemon, token, userId, opPackageName, mSensorId); - mScheduler.scheduleClientMonitor(client, new BaseClientMonitor.Callback() { + mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() { @Override public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, boolean success) { @@ -591,7 +592,7 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { opPackageName, id, FaceUtils.getLegacyInstance(mSensorId), disabledFeatures, ENROLL_TIMEOUT_SEC, previewSurface, mSensorId); - mScheduler.scheduleClientMonitor(client, new BaseClientMonitor.Callback() { + mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() { @Override public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, boolean success) { @@ -742,7 +743,7 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { final int faceId = faces.get(0).getBiometricId(); final FaceGetFeatureClient client = new FaceGetFeatureClient(mContext, mLazyDaemon, token, listener, userId, opPackageName, mSensorId, feature, faceId); - mScheduler.scheduleClientMonitor(client, new BaseClientMonitor.Callback() { + mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() { @Override public void onClientFinished( @NonNull BaseClientMonitor clientMonitor, boolean success) { @@ -760,7 +761,7 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { } private void scheduleInternalCleanup(int userId, - @Nullable BaseClientMonitor.Callback callback) { + @Nullable ClientMonitorCallback callback) { mHandler.post(() -> { scheduleUpdateActiveUserWithoutHandler(userId); @@ -774,7 +775,7 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { @Override public void scheduleInternalCleanup(int sensorId, int userId, - @Nullable BaseClientMonitor.Callback callback) { + @Nullable ClientMonitorCallback callback) { scheduleInternalCleanup(userId, callback); } @@ -890,7 +891,7 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { final FaceUpdateActiveUserClient client = new FaceUpdateActiveUserClient(mContext, mLazyDaemon, targetUserId, mContext.getOpPackageName(), mSensorId, hasEnrolled, mAuthenticatorIds); - mScheduler.scheduleClientMonitor(client, new BaseClientMonitor.Callback() { + mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() { @Override public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, boolean success) { diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java index 7548d2871a15..1e0e7992bf87 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java @@ -34,7 +34,9 @@ import com.android.internal.R; import com.android.server.biometrics.Utils; import com.android.server.biometrics.sensors.AuthenticationClient; import com.android.server.biometrics.sensors.BiometricNotificationUtils; +import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; +import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback; import com.android.server.biometrics.sensors.LockoutTracker; import com.android.server.biometrics.sensors.face.UsageStats; @@ -87,15 +89,16 @@ class FaceAuthenticationClient extends AuthenticationClient<IBiometricsFace> { } @Override - public void start(@NonNull Callback callback) { + public void start(@NonNull ClientMonitorCallback callback) { super.start(callback); mState = STATE_STARTED; } @NonNull @Override - protected Callback wrapCallbackForStart(@NonNull Callback callback) { - return new CompositeCallback(createALSCallback(true /* startWithClient */), callback); + protected ClientMonitorCallback wrapCallbackForStart(@NonNull ClientMonitorCallback callback) { + return new ClientMonitorCompositeCallback( + getLogger().createALSCallback(true /* startWithClient */), callback); } @Override diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java index 31e5c86103fb..8068e14cf0f0 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java @@ -33,7 +33,9 @@ import android.view.Surface; import com.android.internal.R; import com.android.server.biometrics.Utils; import com.android.server.biometrics.sensors.BiometricUtils; +import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; +import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback; import com.android.server.biometrics.sensors.EnrollClient; import java.util.ArrayList; @@ -69,8 +71,9 @@ public class FaceEnrollClient extends EnrollClient<IBiometricsFace> { @NonNull @Override - protected Callback wrapCallbackForStart(@NonNull Callback callback) { - return new CompositeCallback(createALSCallback(true /* startWithClient */), callback); + protected ClientMonitorCallback wrapCallbackForStart(@NonNull ClientMonitorCallback callback) { + return new ClientMonitorCompositeCallback( + getLogger().createALSCallback(true /* startWithClient */), callback); } @Override diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClient.java index f418104834e3..e29a1923e47e 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClient.java @@ -25,6 +25,7 @@ import android.os.RemoteException; import android.util.Slog; import com.android.internal.util.Preconditions; +import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; import com.android.server.biometrics.sensors.GenerateChallengeClient; @@ -39,7 +40,7 @@ public class FaceGenerateChallengeClient extends GenerateChallengeClient<IBiomet private static final String TAG = "FaceGenerateChallengeClient"; static final int CHALLENGE_TIMEOUT_SEC = 600; // 10 minutes - private static final Callback EMPTY_CALLBACK = new Callback() { + private static final ClientMonitorCallback EMPTY_CALLBACK = new ClientMonitorCallback() { }; private final long mCreatedAt; @@ -94,7 +95,7 @@ public class FaceGenerateChallengeClient extends GenerateChallengeClient<IBiomet } private void sendChallengeResult(@NonNull ClientMonitorCallbackConverter receiver, - @NonNull Callback ownerCallback) { + @NonNull ClientMonitorCallback ownerCallback) { Preconditions.checkState(mChallengeResult != null, "result not available"); try { receiver.onChallengeGenerated(getSensorId(), getTargetUserId(), mChallengeResult); diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGetFeatureClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGetFeatureClient.java index 7821601c8433..0a9d96d00eb6 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGetFeatureClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGetFeatureClient.java @@ -28,6 +28,7 @@ import android.os.RemoteException; import android.util.Slog; import com.android.server.biometrics.BiometricsProto; +import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; import com.android.server.biometrics.sensors.HalClientMonitor; @@ -66,7 +67,7 @@ public class FaceGetFeatureClient extends HalClientMonitor<IBiometricsFace> { } @Override - public void start(@NonNull Callback callback) { + public void start(@NonNull ClientMonitorCallback callback) { super.start(callback); startHalOperation(); } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceResetLockoutClient.java index 9d977d60e705..ee01c43a2e73 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceResetLockoutClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceResetLockoutClient.java @@ -24,6 +24,7 @@ import android.os.RemoteException; import android.util.Slog; import com.android.server.biometrics.BiometricsProto; +import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.HalClientMonitor; import java.util.ArrayList; @@ -57,7 +58,7 @@ public class FaceResetLockoutClient extends HalClientMonitor<IBiometricsFace> { } @Override - public void start(@NonNull Callback callback) { + public void start(@NonNull ClientMonitorCallback callback) { super.start(callback); startHalOperation(); } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceSetFeatureClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceSetFeatureClient.java index cc3d8f0e28ba..ee28f7b0f304 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceSetFeatureClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceSetFeatureClient.java @@ -26,6 +26,7 @@ import android.os.RemoteException; import android.util.Slog; import com.android.server.biometrics.BiometricsProto; +import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; import com.android.server.biometrics.sensors.HalClientMonitor; @@ -71,7 +72,7 @@ public class FaceSetFeatureClient extends HalClientMonitor<IBiometricsFace> { } @Override - public void start(@NonNull Callback callback) { + public void start(@NonNull ClientMonitorCallback callback) { super.start(callback); startHalOperation(); diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceUpdateActiveUserClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceUpdateActiveUserClient.java index 5343d0d17273..8ee8ce522035 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceUpdateActiveUserClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceUpdateActiveUserClient.java @@ -25,6 +25,7 @@ import android.os.RemoteException; import android.util.Slog; import com.android.server.biometrics.BiometricsProto; +import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.HalClientMonitor; import java.io.File; @@ -49,7 +50,7 @@ public class FaceUpdateActiveUserClient extends HalClientMonitor<IBiometricsFace } @Override - public void start(@NonNull Callback callback) { + public void start(@NonNull ClientMonitorCallback callback) { super.start(callback); startHalOperation(); } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintStateCallback.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintStateCallback.java index be0e6edb2a42..04fd534adb3b 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintStateCallback.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintStateCallback.java @@ -31,6 +31,7 @@ import android.util.Slog; import com.android.server.biometrics.Utils; import com.android.server.biometrics.sensors.AuthenticationClient; import com.android.server.biometrics.sensors.BaseClientMonitor; +import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.EnrollClient; import com.android.server.biometrics.sensors.EnrollmentModifier; @@ -39,7 +40,7 @@ import java.util.concurrent.CopyOnWriteArrayList; /** * A callback for receiving notifications about changes in fingerprint state. */ -public class FingerprintStateCallback implements BaseClientMonitor.Callback { +public class FingerprintStateCallback implements ClientMonitorCallback { @NonNull private final CopyOnWriteArrayList<IFingerprintStateListener> mFingerprintStateListeners = new CopyOnWriteArrayList<>(); diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java index 535705c63cab..0bdc4ebad66e 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java @@ -30,7 +30,7 @@ import android.hardware.fingerprint.IUdfpsOverlayController; import android.os.IBinder; import android.util.proto.ProtoOutputStream; -import com.android.server.biometrics.sensors.BaseClientMonitor; +import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; import com.android.server.biometrics.sensors.LockoutTracker; @@ -121,7 +121,7 @@ public interface ServiceProvider { @NonNull String opPackageName); void scheduleInternalCleanup(int sensorId, int userId, - @Nullable BaseClientMonitor.Callback callback); + @Nullable ClientMonitorCallback callback); boolean isHardwareDetected(int sensorId); diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java index 2b50b96c69a1..b29fbb66fa50 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java @@ -32,6 +32,7 @@ import android.util.Slog; import com.android.server.biometrics.HardwareAuthTokenUtils; import com.android.server.biometrics.Utils; import com.android.server.biometrics.sensors.BaseClientMonitor; +import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.fingerprint.FingerprintStateCallback; import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils; @@ -204,7 +205,7 @@ class BiometricTestSessionImpl extends ITestSession.Stub { Utils.checkPermission(mContext, TEST_BIOMETRIC); Slog.d(TAG, "cleanupInternalState: " + userId); - mProvider.scheduleInternalCleanup(mSensorId, userId, new BaseClientMonitor.Callback() { + mProvider.scheduleInternalCleanup(mSensorId, userId, new ClientMonitorCallback() { @Override public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) { try { diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java index e4d5fba3a471..f3d0121c98eb 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java @@ -33,9 +33,13 @@ import android.os.IBinder; import android.os.RemoteException; import android.util.Slog; +import com.android.server.biometrics.log.CallbackWithProbe; +import com.android.server.biometrics.log.Probe; import com.android.server.biometrics.sensors.AuthenticationClient; import com.android.server.biometrics.sensors.BiometricNotificationUtils; +import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; +import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback; import com.android.server.biometrics.sensors.LockoutCache; import com.android.server.biometrics.sensors.LockoutConsumer; import com.android.server.biometrics.sensors.LockoutTracker; @@ -80,11 +84,11 @@ class FingerprintAuthenticationClient extends AuthenticationClient<ISession> imp mLockoutCache = lockoutCache; mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController); mSensorProps = sensorProps; - mALSProbeCallback = createALSCallback(false /* startWithClient */); + mALSProbeCallback = getLogger().createALSCallback(false /* startWithClient */); } @Override - public void start(@NonNull Callback callback) { + public void start(@NonNull ClientMonitorCallback callback) { super.start(callback); if (mSensorProps.isAnyUdfpsType()) { @@ -97,8 +101,8 @@ class FingerprintAuthenticationClient extends AuthenticationClient<ISession> imp @NonNull @Override - protected Callback wrapCallbackForStart(@NonNull Callback callback) { - return new CompositeCallback(mALSProbeCallback, callback); + protected ClientMonitorCallback wrapCallbackForStart(@NonNull ClientMonitorCallback callback) { + return new ClientMonitorCompositeCallback(mALSProbeCallback, callback); } @Override @@ -233,7 +237,8 @@ class FingerprintAuthenticationClient extends AuthenticationClient<ISession> imp mLockoutCache.setLockoutModeForUser(getTargetUserId(), LockoutTracker.LOCKOUT_TIMED); // Lockout metrics are logged as an error code. final int error = BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT; - logOnError(getContext(), error, 0 /* vendorCode */, getTargetUserId()); + getLogger().logOnError(getContext(), error, 0 /* vendorCode */, + isCryptoOperation(), getTargetUserId()); try { getListener().onError(getSensorId(), getCookie(), error, 0 /* vendorCode */); @@ -251,7 +256,8 @@ class FingerprintAuthenticationClient extends AuthenticationClient<ISession> imp mLockoutCache.setLockoutModeForUser(getTargetUserId(), LockoutTracker.LOCKOUT_PERMANENT); // Lockout metrics are logged as an error code. final int error = BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT_PERMANENT; - logOnError(getContext(), error, 0 /* vendorCode */, getTargetUserId()); + getLogger().logOnError(getContext(), error, 0 /* vendorCode */, + isCryptoOperation(), getTargetUserId()); try { getListener().onError(getSensorId(), getCookie(), error, 0 /* vendorCode */); diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java index ac3ce896049b..1f0482db228b 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java @@ -30,6 +30,7 @@ import android.util.Slog; import com.android.server.biometrics.BiometricsProto; import com.android.server.biometrics.sensors.AcquisitionClient; +import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; import com.android.server.biometrics.sensors.DetectionConsumer; import com.android.server.biometrics.sensors.SensorOverlays; @@ -61,7 +62,7 @@ class FingerprintDetectClient extends AcquisitionClient<ISession> implements Det } @Override - public void start(@NonNull Callback callback) { + public void start(@NonNull ClientMonitorCallback callback) { super.start(callback); startHalOperation(); } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java index 67507ccbbbfe..169c3ebec1a7 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java @@ -37,7 +37,9 @@ import android.util.Slog; import com.android.server.biometrics.HardwareAuthTokenUtils; import com.android.server.biometrics.sensors.BiometricNotificationUtils; import com.android.server.biometrics.sensors.BiometricUtils; +import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; +import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback; import com.android.server.biometrics.sensors.EnrollClient; import com.android.server.biometrics.sensors.SensorOverlays; import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils; @@ -76,14 +78,15 @@ class FingerprintEnrollClient extends EnrollClient<ISession> implements Udfps { mEnrollReason = enrollReason; if (enrollReason == FingerprintManager.ENROLL_FIND_SENSOR) { - setShouldLog(false); + getLogger().disableMetrics(); } } @NonNull @Override - protected Callback wrapCallbackForStart(@NonNull Callback callback) { - return new CompositeCallback(createALSCallback(true /* startWithClient */), callback); + protected ClientMonitorCallback wrapCallbackForStart(@NonNull ClientMonitorCallback callback) { + return new ClientMonitorCompositeCallback( + getLogger().createALSCallback(true /* startWithClient */), callback); } @Override diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGetAuthenticatorIdClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGetAuthenticatorIdClient.java index ed2345e3362e..52bd234fcc5d 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGetAuthenticatorIdClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGetAuthenticatorIdClient.java @@ -24,6 +24,7 @@ import android.os.RemoteException; import android.util.Slog; import com.android.server.biometrics.BiometricsProto; +import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.HalClientMonitor; import java.util.Map; @@ -48,7 +49,7 @@ class FingerprintGetAuthenticatorIdClient extends HalClientMonitor<ISession> { // Nothing to do here } - public void start(@NonNull Callback callback) { + public void start(@NonNull ClientMonitorCallback callback) { super.start(callback); startHalOperation(); } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java index eb16c763dea6..efc93045f957 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java @@ -55,7 +55,9 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.server.biometrics.Utils; import com.android.server.biometrics.sensors.AuthenticationClient; import com.android.server.biometrics.sensors.BaseClientMonitor; +import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; +import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback; import com.android.server.biometrics.sensors.InvalidationRequesterClient; import com.android.server.biometrics.sensors.LockoutResetDispatcher; import com.android.server.biometrics.sensors.PerformanceTracker; @@ -248,7 +250,7 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi } private void scheduleForSensor(int sensorId, @NonNull BaseClientMonitor client, - BaseClientMonitor.Callback callback) { + ClientMonitorCallback callback) { if (!mSensors.contains(sensorId)) { throw new IllegalStateException("Unable to schedule client: " + client + " for sensor: " + sensorId); @@ -361,7 +363,7 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi opPackageName, FingerprintUtils.getInstance(sensorId), sensorId, mSensors.get(sensorId).getSensorProperties(), mUdfpsOverlayController, mSidefpsController, maxTemplatesPerUser, enrollReason); - scheduleForSensor(sensorId, client, new BaseClientMonitor.Callback() { + scheduleForSensor(sensorId, client, new ClientMonitorCallback() { @Override public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) { @@ -484,7 +486,7 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi @Override public void scheduleInternalCleanup(int sensorId, int userId, - @Nullable BaseClientMonitor.Callback callback) { + @Nullable ClientMonitorCallback callback) { mHandler.post(() -> { final List<Fingerprint> enrolledList = getEnrolledFingerprints(sensorId, userId); final FingerprintInternalCleanupClient client = @@ -493,7 +495,7 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi mContext.getOpPackageName(), sensorId, enrolledList, FingerprintUtils.getInstance(sensorId), mSensors.get(sensorId).getAuthenticatorIds()); - scheduleForSensor(sensorId, client, new BaseClientMonitor.CompositeCallback(callback, + scheduleForSensor(sensorId, client, new ClientMonitorCompositeCallback(callback, mFingerprintStateCallback)); }); } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java index 878ef46d2b2e..ee8d170af407 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java @@ -27,6 +27,7 @@ import android.util.Slog; import com.android.server.biometrics.BiometricsProto; import com.android.server.biometrics.HardwareAuthTokenUtils; +import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.ErrorConsumer; import com.android.server.biometrics.sensors.HalClientMonitor; import com.android.server.biometrics.sensors.LockoutCache; @@ -64,7 +65,7 @@ class FingerprintResetLockoutClient extends HalClientMonitor<ISession> implement } @Override - public void start(@NonNull Callback callback) { + public void start(@NonNull ClientMonitorCallback callback) { super.start(callback); startHalOperation(); } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStartUserClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStartUserClient.java index ee81620fdf77..9f11df6b939b 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStartUserClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStartUserClient.java @@ -27,6 +27,7 @@ import android.os.IBinder; import android.os.RemoteException; import android.util.Slog; +import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.StartUserClient; public class FingerprintStartUserClient extends StartUserClient<IFingerprint, ISession> { @@ -44,7 +45,7 @@ public class FingerprintStartUserClient extends StartUserClient<IFingerprint, IS } @Override - public void start(@NonNull Callback callback) { + public void start(@NonNull ClientMonitorCallback callback) { super.start(callback); startHalOperation(); } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStopUserClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStopUserClient.java index 7055d653dd16..9d381459566a 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStopUserClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStopUserClient.java @@ -24,6 +24,7 @@ import android.os.IBinder; import android.os.RemoteException; import android.util.Slog; +import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.StopUserClient; public class FingerprintStopUserClient extends StopUserClient<ISession> { @@ -36,7 +37,7 @@ public class FingerprintStopUserClient extends StopUserClient<ISession> { } @Override - public void start(@NonNull Callback callback) { + public void start(@NonNull ClientMonitorCallback callback) { super.start(callback); startHalOperation(); } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java index 79c6b1b30d5b..033855f822a4 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java @@ -31,6 +31,7 @@ import android.util.Slog; import com.android.server.biometrics.Utils; import com.android.server.biometrics.sensors.BaseClientMonitor; +import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.fingerprint.FingerprintStateCallback; import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils; @@ -201,7 +202,7 @@ public class BiometricTestSessionImpl extends ITestSession.Stub { public void cleanupInternalState(int userId) { Utils.checkPermission(mContext, TEST_BIOMETRIC); - mFingerprint21.scheduleInternalCleanup(mSensorId, userId, new BaseClientMonitor.Callback() { + mFingerprint21.scheduleInternalCleanup(mSensorId, userId, new ClientMonitorCallback() { @Override public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) { try { diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java index 6feb5fa418bb..f160dfff5249 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java @@ -62,7 +62,9 @@ import com.android.server.biometrics.sensors.AuthenticationClient; import com.android.server.biometrics.sensors.AuthenticationConsumer; import com.android.server.biometrics.sensors.BaseClientMonitor; import com.android.server.biometrics.sensors.BiometricScheduler; +import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; +import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback; import com.android.server.biometrics.sensors.EnumerateConsumer; import com.android.server.biometrics.sensors.ErrorConsumer; import com.android.server.biometrics.sensors.HalClientMonitor; @@ -492,7 +494,7 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider new FingerprintUpdateActiveUserClient(mContext, mLazyDaemon, targetUserId, mContext.getOpPackageName(), mSensorProperties.sensorId, this::getCurrentUser, hasEnrolled, mAuthenticatorIds, force); - mScheduler.scheduleClientMonitor(client, new BaseClientMonitor.Callback() { + mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() { @Override public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, boolean success) { @@ -577,7 +579,7 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider FingerprintUtils.getLegacyInstance(mSensorId), ENROLL_TIMEOUT_SEC, mSensorProperties.sensorId, mUdfpsOverlayController, mSidefpsController, enrollReason); - mScheduler.scheduleClientMonitor(client, new BaseClientMonitor.Callback() { + mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() { @Override public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) { mFingerprintStateCallback.onClientStarted(clientMonitor); @@ -699,7 +701,7 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider } private void scheduleInternalCleanup(int userId, - @Nullable BaseClientMonitor.Callback callback) { + @Nullable ClientMonitorCallback callback) { mHandler.post(() -> { scheduleUpdateActiveUserWithoutHandler(userId); @@ -715,8 +717,8 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider @Override public void scheduleInternalCleanup(int sensorId, int userId, - @Nullable BaseClientMonitor.Callback callback) { - scheduleInternalCleanup(userId, new BaseClientMonitor.CompositeCallback(callback, + @Nullable ClientMonitorCallback callback) { + scheduleInternalCleanup(userId, new ClientMonitorCompositeCallback(callback, mFingerprintStateCallback)); } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java index 273f8a545db5..1694bd92c73c 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java @@ -364,7 +364,7 @@ public class Fingerprint21UdfpsMock extends Fingerprint21 implements TrustManage final ClientMonitorCallbackConverter listener = client.getListener(); final String opPackageName = client.getOwnerString(); final boolean restricted = authClient.isRestricted(); - final int statsClient = client.getStatsClient(); + final int statsClient = client.getLogger().getStatsClient(); final boolean isKeyguard = authClient.isKeyguard(); // Don't actually send cancel() to the HAL, since successful auth already finishes @@ -414,7 +414,8 @@ public class Fingerprint21UdfpsMock extends Fingerprint21 implements TrustManage } @Override - public void onTrustChanged(boolean enabled, int userId, int flags) { + public void onTrustChanged(boolean enabled, int userId, int flags, + List<String> trustGrantedMessages) { mUserHasTrust.put(userId, enabled); } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java index 3058e2508f5f..87d47c1cffc5 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java @@ -32,9 +32,13 @@ import android.os.IBinder; import android.os.RemoteException; import android.util.Slog; +import com.android.server.biometrics.log.CallbackWithProbe; +import com.android.server.biometrics.log.Probe; import com.android.server.biometrics.sensors.AuthenticationClient; import com.android.server.biometrics.sensors.BiometricNotificationUtils; +import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; +import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback; import com.android.server.biometrics.sensors.LockoutTracker; import com.android.server.biometrics.sensors.SensorOverlays; import com.android.server.biometrics.sensors.fingerprint.Udfps; @@ -80,11 +84,11 @@ class FingerprintAuthenticationClient extends AuthenticationClient<IBiometricsFi mLockoutFrameworkImpl = lockoutTracker; mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController); mSensorProps = sensorProps; - mALSProbeCallback = createALSCallback(false /* startWithClient */); + mALSProbeCallback = getLogger().createALSCallback(false /* startWithClient */); } @Override - public void start(@NonNull Callback callback) { + public void start(@NonNull ClientMonitorCallback callback) { super.start(callback); if (mSensorProps.isAnyUdfpsType()) { @@ -97,8 +101,8 @@ class FingerprintAuthenticationClient extends AuthenticationClient<IBiometricsFi @NonNull @Override - protected Callback wrapCallbackForStart(@NonNull Callback callback) { - return new CompositeCallback(mALSProbeCallback, callback); + protected ClientMonitorCallback wrapCallbackForStart(@NonNull ClientMonitorCallback callback) { + return new ClientMonitorCompositeCallback(mALSProbeCallback, callback); } @Override diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java index b854fb300ece..9137212253e8 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java @@ -32,6 +32,7 @@ import android.util.Slog; import com.android.server.biometrics.BiometricsProto; import com.android.server.biometrics.sensors.AcquisitionClient; import com.android.server.biometrics.sensors.AuthenticationConsumer; +import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; import com.android.server.biometrics.sensors.PerformanceTracker; import com.android.server.biometrics.sensors.SensorOverlays; @@ -82,7 +83,7 @@ class FingerprintDetectClient extends AcquisitionClient<IBiometricsFingerprint> } @Override - public void start(@NonNull Callback callback) { + public void start(@NonNull ClientMonitorCallback callback) { super.start(callback); startHalOperation(); } @@ -127,8 +128,8 @@ class FingerprintDetectClient extends AcquisitionClient<IBiometricsFingerprint> @Override public void onAuthenticated(BiometricAuthenticator.Identifier identifier, boolean authenticated, ArrayList<Byte> hardwareAuthToken) { - logOnAuthenticated(getContext(), authenticated, false /* requireConfirmation */, - getTargetUserId(), false /* isBiometricPrompt */); + getLogger().logOnAuthenticated(getContext(), authenticated, false /* requireConfirmation */, + isCryptoOperation(), getTargetUserId(), false /* isBiometricPrompt */); // Do not distinguish between success/failures. vibrateSuccess(); diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java index cc50bdfb59ae..82b046d0ffd2 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java @@ -33,7 +33,9 @@ import android.util.Slog; import com.android.server.biometrics.sensors.BiometricNotificationUtils; import com.android.server.biometrics.sensors.BiometricUtils; +import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; +import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback; import com.android.server.biometrics.sensors.EnrollClient; import com.android.server.biometrics.sensors.SensorOverlays; import com.android.server.biometrics.sensors.fingerprint.Udfps; @@ -69,14 +71,15 @@ public class FingerprintEnrollClient extends EnrollClient<IBiometricsFingerprint mEnrollReason = enrollReason; if (enrollReason == FingerprintManager.ENROLL_FIND_SENSOR) { - setShouldLog(false); + getLogger().disableMetrics(); } } @NonNull @Override - protected Callback wrapCallbackForStart(@NonNull Callback callback) { - return new CompositeCallback(createALSCallback(true /* startWithClient */), callback); + protected ClientMonitorCallback wrapCallbackForStart(@NonNull ClientMonitorCallback callback) { + return new ClientMonitorCompositeCallback( + getLogger().createALSCallback(true /* startWithClient */), callback); } @Override diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintResetLockoutClient.java index a39f4f8c4d7e..ed28e3ff481e 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintResetLockoutClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintResetLockoutClient.java @@ -22,6 +22,7 @@ import android.hardware.biometrics.BiometricsProtoEnums; import com.android.server.biometrics.BiometricsProto; import com.android.server.biometrics.sensors.BaseClientMonitor; +import com.android.server.biometrics.sensors.ClientMonitorCallback; /** * Clears lockout, which is handled in the framework (and not the HAL) for the @@ -40,7 +41,7 @@ public class FingerprintResetLockoutClient extends BaseClientMonitor { } @Override - public void start(@NonNull Callback callback) { + public void start(@NonNull ClientMonitorCallback callback) { super.start(callback); mLockoutTracker.resetFailedAttemptsForUser(true /* clearAttemptCounter */, getTargetUserId()); diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java index a2c18923c00e..d317984c140d 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java @@ -27,6 +27,7 @@ import android.os.SELinux; import android.util.Slog; import com.android.server.biometrics.BiometricsProto; +import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.HalClientMonitor; import java.io.File; @@ -62,7 +63,7 @@ public class FingerprintUpdateActiveUserClient extends HalClientMonitor<IBiometr } @Override - public void start(@NonNull Callback callback) { + public void start(@NonNull ClientMonitorCallback callback) { super.start(callback); if (mCurrentUserId.get() == getTargetUserId() && !mForceUpdateAuthenticatorId) { diff --git a/services/core/java/com/android/server/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java index 3120dc58eebd..1e00ea9161a8 100644 --- a/services/core/java/com/android/server/camera/CameraServiceProxy.java +++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java @@ -36,6 +36,7 @@ import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.pm.ParceledListSlice; import android.content.res.Configuration; +import android.graphics.Rect; import android.hardware.CameraSessionStats; import android.hardware.CameraStreamStats; import android.hardware.ICameraService; @@ -46,6 +47,8 @@ import android.hardware.camera2.CaptureRequest; import android.hardware.devicestate.DeviceStateManager; import android.hardware.devicestate.DeviceStateManager.FoldStateListener; import android.hardware.display.DisplayManager; +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbManager; import android.media.AudioManager; import android.nfc.INfcAdapter; import android.os.Binder; @@ -303,6 +306,9 @@ public class CameraServiceProxy extends SystemService @Override public void onFixedRotationFinished(int displayId) { } + + @Override + public void onKeepClearAreasChanged(int displayId, List<Rect> keepClearArea) { } } @@ -335,6 +341,16 @@ public class CameraServiceProxy extends SystemService switchUserLocked(mLastUser); } break; + case UsbManager.ACTION_USB_DEVICE_ATTACHED: + case UsbManager.ACTION_USB_DEVICE_DETACHED: + synchronized (mLock) { + UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); + if (device != null) { + notifyUsbDeviceHotplugLocked(device, + action.equals(UsbManager.ACTION_USB_DEVICE_ATTACHED)); + } + } + break; default: break; // do nothing } @@ -645,6 +661,8 @@ public class CameraServiceProxy extends SystemService filter.addAction(Intent.ACTION_USER_INFO_CHANGED); filter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED); filter.addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED); + filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED); + filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED); mContext.registerReceiver(mIntentReceiver, filter); publishBinderService(CAMERA_SERVICE_PROXY_BINDER_NAME, mCameraServiceProxy); @@ -788,6 +806,7 @@ public class CameraServiceProxy extends SystemService streamProtos[i].histogramType = streamStats.getHistogramType(); streamProtos[i].histogramBins = streamStats.getHistogramBins(); streamProtos[i].histogramCounts = streamStats.getHistogramCounts(); + streamProtos[i].dynamicRangeProfile = streamStats.getDynamicRangeProfile(); if (CameraServiceProxy.DEBUG) { String histogramTypeName = @@ -807,7 +826,8 @@ public class CameraServiceProxy extends SystemService + ", histogramBins " + Arrays.toString(streamProtos[i].histogramBins) + ", histogramCounts " - + Arrays.toString(streamProtos[i].histogramCounts)); + + Arrays.toString(streamProtos[i].histogramCounts) + + ", dynamicRangeProfile " + streamProtos[i].dynamicRangeProfile); } } } @@ -961,6 +981,32 @@ public class CameraServiceProxy extends SystemService return true; } + private boolean notifyUsbDeviceHotplugLocked(@NonNull UsbDevice device, boolean attached) { + // Only handle external USB camera devices + if (device.getHasVideoCapture()) { + // Forward the usb hotplug event to the native camera service running in the + // cameraserver + // process. + ICameraService cameraService = getCameraServiceRawLocked(); + if (cameraService == null) { + Slog.w(TAG, "Could not notify cameraserver, camera service not available."); + return false; + } + + try { + int eventType = attached ? ICameraService.EVENT_USB_DEVICE_ATTACHED + : ICameraService.EVENT_USB_DEVICE_DETACHED; + mCameraServiceRaw.notifySystemEvent(eventType, new int[]{device.getDeviceId()}); + } catch (RemoteException e) { + Slog.w(TAG, "Could not notify cameraserver, remote exception: " + e); + // Not much we can do if camera service is dead. + return false; + } + return true; + } + return false; + } + private void updateActivityCount(CameraSessionStats cameraState) { String cameraId = cameraState.getCameraId(); int newCameraState = cameraState.getNewCameraState(); diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java index 582dd7c89e10..5b76695ab0da 100644 --- a/services/core/java/com/android/server/clipboard/ClipboardService.java +++ b/services/core/java/com/android/server/clipboard/ClipboardService.java @@ -81,6 +81,7 @@ import com.android.internal.util.FrameworkStatsLog; import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.UiThread; +import com.android.server.companion.virtual.VirtualDeviceManagerInternal; import com.android.server.contentcapture.ContentCaptureManagerInternal; import com.android.server.uri.UriGrantsManagerInternal; import com.android.server.wm.WindowManagerInternal; @@ -127,6 +128,7 @@ public class ClipboardService extends SystemService { private final IUriGrantsManager mUgm; private final UriGrantsManagerInternal mUgmInternal; private final WindowManagerInternal mWm; + private final VirtualDeviceManagerInternal mVdm; private final IUserManager mUm; private final PackageManager mPm; private final AppOpsManager mAppOps; @@ -158,6 +160,8 @@ public class ClipboardService extends SystemService { mUgm = UriGrantsManager.getService(); mUgmInternal = LocalServices.getService(UriGrantsManagerInternal.class); mWm = LocalServices.getService(WindowManagerInternal.class); + // Can be null; not all products have CDM + VirtualDeviceManager + mVdm = LocalServices.getService(VirtualDeviceManagerInternal.class); mPm = getContext().getPackageManager(); mUm = (IUserManager) ServiceManager.getService(Context.USER_SERVICE); mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE); @@ -973,6 +977,13 @@ public class ClipboardService extends SystemService { // First, verify package ownership to ensure use below is safe. mAppOps.checkPackage(uid, callingPackage); + // Nothing in a virtual session is permitted to touch clipboard contents + if (mVdm != null && mVdm.isAppRunningOnAnyVirtualDevice(uid)) { + Slog.w(TAG, "Clipboard access denied to " + uid + "/" + callingPackage + + " within a virtual device session"); + return false; + } + // Shell can access the clipboard for testing purposes. if (mPm.checkPermission(android.Manifest.permission.READ_CLIPBOARD_IN_BACKGROUND, callingPackage) == PackageManager.PERMISSION_GRANTED) { diff --git a/services/core/java/com/android/server/display/DisplayDeviceInfo.java b/services/core/java/com/android/server/display/DisplayDeviceInfo.java index fd4cd8e6ec88..35e3db7832f1 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceInfo.java +++ b/services/core/java/com/android/server/display/DisplayDeviceInfo.java @@ -358,6 +358,12 @@ final class DisplayDeviceInfo { public float brightnessMaximum; public float brightnessDefault; + /** + * Install orientation of display panel relative to its natural orientation. + */ + @Surface.Rotation + public int installOrientation = Surface.ROTATION_0; + public void setAssumedDensityForExternalDisplay(int width, int height) { densityDpi = Math.min(width, height) * DisplayMetrics.DENSITY_XHIGH / 1080; // Technically, these values should be smaller than the apparent density @@ -417,7 +423,8 @@ final class DisplayDeviceInfo { || !BrightnessSynchronizer.floatEquals(brightnessMaximum, other.brightnessMaximum) || !BrightnessSynchronizer.floatEquals(brightnessDefault, other.brightnessDefault) - || !Objects.equals(roundedCorners, other.roundedCorners)) { + || !Objects.equals(roundedCorners, other.roundedCorners) + || installOrientation != other.installOrientation) { diff |= DIFF_OTHER; } return diff; @@ -461,6 +468,7 @@ final class DisplayDeviceInfo { brightnessMaximum = other.brightnessMaximum; brightnessDefault = other.brightnessDefault; roundedCorners = other.roundedCorners; + installOrientation = other.installOrientation; } // For debugging purposes @@ -508,6 +516,7 @@ final class DisplayDeviceInfo { sb.append(", roundedCorners ").append(roundedCorners); } sb.append(flagsToString(flags)); + sb.append(", installOrientation ").append(installOrientation); sb.append("}"); return sb.toString(); } diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index 34f915e41cd7..c6d382923169 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -50,8 +50,6 @@ import android.os.UserHandle; import android.provider.Settings; import android.util.Log; import android.util.MathUtils; -import android.util.MutableFloat; -import android.util.MutableInt; import android.util.Slog; import android.util.TimeUtils; import android.view.Display; @@ -1382,7 +1380,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call // Animate the screen brightness when the screen is on or dozing. // Skip the animation when the screen is off or suspended or transition to/from VR. - boolean brightnessAdjusted = false; if (!mPendingScreenOff) { if (mSkipScreenOnBrightnessRamp) { if (state == Display.STATE_ON) { @@ -1475,19 +1472,15 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call // slider event so notify as if the system changed the brightness. userInitiatedChange = false; } - notifyBrightnessTrackerChanged(brightnessState, userInitiatedChange, + notifyBrightnessChanged(brightnessState, userInitiatedChange, hadUserBrightnessPoint); } // We save the brightness info *after* the brightness setting has been changed and // adjustments made so that the brightness info reflects the latest value. - brightnessAdjusted = saveBrightnessInfo(getScreenBrightnessSetting(), animateValue); + saveBrightnessInfo(getScreenBrightnessSetting(), animateValue); } else { - brightnessAdjusted = saveBrightnessInfo(getScreenBrightnessSetting()); - } - - if (brightnessAdjusted) { - postBrightnessChangeRunnable(); + saveBrightnessInfo(getScreenBrightnessSetting()); } // Log any changes to what is currently driving the brightness setting. @@ -1603,50 +1596,31 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call public BrightnessInfo getBrightnessInfo() { synchronized (mCachedBrightnessInfo) { return new BrightnessInfo( - mCachedBrightnessInfo.brightness.value, - mCachedBrightnessInfo.adjustedBrightness.value, - mCachedBrightnessInfo.brightnessMin.value, - mCachedBrightnessInfo.brightnessMax.value, - mCachedBrightnessInfo.hbmMode.value, - mCachedBrightnessInfo.hbmTransitionPoint.value); + mCachedBrightnessInfo.brightness, + mCachedBrightnessInfo.adjustedBrightness, + mCachedBrightnessInfo.brightnessMin, + mCachedBrightnessInfo.brightnessMax, + mCachedBrightnessInfo.hbmMode, + mCachedBrightnessInfo.highBrightnessTransitionPoint); } } - private boolean saveBrightnessInfo(float brightness) { - return saveBrightnessInfo(brightness, brightness); + private void saveBrightnessInfo(float brightness) { + saveBrightnessInfo(brightness, brightness); } - private boolean saveBrightnessInfo(float brightness, float adjustedBrightness) { + private void saveBrightnessInfo(float brightness, float adjustedBrightness) { synchronized (mCachedBrightnessInfo) { - boolean changed = false; - - changed |= - mCachedBrightnessInfo.checkAndSetFloat(mCachedBrightnessInfo.brightness, - brightness); - changed |= - mCachedBrightnessInfo.checkAndSetFloat(mCachedBrightnessInfo.adjustedBrightness, - adjustedBrightness); - changed |= - mCachedBrightnessInfo.checkAndSetFloat(mCachedBrightnessInfo.brightnessMin, - mHbmController.getCurrentBrightnessMin()); - changed |= - mCachedBrightnessInfo.checkAndSetFloat(mCachedBrightnessInfo.brightnessMax, - mHbmController.getCurrentBrightnessMax()); - changed |= - mCachedBrightnessInfo.checkAndSetInt(mCachedBrightnessInfo.hbmMode, - mHbmController.getHighBrightnessMode()); - changed |= - mCachedBrightnessInfo.checkAndSetFloat(mCachedBrightnessInfo.hbmTransitionPoint, - mHbmController.getTransitionPoint()); - - return changed; + mCachedBrightnessInfo.brightness = brightness; + mCachedBrightnessInfo.adjustedBrightness = adjustedBrightness; + mCachedBrightnessInfo.brightnessMin = mHbmController.getCurrentBrightnessMin(); + mCachedBrightnessInfo.brightnessMax = mHbmController.getCurrentBrightnessMax(); + mCachedBrightnessInfo.hbmMode = mHbmController.getHighBrightnessMode(); + mCachedBrightnessInfo.highBrightnessTransitionPoint = + mHbmController.getTransitionPoint(); } } - void postBrightnessChangeRunnable() { - mHandler.post(mOnBrightnessChangeRunnable); - } - private HighBrightnessModeController createHbmControllerLocked() { final DisplayDevice device = mLogicalDisplay.getPrimaryDisplayDeviceLocked(); final DisplayDeviceConfig ddConfig = device.getDisplayDeviceConfig(); @@ -1661,7 +1635,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call displayUniqueId, PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX, hbmData, () -> { sendUpdatePowerStateLocked(); - postBrightnessChangeRunnable(); + mHandler.post(mOnBrightnessChangeRunnable); // TODO(b/192258832): Switch the HBMChangeCallback to a listener pattern. if (mAutomaticBrightnessController != null) { mAutomaticBrightnessController.update(); @@ -2163,7 +2137,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call private void setCurrentScreenBrightness(float brightnessValue) { if (brightnessValue != mCurrentScreenBrightnessSetting) { mCurrentScreenBrightnessSetting = brightnessValue; - postBrightnessChangeRunnable(); + mHandler.post(mOnBrightnessChangeRunnable); } } @@ -2215,7 +2189,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call return true; } - private void notifyBrightnessTrackerChanged(float brightness, boolean userInitiated, + private void notifyBrightnessChanged(float brightness, boolean userInitiated, boolean hadUserDataPoint) { final float brightnessInNits = convertToNits(brightness); if (mPowerRequest.useAutoBrightness && brightnessInNits >= 0.0f @@ -2325,17 +2299,16 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call pw.println(" mColorFadeFadesConfig=" + mColorFadeFadesConfig); pw.println(" mColorFadeEnabled=" + mColorFadeEnabled); synchronized (mCachedBrightnessInfo) { - pw.println(" mCachedBrightnessInfo.brightness=" + - mCachedBrightnessInfo.brightness.value); + pw.println(" mCachedBrightnessInfo.brightness=" + mCachedBrightnessInfo.brightness); pw.println(" mCachedBrightnessInfo.adjustedBrightness=" + - mCachedBrightnessInfo.adjustedBrightness.value); + mCachedBrightnessInfo.adjustedBrightness); pw.println(" mCachedBrightnessInfo.brightnessMin=" + - mCachedBrightnessInfo.brightnessMin.value); + mCachedBrightnessInfo.brightnessMin); pw.println(" mCachedBrightnessInfo.brightnessMax=" + - mCachedBrightnessInfo.brightnessMax.value); - pw.println(" mCachedBrightnessInfo.hbmMode=" + mCachedBrightnessInfo.hbmMode.value); - pw.println(" mCachedBrightnessInfo.hbmTransitionPoint=" + - mCachedBrightnessInfo.hbmTransitionPoint.value); + mCachedBrightnessInfo.brightnessMax); + pw.println(" mCachedBrightnessInfo.hbmMode=" + mCachedBrightnessInfo.hbmMode); + pw.println(" mCachedBrightnessInfo.highBrightnessTransitionPoint=" + + mCachedBrightnessInfo.highBrightnessTransitionPoint); } pw.println(" mDisplayBlanksAfterDozeConfig=" + mDisplayBlanksAfterDozeConfig); pw.println(" mBrightnessBucketsInDozeConfig=" + mBrightnessBucketsInDozeConfig); @@ -2493,10 +2466,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call private void reportStats(float brightness) { float hbmTransitionPoint = PowerManager.BRIGHTNESS_MAX; synchronized(mCachedBrightnessInfo) { - if (mCachedBrightnessInfo.hbmTransitionPoint == null) { - return; - } - hbmTransitionPoint = mCachedBrightnessInfo.hbmTransitionPoint.value; + hbmTransitionPoint = mCachedBrightnessInfo.highBrightnessTransitionPoint; } final boolean aboveTransition = brightness > hbmTransitionPoint; @@ -2793,31 +2763,11 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call } static class CachedBrightnessInfo { - public MutableFloat brightness = new MutableFloat(PowerManager.BRIGHTNESS_INVALID_FLOAT); - public MutableFloat adjustedBrightness = - new MutableFloat(PowerManager.BRIGHTNESS_INVALID_FLOAT); - public MutableFloat brightnessMin = - new MutableFloat(PowerManager.BRIGHTNESS_INVALID_FLOAT); - public MutableFloat brightnessMax = - new MutableFloat(PowerManager.BRIGHTNESS_INVALID_FLOAT); - public MutableInt hbmMode = new MutableInt(BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF); - public MutableFloat hbmTransitionPoint = - new MutableFloat(HighBrightnessModeController.HBM_TRANSITION_POINT_INVALID); - - public boolean checkAndSetFloat(MutableFloat mf, float f) { - if (mf.value != f) { - mf.value = f; - return true; - } - return false; - } - - public boolean checkAndSetInt(MutableInt mi, int i) { - if (mi.value != i) { - mi.value = i; - return true; - } - return false; - } + public float brightness; + public float adjustedBrightness; + public float brightnessMin; + public float brightnessMax; + public int hbmMode; + public float highBrightnessTransitionPoint; } } diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java index 84de8229f37b..3a9ef0a83f6b 100644 --- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java +++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java @@ -641,6 +641,7 @@ final class LocalDisplayAdapter extends DisplayAdapter { mInfo.roundedCorners = RoundedCorners.fromResources( res, mInfo.uniqueId, mInfo.width, mInfo.height); + mInfo.installOrientation = mStaticDisplayInfo.installOrientation; if (mStaticDisplayInfo.isInternal) { mInfo.type = Display.TYPE_INTERNAL; diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java index 4d1367a3d083..e3ecf498fbb0 100644 --- a/services/core/java/com/android/server/display/LogicalDisplay.java +++ b/services/core/java/com/android/server/display/LogicalDisplay.java @@ -429,6 +429,7 @@ final class LogicalDisplay { mBaseDisplayInfo.brightnessMaximum = deviceInfo.brightnessMaximum; mBaseDisplayInfo.brightnessDefault = deviceInfo.brightnessDefault; mBaseDisplayInfo.roundedCorners = deviceInfo.roundedCorners; + mBaseDisplayInfo.installOrientation = deviceInfo.installOrientation; mPrimaryDisplayDeviceInfo = deviceInfo; mInfo.set(null); } diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java index 7719dfec928b..93c73be5021e 100644 --- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java +++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java @@ -397,12 +397,16 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { // We already told the displays to turn off, now we need to wake the device as // we transition to this new state. We do it here so that the waking happens // between the transition from one layout to another. - mPowerManager.wakeUp(SystemClock.uptimeMillis(), - PowerManager.WAKE_REASON_UNFOLD_DEVICE, "server.display:unfold"); + mHandler.post(() -> { + mPowerManager.wakeUp(SystemClock.uptimeMillis(), + PowerManager.WAKE_REASON_UNFOLD_DEVICE, "server.display:unfold"); + }); } else if (sleepDevice) { // Send the device to sleep when required. - mPowerManager.goToSleep(SystemClock.uptimeMillis(), - PowerManager.GO_TO_SLEEP_REASON_DEVICE_FOLD, 0); + mHandler.post(() -> { + mPowerManager.goToSleep(SystemClock.uptimeMillis(), + PowerManager.GO_TO_SLEEP_REASON_DEVICE_FOLD, 0); + }); } } diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index 261aa32f093e..2dd7a10ffe29 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -16,6 +16,8 @@ package com.android.server.input; +import static android.view.KeyEvent.KEYCODE_UNKNOWN; + import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Notification; @@ -38,6 +40,7 @@ import android.content.res.Resources.NotFoundException; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; import android.database.ContentObserver; +import android.graphics.PointF; import android.graphics.Rect; import android.hardware.display.DisplayManager; import android.hardware.display.DisplayViewport; @@ -269,6 +272,10 @@ public class InputManagerService extends IInputManager.Stub private final Map<String, Integer> mRuntimeAssociations = new ArrayMap<String, Integer>(); @GuardedBy("mAssociationLock") private final Map<String, String> mUniqueIdAssociations = new ArrayMap<>(); + private final Object mPointerDisplayIdLock = new Object(); + // Forces the MouseCursorController to target a specific display id. + @GuardedBy("mPointerDisplayIdLock") + private int mOverriddenPointerDisplayId = Display.INVALID_DISPLAY; private static native long nativeInit(InputManagerService service, Context context, MessageQueue messageQueue); @@ -284,6 +291,8 @@ public class InputManagerService extends IInputManager.Stub int deviceId, int sourceMask, int sw); private static native boolean nativeHasKeys(long ptr, int deviceId, int sourceMask, int[] keyCodes, boolean[] keyExists); + private static native int nativeGetKeyCodeForKeyLocation(long ptr, int deviceId, + int locationKeyCode); private static native InputChannel nativeCreateInputChannel(long ptr, String name); private static native InputChannel nativeCreateInputMonitor(long ptr, int displayId, boolean isGestureMonitor, String name, int pid); @@ -341,6 +350,9 @@ public class InputManagerService extends IInputManager.Stub private static native boolean nativeCanDispatchToDisplay(long ptr, int deviceId, int displayId); private static native void nativeNotifyPortAssociationsChanged(long ptr); private static native void nativeChangeUniqueIdAssociation(long ptr); + private static native void nativeNotifyPointerDisplayIdChanged(long ptr); + private static native void nativeSetDisplayEligibilityForPointerCapture(long ptr, int displayId, + boolean enabled); private static native void nativeSetMotionClassifierEnabled(long ptr, boolean enabled); private static native InputSensorInfo[] nativeGetSensorList(long ptr, int deviceId); private static native boolean nativeFlushSensor(long ptr, int deviceId, int sensorType); @@ -658,6 +670,22 @@ public class InputManagerService extends IInputManager.Stub } /** + * Returns the keyCode generated by the specified location on a US keyboard layout. + * This takes into consideration the currently active keyboard layout. + * + * @param deviceId The input device id. + * @param locationKeyCode The location of a key on a US keyboard layout. + * @return The KeyCode this physical key location produces. + */ + @Override // Binder call + public int getKeyCodeForKeyLocation(int deviceId, int locationKeyCode) { + if (locationKeyCode <= KEYCODE_UNKNOWN || locationKeyCode > KeyEvent.getMaxKeyCode()) { + return KEYCODE_UNKNOWN; + } + return nativeGetKeyCodeForKeyLocation(mPtr, deviceId, locationKeyCode); + } + + /** * Transfer the current touch gesture to the provided window. * * @param destChannelToken The token of the window or input channel that should receive the @@ -1902,6 +1930,18 @@ public class InputManagerService extends IInputManager.Stub return result; } + private void setVirtualMousePointerDisplayId(int displayId) { + synchronized (mPointerDisplayIdLock) { + mOverriddenPointerDisplayId = displayId; + } + // TODO(b/215597605): trigger MousePositionTracker update + nativeNotifyPointerDisplayIdChanged(mPtr); + } + + private void setDisplayEligibilityForPointerCapture(int displayId, boolean isEligible) { + nativeSetDisplayEligibilityForPointerCapture(mPtr, displayId, isEligible); + } + private static class VibrationInfo { private final long[] mPattern; private final int[] mAmplitudes; @@ -2575,6 +2615,7 @@ public class InputManagerService extends IInputManager.Stub synchronized (mInputFilterLock) { } synchronized (mAssociationsLock) { /* Test if blocked by associations lock. */} synchronized (mLidSwitchLock) { /* Test if blocked by lid switch lock. */ } + synchronized (mPointerDisplayIdLock) { /* Test if blocked by pointer display id lock */ } nativeMonitor(mPtr); } @@ -2965,6 +3006,12 @@ public class InputManagerService extends IInputManager.Stub // Native callback. private int getPointerDisplayId() { + synchronized (mPointerDisplayIdLock) { + // Prefer the override to all other displays. + if (mOverriddenPointerDisplayId != Display.INVALID_DISPLAY) { + return mOverriddenPointerDisplayId; + } + } return mWindowManagerCallbacks.getPointerDisplayId(); } @@ -3109,6 +3156,9 @@ public class InputManagerService extends IInputManager.Stub int getPointerDisplayId(); + /** Gets the x and y coordinates of the cursor's current position. */ + PointF getCursorPosition(); + /** * Notifies window manager that a {@link android.view.MotionEvent#ACTION_DOWN} pointer event * occurred on a window that did not have focus. @@ -3427,6 +3477,21 @@ public class InputManagerService extends IInputManager.Stub } @Override + public void setVirtualMousePointerDisplayId(int pointerDisplayId) { + InputManagerService.this.setVirtualMousePointerDisplayId(pointerDisplayId); + } + + @Override + public PointF getCursorPosition() { + return mWindowManagerCallbacks.getCursorPosition(); + } + + @Override + public void setDisplayEligibilityForPointerCapture(int displayId, boolean isEligible) { + InputManagerService.this.setDisplayEligibilityForPointerCapture(displayId, isEligible); + } + + @Override public void registerLidSwitchCallback(LidSwitchCallback callbacks) { registerLidSwitchCallbackInternal(callbacks); } diff --git a/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java b/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java new file mode 100644 index 000000000000..c86ebd26d871 --- /dev/null +++ b/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java @@ -0,0 +1,224 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.inputmethod; + +import android.annotation.AnyThread; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Binder; +import android.os.DeadObjectException; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.ResultReceiver; +import android.util.Slog; +import android.view.InputChannel; +import android.view.MotionEvent; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputBinding; +import android.view.inputmethod.InputMethodSubtype; + +import com.android.internal.inputmethod.IInputMethodPrivilegedOperations; +import com.android.internal.view.IInlineSuggestionsRequestCallback; +import com.android.internal.view.IInputContext; +import com.android.internal.view.IInputMethod; +import com.android.internal.view.IInputMethodSession; +import com.android.internal.view.IInputSessionCallback; +import com.android.internal.view.InlineSuggestionsRequestInfo; + +import java.util.List; + +/** + * A wrapper class to invoke IPCs defined in {@link IInputMethod}. + */ +final class IInputMethodInvoker { + private static final String TAG = InputMethodManagerService.TAG; + private static final boolean DEBUG = InputMethodManagerService.DEBUG; + + @AnyThread + @Nullable + static IInputMethodInvoker create(@Nullable IInputMethod inputMethod) { + if (inputMethod == null) { + return null; + } + if (!Binder.isProxy(inputMethod)) { + // IInputMethodInvoker must be used only within the system_server and InputMethodService + // must not be running in the system_server. Therefore, "inputMethod" must be a Proxy. + throw new UnsupportedOperationException(inputMethod + " must have been a BinderProxy."); + } + return new IInputMethodInvoker(inputMethod); + } + + /** + * A simplified version of {@link android.os.Debug#getCaller()}. + * + * @return method name of the caller. + */ + @AnyThread + private static String getCallerMethodName() { + final StackTraceElement[] callStack = Thread.currentThread().getStackTrace(); + if (callStack.length <= 4) { + return "<bottom of call stack>"; + } + return callStack[4].getMethodName(); + } + + @AnyThread + private static void logRemoteException(@NonNull RemoteException e) { + if (DEBUG || !(e instanceof DeadObjectException)) { + Slog.w(TAG, "IPC failed at IInputMethodInvoker#" + getCallerMethodName(), e); + } + } + + @AnyThread + static int getBinderIdentityHashCode(@Nullable IInputMethodInvoker obj) { + if (obj == null) { + return 0; + } + + return System.identityHashCode(obj.mTarget); + } + + @NonNull + private final IInputMethod mTarget; + + private IInputMethodInvoker(@NonNull IInputMethod target) { + mTarget = target; + } + + @AnyThread + @NonNull + IBinder asBinder() { + return mTarget.asBinder(); + } + + @AnyThread + void initializeInternal(IBinder token, IInputMethodPrivilegedOperations privOps, + int configChanges, boolean stylusHwSupported) { + try { + mTarget.initializeInternal(token, privOps, configChanges, stylusHwSupported); + } catch (RemoteException e) { + logRemoteException(e); + } + } + + @AnyThread + void onCreateInlineSuggestionsRequest(InlineSuggestionsRequestInfo requestInfo, + IInlineSuggestionsRequestCallback cb) { + try { + mTarget.onCreateInlineSuggestionsRequest(requestInfo, cb); + } catch (RemoteException e) { + logRemoteException(e); + } + } + + @AnyThread + void bindInput(InputBinding binding) { + try { + mTarget.bindInput(binding); + } catch (RemoteException e) { + logRemoteException(e); + } + } + + @AnyThread + void unbindInput() { + try { + mTarget.unbindInput(); + } catch (RemoteException e) { + logRemoteException(e); + } + } + + @AnyThread + void startInput(IBinder startInputToken, IInputContext inputContext, EditorInfo attribute, + boolean restarting) { + try { + mTarget.startInput(startInputToken, inputContext, attribute, restarting); + } catch (RemoteException e) { + logRemoteException(e); + } + } + + @AnyThread + void createSession(InputChannel channel, IInputSessionCallback callback) { + try { + mTarget.createSession(channel, callback); + } catch (RemoteException e) { + logRemoteException(e); + } + } + + @AnyThread + void setSessionEnabled(IInputMethodSession session, boolean enabled) { + try { + mTarget.setSessionEnabled(session, enabled); + } catch (RemoteException e) { + logRemoteException(e); + } + } + + // TODO(b/192412909): Convert this back to void method + @AnyThread + boolean showSoftInput(IBinder showInputToken, int flags, ResultReceiver resultReceiver) { + try { + mTarget.showSoftInput(showInputToken, flags, resultReceiver); + } catch (RemoteException e) { + logRemoteException(e); + return false; + } + return true; + } + + // TODO(b/192412909): Convert this back to void method + @AnyThread + boolean hideSoftInput(IBinder hideInputToken, int flags, ResultReceiver resultReceiver) { + try { + mTarget.hideSoftInput(hideInputToken, flags, resultReceiver); + } catch (RemoteException e) { + logRemoteException(e); + return false; + } + return true; + } + + @AnyThread + void changeInputMethodSubtype(InputMethodSubtype subtype) { + try { + mTarget.changeInputMethodSubtype(subtype); + } catch (RemoteException e) { + logRemoteException(e); + } + } + + @AnyThread + void canStartStylusHandwriting(int requestId) { + try { + mTarget.canStartStylusHandwriting(requestId); + } catch (RemoteException e) { + logRemoteException(e); + } + } + + @AnyThread + void startStylusHandwriting(InputChannel channel, List<MotionEvent> events) { + try { + mTarget.startStylusHandwriting(channel, events); + } catch (RemoteException e) { + logRemoteException(e); + } + } +} diff --git a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java index db13deba1972..2230dcde0869 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java @@ -75,7 +75,7 @@ final class InputMethodBindingController { @GuardedBy("ImfLock.class") @Nullable private String mCurId; @GuardedBy("ImfLock.class") @Nullable private String mSelectedMethodId; @GuardedBy("ImfLock.class") @Nullable private Intent mCurIntent; - @GuardedBy("ImfLock.class") @Nullable private IInputMethod mCurMethod; + @GuardedBy("ImfLock.class") @Nullable private IInputMethodInvoker mCurMethod; @GuardedBy("ImfLock.class") private int mCurMethodUid = Process.INVALID_UID; @GuardedBy("ImfLock.class") private IBinder mCurToken; @GuardedBy("ImfLock.class") private int mCurSeq; @@ -241,7 +241,7 @@ final class InputMethodBindingController { */ @GuardedBy("ImfLock.class") @Nullable - IInputMethod getCurMethod() { + IInputMethodInvoker getCurMethod() { return mCurMethod; } @@ -298,7 +298,7 @@ final class InputMethodBindingController { Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.onServiceConnected"); synchronized (ImfLock.class) { if (mCurIntent != null && name.equals(mCurIntent.getComponent())) { - mCurMethod = IInputMethod.Stub.asInterface(service); + mCurMethod = IInputMethodInvoker.create(IInputMethod.Stub.asInterface(service)); updateCurrentMethodUid(); if (mCurToken == null) { Slog.w(TAG, "Service connected without a token!"); @@ -309,8 +309,8 @@ final class InputMethodBindingController { if (DEBUG) Slog.v(TAG, "Initiating attach with token: " + mCurToken); final InputMethodInfo info = mMethodMap.get(mSelectedMethodId); mSupportsStylusHw = info.supportsStylusHandwriting(); - mService.executeOrSendInitializeIme(mCurMethod, mCurToken, - info.getConfigChanges(), mSupportsStylusHw); + mService.initializeImeLocked(mCurMethod, mCurToken, info.getConfigChanges(), + mSupportsStylusHw); mService.scheduleNotifyImeUidToAudioService(mCurMethodUid); mService.reRequestCurrentClientSessionLocked(); } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index ec49b4768b74..0d41a37caf0d 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -97,7 +97,6 @@ import android.os.Bundle; import android.os.Debug; import android.os.Handler; import android.os.IBinder; -import android.os.IInterface; import android.os.LocaleList; import android.os.Message; import android.os.Parcel; @@ -168,7 +167,6 @@ import com.android.internal.util.DumpUtils; import com.android.internal.view.IInlineSuggestionsRequestCallback; import com.android.internal.view.IInlineSuggestionsResponseCallback; import com.android.internal.view.IInputContext; -import com.android.internal.view.IInputMethod; import com.android.internal.view.IInputMethodClient; import com.android.internal.view.IInputMethodManager; import com.android.internal.view.IInputMethodSession; @@ -220,22 +218,11 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } private static final int MSG_SHOW_IM_SUBTYPE_PICKER = 1; - private static final int MSG_SHOW_IM_SUBTYPE_ENABLER = 2; - private static final int MSG_SHOW_IM_CONFIG = 3; - private static final int MSG_UNBIND_INPUT = 1000; - private static final int MSG_BIND_INPUT = 1010; - private static final int MSG_SHOW_SOFT_INPUT = 1020; - private static final int MSG_HIDE_SOFT_INPUT = 1030; private static final int MSG_HIDE_CURRENT_INPUT_METHOD = 1035; - private static final int MSG_INITIALIZE_IME = 1040; - private static final int MSG_CREATE_SESSION = 1050; private static final int MSG_REMOVE_IME_SURFACE = 1060; private static final int MSG_REMOVE_IME_SURFACE_FROM_WINDOW = 1061; private static final int MSG_UPDATE_IME_WINDOW_STATUS = 1070; - private static final int MSG_START_HANDWRITING = 1100; - - private static final int MSG_START_INPUT = 2000; private static final int MSG_UNBIND_CLIENT = 3000; private static final int MSG_BIND_CLIENT = 3010; @@ -248,8 +235,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub private static final int MSG_SYSTEM_UNLOCK_USER = 5000; private static final int MSG_DISPATCH_ON_INPUT_METHOD_LIST_UPDATED = 5010; - private static final int MSG_INLINE_SUGGESTIONS_REQUEST = 6000; - private static final int MSG_NOTIFY_IME_UID_TO_AUDIO_SERVICE = 7000; private static final int NOT_A_SUBTYPE_ID = InputMethodUtils.NOT_A_SUBTYPE_ID; @@ -343,7 +328,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub static class SessionState { final ClientState client; - final IInputMethod method; + final IInputMethodInvoker method; IInputMethodSession session; InputChannel channel; @@ -352,14 +337,14 @@ public class InputMethodManagerService extends IInputMethodManager.Stub public String toString() { return "SessionState{uid " + client.uid + " pid " + client.pid + " method " + Integer.toHexString( - System.identityHashCode(method)) + IInputMethodInvoker.getBinderIdentityHashCode(method)) + " session " + Integer.toHexString( System.identityHashCode(session)) + " channel " + channel + "}"; } - SessionState(ClientState _client, IInputMethod _method, + SessionState(ClientState _client, IInputMethodInvoker _method, IInputMethodSession _session, InputChannel _channel) { client = _client; method = _method; @@ -627,7 +612,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub */ @GuardedBy("ImfLock.class") @Nullable - private IInputMethod getCurMethodLocked() { + private IInputMethodInvoker getCurMethodLocked() { return mBindingController.getCurMethod(); } @@ -654,9 +639,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub boolean mBoundToMethod; /** - * Currently enabled session. Only touched by service thread, not - * protected by a lock. + * Currently enabled session. */ + @GuardedBy("ImfLock.class") SessionState mEnabledSession; /** @@ -705,11 +690,11 @@ public class InputMethodManagerService extends IInputMethodManager.Stub new CopyOnWriteArrayList<>(); /** - * Internal state snapshot when {@link #MSG_START_INPUT} message is about to be posted to the - * internal message queue. Any subsequent state change inside {@link InputMethodManagerService} - * will not affect those tasks that are already posted. + * Internal state snapshot when + * {@link com.android.internal.view.IInputMethod#startInput(IBinder, IInputContext, EditorInfo, + * boolean)} is about to be called. * - * <p>Posting {@link #MSG_START_INPUT} message basically means that + * <p>Calling that IPC endpoint basically means that * {@link InputMethodService#doStartInput(InputConnection, EditorInfo, boolean)} will be called * back in the current IME process shortly, which will also affect what the current IME starts * receiving from {@link InputMethodService#getCurrentInputConnection()}. In other words, this @@ -785,7 +770,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub final int mFocusedWindowSoftInputMode; @SoftInputShowHideReason final int mReason; - // The timing of handling MSG_SHOW_SOFT_INPUT or MSG_HIDE_SOFT_INPUT. + // The timing of handling showCurrentInputLocked() or hideCurrentInputLocked(). final long mTimestamp; final long mWallTime; final boolean mInFullscreenMode; @@ -1575,7 +1560,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub mHandler.removeCallbacks(mUserSwitchHandlerTask); } // Hide soft input before user switch task since switch task may block main handler a while - // and delayed the MSG_HIDE_SOFT_INPUT. + // and delayed the hideCurrentInputLocked(). hideCurrentInputLocked( mCurFocusedWindow, 0, null, SoftInputShowHideReason.HIDE_SWITCH_USER); final UserSwitchHandlerTask task = new UserSwitchHandlerTask(this, userId, @@ -1782,8 +1767,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub com.android.internal.R.bool.show_ongoing_ime_switcher); if (mShowOngoingImeSwitcherForPhones) { mWindowManagerInternal.setOnHardKeyboardStatusChangeListener(available -> { - mHandler.obtainMessage(MSG_HARD_KEYBOARD_SWITCH_CHANGED, available ? 1 : 0) - .sendToTarget(); + mHandler.obtainMessage(MSG_HARD_KEYBOARD_SWITCH_CHANGED, + available ? 1 : 0, 0 /* unused */).sendToTarget(); }); } @@ -1965,15 +1950,14 @@ public class InputMethodManagerService extends IInputMethodManager.Stub InlineSuggestionsRequestInfo requestInfo, IInlineSuggestionsRequestCallback callback) { final InputMethodInfo imi = mMethodMap.get(getSelectedMethodIdLocked()); try { - IInputMethod curMethod = getCurMethodLocked(); + IInputMethodInvoker curMethod = getCurMethodLocked(); if (userId == mSettings.getCurrentUserId() && imi != null && imi.isInlineSuggestionsEnabled() && curMethod != null) { - executeOrSendMessage(curMethod, - mCaller.obtainMessageOOO(MSG_INLINE_SUGGESTIONS_REQUEST, curMethod, - requestInfo, new InlineSuggestionsRequestCallbackDecorator(callback, - imi.getPackageName(), mCurTokenDisplayId, - getCurTokenLocked(), - this))); + final IInlineSuggestionsRequestCallback callbackImpl = + new InlineSuggestionsRequestCallbackDecorator(callback, + imi.getPackageName(), mCurTokenDisplayId, getCurTokenLocked(), + this); + curMethod.onCreateInlineSuggestionsRequest(requestInfo, callbackImpl); } else { callback.onInlineSuggestionsUnsupported(); } @@ -2201,10 +2185,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub mCurFocusedWindow, 0, null, SoftInputShowHideReason.HIDE_REMOVE_CLIENT); if (mBoundToMethod) { mBoundToMethod = false; - IInputMethod curMethod = getCurMethodLocked(); + IInputMethodInvoker curMethod = getCurMethodLocked(); if (curMethod != null) { - executeOrSendMessage(curMethod, mCaller.obtainMessageO( - MSG_UNBIND_INPUT, curMethod)); + curMethod.unbindInput(); } } mCurClient = null; @@ -2216,8 +2199,14 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } - private void executeOrSendMessage(IInterface target, Message msg) { + private void executeOrSendMessage(IInputMethodClient target, Message msg) { if (target.asBinder() instanceof Binder) { + // This is supposed to be emulating the one-way semantics when the IME client is + // system_server itself, which has not been explicitly prohibited so far while we have + // never ever officially supported such a use case... + // We probably should create a simple wrapper of IInputMethodClient as the first step + // to get rid of executeOrSendMessage() then should prohibit system_server to be the + // IME client for long term. mCaller.sendMessage(msg); } else { handleMessage(msg); @@ -2232,10 +2221,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub + mCurClient.client.asBinder()); if (mBoundToMethod) { mBoundToMethod = false; - IInputMethod curMethod = getCurMethodLocked(); + IInputMethodInvoker curMethod = getCurMethodLocked(); if (curMethod != null) { - executeOrSendMessage(curMethod, mCaller.obtainMessageO( - MSG_UNBIND_INPUT, curMethod)); + curMethod.unbindInput(); } } @@ -2284,16 +2272,15 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @NonNull InputBindResult attachNewInputLocked(@StartInputReason int startInputReason, boolean initial) { if (!mBoundToMethod) { - IInputMethod curMethod = getCurMethodLocked(); - executeOrSendMessage(curMethod, mCaller.obtainMessageOO( - MSG_BIND_INPUT, curMethod, mCurClient.binding)); + getCurMethodLocked().bindInput(mCurClient.binding); mBoundToMethod = true; } + final boolean restarting = !initial; final Binder startInputToken = new Binder(); final StartInputInfo info = new StartInputInfo(mSettings.getCurrentUserId(), getCurTokenLocked(), - mCurTokenDisplayId, getCurIdLocked(), startInputReason, !initial, + mCurTokenDisplayId, getCurIdLocked(), startInputReason, restarting, UserHandle.getUserId(mCurClient.uid), mCurClient.selfReportedDisplayId, mCurFocusedWindow, mCurAttribute, mCurFocusedWindowSoftInputMode, getSequenceNumberLocked()); @@ -2312,9 +2299,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } final SessionState session = mCurClient.curSession; - executeOrSendMessage(session.method, mCaller.obtainMessageIIOOOO( - MSG_START_INPUT, 0 /* unused */, initial ? 0 : 1 /* restarting */, - startInputToken, session, mCurInputContext, mCurAttribute)); + setEnabledSessionLocked(session); + session.method.startInput(startInputToken, mCurInputContext, mCurAttribute, restarting); + if (mShowRequested) { if (DEBUG) Slog.v(TAG, "Attach new input asks to show input"); showCurrentInputLocked(mCurFocusedWindow, getAppShowFlagsLocked(), null, @@ -2330,11 +2317,20 @@ public class InputMethodManagerService extends IInputMethodManager.Stub curId, getSequenceNumberLocked(), suppressesSpellChecker); } + /** + * Called by {@link #startInputOrWindowGainedFocusInternalLocked} to bind/unbind/attach the + * selected InputMethod to the given focused IME client. + * + * Note that this should be called after validating if the IME client has IME focus. + * + * @see WindowManagerInternal#hasInputMethodClientFocus(IBinder, int, int, int) + */ @GuardedBy("ImfLock.class") @NonNull - InputBindResult startInputUncheckedLocked(@NonNull ClientState cs, IInputContext inputContext, - @NonNull EditorInfo attribute, @StartInputFlags int startInputFlags, - @StartInputReason int startInputReason, int unverifiedTargetSdkVersion) { + private InputBindResult startInputUncheckedLocked(@NonNull ClientState cs, + IInputContext inputContext, @NonNull EditorInfo attribute, + @StartInputFlags int startInputFlags, @StartInputReason int startInputReason, + int unverifiedTargetSdkVersion) { // If no method is currently selected, do nothing. String selectedMethodId = getSelectedMethodIdLocked(); if (selectedMethodId == null) { @@ -2356,10 +2352,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub return InputBindResult.INVALID_PACKAGE_NAME; } - if (!mWindowManagerInternal.isUidAllowedOnDisplay(cs.selfReportedDisplayId, cs.uid)) { - // Wait, the client no longer has access to the display. - return InputBindResult.INVALID_DISPLAY_ID; - } // Compute the final shown display ID with validated cs.selfReportedDisplayId for this // session & other conditions. mDisplayIdToShowIme = computeImeDisplayIdForTarget(cs.selfReportedDisplayId, @@ -2501,11 +2493,15 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } - @AnyThread - void executeOrSendInitializeIme(@NonNull IInputMethod inputMethod, @NonNull IBinder token, + @GuardedBy("ImfLock.class") + void initializeImeLocked(@NonNull IInputMethodInvoker inputMethod, @NonNull IBinder token, @android.content.pm.ActivityInfo.Config int configChanges, boolean supportStylusHw) { - executeOrSendMessage(inputMethod, mCaller.obtainMessageIOOO(MSG_INITIALIZE_IME, - configChanges, inputMethod, token, supportStylusHw)); + if (DEBUG) { + Slog.v(TAG, "Sending attach of token: " + token + " for display: " + + mCurTokenDisplayId); + } + inputMethod.initializeInternal(token, new InputMethodPrivilegedOperationsImpl(this, token), + configChanges, supportStylusHw); } @AnyThread @@ -2515,7 +2511,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } @BinderThread - void onSessionCreated(IInputMethod method, IInputMethodSession session, InputChannel channel) { + void onSessionCreated(IInputMethodInvoker method, IInputMethodSession session, + InputChannel channel) { Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.onSessionCreated"); try { synchronized (ImfLock.class) { @@ -2525,7 +2522,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub channel.dispose(); return; } - IInputMethod curMethod = getCurMethodLocked(); + IInputMethodInvoker curMethod = getCurMethodLocked(); if (curMethod != null && method != null && curMethod.asBinder() == method.asBinder()) { if (mCurClient != null) { @@ -2579,22 +2576,38 @@ public class InputMethodManagerService extends IInputMethodManager.Stub void requestClientSessionLocked(ClientState cs) { if (!cs.sessionRequested) { if (DEBUG) Slog.v(TAG, "Creating new session for client " + cs); - InputChannel[] channels = InputChannel.openInputChannelPair(cs.toString()); + final InputChannel serverChannel; + final InputChannel clientChannel; + { + final InputChannel[] channels = InputChannel.openInputChannelPair(cs.toString()); + serverChannel = channels[0]; + clientChannel = channels[1]; + } + cs.sessionRequested = true; - IInputMethod curMethod = getCurMethodLocked(); - executeOrSendMessage(curMethod, mCaller.obtainMessageOOO( - MSG_CREATE_SESSION, curMethod, channels[1], - new IInputSessionCallback.Stub() { - @Override - public void sessionCreated(IInputMethodSession session) { - final long ident = Binder.clearCallingIdentity(); - try { - onSessionCreated(curMethod, session, channels[0]); - } finally { - Binder.restoreCallingIdentity(ident); - } - } - })); + + final IInputMethodInvoker curMethod = getCurMethodLocked(); + final IInputSessionCallback.Stub callback = new IInputSessionCallback.Stub() { + @Override + public void sessionCreated(IInputMethodSession session) { + final long ident = Binder.clearCallingIdentity(); + try { + onSessionCreated(curMethod, session, serverChannel); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + }; + + try { + curMethod.createSession(clientChannel, callback); + } finally { + // Dispose the channel because the remote proxy will get its own copy when + // unparceled. + if (clientChannel != null) { + clientChannel.dispose(); + } + } } } @@ -2975,14 +2988,10 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } if (newSubtype != oldSubtype) { setSelectedInputMethodAndSubtypeLocked(info, subtypeId, true); - IInputMethod curMethod = getCurMethodLocked(); + IInputMethodInvoker curMethod = getCurMethodLocked(); if (curMethod != null) { - try { - updateSystemUiLocked(mImeWindowVis, mBackDisposition); - curMethod.changeInputMethodSubtype(newSubtype); - } catch (RemoteException e) { - Slog.w(TAG, "Failed to call changeInputMethodSubtype"); - } + updateSystemUiLocked(mImeWindowVis, mBackDisposition); + curMethod.changeInputMethodSubtype(newSubtype); } } return; @@ -3059,9 +3068,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub return; } if (DEBUG) Slog.v(TAG, "Client requesting Stylus Handwriting to be started"); - if (getCurMethodLocked() != null) { - executeOrSendMessage(getCurMethodLocked(), mCaller.obtainMessageIO( - MSG_START_HANDWRITING, ++mHwRequestId, getCurMethodLocked())); + final IInputMethodInvoker curMethod = getCurMethodLocked(); + if (curMethod != null) { + curMethod.canStartStylusHandwriting(++mHwRequestId); } } finally { Binder.restoreCallingIdentity(ident); @@ -3112,18 +3121,24 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } mBindingController.setCurrentMethodVisible(); - if (getCurMethodLocked() != null) { + final IInputMethodInvoker curMethod = getCurMethodLocked(); + if (curMethod != null) { // create a placeholder token for IMS so that IMS cannot inject windows into client app. Binder showInputToken = new Binder(); mShowRequestWindowMap.put(showInputToken, windowToken); - IInputMethod curMethod = getCurMethodLocked(); - executeOrSendMessage(curMethod, mCaller.obtainMessageIIOOO(MSG_SHOW_SOFT_INPUT, - getImeShowFlagsLocked(), reason, curMethod, resultReceiver, - showInputToken)); + final int showFlags = getImeShowFlagsLocked(); + if (DEBUG) { + Slog.v(TAG, "Calling " + curMethod + ".showSoftInput(" + showInputToken + + ", " + showFlags + ", " + resultReceiver + ") for reason: " + + InputMethodDebug.softInputDisplayReasonToString(reason)); + } + // TODO(b/192412909): Check if we can always call onShowHideSoftInputRequested() or not. + if (curMethod.showSoftInput(showInputToken, showFlags, resultReceiver)) { + onShowHideSoftInputRequested(true /* show */, windowToken, reason); + } mInputShown = true; return true; } - return false; } @@ -3147,14 +3162,11 @@ public class InputMethodManagerService extends IInputMethodManager.Stub // be made before input is started in it. final ClientState cs = mClients.get(client.asBinder()); if (cs == null) { - throw new IllegalArgumentException( - "unknown client " + client.asBinder()); + throw new IllegalArgumentException("unknown client " + client.asBinder()); } - if (!mWindowManagerInternal.isInputMethodClientFocus(cs.uid, cs.pid, - cs.selfReportedDisplayId)) { + if (!isImeClientFocused(windowToken, cs)) { if (DEBUG) { - Slog.w(TAG, - "Ignoring hideSoftInput of uid " + uid + ": " + client); + Slog.w(TAG, "Ignoring hideSoftInput of uid " + uid + ": " + client); } return false; } @@ -3191,7 +3203,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub // since Android Eclair. That's why we need to accept IMM#hideSoftInput() even when only // IMMS#InputShown indicates that the software keyboard is shown. // TODO: Clean up, IMMS#mInputShown, IMMS#mImeWindowVis and mShowRequested. - IInputMethod curMethod = getCurMethodLocked(); + IInputMethodInvoker curMethod = getCurMethodLocked(); final boolean shouldHideSoftInput = (curMethod != null) && (mInputShown || (mImeWindowVis & InputMethodService.IME_ACTIVE) != 0); boolean res; @@ -3202,8 +3214,15 @@ public class InputMethodManagerService extends IInputMethodManager.Stub // delivered to the IME process as an IPC. Hence the inconsistency between // IMMS#mInputShown and IMMS#mImeWindowVis should be resolved spontaneously in // the final state. - executeOrSendMessage(curMethod, mCaller.obtainMessageIOOO(MSG_HIDE_SOFT_INPUT, - reason, curMethod, resultReceiver, hideInputToken)); + if (DEBUG) { + Slog.v(TAG, "Calling " + curMethod + ".hideSoftInput(0, " + hideInputToken + + ", " + resultReceiver + ") for reason: " + + InputMethodDebug.softInputDisplayReasonToString(reason)); + } + // TODO(b/192412909): Check if we can always call onShowHideSoftInputRequested() or not. + if (curMethod.hideSoftInput(hideInputToken, 0 /* flags */, resultReceiver)) { + onShowHideSoftInputRequested(false /* show */, windowToken, reason); + } res = true; } else { res = false; @@ -3216,6 +3235,12 @@ public class InputMethodManagerService extends IInputMethodManager.Stub return res; } + private boolean isImeClientFocused(IBinder windowToken, ClientState cs) { + final int imeClientFocus = mWindowManagerInternal.hasInputMethodClientFocus( + windowToken, cs.uid, cs.pid, cs.selfReportedDisplayId); + return imeClientFocus == WindowManagerInternal.ImeClientFocusResult.HAS_IME_FOCUS; + } + @NonNull @Override public InputBindResult startInputOrWindowGainedFocus( @@ -3309,31 +3334,30 @@ public class InputMethodManagerService extends IInputMethodManager.Stub + " unverifiedTargetSdkVersion=" + unverifiedTargetSdkVersion); } - final int windowDisplayId = mWindowManagerInternal.getDisplayIdForWindow(windowToken); - final ClientState cs = mClients.get(client.asBinder()); if (cs == null) { throw new IllegalArgumentException("unknown client " + client.asBinder()); } - if (cs.selfReportedDisplayId != windowDisplayId) { - Slog.e(TAG, "startInputOrWindowGainedFocusInternal: display ID mismatch." - + " from client:" + cs.selfReportedDisplayId - + " from window:" + windowDisplayId); - return InputBindResult.DISPLAY_ID_MISMATCH; - } - if (!mWindowManagerInternal.isInputMethodClientFocus(cs.uid, cs.pid, - cs.selfReportedDisplayId)) { - // Check with the window manager to make sure this client actually - // has a window with focus. If not, reject. This is thread safe - // because if the focus changes some time before or after, the - // next client receiving focus that has any interest in input will - // be calling through here after that change happens. - if (DEBUG) { - Slog.w(TAG, "Focus gain on non-focused client " + cs.client - + " (uid=" + cs.uid + " pid=" + cs.pid + ")"); - } - return InputBindResult.NOT_IME_TARGET_WINDOW; + final int imeClientFocus = mWindowManagerInternal.hasInputMethodClientFocus( + windowToken, cs.uid, cs.pid, cs.selfReportedDisplayId); + switch (imeClientFocus) { + case WindowManagerInternal.ImeClientFocusResult.DISPLAY_ID_MISMATCH: + Slog.e(TAG, "startInputOrWindowGainedFocusInternal: display ID mismatch."); + return InputBindResult.DISPLAY_ID_MISMATCH; + case WindowManagerInternal.ImeClientFocusResult.NOT_IME_TARGET_WINDOW: + // Check with the window manager to make sure this client actually + // has a window with focus. If not, reject. This is thread safe + // because if the focus changes some time before or after, the + // next client receiving focus that has any interest in input will + // be calling through here after that change happens. + if (DEBUG) { + Slog.w(TAG, "Focus gain on non-focused client " + cs.client + + " (uid=" + cs.uid + " pid=" + cs.pid + ")"); + } + return InputBindResult.NOT_IME_TARGET_WINDOW; + case WindowManagerInternal.ImeClientFocusResult.INVALID_DISPLAY_ID: + return InputBindResult.INVALID_DISPLAY_ID; } if (mUserSwitchHandlerTask != null) { @@ -3561,8 +3585,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub if (cs == null) { throw new IllegalArgumentException("unknown client " + client.asBinder()); } - if (!mWindowManagerInternal.isInputMethodClientFocus(cs.uid, cs.pid, - cs.selfReportedDisplayId)) { + if (!isImeClientFocused(mCurFocusedWindow, cs)) { Slog.w(TAG, String.format("Ignoring %s of uid %d : %s", methodName, uid, client)); return false; } @@ -3679,8 +3702,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub if (!calledFromValidUserLocked()) { return; } - executeOrSendMessage(getCurMethodLocked(), mCaller.obtainMessageO( - MSG_SHOW_IM_SUBTYPE_ENABLER, inputMethodId)); + showInputMethodAndSubtypeEnabler(inputMethodId); } } @@ -4104,7 +4126,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } - /** Called right after {@link IInputMethod#showSoftInput}. */ + /** Called right after {@link com.android.internal.view.IInputMethod#showSoftInput}. */ @GuardedBy("ImfLock.class") private void onShowHideSoftInputRequested(boolean show, IBinder requestToken, @SoftInputShowHideReason int reason) { @@ -4155,22 +4177,17 @@ public class InputMethodManagerService extends IInputMethodManager.Stub Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); } - void setEnabledSessionInHandlerThread(SessionState session) { + @GuardedBy("ImfLock.class") + void setEnabledSessionLocked(SessionState session) { if (mEnabledSession != session) { if (mEnabledSession != null && mEnabledSession.session != null) { - try { - if (DEBUG) Slog.v(TAG, "Disabling: " + mEnabledSession); - mEnabledSession.method.setSessionEnabled(mEnabledSession.session, false); - } catch (RemoteException e) { - } + if (DEBUG) Slog.v(TAG, "Disabling: " + mEnabledSession); + mEnabledSession.method.setSessionEnabled(mEnabledSession.session, false); } mEnabledSession = session; if (mEnabledSession != null && mEnabledSession.session != null) { - try { - if (DEBUG) Slog.v(TAG, "Enabling: " + mEnabledSession); - mEnabledSession.method.setSessionEnabled(mEnabledSession.session, true); - } catch (RemoteException e) { - } + if (DEBUG) Slog.v(TAG, "Enabling: " + mEnabledSession); + mEnabledSession.method.setSessionEnabled(mEnabledSession.session, true); } } } @@ -4203,69 +4220,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub mMenuController.showInputMethodMenu(showAuxSubtypes, displayId); return true; - case MSG_SHOW_IM_SUBTYPE_ENABLER: - showInputMethodAndSubtypeEnabler((String)msg.obj); - return true; - - case MSG_SHOW_IM_CONFIG: - showConfigureInputMethods(); - return true; - // --------------------------------------------------------- - case MSG_UNBIND_INPUT: - try { - ((IInputMethod)msg.obj).unbindInput(); - } catch (RemoteException e) { - // There is nothing interesting about the method dying. - } - return true; - case MSG_BIND_INPUT: - args = (SomeArgs)msg.obj; - try { - ((IInputMethod)args.arg1).bindInput((InputBinding)args.arg2); - } catch (RemoteException e) { - } - args.recycle(); - return true; - case MSG_SHOW_SOFT_INPUT: - args = (SomeArgs) msg.obj; - try { - final @SoftInputShowHideReason int reason = msg.arg2; - if (DEBUG) Slog.v(TAG, "Calling " + args.arg1 + ".showSoftInput(" - + args.arg3 + ", " + msg.arg1 + ", " + args.arg2 + ") for reason: " - + InputMethodDebug.softInputDisplayReasonToString(reason)); - final IBinder token = (IBinder) args.arg3; - ((IInputMethod) args.arg1).showSoftInput( - token, msg.arg1 /* flags */, (ResultReceiver) args.arg2); - final IBinder requestToken; - synchronized (ImfLock.class) { - requestToken = mShowRequestWindowMap.get(token); - onShowHideSoftInputRequested(true /* show */, requestToken, reason); - } - } catch (RemoteException e) { - } - args.recycle(); - return true; - case MSG_HIDE_SOFT_INPUT: - args = (SomeArgs) msg.obj; - try { - final @SoftInputShowHideReason int reason = msg.arg1; - if (DEBUG) Slog.v(TAG, "Calling " + args.arg1 + ".hideSoftInput(0, " - + args.arg3 + ", " + args.arg2 + ") for reason: " - + InputMethodDebug.softInputDisplayReasonToString(reason)); - final IBinder token = (IBinder) args.arg3; - ((IInputMethod)args.arg1).hideSoftInput( - token, 0 /* flags */, (ResultReceiver) args.arg2); - final IBinder requestToken; - synchronized (ImfLock.class) { - requestToken = mHideRequestWindowMap.get(token); - onShowHideSoftInputRequested(false /* show */, requestToken, reason); - } - } catch (RemoteException e) { - } - args.recycle(); - return true; case MSG_HIDE_CURRENT_INPUT_METHOD: synchronized (ImfLock.class) { final @SoftInputShowHideReason int reason = (int) msg.obj; @@ -4273,40 +4229,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } return true; - case MSG_INITIALIZE_IME: - args = (SomeArgs)msg.obj; - try { - if (DEBUG) { - synchronized (ImfLock.class) { - Slog.v(TAG, "Sending attach of token: " + args.arg2 + " for display: " - + mCurTokenDisplayId); - } - } - final IBinder token = (IBinder) args.arg2; - ((IInputMethod) args.arg1).initializeInternal(token, - new InputMethodPrivilegedOperationsImpl(this, token), - msg.arg1, (boolean) args.arg3); - } catch (RemoteException e) { - } - args.recycle(); - return true; - case MSG_CREATE_SESSION: { - args = (SomeArgs)msg.obj; - IInputMethod method = (IInputMethod)args.arg1; - InputChannel channel = (InputChannel)args.arg2; - try { - method.createSession(channel, (IInputSessionCallback)args.arg3); - } catch (RemoteException e) { - } finally { - // Dispose the channel if the input method is not local to this process - // because the remote proxy will get its own copy when unparceled. - if (channel != null && Binder.isProxy(method)) { - channel.dispose(); - } - } - args.recycle(); - return true; - } case MSG_REMOVE_IME_SURFACE: { synchronized (ImfLock.class) { try { @@ -4338,25 +4260,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } // --------------------------------------------------------- - case MSG_START_INPUT: { - final boolean restarting = msg.arg2 != 0; - args = (SomeArgs) msg.obj; - final IBinder startInputToken = (IBinder) args.arg1; - final SessionState session = (SessionState) args.arg2; - final IInputContext inputContext = (IInputContext) args.arg3; - final EditorInfo editorInfo = (EditorInfo) args.arg4; - try { - setEnabledSessionInHandlerThread(session); - session.method.startInput(startInputToken, inputContext, editorInfo, - restarting); - } catch (RemoteException e) { - } - args.recycle(); - return true; - } - - // --------------------------------------------------------- - case MSG_UNBIND_CLIENT: try { ((IInputMethodClient)msg.obj).onUnbindMethod(msg.arg1, msg.arg2); @@ -4430,23 +4333,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } // --------------------------------------------------------------- - case MSG_INLINE_SUGGESTIONS_REQUEST: { - args = (SomeArgs) msg.obj; - final InlineSuggestionsRequestInfo requestInfo = - (InlineSuggestionsRequestInfo) args.arg2; - final IInlineSuggestionsRequestCallback callback = - (IInlineSuggestionsRequestCallback) args.arg3; - try { - ((IInputMethod) args.arg1).onCreateInlineSuggestionsRequest(requestInfo, - callback); - } catch (RemoteException e) { - Slog.w(TAG, "RemoteException calling onCreateInlineSuggestionsRequest(): " + e); - } - args.recycle(); - return true; - } - - // --------------------------------------------------------------- case MSG_NOTIFY_IME_UID_TO_AUDIO_SERVICE: { if (mAudioManagerInternal == null) { mAudioManagerInternal = LocalServices.getService(AudioManagerInternal.class); @@ -4456,13 +4342,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } return true; } - case MSG_START_HANDWRITING: - try { - (((IInputMethod) msg.obj)).canStartStylusHandwriting(msg.arg1); - } catch (RemoteException e) { - Slog.w(TAG, "RemoteException calling canStartStylusHandwriting(): ", e); - } - return true; } return false; } @@ -4475,12 +4354,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub return; } - try { - // TODO: replace null with actual Channel, MotionEvents - getCurMethodLocked().startStylusHandwriting(null, null); - } catch (RemoteException e) { - Slog.w(TAG, "RemoteException calling startStylusHandwriting(): ", e); - } + // TODO: replace null with actual Channel, MotionEvents + getCurMethodLocked().startStylusHandwriting(null, null); } } @@ -4741,14 +4616,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub mContext.startActivityAsUser(intent, null, UserHandle.of(userId)); } - private void showConfigureInputMethods() { - Intent intent = new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK - | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED - | Intent.FLAG_ACTIVITY_CLEAR_TOP); - mContext.startActivityAsUser(intent, null, UserHandle.CURRENT); - } - // ---------------------------------------------------------------------- /** @@ -5221,7 +5088,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @BinderThread private void dumpAsStringNoCheck(FileDescriptor fd, PrintWriter pw, String[] args, boolean isCritical) { - IInputMethod method; + IInputMethodInvoker method; ClientState client; ClientState focusedWindowClient; diff --git a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java index f1141845f2bd..c02411ec4c1b 100644 --- a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java +++ b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java @@ -16,14 +16,15 @@ package com.android.server.location.gnss; -import static android.content.pm.PackageManager.FEATURE_WATCH; - import static android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP; +import static android.content.pm.PackageManager.FEATURE_WATCH; import static android.location.provider.ProviderProperties.ACCURACY_FINE; import static android.location.provider.ProviderProperties.POWER_USAGE_HIGH; import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR; import static com.android.server.location.gnss.hal.GnssNative.AGPS_REF_LOCATION_TYPE_GSM_CELLID; +import static com.android.server.location.gnss.hal.GnssNative.AGPS_REF_LOCATION_TYPE_LTE_CELLID; +import static com.android.server.location.gnss.hal.GnssNative.AGPS_REF_LOCATION_TYPE_NR_CELLID; import static com.android.server.location.gnss.hal.GnssNative.AGPS_REF_LOCATION_TYPE_UMTS_CELLID; import static com.android.server.location.gnss.hal.GnssNative.AGPS_SETID_TYPE_IMSI; import static com.android.server.location.gnss.hal.GnssNative.AGPS_SETID_TYPE_MSISDN; @@ -84,9 +85,18 @@ import android.os.WorkSource; import android.os.WorkSource.WorkChain; import android.provider.Settings; import android.telephony.CarrierConfigManager; +import android.telephony.CellIdentity; +import android.telephony.CellIdentityGsm; +import android.telephony.CellIdentityLte; +import android.telephony.CellIdentityNr; +import android.telephony.CellIdentityWcdma; +import android.telephony.CellInfo; +import android.telephony.CellInfoGsm; +import android.telephony.CellInfoLte; +import android.telephony.CellInfoNr; +import android.telephony.CellInfoWcdma; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; -import android.telephony.gsm.GsmCellLocation; import android.text.TextUtils; import android.text.format.DateUtils; import android.util.Log; @@ -110,6 +120,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; +import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Objects; @@ -1136,7 +1147,7 @@ public class GnssLocationProvider extends AbstractLocationProvider implements if (DEBUG) { Log.d(TAG, "startBatching " + mFixInterval + " " + batchLengthMs); } - if (mGnssNative.startBatch(MILLISECONDS.toNanos(mFixInterval), true)) { + if (mGnssNative.startBatch(MILLISECONDS.toNanos(mFixInterval), 0, true)) { mBatchingStarted = true; if (batchSize < getBatchSize()) { @@ -1386,29 +1397,127 @@ public class GnssLocationProvider extends AbstractLocationProvider implements postWithWakeLockHeld(mNtpTimeHelper::retrieveAndInjectNtpTime); } + + private static int getCellType(CellInfo ci) { + if (ci instanceof CellInfoGsm) { + return CellInfo.TYPE_GSM; + } else if (ci instanceof CellInfoWcdma) { + return CellInfo.TYPE_WCDMA; + } else if (ci instanceof CellInfoLte) { + return CellInfo.TYPE_LTE; + } else if (ci instanceof CellInfoNr) { + return CellInfo.TYPE_NR; + } + return CellInfo.TYPE_UNKNOWN; + } + + /** + * Extract the CID/CI for GSM/WCDMA/LTE/NR + * + * @return the cell ID or -1 if invalid + */ + private static long getCidFromCellIdentity(CellIdentity id) { + if (id == null) return -1; + long cid = -1; + switch(id.getType()) { + case CellInfo.TYPE_GSM: cid = ((CellIdentityGsm) id).getCid(); break; + case CellInfo.TYPE_WCDMA: cid = ((CellIdentityWcdma) id).getCid(); break; + case CellInfo.TYPE_LTE: cid = ((CellIdentityLte) id).getCi(); break; + case CellInfo.TYPE_NR: cid = ((CellIdentityNr) id).getNci(); break; + default: break; + } + // If the CID is unreported + if (cid == (id.getType() == CellInfo.TYPE_NR + ? CellInfo.UNAVAILABLE_LONG : CellInfo.UNAVAILABLE)) { + cid = -1; + } + + return cid; + } + + private void setRefLocation(int type, CellIdentity ci) { + String mcc_str = ci.getMccString(); + String mnc_str = ci.getMncString(); + int mcc = mcc_str != null ? Integer.parseInt(mcc_str) : CellInfo.UNAVAILABLE; + int mnc = mnc_str != null ? Integer.parseInt(mnc_str) : CellInfo.UNAVAILABLE; + int lac = CellInfo.UNAVAILABLE; + int tac = CellInfo.UNAVAILABLE; + int pcid = CellInfo.UNAVAILABLE; + int arfcn = CellInfo.UNAVAILABLE; + long cid = CellInfo.UNAVAILABLE_LONG; + + switch (type) { + case AGPS_REF_LOCATION_TYPE_GSM_CELLID: + CellIdentityGsm cig = (CellIdentityGsm) ci; + cid = cig.getCid(); + lac = cig.getLac(); + break; + case AGPS_REF_LOCATION_TYPE_UMTS_CELLID: + CellIdentityWcdma ciw = (CellIdentityWcdma) ci; + cid = ciw.getCid(); + lac = ciw.getLac(); + break; + case AGPS_REF_LOCATION_TYPE_LTE_CELLID: + CellIdentityLte cil = (CellIdentityLte) ci; + cid = cil.getCi(); + tac = cil.getTac(); + pcid = cil.getPci(); + break; + case AGPS_REF_LOCATION_TYPE_NR_CELLID: + CellIdentityNr cin = (CellIdentityNr) ci; + cid = cin.getNci(); + tac = cin.getTac(); + pcid = cin.getPci(); + arfcn = cin.getNrarfcn(); + break; + default: + } + + mGnssNative.setAgpsReferenceLocationCellId( + type, mcc, mnc, lac, cid, tac, pcid, arfcn); + } + private void requestRefLocation() { TelephonyManager phone = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); + final int phoneType = phone.getPhoneType(); if (phoneType == TelephonyManager.PHONE_TYPE_GSM) { - GsmCellLocation gsm_cell = (GsmCellLocation) phone.getCellLocation(); - if ((gsm_cell != null) && (phone.getNetworkOperator() != null) - && (phone.getNetworkOperator().length() > 3)) { - int type; - int mcc = Integer.parseInt(phone.getNetworkOperator().substring(0, 3)); - int mnc = Integer.parseInt(phone.getNetworkOperator().substring(3)); - int networkType = phone.getNetworkType(); - if (networkType == TelephonyManager.NETWORK_TYPE_UMTS - || networkType == TelephonyManager.NETWORK_TYPE_HSDPA - || networkType == TelephonyManager.NETWORK_TYPE_HSUPA - || networkType == TelephonyManager.NETWORK_TYPE_HSPA - || networkType == TelephonyManager.NETWORK_TYPE_HSPAP) { - type = AGPS_REF_LOCATION_TYPE_UMTS_CELLID; + + List<CellInfo> cil = phone.getAllCellInfo(); + if (cil != null) { + HashMap<Integer, CellIdentity> cellIdentityMap = new HashMap<>(); + cil.sort(Comparator.comparingInt( + (CellInfo ci) -> ci.getCellSignalStrength().getAsuLevel()).reversed()); + + for (CellInfo ci : cil) { + int status = ci.getCellConnectionStatus(); + if (status == CellInfo.CONNECTION_PRIMARY_SERVING + || status == CellInfo.CONNECTION_SECONDARY_SERVING) { + CellIdentity c = ci.getCellIdentity(); + int t = getCellType(ci); + if (getCidFromCellIdentity(c) != -1 + && !cellIdentityMap.containsKey(t)) { + cellIdentityMap.put(t, c); + } + } + } + + if (cellIdentityMap.containsKey(CellInfo.TYPE_GSM)) { + setRefLocation(AGPS_REF_LOCATION_TYPE_GSM_CELLID, + cellIdentityMap.get(CellInfo.TYPE_GSM)); + } else if (cellIdentityMap.containsKey(CellInfo.TYPE_WCDMA)) { + setRefLocation(AGPS_REF_LOCATION_TYPE_UMTS_CELLID, + cellIdentityMap.get(CellInfo.TYPE_WCDMA)); + } else if (cellIdentityMap.containsKey(CellInfo.TYPE_LTE)) { + setRefLocation(AGPS_REF_LOCATION_TYPE_LTE_CELLID, + cellIdentityMap.get(CellInfo.TYPE_LTE)); + } else if (cellIdentityMap.containsKey(CellInfo.TYPE_NR)) { + setRefLocation(AGPS_REF_LOCATION_TYPE_NR_CELLID, + cellIdentityMap.get(CellInfo.TYPE_NR)); } else { - type = AGPS_REF_LOCATION_TYPE_GSM_CELLID; + Log.e(TAG, "No available serving cell information."); } - mGnssNative.setAgpsReferenceLocationCellId(type, mcc, mnc, gsm_cell.getLac(), - gsm_cell.getCid()); } else { Log.e(TAG, "Error getting cell location info."); } diff --git a/services/core/java/com/android/server/location/gnss/hal/GnssNative.java b/services/core/java/com/android/server/location/gnss/hal/GnssNative.java index a513e0898344..e072bf7dc1f7 100644 --- a/services/core/java/com/android/server/location/gnss/hal/GnssNative.java +++ b/services/core/java/com/android/server/location/gnss/hal/GnssNative.java @@ -124,9 +124,12 @@ public class GnssNative { // IMPORTANT - must match OEM definitions, this isn't part of a hal for some reason public static final int AGPS_REF_LOCATION_TYPE_GSM_CELLID = 1; public static final int AGPS_REF_LOCATION_TYPE_UMTS_CELLID = 2; + public static final int AGPS_REF_LOCATION_TYPE_LTE_CELLID = 4; + public static final int AGPS_REF_LOCATION_TYPE_NR_CELLID = 8; @IntDef(prefix = "AGPS_REF_LOCATION_TYPE_", value = {AGPS_REF_LOCATION_TYPE_GSM_CELLID, - AGPS_REF_LOCATION_TYPE_UMTS_CELLID}) + AGPS_REF_LOCATION_TYPE_UMTS_CELLID, AGPS_REF_LOCATION_TYPE_LTE_CELLID, + AGPS_REF_LOCATION_TYPE_NR_CELLID}) @Retention(RetentionPolicy.SOURCE) public @interface AgpsReferenceLocationType {} @@ -814,9 +817,10 @@ public class GnssNative { /** * Start batching. */ - public boolean startBatch(long periodNanos, boolean wakeOnFifoFull) { + public boolean startBatch(long periodNanos, float minUpdateDistanceMeters, + boolean wakeOnFifoFull) { Preconditions.checkState(mRegistered); - return mGnssHal.startBatch(periodNanos, wakeOnFifoFull); + return mGnssHal.startBatch(periodNanos, minUpdateDistanceMeters, wakeOnFifoFull); } /** @@ -930,9 +934,9 @@ public class GnssNative { * Sets AGPS reference cell id location. */ public void setAgpsReferenceLocationCellId(@AgpsReferenceLocationType int type, int mcc, - int mnc, int lac, int cid) { + int mnc, int lac, long cid, int tac, int pcid, int arfcn) { Preconditions.checkState(mRegistered); - mGnssHal.setAgpsReferenceLocationCellId(type, mcc, mnc, lac, cid); + mGnssHal.setAgpsReferenceLocationCellId(type, mcc, mnc, lac, cid, tac, pcid, arfcn); } /** @@ -1377,8 +1381,9 @@ public class GnssNative { native_cleanup_batching(); } - protected boolean startBatch(long periodNanos, boolean wakeOnFifoFull) { - return native_start_batch(periodNanos, wakeOnFifoFull); + protected boolean startBatch(long periodNanos, float minUpdateDistanceMeters, + boolean wakeOnFifoFull) { + return native_start_batch(periodNanos, minUpdateDistanceMeters, wakeOnFifoFull); } protected void flushBatch() { @@ -1433,8 +1438,8 @@ public class GnssNative { } protected void setAgpsReferenceLocationCellId(@AgpsReferenceLocationType int type, int mcc, - int mnc, int lac, int cid) { - native_agps_set_ref_location_cellid(type, mcc, mnc, lac, cid); + int mnc, int lac, long cid, int tac, int pcid, int arfcn) { + native_agps_set_ref_location_cellid(type, mcc, mnc, lac, cid, tac, pcid, arfcn); } protected boolean isPsdsSupported() { @@ -1536,7 +1541,8 @@ public class GnssNative { private static native void native_cleanup_batching(); - private static native boolean native_start_batch(long periodNanos, boolean wakeOnFifoFull); + private static native boolean native_start_batch(long periodNanos, + float minUpdateDistanceMeters, boolean wakeOnFifoFull); private static native void native_flush_batch(); @@ -1575,7 +1581,7 @@ public class GnssNative { private static native void native_agps_set_id(int type, String setid); private static native void native_agps_set_ref_location_cellid(int type, int mcc, int mnc, - int lac, int cid); + int lac, long cid, int tac, int pcid, int arfcn); // PSDS APIs diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java index 682a27adc15f..8f051303bf03 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsService.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java @@ -20,6 +20,9 @@ import static android.Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE; import static android.Manifest.permission.MANAGE_BIOMETRIC; import static android.Manifest.permission.READ_CONTACTS; import static android.Manifest.permission.SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS; +import static android.app.admin.DevicePolicyResources.Strings.Core.PROFILE_ENCRYPTED_DETAIL; +import static android.app.admin.DevicePolicyResources.Strings.Core.PROFILE_ENCRYPTED_MESSAGE; +import static android.app.admin.DevicePolicyResources.Strings.Core.PROFILE_ENCRYPTED_TITLE; import static android.content.Context.KEYGUARD_SERVICE; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.os.UserHandle.USER_ALL; @@ -117,6 +120,7 @@ import android.util.LongSparseArray; import android.util.Slog; import android.util.SparseArray; +import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; @@ -642,12 +646,9 @@ public class LockSettingsService extends ILockSettings.Stub { private void showEncryptionNotificationForProfile(UserHandle user) { Resources r = mContext.getResources(); - CharSequence title = r.getText( - com.android.internal.R.string.profile_encrypted_title); - CharSequence message = r.getText( - com.android.internal.R.string.profile_encrypted_message); - CharSequence detail = r.getText( - com.android.internal.R.string.profile_encrypted_detail); + CharSequence title = getEncryptionNotificationTitle(); + CharSequence message = getEncryptionNotificationMessage(); + CharSequence detail = getEncryptionNotificationDetail(); final KeyguardManager km = (KeyguardManager) mContext.getSystemService(KEYGUARD_SERVICE); final Intent unlockIntent = km.createConfirmDeviceCredentialIntent(null, null, @@ -663,6 +664,24 @@ public class LockSettingsService extends ILockSettings.Stub { showEncryptionNotification(user, title, message, detail, intent); } + private String getEncryptionNotificationTitle() { + return mInjector.getDevicePolicyManager().getString( + PROFILE_ENCRYPTED_TITLE, + () -> mContext.getString(R.string.profile_encrypted_title)); + } + + private String getEncryptionNotificationDetail() { + return mInjector.getDevicePolicyManager().getString( + PROFILE_ENCRYPTED_DETAIL, + () -> mContext.getString(R.string.profile_encrypted_detail)); + } + + private String getEncryptionNotificationMessage() { + return mInjector.getDevicePolicyManager().getString( + PROFILE_ENCRYPTED_MESSAGE, + () -> mContext.getString(R.string.profile_encrypted_message)); + } + private void showEncryptionNotification(UserHandle user, CharSequence title, CharSequence message, CharSequence detail, PendingIntent intent) { if (DEBUG) Slog.v(TAG, "showing encryption notification, user: " + user.getIdentifier()); diff --git a/services/core/java/com/android/server/logcat/LogcatManagerService.java b/services/core/java/com/android/server/logcat/LogcatManagerService.java new file mode 100644 index 000000000000..ff6372aec3bd --- /dev/null +++ b/services/core/java/com/android/server/logcat/LogcatManagerService.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.logcat; + +import android.content.Context; +import android.os.ILogd; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.logcat.ILogcatManagerService; +import android.util.Slog; + +import com.android.server.SystemService; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +/** + * Service responsible for manage the access to Logcat. + */ +public final class LogcatManagerService extends SystemService { + + private static final String TAG = "LogcatManagerService"; + private final Context mContext; + private final BinderService mBinderService; + private final ExecutorService mThreadExecutor; + private ILogd mLogdService; + + private final class BinderService extends ILogcatManagerService.Stub { + @Override + public void startThread(int uid, int gid, int pid, int fd) { + mThreadExecutor.execute(new LogdMonitor(uid, gid, pid, fd, true)); + } + + @Override + public void finishThread(int uid, int gid, int pid, int fd) { + // TODO This thread will be used to notify the AppOpsManager that + // the logd data access is finished. + mThreadExecutor.execute(new LogdMonitor(uid, gid, pid, fd, false)); + } + } + + private class LogdMonitor implements Runnable { + + private final int mUid; + private final int mGid; + private final int mPid; + private final int mFd; + private final boolean mStart; + + /** + * For starting a thread, the start value is true. + * For finishing a thread, the start value is false. + */ + LogdMonitor(int uid, int gid, int pid, int fd, boolean start) { + mUid = uid; + mGid = gid; + mPid = pid; + mFd = fd; + mStart = start; + } + + /** + * The current version grant the permission by default. + * And track the logd access. + * The next version will generate a prompt for users. + * The users decide whether the logd access is allowed. + */ + @Override + public void run() { + if (mLogdService == null) { + LogcatManagerService.this.addLogdService(); + } + + if (mStart) { + try { + mLogdService.approve(mUid, mGid, mPid, mFd); + } catch (RemoteException ex) { + Slog.e(TAG, "Fails to call remote functions ", ex); + } + } + } + } + + public LogcatManagerService(Context context) { + super(context); + mContext = context; + mBinderService = new BinderService(); + mThreadExecutor = Executors.newCachedThreadPool(); + } + + @Override + public void onStart() { + try { + publishBinderService("logcat", mBinderService); + } catch (Throwable t) { + Slog.e(TAG, "Could not start the LogcatManagerService.", t); + } + } + + private void addLogdService() { + mLogdService = ILogd.Stub.asInterface(ServiceManager.getService("logd")); + } + +} diff --git a/services/core/java/com/android/server/logcat/OWNERS b/services/core/java/com/android/server/logcat/OWNERS new file mode 100644 index 000000000000..9588fa9d1f8e --- /dev/null +++ b/services/core/java/com/android/server/logcat/OWNERS @@ -0,0 +1,5 @@ +cbrubaker@google.com +eunjeongshin@google.com +jsharkey@google.com +vishwath@google.com +wenhaowang@google.com diff --git a/services/core/java/com/android/server/media/BluetoothRouteProvider.java b/services/core/java/com/android/server/media/BluetoothRouteProvider.java index ffc1aed4c672..91de9e559e13 100644 --- a/services/core/java/com/android/server/media/BluetoothRouteProvider.java +++ b/services/core/java/com/android/server/media/BluetoothRouteProvider.java @@ -344,10 +344,19 @@ class BluetoothRouteProvider { } private void addActiveRoute(BluetoothRouteInfo btRoute) { + if (btRoute == null) { + if (DEBUG) { + Log.d(TAG, " btRoute is null"); + } + return; + } if (DEBUG) { Log.d(TAG, "Adding active route: " + btRoute.route); } - if (btRoute == null || mActiveRoutes.contains(btRoute)) { + if (mActiveRoutes.contains(btRoute)) { + if (DEBUG) { + Log.d(TAG, " btRoute is already added."); + } return; } setRouteConnectionState(btRoute, STATE_CONNECTED); diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java index 9bc090fe46d4..e555c1356df7 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java @@ -63,7 +63,6 @@ import static android.net.INetd.FIREWALL_RULE_DENY; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; -import static android.net.NetworkIdentity.OEM_NONE; import static android.net.NetworkPolicy.LIMIT_DISABLED; import static android.net.NetworkPolicy.SNOOZE_NEVER; import static android.net.NetworkPolicy.WARNING_DISABLED; @@ -1498,13 +1497,11 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { for (int i = 0; i < mSubIdToSubscriberId.size(); i++) { final int subId = mSubIdToSubscriberId.keyAt(i); final String subscriberId = mSubIdToSubscriberId.valueAt(i); - final NetworkIdentity probeIdent = new NetworkIdentity(TYPE_MOBILE, - TelephonyManager.NETWORK_TYPE_UNKNOWN, subscriberId, null, false, true, - true, OEM_NONE); - /* While OEM_NONE indicates "any non OEM managed network", OEM_NONE is meant to be a - * placeholder value here. The probeIdent is matched against a NetworkTemplate which - * should have its OEM managed value set to OEM_MANAGED_ALL, which will cause the - * template to match probeIdent without regard to OEM managed status. */ + final NetworkIdentity probeIdent = new NetworkIdentity.Builder() + .setType(TYPE_MOBILE) + .setSubscriberId(subscriberId) + .setMetered(true) + .setDefaultNetwork(true).build(); if (template.matches(probeIdent)) { return subId; } @@ -1737,9 +1734,11 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { // find and update the carrier NetworkPolicy for this subscriber id boolean policyUpdated = false; - final NetworkIdentity probeIdent = new NetworkIdentity(TYPE_MOBILE, - TelephonyManager.NETWORK_TYPE_UNKNOWN, subscriberId, null, false, true, true, - OEM_NONE); + final NetworkIdentity probeIdent = new NetworkIdentity.Builder() + .setType(TYPE_MOBILE) + .setSubscriberId(subscriberId) + .setMetered(true) + .setDefaultNetwork(true).build(); for (int i = mNetworkPolicy.size() - 1; i >= 0; i--) { final NetworkTemplate template = mNetworkPolicy.keyAt(i); if (template.matches(probeIdent)) { @@ -1967,10 +1966,11 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { for (int i = 0; i < mSubIdToSubscriberId.size(); i++) { final int subId = mSubIdToSubscriberId.keyAt(i); final String subscriberId = mSubIdToSubscriberId.valueAt(i); - - final NetworkIdentity probeIdent = new NetworkIdentity(TYPE_MOBILE, - TelephonyManager.NETWORK_TYPE_UNKNOWN, subscriberId, null, false, true, - true, OEM_NONE); + final NetworkIdentity probeIdent = new NetworkIdentity.Builder() + .setType(TYPE_MOBILE) + .setSubscriberId(subscriberId) + .setMetered(true) + .setDefaultNetwork(true).build(); // Template is matched when subscriber id matches. if (template.matches(probeIdent)) { matchingSubIds.add(subId); @@ -2074,11 +2074,9 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { for (final NetworkStateSnapshot snapshot : snapshots) { mNetIdToSubId.put(snapshot.getNetwork().getNetId(), parseSubId(snapshot)); - // Policies matched by NPMS only match by subscriber ID or by network ID. Thus subtype - // in the object created here is never used and its value doesn't matter, so use - // NETWORK_TYPE_UNKNOWN. - final NetworkIdentity ident = NetworkIdentity.buildNetworkIdentity(mContext, snapshot, - true, TelephonyManager.NETWORK_TYPE_UNKNOWN /* subType */); + // Policies matched by NPMS only match by subscriber ID or by network ID. + final NetworkIdentity ident = new NetworkIdentity.Builder() + .setNetworkStateSnapshot(snapshot).setDefaultNetwork(true).build(); identified.put(snapshot, ident); } @@ -2275,9 +2273,11 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { @GuardedBy("mNetworkPoliciesSecondLock") private boolean ensureActiveCarrierPolicyAL(int subId, String subscriberId) { // Poke around to see if we already have a policy - final NetworkIdentity probeIdent = new NetworkIdentity(TYPE_MOBILE, - TelephonyManager.NETWORK_TYPE_UNKNOWN, subscriberId, null, false, true, true, - OEM_NONE); + final NetworkIdentity probeIdent = new NetworkIdentity.Builder() + .setType(TYPE_MOBILE) + .setSubscriberId(subscriberId) + .setMetered(true) + .setDefaultNetwork(true).build(); for (int i = mNetworkPolicy.size() - 1; i >= 0; i--) { final NetworkTemplate template = mNetworkPolicy.keyAt(i); if (template.matches(probeIdent)) { @@ -2687,7 +2687,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { final List<WifiConfiguration> configs = wm.getConfiguredNetworks(); for (int i = 0; i < configs.size(); ++i) { final WifiConfiguration config = configs.get(i); - for (String key : config.getAllPersistableNetworkKeys()) { + for (String key : config.getAllNetworkKeys()) { final Boolean metered = wifiNetworkKeys.get(key); if (metered != null) { Slog.d(TAG, "Found network " + key + "; upgrading metered hint"); @@ -3452,7 +3452,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { * {@link NetworkStatsProvider#onSetWarningAndLimit(String, long, long)}. */ @Override - public void onStatsProviderWarningOrLimitReached() { + public void notifyStatsProviderWarningOrLimitReached() { enforceAnyPermissionOf(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK); // This API may be called before the system is ready. synchronized (mNetworkPoliciesSecondLock) { @@ -5078,7 +5078,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { // make sure stats are recorded frequently enough; we aim // for 2MB threshold for 2GB/month rules. final long persistThreshold = lowestRule / 1000; - mNetworkStats.advisePersistThreshold(persistThreshold); + // TODO: Sync internal naming with the API surface. + mNetworkStats.setDefaultGlobalAlert(persistThreshold); return true; } case MSG_UPDATE_INTERFACE_QUOTAS: { diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 86b385b4d810..6b27321f856f 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -499,6 +499,7 @@ public class NotificationManagerService extends SystemService { private IPlatformCompat mPlatformCompat; private ShortcutHelper mShortcutHelper; private PermissionHelper mPermissionHelper; + private UsageStatsManagerInternal mUsageStatsManagerInternal; final IBinder mForegroundToken = new Binder(); private WorkerHandler mHandler; @@ -2092,7 +2093,8 @@ public class NotificationManagerService extends SystemService { UserManager userManager, NotificationHistoryManager historyManager, StatsManager statsManager, TelephonyManager telephonyManager, ActivityManagerInternal ami, - MultiRateLimiter toastRateLimiter, PermissionHelper permissionHelper) { + MultiRateLimiter toastRateLimiter, PermissionHelper permissionHelper, + UsageStatsManagerInternal usageStatsManagerInternal) { mHandler = handler; Resources resources = getContext().getResources(); mMaxPackageEnqueueRate = Settings.Global.getFloat(getContext().getContentResolver(), @@ -2110,6 +2112,7 @@ public class NotificationManagerService extends SystemService { mPackageManagerClient = packageManagerClient; mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class); mPermissionPolicyInternal = LocalServices.getService(PermissionPolicyInternal.class); + mUsageStatsManagerInternal = usageStatsManagerInternal; mAppOps = appOps; mAppOpsService = iAppOps; try { @@ -2411,7 +2414,8 @@ public class NotificationManagerService extends SystemService { LocalServices.getService(ActivityManagerInternal.class), createToastRateLimiter(), new PermissionHelper(LocalServices.getService( PermissionManagerServiceInternal.class), AppGlobals.getPackageManager(), - AppGlobals.getPermissionManager(), mEnableAppSettingMigration)); + AppGlobals.getPermissionManager(), mEnableAppSettingMigration), + LocalServices.getService(UsageStatsManagerInternal.class)); publishBinderService(Context.NOTIFICATION_SERVICE, mService, /* allowIsolated= */ false, DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PRIORITY_NORMAL); @@ -7259,6 +7263,8 @@ public class NotificationManagerService extends SystemService { if (index < 0) { mNotificationList.add(r); mUsageStats.registerPostedByApp(r); + mUsageStatsManagerInternal.reportNotificationPosted(r.getSbn().getOpPkg(), + r.getSbn().getUser(), SystemClock.elapsedRealtime()); final boolean isInterruptive = isVisuallyInterruptive(null, r); r.setInterruptive(isInterruptive); r.setTextChanged(isInterruptive); @@ -7266,6 +7272,8 @@ public class NotificationManagerService extends SystemService { old = mNotificationList.get(index); // Potentially *changes* old mNotificationList.set(index, r); mUsageStats.registerUpdatedByApp(r, old); + mUsageStatsManagerInternal.reportNotificationUpdated(r.getSbn().getOpPkg(), + r.getSbn().getUser(), SystemClock.elapsedRealtime()); // Make sure we don't lose the foreground service state. notification.flags |= old.getNotification().flags & FLAG_FOREGROUND_SERVICE; @@ -8748,6 +8756,8 @@ public class NotificationManagerService extends SystemService { case REASON_APP_CANCEL: case REASON_APP_CANCEL_ALL: mUsageStats.registerRemovedByApp(r); + mUsageStatsManagerInternal.reportNotificationRemoved(r.getSbn().getOpPkg(), + r.getUser(), SystemClock.elapsedRealtime()); break; } diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java index 5e333daed7d9..05f000c607d8 100644 --- a/services/core/java/com/android/server/notification/PreferencesHelper.java +++ b/services/core/java/com/android/server/notification/PreferencesHelper.java @@ -101,6 +101,8 @@ public class PreferencesHelper implements RankingConfig { @VisibleForTesting static final int NOTIFICATION_CHANNEL_COUNT_LIMIT = 50000; + @VisibleForTesting + static final int NOTIFICATION_CHANNEL_GROUP_COUNT_LIMIT = 50000; private static final int NOTIFICATION_PREFERENCES_PULL_LIMIT = 1000; private static final int NOTIFICATION_CHANNEL_PULL_LIMIT = 2000; @@ -254,6 +256,7 @@ public class PreferencesHelper implements RankingConfig { } } boolean skipWarningLogged = false; + boolean skipGroupWarningLogged = false; boolean hasSAWPermission = false; if (upgradeForBubbles && uid != UNKNOWN_UID) { hasSAWPermission = mAppOps.noteOpNoThrow( @@ -303,6 +306,14 @@ public class PreferencesHelper implements RankingConfig { String tagName = parser.getName(); // Channel groups if (TAG_GROUP.equals(tagName)) { + if (r.groups.size() >= NOTIFICATION_CHANNEL_GROUP_COUNT_LIMIT) { + if (!skipGroupWarningLogged) { + Slog.w(TAG, "Skipping further groups for " + r.pkg + + "; app has too many"); + skipGroupWarningLogged = true; + } + continue; + } String id = parser.getAttributeValue(null, ATT_ID); CharSequence groupName = parser.getAttributeValue(null, ATT_NAME); @@ -867,6 +878,9 @@ public class PreferencesHelper implements RankingConfig { } if (fromTargetApp) { group.setBlocked(false); + if (r.groups.size() >= NOTIFICATION_CHANNEL_GROUP_COUNT_LIMIT) { + throw new IllegalStateException("Limit exceed; cannot create more groups"); + } } final NotificationChannelGroup oldGroup = r.groups.get(group.getId()); if (oldGroup != null) { diff --git a/services/core/java/com/android/server/om/IdmapDaemon.java b/services/core/java/com/android/server/om/IdmapDaemon.java index c9e564a0ce9d..8e944b7a965d 100644 --- a/services/core/java/com/android/server/om/IdmapDaemon.java +++ b/services/core/java/com/android/server/om/IdmapDaemon.java @@ -37,13 +37,14 @@ import com.android.server.FgThread; import java.io.File; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; /** - * To prevent idmap2d from continuously running, the idmap daemon will terminate after 10 - * seconds without a transaction. + * To prevent idmap2d from continuously running, the idmap daemon will terminate after 10 seconds + * without a transaction. **/ class IdmapDaemon { // The amount of time in milliseconds to wait after a transaction to the idmap service is made @@ -67,11 +68,14 @@ class IdmapDaemon { * to the service is open. **/ private class Connection implements AutoCloseable { + @Nullable + private final IIdmap2 mIdmap2; private boolean mOpened = true; - private Connection() { + private Connection(IIdmap2 idmap2) { synchronized (mIdmapToken) { mOpenedCount.incrementAndGet(); + mIdmap2 = idmap2; } } @@ -102,6 +106,11 @@ class IdmapDaemon { }, mIdmapToken, SERVICE_TIMEOUT_MS); } } + + @Nullable + public IIdmap2 getIdmap2() { + return mIdmap2; + } } static IdmapDaemon getInstance() { @@ -115,14 +124,29 @@ class IdmapDaemon { @Nullable String overlayName, int policies, boolean enforce, int userId) throws TimeoutException, RemoteException { try (Connection c = connect()) { - return mService.createIdmap(targetPath, overlayPath, TextUtils.emptyIfNull(overlayName), + final IIdmap2 idmap2 = c.getIdmap2(); + if (idmap2 == null) { + Slog.w(TAG, "idmap2d service is not ready for createIdmap(\"" + targetPath + + "\", \"" + overlayPath + "\", \"" + overlayName + "\", " + policies + ", " + + enforce + ", " + userId + ")"); + return null; + } + + return idmap2.createIdmap(targetPath, overlayPath, TextUtils.emptyIfNull(overlayName), policies, enforce, userId); } } boolean removeIdmap(String overlayPath, int userId) throws TimeoutException, RemoteException { try (Connection c = connect()) { - return mService.removeIdmap(overlayPath, userId); + final IIdmap2 idmap2 = c.getIdmap2(); + if (idmap2 == null) { + Slog.w(TAG, "idmap2d service is not ready for removeIdmap(\"" + overlayPath + + "\", " + userId + ")"); + return false; + } + + return idmap2.removeIdmap(overlayPath, userId); } } @@ -130,14 +154,29 @@ class IdmapDaemon { @Nullable String overlayName, int policies, boolean enforce, int userId) throws Exception { try (Connection c = connect()) { - return mService.verifyIdmap(targetPath, overlayPath, TextUtils.emptyIfNull(overlayName), + final IIdmap2 idmap2 = c.getIdmap2(); + if (idmap2 == null) { + Slog.w(TAG, "idmap2d service is not ready for verifyIdmap(\"" + targetPath + + "\", \"" + overlayPath + "\", \"" + overlayName + "\", " + policies + ", " + + enforce + ", " + userId + ")"); + return false; + } + + return idmap2.verifyIdmap(targetPath, overlayPath, TextUtils.emptyIfNull(overlayName), policies, enforce, userId); } } boolean idmapExists(String overlayPath, int userId) { try (Connection c = connect()) { - return new File(mService.getIdmapPath(overlayPath, userId)).isFile(); + final IIdmap2 idmap2 = c.getIdmap2(); + if (idmap2 == null) { + Slog.w(TAG, "idmap2d service is not ready for idmapExists(\"" + overlayPath + + "\", " + userId + ")"); + return false; + } + + return new File(idmap2.getIdmapPath(overlayPath, userId)).isFile(); } catch (Exception e) { Slog.wtf(TAG, "failed to check if idmap exists for " + overlayPath, e); return false; @@ -146,7 +185,13 @@ class IdmapDaemon { FabricatedOverlayInfo createFabricatedOverlay(@NonNull FabricatedOverlayInternal overlay) { try (Connection c = connect()) { - return mService.createFabricatedOverlay(overlay); + final IIdmap2 idmap2 = c.getIdmap2(); + if (idmap2 == null) { + Slog.w(TAG, "idmap2d service is not ready for createFabricatedOverlay()"); + return null; + } + + return idmap2.createFabricatedOverlay(overlay); } catch (Exception e) { Slog.wtf(TAG, "failed to fabricate overlay " + overlay, e); return null; @@ -155,7 +200,14 @@ class IdmapDaemon { boolean deleteFabricatedOverlay(@NonNull String path) { try (Connection c = connect()) { - return mService.deleteFabricatedOverlay(path); + final IIdmap2 idmap2 = c.getIdmap2(); + if (idmap2 == null) { + Slog.w(TAG, "idmap2d service is not ready for deleteFabricatedOverlay(\"" + path + + "\")"); + return false; + } + + return idmap2.deleteFabricatedOverlay(path); } catch (Exception e) { Slog.wtf(TAG, "failed to delete fabricated overlay '" + path + "'", e); return false; @@ -164,10 +216,18 @@ class IdmapDaemon { synchronized List<FabricatedOverlayInfo> getFabricatedOverlayInfos() { final ArrayList<FabricatedOverlayInfo> allInfos = new ArrayList<>(); - try (Connection c = connect()) { - mService.acquireFabricatedOverlayIterator(); + Connection c = null; + try { + c = connect(); + final IIdmap2 service = c.getIdmap2(); + if (service == null) { + Slog.w(TAG, "idmap2d service is not ready for getFabricatedOverlayInfos()"); + return Collections.emptyList(); + } + + service.acquireFabricatedOverlayIterator(); List<FabricatedOverlayInfo> infos; - while (!(infos = mService.nextFabricatedOverlayInfos()).isEmpty()) { + while (!(infos = service.nextFabricatedOverlayInfos()).isEmpty()) { allInfos.addAll(infos); } return allInfos; @@ -175,17 +235,26 @@ class IdmapDaemon { Slog.wtf(TAG, "failed to get all fabricated overlays", e); } finally { try { - mService.releaseFabricatedOverlayIterator(); + if (c.getIdmap2() != null) { + c.getIdmap2().releaseFabricatedOverlayIterator(); + } } catch (RemoteException e) { // ignore } + c.close(); } return allInfos; } String dumpIdmap(@NonNull String overlayPath) { try (Connection c = connect()) { - String dump = mService.dumpIdmap(overlayPath); + final IIdmap2 service = c.getIdmap2(); + if (service == null) { + final String dumpText = "idmap2d service is not ready for dumpIdmap()"; + Slog.w(TAG, dumpText); + return dumpText; + } + String dump = service.dumpIdmap(overlayPath); return TextUtils.nullIfEmpty(dump); } catch (Exception e) { Slog.wtf(TAG, "failed to dump idmap", e); @@ -193,8 +262,16 @@ class IdmapDaemon { } } + @Nullable private IBinder getIdmapService() throws TimeoutException, RemoteException { - SystemService.start(IDMAP_DAEMON); + try { + SystemService.start(IDMAP_DAEMON); + } catch (RuntimeException e) { + if (e.getMessage().contains("failed to set system property")) { + Slog.w(TAG, "Failed to enable idmap2 daemon", e); + return null; + } + } final long endMillis = SystemClock.elapsedRealtime() + SERVICE_CONNECT_TIMEOUT_MS; while (SystemClock.elapsedRealtime() <= endMillis) { @@ -226,17 +303,23 @@ class IdmapDaemon { } } + @NonNull private Connection connect() throws TimeoutException, RemoteException { synchronized (mIdmapToken) { FgThread.getHandler().removeCallbacksAndMessages(mIdmapToken); if (mService != null) { // Not enough time has passed to stop the idmap service. Reuse the existing // interface. - return new Connection(); + return new Connection(mService); + } + + IBinder binder = getIdmapService(); + if (binder == null) { + return new Connection(null); } - mService = IIdmap2.Stub.asInterface(getIdmapService()); - return new Connection(); + mService = IIdmap2.Stub.asInterface(binder); + return new Connection(mService); } } } diff --git a/services/core/java/com/android/server/pm/ApexManager.java b/services/core/java/com/android/server/pm/ApexManager.java index 6f10a6bbb9ca..2e9ad50f23f6 100644 --- a/services/core/java/com/android/server/pm/ApexManager.java +++ b/services/core/java/com/android/server/pm/ApexManager.java @@ -45,6 +45,7 @@ import android.os.Trace; import android.sysprop.ApexProperties; import android.util.ArrayMap; import android.util.ArraySet; +import android.util.PrintWriterPrinter; import android.util.Singleton; import android.util.Slog; import android.util.SparseArray; @@ -1164,6 +1165,10 @@ public abstract class ApexManager { ipw.println("Path: " + pi.applicationInfo.sourceDir); ipw.println("IsActive: " + isActive(pi)); ipw.println("IsFactory: " + isFactory(pi)); + ipw.println("ApplicationInfo: "); + ipw.increaseIndent(); + pi.applicationInfo.dump(new PrintWriterPrinter(ipw), ""); + ipw.decreaseIndent(); ipw.decreaseIndent(); } ipw.decreaseIndent(); diff --git a/services/core/java/com/android/server/pm/AppDataHelper.java b/services/core/java/com/android/server/pm/AppDataHelper.java index a66af3cf7603..4b999e9fac7f 100644 --- a/services/core/java/com/android/server/pm/AppDataHelper.java +++ b/services/core/java/com/android/server/pm/AppDataHelper.java @@ -24,7 +24,6 @@ import static com.android.server.pm.PackageManagerServiceUtils.logCriticalInfo; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.pm.PackageManager; -import com.android.server.pm.pkg.SELinuxUtil; import android.content.pm.UserInfo; import android.os.CreateAppDataArgs; import android.os.Environment; @@ -35,6 +34,9 @@ import android.os.UserHandle; import android.os.storage.StorageManager; import android.os.storage.StorageManagerInternal; import android.os.storage.VolumeInfo; +import android.security.AndroidKeyStoreMaintenance; +import android.system.keystore2.Domain; +import android.system.keystore2.KeyDescriptor; import android.text.TextUtils; import android.util.Log; import android.util.Slog; @@ -46,6 +48,7 @@ import com.android.server.SystemServerInitThreadPool; import com.android.server.pm.dex.ArtManagerService; import com.android.server.pm.parsing.pkg.AndroidPackage; import com.android.server.pm.parsing.pkg.AndroidPackageUtils; +import com.android.server.pm.pkg.SELinuxUtil; import dalvik.system.VMRuntime; @@ -156,8 +159,7 @@ final class AppDataHelper { * <ul> * <li>If previousAppId < 0, app data will be migrated to the new app ID * <li>If previousAppId == 0, no migration will happen and data will be wiped and recreated - * <li>If previousAppId > 0, it will migrate all data owned by previousAppId - * to the new app ID + * <li>If previousAppId > 0, app data owned by previousAppId will be migrated to the new app ID * </ul> */ private @NonNull CompletableFuture<?> prepareAppData(@NonNull Installer.Batch batch, @@ -476,13 +478,9 @@ final class AppDataHelper { } else if (!ps.getInstalled(userId)) { throw new PackageManagerException( "Package " + packageName + " not installed for user " + userId); - } else if (ps.getPkg() == null) { - throw new PackageManagerException("Package " + packageName + " is not parsed yet"); - } else { - if (!shouldHaveAppStorage(ps.getPkg())) { - throw new PackageManagerException( - "Package " + packageName + " shouldn't have storage"); - } + } else if (ps.getPkg() != null && !shouldHaveAppStorage(ps.getPkg())) { + throw new PackageManagerException( + "Package " + packageName + " shouldn't have storage"); } } } @@ -549,6 +547,22 @@ final class AppDataHelper { return prepareAppDataFuture; } + public void migrateKeyStoreData(int previousAppId, int appId) { + for (int userId : mPm.resolveUserIds(UserHandle.USER_ALL)) { + int srcUid = UserHandle.getUid(userId, previousAppId); + int destUid = UserHandle.getUid(userId, appId); + final KeyDescriptor[] keys = AndroidKeyStoreMaintenance.listEntries(Domain.APP, srcUid); + if (keys == null) continue; + for (final KeyDescriptor key : keys) { + KeyDescriptor dest = new KeyDescriptor(); + dest.domain = Domain.APP; + dest.nspace = destUid; + dest.alias = key.alias; + AndroidKeyStoreMaintenance.migrateKeyNamespace(key, dest); + } + } + } + void clearAppDataLIF(AndroidPackage pkg, int userId, int flags) { if (pkg == null) { return; @@ -633,4 +647,18 @@ final class AppDataHelper { pkg.getProperties().get(PackageManager.PROPERTY_NO_APP_DATA_STORAGE); return noAppDataProp == null || !noAppDataProp.getBoolean(); } + + /** + * Remove entries from the keystore daemon. Will only remove if the {@code appId} is valid. + */ + public void clearKeystoreData(int userId, int appId) { + if (appId < 0) { + return; + } + + for (int realUserId : mPm.resolveUserIds(userId)) { + AndroidKeyStoreMaintenance.clearNamespace( + Domain.APP, UserHandle.getUid(realUserId, appId)); + } + } } diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java index 2aa0e0174d0d..69c475a05a93 100644 --- a/services/core/java/com/android/server/pm/ComputerEngine.java +++ b/services/core/java/com/android/server/pm/ComputerEngine.java @@ -20,6 +20,7 @@ import static android.Manifest.permission.DELETE_PACKAGES; import static android.Manifest.permission.INSTALL_PACKAGES; import static android.Manifest.permission.REQUEST_DELETE_PACKAGES; import static android.Manifest.permission.SET_HARMFUL_APP_WARNINGS; +import static android.content.ContentProvider.isAuthorityRedirectedForCloneProfile; import static android.content.Intent.ACTION_MAIN; import static android.content.Intent.CATEGORY_DEFAULT; import static android.content.Intent.CATEGORY_HOME; @@ -92,14 +93,6 @@ import android.content.pm.SigningDetails; import android.content.pm.SigningInfo; import android.content.pm.UserInfo; import android.content.pm.VersionedPackage; -import com.android.server.pm.pkg.parsing.PackageInfoWithoutStateUtils; -import com.android.server.pm.pkg.component.ParsedActivity; -import com.android.server.pm.pkg.component.ParsedInstrumentation; -import com.android.server.pm.pkg.component.ParsedIntentInfo; -import com.android.server.pm.pkg.component.ParsedMainComponent; -import com.android.server.pm.pkg.component.ParsedProvider; -import com.android.server.pm.pkg.component.ParsedService; -import com.android.server.pm.pkg.PackageUserStateUtils; import android.os.Binder; import android.os.Build; import android.os.IBinder; @@ -142,6 +135,14 @@ import com.android.server.pm.pkg.PackageStateInternal; import com.android.server.pm.pkg.PackageStateUtils; import com.android.server.pm.pkg.PackageUserState; import com.android.server.pm.pkg.PackageUserStateInternal; +import com.android.server.pm.pkg.PackageUserStateUtils; +import com.android.server.pm.pkg.component.ParsedActivity; +import com.android.server.pm.pkg.component.ParsedInstrumentation; +import com.android.server.pm.pkg.component.ParsedIntentInfo; +import com.android.server.pm.pkg.component.ParsedMainComponent; +import com.android.server.pm.pkg.component.ParsedProvider; +import com.android.server.pm.pkg.component.ParsedService; +import com.android.server.pm.pkg.parsing.PackageInfoWithoutStateUtils; import com.android.server.pm.verify.domain.DomainVerificationManagerInternal; import com.android.server.pm.verify.domain.DomainVerificationUtils; import com.android.server.uri.UriGrantsManagerInternal; @@ -350,7 +351,6 @@ public class ComputerEngine implements Computer { private final CompilerStats mCompilerStats; private final BackgroundDexOptService mBackgroundDexOptService; private final PackageManagerInternal.ExternalSourcesPolicy mExternalSourcesPolicy; - private final ProtectedPackages mProtectedPackages; // PackageManagerService attributes that are primitives are referenced through the // pms object directly. Primitives are the only attributes so referenced. @@ -402,7 +402,6 @@ public class ComputerEngine implements Computer { mCompilerStats = args.service.mCompilerStats; mBackgroundDexOptService = args.service.mBackgroundDexOptService; mExternalSourcesPolicy = args.service.mExternalSourcesPolicy; - mProtectedPackages = args.service.mProtectedPackages; // Used to reference PMS attributes that are primitives and which are not // updated under control of the PMS lock. @@ -4619,8 +4618,24 @@ public class ComputerEngine implements Computer { } } if (!checkedGrants) { - enforceCrossUserPermission(callingUid, userId, false, false, "resolveContentProvider"); + boolean enforceCrossUser = true; + + if (isAuthorityRedirectedForCloneProfile(name)) { + final UserManagerInternal umInternal = mInjector.getUserManagerInternal(); + + UserInfo userInfo = umInternal.getUserInfo(UserHandle.getUserId(callingUid)); + if (userInfo != null && userInfo.isCloneProfile() + && userInfo.profileGroupId == userId) { + enforceCrossUser = false; + } + } + + if (enforceCrossUser) { + enforceCrossUserPermission(callingUid, userId, false, false, + "resolveContentProvider"); + } } + if (providerInfo == null) { return null; } diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java index 9a80a4e558c4..48689a8da335 100644 --- a/services/core/java/com/android/server/pm/DeletePackageHelper.java +++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java @@ -485,8 +485,7 @@ final class DeletePackageHelper { mAppDataHelper.destroyAppDataLIF(pkg, nextUserId, FLAG_STORAGE_DE | FLAG_STORAGE_CE | FLAG_STORAGE_EXTERNAL); } - PackageManagerService.removeKeystoreDataIfNeeded(mUserManagerInternal, nextUserId, - ps.getAppId()); + mAppDataHelper.clearKeystoreData(nextUserId, ps.getAppId()); preferredActivityHelper.clearPackagePreferredActivities(ps.getPackageName(), nextUserId); mPm.mDomainVerificationManager.clearPackageForUser(ps.getPackageName(), nextUserId); diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index 6a5d76bc248b..80699ac5dd82 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -2245,6 +2245,17 @@ final class InstallPackageHelper { if (reconciledPkg.mScanResult.needsNewAppId()) { // Only set previousAppId if the app is migrating out of shared UID previousAppId = reconciledPkg.mScanResult.mPreviousAppId; + + if (pkg.shouldInheritKeyStoreKeys()) { + // Migrate keystore data + mAppDataHelper.migrateKeyStoreData( + previousAppId, reconciledPkg.mPkgSetting.getAppId()); + } + + if (reconciledPkg.mInstallResult.mRemovedInfo.mRemovedAppId == previousAppId) { + // If the previous app ID is removed, clear the keys + mAppDataHelper.clearKeystoreData(UserHandle.USER_ALL, previousAppId); + } } mAppDataHelper.prepareAppDataPostCommitLIF(pkg, previousAppId); if (reconciledPkg.mPrepareResult.mClearCodeCache) { diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index 1f10d77086d3..ccc375ff85f2 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -16,6 +16,8 @@ package com.android.server.pm; +import static android.app.admin.DevicePolicyResources.Strings.Core.PACKAGE_DELETED_BY_DO; + import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT; import static org.xmlpull.v1.XmlPullParser.START_TAG; @@ -29,6 +31,7 @@ import android.app.Notification; import android.app.NotificationManager; import android.app.PackageDeleteObserver; import android.app.admin.DevicePolicyEventLogger; +import android.app.admin.DevicePolicyManager; import android.app.admin.DevicePolicyManagerInternal; import android.content.Context; import android.content.Intent; @@ -1312,7 +1315,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements mPackageName = packageName; if (showNotification) { mNotification = buildSuccessNotification(mContext, - mContext.getResources().getString(R.string.package_deleted_device_owner), + getDeviceOwnerDeletedPackageMsg(), packageName, userId); } else { @@ -1320,6 +1323,12 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements } } + private String getDeviceOwnerDeletedPackageMsg() { + DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class); + return dpm.getString(PACKAGE_DELETED_BY_DO, + () -> mContext.getString(R.string.package_updated_device_owner)); + } + @Override public void onUserActionRequired(Intent intent) { if (mTarget == null) { diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index e0f1b0b44cf8..d9ade967d0b4 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -17,6 +17,8 @@ package com.android.server.pm; import static android.app.AppOpsManager.MODE_DEFAULT; +import static android.app.admin.DevicePolicyResources.Strings.Core.PACKAGE_INSTALLED_BY_DO; +import static android.app.admin.DevicePolicyResources.Strings.Core.PACKAGE_UPDATED_BY_DO; import static android.content.pm.DataLoaderType.INCREMENTAL; import static android.content.pm.DataLoaderType.STREAMING; import static android.content.pm.PackageInstaller.LOCATION_DATA_APP; @@ -56,6 +58,7 @@ import android.app.AppOpsManager; import android.app.Notification; import android.app.NotificationManager; import android.app.admin.DevicePolicyEventLogger; +import android.app.admin.DevicePolicyManager; import android.app.admin.DevicePolicyManagerInternal; import android.content.ComponentName; import android.content.Context; @@ -91,7 +94,6 @@ import android.content.pm.dex.DexMetadataHelper; import android.content.pm.parsing.ApkLite; import android.content.pm.parsing.ApkLiteParseUtils; import android.content.pm.parsing.PackageLite; -import com.android.server.pm.pkg.parsing.ParsingPackageUtils; import android.content.pm.parsing.result.ParseResult; import android.content.pm.parsing.result.ParseTypeImpl; import android.graphics.Bitmap; @@ -156,6 +158,7 @@ import com.android.server.pm.Installer.InstallerException; import com.android.server.pm.dex.DexManager; import com.android.server.pm.parsing.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageStateInternal; +import com.android.server.pm.pkg.parsing.ParsingPackageUtils; import libcore.io.IoUtils; import libcore.util.EmptyArray; @@ -4336,9 +4339,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { if (INSTALL_SUCCEEDED == returnCode && showNotification) { boolean update = (extras != null) && extras.getBoolean(Intent.EXTRA_REPLACING); Notification notification = PackageInstallerService.buildSuccessNotification(context, - context.getResources() - .getString(update ? R.string.package_updated_device_owner : - R.string.package_installed_device_owner), + getDeviceOwnerInstalledPackageMsg(context, update), basePackageName, userId); if (notification != null) { @@ -4370,6 +4371,15 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } } + private static String getDeviceOwnerInstalledPackageMsg(Context context, boolean update) { + DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class); + return update + ? dpm.getString(PACKAGE_UPDATED_BY_DO, + () -> context.getString(R.string.package_updated_device_owner)) + : dpm.getString(PACKAGE_INSTALLED_BY_DO, + () -> context.getString(R.string.package_installed_device_owner)); + } + /** * This method doesn't change internal states and is safe to call outside the lock. */ diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 13f91e0dca62..e00f4f591d54 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -167,7 +167,6 @@ import android.permission.PermissionManager; import android.provider.DeviceConfig; import android.provider.Settings.Global; import android.provider.Settings.Secure; -import android.security.KeyStore; import android.text.TextUtils; import android.text.format.DateUtils; import android.util.ArrayMap; @@ -234,8 +233,6 @@ import com.android.server.pm.pkg.PackageState; import com.android.server.pm.pkg.PackageStateInternal; import com.android.server.pm.pkg.PackageStateUtils; import com.android.server.pm.pkg.PackageUserState; -import com.android.server.pm.pkg.PackageUserStateInternal; -import com.android.server.pm.pkg.SuspendParams; import com.android.server.pm.pkg.component.ParsedInstrumentation; import com.android.server.pm.pkg.component.ParsedMainComponent; import com.android.server.pm.pkg.mutate.PackageStateMutator; @@ -291,7 +288,6 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; import java.util.function.Function; -import java.util.function.Predicate; /** * Keep track of all those APKs everywhere. @@ -954,6 +950,7 @@ public class PackageManagerService extends IPackageManager.Stub final @Nullable String mRetailDemoPackage; final @Nullable String mOverlayConfigSignaturePackage; final @Nullable String mRecentsPackage; + final @Nullable String mAmbientContextDetectionPackage; @GuardedBy("mLock") private final PackageUsage mPackageUsage = new PackageUsage(); @@ -970,6 +967,7 @@ public class PackageManagerService extends IPackageManager.Stub private final PreferredActivityHelper mPreferredActivityHelper; private final ResolveIntentHelper mResolveIntentHelper; private final DexOptHelper mDexOptHelper; + private final SuspendPackageHelper mSuspendPackageHelper; /** * Invalidate the package info cache, which includes updating the cached computer. @@ -1671,6 +1669,7 @@ public class PackageManagerService extends IPackageManager.Stub mSystemTextClassifierPackageName = testParams.systemTextClassifierPackage; mRetailDemoPackage = testParams.retailDemoPackage; mRecentsPackage = testParams.recentsPackage; + mAmbientContextDetectionPackage = testParams.ambientContextDetectionPackage; mConfiguratorPackage = testParams.configuratorPackage; mAppPredictionServicePackage = testParams.appPredictionServicePackage; mIncidentReportApproverPackage = testParams.incidentReportApproverPackage; @@ -1705,6 +1704,7 @@ public class PackageManagerService extends IPackageManager.Stub mPreferredActivityHelper = testParams.preferredActivityHelper; mResolveIntentHelper = testParams.resolveIntentHelper; mDexOptHelper = testParams.dexOptHelper; + mSuspendPackageHelper = testParams.suspendPackageHelper; mSharedLibraries.setDeletePackageHelper(mDeletePackageHelper); invalidatePackageInfoCache(); @@ -1851,6 +1851,8 @@ public class PackageManagerService extends IPackageManager.Stub mPreferredActivityHelper = new PreferredActivityHelper(this); mResolveIntentHelper = new ResolveIntentHelper(this, mPreferredActivityHelper); mDexOptHelper = new DexOptHelper(this); + mSuspendPackageHelper = new SuspendPackageHelper(this, mInjector, mBroadcastHelper, + mProtectedPackages); synchronized (mLock) { // Create the computer as soon as the state objects have been installed. The @@ -1995,6 +1997,7 @@ public class PackageManagerService extends IPackageManager.Stub mRetailDemoPackage = getRetailDemoPackageName(); mOverlayConfigSignaturePackage = getOverlayConfigSignaturePackageName(); mRecentsPackage = getRecentsPackageName(); + mAmbientContextDetectionPackage = getAmbientContextDetectionPackageName(); // Now that we know all of the shared libraries, update all clients to have // the correct library paths. @@ -2682,7 +2685,7 @@ public class PackageManagerService extends IPackageManager.Stub return mComputer.getPackageUid(packageName, flags, userId); } - private int getPackageUidInternal(String packageName, + int getPackageUidInternal(String packageName, @PackageManager.PackageInfoFlagsBits long flags, int userId, int callingUid) { return mComputer.getPackageUidInternal(packageName, flags, userId, callingUid); } @@ -4294,51 +4297,6 @@ public class PackageManagerService extends IPackageManager.Stub info.sendPackageRemovedBroadcasts(true /*killApp*/, false /*removedBySystem*/); } - @VisibleForTesting(visibility = Visibility.PRIVATE) - void sendPackagesSuspendedForUser(String intent, String[] pkgList, int[] uidList, int userId) { - final List<List<String>> pkgsToSend = new ArrayList(pkgList.length); - final List<IntArray> uidsToSend = new ArrayList(pkgList.length); - final List<SparseArray<int[]>> allowListsToSend = new ArrayList(pkgList.length); - final int[] userIds = new int[] {userId}; - // Get allow lists for the pkg in the pkgList. Merge into the existed pkgs and uids if - // allow lists are the same. - for (int i = 0; i < pkgList.length; i++) { - final String pkgName = pkgList[i]; - final int uid = uidList[i]; - SparseArray<int[]> allowList = mAppsFilter.getVisibilityAllowList( - getPackageStateInternal(pkgName, Process.SYSTEM_UID), - userIds, getPackageStates()); - if (allowList == null) { - allowList = new SparseArray<>(0); - } - boolean merged = false; - for (int j = 0; j < allowListsToSend.size(); j++) { - if (Arrays.equals(allowListsToSend.get(j).get(userId), allowList.get(userId))) { - pkgsToSend.get(j).add(pkgName); - uidsToSend.get(j).add(uid); - merged = true; - break; - } - } - if (!merged) { - pkgsToSend.add(new ArrayList<>(Arrays.asList(pkgName))); - uidsToSend.add(IntArray.wrap(new int[] {uid})); - allowListsToSend.add(allowList); - } - } - - for (int i = 0; i < pkgsToSend.size(); i++) { - final Bundle extras = new Bundle(3); - extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, - pkgsToSend.get(i).toArray(new String[pkgsToSend.get(i).size()])); - extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, uidsToSend.get(i).toArray()); - final SparseArray<int[]> allowList = allowListsToSend.get(i).size() == 0 - ? null : allowListsToSend.get(i); - sendPackageBroadcast(intent, null, extras, Intent.FLAG_RECEIVER_REGISTERED_ONLY, null, - null, userIds, null, allowList, null); - } - } - /** * Returns true if application is not found or there was an error. Otherwise it returns * the hidden state of the package for the given user. @@ -4381,7 +4339,8 @@ public class PackageManagerService extends IPackageManager.Stub + userId); } Objects.requireNonNull(packageNames, "packageNames cannot be null"); - if (restrictionFlags != 0 && !isSuspendAllowedForUser(userId)) { + if (restrictionFlags != 0 + && !mSuspendPackageHelper.isSuspendAllowedForUser(userId, callingUid)) { Slog.w(TAG, "Cannot restrict packages due to restrictions on user " + userId); return packageNames; } @@ -4389,8 +4348,9 @@ public class PackageManagerService extends IPackageManager.Stub final List<String> changedPackagesList = new ArrayList<>(packageNames.length); final IntArray changedUids = new IntArray(packageNames.length); final List<String> unactionedPackages = new ArrayList<>(packageNames.length); - final boolean[] canRestrict = (restrictionFlags != 0) ? canSuspendPackageForUserInternal( - packageNames, userId) : null; + final boolean[] canRestrict = (restrictionFlags != 0) + ? mSuspendPackageHelper.canSuspendPackageForUser(packageNames, userId, callingUid) + : null; for (int i = 0; i < packageNames.length; i++) { final String packageName = packageNames[i]; @@ -4469,84 +4429,8 @@ public class PackageManagerService extends IPackageManager.Stub final int callingUid = Binder.getCallingUid(); enforceCanSetPackagesSuspendedAsUser(callingPackage, callingUid, userId, "setPackagesSuspendedAsUser"); - - if (ArrayUtils.isEmpty(packageNames)) { - return packageNames; - } - if (suspended && !isSuspendAllowedForUser(userId)) { - Slog.w(TAG, "Cannot suspend due to restrictions on user " + userId); - return packageNames; - } - - final List<String> changedPackagesList = new ArrayList<>(packageNames.length); - final IntArray changedUids = new IntArray(packageNames.length); - final List<String> modifiedPackagesList = new ArrayList<>(packageNames.length); - final IntArray modifiedUids = new IntArray(packageNames.length); - final List<String> unactionedPackages = new ArrayList<>(packageNames.length); - final boolean[] canSuspend = suspended ? canSuspendPackageForUserInternal(packageNames, - userId) : null; - - for (int i = 0; i < packageNames.length; i++) { - final String packageName = packageNames[i]; - if (callingPackage.equals(packageName)) { - Slog.w(TAG, "Calling package: " + callingPackage + " trying to " - + (suspended ? "" : "un") + "suspend itself. Ignoring"); - unactionedPackages.add(packageName); - continue; - } - final PackageSetting pkgSetting; - synchronized (mLock) { - pkgSetting = mSettings.getPackageLPr(packageName); - if (pkgSetting == null - || shouldFilterApplication(pkgSetting, callingUid, userId)) { - Slog.w(TAG, "Could not find package setting for package: " + packageName - + ". Skipping suspending/un-suspending."); - unactionedPackages.add(packageName); - continue; - } - } - if (canSuspend != null && !canSuspend[i]) { - unactionedPackages.add(packageName); - continue; - } - final boolean packageUnsuspended; - final boolean packageModified; - synchronized (mLock) { - if (suspended) { - packageModified = pkgSetting.addOrUpdateSuspension(callingPackage, - dialogInfo, appExtras, launcherExtras, userId); - } else { - packageModified = pkgSetting.removeSuspension(callingPackage, userId); - } - packageUnsuspended = !suspended && !pkgSetting.getSuspended(userId); - } - if (suspended || packageUnsuspended) { - changedPackagesList.add(packageName); - changedUids.add(UserHandle.getUid(userId, pkgSetting.getAppId())); - } - if (packageModified) { - modifiedPackagesList.add(packageName); - modifiedUids.add(UserHandle.getUid(userId, pkgSetting.getAppId())); - } - } - - if (!changedPackagesList.isEmpty()) { - final String[] changedPackages = changedPackagesList.toArray(new String[0]); - sendPackagesSuspendedForUser( - suspended ? Intent.ACTION_PACKAGES_SUSPENDED - : Intent.ACTION_PACKAGES_UNSUSPENDED, - changedPackages, changedUids.toArray(), userId); - sendMyPackageSuspendedOrUnsuspended(changedPackages, suspended, userId); - synchronized (mLock) { - scheduleWritePackageRestrictionsLocked(userId); - } - } - // Send the suspension changed broadcast to ensure suspension state is not stale. - if (!modifiedPackagesList.isEmpty()) { - sendPackagesSuspendedForUser(Intent.ACTION_PACKAGES_SUSPENSION_CHANGED, - modifiedPackagesList.toArray(new String[0]), modifiedUids.toArray(), userId); - } - return unactionedPackages.toArray(new String[0]); + return mSuspendPackageHelper.setPackagesSuspended(packageNames, suspended, appExtras, + launcherExtras, dialogInfo, callingPackage, userId, callingUid); } @Override @@ -4556,56 +4440,8 @@ public class PackageManagerService extends IPackageManager.Stub throw new SecurityException("Calling package " + packageName + " does not belong to calling uid " + callingUid); } - return getSuspendedPackageAppExtrasInternal(packageName, userId); - } - - private Bundle getSuspendedPackageAppExtrasInternal(String packageName, int userId) { - final PackageStateInternal ps = getPackageStateInternal(packageName); - if (ps == null) { - return null; - } - final PackageUserStateInternal pus = ps.getUserStateOrDefault(userId); - final Bundle allExtras = new Bundle(); - if (pus.isSuspended()) { - for (int i = 0; i < pus.getSuspendParams().size(); i++) { - final SuspendParams params = pus.getSuspendParams().valueAt(i); - if (params != null && params.getAppExtras() != null) { - allExtras.putAll(params.getAppExtras()); - } - } - } - return (allExtras.size() > 0) ? allExtras : null; - } - - private void sendMyPackageSuspendedOrUnsuspended(String[] affectedPackages, boolean suspended, - int userId) { - final String action = suspended - ? Intent.ACTION_MY_PACKAGE_SUSPENDED - : Intent.ACTION_MY_PACKAGE_UNSUSPENDED; - mHandler.post(() -> { - final IActivityManager am = ActivityManager.getService(); - if (am == null) { - Slog.wtf(TAG, "IActivityManager null. Cannot send MY_PACKAGE_ " - + (suspended ? "" : "UN") + "SUSPENDED broadcasts"); - return; - } - final int[] targetUserIds = new int[] {userId}; - for (String packageName : affectedPackages) { - final Bundle appExtras = suspended - ? getSuspendedPackageAppExtrasInternal(packageName, userId) - : null; - final Bundle intentExtras; - if (appExtras != null) { - intentExtras = new Bundle(1); - intentExtras.putBundle(Intent.EXTRA_SUSPENDED_PACKAGE_EXTRAS, appExtras); - } else { - intentExtras = null; - } - mHandler.post(() -> mBroadcastHelper.doSendBroadcast(action, null, intentExtras, - Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND, packageName, null, - targetUserIds, false, null, null)); - } - }); + return mSuspendPackageHelper.getSuspendedPackageAppExtras( + packageName, userId, callingUid); } @Override @@ -4618,50 +4454,14 @@ public class PackageManagerService extends IPackageManager.Stub synchronized (mLock) { allPackages = mPackages.keySet().toArray(new String[mPackages.size()]); } - removeSuspensionsBySuspendingPackage(allPackages, suspendingPackage::equals, userId); + mSuspendPackageHelper.removeSuspensionsBySuspendingPackage( + allPackages, suspendingPackage::equals, userId); } private boolean isSuspendingAnyPackages(String suspendingPackage, int userId) { return mComputer.isSuspendingAnyPackages(suspendingPackage, userId); } - /** - * Removes any suspensions on given packages that were added by packages that pass the given - * predicate. - * - * <p> Caller must flush package restrictions if it cares about immediate data consistency. - * - * @param packagesToChange The packages on which the suspension are to be removed. - * @param suspendingPackagePredicate A predicate identifying the suspending packages whose - * suspensions will be removed. - * @param userId The user for which the changes are taking place. - */ - private void removeSuspensionsBySuspendingPackage(String[] packagesToChange, - Predicate<String> suspendingPackagePredicate, int userId) { - final List<String> unsuspendedPackages = new ArrayList<>(); - final IntArray unsuspendedUids = new IntArray(); - synchronized (mLock) { - for (String packageName : packagesToChange) { - final PackageSetting ps = mSettings.getPackageLPr(packageName); - if (ps != null && ps.getUserStateOrDefault(userId).isSuspended()) { - ps.removeSuspension(suspendingPackagePredicate, userId); - if (!ps.getUserStateOrDefault(userId).isSuspended()) { - unsuspendedPackages.add(ps.getPackageName()); - unsuspendedUids.add(UserHandle.getUid(userId, ps.getAppId())); - } - } - } - scheduleWritePackageRestrictionsLocked(userId); - } - if (!unsuspendedPackages.isEmpty()) { - final String[] packageArray = unsuspendedPackages.toArray( - new String[unsuspendedPackages.size()]); - sendMyPackageSuspendedOrUnsuspended(packageArray, false, userId); - sendPackagesSuspendedForUser(Intent.ACTION_PACKAGES_UNSUSPENDED, - packageArray, unsuspendedUids.toArray(), userId); - } - } - void removeAllDistractingPackageRestrictions(int userId) { final String[] allPackages = mComputer.getAllAvailablePackageNames(); removeDistractingPackageRestrictions(allPackages, userId); @@ -4698,24 +4498,6 @@ public class PackageManagerService extends IPackageManager.Stub } } - private boolean isCallerDeviceOrProfileOwner(int userId) { - final int callingUid = Binder.getCallingUid(); - if (callingUid == Process.SYSTEM_UID) { - return true; - } - final String ownerPackage = mProtectedPackages.getDeviceOwnerOrProfileOwnerPackage(userId); - if (ownerPackage != null) { - return callingUid == getPackageUidInternal(ownerPackage, 0, userId, callingUid); - } - return false; - } - - private boolean isSuspendAllowedForUser(int userId) { - return isCallerDeviceOrProfileOwner(userId) - || (!mUserManager.hasUserRestriction(UserManager.DISALLOW_APPS_CONTROL, userId) - && !mUserManager.hasUserRestriction(UserManager.DISALLOW_UNINSTALL_APPS, userId)); - } - @Override public String[] getUnsuspendablePackagesForUser(String[] packageNames, int userId) { Objects.requireNonNull(packageNames, "packageNames cannot be null"); @@ -4726,125 +4508,8 @@ public class PackageManagerService extends IPackageManager.Stub throw new SecurityException("Calling uid " + callingUid + " cannot query getUnsuspendablePackagesForUser for user " + userId); } - if (!isSuspendAllowedForUser(userId)) { - Slog.w(TAG, "Cannot suspend due to restrictions on user " + userId); - return packageNames; - } - final ArraySet<String> unactionablePackages = new ArraySet<>(); - final boolean[] canSuspend = canSuspendPackageForUserInternal(packageNames, userId); - for (int i = 0; i < packageNames.length; i++) { - if (!canSuspend[i]) { - unactionablePackages.add(packageNames[i]); - continue; - } - synchronized (mLock) { - final PackageSetting ps = mSettings.getPackageLPr(packageNames[i]); - if (ps == null || shouldFilterApplication(ps, callingUid, userId)) { - Slog.w(TAG, "Could not find package setting for package: " + packageNames[i]); - unactionablePackages.add(packageNames[i]); - } - } - } - return unactionablePackages.toArray(new String[unactionablePackages.size()]); - } - - /** - * Returns an array of booleans, such that the ith boolean denotes whether the ith package can - * be suspended or not. - * - * @param packageNames The package names to check suspendability for. - * @param userId The user to check in - * @return An array containing results of the checks - */ - @NonNull - private boolean[] canSuspendPackageForUserInternal(@NonNull String[] packageNames, int userId) { - final boolean[] canSuspend = new boolean[packageNames.length]; - final boolean isCallerOwner = isCallerDeviceOrProfileOwner(userId); - final long callingId = Binder.clearCallingIdentity(); - try { - final String activeLauncherPackageName = getActiveLauncherPackageName(userId); - final String dialerPackageName = mDefaultAppProvider.getDefaultDialer(userId); - for (int i = 0; i < packageNames.length; i++) { - canSuspend[i] = false; - final String packageName = packageNames[i]; - - if (isPackageDeviceAdmin(packageName, userId)) { - Slog.w(TAG, "Cannot suspend package \"" + packageName - + "\": has an active device admin"); - continue; - } - if (packageName.equals(activeLauncherPackageName)) { - Slog.w(TAG, "Cannot suspend package \"" + packageName - + "\": contains the active launcher"); - continue; - } - if (packageName.equals(mRequiredInstallerPackage)) { - Slog.w(TAG, "Cannot suspend package \"" + packageName - + "\": required for package installation"); - continue; - } - if (packageName.equals(mRequiredUninstallerPackage)) { - Slog.w(TAG, "Cannot suspend package \"" + packageName - + "\": required for package uninstallation"); - continue; - } - if (packageName.equals(mRequiredVerifierPackage)) { - Slog.w(TAG, "Cannot suspend package \"" + packageName - + "\": required for package verification"); - continue; - } - if (packageName.equals(dialerPackageName)) { - Slog.w(TAG, "Cannot suspend package \"" + packageName - + "\": is the default dialer"); - continue; - } - if (packageName.equals(mRequiredPermissionControllerPackage)) { - Slog.w(TAG, "Cannot suspend package \"" + packageName - + "\": required for permissions management"); - continue; - } - synchronized (mLock) { - if (mProtectedPackages.isPackageStateProtected(userId, packageName)) { - Slog.w(TAG, "Cannot suspend package \"" + packageName - + "\": protected package"); - continue; - } - if (!isCallerOwner && mSettings.getBlockUninstallLPr(userId, packageName)) { - Slog.w(TAG, "Cannot suspend package \"" + packageName - + "\": blocked by admin"); - continue; - } - - AndroidPackage pkg = mPackages.get(packageName); - if (pkg != null) { - // Cannot suspend SDK libs as they are controlled by SDK manager. - if (pkg.isSdkLibrary()) { - Slog.w(TAG, "Cannot suspend package: " + packageName - + " providing SDK library: " - + pkg.getSdkLibName()); - continue; - } - // Cannot suspend static shared libs as they are considered - // a part of the using app (emulating static linking). Also - // static libs are installed always on internal storage. - if (pkg.isStaticSharedLibrary()) { - Slog.w(TAG, "Cannot suspend package: " + packageName - + " providing static shared library: " - + pkg.getStaticSharedLibName()); - continue; - } - } - } - if (PLATFORM_PACKAGE_NAME.equals(packageName)) { - Slog.w(TAG, "Cannot suspend the platform package: " + packageName); - continue; - } - canSuspend[i] = true; - } - } finally { - Binder.restoreCallingIdentity(callingId); - } - return canSuspend; + return mSuspendPackageHelper.getUnsuspendablePackagesForUser( + packageNames, userId, callingUid); } @Override @@ -5445,7 +5110,7 @@ public class PackageManagerService extends IPackageManager.Stub FLAG_STORAGE_DE | FLAG_STORAGE_CE | FLAG_STORAGE_EXTERNAL); final int appId = UserHandle.getAppId(pkg.getUid()); - removeKeystoreDataIfNeeded(mInjector.getUserManagerInternal(), userId, appId); + mAppDataHelper.clearKeystoreData(userId, appId); UserManagerInternal umInternal = mInjector.getUserManagerInternal(); StorageManagerInternal smInternal = mInjector.getLocalService(StorageManagerInternal.class); @@ -5518,30 +5183,6 @@ public class PackageManagerService extends IPackageManager.Stub } } - /** - * Remove entries from the keystore daemon. Will only remove it if the - * {@code appId} is valid. - */ - static void removeKeystoreDataIfNeeded(UserManagerInternal um, @UserIdInt int userId, - @AppIdInt int appId) { - if (appId < 0) { - return; - } - - final KeyStore keyStore = KeyStore.getInstance(); - if (keyStore != null) { - if (userId == UserHandle.USER_ALL) { - for (final int individual : um.getUserIds()) { - keyStore.clearUid(UserHandle.getUid(individual, appId)); - } - } else { - keyStore.clearUid(UserHandle.getUid(userId, appId)); - } - } else { - Slog.w(TAG, "Could not contact keystore to clear entries for app id " + appId); - } - } - @Override public void deleteApplicationCacheFiles(final String packageName, final IPackageDataObserver observer) { @@ -5980,6 +5621,11 @@ public class PackageManagerService extends IPackageManager.Stub return mPmInternal.getSetupWizardPackageName(); } + public @Nullable String getAmbientContextDetectionPackageName() { + return ensureSystemPackageName(getPackageFromComponentString( + R.string.config_defaultAmbientContextDetectionService)); + } + public String getIncidentReportApproverPackageName() { return ensureSystemPackageName(mContext.getString( R.string.config_incidentReportApproverPackage)); @@ -7420,41 +7066,27 @@ public class PackageManagerService extends IPackageManager.Stub @Override public Bundle getSuspendedPackageLauncherExtras(String packageName, int userId) { - final PackageStateInternal packageState = getPackageStateInternal(packageName); - if (packageState == null) { - return null; - } - Bundle allExtras = new Bundle(); - PackageUserStateInternal userState = packageState.getUserStateOrDefault(userId); - if (userState.isSuspended()) { - for (int i = 0; i < userState.getSuspendParams().size(); i++) { - final SuspendParams params = userState.getSuspendParams().valueAt(i); - if (params != null && params.getLauncherExtras() != null) { - allExtras.putAll(params.getLauncherExtras()); - } - } - } - return (allExtras.size() > 0) ? allExtras : null; + return mSuspendPackageHelper.getSuspendedPackageLauncherExtras( + packageName, userId, Binder.getCallingUid()); } @Override public boolean isPackageSuspended(String packageName, int userId) { - final PackageStateInternal packageState = getPackageStateInternal(packageName); - return packageState != null && packageState.getUserStateOrDefault(userId) - .isSuspended(); + return mSuspendPackageHelper.isPackageSuspended( + packageName, userId, Binder.getCallingUid()); } @Override public void removeAllNonSystemPackageSuspensions(int userId) { final String[] allPackages = mComputer.getAllAvailablePackageNames(); - PackageManagerService.this.removeSuspensionsBySuspendingPackage(allPackages, + mSuspendPackageHelper.removeSuspensionsBySuspendingPackage(allPackages, (suspendingPackage) -> !PLATFORM_PACKAGE_NAME.equals(suspendingPackage), userId); } @Override public void removeNonSystemPackageSuspensions(String packageName, int userId) { - PackageManagerService.this.removeSuspensionsBySuspendingPackage( + mSuspendPackageHelper.removeSuspensionsBySuspendingPackage( new String[]{packageName}, (suspendingPackage) -> !PLATFORM_PACKAGE_NAME.equals(suspendingPackage), userId); @@ -7480,46 +7112,15 @@ public class PackageManagerService extends IPackageManager.Stub @Override public String getSuspendingPackage(String suspendedPackage, int userId) { - final PackageStateInternal packageState = getPackageStateInternal(suspendedPackage); - if (packageState == null) { - return null; - } - - final PackageUserStateInternal userState = packageState.getUserStateOrDefault(userId); - if (!userState.isSuspended()) { - return null; - } - - String suspendingPackage = null; - for (int i = 0; i < userState.getSuspendParams().size(); i++) { - suspendingPackage = userState.getSuspendParams().keyAt(i); - if (PLATFORM_PACKAGE_NAME.equals(suspendingPackage)) { - return suspendingPackage; - } - } - return suspendingPackage; + return mSuspendPackageHelper.getSuspendingPackage( + suspendedPackage, userId, Binder.getCallingUid()); } @Override public SuspendDialogInfo getSuspendedDialogInfo(String suspendedPackage, String suspendingPackage, int userId) { - final PackageStateInternal packageState = getPackageStateInternal(suspendedPackage); - if (packageState == null) { - return null; - } - - final PackageUserStateInternal userState = packageState.getUserStateOrDefault(userId); - if (!userState.isSuspended()) { - return null; - } - - final ArrayMap<String, SuspendParams> suspendParamsMap = userState.getSuspendParams(); - if (suspendParamsMap == null) { - return null; - } - - final SuspendParams suspendParams = suspendParamsMap.get(suspendingPackage); - return (suspendParams != null) ? suspendParams.getDialogInfo() : null; + return mSuspendPackageHelper.getSuspendedDialogInfo( + suspendedPackage, suspendingPackage, userId, Binder.getCallingUid()); } @Override @@ -9083,6 +8684,8 @@ public class PackageManagerService extends IPackageManager.Stub return new String[] { mDefaultAppProvider.getDefaultBrowser(userId) }; case PackageManagerInternal.PACKAGE_INSTALLER: return mComputer.filterOnlySystemPackages(mRequiredInstallerPackage); + case PackageManagerInternal.PACKAGE_UNINSTALLER: + return mComputer.filterOnlySystemPackages(mRequiredUninstallerPackage); case PackageManagerInternal.PACKAGE_SETUP_WIZARD: return mComputer.filterOnlySystemPackages(mSetupWizardPackage); case PackageManagerInternal.PACKAGE_SYSTEM: @@ -9098,6 +8701,8 @@ public class PackageManagerService extends IPackageManager.Stub return mComputer.filterOnlySystemPackages(mConfiguratorPackage); case PackageManagerInternal.PACKAGE_INCIDENT_REPORT_APPROVER: return mComputer.filterOnlySystemPackages(mIncidentReportApproverPackage); + case PackageManagerInternal.PACKAGE_AMBIENT_CONTEXT_DETECTION: + return mComputer.filterOnlySystemPackages(mAmbientContextDetectionPackage); case PackageManagerInternal.PACKAGE_APP_PREDICTOR: return mComputer.filterOnlySystemPackages(mAppPredictionServicePackage); case PackageManagerInternal.PACKAGE_COMPANION: diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java index a1acc388146e..db606863248a 100644 --- a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java +++ b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java @@ -91,6 +91,7 @@ public final class PackageManagerServiceTestParams { public ViewCompiler viewCompiler; public @Nullable String retailDemoPackage; public @Nullable String recentsPackage; + public @Nullable String ambientContextDetectionPackage; public ComponentName resolveComponentName; public ArrayMap<String, AndroidPackage> packages; public boolean enableFreeCacheV2; @@ -111,4 +112,5 @@ public final class PackageManagerServiceTestParams { public PreferredActivityHelper preferredActivityHelper; public ResolveIntentHelper resolveIntentHelper; public DexOptHelper dexOptHelper; + public SuspendPackageHelper suspendPackageHelper; } diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java index d8f0cc340884..e03cf0a10537 100644 --- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java +++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java @@ -51,7 +51,6 @@ import android.content.pm.Signature; import android.content.pm.SigningDetails; import android.content.pm.parsing.ApkLiteParseUtils; import android.content.pm.parsing.PackageLite; -import com.android.server.pm.pkg.component.ParsedMainComponent; import android.content.pm.parsing.result.ParseResult; import android.content.pm.parsing.result.ParseTypeImpl; import android.os.Binder; @@ -92,6 +91,7 @@ import com.android.server.compat.PlatformCompat; import com.android.server.pm.dex.PackageDexUsage; import com.android.server.pm.parsing.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageStateInternal; +import com.android.server.pm.pkg.component.ParsedMainComponent; import com.android.server.pm.verify.domain.DomainVerificationManagerInternal; import dalvik.system.VMRuntime; @@ -560,8 +560,8 @@ public class PackageManagerServiceUtils { if (!match) { throw new PackageManagerException(INSTALL_FAILED_UPDATE_INCOMPATIBLE, - "Package " + packageName + - " signatures do not match previously installed version; ignoring!"); + "Existing package " + packageName + + " signatures do not match newer version; ignoring!"); } } // Check for shared user signatures diff --git a/services/core/java/com/android/server/pm/RemovePackageHelper.java b/services/core/java/com/android/server/pm/RemovePackageHelper.java index b0e03403b653..7e898cbe86b0 100644 --- a/services/core/java/com/android/server/pm/RemovePackageHelper.java +++ b/services/core/java/com/android/server/pm/RemovePackageHelper.java @@ -29,7 +29,6 @@ import static com.android.server.pm.PackageManagerService.TAG; import android.annotation.NonNull; import android.content.pm.PackageManager; -import com.android.server.pm.pkg.component.ParsedInstrumentation; import android.os.UserHandle; import android.os.incremental.IncrementalManager; import android.util.Log; @@ -43,6 +42,7 @@ import com.android.server.pm.parsing.pkg.AndroidPackage; import com.android.server.pm.parsing.pkg.PackageImpl; import com.android.server.pm.permission.PermissionManagerServiceInternal; import com.android.server.pm.pkg.PackageStateInternal; +import com.android.server.pm.pkg.component.ParsedInstrumentation; import java.io.File; import java.util.Collections; @@ -330,8 +330,7 @@ final class RemovePackageHelper { if (removedAppId != -1) { // A user ID was deleted here. Go through all users and remove it // from KeyStore. - mPm.removeKeystoreDataIfNeeded( - mUserManagerInternal, UserHandle.USER_ALL, removedAppId); + mAppDataHelper.clearKeystoreData(UserHandle.USER_ALL, removedAppId); } } } diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java index bf7ef1b24776..15e64dffe892 100644 --- a/services/core/java/com/android/server/pm/ShortcutPackage.java +++ b/services/core/java/com/android/server/pm/ShortcutPackage.java @@ -146,8 +146,10 @@ class ShortcutPackage extends ShortcutPackageItem { private static final String ATTR_PERSON_IS_IMPORTANT = "is-important"; private static final String NAME_CATEGORIES = "categories"; + private static final String NAME_CAPABILITY = "capability"; private static final String TAG_STRING_ARRAY_XMLUTILS = "string-array"; + private static final String TAG_MAP_XMLUTILS = "map"; private static final String ATTR_NAME_XMLUTILS = "name"; private static final String KEY_DYNAMIC = "dynamic"; @@ -1829,6 +1831,12 @@ class ShortcutPackage extends ShortcutPackageItem { } ShortcutService.writeTagExtra(out, TAG_EXTRAS, si.getExtras()); + + final Map<String, Map<String, List<String>>> capabilityBindings = + si.getCapabilityBindings(); + if (capabilityBindings != null && !capabilityBindings.isEmpty()) { + XmlUtils.writeMapXml(si.getCapabilityBindings(), NAME_CAPABILITY, out); + } } out.endTag(null, TAG_SHORTCUT); @@ -1961,6 +1969,7 @@ class ShortcutPackage extends ShortcutPackageItem { int backupVersionCode; ArraySet<String> categories = null; ArrayList<Person> persons = new ArrayList<>(); + Map<String, Map<String, List<String>>> capabilityBindings = null; id = ShortcutService.parseStringAttribute(parser, ATTR_ID); activityComponent = ShortcutService.parseComponentNameAttribute(parser, @@ -2029,6 +2038,13 @@ class ShortcutPackage extends ShortcutPackageItem { } } continue; + case TAG_MAP_XMLUTILS: + if (NAME_CAPABILITY.equals(ShortcutService.parseStringAttribute(parser, + ATTR_NAME_XMLUTILS))) { + capabilityBindings = (Map<String, Map<String, List<String>>>) + XmlUtils.readValueXml(parser, new String[1]); + } + continue; } throw ShortcutService.throwForInvalidTag(depth, tag); } @@ -2064,7 +2080,7 @@ class ShortcutPackage extends ShortcutPackageItem { rank, extras, lastChangedTimestamp, flags, iconResId, iconResName, bitmapPath, iconUri, disabledReason, persons.toArray(new Person[persons.size()]), locusId, - splashScreenThemeResName); + splashScreenThemeResName, capabilityBindings); } private static Intent parseIntent(TypedXmlPullParser parser) diff --git a/services/core/java/com/android/server/pm/ShortcutParser.java b/services/core/java/com/android/server/pm/ShortcutParser.java index b86c50b23687..63f1f2d518f7 100644 --- a/services/core/java/com/android/server/pm/ShortcutParser.java +++ b/services/core/java/com/android/server/pm/ShortcutParser.java @@ -459,7 +459,8 @@ public class ShortcutParser { disabledReason, null /* persons */, null /* locusId */, - splashScreenThemeResName); + splashScreenThemeResName, + null /* capabilityBindings */); } private static String parseCategory(ShortcutService service, AttributeSet attrs) { diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java index 0a2735cdbf76..057f8def1798 100644 --- a/services/core/java/com/android/server/pm/ShortcutService.java +++ b/services/core/java/com/android/server/pm/ShortcutService.java @@ -3474,8 +3474,8 @@ public class ShortcutService extends IShortcutService.Stub { @Nullable private ParcelFileDescriptor getShortcutIconParcelFileDescriptor( - @NonNull final ShortcutInfo shortcutInfo) { - if (!shortcutInfo.hasIconFile()) { + @Nullable final ShortcutInfo shortcutInfo) { + if (shortcutInfo == null || !shortcutInfo.hasIconFile()) { return null; } final String path = mShortcutBitmapSaver.getBitmapPathMayWaitLocked(shortcutInfo); diff --git a/services/core/java/com/android/server/pm/SuspendPackageHelper.java b/services/core/java/com/android/server/pm/SuspendPackageHelper.java new file mode 100644 index 000000000000..f466ca72f681 --- /dev/null +++ b/services/core/java/com/android/server/pm/SuspendPackageHelper.java @@ -0,0 +1,611 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.pm; + +import static android.content.pm.PackageManagerInternal.PACKAGE_INSTALLER; +import static android.content.pm.PackageManagerInternal.PACKAGE_PERMISSION_CONTROLLER; +import static android.content.pm.PackageManagerInternal.PACKAGE_UNINSTALLER; +import static android.content.pm.PackageManagerInternal.PACKAGE_VERIFIER; +import static android.os.Process.SYSTEM_UID; + +import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME; +import static com.android.server.pm.PackageManagerService.TAG; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.ActivityManager; +import android.app.IActivityManager; +import android.content.Intent; +import android.content.pm.PackageManagerInternal.KnownPackage; +import android.content.pm.SuspendDialogInfo; +import android.os.Binder; +import android.os.Bundle; +import android.os.Handler; +import android.os.PersistableBundle; +import android.os.UserHandle; +import android.os.UserManager; +import android.util.ArrayMap; +import android.util.ArraySet; +import android.util.IntArray; +import android.util.Slog; +import android.util.SparseArray; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.ArrayUtils; +import com.android.server.pm.parsing.pkg.AndroidPackage; +import com.android.server.pm.pkg.PackageStateInternal; +import com.android.server.pm.pkg.PackageUserStateInternal; +import com.android.server.pm.pkg.SuspendParams; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.function.Predicate; + +public final class SuspendPackageHelper { + // TODO(b/198166813): remove PMS dependency + private final PackageManagerService mPm; + private final PackageManagerServiceInjector mInjector; + + private final BroadcastHelper mBroadcastHelper; + private final ProtectedPackages mProtectedPackages; + + /** + * Constructor for {@link PackageManagerService}. + */ + SuspendPackageHelper(PackageManagerService pm, PackageManagerServiceInjector injector, + BroadcastHelper broadcastHelper, ProtectedPackages protectedPackages) { + mPm = pm; + mInjector = injector; + mBroadcastHelper = broadcastHelper; + mProtectedPackages = protectedPackages; + } + + /** + * Updates the package to the suspended or unsuspended state. + * + * @param packageNames The names of the packages to set the suspended status. + * @param suspended {@code true} to suspend packages, or {@code false} to unsuspend packages. + * @param appExtras An optional {@link PersistableBundle} that the suspending app can provide + * which will be shared with the apps being suspended. Ignored if + * {@code suspended} is false. + * @param launcherExtras An optional {@link PersistableBundle} that the suspending app can + * provide which will be shared with the launcher. Ignored if + * {@code suspended} is false. + * @param dialogInfo An optional {@link SuspendDialogInfo} object describing the dialog that + * should be shown to the user when they try to launch a suspended app. + * Ignored if {@code suspended} is false. + * @param callingPackage The caller's package name. + * @param userId The user where packages reside. + * @param callingUid The caller's uid. + * @return The names of failed packages. + */ + @Nullable + String[] setPackagesSuspended(@Nullable String[] packageNames, boolean suspended, + @Nullable PersistableBundle appExtras, @Nullable PersistableBundle launcherExtras, + @Nullable SuspendDialogInfo dialogInfo, @NonNull String callingPackage, + int userId, int callingUid) { + if (ArrayUtils.isEmpty(packageNames)) { + return packageNames; + } + if (suspended && !isSuspendAllowedForUser(userId, callingUid)) { + Slog.w(TAG, "Cannot suspend due to restrictions on user " + userId); + return packageNames; + } + + final List<String> changedPackagesList = new ArrayList<>(packageNames.length); + final IntArray changedUids = new IntArray(packageNames.length); + final List<String> modifiedPackagesList = new ArrayList<>(packageNames.length); + final IntArray modifiedUids = new IntArray(packageNames.length); + final List<String> unactionedPackages = new ArrayList<>(packageNames.length); + final boolean[] canSuspend = + suspended ? canSuspendPackageForUser(packageNames, userId, callingUid) : null; + + for (int i = 0; i < packageNames.length; i++) { + final String packageName = packageNames[i]; + if (callingPackage.equals(packageName)) { + Slog.w(TAG, "Calling package: " + callingPackage + " trying to " + + (suspended ? "" : "un") + "suspend itself. Ignoring"); + unactionedPackages.add(packageName); + continue; + } + final PackageSetting pkgSetting; + synchronized (mPm.mLock) { + pkgSetting = mPm.mSettings.getPackageLPr(packageName); + if (pkgSetting == null + || mPm.shouldFilterApplication(pkgSetting, callingUid, userId)) { + Slog.w(TAG, "Could not find package setting for package: " + packageName + + ". Skipping suspending/un-suspending."); + unactionedPackages.add(packageName); + continue; + } + } + if (canSuspend != null && !canSuspend[i]) { + unactionedPackages.add(packageName); + continue; + } + final boolean packageUnsuspended; + final boolean packageModified; + synchronized (mPm.mLock) { + if (suspended) { + packageModified = pkgSetting.addOrUpdateSuspension(callingPackage, + dialogInfo, appExtras, launcherExtras, userId); + } else { + packageModified = pkgSetting.removeSuspension(callingPackage, userId); + } + packageUnsuspended = !suspended && !pkgSetting.getSuspended(userId); + } + if (suspended || packageUnsuspended) { + changedPackagesList.add(packageName); + changedUids.add(UserHandle.getUid(userId, pkgSetting.getAppId())); + } + if (packageModified) { + modifiedPackagesList.add(packageName); + modifiedUids.add(UserHandle.getUid(userId, pkgSetting.getAppId())); + } + } + + if (!changedPackagesList.isEmpty()) { + final String[] changedPackages = changedPackagesList.toArray(new String[0]); + sendPackagesSuspendedForUser( + suspended ? Intent.ACTION_PACKAGES_SUSPENDED + : Intent.ACTION_PACKAGES_UNSUSPENDED, + changedPackages, changedUids.toArray(), userId); + sendMyPackageSuspendedOrUnsuspended(changedPackages, suspended, userId); + synchronized (mPm.mLock) { + mPm.scheduleWritePackageRestrictionsLocked(userId); + } + } + // Send the suspension changed broadcast to ensure suspension state is not stale. + if (!modifiedPackagesList.isEmpty()) { + sendPackagesSuspendedForUser(Intent.ACTION_PACKAGES_SUSPENSION_CHANGED, + modifiedPackagesList.toArray(new String[0]), modifiedUids.toArray(), userId); + } + return unactionedPackages.toArray(new String[0]); + } + + /** + * Returns the names in the {@code packageNames} which can not be suspended by the caller. + * + * @param packageNames The names of packages to check. + * @param userId The user where packages reside. + * @param callingUid The caller's uid. + * @return The names of packages which are Unsuspendable. + */ + @NonNull + String[] getUnsuspendablePackagesForUser(@NonNull String[] packageNames, int userId, + int callingUid) { + if (!isSuspendAllowedForUser(userId, callingUid)) { + Slog.w(TAG, "Cannot suspend due to restrictions on user " + userId); + return packageNames; + } + final ArraySet<String> unactionablePackages = new ArraySet<>(); + final boolean[] canSuspend = canSuspendPackageForUser(packageNames, userId, callingUid); + for (int i = 0; i < packageNames.length; i++) { + if (!canSuspend[i]) { + unactionablePackages.add(packageNames[i]); + continue; + } + synchronized (mPm.mLock) { + final PackageSetting ps = mPm.mSettings.getPackageLPr(packageNames[i]); + if (ps == null || mPm.shouldFilterApplication(ps, callingUid, userId)) { + Slog.w(TAG, "Could not find package setting for package: " + packageNames[i]); + unactionablePackages.add(packageNames[i]); + } + } + } + return unactionablePackages.toArray(new String[unactionablePackages.size()]); + } + + /** + * Returns the app extras of the given suspended package. + * + * @param packageName The suspended package name. + * @param userId The user where the package resides. + * @param callingUid The caller's uid. + * @return The app extras of the suspended package. + */ + @Nullable + Bundle getSuspendedPackageAppExtras(@NonNull String packageName, int userId, int callingUid) { + final PackageStateInternal ps = mPm.getPackageStateInternal(packageName, callingUid); + if (ps == null) { + return null; + } + final PackageUserStateInternal pus = ps.getUserStateOrDefault(userId); + final Bundle allExtras = new Bundle(); + if (pus.isSuspended()) { + for (int i = 0; i < pus.getSuspendParams().size(); i++) { + final SuspendParams params = pus.getSuspendParams().valueAt(i); + if (params != null && params.getAppExtras() != null) { + allExtras.putAll(params.getAppExtras()); + } + } + } + return (allExtras.size() > 0) ? allExtras : null; + } + + /** + * Removes any suspensions on given packages that were added by packages that pass the given + * predicate. + * + * <p> Caller must flush package restrictions if it cares about immediate data consistency. + * + * @param packagesToChange The packages on which the suspension are to be removed. + * @param suspendingPackagePredicate A predicate identifying the suspending packages whose + * suspensions will be removed. + * @param userId The user for which the changes are taking place. + */ + void removeSuspensionsBySuspendingPackage(@NonNull String[] packagesToChange, + @NonNull Predicate<String> suspendingPackagePredicate, int userId) { + final List<String> unsuspendedPackages = new ArrayList<>(); + final IntArray unsuspendedUids = new IntArray(); + synchronized (mPm.mLock) { + for (String packageName : packagesToChange) { + final PackageSetting ps = mPm.mSettings.getPackageLPr(packageName); + if (ps != null && ps.getUserStateOrDefault(userId).isSuspended()) { + ps.removeSuspension(suspendingPackagePredicate, userId); + if (!ps.getUserStateOrDefault(userId).isSuspended()) { + unsuspendedPackages.add(ps.getPackageName()); + unsuspendedUids.add(UserHandle.getUid(userId, ps.getAppId())); + } + } + } + mPm.scheduleWritePackageRestrictionsLocked(userId); + } + if (!unsuspendedPackages.isEmpty()) { + final String[] packageArray = unsuspendedPackages.toArray( + new String[unsuspendedPackages.size()]); + sendMyPackageSuspendedOrUnsuspended(packageArray, false, userId); + sendPackagesSuspendedForUser(Intent.ACTION_PACKAGES_UNSUSPENDED, + packageArray, unsuspendedUids.toArray(), userId); + } + } + + /** + * Returns the launcher extras for the given suspended package. + * + * @param packageName The name of the suspended package. + * @param userId The user where the package resides. + * @param callingUid The caller's uid. + * @return The launcher extras. + */ + @Nullable + Bundle getSuspendedPackageLauncherExtras(@NonNull String packageName, int userId, + int callingUid) { + final PackageStateInternal packageState = mPm.getPackageStateInternal( + packageName, callingUid); + if (packageState == null) { + return null; + } + Bundle allExtras = new Bundle(); + PackageUserStateInternal userState = packageState.getUserStateOrDefault(userId); + if (userState.isSuspended()) { + for (int i = 0; i < userState.getSuspendParams().size(); i++) { + final SuspendParams params = userState.getSuspendParams().valueAt(i); + if (params != null && params.getLauncherExtras() != null) { + allExtras.putAll(params.getLauncherExtras()); + } + } + } + return (allExtras.size() > 0) ? allExtras : null; + } + + /** + * Return {@code true}, if the given package is suspended. + * + * @param packageName The name of package to check. + * @param userId The user where the package resides. + * @param callingUid The caller's uid. + * @return {@code true}, if the given package is suspended. + */ + boolean isPackageSuspended(@NonNull String packageName, int userId, int callingUid) { + final PackageStateInternal packageState = mPm.getPackageStateInternal( + packageName, callingUid); + return packageState != null && packageState.getUserStateOrDefault(userId) + .isSuspended(); + } + + /** + * Given a suspended package, returns the name of package which invokes suspending to it. + * + * @param suspendedPackage The suspended package to check. + * @param userId The user where the package resides. + * @param callingUid The caller's uid. + * @return The name of suspending package. + */ + @Nullable + String getSuspendingPackage(@NonNull String suspendedPackage, int userId, int callingUid) { + final PackageStateInternal packageState = mPm.getPackageStateInternal( + suspendedPackage, callingUid); + if (packageState == null) { + return null; + } + + final PackageUserStateInternal userState = packageState.getUserStateOrDefault(userId); + if (!userState.isSuspended()) { + return null; + } + + String suspendingPackage = null; + for (int i = 0; i < userState.getSuspendParams().size(); i++) { + suspendingPackage = userState.getSuspendParams().keyAt(i); + if (PLATFORM_PACKAGE_NAME.equals(suspendingPackage)) { + return suspendingPackage; + } + } + return suspendingPackage; + } + + /** + * Returns the dialog info of the given suspended package. + * + * @param suspendedPackage The name of the suspended package. + * @param suspendingPackage The name of the suspending package. + * @param userId The user where the package resides. + * @param callingUid The caller's uid. + * @return The dialog info. + */ + @Nullable + SuspendDialogInfo getSuspendedDialogInfo(@NonNull String suspendedPackage, + @NonNull String suspendingPackage, int userId, int callingUid) { + final PackageStateInternal packageState = mPm.getPackageStateInternal( + suspendedPackage, callingUid); + if (packageState == null) { + return null; + } + + final PackageUserStateInternal userState = packageState.getUserStateOrDefault(userId); + if (!userState.isSuspended()) { + return null; + } + + final ArrayMap<String, SuspendParams> suspendParamsMap = userState.getSuspendParams(); + if (suspendParamsMap == null) { + return null; + } + + final SuspendParams suspendParams = suspendParamsMap.get(suspendingPackage); + return (suspendParams != null) ? suspendParams.getDialogInfo() : null; + } + + /** + * Return {@code true} if the user is allowed to suspend packages by the caller. + * + * @param userId The user id to check. + * @param callingUid The caller's uid. + * @return {@code true} if the user is allowed to suspend packages by the caller. + */ + boolean isSuspendAllowedForUser(int userId, int callingUid) { + final UserManagerService userManager = mInjector.getUserManagerService(); + return isCallerDeviceOrProfileOwner(userId, callingUid) + || (!userManager.hasUserRestriction(UserManager.DISALLOW_APPS_CONTROL, userId) + && !userManager.hasUserRestriction(UserManager.DISALLOW_UNINSTALL_APPS, userId)); + } + + /** + * Returns an array of booleans, such that the ith boolean denotes whether the ith package can + * be suspended or not. + * + * @param packageNames The package names to check suspendability for. + * @param userId The user to check in + * @param callingUid The caller's uid. + * @return An array containing results of the checks + */ + @NonNull + boolean[] canSuspendPackageForUser(@NonNull String[] packageNames, int userId, int callingUid) { + final boolean[] canSuspend = new boolean[packageNames.length]; + final boolean isCallerOwner = isCallerDeviceOrProfileOwner(userId, callingUid); + final long token = Binder.clearCallingIdentity(); + try { + final DefaultAppProvider defaultAppProvider = mInjector.getDefaultAppProvider(); + final String activeLauncherPackageName = defaultAppProvider.getDefaultHome(userId); + final String dialerPackageName = defaultAppProvider.getDefaultDialer(userId); + final String requiredInstallerPackage = getKnownPackageName(PACKAGE_INSTALLER, userId); + final String requiredUninstallerPackage = + getKnownPackageName(PACKAGE_UNINSTALLER, userId); + final String requiredVerifierPackage = getKnownPackageName(PACKAGE_VERIFIER, userId); + final String requiredPermissionControllerPackage = + getKnownPackageName(PACKAGE_PERMISSION_CONTROLLER, userId); + for (int i = 0; i < packageNames.length; i++) { + canSuspend[i] = false; + final String packageName = packageNames[i]; + + if (mPm.isPackageDeviceAdmin(packageName, userId)) { + Slog.w(TAG, "Cannot suspend package \"" + packageName + + "\": has an active device admin"); + continue; + } + if (packageName.equals(activeLauncherPackageName)) { + Slog.w(TAG, "Cannot suspend package \"" + packageName + + "\": contains the active launcher"); + continue; + } + if (packageName.equals(requiredInstallerPackage)) { + Slog.w(TAG, "Cannot suspend package \"" + packageName + + "\": required for package installation"); + continue; + } + if (packageName.equals(requiredUninstallerPackage)) { + Slog.w(TAG, "Cannot suspend package \"" + packageName + + "\": required for package uninstallation"); + continue; + } + if (packageName.equals(requiredVerifierPackage)) { + Slog.w(TAG, "Cannot suspend package \"" + packageName + + "\": required for package verification"); + continue; + } + if (packageName.equals(dialerPackageName)) { + Slog.w(TAG, "Cannot suspend package \"" + packageName + + "\": is the default dialer"); + continue; + } + if (packageName.equals(requiredPermissionControllerPackage)) { + Slog.w(TAG, "Cannot suspend package \"" + packageName + + "\": required for permissions management"); + continue; + } + synchronized (mPm.mLock) { + if (mProtectedPackages.isPackageStateProtected(userId, packageName)) { + Slog.w(TAG, "Cannot suspend package \"" + packageName + + "\": protected package"); + continue; + } + if (!isCallerOwner && mPm.mSettings.getBlockUninstallLPr(userId, packageName)) { + Slog.w(TAG, "Cannot suspend package \"" + packageName + + "\": blocked by admin"); + continue; + } + + AndroidPackage pkg = mPm.mPackages.get(packageName); + if (pkg != null) { + // Cannot suspend SDK libs as they are controlled by SDK manager. + if (pkg.isSdkLibrary()) { + Slog.w(TAG, "Cannot suspend package: " + packageName + + " providing SDK library: " + + pkg.getSdkLibName()); + continue; + } + // Cannot suspend static shared libs as they are considered + // a part of the using app (emulating static linking). Also + // static libs are installed always on internal storage. + if (pkg.isStaticSharedLibrary()) { + Slog.w(TAG, "Cannot suspend package: " + packageName + + " providing static shared library: " + + pkg.getStaticSharedLibName()); + continue; + } + } + } + if (PLATFORM_PACKAGE_NAME.equals(packageName)) { + Slog.w(TAG, "Cannot suspend the platform package: " + packageName); + continue; + } + canSuspend[i] = true; + } + } finally { + Binder.restoreCallingIdentity(token); + } + return canSuspend; + } + + /** + * Send broadcast intents for packages suspension changes. + * + * @param intent The action name of the suspension intent. + * @param pkgList The names of packages which have suspension changes. + * @param uidList The uids of packages which have suspension changes. + * @param userId The user where packages reside. + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) + void sendPackagesSuspendedForUser(@NonNull String intent, @NonNull String[] pkgList, + @NonNull int[] uidList, int userId) { + final List<List<String>> pkgsToSend = new ArrayList(pkgList.length); + final List<IntArray> uidsToSend = new ArrayList(pkgList.length); + final List<SparseArray<int[]>> allowListsToSend = new ArrayList(pkgList.length); + final int[] userIds = new int[] {userId}; + // Get allow lists for the pkg in the pkgList. Merge into the existed pkgs and uids if + // allow lists are the same. + for (int i = 0; i < pkgList.length; i++) { + final String pkgName = pkgList[i]; + final int uid = uidList[i]; + SparseArray<int[]> allowList = mInjector.getAppsFilter().getVisibilityAllowList( + mPm.getPackageStateInternal(pkgName, SYSTEM_UID), + userIds, mPm.getPackageStates()); + if (allowList == null) { + allowList = new SparseArray<>(0); + } + boolean merged = false; + for (int j = 0; j < allowListsToSend.size(); j++) { + if (Arrays.equals(allowListsToSend.get(j).get(userId), allowList.get(userId))) { + pkgsToSend.get(j).add(pkgName); + uidsToSend.get(j).add(uid); + merged = true; + break; + } + } + if (!merged) { + pkgsToSend.add(new ArrayList<>(Arrays.asList(pkgName))); + uidsToSend.add(IntArray.wrap(new int[] {uid})); + allowListsToSend.add(allowList); + } + } + + final Handler handler = mInjector.getHandler(); + for (int i = 0; i < pkgsToSend.size(); i++) { + final Bundle extras = new Bundle(3); + extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, + pkgsToSend.get(i).toArray(new String[pkgsToSend.get(i).size()])); + extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, uidsToSend.get(i).toArray()); + final SparseArray<int[]> allowList = allowListsToSend.get(i).size() == 0 + ? null : allowListsToSend.get(i); + handler.post(() -> mBroadcastHelper.sendPackageBroadcast(intent, null /* pkg */, + extras, Intent.FLAG_RECEIVER_REGISTERED_ONLY, null /* targetPkg */, + null /* finishedReceiver */, userIds, null /* instantUserIds */, + allowList, null /* bOptions */)); + } + } + + private String getKnownPackageName(@KnownPackage int knownPackage, int userId) { + final String[] knownPackages = mPm.getKnownPackageNamesInternal(knownPackage, userId); + return knownPackages.length > 0 ? knownPackages[0] : null; + } + + private boolean isCallerDeviceOrProfileOwner(int userId, int callingUid) { + if (callingUid == SYSTEM_UID) { + return true; + } + final String ownerPackage = mProtectedPackages.getDeviceOwnerOrProfileOwnerPackage(userId); + if (ownerPackage != null) { + return callingUid == mPm.getPackageUidInternal( + ownerPackage, 0, userId, callingUid); + } + return false; + } + + private void sendMyPackageSuspendedOrUnsuspended(String[] affectedPackages, boolean suspended, + int userId) { + final Handler handler = mInjector.getHandler(); + final String action = suspended + ? Intent.ACTION_MY_PACKAGE_SUSPENDED + : Intent.ACTION_MY_PACKAGE_UNSUSPENDED; + handler.post(() -> { + final IActivityManager am = ActivityManager.getService(); + if (am == null) { + Slog.wtf(TAG, "IActivityManager null. Cannot send MY_PACKAGE_ " + + (suspended ? "" : "UN") + "SUSPENDED broadcasts"); + return; + } + final int[] targetUserIds = new int[] {userId}; + for (String packageName : affectedPackages) { + final Bundle appExtras = suspended + ? getSuspendedPackageAppExtras(packageName, userId, SYSTEM_UID) + : null; + final Bundle intentExtras; + if (appExtras != null) { + intentExtras = new Bundle(1); + intentExtras.putBundle(Intent.EXTRA_SUSPENDED_PACKAGE_EXTRAS, appExtras); + } else { + intentExtras = null; + } + handler.post(() -> mBroadcastHelper.doSendBroadcast(action, null, intentExtras, + Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND, packageName, null, + targetUserIds, false, null, null)); + } + }); + } +} diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index d29dbbc7c04a..d6e88f40d05d 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -3675,9 +3675,18 @@ public class UserManagerService extends IUserManager.Stub { @UserInfoFlag int flags, @UserIdInt int parentId, @Nullable String[] disallowedPackages) throws UserManager.CheckedUserOperationException { - String restriction = (UserManager.isUserTypeManagedProfile(userType)) - ? UserManager.DISALLOW_ADD_MANAGED_PROFILE - : UserManager.DISALLOW_ADD_USER; + + // Checking user restriction before creating new user, + // default check is for DISALLOW_ADD_USER + // If new user is of type CLONE, check if creation of clone profile is allowed + // If new user is of type MANAGED, check if creation of managed profile is allowed + String restriction = UserManager.DISALLOW_ADD_USER; + if (UserManager.isUserTypeCloneProfile(userType)) { + restriction = UserManager.DISALLOW_ADD_CLONE_PROFILE; + } else if (UserManager.isUserTypeManagedProfile(userType)) { + restriction = UserManager.DISALLOW_ADD_MANAGED_PROFILE; + } + enforceUserRestriction(restriction, UserHandle.getCallingUserId(), "Cannot add user"); return createUserInternalUnchecked(name, userType, flags, parentId, diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java index 0f3b4bcfac56..1fa901352c3d 100644 --- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java +++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java @@ -101,6 +101,7 @@ public class UserRestrictionsUtils { UserManager.DISALLOW_FACTORY_RESET, UserManager.DISALLOW_ADD_USER, UserManager.DISALLOW_ADD_MANAGED_PROFILE, + UserManager.DISALLOW_ADD_CLONE_PROFILE, UserManager.ENSURE_VERIFY_APPS, UserManager.DISALLOW_CONFIG_CELL_BROADCASTS, UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS, diff --git a/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java b/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java index 4bbe3733719e..d455be7e4a69 100644 --- a/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java +++ b/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java @@ -162,7 +162,10 @@ public class OneTimePermissionUserManager { * The delay to wait before revoking on the event an app is terminated. Recommended to be long * enough so that apps don't lose permission on an immediate restart */ - private static long getKilledDelayMillis() { + private long getKilledDelayMillis(boolean isSelfRevokedPermissionSession) { + if (isSelfRevokedPermissionSession) { + return 0; + } return DeviceConfig.getLong(DeviceConfig.NAMESPACE_PERMISSIONS, PROPERTY_KILLED_DELAY_CONFIG_KEY, DEFAULT_KILLED_DELAY_MILLIS); } @@ -175,6 +178,18 @@ public class OneTimePermissionUserManager { mContext.registerReceiver(mUninstallListener, new IntentFilter(Intent.ACTION_UID_REMOVED)); } + void setSelfRevokedPermissionSession(int uid) { + synchronized (mLock) { + PackageInactivityListener listener = mListeners.get(uid); + if (listener == null) { + Log.e(LOG_TAG, "Could not set session for uid " + uid + + " as self-revoke session: session not found"); + return; + } + listener.setSelfRevokedPermissionSession(); + } + } + /** * A class which watches a package for inactivity and notifies the permission controller when * the package becomes inactive @@ -189,6 +204,7 @@ public class OneTimePermissionUserManager { private final int mImportanceToResetTimer; private final int mImportanceToKeepSessionAlive; + private boolean mIsSelfRevokedPermissionSession; private boolean mIsAlarmSet; private boolean mIsFinished; @@ -255,7 +271,7 @@ public class OneTimePermissionUserManager { } onImportanceChanged(mUid, imp); } - }, mToken, getKilledDelayMillis()); + }, mToken, getKilledDelayMillis(mIsSelfRevokedPermissionSession)); return; } if (importance > mImportanceToResetTimer) { @@ -291,6 +307,14 @@ public class OneTimePermissionUserManager { } /** + * Marks the session as a self-revoke session, which does not delay the revocation when + * the app is restarting. + */ + public void setSelfRevokedPermissionSession() { + mIsSelfRevokedPermissionSession = true; + } + + /** * Set the alarm which will callback when the package is inactive */ @GuardedBy("mInnerLock") diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java index 1cfcdf51f5b8..317730a9f606 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java @@ -66,6 +66,7 @@ import android.util.Slog; import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; +import com.android.internal.infra.AndroidFuture; import com.android.internal.util.Preconditions; import com.android.internal.util.function.TriFunction; import com.android.server.LocalServices; @@ -558,9 +559,18 @@ public class PermissionManagerService extends IPermissionManager.Stub { } @Override - public void selfRevokePermissions(@NonNull String packageName, + public void revokeOwnPermissionsOnKill(@NonNull String packageName, @NonNull List<String> permissions) { - mPermissionManagerServiceImpl.selfRevokePermissions(packageName, permissions); + final int callingUid = Binder.getCallingUid(); + final int callingUserId = UserHandle.getUserId(callingUid); + AndroidFuture<Void> future = new AndroidFuture<>(); + future.whenComplete((result, err) -> { + if (err == null) { + getOneTimePermissionUserManager(callingUserId) + .setSelfRevokedPermissionSession(callingUid); + } + }); + mPermissionManagerServiceImpl.revokeOwnPermissionsOnKill(packageName, permissions, future); } @Override diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java index 981fd8e9e789..9b3d6d6eb3de 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java @@ -113,6 +113,7 @@ import android.util.SparseBooleanArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.compat.IPlatformCompat; +import com.android.internal.infra.AndroidFuture; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto; import com.android.internal.os.RoSystemProperties; @@ -1592,7 +1593,8 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt } @Override - public void selfRevokePermissions(String packageName, List<String> permissions) { + public void revokeOwnPermissionsOnKill(String packageName, List<String> permissions, + AndroidFuture<Void> callback) { final int callingUid = Binder.getCallingUid(); int callingUserId = UserHandle.getUserId(callingUid); int targetPackageUid = mPackageManagerInt.getPackageUid(packageName, 0, callingUserId); @@ -1607,7 +1609,8 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt + permName + " because it does not hold that permission"); } } - mPermissionControllerManager.selfRevokePermissions(packageName, permissions); + mPermissionControllerManager.revokeOwnPermissionsOnKill(packageName, permissions, + callback); } private boolean mayManageRolePermission(int uid) { @@ -3181,9 +3184,13 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt ps.updatePermissionFlags(bp, PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED | FLAG_PERMISSION_REVOKE_WHEN_REQUESTED, PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED); - // TODO(b/205888750): remove revoke once propagated through droidfood - if (ps.isPermissionGranted(newPerm)) { + // TODO(b/205888750): remove if/else block once propagated through droidfood + if (ps.isPermissionGranted(newPerm) + && pkg.getTargetSdkVersion() >= Build.VERSION_CODES.M) { ps.revokePermission(bp); + } else if (!ps.isPermissionGranted(newPerm) + && pkg.getTargetSdkVersion() < Build.VERSION_CODES.M) { + ps.grantPermission(bp); } } } diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java index c582f9efa7a0..91c558b2f35e 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java @@ -27,6 +27,7 @@ import android.content.pm.permission.SplitPermissionInfoParcelable; import android.permission.IOnPermissionsChangeListener; import android.permission.PermissionManagerInternal; +import com.android.internal.infra.AndroidFuture; import com.android.server.pm.parsing.pkg.AndroidPackage; import java.io.FileDescriptor; @@ -343,8 +344,10 @@ public interface PermissionManagerServiceInterface extends PermissionManagerInte * * @param packageName The name of the package for which the permissions will be revoked. * @param permissions List of permissions to be revoked. + * @param callback Callback called when the revocation request has been completed. */ - void selfRevokePermissions(String packageName, List<String> permissions); + void revokeOwnPermissionsOnKill(String packageName, List<String> permissions, + AndroidFuture<Void> callback); /** * Get whether you should show UI with rationale for requesting a permission. You should do this diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java index 18a6435d17c8..52d9b7a3abc1 100644 --- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java +++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java @@ -26,6 +26,10 @@ import android.content.pm.FeatureGroupInfo; import android.content.pm.FeatureInfo; import android.content.pm.PackageManager.Property; import android.content.pm.SigningDetails; +import android.os.Bundle; +import android.util.SparseArray; +import android.util.SparseIntArray; + import com.android.server.pm.pkg.component.ParsedActivity; import com.android.server.pm.pkg.component.ParsedApexSystemService; import com.android.server.pm.pkg.component.ParsedAttribution; @@ -37,9 +41,6 @@ import com.android.server.pm.pkg.component.ParsedProcess; import com.android.server.pm.pkg.component.ParsedProvider; import com.android.server.pm.pkg.component.ParsedService; import com.android.server.pm.pkg.component.ParsedUsesPermission; -import android.os.Bundle; -import android.util.SparseArray; -import android.util.SparseIntArray; import java.security.PublicKey; import java.util.Map; @@ -286,6 +287,9 @@ public interface ParsingPackage extends ParsingPackageRead { ParsingPackage setInstallLocation(int installLocation); + /** @see R#styleable.AndroidManifest_inheritKeyStoreKeys */ + ParsingPackage setInheritKeyStoreKeys(boolean inheritKeyStoreKeys); + ParsingPackage setLabelRes(int labelRes); ParsingPackage setLargestWidthLimitDp(int largestWidthLimitDp); diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageImpl.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageImpl.java index c4de862bccd9..1f21938fc706 100644 --- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageImpl.java +++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageImpl.java @@ -492,6 +492,10 @@ public class ParsingPackageImpl implements ParsingPackage, ParsingPackageHidden, ENABLED, DISALLOW_PROFILING, REQUEST_FOREGROUND_SERVICE_EXEMPTION, + ATTRIBUTIONS_ARE_USER_VISIBLE, + RESET_ENABLED_SETTINGS_ON_APP_DATA_CLEARED, + SDK_LIBRARY, + INHERIT_KEYSTORE_KEYS, }) public @interface Values {} private static final long EXTERNAL_STORAGE = 1L; @@ -544,6 +548,7 @@ public class ParsingPackageImpl implements ParsingPackage, ParsingPackageHidden, private static final long ATTRIBUTIONS_ARE_USER_VISIBLE = 1L << 47; private static final long RESET_ENABLED_SETTINGS_ON_APP_DATA_CLEARED = 1L << 48; private static final long SDK_LIBRARY = 1L << 49; + private static final long INHERIT_KEYSTORE_KEYS = 1L << 50; } private ParsingPackageImpl setBoolean(@Booleans.Values long flag, boolean value) { @@ -2371,6 +2376,11 @@ public class ParsingPackageImpl implements ParsingPackage, ParsingPackageHidden, } @Override + public boolean shouldInheritKeyStoreKeys() { + return getBoolean(Booleans.INHERIT_KEYSTORE_KEYS); + } + + @Override public ParsingPackageImpl setBaseRevisionCode(int value) { baseRevisionCode = value; return this; @@ -2514,6 +2524,11 @@ public class ParsingPackageImpl implements ParsingPackage, ParsingPackageHidden, } @Override + public ParsingPackageImpl setInheritKeyStoreKeys(boolean value) { + return setBoolean(Booleans.INHERIT_KEYSTORE_KEYS, value); + } + + @Override public ParsingPackageImpl setLabelRes(int value) { labelRes = value; return this; diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageRead.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageRead.java index 149711287f32..4b659a14418f 100644 --- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageRead.java +++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageRead.java @@ -350,4 +350,9 @@ public interface ParsingPackageRead extends PkgWithoutStateAppInfo, PkgWithoutSt * @see R.styleable#AndroidManifestApplication_localeConfig */ int getLocaleConfigRes(); + + /** + * @see R.styleable#AndroidManifest_inheritKeyStoreKeys + */ + boolean shouldInheritKeyStoreKeys(); } diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java index 1ce01f633791..bf7c55f0f59e 100644 --- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java +++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java @@ -868,7 +868,9 @@ public class ParsingPackageUtils { .setTargetSandboxVersion(anInteger(PARSE_DEFAULT_TARGET_SANDBOX, R.styleable.AndroidManifest_targetSandboxVersion, sa)) /* Set the global "on SD card" flag */ - .setExternalStorage((flags & PARSE_EXTERNAL_STORAGE) != 0); + .setExternalStorage((flags & PARSE_EXTERNAL_STORAGE) != 0) + .setInheritKeyStoreKeys(bool(false, + R.styleable.AndroidManifest_inheritKeyStoreKeys, sa)); boolean foundApp = false; final int depth = parser.getDepth(); diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 28f65cf6d1a0..7dd942507a16 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -5401,18 +5401,18 @@ public class PhoneWindowManager implements WindowManagerPolicy { if (!mVibrator.hasVibrator()) { return false; } - final boolean hapticsDisabled = Settings.System.getIntForUser(mContext.getContentResolver(), - Settings.System.HAPTIC_FEEDBACK_ENABLED, 0, UserHandle.USER_CURRENT) == 0; - if (hapticsDisabled && !always) { - return false; - } - VibrationEffect effect = getVibrationEffect(effectId); if (effect == null) { return false; } - - mVibrator.vibrate(uid, packageName, effect, reason, getVibrationAttributes(effectId)); + VibrationAttributes attrs = getVibrationAttributes(effectId); + if (always) { + attrs = new VibrationAttributes.Builder(attrs) + .setFlags(VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF, + VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF) + .build(); + } + mVibrator.vibrate(uid, packageName, effect, reason, attrs); return true; } diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java index 16176f026578..b7ca4defda5f 100644 --- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java +++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java @@ -121,7 +121,6 @@ import android.os.IThermalService; import android.os.OutcomeReceiver; import android.os.ParcelFileDescriptor; import android.os.Parcelable; -import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; import android.os.ServiceSpecificException; @@ -231,7 +230,6 @@ import java.time.Instant; import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.Arrays; -import java.util.Comparator; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -2332,11 +2330,7 @@ public class StatsPullAtomService extends SystemService { List<ProcessMemoryState> managedProcessList = LocalServices.getService(ActivityManagerInternal.class) .getMemoryStateForProcesses(); - managedProcessList.sort(Comparator.comparingInt(x -> x.oomScore)); for (ProcessMemoryState process : managedProcessList) { - if (process.uid == Process.SYSTEM_UID) { - continue; - } KernelAllocationStats.ProcessDmabuf proc = KernelAllocationStats.getDmabufAllocations(process.pid); if (proc == null || (proc.retainedBuffersCount <= 0 && proc.mappedBuffersCount <= 0)) { @@ -2353,6 +2347,32 @@ public class StatsPullAtomService extends SystemService { proc.mappedSizeKb, proc.mappedBuffersCount)); } + SparseArray<String> processCmdlines = getProcessCmdlines(); + managedProcessList.forEach(managedProcess -> processCmdlines.delete(managedProcess.pid)); + int size = processCmdlines.size(); + for (int i = 0; i < size; ++i) { + int pid = processCmdlines.keyAt(i); + int uid = getUidForPid(pid); + // ignore root processes (unlikely to be interesting) + if (uid <= 0) { + continue; + } + KernelAllocationStats.ProcessDmabuf proc = + KernelAllocationStats.getDmabufAllocations(pid); + if (proc == null || (proc.retainedBuffersCount <= 0 && proc.mappedBuffersCount <= 0)) { + continue; + } + pulledData.add( + FrameworkStatsLog.buildStatsEvent( + atomTag, + uid, + processCmdlines.valueAt(i), + -1001 /*Placeholder for native processes, OOM_SCORE_ADJ_MIN - 1.*/, + proc.retainedSizeKb, + proc.retainedBuffersCount, + proc.mappedSizeKb, + proc.mappedBuffersCount)); + } return StatsManager.PULL_SUCCESS; } diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java index 17f5566a9864..0edd06acd7e9 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java @@ -22,6 +22,7 @@ import static android.app.StatusBarManager.NAV_BAR_MODE_OVERRIDE_KIDS; import static android.app.StatusBarManager.NAV_BAR_MODE_OVERRIDE_NONE; import static android.app.StatusBarManager.NavBarModeOverride; import static android.view.Display.DEFAULT_DISPLAY; +import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY; import android.Manifest; import android.annotation.NonNull; @@ -38,6 +39,7 @@ import android.compat.annotation.EnabledSince; import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.content.om.IOverlayManager; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.ResolveInfo; @@ -60,6 +62,7 @@ import android.os.PowerManager; import android.os.Process; import android.os.RemoteException; import android.os.ResultReceiver; +import android.os.ServiceManager; import android.os.ShellCallback; import android.os.UserHandle; import android.provider.Settings; @@ -79,6 +82,7 @@ import android.view.WindowInsetsController.Behavior; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.inputmethod.SoftInputShowHideReason; import com.android.internal.os.TransferPipe; import com.android.internal.statusbar.IAddTileResultCallback; @@ -154,6 +158,8 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D private final ArrayMap<String, Long> mCurrentRequestAddTilePackages = new ArrayMap<>(); private static final long REQUEST_TIME_OUT = TimeUnit.MINUTES.toNanos(5); + private IOverlayManager mOverlayManager; + private class DeathRecipient implements IBinder.DeathRecipient { public void binderDied() { mBar.asBinder().unlinkToDeath(this,0); @@ -256,6 +262,18 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D mTileRequestTracker = new TileRequestTracker(mContext); } + private IOverlayManager getOverlayManager() { + // No need to synchronize; worst-case scenario it will be fetched twice. + if (mOverlayManager == null) { + mOverlayManager = IOverlayManager.Stub.asInterface( + ServiceManager.getService(Context.OVERLAY_SERVICE)); + if (mOverlayManager == null) { + Slog.w("StatusBarManager", "warning: no OVERLAY_SERVICE"); + } + } + return mOverlayManager; + } + @Override public void onDisplayAdded(int displayId) {} @@ -1296,6 +1314,11 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D }); } + @VisibleForTesting + void registerOverlayManager(IOverlayManager overlayManager) { + mOverlayManager = overlayManager; + } + /** * @param clearNotificationEffects whether to consider notifications as "shown" and stop * LED, vibration, and ringing @@ -1869,6 +1892,14 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D try { Settings.Secure.putIntForUser(mContext.getContentResolver(), Settings.Secure.NAV_BAR_KIDS_MODE, navBarModeOverride, userId); + + IOverlayManager overlayManager = getOverlayManager(); + if (overlayManager != null && navBarModeOverride == NAV_BAR_MODE_OVERRIDE_KIDS + && isPackageSupported(NAV_BAR_MODE_3BUTTON_OVERLAY)) { + overlayManager.setEnabledExclusiveInCategory(NAV_BAR_MODE_3BUTTON_OVERLAY, userId); + } + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); } finally { Binder.restoreCallingIdentity(userIdentity); } @@ -1896,6 +1927,21 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D return navBarKidsMode; } + private boolean isPackageSupported(String packageName) { + if (packageName == null) { + return false; + } + try { + return mContext.getPackageManager().getPackageInfo(packageName, + PackageManager.PackageInfoFlags.of(0)) != null; + } catch (PackageManager.NameNotFoundException ignored) { + if (SPEW) { + Slog.d(TAG, "Package not found: " + packageName); + } + } + return false; + } + /** @hide */ public void passThroughShellCommand(String[] args, FileDescriptor fd) { enforceStatusBarOrShell(); diff --git a/services/core/java/com/android/server/storage/DeviceStorageMonitorService.java b/services/core/java/com/android/server/storage/DeviceStorageMonitorService.java index 3093509428d3..c0207f044b83 100644 --- a/services/core/java/com/android/server/storage/DeviceStorageMonitorService.java +++ b/services/core/java/com/android/server/storage/DeviceStorageMonitorService.java @@ -37,6 +37,7 @@ import android.os.ShellCommand; import android.os.UserHandle; import android.os.storage.StorageManager; import android.os.storage.VolumeInfo; +import android.provider.DeviceConfig; import android.text.format.DateUtils; import android.util.ArrayMap; import android.util.DataUnit; @@ -255,11 +256,14 @@ public class DeviceStorageMonitorService extends SystemService { private void checkHigh() { final StorageManager storage = getContext().getSystemService(StorageManager.class); // Check every mounted private volume to see if they're under the high storage threshold - // which is StorageManager.STORAGE_THRESHOLD_PERCENT_HIGH of total space + // which is storageThresholdPercentHigh of total space + final int storageThresholdPercentHigh = DeviceConfig.getInt( + DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT, + StorageManager.STORAGE_THRESHOLD_PERCENT_HIGH_KEY, + StorageManager.DEFAULT_STORAGE_THRESHOLD_PERCENT_HIGH); for (VolumeInfo vol : storage.getWritablePrivateVolumes()) { final File file = vol.getPath(); - if (file.getUsableSpace() < file.getTotalSpace() - * StorageManager.STORAGE_THRESHOLD_PERCENT_HIGH / 100) { + if (file.getUsableSpace() < file.getTotalSpace() * storageThresholdPercentHigh / 100) { final PackageManagerService pms = (PackageManagerService) ServiceManager .getService("package"); try { diff --git a/services/core/java/com/android/server/trust/TrustAgentWrapper.java b/services/core/java/com/android/server/trust/TrustAgentWrapper.java index 79231f709738..06ce4a41afec 100644 --- a/services/core/java/com/android/server/trust/TrustAgentWrapper.java +++ b/services/core/java/com/android/server/trust/TrustAgentWrapper.java @@ -16,6 +16,8 @@ package com.android.server.trust; +import static android.service.trust.TrustAgentService.FLAG_GRANT_TRUST_DISPLAY_MESSAGE; + import android.annotation.TargetApi; import android.app.AlarmManager; import android.app.PendingIntent; @@ -99,6 +101,7 @@ public class TrustAgentWrapper { // Trust state private boolean mTrusted; private CharSequence mMessage; + private boolean mDisplayTrustGrantedMessage; private boolean mTrustDisabledByDpm; private boolean mManagingTrust; private IBinder mSetTrustAgentFeaturesToken; @@ -132,6 +135,7 @@ public class TrustAgentWrapper { mTrusted = true; mMessage = (CharSequence) msg.obj; int flags = msg.arg1; + mDisplayTrustGrantedMessage = (flags & FLAG_GRANT_TRUST_DISPLAY_MESSAGE) != 0; long durationMs = msg.getData().getLong(DATA_DURATION); if (durationMs > 0) { final long duration; @@ -166,6 +170,7 @@ public class TrustAgentWrapper { // Fall through. case MSG_REVOKE_TRUST: mTrusted = false; + mDisplayTrustGrantedMessage = false; mMessage = null; mHandler.removeMessages(MSG_TRUST_TIMEOUT); if (msg.what == MSG_REVOKE_TRUST) { @@ -199,6 +204,7 @@ public class TrustAgentWrapper { mManagingTrust = msg.arg1 != 0; if (!mManagingTrust) { mTrusted = false; + mDisplayTrustGrantedMessage = false; mMessage = null; } mTrustManagerService.mArchive.logManagingTrust(mUserId, mName, mManagingTrust); @@ -271,12 +277,13 @@ public class TrustAgentWrapper { private ITrustAgentServiceCallback mCallback = new ITrustAgentServiceCallback.Stub() { @Override - public void grantTrust(CharSequence userMessage, long durationMs, int flags) { - if (DEBUG) Slog.d(TAG, "enableTrust(" + userMessage + ", durationMs = " + durationMs + public void grantTrust(CharSequence message, long durationMs, int flags) { + if (DEBUG) { + Slog.d(TAG, "enableTrust(" + message + ", durationMs = " + durationMs + ", flags = " + flags + ")"); + } - Message msg = mHandler.obtainMessage( - MSG_GRANT_TRUST, flags, 0, userMessage); + Message msg = mHandler.obtainMessage(MSG_GRANT_TRUST, flags, 0, message); msg.getData().putLong(DATA_DURATION, durationMs); msg.sendToTarget(); } @@ -461,6 +468,19 @@ public class TrustAgentWrapper { } /** + * @see android.service.trust.TrustAgentService#onUserRequestedUnlock() + */ + public void onUserRequestedUnlock() { + try { + if (mTrustAgentService != null) { + mTrustAgentService.onUserRequestedUnlock(); + } + } catch (RemoteException e) { + onError(e); + } + } + + /** * @see android.service.trust.TrustAgentService#onUnlockLockout(int) */ public void onUnlockLockout(int timeoutMs) { @@ -579,6 +599,14 @@ public class TrustAgentWrapper { return mMessage; } + /** + * Whether the trust agent would like to display {@link #getMessage()} to the user when trust + * is granted. + */ + public boolean shouldDisplayTrustGrantedMessage() { + return mDisplayTrustGrantedMessage; + } + public void destroy() { mHandler.removeMessages(MSG_RESTART_TIMEOUT); diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java index fc87253825f4..9bed24d05f3d 100644 --- a/services/core/java/com/android/server/trust/TrustManagerService.java +++ b/services/core/java/com/android/server/trust/TrustManagerService.java @@ -75,7 +75,6 @@ import com.android.internal.content.PackageMonitor; import com.android.internal.util.DumpUtils; import com.android.internal.widget.LockPatternUtils; import com.android.server.SystemService; -import com.android.server.SystemService.TargetUser; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -123,6 +122,7 @@ public class TrustManagerService extends SystemService { private static final int MSG_DISPATCH_UNLOCK_LOCKOUT = 13; private static final int MSG_REFRESH_DEVICE_LOCKED_FOR_USER = 14; private static final int MSG_SCHEDULE_TRUST_TIMEOUT = 15; + public static final int MSG_USER_REQUESTED_UNLOCK = 16; private static final String REFRESH_DEVICE_LOCKED_EXCEPT_USER = "except"; @@ -391,7 +391,6 @@ public class TrustManagerService extends SystemService { } } - public void updateTrust(int userId, int flags) { updateTrust(userId, flags, false /* isFromUnlock */); } @@ -431,7 +430,7 @@ public class TrustManagerService extends SystemService { changed = mUserIsTrusted.get(userId) != trusted; mUserIsTrusted.put(userId, trusted); } - dispatchOnTrustChanged(trusted, userId, flags); + dispatchOnTrustChanged(trusted, userId, flags, getTrustGrantedMessages(userId)); if (changed) { refreshDeviceLockedForUser(userId); if (!trusted) { @@ -951,6 +950,24 @@ public class TrustManagerService extends SystemService { return false; } + private List<String> getTrustGrantedMessages(int userId) { + if (!mStrongAuthTracker.isTrustAllowedForUser(userId)) { + return new ArrayList<>(); + } + + List<String> trustGrantedMessages = new ArrayList<>(); + for (int i = 0; i < mActiveAgents.size(); i++) { + AgentInfo info = mActiveAgents.valueAt(i); + if (info.userId == userId + && info.agent.isTrusted() + && info.agent.shouldDisplayTrustGrantedMessage() + && !TextUtils.isEmpty(info.agent.getMessage())) { + trustGrantedMessages.add(info.agent.getMessage().toString()); + } + } + return trustGrantedMessages; + } + private boolean aggregateIsTrustManaged(int userId) { if (!mStrongAuthTracker.isTrustAllowedForUser(userId)) { return false; @@ -981,6 +998,15 @@ public class TrustManagerService extends SystemService { } } + private void dispatchUserRequestedUnlock(int userId) { + for (int i = 0; i < mActiveAgents.size(); i++) { + AgentInfo info = mActiveAgents.valueAt(i); + if (info.userId == userId) { + info.agent.onUserRequestedUnlock(); + } + } + } + private void dispatchUnlockLockout(int timeoutMs, int userId) { for (int i = 0; i < mActiveAgents.size(); i++) { AgentInfo info = mActiveAgents.valueAt(i); @@ -1011,7 +1037,8 @@ public class TrustManagerService extends SystemService { } } - private void dispatchOnTrustChanged(boolean enabled, int userId, int flags) { + private void dispatchOnTrustChanged(boolean enabled, int userId, int flags, + @NonNull List<String> trustGrantedMessages) { if (DEBUG) { Log.i(TAG, "onTrustChanged(" + enabled + ", " + userId + ", 0x" + Integer.toHexString(flags) + ")"); @@ -1019,7 +1046,7 @@ public class TrustManagerService extends SystemService { if (!enabled) flags = 0; for (int i = 0; i < mTrustListeners.size(); i++) { try { - mTrustListeners.get(i).onTrustChanged(enabled, userId, flags); + mTrustListeners.get(i).onTrustChanged(enabled, userId, flags, trustGrantedMessages); } catch (DeadObjectException e) { Slog.d(TAG, "Removing dead TrustListener."); mTrustListeners.remove(i); @@ -1110,6 +1137,12 @@ public class TrustManagerService extends SystemService { } @Override + public void reportUserRequestedUnlock(int userId) throws RemoteException { + enforceReportPermission(); + mHandler.obtainMessage(MSG_USER_REQUESTED_UNLOCK, userId).sendToTarget(); + } + + @Override public void reportUnlockLockout(int timeoutMs, int userId) throws RemoteException { enforceReportPermission(); mHandler.obtainMessage(MSG_DISPATCH_UNLOCK_LOCKOUT, timeoutMs, userId) @@ -1389,6 +1422,9 @@ public class TrustManagerService extends SystemService { case MSG_DISPATCH_UNLOCK_ATTEMPT: dispatchUnlockAttempt(msg.arg1 != 0, msg.arg2); break; + case MSG_USER_REQUESTED_UNLOCK: + dispatchUserRequestedUnlock(msg.arg1); + break; case MSG_DISPATCH_UNLOCK_LOCKOUT: dispatchUnlockLockout(msg.arg1, msg.arg2); break; diff --git a/services/core/java/com/android/server/tv/interactive/TvIAppManagerService.java b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java index 6058d8873e94..b3649a75ee5b 100644 --- a/services/core/java/com/android/server/tv/interactive/TvIAppManagerService.java +++ b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java @@ -34,15 +34,16 @@ import android.media.tv.AdResponse; import android.media.tv.BroadcastInfoRequest; import android.media.tv.BroadcastInfoResponse; import android.media.tv.TvTrackInfo; -import android.media.tv.interactive.ITvIAppManager; +import android.media.tv.interactive.AppLinkInfo; import android.media.tv.interactive.ITvInteractiveAppClient; +import android.media.tv.interactive.ITvInteractiveAppManager; import android.media.tv.interactive.ITvInteractiveAppManagerCallback; import android.media.tv.interactive.ITvInteractiveAppService; import android.media.tv.interactive.ITvInteractiveAppServiceCallback; import android.media.tv.interactive.ITvInteractiveAppSession; import android.media.tv.interactive.ITvInteractiveAppSessionCallback; -import android.media.tv.interactive.TvIAppService; import android.media.tv.interactive.TvInteractiveAppInfo; +import android.media.tv.interactive.TvInteractiveAppService; import android.net.Uri; import android.os.Binder; import android.os.Bundle; @@ -79,9 +80,9 @@ import java.util.Set; /** * This class provides a system service that manages interactive TV applications. */ -public class TvIAppManagerService extends SystemService { +public class TvInteractiveAppManagerService extends SystemService { private static final boolean DEBUG = false; - private static final String TAG = "TvIAppManagerService"; + private static final String TAG = "TvInteractiveAppManagerService"; // A global lock. private final Object mLock = new Object(); private final Context mContext; @@ -95,6 +96,10 @@ public class TvIAppManagerService extends SystemService { @GuardedBy("mLock") private final SparseArray<UserState> mUserStates = new SparseArray<>(); + // TODO: remove mGetServiceListCalled if onBootPhrase work correctly + @GuardedBy("mLock") + private boolean mGetServiceListCalled = false; + private final UserManager mUserManager; /** @@ -106,7 +111,7 @@ public class TvIAppManagerService extends SystemService { * * @param context The system server context. */ - public TvIAppManagerService(Context context) { + public TvInteractiveAppManagerService(Context context) { super(context); mContext = context; mUserManager = (UserManager) getContext().getSystemService(Context.USER_SERVICE); @@ -122,7 +127,7 @@ public class TvIAppManagerService extends SystemService { } PackageManager pm = mContext.getPackageManager(); List<ResolveInfo> services = pm.queryIntentServicesAsUser( - new Intent(TvIAppService.SERVICE_INTERFACE), + new Intent(TvInteractiveAppService.SERVICE_INTERFACE), PackageManager.GET_SERVICES | PackageManager.GET_META_DATA, userId); List<TvInteractiveAppInfo> iAppList = new ArrayList<>(); @@ -256,15 +261,16 @@ public class TvIAppManagerService extends SystemService { @GuardedBy("mLock") private void notifyStateChangedLocked( - UserState userState, String iAppServiceId, int type, int state) { + UserState userState, String iAppServiceId, int type, int state, int err) { if (DEBUG) { Slog.d(TAG, "notifyRteStateChanged(iAppServiceId=" - + iAppServiceId + ", type=" + type + ", state=" + state + ")"); + + iAppServiceId + ", type=" + type + ", state=" + state + ", err=" + err + ")"); } int n = userState.mCallbacks.beginBroadcast(); for (int i = 0; i < n; ++i) { try { - userState.mCallbacks.getBroadcastItem(i).onStateChanged(iAppServiceId, type, state); + userState.mCallbacks.getBroadcastItem(i) + .onStateChanged(iAppServiceId, type, state, err); } catch (RemoteException e) { Slog.e(TAG, "failed to report RTE state changed", e); } @@ -287,7 +293,7 @@ public class TvIAppManagerService extends SystemService { if (DEBUG) { Slogf.d(TAG, "onStart"); } - publishBinderService(Context.TV_IAPP_SERVICE, new BinderService()); + publishBinderService(Context.TV_INTERACTIVE_APP_SERVICE, new BinderService()); } @Override @@ -628,7 +634,7 @@ public class TvIAppManagerService extends SystemService { return session; } - private final class BinderService extends ITvIAppManager.Stub { + private final class BinderService extends ITvInteractiveAppManager.Stub { @Override public List<TvInteractiveAppInfo> getTvInteractiveAppServiceList(int userId) { @@ -637,6 +643,10 @@ public class TvIAppManagerService extends SystemService { final long identity = Binder.clearCallingIdentity(); try { synchronized (mLock) { + if (!mGetServiceListCalled) { + buildTvInteractiveAppServiceListLocked(userId, null); + mGetServiceListCalled = true; + } UserState userState = getOrCreateUserStateLocked(resolvedUserId); List<TvInteractiveAppInfo> iAppList = new ArrayList<>(); for (TvInteractiveAppState state : userState.mIAppMap.values()) { @@ -686,9 +696,9 @@ public class TvIAppManagerService extends SystemService { } @Override - public void registerAppLinkInfo(String tiasId, Bundle appLinkInfo, int userId) { + public void registerAppLinkInfo(String tiasId, AppLinkInfo appLinkInfo, int userId) { final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), - Binder.getCallingUid(), userId, "registerAppLinkInfo"); + Binder.getCallingUid(), userId, "registerAppLinkInfo: " + appLinkInfo); final long identity = Binder.clearCallingIdentity(); try { synchronized (mLock) { @@ -722,9 +732,9 @@ public class TvIAppManagerService extends SystemService { } @Override - public void unregisterAppLinkInfo(String tiasId, Bundle appLinkInfo, int userId) { + public void unregisterAppLinkInfo(String tiasId, AppLinkInfo appLinkInfo, int userId) { final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), - Binder.getCallingUid(), userId, "unregisterAppLinkInfo"); + Binder.getCallingUid(), userId, "unregisterAppLinkInfo: " + appLinkInfo); final long identity = Binder.clearCallingIdentity(); try { synchronized (mLock) { @@ -1361,7 +1371,7 @@ public class TvIAppManagerService extends SystemService { } } finally { if (surface != null) { - // surface is not used in TvIAppManagerService. + // surface is not used in TvInteractiveAppManagerService. surface.release(); } Binder.restoreCallingIdentity(identity); @@ -1678,7 +1688,7 @@ public class TvIAppManagerService extends SystemService { } Intent i = - new Intent(TvIAppService.SERVICE_INTERFACE).setComponent(component); + new Intent(TvInteractiveAppService.SERVICE_INTERFACE).setComponent(component); serviceState.mBound = mContext.bindServiceAsUser( i, serviceState.mConnection, Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE, @@ -1810,7 +1820,7 @@ public class TvIAppManagerService extends SystemService { private final ServiceConnection mConnection; private final ComponentName mComponent; private final String mIAppServiceId; - private final List<Pair<Bundle, Boolean>> mPendingAppLinkInfo = new ArrayList<>(); + private final List<Pair<AppLinkInfo, Boolean>> mPendingAppLinkInfo = new ArrayList<>(); private final List<Bundle> mPendingAppLinkCommand = new ArrayList<>(); private boolean mPendingPrepare = false; @@ -1833,7 +1843,7 @@ public class TvIAppManagerService extends SystemService { mIAppServiceId = tias; } - private void addPendingAppLink(Bundle info, boolean register) { + private void addPendingAppLink(AppLinkInfo info, boolean register) { mPendingAppLinkInfo.add(Pair.create(info, register)); } @@ -1866,6 +1876,16 @@ public class TvIAppManagerService extends SystemService { ServiceState serviceState = userState.mServiceStateMap.get(mComponent); serviceState.mService = ITvInteractiveAppService.Stub.asInterface(service); + // Register a callback, if we need to. + if (serviceState.mCallback == null) { + serviceState.mCallback = new ServiceCallback(mComponent, mUserId); + try { + serviceState.mService.registerCallback(serviceState.mCallback); + } catch (RemoteException e) { + Slog.e(TAG, "error in registerCallback", e); + } + } + if (serviceState.mPendingPrepare) { final long identity = Binder.clearCallingIdentity(); try { @@ -1880,10 +1900,10 @@ public class TvIAppManagerService extends SystemService { } if (!serviceState.mPendingAppLinkInfo.isEmpty()) { - for (Iterator<Pair<Bundle, Boolean>> it = + for (Iterator<Pair<AppLinkInfo, Boolean>> it = serviceState.mPendingAppLinkInfo.iterator(); it.hasNext(); ) { - Pair<Bundle, Boolean> appLinkInfoPair = it.next(); + Pair<AppLinkInfo, Boolean> appLinkInfoPair = it.next(); final long identity = Binder.clearCallingIdentity(); try { if (appLinkInfoPair.second) { @@ -1968,14 +1988,14 @@ public class TvIAppManagerService extends SystemService { } @Override - public void onStateChanged(int type, int state) { + public void onStateChanged(int type, int state, int error) { final long identity = Binder.clearCallingIdentity(); try { synchronized (mLock) { ServiceState serviceState = getServiceStateLocked(mComponent, mUserId); String iAppServiceId = serviceState.mIAppServiceId; UserState userState = getUserStateLocked(mUserId); - notifyStateChangedLocked(userState, iAppServiceId, type, state); + notifyStateChangedLocked(userState, iAppServiceId, type, state, error); } } finally { Binder.restoreCallingIdentity(identity); @@ -2072,7 +2092,7 @@ public class TvIAppManagerService extends SystemService { @Override public void onCommandRequest( - @TvIAppService.InteractiveAppServiceCommandType String cmdType, + @TvInteractiveAppService.InteractiveAppServiceCommandType String cmdType, Bundle parameters) { synchronized (mLock) { if (DEBUG) { @@ -2210,16 +2230,16 @@ public class TvIAppManagerService extends SystemService { } @Override - public void onSessionStateChanged(int state) { + public void onSessionStateChanged(int state, int err) { synchronized (mLock) { if (DEBUG) { - Slogf.d(TAG, "onSessionStateChanged (state=" + state + ")"); + Slogf.d(TAG, "onSessionStateChanged (state=" + state + ", err=" + err + ")"); } if (mSessionState.mSession == null || mSessionState.mClient == null) { return; } try { - mSessionState.mClient.onSessionStateChanged(state, mSessionState.mSeq); + mSessionState.mClient.onSessionStateChanged(state, err, mSessionState.mSeq); } catch (RemoteException e) { Slogf.e(TAG, "error in onSessionStateChanged", e); } diff --git a/services/core/java/com/android/server/uri/UriGrantsManagerService.java b/services/core/java/com/android/server/uri/UriGrantsManagerService.java index 344270427569..6aa06e8ee068 100644 --- a/services/core/java/com/android/server/uri/UriGrantsManagerService.java +++ b/services/core/java/com/android/server/uri/UriGrantsManagerService.java @@ -104,7 +104,8 @@ import java.util.List; import java.util.Objects; /** Manages uri grants. */ -public class UriGrantsManagerService extends IUriGrantsManager.Stub { +public class UriGrantsManagerService extends IUriGrantsManager.Stub implements + UriMetricsHelper.PersistentUriGrantsProvider { private static final boolean DEBUG = false; private static final String TAG = "UriGrantsManagerService"; // Maximum number of persisted Uri grants a package is allowed @@ -115,6 +116,7 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub { private final H mH; ActivityManagerInternal mAmInternal; PackageManagerInternal mPmInternal; + UriMetricsHelper mMetricsHelper; /** File storing persisted {@link #mGrantedUriPermissions}. */ private final AtomicFile mGrantFile; @@ -168,16 +170,19 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub { } public static final class Lifecycle extends SystemService { + private final Context mContext; private final UriGrantsManagerService mService; public Lifecycle(Context context) { super(context); + mContext = context; mService = new UriGrantsManagerService(); } @Override public void onStart() { publishBinderService(Context.URI_GRANTS_SERVICE, mService); + mService.mMetricsHelper = new UriMetricsHelper(mContext, mService); mService.start(); } @@ -186,6 +191,7 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub { if (phase == PHASE_SYSTEM_SERVICES_READY) { mService.mAmInternal = LocalServices.getService(ActivityManagerInternal.class); mService.mPmInternal = LocalServices.getService(PackageManagerInternal.class); + mService.mMetricsHelper.registerPuller(); } } @@ -1298,20 +1304,50 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub { return false; } + @Override + public ArrayList<UriPermission> providePersistentUriGrants() { + final ArrayList<UriPermission> result = new ArrayList<>(); + + synchronized (mLock) { + final int size = mGrantedUriPermissions.size(); + for (int i = 0; i < size; i++) { + final ArrayMap<GrantUri, UriPermission> perms = mGrantedUriPermissions.valueAt(i); + + final int permissionsForPackageSize = perms.size(); + for (int j = 0; j < permissionsForPackageSize; j++) { + final UriPermission permission = perms.valueAt(j); + + if (permission.persistedModeFlags != 0) { + result.add(permission); + } + } + } + } + + return result; + } + private void writeGrantedUriPermissions() { if (DEBUG) Slog.v(TAG, "writeGrantedUriPermissions()"); final long startTime = SystemClock.uptimeMillis(); + int persistentUriPermissionsCount = 0; + // Snapshot permissions so we can persist without lock ArrayList<UriPermission.Snapshot> persist = Lists.newArrayList(); synchronized (mLock) { final int size = mGrantedUriPermissions.size(); for (int i = 0; i < size; i++) { final ArrayMap<GrantUri, UriPermission> perms = mGrantedUriPermissions.valueAt(i); - for (UriPermission perm : perms.values()) { - if (perm.persistedModeFlags != 0) { - persist.add(perm.snapshot()); + + final int permissionsForPackageSize = perms.size(); + for (int j = 0; j < permissionsForPackageSize; j++) { + final UriPermission permission = perms.valueAt(j); + + if (permission.persistedModeFlags != 0) { + persistentUriPermissionsCount++; + persist.add(permission.snapshot()); } } } @@ -1345,6 +1381,8 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub { mGrantFile.failWrite(fos); } } + + mMetricsHelper.reportPersistentUriFlushed(persistentUriPermissionsCount); } final class H extends Handler { diff --git a/services/core/java/com/android/server/uri/UriMetricsHelper.java b/services/core/java/com/android/server/uri/UriMetricsHelper.java new file mode 100644 index 000000000000..dbc959928a3b --- /dev/null +++ b/services/core/java/com/android/server/uri/UriMetricsHelper.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.uri; + +import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR; + +import android.app.StatsManager; +import android.content.Context; +import android.util.SparseArray; +import android.util.StatsEvent; + +import com.android.internal.util.FrameworkStatsLog; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; + +final class UriMetricsHelper { + + private static final StatsManager.PullAtomMetadata DAILY_PULL_METADATA = + new StatsManager.PullAtomMetadata.Builder() + .setCoolDownMillis(TimeUnit.DAYS.toMillis(1)) + .build(); + + + private final Context mContext; + private final PersistentUriGrantsProvider mPersistentUriGrantsProvider; + + UriMetricsHelper(Context context, PersistentUriGrantsProvider provider) { + mContext = context; + mPersistentUriGrantsProvider = provider; + } + + void registerPuller() { + final StatsManager statsManager = mContext.getSystemService(StatsManager.class); + statsManager.setPullAtomCallback( + FrameworkStatsLog.PERSISTENT_URI_PERMISSIONS_AMOUNT_PER_PACKAGE, + DAILY_PULL_METADATA, + DIRECT_EXECUTOR, + (atomTag, data) -> { + reportPersistentUriPermissionsPerPackage(data); + return StatsManager.PULL_SUCCESS; + }); + } + + void reportPersistentUriFlushed(int amount) { + FrameworkStatsLog.write( + FrameworkStatsLog.PERSISTENT_URI_PERMISSIONS_FLUSHED, + amount + ); + } + + private void reportPersistentUriPermissionsPerPackage(List<StatsEvent> data) { + final ArrayList<UriPermission> persistentUriGrants = + mPersistentUriGrantsProvider.providePersistentUriGrants(); + + final SparseArray<Integer> perUidCount = new SparseArray<>(); + + final int persistentUriGrantsSize = persistentUriGrants.size(); + for (int i = 0; i < persistentUriGrantsSize; i++) { + final UriPermission uriPermission = persistentUriGrants.get(i); + + perUidCount.put( + uriPermission.targetUid, + perUidCount.get(uriPermission.targetUid, 0) + 1 + ); + } + + final int perUidCountSize = perUidCount.size(); + for (int i = 0; i < perUidCountSize; i++) { + final int uid = perUidCount.keyAt(i); + final int amount = perUidCount.valueAt(i); + + data.add( + FrameworkStatsLog.buildStatsEvent( + FrameworkStatsLog.PERSISTENT_URI_PERMISSIONS_AMOUNT_PER_PACKAGE, + uid, + amount + ) + ); + } + } + + interface PersistentUriGrantsProvider { + ArrayList<UriPermission> providePersistentUriGrants(); + } +} diff --git a/services/core/java/com/android/server/vcn/Vcn.java b/services/core/java/com/android/server/vcn/Vcn.java index e0cc8e182079..f29c40f74353 100644 --- a/services/core/java/com/android/server/vcn/Vcn.java +++ b/services/core/java/com/android/server/vcn/Vcn.java @@ -39,10 +39,13 @@ import android.net.vcn.VcnConfig; import android.net.vcn.VcnGatewayConnectionConfig; import android.net.vcn.VcnManager.VcnErrorCode; import android.os.Handler; +import android.os.HandlerExecutor; import android.os.Message; import android.os.ParcelUuid; import android.provider.Settings; +import android.telephony.TelephonyCallback; import android.telephony.TelephonyManager; +import android.util.ArrayMap; import android.util.ArraySet; import android.util.Slog; @@ -57,6 +60,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -148,6 +152,10 @@ public class Vcn extends Handler { @NonNull private final VcnContentResolver mContentResolver; @NonNull private final ContentObserver mMobileDataSettingsObserver; + @NonNull + private final Map<Integer, VcnUserMobileDataStateListener> mMobileDataStateListeners = + new ArrayMap<>(); + /** * Map containing all VcnGatewayConnections and their VcnGatewayConnectionConfigs. * @@ -221,6 +229,9 @@ public class Vcn extends Handler { // Update mIsMobileDataEnabled before starting handling of NetworkRequests. mIsMobileDataEnabled = getMobileDataStatus(); + // Register mobile data state listeners. + updateMobileDataStateListeners(); + // Register to receive cached and future NetworkRequests mVcnContext.getVcnNetworkProvider().registerListener(mRequestListener); } @@ -348,6 +359,12 @@ public class Vcn extends Handler { gatewayConnection.teardownAsynchronously(); } + // Unregister MobileDataStateListeners + for (VcnUserMobileDataStateListener listener : mMobileDataStateListeners.values()) { + getTelephonyManager().unregisterTelephonyCallback(listener); + } + mMobileDataStateListeners.clear(); + mCurrentStatus = VCN_STATUS_CODE_INACTIVE; } @@ -454,11 +471,40 @@ public class Vcn extends Handler { gatewayConnection.updateSubscriptionSnapshot(mLastSnapshot); } + updateMobileDataStateListeners(); + // Update the mobile data state after updating the subscription snapshot as a change in // subIds for a subGroup may affect the mobile data state. handleMobileDataToggled(); } + private void updateMobileDataStateListeners() { + final Set<Integer> subIdsInGroup = mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup); + final HandlerExecutor executor = new HandlerExecutor(this); + + // Register new callbacks + for (int subId : subIdsInGroup) { + if (!mMobileDataStateListeners.containsKey(subId)) { + final VcnUserMobileDataStateListener listener = + new VcnUserMobileDataStateListener(); + + getTelephonyManagerForSubid(subId).registerTelephonyCallback(executor, listener); + mMobileDataStateListeners.put(subId, listener); + } + } + + // Unregister old callbacks + Iterator<Entry<Integer, VcnUserMobileDataStateListener>> iterator = + mMobileDataStateListeners.entrySet().iterator(); + while (iterator.hasNext()) { + final Entry<Integer, VcnUserMobileDataStateListener> entry = iterator.next(); + if (!subIdsInGroup.contains(entry.getKey())) { + getTelephonyManager().unregisterTelephonyCallback(entry.getValue()); + iterator.remove(); + } + } + } + private void handleMobileDataToggled() { final boolean oldMobileDataEnabledStatus = mIsMobileDataEnabled; mIsMobileDataEnabled = getMobileDataStatus(); @@ -493,11 +539,8 @@ public class Vcn extends Handler { } private boolean getMobileDataStatus() { - final TelephonyManager genericTelMan = - mVcnContext.getContext().getSystemService(TelephonyManager.class); - for (int subId : mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup)) { - if (genericTelMan.createForSubscriptionId(subId).isDataEnabled()) { + if (getTelephonyManagerForSubid(subId).isDataEnabled()) { return true; } } @@ -517,6 +560,14 @@ public class Vcn extends Handler { return request.canBeSatisfiedBy(builder.build()); } + private TelephonyManager getTelephonyManager() { + return mVcnContext.getContext().getSystemService(TelephonyManager.class); + } + + private TelephonyManager getTelephonyManagerForSubid(int subid) { + return getTelephonyManager().createForSubscriptionId(subid); + } + private String getLogPrefix() { return "[" + LogUtils.getHashedSubscriptionGroup(mSubscriptionGroup) @@ -670,6 +721,16 @@ public class Vcn extends Handler { } } + @VisibleForTesting(visibility = Visibility.PRIVATE) + class VcnUserMobileDataStateListener extends TelephonyCallback + implements TelephonyCallback.UserMobileDataStateListener { + + @Override + public void onUserMobileDataStateChanged(boolean enabled) { + sendMessage(obtainMessage(MSG_EVENT_MOBILE_DATA_TOGGLED)); + } + } + /** External dependencies used by Vcn, for injection in tests */ @VisibleForTesting(visibility = Visibility.PRIVATE) public static class Dependencies { diff --git a/services/core/java/com/android/server/vibrator/ClippingAmplitudeAndFrequencyAdapter.java b/services/core/java/com/android/server/vibrator/ClippingAmplitudeAndFrequencyAdapter.java index 8189e74f922c..160f4f971e2f 100644 --- a/services/core/java/com/android/server/vibrator/ClippingAmplitudeAndFrequencyAdapter.java +++ b/services/core/java/com/android/server/vibrator/ClippingAmplitudeAndFrequencyAdapter.java @@ -26,8 +26,8 @@ import android.util.Range; import java.util.List; /** - * Adapter that clips frequency values to {@link VibratorInfo#getFrequencyRangeHz()} and - * amplitude values to respective {@link VibratorInfo#getMaxAmplitude}. + * Adapter that clips frequency values to the ones specified by the + * {@link VibratorInfo.FrequencyProfile}. * * <p>Devices with no frequency control will collapse all frequencies to the resonant frequency and * leave amplitudes unchanged. @@ -69,20 +69,20 @@ final class ClippingAmplitudeAndFrequencyAdapter } private float clampFrequency(VibratorInfo info, float frequencyHz) { - Range<Float> frequencyRangeHz = info.getFrequencyRangeHz(); + Range<Float> frequencyRangeHz = info.getFrequencyProfile().getFrequencyRangeHz(); if (frequencyHz == 0 || frequencyRangeHz == null) { - return info.getResonantFrequency(); + return info.getResonantFrequencyHz(); } return frequencyRangeHz.clamp(frequencyHz); } private float clampAmplitude(VibratorInfo info, float frequencyHz, float amplitude) { - Range<Float> frequencyRangeHz = info.getFrequencyRangeHz(); - if (frequencyRangeHz == null) { - // No frequency range was specified, leave amplitude unchanged, the frequency will be - // clamped to the device's resonant frequency. + VibratorInfo.FrequencyProfile mapping = info.getFrequencyProfile(); + if (mapping.isEmpty()) { + // No frequency mapping was specified so leave amplitude unchanged. + // The frequency will be clamped to the device's resonant frequency. return amplitude; } - return MathUtils.min(amplitude, info.getMaxAmplitude(frequencyHz)); + return MathUtils.min(amplitude, mapping.getMaxAmplitude(frequencyHz)); } } diff --git a/services/core/java/com/android/server/vibrator/RampToStepAdapter.java b/services/core/java/com/android/server/vibrator/RampToStepAdapter.java index c592a7072407..c943bb283fe7 100644 --- a/services/core/java/com/android/server/vibrator/RampToStepAdapter.java +++ b/services/core/java/com/android/server/vibrator/RampToStepAdapter.java @@ -94,6 +94,6 @@ final class RampToStepAdapter implements VibrationEffectAdapters.SegmentsAdapter } private static float fillEmptyFrequency(VibratorInfo info, float frequencyHz) { - return frequencyHz == 0 ? info.getResonantFrequency() : frequencyHz; + return frequencyHz == 0 ? info.getResonantFrequencyHz() : frequencyHz; } } diff --git a/services/core/java/com/android/server/vibrator/StepToRampAdapter.java b/services/core/java/com/android/server/vibrator/StepToRampAdapter.java index 5ace3896f387..86fc642f7230 100644 --- a/services/core/java/com/android/server/vibrator/StepToRampAdapter.java +++ b/services/core/java/com/android/server/vibrator/StepToRampAdapter.java @@ -148,6 +148,6 @@ final class StepToRampAdapter implements VibrationEffectAdapters.SegmentsAdapter } private static float fillEmptyFrequency(VibratorInfo info, float frequencyHz) { - return frequencyHz == 0 ? info.getResonantFrequency() : frequencyHz; + return frequencyHz == 0 ? info.getResonantFrequencyHz() : frequencyHz; } } diff --git a/services/core/java/com/android/server/vibrator/VibrationScaler.java b/services/core/java/com/android/server/vibrator/VibrationScaler.java index f481772d7a7f..a528f063e875 100644 --- a/services/core/java/com/android/server/vibrator/VibrationScaler.java +++ b/services/core/java/com/android/server/vibrator/VibrationScaler.java @@ -74,6 +74,12 @@ final class VibrationScaler { public int getExternalVibrationScale(int usageHint) { int defaultIntensity = mSettingsController.getDefaultIntensity(usageHint); int currentIntensity = mSettingsController.getCurrentIntensity(usageHint); + + if (currentIntensity == Vibrator.VIBRATION_INTENSITY_OFF) { + // Bypassing user settings, or it has changed between checking and scaling. Use default. + return SCALE_NONE; + } + int scaleLevel = currentIntensity - defaultIntensity; if (scaleLevel >= SCALE_VERY_LOW && scaleLevel <= SCALE_VERY_HIGH) { @@ -97,6 +103,12 @@ final class VibrationScaler { public <T extends VibrationEffect> T scale(VibrationEffect effect, int usageHint) { int defaultIntensity = mSettingsController.getDefaultIntensity(usageHint); int currentIntensity = mSettingsController.getCurrentIntensity(usageHint); + + if (currentIntensity == Vibrator.VIBRATION_INTENSITY_OFF) { + // Bypassing user settings, or it has changed between checking and scaling. Use default. + currentIntensity = defaultIntensity; + } + int newEffectStrength = intensityToEffectStrength(currentIntensity); effect = effect.applyEffectStrength(newEffectStrength).resolve(mDefaultVibrationAmplitude); ScaleLevel scale = mScaleLevels.get(currentIntensity - defaultIntensity); @@ -121,6 +133,12 @@ final class VibrationScaler { */ public PrebakedSegment scale(PrebakedSegment prebaked, int usageHint) { int currentIntensity = mSettingsController.getCurrentIntensity(usageHint); + + if (currentIntensity == Vibrator.VIBRATION_INTENSITY_OFF) { + // Bypassing user settings, or it has changed between checking and scaling. Use default. + currentIntensity = mSettingsController.getDefaultIntensity(usageHint); + } + int newEffectStrength = intensityToEffectStrength(currentIntensity); return prebaked.applyEffectStrength(newEffectStrength); } diff --git a/services/core/java/com/android/server/vibrator/VibrationSettings.java b/services/core/java/com/android/server/vibrator/VibrationSettings.java index df6ffa2bd009..eafd9d7f0b6c 100644 --- a/services/core/java/com/android/server/vibrator/VibrationSettings.java +++ b/services/core/java/com/android/server/vibrator/VibrationSettings.java @@ -97,6 +97,15 @@ final class VibrationSettings { USAGE_ALARM, USAGE_COMMUNICATION_REQUEST)); + /** + * Usage allowed for vibrations when {@link Settings.System#VIBRATE_ON} is disabled. + * + * <p>The only allowed usage is accessibility, which is applied when the user enables talkback. + * Other usages that must ignore this setting should use + * {@link VibrationAttributes#FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF}. + */ + private static final int VIBRATE_ON_DISABLED_USAGE_ALLOWED = USAGE_ACCESSIBILITY; + /** Listener for changes on vibration settings. */ interface OnVibratorSettingsChanged { /** Callback triggered when any of the vibrator settings change. */ @@ -127,6 +136,8 @@ final class VibrationSettings { private SparseIntArray mCurrentVibrationIntensities = new SparseIntArray(); @GuardedBy("mLock") private boolean mBatterySaverMode; + @GuardedBy("mLock") + private boolean mVibrateOn; VibrationSettings(Context context, Handler handler) { this(context, handler, new VibrationConfig(context.getResources())); @@ -168,7 +179,7 @@ final class VibrationSettings { try { ActivityManager.getService().registerUidObserver(mUidObserver, ActivityManager.UID_OBSERVER_PROCSTATE | ActivityManager.UID_OBSERVER_GONE, - ActivityManager.PROCESS_STATE_UNKNOWN, null); + ActivityManager.PROCESS_STATE_UNKNOWN, mContext.getOpPackageName()); } catch (RemoteException e) { // ignored; both services live in system_server } @@ -199,6 +210,7 @@ final class VibrationSettings { // Listen to all settings that might affect the result of Vibrator.getVibrationIntensity. registerSettingsObserver(Settings.System.getUriFor(Settings.System.VIBRATE_INPUT_DEVICES)); + registerSettingsObserver(Settings.System.getUriFor(Settings.System.VIBRATE_ON)); registerSettingsObserver(Settings.System.getUriFor(Settings.System.VIBRATE_WHEN_RINGING)); registerSettingsObserver(Settings.System.getUriFor(Settings.System.APPLY_RAMPING_RINGER)); registerSettingsObserver(Settings.System.getUriFor( @@ -314,9 +326,14 @@ final class VibrationSettings { return Vibration.Status.IGNORED_FOR_POWER; } - int intensity = getCurrentIntensity(usage); - if (intensity == Vibrator.VIBRATION_INTENSITY_OFF) { - return Vibration.Status.IGNORED_FOR_SETTINGS; + if (!attrs.isFlagSet(VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF)) { + if (!mVibrateOn && (VIBRATE_ON_DISABLED_USAGE_ALLOWED != usage)) { + return Vibration.Status.IGNORED_FOR_SETTINGS; + } + + if (getCurrentIntensity(usage) == Vibrator.VIBRATION_INTENSITY_OFF) { + return Vibration.Status.IGNORED_FOR_SETTINGS; + } } if (!shouldVibrateForRingerModeLocked(usage)) { @@ -355,6 +372,7 @@ final class VibrationSettings { void updateSettings() { synchronized (mLock) { mVibrateInputDevices = loadSystemSetting(Settings.System.VIBRATE_INPUT_DEVICES, 0) > 0; + mVibrateOn = loadSystemSetting(Settings.System.VIBRATE_ON, 1) > 0; int alarmIntensity = toIntensity( loadSystemSetting(Settings.System.ALARM_VIBRATION_INTENSITY, -1), @@ -435,8 +453,9 @@ final class VibrationSettings { + "mVibratorConfig=" + mVibrationConfig + ", mVibrateInputDevices=" + mVibrateInputDevices + ", mBatterySaverMode=" + mBatterySaverMode - + ", mProcStatesCache=" + mUidObserver.mProcStatesCache + + ", mVibrateOn=" + mVibrateOn + ", mVibrationIntensities=" + vibrationIntensitiesString + + ", mProcStatesCache=" + mUidObserver.mProcStatesCache + '}'; } } @@ -444,6 +463,8 @@ final class VibrationSettings { /** Write current settings into given {@link ProtoOutputStream}. */ public void dumpProto(ProtoOutputStream proto) { synchronized (mLock) { + proto.write(VibratorManagerServiceDumpProto.VIBRATE_ON, mVibrateOn); + proto.write(VibratorManagerServiceDumpProto.LOW_POWER_MODE, mBatterySaverMode); proto.write(VibratorManagerServiceDumpProto.ALARM_INTENSITY, getCurrentIntensity(USAGE_ALARM)); proto.write(VibratorManagerServiceDumpProto.ALARM_DEFAULT_INTENSITY, diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java index 27566b301a6e..a95b6c955d63 100644 --- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java +++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java @@ -80,6 +80,9 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { private static final boolean DEBUG = false; private static final VibrationAttributes DEFAULT_ATTRIBUTES = new VibrationAttributes.Builder().build(); + private static final int ATTRIBUTES_ALL_BYPASS_FLAGS = + VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY + | VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF; /** Lifecycle responsible for initializing this class at the right system server phases. */ public static class Lifecycle extends SystemService { @@ -975,12 +978,12 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { usage = VibrationAttributes.USAGE_TOUCH; } int flags = attrs.getFlags(); - if (attrs.isFlagSet(VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY)) { + if ((flags & ATTRIBUTES_ALL_BYPASS_FLAGS) != 0) { if (!(hasPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) || hasPermission(android.Manifest.permission.MODIFY_PHONE_STATE) || hasPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING))) { - // Remove bypass policy flag from attributes if the app does not have permissions. - flags &= ~VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY; + // Remove bypass flags from attributes if the app does not have permissions. + flags &= ~ATTRIBUTES_ALL_BYPASS_FLAGS; } } if ((usage == attrs.getUsage()) && (flags == attrs.getFlags())) { diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index 2ec42b41fc9f..ee2cc7bd7486 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -81,7 +81,9 @@ import android.os.ParcelFileDescriptor; import android.os.Process; import android.os.RemoteCallbackList; import android.os.RemoteException; +import android.os.ResultReceiver; import android.os.SELinux; +import android.os.ShellCallback; import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; @@ -92,6 +94,7 @@ import android.service.wallpaper.IWallpaperService; import android.service.wallpaper.WallpaperService; import android.system.ErrnoException; import android.system.Os; +import android.util.ArrayMap; import android.util.EventLog; import android.util.Slog; import android.util.SparseArray; @@ -420,7 +423,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub Slog.v(TAG, "notifyWallpaperColorsChangedOnDisplay " + which); } - needsExtraction = wallpaper.primaryColors == null; + needsExtraction = wallpaper.primaryColors == null || wallpaper.mIsColorExtractedFromDim; } if (needsExtraction) { @@ -491,12 +494,17 @@ public class WallpaperManagerService extends IWallpaperManager.Stub String cropFile = null; boolean defaultImageWallpaper = false; int wallpaperId; + float dimAmount; + + synchronized (mLock) { + wallpaper.mIsColorExtractedFromDim = false; + } if (wallpaper.equals(mFallbackWallpaper)) { synchronized (mLock) { if (mFallbackWallpaper.primaryColors != null) return; } - final WallpaperColors colors = extractDefaultImageWallpaperColors(); + final WallpaperColors colors = extractDefaultImageWallpaperColors(wallpaper); synchronized (mLock) { mFallbackWallpaper.primaryColors = colors; } @@ -513,18 +521,19 @@ public class WallpaperManagerService extends IWallpaperManager.Stub defaultImageWallpaper = true; } wallpaperId = wallpaper.wallpaperId; + dimAmount = wallpaper.mWallpaperDimAmount; } WallpaperColors colors = null; if (cropFile != null) { Bitmap bitmap = BitmapFactory.decodeFile(cropFile); if (bitmap != null) { - colors = WallpaperColors.fromBitmap(bitmap); + colors = WallpaperColors.fromBitmap(bitmap, dimAmount); bitmap.recycle(); } } else if (defaultImageWallpaper) { // There is no crop and source file because this is default image wallpaper. - colors = extractDefaultImageWallpaperColors(); + colors = extractDefaultImageWallpaperColors(wallpaper); } if (colors == null) { @@ -544,11 +553,13 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } } - private WallpaperColors extractDefaultImageWallpaperColors() { + private WallpaperColors extractDefaultImageWallpaperColors(WallpaperData wallpaper) { if (DEBUG) Slog.d(TAG, "Extract default image wallpaper colors"); + float dimAmount; synchronized (mLock) { if (mCacheDefaultImageWallpaperColors != null) return mCacheDefaultImageWallpaperColors; + dimAmount = wallpaper.mWallpaperDimAmount; } WallpaperColors colors = null; @@ -561,7 +572,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub final BitmapFactory.Options options = new BitmapFactory.Options(); final Bitmap bitmap = BitmapFactory.decodeStream(is, null, options); if (bitmap != null) { - colors = WallpaperColors.fromBitmap(bitmap); + colors = WallpaperColors.fromBitmap(bitmap, dimAmount); bitmap.recycle(); } } catch (OutOfMemoryError e) { @@ -948,6 +959,23 @@ public class WallpaperManagerService extends IWallpaperManager.Stub WallpaperObserver wallpaperObserver; /** + * The dim amount to be applied to the wallpaper. + */ + float mWallpaperDimAmount = 0.0f; + + /** + * A map to keep track of the dimming set by different applications. The key is the calling + * UID and the value is the dim amount. + */ + ArrayMap<Integer, Float> mUidToDimAmount = new ArrayMap<>(); + + /** + * Whether we need to extract the wallpaper colors again to calculate the dark hints + * after dimming is applied. + */ + boolean mIsColorExtractedFromDim; + + /** * List of callbacks registered they should each be notified when the wallpaper is changed. */ private RemoteCallbackList<IWallpaperManagerCallback> callbacks @@ -1487,6 +1515,15 @@ public class WallpaperManagerService extends IWallpaperManager.Stub Slog.w(TAG, "Failed to register local colors areas", e); } } + + if (mWallpaper.mWallpaperDimAmount != 0f) { + try { + connector.mEngine.applyDimming(mWallpaper.mWallpaperDimAmount); + notifyWallpaperColorsChanged(mWallpaper, FLAG_SYSTEM); + } catch (RemoteException e) { + Slog.w(TAG, "Failed to dim wallpaper", e); + } + } } } @@ -2536,6 +2573,98 @@ public class WallpaperManagerService extends IWallpaperManager.Stub if (purgeAreas.size() > 0) engine.removeLocalColorsAreas(purgeAreas); } + /** + * Returns true if the lock screen wallpaper exists (different wallpaper from the system) + */ + @Override + public boolean lockScreenWallpaperExists() { + synchronized (mLock) { + return mLockWallpaperMap.get(mCurrentUserId) != null; + } + } + + /** + * Sets wallpaper dim amount for the calling UID. This only applies to FLAG_SYSTEM wallpaper as + * the lock screen does not have a wallpaper component, so we use mWallpaperMap. + * + * @param dimAmount Dim amount which would be blended with the system default dimming. + */ + @Override + public void setWallpaperDimAmount(float dimAmount) throws RemoteException { + checkPermission(android.Manifest.permission.SET_WALLPAPER_DIM_AMOUNT); + int uid = Binder.getCallingUid(); + final long ident = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + WallpaperData wallpaper = mWallpaperMap.get(mCurrentUserId); + WallpaperData lockWallpaper = mLockWallpaperMap.get(mCurrentUserId); + + if (dimAmount == 0.0f) { + wallpaper.mUidToDimAmount.remove(uid); + } else { + wallpaper.mUidToDimAmount.put(uid, dimAmount); + } + + float maxDimAmount = getHighestDimAmountFromMap(wallpaper.mUidToDimAmount); + wallpaper.mWallpaperDimAmount = maxDimAmount; + // Also set the dim amount to the lock screen wallpaper if the lock and home screen + // do not share the same wallpaper + if (lockWallpaper != null) { + lockWallpaper.mWallpaperDimAmount = maxDimAmount; + } + + if (wallpaper.connection != null) { + wallpaper.connection.forEachDisplayConnector(connector -> { + if (connector.mEngine != null) { + try { + connector.mEngine.applyDimming(maxDimAmount); + } catch (RemoteException e) { + Slog.w(TAG, + "Can't apply dimming on wallpaper display connector", e); + } + } + }); + // Need to extract colors again to re-calculate dark hints after + // applying dimming. + wallpaper.mIsColorExtractedFromDim = true; + notifyWallpaperColorsChanged(wallpaper, FLAG_SYSTEM); + if (lockWallpaper != null) { + lockWallpaper.mIsColorExtractedFromDim = true; + notifyWallpaperColorsChanged(lockWallpaper, FLAG_LOCK); + } + saveSettingsLocked(wallpaper.userId); + } + } + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + @Override + public float getWallpaperDimAmount() { + checkPermission(android.Manifest.permission.SET_WALLPAPER_DIM_AMOUNT); + synchronized (mLock) { + WallpaperData data = mWallpaperMap.get(mCurrentUserId); + return data.mWallpaperDimAmount; + } + } + + /** + * Gets the highest dim amount among all the calling UIDs that set the wallpaper dim amount. + * Return 0f as default value to indicate no application has dimmed the wallpaper. + * + * @param uidToDimAmountMap Map of UIDs to dim amounts + */ + private float getHighestDimAmountFromMap(ArrayMap<Integer, Float> uidToDimAmountMap) { + float maxDimAmount = 0.0f; + for (Map.Entry<Integer, Float> entry : uidToDimAmountMap.entrySet()) { + if (entry.getValue() > maxDimAmount) { + maxDimAmount = entry.getValue(); + } + } + return maxDimAmount; + } + @Override public WallpaperColors getWallpaperColors(int which, int userId, int displayId) throws RemoteException { @@ -2562,7 +2691,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub if (wallpaperData == null) { return null; } - shouldExtract = wallpaperData.primaryColors == null; + shouldExtract = wallpaperData.primaryColors == null + || wallpaperData.mIsColorExtractedFromDim; } if (shouldExtract) { @@ -2664,6 +2794,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub lockWP.cropHint.set(sysWP.cropHint); lockWP.allowBackup = sysWP.allowBackup; lockWP.primaryColors = sysWP.primaryColors; + lockWP.mWallpaperDimAmount = sysWP.mWallpaperDimAmount; // Migrate the bitmap files outright; no need to copy try { @@ -3191,6 +3322,18 @@ public class WallpaperManagerService extends IWallpaperManager.Stub out.attributeInt(null, "paddingBottom", wpdData.mPadding.bottom); } + out.attributeFloat(null, "dimAmount", wallpaper.mWallpaperDimAmount); + int dimAmountsCount = wallpaper.mUidToDimAmount.size(); + out.attributeInt(null, "dimAmountsCount", dimAmountsCount); + if (dimAmountsCount > 0) { + int index = 0; + for (Map.Entry<Integer, Float> entry : wallpaper.mUidToDimAmount.entrySet()) { + out.attributeInt(null, "dimUID" + index, entry.getKey()); + out.attributeFloat(null, "dimValue" + index, entry.getValue()); + index++; + } + } + if (wallpaper.primaryColors != null) { int colorsCount = wallpaper.primaryColors.getMainColors().size(); out.attributeInt(null, "colorsCount", colorsCount); @@ -3267,6 +3410,10 @@ public class WallpaperManagerService extends IWallpaperManager.Stub return parser.getAttributeInt(null, name, defValue); } + private float getAttributeFloat(TypedXmlPullParser parser, String name, float defValue) { + return parser.getAttributeFloat(null, name, defValue); + } + /** * Sometimes it is expected the wallpaper map may not have a user's data. E.g. This could * happen during user switch. The async user switch observer may not have received @@ -3471,6 +3618,17 @@ public class WallpaperManagerService extends IWallpaperManager.Stub wpData.mPadding.top = getAttributeInt(parser, "paddingTop", 0); wpData.mPadding.right = getAttributeInt(parser, "paddingRight", 0); wpData.mPadding.bottom = getAttributeInt(parser, "paddingBottom", 0); + wallpaper.mWallpaperDimAmount = getAttributeFloat(parser, "dimAmount", 0f); + int dimAmountsCount = getAttributeInt(parser, "dimAmountsCount", 0); + if (dimAmountsCount > 0) { + ArrayMap<Integer, Float> allDimAmounts = new ArrayMap<>(dimAmountsCount); + for (int i = 0; i < dimAmountsCount; i++) { + int uid = getAttributeInt(parser, "dimUID" + i, 0); + float dimValue = getAttributeFloat(parser, "dimValue" + i, 0f); + allDimAmounts.put(uid, dimValue); + } + wallpaper.mUidToDimAmount = allDimAmounts; + } int colorsCount = getAttributeInt(parser, "colorsCount", 0); int allColorsCount = getAttributeInt(parser, "allColorsCount", 0); if (allColorsCount > 0) { @@ -3637,6 +3795,14 @@ public class WallpaperManagerService extends IWallpaperManager.Stub return false; } + @Override // Binder call + public void onShellCommand(FileDescriptor in, FileDescriptor out, + FileDescriptor err, String[] args, ShellCallback callback, + ResultReceiver resultReceiver) { + new WallpaperManagerShellCommand(WallpaperManagerService.this).exec(this, in, out, err, + args, callback, resultReceiver); + } + @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return; @@ -3664,6 +3830,13 @@ public class WallpaperManagerService extends IWallpaperManager.Stub pw.print(" mName="); pw.println(wallpaper.name); pw.print(" mAllowBackup="); pw.println(wallpaper.allowBackup); pw.print(" mWallpaperComponent="); pw.println(wallpaper.wallpaperComponent); + pw.print(" mWallpaperDimAmount="); pw.println(wallpaper.mWallpaperDimAmount); + pw.print(" isColorExtracted="); pw.println(wallpaper.mIsColorExtractedFromDim); + pw.println(" mUidToDimAmount:"); + for (Map.Entry<Integer, Float> entry : wallpaper.mUidToDimAmount.entrySet()) { + pw.print(" UID="); pw.print(entry.getKey()); + pw.print(" dimAmount="); pw.println(entry.getValue()); + } if (wallpaper.connection != null) { WallpaperConnection conn = wallpaper.connection; pw.print(" Wallpaper connection "); @@ -3695,6 +3868,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub pw.print(" mCropHint="); pw.println(wallpaper.cropHint); pw.print(" mName="); pw.println(wallpaper.name); pw.print(" mAllowBackup="); pw.println(wallpaper.allowBackup); + pw.print(" mWallpaperDimAmount="); pw.println(wallpaper.mWallpaperDimAmount); } pw.println("Fallback wallpaper state:"); pw.print(" User "); pw.print(mFallbackWallpaper.userId); diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerShellCommand.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerShellCommand.java new file mode 100644 index 000000000000..fc827b40f3a1 --- /dev/null +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerShellCommand.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wallpaper; + +import android.os.RemoteException; +import android.os.ShellCommand; +import android.util.Log; + +import java.io.PrintWriter; + +/** + * Shell Command class to run adb commands on the wallpaper service + */ +public class WallpaperManagerShellCommand extends ShellCommand { + private static final String TAG = "WallpaperManagerShellCommand"; + + private final WallpaperManagerService mService; + + public WallpaperManagerShellCommand(WallpaperManagerService service) { + mService = service; + } + + @Override + public int onCommand(String cmd) { + if (cmd == null) { + onHelp(); + return 1; + } + switch(cmd) { + case "set-dim-amount": + return setWallpaperDimAmount(); + case "get-dim-amount": + return getWallpaperDimAmount(); + case "-h": + case "help": + onHelp(); + return 0; + default: + return handleDefaultCommands(cmd); + } + } + + @Override + public void onHelp() { + final PrintWriter pw = getOutPrintWriter(); + pw.println("Wallpaper manager commands:"); + pw.println(" help"); + pw.println(" Print this help text."); + pw.println(); + pw.println(" set-dim-amount DIMMING"); + pw.println(" Sets the current dimming value to DIMMING (a number between 0 and 1)."); + pw.println(); + pw.println(" get-dim-amount"); + pw.println(" Get the current wallpaper dim amount."); + } + + /** + * Sets the wallpaper dim amount between [0f, 1f] which would be blended with the system default + * dimming. 0f doesn't add any additional dimming and 1f makes the wallpaper fully black. + */ + private int setWallpaperDimAmount() { + float dimAmount = Float.parseFloat(getNextArgRequired()); + try { + mService.setWallpaperDimAmount(dimAmount); + } catch (RemoteException e) { + Log.e(TAG, "Can't set wallpaper dim amount"); + } + getOutPrintWriter().println("Dimming the wallpaper to: " + dimAmount); + return 0; + } + + /** + * Gets the current additional dim amount set on the wallpaper. 0f means no application has + * added any dimming on top of the system default dim amount. + */ + private int getWallpaperDimAmount() { + float dimAmount = mService.getWallpaperDimAmount(); + getOutPrintWriter().println("The current wallpaper dim amount is: " + dimAmount); + return 0; + } +} diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index c2765db4ccff..76434c71d342 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -2764,9 +2764,13 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } boolean isResizeable() { + return isResizeable(/* checkPictureInPictureSupport */ true); + } + + boolean isResizeable(boolean checkPictureInPictureSupport) { return mAtmService.mForceResizableActivities || ActivityInfo.isResizeableMode(info.resizeMode) - || info.supportsPictureInPicture() + || (info.supportsPictureInPicture() && checkPictureInPictureSupport) // If the activity can be embedded, it should inherit the bounds of task fragment. || isEmbedded(); } @@ -7360,12 +7364,17 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A @VisibleForTesting void clearSizeCompatMode() { + final float lastSizeCompatScale = mSizeCompatScale; mInSizeCompatModeForBounds = false; mSizeCompatScale = 1f; mSizeCompatBounds = null; mCompatDisplayInsets = null; + if (mSizeCompatScale != lastSizeCompatScale) { + forAllWindows(WindowState::updateGlobalScale, false /* traverseTopToBottom */); + } - onRequestedOverrideConfigurationChanged(getRequestedOverrideConfiguration()); + // Clear config override in #updateCompatDisplayInsets(). + onRequestedOverrideConfigurationChanged(EMPTY); } @Override @@ -7680,10 +7689,16 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // orientation with insets applied. return; } - // Activity should be resizable if the task is. + // Not using Task#isResizeable() or ActivityRecord#isResizeable() directly because app + // compatibility testing showed that android:supportsPictureInPicture="true" alone is not + // sufficient signal for not letterboxing an app. + // TODO(214602463): Remove multi-window check since orientation and aspect ratio + // restrictions should always be applied in multi-window. final boolean isResizeable = task != null - ? task.isResizeable() || isResizeable() - : isResizeable(); + // Activity should be resizable if the task is. + ? task.isResizeable(/* checkPictureInPictureSupport */ false) + || isResizeable(/* checkPictureInPictureSupport */ false) + : isResizeable(/* checkPictureInPictureSupport */ false); if (WindowConfiguration.inMultiWindowMode(windowingMode) && isResizeable) { // Ignore orientation request for resizable apps in multi window. return; @@ -7914,6 +7929,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A final int contentH = resolvedAppBounds.height(); final int viewportW = containerAppBounds.width(); final int viewportH = containerAppBounds.height(); + final float lastSizeCompatScale = mSizeCompatScale; // Only allow to scale down. mSizeCompatScale = (contentW <= viewportW && contentH <= viewportH) ? 1f : Math.min((float) viewportW / contentW, (float) viewportH / contentH); @@ -7932,6 +7948,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } else { mSizeCompatBounds = null; } + if (mSizeCompatScale != lastSizeCompatScale) { + forAllWindows(WindowState::updateGlobalScale, false /* traverseTopToBottom */); + } // Vertically center within parent (bounds) - this is a UX choice and exclude the horizontal // decor if needed. Horizontal position is adjusted in @@ -8199,8 +8218,13 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A final float maxAspectRatio = info.getMaxAspectRatio(); final Task rootTask = getRootTask(); final float minAspectRatio = getMinAspectRatio(); + // Not using ActivityRecord#isResizeable() directly because app compatibility testing + // showed that android:supportsPictureInPicture="true" alone is not sufficient signal for + // not letterboxing an app. + // TODO(214602463): Remove multi-window check since orientation and aspect ratio + // restrictions should always be applied in multi-window. if (task == null || rootTask == null - || (inMultiWindowMode() && !shouldCreateCompatDisplayInsets() + || (inMultiWindowMode() && isResizeable(/* checkPictureInPictureSupport */ false) && !fixedOrientationLetterboxed) || (maxAspectRatio < 1 && minAspectRatio < 1 && desiredAspectRatio < 1) || isInVrUiMode(getConfiguration())) { diff --git a/services/core/java/com/android/server/wm/ActivityRecordInputSink.java b/services/core/java/com/android/server/wm/ActivityRecordInputSink.java index 4b3078ae7c00..316bf2017585 100644 --- a/services/core/java/com/android/server/wm/ActivityRecordInputSink.java +++ b/services/core/java/com/android/server/wm/ActivityRecordInputSink.java @@ -22,7 +22,6 @@ import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION; import android.app.compat.CompatChanges; import android.compat.annotation.ChangeId; -import android.compat.annotation.Overridable; import android.os.IBinder; import android.os.InputConstants; import android.os.Looper; @@ -47,7 +46,6 @@ class ActivityRecordInputSink { * Feature flag for making Activities consume all touches within their task bounds. */ @ChangeId - @Overridable static final long ENABLE_TOUCH_OPAQUE_ACTIVITIES = 194480991L; private static final String TAG = "ActivityRecordInputSink"; @@ -116,7 +114,7 @@ class ActivityRecordInputSink { private InputWindowHandle createInputWindowHandle() { InputWindowHandle inputWindowHandle = new InputWindowHandle(null, mActivityRecord.getDisplayId()); - inputWindowHandle.replaceTouchableRegionWithCrop(mActivityRecord.getSurfaceControl()); + inputWindowHandle.replaceTouchableRegionWithCrop = true; inputWindowHandle.name = mName; inputWindowHandle.ownerUid = Process.myUid(); inputWindowHandle.ownerPid = Process.myPid(); diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index ddd624d115c3..ed9dcef864c6 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -221,10 +221,10 @@ import android.util.SparseArray; import android.util.TimeUtils; import android.util.proto.ProtoOutputStream; import android.view.IRecentsAnimationRunner; -import android.view.IRemoteAnimationRunner; import android.view.RemoteAnimationAdapter; import android.view.RemoteAnimationDefinition; import android.view.WindowManager; +import android.window.BackNavigationInfo; import android.window.IWindowOrganizerController; import android.window.SplashScreenView.SplashScreenViewParcelable; import android.window.TaskSnapshot; @@ -458,7 +458,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { private final ClientLifecycleManager mLifecycleManager; @Nullable - private final BackGestureController mBackGestureController; + private final BackNavigationController mBackNavigationController; private TaskChangeNotificationController mTaskChangeNotificationController; /** The controller for all operations related to locktask. */ @@ -836,8 +836,6 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { mSystemThread = ActivityThread.currentActivityThread(); mUiContext = mSystemThread.getSystemUiContext(); mLifecycleManager = new ClientLifecycleManager(); - mBackGestureController = BackGestureController.isEnabled() ? new BackGestureController() - : null; mVisibleActivityProcessTracker = new VisibleActivityProcessTracker(this); mInternal = new LocalService(); GL_ES_VERSION = SystemProperties.getInt("ro.opengles.version", GL_ES_VERSION_UNDEFINED); @@ -845,6 +843,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { mTaskOrganizerController = mWindowOrganizerController.mTaskOrganizerController; mTaskFragmentOrganizerController = mWindowOrganizerController.mTaskFragmentOrganizerController; + mBackNavigationController = BackNavigationController.isEnabled() + ? new BackNavigationController() : null; } public void onSystemReady() { @@ -1022,6 +1022,9 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { mLockTaskController.setWindowManager(wm); mTaskSupervisor.setWindowManager(wm); mRootWindowContainer.setWindowManager(wm); + if (mBackNavigationController != null) { + mBackNavigationController.setTaskSnapshotController(wm.mTaskSnapshotController); + } } } @@ -1768,11 +1771,13 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } @Override - public void startBackPreview(IRemoteAnimationRunner runner) { - if (mBackGestureController == null) { - return; + public BackNavigationInfo startBackNavigation() { + mAmInternal.enforceCallingPermission(START_TASKS_FROM_RECENTS, + "startBackNavigation()"); + if (mBackNavigationController == null) { + return null; } - mBackGestureController.startBackPreview(); + return mBackNavigationController.startBackNavigation(getTopDisplayFocusedRootTask()); } /** diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java new file mode 100644 index 000000000000..a8779fa79675 --- /dev/null +++ b/services/core/java/com/android/server/wm/BackNavigationController.java @@ -0,0 +1,240 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm; + +import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_BACK_PREVIEW; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.WindowConfiguration; +import android.content.ComponentName; +import android.hardware.HardwareBuffer; +import android.os.RemoteCallback; +import android.os.RemoteException; +import android.os.SystemProperties; +import android.util.Slog; +import android.view.SurfaceControl; +import android.window.BackNavigationInfo; +import android.window.TaskSnapshot; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.protolog.common.ProtoLog; + +/** + * Controller to handle actions related to the back gesture on the server side. + */ +class BackNavigationController { + + private static final String TAG = "BackNavigationController"; + private static final String BACK_PREDICTABILITY_PROP = "persist.debug.back_predictability"; + + @Nullable + private TaskSnapshotController mTaskSnapshotController; + + /** + * Returns true if the back predictability feature is enabled + */ + static boolean isEnabled() { + return SystemProperties.getInt(BACK_PREDICTABILITY_PROP, 0) > 0; + } + + /** + * Set up the necessary leashes and build a {@link BackNavigationInfo} instance for an upcoming + * back gesture animation. + * + * @param task the currently focused {@link Task}. + * @return a {@link BackNavigationInfo} instance containing the required leashes and metadata + * for the animation. + */ + @Nullable + BackNavigationInfo startBackNavigation(@NonNull Task task) { + return startBackNavigation(task, null); + } + + /** + * @param tx, a transaction to be used for the attaching the animation leash. + * This is used in tests. If null, the object will be initialized with a new {@link + * android.view.SurfaceControl.Transaction} + * @see #startBackNavigation(Task) + */ + @VisibleForTesting + @Nullable + BackNavigationInfo startBackNavigation(@NonNull Task task, + @Nullable SurfaceControl.Transaction tx) { + + if (tx == null) { + tx = new SurfaceControl.Transaction(); + } + + int backType = BackNavigationInfo.TYPE_UNDEFINED; + Task prevTask = task; + ActivityRecord prev; + WindowContainer<?> removedWindowContainer; + ActivityRecord activityRecord; + SurfaceControl animationLeashParent; + WindowConfiguration taskWindowConfiguration; + SurfaceControl animLeash; + HardwareBuffer screenshotBuffer = null; + int prevTaskId; + int prevUserId; + + synchronized (task.mWmService.mGlobalLock) { + activityRecord = task.topRunningActivity(); + removedWindowContainer = activityRecord; + taskWindowConfiguration = task.getTaskInfo().configuration.windowConfiguration; + + ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "startBackNavigation task=%s, topRunningActivity=%s", + task, activityRecord); + + // IME is visible, back gesture will dismiss it, nothing to preview. + if (task.getDisplayContent().getImeContainer().isVisible()) { + return null; + } + + // Current Activity is home, there is no previous activity to display + if (activityRecord.isActivityTypeHome()) { + return null; + } + + prev = task.getActivity( + (r) -> !r.finishing && r.getTask() == task && !r.isTopRunningActivity()); + + if (prev != null) { + backType = BackNavigationInfo.TYPE_CROSS_ACTIVITY; + } else if (task.returnsToHomeRootTask()) { + prevTask = null; + removedWindowContainer = task; + backType = BackNavigationInfo.TYPE_RETURN_TO_HOME; + } else if (activityRecord.isRootOfTask()) { + // TODO(208789724): Create single source of truth for this, maybe in + // RootWindowContainer + // TODO: Also check Task.shouldUpRecreateTaskLocked() for prev logic + prevTask = task.mRootWindowContainer.getTaskBelow(task); + removedWindowContainer = task; + if (prevTask.isActivityTypeHome()) { + backType = BackNavigationInfo.TYPE_RETURN_TO_HOME; + } else { + prev = prevTask.getTopNonFinishingActivity(); + backType = BackNavigationInfo.TYPE_CROSS_TASK; + } + } + + prevTaskId = prevTask != null ? prevTask.mTaskId : 0; + prevUserId = prevTask != null ? prevTask.mUserId : 0; + + ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "Previous Activity is %s", + prev != null ? prev.mActivityComponent : null); + + //TODO(207481538) Remove once the infrastructure to support per-activity screenshot is + // implemented. For now we simply have the mBackScreenshots hash map that dumbly + // saves the screenshots. + if (needsScreenshot(backType) && prev != null && prev.mActivityComponent != null) { + screenshotBuffer = getActivitySnapshot(task, prev.mActivityComponent); + } + + // Prepare a leash to animate the current top window + animLeash = removedWindowContainer.makeAnimationLeash() + .setName("BackPreview Leash") + .setHidden(false) + .setBLASTLayer() + .build(); + removedWindowContainer.reparentSurfaceControl(tx, animLeash); + + animationLeashParent = removedWindowContainer.getAnimationLeashParent(); + } + + SurfaceControl.Builder builder = new SurfaceControl.Builder() + .setName("BackPreview Screenshot") + .setParent(animationLeashParent) + .setHidden(false) + .setBLASTLayer(); + SurfaceControl screenshotSurface = builder.build(); + + // Find a screenshot of the previous activity + + if (needsScreenshot(backType) && prevTask != null) { + if (screenshotBuffer == null) { + screenshotBuffer = getTaskSnapshot(prevTaskId, prevUserId); + } + } + tx.apply(); + + WindowContainer<?> finalRemovedWindowContainer = removedWindowContainer; + try { + activityRecord.token.linkToDeath( + () -> resetSurfaces(finalRemovedWindowContainer), 0); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to link to death", e); + resetSurfaces(removedWindowContainer); + return null; + } + + return new BackNavigationInfo(backType, + animLeash, + screenshotSurface, + screenshotBuffer, + taskWindowConfiguration, + new RemoteCallback(result -> resetSurfaces(finalRemovedWindowContainer + ))); + } + + + private HardwareBuffer getActivitySnapshot(@NonNull Task task, + ComponentName activityComponent) { + // Check if we have a screenshot of the previous activity, indexed by its + // component name. + SurfaceControl.ScreenshotHardwareBuffer backBuffer = task.mBackScreenshots + .get(activityComponent.flattenToString()); + return backBuffer != null ? backBuffer.getHardwareBuffer() : null; + + } + + private HardwareBuffer getTaskSnapshot(int taskId, int userId) { + if (mTaskSnapshotController == null) { + return null; + } + TaskSnapshot snapshot = mTaskSnapshotController.getSnapshot(taskId, + userId, true /* restoreFromDisk */, false /* isLowResolution */); + return snapshot != null ? snapshot.getHardwareBuffer() : null; + } + + private boolean needsScreenshot(int backType) { + switch (backType) { + case BackNavigationInfo.TYPE_RETURN_TO_HOME: + case BackNavigationInfo.TYPE_DIALOG_CLOSE: + return false; + } + return true; + } + + private void resetSurfaces(@NonNull WindowContainer<?> windowContainer) { + synchronized (windowContainer.mWmService.mGlobalLock) { + ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "Back: Reset surfaces"); + SurfaceControl.Transaction tx = windowContainer.getSyncTransaction(); + SurfaceControl surfaceControl = windowContainer.getSurfaceControl(); + if (surfaceControl != null) { + tx.reparent(surfaceControl, + windowContainer.getParent().getSurfaceControl()); + tx.apply(); + } + } + } + + void setTaskSnapshotController(@Nullable TaskSnapshotController taskSnapshotController) { + mTaskSnapshotController = taskSnapshotController; + } +} diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index c65ca0847563..e449dde15c67 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -122,6 +122,7 @@ import static com.android.server.wm.DisplayContentProto.INPUT_METHOD_INPUT_TARGE import static com.android.server.wm.DisplayContentProto.INPUT_METHOD_TARGET; import static com.android.server.wm.DisplayContentProto.INSETS_SOURCE_PROVIDERS; import static com.android.server.wm.DisplayContentProto.IS_SLEEPING; +import static com.android.server.wm.DisplayContentProto.KEEP_CLEAR_AREAS; import static com.android.server.wm.DisplayContentProto.OPENING_APPS; import static com.android.server.wm.DisplayContentProto.RESUMED_ACTIVITY; import static com.android.server.wm.DisplayContentProto.ROOT_DISPLAY_AREA; @@ -169,6 +170,7 @@ import android.content.res.Configuration; import android.graphics.Bitmap; import android.graphics.ColorSpace; import android.graphics.Insets; +import android.graphics.Matrix; import android.graphics.Point; import android.graphics.Rect; import android.graphics.Region; @@ -838,7 +840,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } w.mSurfacePlacementNeeded = true; w.mLayoutNeeded = false; - w.prelayout(); final boolean firstLayout = !w.isLaidOut(); getDisplayPolicy().layoutWindowLw(w, null, mDisplayFrames); w.mLayoutSeq = mLayoutSeq; @@ -881,7 +882,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } w.mSurfacePlacementNeeded = true; w.mLayoutNeeded = false; - w.prelayout(); getDisplayPolicy().layoutWindowLw(w, w.getParentWindow(), mDisplayFrames); w.mLayoutSeq = mLayoutSeq; if (DEBUG_LAYOUT) Slog.v(TAG, " LAYOUT: mFrame=" + w.getFrame() @@ -2001,6 +2001,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } void configureDisplayPolicy() { + mRootWindowContainer.updateDisplayImePolicyCache(); mDisplayPolicy.updateConfigurationAndScreenSizeDependentBehaviors(); mDisplayRotation.configure(mBaseDisplayWidth, mBaseDisplayHeight); } @@ -2718,6 +2719,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp void onDisplayChanged(DisplayContent dc) { super.onDisplayChanged(dc); updateSystemGestureExclusionLimit(); + updateKeepClearAreas(); } void updateSystemGestureExclusionLimit() { @@ -3327,6 +3329,9 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } } proto.write(IME_POLICY, getImePolicy()); + for (Rect r : getKeepClearAreas()) { + r.dumpDebug(proto, KEEP_CLEAR_AREAS); + } proto.end(token); } @@ -3386,6 +3391,13 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp pw.println(mSystemGestureExclusion); } + final List<Rect> keepClearAreas = getKeepClearAreas(); + if (!keepClearAreas.isEmpty()) { + pw.println(); + pw.print(" keepClearAreas="); + pw.println(keepClearAreas); + } + pw.println(); pw.println(prefix + "Display areas in top down Z order:"); dumpChildDisplayArea(pw, subPrefix, dumpAll); @@ -3613,6 +3625,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } adjustForImeIfNeeded(); + updateKeepClearAreas(); // We may need to schedule some toast windows to be removed. The toasts for an app that // does not have input focus are removed within a timeout to prevent apps to redress @@ -3939,6 +3952,9 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } } + // IMPORTANT: When introducing new dependencies in this method, make sure that + // changes to those result in RootWindowContainer.updateDisplayImePolicyCache() + // being called. @DisplayImePolicy int getImePolicy() { if (!isTrusted()) { return DISPLAY_IME_POLICY_FALLBACK_DISPLAY; @@ -3987,11 +4003,12 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp if (target == mImeLayeringTarget) { return; } - // Prepare the IME screenshot for the last IME target when its task is applying app - // transition. This is for the better IME transition to keep IME visibility when - // transitioning to the next task. + // If the IME target is the input target, before it changes, prepare the IME screenshot + // for the last IME target when its task is applying app transition. This is for the + // better IME transition to keep IME visibility when transitioning to the next task. if (mImeLayeringTarget != null && mImeLayeringTarget.isAnimating(PARENTS | TRANSITION, - ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS)) { + ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS) + && mImeLayeringTarget == mImeInputTarget) { attachAndShowImeScreenshotOnTarget(); } @@ -5477,19 +5494,28 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mSystemGestureExclusionListeners.unregister(listener); } + void updateKeepClearAreas() { + mWmService.mDisplayNotificationController.dispatchKeepClearAreasChanged( + this, getKeepClearAreas()); + } + /** - * @see IWindowManager#setForwardedInsets + * Returns all keep-clear areas from visible windows on this display. */ - public void setForwardedInsets(Insets insets) { - if (insets == null) { - insets = Insets.NONE; - } - if (mDisplayPolicy.getForwardedInsets().equals(insets)) { - return; - } - mDisplayPolicy.setForwardedInsets(insets); - setLayoutNeeded(); - mWmService.mWindowPlacerLocked.requestTraversal(); + ArrayList<Rect> getKeepClearAreas() { + final ArrayList<Rect> keepClearAreas = new ArrayList<Rect>(); + final Matrix tmpMatrix = new Matrix(); + final float[] tmpFloat9 = new float[9]; + forAllWindows(w -> { + if (w.isVisible() && !w.inPinnedWindowingMode()) { + keepClearAreas.addAll(w.getKeepClearAreas(tmpMatrix, tmpFloat9)); + } + + // We stop traversing when we reach the base of a fullscreen app. + return w.getWindowType() == TYPE_BASE_APPLICATION + && w.getWindowingMode() == WINDOWING_MODE_FULLSCREEN; + }, true); + return keepClearAreas; } protected MetricsLogger getMetricsLogger() { diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index 0745b3b6d971..18885541b1fe 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -305,10 +305,10 @@ public class DisplayPolicy { private WindowState mRoundedCornerWindow; /** - * Windows to determine the color of status bar. See {@link #mNavBarColorWindowCandidate} for - * the conditions of being candidate window. + * A collection of {@link AppearanceRegion} to indicate that which region of status bar applies + * which appearance. */ - private final ArrayList<WindowState> mStatusBarColorWindows = new ArrayList<>(); + private final ArrayList<AppearanceRegion> mStatusBarAppearanceRegionList = new ArrayList<>(); /** * Windows to determine opacity and background of translucent status bar. The window needs to be @@ -323,7 +323,7 @@ public class DisplayPolicy { private InsetsVisibilities mRequestedVisibilities = new InsetsVisibilities(); private AppearanceRegion[] mLastStatusBarAppearanceRegions; - /** The union of checked bounds while fetching {@link #mStatusBarColorWindows}. */ + /** The union of checked bounds while building {@link #mStatusBarAppearanceRegionList}. */ private final Rect mStatusBarColorCheckedBounds = new Rect(); /** The union of checked bounds while fetching {@link #mStatusBarBackgroundWindows}. */ @@ -336,6 +336,7 @@ public class DisplayPolicy { private long mPendingPanicGestureUptime; private static final Rect sTmpRect = new Rect(); + private static final Rect sTmpRect2 = new Rect(); private static final Rect sTmpLastParentFrame = new Rect(); private static final Rect sTmpDisplayCutoutSafe = new Rect(); private static final Rect sTmpDisplayFrame = new Rect(); @@ -359,16 +360,6 @@ public class DisplayPolicy { private int mDisplayCutoutTouchableRegionSize; - /** - * The area covered by system windows which belong to another display. Forwarded insets is set - * in case this is a virtual display, this is displayed on another display that has insets, and - * the bounds of this display is overlapping with the insets of the host display (e.g. IME is - * displayed on the host display, and it covers a part of this virtual display.) - * The forwarded insets is used to compute display frames of this virtual display, which will - * be then used to layout windows in the virtual display. - */ - @NonNull private Insets mForwardedInsets = Insets.NONE; - private RefreshRatePolicy mRefreshRatePolicy; /** @@ -1442,33 +1433,6 @@ public class DisplayPolicy { return mForceShowSystemBars; } - // TODO: Should probably be moved into DisplayFrames. - /** - * Return the layout hints for a newly added window. These values are computed on the - * most recent layout, so they are not guaranteed to be correct. - * - * @param attrs The LayoutParams of the window. - * @param windowToken The token of the window. - * @param outInsetsState The insets state of this display from the client's perspective. - * @param localClient Whether the client is from the our process. - * @return Whether to always consume the system bars. - * See {@link #areSystemBarsForcedShownLw()}. - */ - boolean getLayoutHint(LayoutParams attrs, WindowToken windowToken, InsetsState outInsetsState, - boolean localClient) { - final InsetsState state = - mDisplayContent.getInsetsPolicy().getInsetsForWindowMetrics(attrs); - final boolean hasCompatScale = WindowState.hasCompatScale(attrs, windowToken); - outInsetsState.set(state, hasCompatScale || localClient); - if (hasCompatScale) { - final float compatScale = windowToken != null - ? windowToken.getSizeCompatScale() - : mDisplayContent.mCompatibleScreenScale; - outInsetsState.scale(1f / compatScale); - } - return mForceShowSystemBars; - } - /** * Computes the frames of display (its logical size, rotation and cutout should already be set) * used to layout window. This method only changes the given display frames, insets state and @@ -1563,7 +1527,7 @@ public class DisplayPolicy { mTopFullscreenOpaqueWindowState = null; mNavBarColorWindowCandidate = null; mNavBarBackgroundWindow = null; - mStatusBarColorWindows.clear(); + mStatusBarAppearanceRegionList.clear(); mStatusBarBackgroundWindows.clear(); mStatusBarColorCheckedBounds.setEmpty(); mStatusBarBackgroundCheckedBounds.setEmpty(); @@ -1643,7 +1607,9 @@ public class DisplayPolicy { mStatusBarBackgroundWindows.add(win); mStatusBarBackgroundCheckedBounds.union(sTmpRect); if (!mStatusBarColorCheckedBounds.contains(sTmpRect)) { - mStatusBarColorWindows.add(win); + mStatusBarAppearanceRegionList.add(new AppearanceRegion( + win.mAttrs.insetsFlags.appearance & APPEARANCE_LIGHT_STATUS_BARS, + new Rect(win.getFrame()))); mStatusBarColorCheckedBounds.union(sTmpRect); } } @@ -1663,13 +1629,10 @@ public class DisplayPolicy { } } } else if (win.isDimming()) { - // For dimming window whose host bounds is overlapping with system bars, it can be - // used to determine colors but not opacity of system bars. - if (mStatusBar != null - && sTmpRect.setIntersect(win.getBounds(), mStatusBar.getFrame()) - && !mStatusBarColorCheckedBounds.contains(sTmpRect)) { - mStatusBarColorWindows.add(win); - mStatusBarColorCheckedBounds.union(sTmpRect); + if (mStatusBar != null) { + addStatusBarAppearanceRegionsForDimmingWindow( + win.mAttrs.insetsFlags.appearance & APPEARANCE_LIGHT_STATUS_BARS, + mStatusBar.getFrame(), win.getBounds(), win.getFrame()); } if (isOverlappingWithNavBar && mNavBarColorWindowCandidate == null) { mNavBarColorWindowCandidate = win; @@ -1677,6 +1640,48 @@ public class DisplayPolicy { } } + private void addStatusBarAppearanceRegionsForDimmingWindow(int appearance, Rect statusBarFrame, + Rect winBounds, Rect winFrame) { + if (!sTmpRect.setIntersect(winBounds, statusBarFrame)) { + return; + } + if (mStatusBarColorCheckedBounds.contains(sTmpRect)) { + return; + } + if (appearance == 0 || !sTmpRect2.setIntersect(winFrame, statusBarFrame)) { + mStatusBarAppearanceRegionList.add(new AppearanceRegion(0, new Rect(winBounds))); + mStatusBarColorCheckedBounds.union(sTmpRect); + return; + } + // A dimming window can divide status bar into different appearance regions (up to 3). + // +---------+-------------+---------+ + // |/////////| |/////////| <-- Status Bar + // +---------+-------------+---------+ + // |/////////| |/////////| + // |/////////| |/////////| + // |/////////| |/////////| + // |/////////| |/////////| + // |/////////| |/////////| + // +---------+-------------+---------+ + // ^ ^ ^ + // dim layer window dim layer + mStatusBarAppearanceRegionList.add(new AppearanceRegion(appearance, new Rect(winFrame))); + if (!sTmpRect.equals(sTmpRect2)) { + if (sTmpRect.height() == sTmpRect2.height()) { + if (sTmpRect.left != sTmpRect2.left) { + mStatusBarAppearanceRegionList.add(new AppearanceRegion(0, new Rect( + winBounds.left, winBounds.top, sTmpRect2.left, winBounds.bottom))); + } + if (sTmpRect.right != sTmpRect2.right) { + mStatusBarAppearanceRegionList.add(new AppearanceRegion(0, new Rect( + sTmpRect2.right, winBounds.top, winBounds.right, winBounds.bottom))); + } + } + // We don't have vertical status bar yet, so we don't handle the other orientation. + } + mStatusBarColorCheckedBounds.union(sTmpRect); + } + /** * Called following layout of all windows and after policy has been applied * to each window. If in this function you do @@ -2149,18 +2154,6 @@ public class DisplayPolicy { } } - /** - * @see IWindowManager#setForwardedInsets - */ - public void setForwardedInsets(@NonNull Insets forwardedInsets) { - mForwardedInsets = forwardedInsets; - } - - @NonNull - public Insets getForwardedInsets() { - return mForwardedInsets; - } - @NavigationBarPosition int navigationBarPosition(int displayRotation) { if (mNavigationBar != null) { @@ -2319,14 +2312,9 @@ public class DisplayPolicy { final String focusedApp = win.mAttrs.packageName; final boolean isFullscreen = !win.getRequestedVisibility(ITYPE_STATUS_BAR) || !win.getRequestedVisibility(ITYPE_NAVIGATION_BAR); - final AppearanceRegion[] appearanceRegions = - new AppearanceRegion[mStatusBarColorWindows.size()]; - for (int i = mStatusBarColorWindows.size() - 1; i >= 0; i--) { - final WindowState windowState = mStatusBarColorWindows.get(i); - appearanceRegions[i] = new AppearanceRegion( - getStatusBarAppearance(windowState, windowState), - new Rect(windowState.getFrame())); - } + final AppearanceRegion[] statusBarAppearanceRegions = + new AppearanceRegion[mStatusBarAppearanceRegionList.size()]; + mStatusBarAppearanceRegionList.toArray(statusBarAppearanceRegions); if (mLastDisableFlags != disableFlags) { mLastDisableFlags = disableFlags; final String cause = win.toString(); @@ -2338,7 +2326,7 @@ public class DisplayPolicy { && mRequestedVisibilities.equals(win.getRequestedVisibilities()) && Objects.equals(mFocusedApp, focusedApp) && mLastFocusIsFullscreen == isFullscreen - && Arrays.equals(mLastStatusBarAppearanceRegions, appearanceRegions)) { + && Arrays.equals(mLastStatusBarAppearanceRegions, statusBarAppearanceRegions)) { return; } if (mDisplayContent.isDefaultDisplay && mLastFocusIsFullscreen != isFullscreen @@ -2353,20 +2341,12 @@ public class DisplayPolicy { mRequestedVisibilities = requestedVisibilities; mFocusedApp = focusedApp; mLastFocusIsFullscreen = isFullscreen; - mLastStatusBarAppearanceRegions = appearanceRegions; + mLastStatusBarAppearanceRegions = statusBarAppearanceRegions; callStatusBarSafely(statusBar -> statusBar.onSystemBarAttributesChanged(displayId, - appearance, appearanceRegions, isNavbarColorManagedByIme, behavior, + appearance, statusBarAppearanceRegions, isNavbarColorManagedByIme, behavior, requestedVisibilities, focusedApp)); } - private int getStatusBarAppearance(WindowState opaque, WindowState opaqueOrDimming) { - final boolean onKeyguard = isKeyguardShowing() && !isKeyguardOccluded(); - final WindowState colorWin = onKeyguard ? mNotificationShade : opaqueOrDimming; - return isLightBarAllowed(colorWin, Type.statusBars()) && (colorWin == opaque || onKeyguard) - ? (colorWin.mAttrs.insetsFlags.appearance & APPEARANCE_LIGHT_STATUS_BARS) - : 0; - } - private void callStatusBarSafely(Consumer<StatusBarManagerInternal> consumer) { mHandler.post(() -> { StatusBarManagerInternal statusBar = getStatusBarManagerInternal(); @@ -2760,11 +2740,10 @@ public class DisplayPolicy { pw.print(prefix); pw.print("mNavBarBackgroundWindow="); pw.println(mNavBarBackgroundWindow); } - if (!mStatusBarColorWindows.isEmpty()) { - pw.print(prefix); pw.println("mStatusBarColorWindows="); - for (int i = mStatusBarColorWindows.size() - 1; i >= 0; i--) { - final WindowState win = mStatusBarColorWindows.get(i); - pw.print(prefixInner); pw.println(win); + if (mLastStatusBarAppearanceRegions != null) { + pw.print(prefix); pw.println("mLastStatusBarAppearanceRegions="); + for (int i = mLastStatusBarAppearanceRegions.length - 1; i >= 0; i--) { + pw.print(prefixInner); pw.println(mLastStatusBarAppearanceRegions[i]); } } if (!mStatusBarBackgroundWindows.isEmpty()) { diff --git a/services/core/java/com/android/server/wm/DisplayWindowListenerController.java b/services/core/java/com/android/server/wm/DisplayWindowListenerController.java index 4141090f7fa0..276dbe9c844a 100644 --- a/services/core/java/com/android/server/wm/DisplayWindowListenerController.java +++ b/services/core/java/com/android/server/wm/DisplayWindowListenerController.java @@ -17,11 +17,14 @@ package com.android.server.wm; import android.content.res.Configuration; +import android.graphics.Rect; import android.os.RemoteCallbackList; import android.os.RemoteException; import android.util.IntArray; import android.view.IDisplayWindowListener; +import java.util.List; + /** * Manages dispatch of relevant hierarchy changes to interested listeners. Listeners are assumed * to be remote. @@ -116,4 +119,16 @@ class DisplayWindowListenerController { } mDisplayListeners.finishBroadcast(); } + + void dispatchKeepClearAreasChanged(DisplayContent display, List<Rect> keepClearAreas) { + int count = mDisplayListeners.beginBroadcast(); + for (int i = 0; i < count; ++i) { + try { + mDisplayListeners.getBroadcastItem(i).onKeepClearAreasChanged( + display.mDisplayId, keepClearAreas); + } catch (RemoteException e) { + } + } + mDisplayListeners.finishBroadcast(); + } } diff --git a/services/core/java/com/android/server/wm/EmbeddedWindowController.java b/services/core/java/com/android/server/wm/EmbeddedWindowController.java index fc317a1212d5..0e2d84779602 100644 --- a/services/core/java/com/android/server/wm/EmbeddedWindowController.java +++ b/services/core/java/com/android/server/wm/EmbeddedWindowController.java @@ -93,6 +93,18 @@ class EmbeddedWindowController { return embeddedWindow != null ? embeddedWindow.mHostWindowState : null; } + boolean isOverlay(IBinder inputToken) { + EmbeddedWindow embeddedWindow = mWindows.get(inputToken); + return embeddedWindow != null ? embeddedWindow.getIsOverlay() : false; + } + + void setIsOverlay(IBinder inputToken) { + EmbeddedWindow embeddedWindow = mWindows.get(inputToken); + if (embeddedWindow != null) { + embeddedWindow.setIsOverlay(); + } + } + void remove(IWindow client) { for (int i = mWindows.size() - 1; i >= 0; i--) { if (mWindows.valueAt(i).mClient.asBinder() == client.asBinder()) { @@ -138,6 +150,12 @@ class EmbeddedWindowController { public Session mSession; InputChannel mInputChannel; final int mWindowType; + // Track whether the EmbeddedWindow is a system hosted overlay via + // {@link OverlayHost}. In the case of client hosted overlays, the client + // view hierarchy will take care of invoking requestEmbeddedWindowFocus + // but for system hosted overlays we have to do this via tapOutsideDetection + // and this variable is mostly used for tracking that. + boolean mIsOverlay = false; /** * @param session calling session to check ownership of the window @@ -216,5 +234,39 @@ class EmbeddedWindowController { public int getPid() { return mOwnerPid; } + + void setIsOverlay() { + mIsOverlay = true; + } + boolean getIsOverlay() { + return mIsOverlay; + } + + /** + * System hosted overlays need the WM to invoke grantEmbeddedWindowFocus and + * so we need to participate inside handlePointerDownOutsideFocus logic + * however client hosted overlays will rely on the hosting view hierarchy + * to grant and revoke focus, and so the server side logic is not needed. + */ + @Override + public boolean receiveFocusFromTapOutside() { + return mIsOverlay; + } + + private void handleTap(boolean grantFocus) { + if (mInputChannel != null) { + mWmService.grantEmbeddedWindowFocus(mSession, mInputChannel.getToken(), grantFocus); + } + } + + @Override + public void handleTapOutsideFocusOutsideSelf() { + handleTap(false); + } + + @Override + public void handleTapOutsideFocusInsideSelf() { + handleTap(true); + } } } diff --git a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java index 963f3265757d..8d3e07116693 100644 --- a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java +++ b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java @@ -97,7 +97,7 @@ class EnsureActivitiesVisibleHelper { // activities are actually behind other fullscreen activities, but still required // to be visible (such as performing Recents animation). final boolean resumeTopActivity = mTop != null && !mTop.mLaunchTaskBehind - && mTaskFragment.isTopActivityFocusable() + && mTaskFragment.canBeResumed(starting) && (starting == null || !starting.isDescendantOf(mTaskFragment)); ArrayList<TaskFragment> adjacentTaskFragments = null; diff --git a/services/core/java/com/android/server/wm/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java index e02e7c5ab15d..f91969b2c558 100644 --- a/services/core/java/com/android/server/wm/InputManagerCallback.java +++ b/services/core/java/com/android/server/wm/InputManagerCallback.java @@ -24,6 +24,7 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import static com.android.server.wm.WindowManagerService.H.ON_POINTER_DOWN_OUTSIDE_FOCUS; import android.annotation.NonNull; +import android.graphics.PointF; import android.os.Debug; import android.os.IBinder; import android.util.Slog; @@ -219,6 +220,11 @@ final class InputManagerCallback implements InputManagerService.WindowManagerCal } @Override + public PointF getCursorPosition() { + return mService.getLatestMousePosition(); + } + + @Override public void onPointerDownOutsideFocus(IBinder touchedToken) { mService.mH.obtainMessage(ON_POINTER_DOWN_OUTSIDE_FOCUS, touchedToken).sendToTarget(); } diff --git a/services/core/java/com/android/server/wm/InputTarget.java b/services/core/java/com/android/server/wm/InputTarget.java index c7d328a2b18a..5166b8adcecc 100644 --- a/services/core/java/com/android/server/wm/InputTarget.java +++ b/services/core/java/com/android/server/wm/InputTarget.java @@ -36,5 +36,16 @@ interface InputTarget { /* Owning pid of the target. */ int getPid(); + + /** + * Indicates whether a target should receive focus from server side + * tap outside focus detection. For example, this is false in the case of + * EmbeddedWindows in a client view hierarchy, where the client will do internal + * tap detection and invoke grantEmbeddedWindowFocus itself + */ + boolean receiveFocusFromTapOutside(); + + void handleTapOutsideFocusInsideSelf(); + void handleTapOutsideFocusOutsideSelf(); } diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java index 1a1101e45f45..a1468cc60682 100644 --- a/services/core/java/com/android/server/wm/InsetsStateController.java +++ b/services/core/java/com/android/server/wm/InsetsStateController.java @@ -366,6 +366,7 @@ class InsetsStateController { if (changed) { notifyInsetsChanged(); mDisplayContent.updateSystemGestureExclusion(); + mDisplayContent.updateKeepClearAreas(); mDisplayContent.getDisplayPolicy().updateSystemBarAttributes(); } } diff --git a/services/core/java/com/android/server/wm/OverlayHost.java b/services/core/java/com/android/server/wm/OverlayHost.java index 14f8983571c8..724e1247b100 100644 --- a/services/core/java/com/android/server/wm/OverlayHost.java +++ b/services/core/java/com/android/server/wm/OverlayHost.java @@ -74,6 +74,8 @@ class OverlayHost { requireOverlaySurfaceControl(); mOverlays.add(p); + mWmService.mEmbeddedWindowController.setIsOverlay(p.getInputToken()); + SurfaceControl.Transaction t = mWmService.mTransactionFactory.get(); t.reparent(p.getSurfaceControl(), mSurfaceControl) .show(p.getSurfaceControl()); diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 5a420caa176c..d031bec5443f 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -165,6 +165,7 @@ import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.Set; @@ -986,6 +987,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> forAllDisplays(dc -> { dc.getInputMonitor().updateInputWindowsLw(true /*force*/); dc.updateSystemGestureExclusion(); + dc.updateKeepClearAreas(); dc.updateTouchExcludeRegion(); }); @@ -2530,9 +2532,16 @@ class RootWindowContainer extends WindowContainer<DisplayContent> // Drop any cached DisplayInfos associated with this display id - the values are now // out of date given this display changed event. mWmService.mPossibleDisplayInfoMapper.removePossibleDisplayInfos(displayId); + updateDisplayImePolicyCache(); } } + void updateDisplayImePolicyCache() { + ArrayMap<Integer, Integer> displayImePolicyMap = new ArrayMap<>(); + forAllDisplays(dc -> displayImePolicyMap.put(dc.getDisplayId(), dc.getImePolicy())); + mWmService.mDisplayImePolicyCache = Collections.unmodifiableMap(displayImePolicyMap); + } + /** Update lists of UIDs that are present on displays and have access to them. */ void updateUIDsPresentOnDisplay() { mDisplayAccessUIDs.clear(); @@ -3665,7 +3674,8 @@ class RootWindowContainer extends WindowContainer<DisplayContent> try { if (mTaskSupervisor.realStartActivityLocked(r, mApp, - mTop == r && r.isFocusable() /* andResume */, true /* checkConfig */)) { + mTop == r && r.getTask().canBeResumed(r) /* andResume */, + true /* checkConfig */)) { mHasActivityStarted = true; } } catch (RemoteException e) { diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java index 005544b961c3..7acc0c52cc4c 100644 --- a/services/core/java/com/android/server/wm/Session.java +++ b/services/core/java/com/android/server/wm/Session.java @@ -73,6 +73,7 @@ import android.view.SurfaceControl; import android.view.SurfaceSession; import android.view.WindowManager; import android.window.ClientWindowFrames; +import android.window.IOnBackInvokedCallback; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.logging.MetricsLoggerWrapper; @@ -490,6 +491,16 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { } } + @Override + public void reportKeepClearAreasChanged(IWindow window, List<Rect> keepClearAreas) { + final long ident = Binder.clearCallingIdentity(); + try { + mService.reportKeepClearAreasChanged(this, window, keepClearAreas); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + private void actionOnWallpaper(IBinder window, BiConsumer<WallpaperController, WindowState> action) { final WindowState windowState = mService.windowForClientLocked(this, window, true); @@ -863,4 +874,10 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { Binder.restoreCallingIdentity(origId); } } + + @Override + public void setOnBackInvokedCallback(IWindow iWindow, + IOnBackInvokedCallback iOnBackInvokedCallback) throws RemoteException { + // TODO: Set the callback to the WindowState of the window. + } } diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 87ef29160d71..2331dc4dff52 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -2707,10 +2707,14 @@ class Task extends TaskFragment { } boolean isResizeable() { + return isResizeable(/* checkPictureInPictureSupport */ true); + } + + boolean isResizeable(boolean checkPictureInPictureSupport) { final boolean forceResizable = mAtmService.mForceResizableActivities && getActivityType() == ACTIVITY_TYPE_STANDARD; return forceResizable || ActivityInfo.isResizeableMode(mResizeMode) - || mSupportsPictureInPicture; + || (mSupportsPictureInPicture && checkPictureInPictureSupport); } /** diff --git a/services/core/java/com/android/server/wm/TaskFpsCallbackController.java b/services/core/java/com/android/server/wm/TaskFpsCallbackController.java new file mode 100644 index 000000000000..d9dc9aa9e5e2 --- /dev/null +++ b/services/core/java/com/android/server/wm/TaskFpsCallbackController.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm; + +import android.content.Context; +import android.os.IBinder; +import android.os.RemoteException; +import android.window.IOnFpsCallbackListener; + +import java.util.HashMap; + +final class TaskFpsCallbackController { + + private final Context mContext; + private final HashMap<IOnFpsCallbackListener, Long> mTaskFpsCallbackListeners; + private final HashMap<IOnFpsCallbackListener, IBinder.DeathRecipient> mDeathRecipients; + + TaskFpsCallbackController(Context context) { + mContext = context; + mTaskFpsCallbackListeners = new HashMap<>(); + mDeathRecipients = new HashMap<>(); + } + + void registerCallback(int taskId, IOnFpsCallbackListener listener) { + if (mTaskFpsCallbackListeners.containsKey(listener)) { + return; + } + + final long nativeListener = nativeRegister(listener, taskId); + mTaskFpsCallbackListeners.put(listener, nativeListener); + + final IBinder.DeathRecipient deathRecipient = () -> unregisterCallback(listener); + try { + listener.asBinder().linkToDeath(deathRecipient, 0); + mDeathRecipients.put(listener, deathRecipient); + } catch (RemoteException e) { + // ignore + } + } + + void unregisterCallback(IOnFpsCallbackListener listener) { + if (!mTaskFpsCallbackListeners.containsKey(listener)) { + return; + } + + listener.asBinder().unlinkToDeath(mDeathRecipients.get(listener), 0); + mDeathRecipients.remove(listener); + + nativeUnregister(mTaskFpsCallbackListeners.get(listener)); + mTaskFpsCallbackListeners.remove(listener); + } + + private static native long nativeRegister(IOnFpsCallbackListener listener, int taskId); + private static native void nativeUnregister(long ptr); +} diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index b681a96d2c0f..177d2e61d5f0 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -24,8 +24,6 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; -import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; -import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.content.pm.ActivityInfo.FLAG_RESUME_WHILE_PAUSING; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; @@ -39,6 +37,7 @@ import static android.view.WindowManager.TRANSIT_FLAG_OPEN_BEHIND; import static android.view.WindowManager.TRANSIT_NONE; import static android.view.WindowManager.TRANSIT_OPEN; +import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_BACK_PREVIEW; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES; import static com.android.server.wm.ActivityRecord.State.PAUSED; import static com.android.server.wm.ActivityRecord.State.PAUSING; @@ -100,6 +99,7 @@ import com.android.internal.util.function.pooled.PooledPredicate; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Objects; import java.util.function.Consumer; @@ -254,6 +254,10 @@ class TaskFragment extends WindowContainer<WindowContainer> { private final Rect mTmpStableBounds = new Rect(); private final Rect mTmpNonDecorBounds = new Rect(); + //TODO(b/207481538) Remove once the infrastructure to support per-activity screenshot is + // implemented + HashMap<String, SurfaceControl.ScreenshotHardwareBuffer> mBackScreenshots = new HashMap<>(); + private final EnsureActivitiesVisibleHelper mEnsureActivitiesVisibleHelper = new EnsureActivitiesVisibleHelper(this); private final EnsureVisibleActivitiesConfigHelper mEnsureVisibleActivitiesConfigHelper = @@ -790,13 +794,8 @@ class TaskFragment extends WindowContainer<WindowContainer> { return TASK_FRAGMENT_VISIBILITY_VISIBLE; } - boolean gotRootSplitScreenFragment = false; - boolean gotOpaqueSplitScreenPrimary = false; - boolean gotOpaqueSplitScreenSecondary = false; boolean gotTranslucentFullscreen = false; boolean gotTranslucentAdjacent = false; - boolean gotTranslucentSplitScreenPrimary = false; - boolean gotTranslucentSplitScreenSecondary = false; boolean shouldBeVisible = true; // This TaskFragment is only considered visible if all its parent TaskFragments are @@ -815,8 +814,6 @@ class TaskFragment extends WindowContainer<WindowContainer> { } final List<TaskFragment> adjacentTaskFragments = new ArrayList<>(); - final int windowingMode = getWindowingMode(); - final boolean isAssistantType = isActivityTypeAssistant(); for (int i = parent.getChildCount() - 1; i >= 0; --i) { final WindowContainer other = parent.getChildAt(i); if (other == null) continue; @@ -864,37 +861,6 @@ class TaskFragment extends WindowContainer<WindowContainer> { } // Multi-window TaskFragment that matches parent bounds would occlude other children return TASK_FRAGMENT_VISIBILITY_INVISIBLE; - } else if (otherWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY - && !gotOpaqueSplitScreenPrimary) { - gotRootSplitScreenFragment = true; - gotTranslucentSplitScreenPrimary = isTranslucent(other, starting); - gotOpaqueSplitScreenPrimary = !gotTranslucentSplitScreenPrimary; - if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY - && gotOpaqueSplitScreenPrimary) { - // Can't be visible behind another opaque TaskFragment in split-screen-primary. - return TASK_FRAGMENT_VISIBILITY_INVISIBLE; - } - } else if (otherWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY - && !gotOpaqueSplitScreenSecondary) { - gotRootSplitScreenFragment = true; - gotTranslucentSplitScreenSecondary = isTranslucent(other, starting); - gotOpaqueSplitScreenSecondary = !gotTranslucentSplitScreenSecondary; - if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY - && gotOpaqueSplitScreenSecondary) { - // Can't be visible behind another opaque TaskFragment in split-screen-secondary - return TASK_FRAGMENT_VISIBILITY_INVISIBLE; - } - } - if (gotOpaqueSplitScreenPrimary && gotOpaqueSplitScreenSecondary) { - // Can not be visible if we are in split-screen windowing mode and both halves of - // the screen are opaque. - return TASK_FRAGMENT_VISIBILITY_INVISIBLE; - } - if (isAssistantType && gotRootSplitScreenFragment) { - // Assistant TaskFragment can't be visible behind split-screen. In addition to - // this not making sense, it also works around an issue here we boost the z-order - // of the assistant window surfaces in window manager whenever it is visible. - return TASK_FRAGMENT_VISIBILITY_INVISIBLE; } final TaskFragment otherTaskFrag = other.asTaskFragment(); @@ -920,34 +886,6 @@ class TaskFragment extends WindowContainer<WindowContainer> { return TASK_FRAGMENT_VISIBILITY_INVISIBLE; } - // Handle cases when there can be a translucent split-screen TaskFragment on top. - switch (windowingMode) { - case WINDOWING_MODE_FULLSCREEN: - if (gotTranslucentSplitScreenPrimary || gotTranslucentSplitScreenSecondary) { - // At least one of the split-screen TaskFragment that covers this one is - // translucent. - // When in split mode, home will be reparented to the secondary split while - // leaving TaskFragments not supporting split below. Due to - // TaskDisplayArea#assignRootTaskOrdering always adjusts home surface layer to - // the bottom, this makes sure TaskFragments not in split roots won't occlude - // home task unexpectedly. - return TASK_FRAGMENT_VISIBILITY_INVISIBLE; - } - break; - case WINDOWING_MODE_SPLIT_SCREEN_PRIMARY: - if (gotTranslucentSplitScreenPrimary) { - // Covered by translucent primary split-screen on top. - return TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT; - } - break; - case WINDOWING_MODE_SPLIT_SCREEN_SECONDARY: - if (gotTranslucentSplitScreenSecondary) { - // Covered by translucent secondary split-screen on top. - return TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT; - } - break; - } - // Lastly - check if there is a translucent fullscreen TaskFragment on top. return gotTranslucentFullscreen ? TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT @@ -1683,6 +1621,7 @@ class TaskFragment extends WindowContainer<WindowContainer> { @Override void addChild(WindowContainer child, int index) { + ActivityRecord r = topRunningActivity(); mClearedTaskForReuse = false; boolean isAddingActivity = child.asActivityRecord() != null; @@ -1697,6 +1636,18 @@ class TaskFragment extends WindowContainer<WindowContainer> { super.addChild(child, index); if (isAddingActivity && task != null) { + + // TODO(b/207481538): temporary per-activity screenshoting + if (r != null && BackNavigationController.isEnabled()) { + ProtoLog.v(WM_DEBUG_BACK_PREVIEW, "Screenshotting Activity %s", + r.mActivityComponent.flattenToString()); + Rect outBounds = r.getBounds(); + SurfaceControl.ScreenshotHardwareBuffer backBuffer = SurfaceControl.captureLayers( + r.mSurfaceControl, + new Rect(0, 0, outBounds.width(), outBounds.height()), + 1f); + mBackScreenshots.put(r.mActivityComponent.flattenToString(), backBuffer); + } child.asActivityRecord().inHistory = true; task.onDescendantActivityAdded(taskHadActivity, activityType, child.asActivityRecord()); } @@ -2290,6 +2241,14 @@ class TaskFragment extends WindowContainer<WindowContainer> { void removeChild(WindowContainer child, boolean removeSelfIfPossible) { super.removeChild(child); + if (BackNavigationController.isEnabled()) { + //TODO(b/207481538) Remove once the infrastructure to support per-activity screenshot is + // implemented + ActivityRecord r = child.asActivityRecord(); + if (r != null) { + mBackScreenshots.remove(r.mActivityComponent.flattenToString()); + } + } if (removeSelfIfPossible && (!mCreatedByOrganizer || mIsRemovalRequested) && !hasChild()) { removeImmediately("removeLastChild " + child); } diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java index 1ab191bb6650..b9fa29733aa6 100644 --- a/services/core/java/com/android/server/wm/WindowManagerInternal.java +++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java @@ -16,6 +16,9 @@ package com.android.server.wm; +import static java.lang.annotation.RetentionPolicy.SOURCE; + +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ClipData; @@ -40,6 +43,7 @@ import com.android.internal.policy.KeyInterceptionInfo; import com.android.server.input.InputManagerService; import com.android.server.policy.WindowManagerPolicy; +import java.lang.annotation.Retention; import java.util.List; import java.util.Set; @@ -609,6 +613,7 @@ public abstract class WindowManagerInternal { /** * Checks whether the specified IME client has IME focus or not. * + * @param windowToken The window token of the input method client * @param uid UID of the process to be queried * @param pid PID of the process to be queried * @param displayId Display ID reported from the client. Note that this method also verifies @@ -616,7 +621,22 @@ public abstract class WindowManagerInternal { * @return {@code true} if the IME client specified with {@code uid}, {@code pid}, and * {@code displayId} has IME focus */ - public abstract boolean isInputMethodClientFocus(int uid, int pid, int displayId); + public abstract @ImeClientFocusResult int hasInputMethodClientFocus(IBinder windowToken, + int uid, int pid, int displayId); + + @Retention(SOURCE) + @IntDef({ + ImeClientFocusResult.HAS_IME_FOCUS, + ImeClientFocusResult.NOT_IME_TARGET_WINDOW, + ImeClientFocusResult.DISPLAY_ID_MISMATCH, + ImeClientFocusResult.INVALID_DISPLAY_ID + }) + public @interface ImeClientFocusResult { + int HAS_IME_FOCUS = 0; + int NOT_IME_TARGET_WINDOW = -1; + int DISPLAY_ID_MISMATCH = -2; + int INVALID_DISPLAY_ID = -3; + } /** * Checks whether the given {@code uid} is allowed to use the given {@code displayId} or not. diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index e4216bfb6679..026b9e1094d0 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -143,6 +143,7 @@ import android.Manifest; import android.Manifest.permission; import android.animation.ValueAnimator; import android.annotation.IntDef; +import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; @@ -170,14 +171,12 @@ import android.content.res.Resources; import android.content.res.TypedArray; import android.database.ContentObserver; import android.graphics.Bitmap; -import android.graphics.Insets; import android.graphics.Point; +import android.graphics.PointF; import android.graphics.Rect; import android.graphics.Region; import android.hardware.configstore.V1_0.OptionalBool; -import android.hardware.configstore.V1_1.DisplayOrientation; import android.hardware.configstore.V1_1.ISurfaceFlingerConfigs; -import android.hardware.configstore.V1_1.OptionalDisplayOrientation; import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManagerInternal; import android.hardware.input.InputManager; @@ -229,7 +228,6 @@ import android.util.TypedValue; import android.util.proto.ProtoOutputStream; import android.view.Choreographer; import android.view.Display; -import android.view.DisplayAddress; import android.view.DisplayInfo; import android.view.Gravity; import android.view.IAppTransitionAnimationSpecsFuture; @@ -282,6 +280,7 @@ import android.view.WindowManagerPolicyConstants.PointerEventListener; import android.view.displayhash.DisplayHash; import android.view.displayhash.VerifiedDisplayHash; import android.window.ClientWindowFrames; +import android.window.IOnFpsCallbackListener; import android.window.TaskSnapshot; import com.android.internal.R; @@ -439,8 +438,6 @@ public class WindowManagerService extends IWindowManager.Stub */ static final boolean ENABLE_FIXED_ROTATION_TRANSFORM = SystemProperties.getBoolean("persist.wm.fixed_rotation_transform", true); - private @Surface.Rotation int mPrimaryDisplayOrientation = Surface.ROTATION_0; - private DisplayAddress mPrimaryDisplayPhysicalAddress; // Enums for animation scale update types. @Retention(RetentionPolicy.SOURCE) @@ -589,6 +586,13 @@ public class WindowManagerService extends IWindowManager.Stub final ArrayList<WindowState> mResizingWindows = new ArrayList<>(); /** + * Mapping of displayId to {@link DisplayImePolicy}. + * Note that this can be accessed without holding the lock. + */ + volatile Map<Integer, Integer> mDisplayImePolicyCache = Collections.unmodifiableMap( + new ArrayMap<>()); + + /** * Windows whose surface should be destroyed. */ final ArrayList<WindowState> mDestroySurface = new ArrayList<>(); @@ -708,6 +712,7 @@ public class WindowManagerService extends IWindowManager.Stub final TaskSnapshotController mTaskSnapshotController; final BlurController mBlurController; + final TaskFpsCallbackController mTaskFpsCallbackController; boolean mIsTouchDevice; boolean mIsFakeTouchDevice; @@ -738,6 +743,8 @@ public class WindowManagerService extends IWindowManager.Stub final WindowContextListenerController mWindowContextListenerController = new WindowContextListenerController(); + private InputTarget mFocusedInputTarget; + @VisibleForTesting final class SettingsObserver extends ContentObserver { private final Uri mDisplayInversionEnabledUri = @@ -1352,6 +1359,7 @@ public class WindowManagerService extends IWindowManager.Stub mStartingSurfaceController = new StartingSurfaceController(this); mBlurController = new BlurController(mContext, mPowerManager); + mTaskFpsCallbackController = new TaskFpsCallbackController(mContext); mAccessibilityController = new AccessibilityController(this); } @@ -1786,8 +1794,7 @@ public class WindowManagerService extends IWindowManager.Stub prepareNoneTransitionForRelaunching(activity); } - if (displayPolicy.getLayoutHint(win.mAttrs, token, outInsetsState, - win.isClientLocal())) { + if (displayPolicy.areSystemBarsForcedShownLw()) { res |= WindowManagerGlobal.ADD_FLAG_ALWAYS_CONSUME_SYSTEM_BARS; } @@ -1834,6 +1841,7 @@ public class WindowManagerService extends IWindowManager.Stub displayContent.getInsetsStateController().updateAboveInsetsState( win, false /* notifyInsetsChanged */); + outInsetsState.set(win.getCompatInsetsState(), win.isClientLocal()); getInsetsSourceControls(win, outActiveControls); } @@ -2436,23 +2444,6 @@ public class WindowManagerService extends IWindowManager.Stub configChanged = displayContent.updateOrientation(); Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); - final DisplayInfo displayInfo = win.getDisplayInfo(); - int transformHint = displayInfo.rotation; - // If the window is on the primary display, use the panel orientation to adjust the - // transform hint - final boolean isPrimaryDisplay = displayInfo.address != null && - displayInfo.address.equals(mPrimaryDisplayPhysicalAddress); - if (isPrimaryDisplay) { - transformHint = (transformHint + mPrimaryDisplayOrientation) % 4; - } - outSurfaceControl.setTransformHint( - SurfaceControl.rotationToBufferTransform(transformHint)); - ProtoLog.v(WM_DEBUG_ORIENTATION, - "Passing transform hint %d for window %s%s", - transformHint, win, - isPrimaryDisplay ? " on primary display with orientation " - + mPrimaryDisplayOrientation : ""); - if (toBeDisplayed && win.mIsWallpaper) { displayContent.mWallpaperController.updateWallpaperOffset(win, false /* sync */); } @@ -3836,6 +3827,40 @@ public class WindowManagerService extends IWindowManager.Stub } /** + * Generates and returns an up-to-date {@link Bitmap} for the specified taskId. The returned + * bitmap will be full size and will not include any secure content. + * + * @param taskId The task ID of the task for which a snapshot is requested. + * @return The Bitmap, or null if no task with the specified ID can be found or the bitmap could + * not be generated. + */ + @Nullable public Bitmap captureTaskBitmap(int taskId) { + if (mTaskSnapshotController.shouldDisableSnapshots()) { + return null; + } + + synchronized (mGlobalLock) { + final Task task = mRoot.anyTaskForId(taskId); + if (task == null) { + return null; + } + + task.getBounds(mTmpRect); + final SurfaceControl sc = task.getSurfaceControl(); + final SurfaceControl.ScreenshotHardwareBuffer buffer = SurfaceControl.captureLayers( + new SurfaceControl.LayerCaptureArgs.Builder(sc) + .setSourceCrop(mTmpRect) + .build()); + if (buffer == null) { + Slog.w(TAG, "Could not get screenshot buffer for taskId: " + taskId); + return null; + } + + return buffer.asBitmap(); + } + } + + /** * In case a task write/delete operation was lost because the system crashed, this makes sure to * clean up the directory to remove obsolete files. * @@ -4314,6 +4339,15 @@ public class WindowManagerService extends IWindowManager.Stub } } + void reportKeepClearAreasChanged(Session session, IWindow window, List<Rect> keepClearAreas) { + synchronized (mGlobalLock) { + final WindowState win = windowForClientLocked(session, window, true); + if (win.setKeepClearAreas(keepClearAreas)) { + win.getDisplayContent().updateKeepClearAreas(); + } + } + } + @Override public void registerDisplayFoldListener(IDisplayFoldListener listener) { mPolicy.registerDisplayFoldListener(listener); @@ -4886,9 +4920,6 @@ public class WindowManagerService extends IWindowManager.Stub mTaskSnapshotController.systemReady(); mHasWideColorGamutSupport = queryWideColorGamutSupport(); mHasHdrSupport = queryHdrSupport(); - mPrimaryDisplayOrientation = queryPrimaryDisplayOrientation(); - mPrimaryDisplayPhysicalAddress = - DisplayAddress.fromPhysicalDisplayId(SurfaceControl.getPrimaryPhysicalDisplayId()); UiThread.getHandler().post(mSettingsObserver::loadSettings); IVrManager vrManager = IVrManager.Stub.asInterface( ServiceManager.getService(Context.VR_SERVICE)); @@ -4951,39 +4982,6 @@ public class WindowManagerService extends IWindowManager.Stub return false; } - private static @Surface.Rotation int queryPrimaryDisplayOrientation() { - Optional<SurfaceFlingerProperties.primary_display_orientation_values> prop = - SurfaceFlingerProperties.primary_display_orientation(); - if (prop.isPresent()) { - switch (prop.get()) { - case ORIENTATION_90: return Surface.ROTATION_90; - case ORIENTATION_180: return Surface.ROTATION_180; - case ORIENTATION_270: return Surface.ROTATION_270; - case ORIENTATION_0: - default: - return Surface.ROTATION_0; - } - } - try { - ISurfaceFlingerConfigs surfaceFlinger = ISurfaceFlingerConfigs.getService(); - OptionalDisplayOrientation primaryDisplayOrientation = - surfaceFlinger.primaryDisplayOrientation(); - if (primaryDisplayOrientation != null && primaryDisplayOrientation.specified) { - switch (primaryDisplayOrientation.value) { - case DisplayOrientation.ORIENTATION_90: return Surface.ROTATION_90; - case DisplayOrientation.ORIENTATION_180: return Surface.ROTATION_180; - case DisplayOrientation.ORIENTATION_270: return Surface.ROTATION_270; - case DisplayOrientation.ORIENTATION_0: - default: - return Surface.ROTATION_0; - } - } - } catch (Exception e) { - // Use default value if we can't talk to config store. - } - return Surface.ROTATION_0; - } - // Returns an input target which is mapped to the given input token. This can be a WindowState // or an embedded window. @Nullable InputTarget getInputTargetFromToken(IBinder inputToken) { @@ -5011,6 +5009,7 @@ public class WindowManagerService extends IWindowManager.Stub Slog.v(TAG_WM, "Unknown focus tokens, dropping reportFocusChanged"); return; } + mFocusedInputTarget = newTarget; mAccessibilityController.onFocusChanged(lastTarget, newTarget); ProtoLog.i(WM_DEBUG_FOCUS_LIGHT, "Focus changing: %s -> %s", lastTarget, newTarget); @@ -5681,6 +5680,11 @@ public class WindowManagerService extends IWindowManager.Stub } @Override + public void saveWindowTraceToFile() { + mWindowTracing.saveForBugreport(null /* printwriter */); + } + + @Override public boolean isWindowTraceEnabled() { return mWindowTracing.isEnabled(); } @@ -6905,6 +6909,7 @@ public class WindowManagerService extends IWindowManager.Stub void setForceDesktopModeOnExternalDisplays(boolean forceDesktopModeOnExternalDisplays) { synchronized (mGlobalLock) { mForceDesktopModeOnExternalDisplays = forceDesktopModeOnExternalDisplays; + mRoot.updateDisplayImePolicyCache(); } } @@ -6960,23 +6965,6 @@ public class WindowManagerService extends IWindowManager.Stub } } - @Override - public void setForwardedInsets(int displayId, Insets insets) throws RemoteException { - synchronized (mGlobalLock) { - final DisplayContent dc = mRoot.getDisplayContent(displayId); - if (dc == null) { - return; - } - final int callingUid = Binder.getCallingUid(); - final int displayOwnerUid = dc.getDisplay().getOwnerUid(); - if (callingUid != displayOwnerUid) { - throw new SecurityException( - "Only owner of the display can set ForwardedInsets to it."); - } - dc.setForwardedInsets(insets); - } - } - MousePositionTracker mMousePositionTracker = new MousePositionTracker(); private static class MousePositionTracker implements PointerEventListener { @@ -7063,6 +7051,13 @@ public class WindowManagerService extends IWindowManager.Stub } } + PointF getLatestMousePosition() { + synchronized (mMousePositionTracker) { + return new PointF(mMousePositionTracker.mLatestMouseX, + mMousePositionTracker.mLatestMouseY); + } + } + /** * Update a tap exclude region in the window identified by the provided id. Touches down on this * region will not: @@ -7329,16 +7324,14 @@ public class WindowManagerService extends IWindowManager.Stub if (!checkCallingPermission(INTERNAL_SYSTEM_WINDOW, "getDisplayImePolicy()")) { throw new SecurityException("Requires INTERNAL_SYSTEM_WINDOW permission"); } - final DisplayContent dc = mRoot.getDisplayContent(displayId); - if (dc == null) { + final Map<Integer, Integer> displayImePolicyCache = mDisplayImePolicyCache; + if (!displayImePolicyCache.containsKey(displayId)) { ProtoLog.w(WM_ERROR, "Attempted to get IME policy of a display that does not exist: %d", displayId); return DISPLAY_IME_POLICY_FALLBACK_DISPLAY; } - synchronized (mGlobalLock) { - return dc.getImePolicy(); - } + return displayImePolicyCache.get(displayId); } @Override @@ -7687,19 +7680,32 @@ public class WindowManagerService extends IWindowManager.Stub } @Override - public boolean isInputMethodClientFocus(int uid, int pid, int displayId) { + public @ImeClientFocusResult int hasInputMethodClientFocus(IBinder windowToken, + int uid, int pid, int displayId) { if (displayId == Display.INVALID_DISPLAY) { - return false; + return ImeClientFocusResult.INVALID_DISPLAY_ID; } synchronized (mGlobalLock) { final DisplayContent displayContent = mRoot.getTopFocusedDisplayContent(); + final WindowState window = mWindowMap.get(windowToken); + if (window == null) { + return ImeClientFocusResult.NOT_IME_TARGET_WINDOW; + } + final int tokenDisplayId = window.getDisplayContent().getDisplayId(); + if (tokenDisplayId != displayId) { + Slog.e(TAG, "isInputMethodClientFocus: display ID mismatch." + + " from client: " + displayId + + " from window: " + tokenDisplayId); + return ImeClientFocusResult.DISPLAY_ID_MISMATCH; + } if (displayContent == null || displayContent.getDisplayId() != displayId || !displayContent.hasAccess(uid)) { - return false; + return ImeClientFocusResult.INVALID_DISPLAY_ID; } + if (displayContent.isInputMethodClientFocus(uid, pid)) { - return true; + return ImeClientFocusResult.HAS_IME_FOCUS; } // Okay, how about this... what is the current focus? // It seems in some cases we may not have moved the IM @@ -7712,10 +7718,11 @@ public class WindowManagerService extends IWindowManager.Stub final WindowState currentFocus = displayContent.mCurrentFocus; if (currentFocus != null && currentFocus.mSession.mUid == uid && currentFocus.mSession.mPid == pid) { - return currentFocus.canBeImeTarget(); + return currentFocus.canBeImeTarget() ? ImeClientFocusResult.HAS_IME_FOCUS + : ImeClientFocusResult.NOT_IME_TARGET_WINDOW; } } - return false; + return ImeClientFocusResult.NOT_IME_TARGET_WINDOW; } @Override @@ -7814,9 +7821,7 @@ public class WindowManagerService extends IWindowManager.Stub @Override public @DisplayImePolicy int getDisplayImePolicy(int displayId) { - synchronized (mGlobalLock) { - return WindowManagerService.this.getDisplayImePolicy(displayId); - } + return WindowManagerService.this.getDisplayImePolicy(displayId); } @Override @@ -8170,21 +8175,14 @@ public class WindowManagerService extends IWindowManager.Stub } private void onPointerDownOutsideFocusLocked(IBinder touchedToken) { - WindowState touchedWindow = mInputToWindowMap.get(touchedToken); - if (touchedWindow == null) { - // if a user taps outside the currently focused window onto an embedded window, treat - // it as if the host window was tapped. - touchedWindow = mEmbeddedWindowController.getHostWindow(touchedToken); - } - - if (touchedWindow == null || !touchedWindow.canReceiveKeys(true /* fromUserTouch */)) { + InputTarget t = getInputTargetFromToken(touchedToken); + if (t == null || !t.receiveFocusFromTapOutside()) { // If the window that received the input event cannot receive keys, don't move the // display it's on to the top since that window won't be able to get focus anyway. return; } - if (mRecentsAnimationController != null - && mRecentsAnimationController.getTargetAppMainWindow() == touchedWindow) { + && mRecentsAnimationController.getTargetAppMainWindow() == t) { // If there is an active recents animation and touched window is the target, then ignore // the touch. The target already handles touches using its own input monitor and we // don't want to trigger any lifecycle changes from focusing another window. @@ -8194,13 +8192,11 @@ public class WindowManagerService extends IWindowManager.Stub } ProtoLog.i(WM_DEBUG_FOCUS_LIGHT, "onPointerDownOutsideFocusLocked called on %s", - touchedWindow); - final DisplayContent displayContent = touchedWindow.getDisplayContent(); - if (!displayContent.isOnTop()) { - displayContent.getParent().positionChildAt(WindowContainer.POSITION_TOP, displayContent, - true /* includingParents */); + t); + if (mFocusedInputTarget != t && mFocusedInputTarget != null) { + mFocusedInputTarget.handleTapOutsideFocusOutsideSelf(); } - handleTaskFocusChange(touchedWindow.getTask(), touchedWindow.mActivityRecord); + t.handleTapOutsideFocusInsideSelf(); } @VisibleForTesting @@ -8481,6 +8477,7 @@ public class WindowManagerService extends IWindowManager.Stub public boolean getWindowInsets(WindowManager.LayoutParams attrs, int displayId, InsetsState outInsetsState) { final boolean fromLocal = Binder.getCallingPid() == myPid(); + final int uid = Binder.getCallingUid(); final long origId = Binder.clearCallingIdentity(); try { synchronized (mGlobalLock) { @@ -8489,9 +8486,20 @@ public class WindowManagerService extends IWindowManager.Stub throw new WindowManager.InvalidDisplayException("Display#" + displayId + "could not be found!"); } - final WindowToken windowToken = dc.getWindowToken(attrs.token); - return dc.getDisplayPolicy().getLayoutHint(attrs, windowToken, outInsetsState, - fromLocal); + final WindowToken token = dc.getWindowToken(attrs.token); + final float overrideScale = mAtmService.mCompatModePackages.getCompatScale( + attrs.packageName, uid); + final InsetsState state = dc.getInsetsPolicy().getInsetsForWindowMetrics(attrs); + final boolean hasCompatScale = + WindowState.hasCompatScale(attrs, token, overrideScale); + outInsetsState.set(state, hasCompatScale || fromLocal); + if (hasCompatScale) { + final float compatScale = token != null && token.hasSizeCompatBounds() + ? token.getSizeCompatScale() * overrideScale + : overrideScale; + outInsetsState.scale(1f / compatScale); + } + return dc.getDisplayPolicy().areSystemBarsForcedShownLw(); } } finally { Binder.restoreCallingIdentity(origId); @@ -8720,20 +8728,21 @@ public class WindowManagerService extends IWindowManager.Stub } boolean shouldRestoreImeVisibility(IBinder imeTargetWindowToken) { + final Task imeTargetWindowTask; synchronized (mGlobalLock) { final WindowState imeTargetWindow = mWindowMap.get(imeTargetWindowToken); if (imeTargetWindow == null) { return false; } - final Task imeTargetWindowTask = imeTargetWindow.getTask(); + imeTargetWindowTask = imeTargetWindow.getTask(); if (imeTargetWindowTask == null) { return false; } - final TaskSnapshot snapshot = getTaskSnapshot(imeTargetWindowTask.mTaskId, - imeTargetWindowTask.mUserId, false /* isLowResolution */, - false /* restoreFromDisk */); - return snapshot != null && snapshot.hasImeSurface(); } + final TaskSnapshot snapshot = getTaskSnapshot(imeTargetWindowTask.mTaskId, + imeTargetWindowTask.mUserId, false /* isLowResolution */, + false /* restoreFromDisk */); + return snapshot != null && snapshot.hasImeSurface(); } @Override @@ -8777,4 +8786,35 @@ public class WindowManagerService extends IWindowManager.Stub mTaskTransitionSpec = null; } + + @Override + @RequiresPermission(Manifest.permission.ACCESS_FPS_COUNTER) + public void registerTaskFpsCallback(@IntRange(from = 0) int taskId, + IOnFpsCallbackListener listener) { + if (mContext.checkCallingOrSelfPermission(Manifest.permission.ACCESS_FPS_COUNTER) + != PackageManager.PERMISSION_GRANTED) { + final int pid = Binder.getCallingPid(); + throw new SecurityException("Access denied to process: " + pid + + ", must have permission " + Manifest.permission.ACCESS_FPS_COUNTER); + } + + if (mRoot.anyTaskForId(taskId) == null) { + throw new IllegalArgumentException("no task with taskId: " + taskId); + } + + mTaskFpsCallbackController.registerCallback(taskId, listener); + } + + @Override + @RequiresPermission(Manifest.permission.ACCESS_FPS_COUNTER) + public void unregisterTaskFpsCallback(IOnFpsCallbackListener listener) { + if (mContext.checkCallingOrSelfPermission(Manifest.permission.ACCESS_FPS_COUNTER) + != PackageManager.PERMISSION_GRANTED) { + final int pid = Binder.getCallingPid(); + throw new SecurityException("Access denied to process: " + pid + + ", must have permission " + Manifest.permission.ACCESS_FPS_COUNTER); + } + + mTaskFpsCallbackController.unregisterCallback(listener); + } } diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 94d4a77a7ecc..1f837677ab36 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -172,6 +172,7 @@ import static com.android.server.wm.WindowStateProto.HAS_SURFACE; import static com.android.server.wm.WindowStateProto.IS_ON_SCREEN; import static com.android.server.wm.WindowStateProto.IS_READY_FOR_DISPLAY; import static com.android.server.wm.WindowStateProto.IS_VISIBLE; +import static com.android.server.wm.WindowStateProto.KEEP_CLEAR_AREAS; import static com.android.server.wm.WindowStateProto.PENDING_SEAMLESS_ROTATION; import static com.android.server.wm.WindowStateProto.REMOVED; import static com.android.server.wm.WindowStateProto.REMOVE_ON_EXIT; @@ -196,6 +197,7 @@ import android.graphics.Matrix; import android.graphics.PixelFormat; import android.graphics.Point; import android.graphics.Rect; +import android.graphics.RectF; import android.graphics.Region; import android.gui.TouchOcclusionMode; import android.os.Binder; @@ -442,9 +444,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // Current transformation being applied. float mGlobalScale=1; - float mLastGlobalScale=1; float mInvGlobalScale=1; - float mOverrideScale = 1; + final float mOverrideScale; float mHScale=1, mVScale=1; float mLastHScale=1, mLastVScale=1; @@ -471,6 +472,12 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP * Coordinates are relative to the window's position. */ private final List<Rect> mExclusionRects = new ArrayList<>(); + /** + * List of rects which should ideally not be covered by floating windows like Pip. + * + * Coordinates are relative to the window's position. + */ + private final List<Rect> mKeepClearAreas = new ArrayList<>(); // 0 = left, 1 = right private final int[] mLastRequestedExclusionHeight = {0, 0}; @@ -1012,6 +1019,55 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } } + /** + * @return a list of rects that should ideally not be covered by floating windows like pip. + * The returned rect coordinates are relative to the display origin. + */ + List<Rect> getKeepClearAreas() { + final Matrix tmpMatrix = new Matrix(); + final float[] tmpFloat9 = new float[9]; + return getKeepClearAreas(tmpMatrix, tmpFloat9); + } + + /** + * @param tmpMatrix a temporary matrix to be used for transformations + * @param float9 a temporary array of 9 floats + * + * @return a list of rects that should ideally not be covered by floating windows like pip. + * The returned rect coordinates are relative to the display origin. + */ + List<Rect> getKeepClearAreas(Matrix tmpMatrix, float[] float9) { + getTransformationMatrix(float9, tmpMatrix); + + // Translate all keep-clear rects to screen coordinates. + final List<Rect> transformedKeepClearAreas = new ArrayList<Rect>(); + final RectF tmpRect = new RectF(); + Rect curr; + for (Rect r : mKeepClearAreas) { + tmpRect.set(r); + tmpMatrix.mapRect(tmpRect); + curr = new Rect(); + tmpRect.roundOut(curr); + transformedKeepClearAreas.add(curr); + } + return transformedKeepClearAreas; + } + + /** + * @param keepClearAreas the new keep-clear areas for this window. The rects should be defined + * in window coordinate space + * + * @return true if there is a change in the list of keep-clear areas; false otherwise + */ + boolean setKeepClearAreas(List<Rect> keepClearAreas) { + if (mKeepClearAreas.equals(keepClearAreas)) { + return false; + } + mKeepClearAreas.clear(); + mKeepClearAreas.addAll(keepClearAreas); + return true; + } + interface PowerManagerWrapper { void wakeUp(long time, @WakeReason int reason, String details); @@ -1091,6 +1147,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP mSubLayer = 0; mWinAnimator = null; mWpcForDisplayAreaConfigChanges = null; + mOverrideScale = 1f; return; } mDeathRecipient = deathRecipient; @@ -1138,6 +1195,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP mLayer = 0; mOverrideScale = mWmService.mAtmService.mCompatModePackages.getCompatScale( mAttrs.packageName, s.mUid); + updateGlobalScale(); // Make sure we initial all fields before adding to parentWindow, to prevent exception // during onDisplayChanged. @@ -1167,6 +1225,23 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP mSession.windowAddedLocked(); } + boolean updateGlobalScale() { + if (hasCompatScale()) { + if (mOverrideScale != 1f) { + mGlobalScale = mToken.hasSizeCompatBounds() + ? mToken.getSizeCompatScale() * mOverrideScale + : mOverrideScale; + } else { + mGlobalScale = mToken.getSizeCompatScale(); + } + mInvGlobalScale = 1f / mGlobalScale; + return true; + } + + mGlobalScale = mInvGlobalScale = 1f; + return false; + } + /** * @return {@code true} if the application runs in size compatibility mode or has an app level * scaling override set. @@ -1175,7 +1250,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP * @see ActivityRecord#hasSizeCompatBounds() */ boolean hasCompatScale() { - return mOverrideScale != 1f || hasCompatScale(mAttrs, mActivityRecord); + return hasCompatScale(mAttrs, mActivityRecord, mOverrideScale); } /** @@ -1183,11 +1258,16 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP * @see android.content.res.CompatibilityInfo#supportsScreen * @see ActivityRecord#hasSizeCompatBounds() */ - static boolean hasCompatScale(WindowManager.LayoutParams attrs, WindowToken windowToken) { - return (attrs.privateFlags & PRIVATE_FLAG_COMPATIBLE_WINDOW) != 0 - || (windowToken != null && windowToken.hasSizeCompatBounds() - // Exclude starting window because it is not displayed by the application. - && attrs.type != TYPE_APPLICATION_STARTING); + static boolean hasCompatScale(WindowManager.LayoutParams attrs, WindowToken token, + float overrideScale) { + if ((attrs.privateFlags & PRIVATE_FLAG_COMPATIBLE_WINDOW) != 0) { + return true; + } + if (attrs.type == TYPE_APPLICATION_STARTING) { + // Exclude starting window because it is not displayed by the application. + return false; + } + return token != null && token.hasSizeCompatBounds() || overrideScale != 1f; } /** @@ -1691,21 +1771,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP && (mActivityRecord.firstWindowDrawn || mActivityRecord.startingDisplayed); } - void prelayout() { - if (hasCompatScale()) { - if (mOverrideScale != 1f) { - mGlobalScale = mToken.hasSizeCompatBounds() - ? mToken.getSizeCompatScale() * mOverrideScale - : mOverrideScale; - } else { - mGlobalScale = mToken.getSizeCompatScale(); - } - mInvGlobalScale = 1 / mGlobalScale; - } else { - mGlobalScale = mInvGlobalScale = 1; - } - } - @Override boolean hasContentToDisplay() { if (!mAppFreezing && isDrawn() && (mViewVisibility == View.VISIBLE @@ -2928,7 +2993,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP @Override public void binderDied() { try { - boolean resetSplitScreenResizing = false; synchronized (mWmService.mGlobalLock) { final WindowState win = mWmService .windowForClientLocked(mSession, mClient, false); @@ -2944,16 +3008,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP WindowState.this.removeIfPossible(); } } - if (resetSplitScreenResizing) { - try { - // Note: this calls into ActivityManager, so we must *not* hold the window - // manager lock while calling this. - mWmService.mActivityTaskManager.setSplitScreenResizing(false); - } catch (RemoteException e) { - // Local call, shouldn't return RemoteException. - throw e.rethrowAsRuntimeException(); - } - } } catch (IllegalArgumentException ex) { // This will happen if the window has already been removed. } @@ -4063,6 +4117,9 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP proto.write(FORCE_SEAMLESS_ROTATION, mForceSeamlesslyRotate); proto.write(HAS_COMPAT_SCALE, hasCompatScale()); proto.write(GLOBAL_SCALE, mGlobalScale); + for (Rect r : getKeepClearAreas()) { + r.dumpDebug(proto, KEEP_CLEAR_AREAS); + } proto.end(token); } @@ -4231,6 +4288,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } pw.println(prefix + "isOnScreen=" + isOnScreen()); pw.println(prefix + "isVisible=" + isVisible()); + pw.println(prefix + "keepClearAreas=" + getKeepClearAreas()); if (dumpAll) { final String visibilityString = mRequestedVisibilities.toString(); if (!visibilityString.isEmpty()) { @@ -5259,7 +5317,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP mLastVScale != newVScale ) { getPendingTransaction().setMatrix(getSurfaceControl(), newHScale, 0, 0, newVScale); - mLastGlobalScale = mGlobalScale; mLastHScale = newHScale; mLastVScale = newVScale; } @@ -5985,4 +6042,24 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP boolean isTrustedOverlay() { return mInputWindowHandle.isTrustedOverlay(); } + + public boolean receiveFocusFromTapOutside() { + return canReceiveKeys(true); + } + + @Override + public void handleTapOutsideFocusOutsideSelf() { + // Nothing to do here since raising the other window will naturally take care of + // us loosing focus + } + + @Override + public void handleTapOutsideFocusInsideSelf() { + final DisplayContent displayContent = getDisplayContent(); + if (!displayContent.isOnTop()) { + displayContent.getParent().positionChildAt(WindowContainer.POSITION_TOP, displayContent, + true /* includingParents */); + } + mWmService.handleTaskFocusChange(getTask(), mActivityRecord); + } } diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp index 7b4fd365905c..79a980f0d9ee 100644 --- a/services/core/jni/Android.bp +++ b/services/core/jni/Android.bp @@ -68,14 +68,18 @@ cc_library_static { "com_android_server_am_LowMemDetector.cpp", "com_android_server_pm_PackageManagerShellCommandDataLoader.cpp", "com_android_server_sensor_SensorService.cpp", + "com_android_server_wm_TaskFpsCallbackController.cpp", "onload.cpp", ":lib_cachedAppOptimizer_native", ":lib_networkStatsFactory_native", + ":lib_gameManagerService_native", ], include_dirs: [ "frameworks/base/libs", "frameworks/native/services", + "frameworks/native/libs/math/include", + "frameworks/native/libs/ui/include", "system/gatekeeper/include", "system/memory/libmeminfo/include", ], @@ -103,6 +107,7 @@ cc_defaults { "libcrypto", "liblog", "libgraphicsenv", + "libgralloctypes", "libhardware", "libhardware_legacy", "libhidlbase", @@ -157,6 +162,10 @@ cc_defaults { "android.hardware.gnss.measurement_corrections@1.0", "android.hardware.gnss.visibility_control@1.0", "android.hardware.graphics.bufferqueue@1.0", + "android.hardware.graphics.bufferqueue@2.0", + "android.hardware.graphics.common@1.2", + "android.hardware.graphics.common-V3-ndk", + "android.hardware.graphics.mapper@4.0", "android.hardware.input.classifier@1.0", "android.hardware.ir@1.0", "android.hardware.light@2.0", @@ -216,3 +225,10 @@ filegroup { "com_android_server_am_CachedAppOptimizer.cpp", ], } + +filegroup { + name: "lib_gameManagerService_native", + srcs: [ + "com_android_server_app_GameManagerService.cpp", + ], +} diff --git a/services/core/jni/com_android_server_app_GameManagerService.cpp b/services/core/jni/com_android_server_app_GameManagerService.cpp new file mode 100644 index 000000000000..3028813d0d5a --- /dev/null +++ b/services/core/jni/com_android_server_app_GameManagerService.cpp @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "GameManagerService" + +#include <android/log.h> +#include <gui/SurfaceComposerClient.h> +#include <log/log.h> +#include <nativehelper/JNIHelp.h> + +#include "jni.h" + +namespace android { + +static void android_server_app_GameManagerService_nativeSetOverrideFrameRate(JNIEnv* env, + jclass clazz, jint uid, + jfloat frameRate) { + SurfaceComposerClient::setOverrideFrameRate(uid, frameRate); +} + +static const JNINativeMethod gMethods[] = { + {"nativeSetOverrideFrameRate", "(IF)V", + (void*)android_server_app_GameManagerService_nativeSetOverrideFrameRate}, +}; + +int register_android_server_app_GameManagerService(JNIEnv* env) { + return jniRegisterNativeMethods(env, "com/android/server/app/GameManagerService", gMethods, + NELEM(gMethods)); +} + +}; // namespace android
\ No newline at end of file diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp index 3cd4e5ee82cf..c71686abe9de 100644 --- a/services/core/jni/com_android_server_input_InputManagerService.cpp +++ b/services/core/jni/com_android_server_input_InputManagerService.cpp @@ -286,6 +286,7 @@ public: void requestPointerCapture(const sp<IBinder>& windowToken, bool enabled); void setCustomPointerIcon(const SpriteIcon& icon); void setMotionClassifierEnabled(bool enabled); + void notifyPointerDisplayIdChanged(); /* --- InputReaderPolicyInterface implementation --- */ @@ -1494,6 +1495,18 @@ void NativeInputManager::setMotionClassifierEnabled(bool enabled) { mInputManager->getClassifier().setMotionClassifierEnabled(enabled); } +void NativeInputManager::notifyPointerDisplayIdChanged() { + int32_t pointerDisplayId = getPointerDisplayId(); + + { // acquire lock + AutoMutex _l(mLock); + mLocked.pointerDisplayId = pointerDisplayId; + } // release lock + + mInputManager->getReader().requestRefreshConfiguration( + InputReaderConfiguration::CHANGE_DISPLAY_INFO); +} + // ---------------------------------------------------------------------------- static jlong nativeInit(JNIEnv* env, jclass /* clazz */, @@ -1573,6 +1586,13 @@ static jboolean nativeHasKeys(JNIEnv* env, jclass /* clazz */, return result; } +static jint nativeGetKeyCodeForKeyLocation(JNIEnv* env, jclass /* clazz */, jlong ptr, + jint deviceId, jint locationKeyCode) { + NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr); + return (jint)im->getInputManager()->getReader().getKeyCodeForKeyLocation(deviceId, + locationKeyCode); +} + static void handleInputChannelDisposed(JNIEnv* env, jobject /* inputChannelObj */, const std::shared_ptr<InputChannel>& inputChannel, void* data) { @@ -2186,6 +2206,18 @@ static void nativeNotifyPortAssociationsChanged(JNIEnv* env, jclass /* clazz */, InputReaderConfiguration::CHANGE_DISPLAY_INFO); } +static void nativeNotifyPointerDisplayIdChanged(JNIEnv* env, jclass /* clazz */, jlong ptr) { + NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr); + im->notifyPointerDisplayIdChanged(); +} + +static void nativeSetDisplayEligibilityForPointerCapture(JNIEnv* env, jclass /* clazz */, jlong ptr, + jint displayId, jboolean isEligible) { + NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr); + im->getInputManager()->getDispatcher().setDisplayEligibilityForPointerCapture(displayId, + isEligible); +} + static void nativeChangeUniqueIdAssociation(JNIEnv* env, jclass /* clazz */, jlong ptr) { NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr); im->getInputManager()->getReader().requestRefreshConfiguration( @@ -2312,6 +2344,7 @@ static const JNINativeMethod gInputManagerMethods[] = { {"nativeGetKeyCodeState", "(JIII)I", (void*)nativeGetKeyCodeState}, {"nativeGetSwitchState", "(JIII)I", (void*)nativeGetSwitchState}, {"nativeHasKeys", "(JII[I[Z)Z", (void*)nativeHasKeys}, + {"nativeGetKeyCodeForKeyLocation", "(JII)I", (void*)nativeGetKeyCodeForKeyLocation}, {"nativeCreateInputChannel", "(JLjava/lang/String;)Landroid/view/InputChannel;", (void*)nativeCreateInputChannel}, {"nativeCreateInputMonitor", "(JIZLjava/lang/String;I)Landroid/view/InputChannel;", @@ -2370,6 +2403,9 @@ static const JNINativeMethod gInputManagerMethods[] = { {"nativeCanDispatchToDisplay", "(JII)Z", (void*)nativeCanDispatchToDisplay}, {"nativeNotifyPortAssociationsChanged", "(J)V", (void*)nativeNotifyPortAssociationsChanged}, {"nativeChangeUniqueIdAssociation", "(J)V", (void*)nativeChangeUniqueIdAssociation}, + {"nativeNotifyPointerDisplayIdChanged", "(J)V", (void*)nativeNotifyPointerDisplayIdChanged}, + {"nativeSetDisplayEligibilityForPointerCapture", "(JIZ)V", + (void*)nativeSetDisplayEligibilityForPointerCapture}, {"nativeSetMotionClassifierEnabled", "(JZ)V", (void*)nativeSetMotionClassifierEnabled}, {"nativeGetSensorList", "(JI)[Landroid/hardware/input/InputSensorInfo;", (void*)nativeGetSensorList}, diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp index be656e3f3a27..0da8f7ef0dea 100644 --- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp +++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp @@ -51,6 +51,7 @@ #include "android_runtime/AndroidRuntime.h" #include "android_runtime/Log.h" #include "gnss/AGnss.h" +#include "gnss/AGnssRil.h" #include "gnss/GnssAntennaInfoCallback.h" #include "gnss/GnssBatching.h" #include "gnss/GnssConfiguration.h" @@ -76,8 +77,6 @@ static jmethodID method_setGnssHardwareModelName; static jmethodID method_psdsDownloadRequest; static jmethodID method_reportNiNotification; static jmethodID method_requestLocation; -static jmethodID method_requestRefLocation; -static jmethodID method_requestSetID; static jmethodID method_requestUtcTime; static jmethodID method_reportGnssServiceDied; static jmethodID method_reportGnssPowerStats; @@ -126,7 +125,6 @@ using android::hardware::hidl_string; using android::hardware::hidl_death_recipient; using android::hardware::gnss::V1_0::GnssLocationFlags; -using android::hardware::gnss::V1_0::IAGnssRilCallback; using android::hardware::gnss::V1_0::IGnssNavigationMessage; using android::hardware::gnss::V1_0::IGnssNavigationMessageCallback; using android::hardware::gnss::V1_0::IGnssNi; @@ -158,8 +156,6 @@ using IGnssCallback_V1_0 = android::hardware::gnss::V1_0::IGnssCallback; using IGnssCallback_V2_0 = android::hardware::gnss::V2_0::IGnssCallback; using IGnssCallback_V2_1 = android::hardware::gnss::V2_1::IGnssCallback; using IGnssAntennaInfo = android::hardware::gnss::V2_1::IGnssAntennaInfo; -using IAGnssRil_V1_0 = android::hardware::gnss::V1_0::IAGnssRil; -using IAGnssRil_V2_0 = android::hardware::gnss::V2_0::IAGnssRil; using IMeasurementCorrections_V1_0 = android::hardware::gnss::measurement_corrections::V1_0::IMeasurementCorrections; using IMeasurementCorrections_V1_1 = android::hardware::gnss::measurement_corrections::V1_1::IMeasurementCorrections; @@ -175,7 +171,9 @@ using android::hardware::gnss::GnssPowerStats; using android::hardware::gnss::IGnssPowerIndication; using android::hardware::gnss::IGnssPowerIndicationCallback; using android::hardware::gnss::PsdsType; + using IAGnssAidl = android::hardware::gnss::IAGnss; +using IAGnssRilAidl = android::hardware::gnss::IAGnssRil; using IGnssAidl = android::hardware::gnss::IGnss; using IGnssCallbackAidl = android::hardware::gnss::IGnssCallback; using IGnssBatchingAidl = android::hardware::gnss::IGnssBatching; @@ -208,8 +206,6 @@ sp<IGnssAidl> gnssHalAidl = nullptr; sp<IGnssBatchingAidl> gnssBatchingAidlIface = nullptr; sp<IGnssPsdsAidl> gnssPsdsAidlIface = nullptr; sp<IGnssXtra> gnssXtraIface = nullptr; -sp<IAGnssRil_V1_0> agnssRilIface = nullptr; -sp<IAGnssRil_V2_0> agnssRilIface_V2_0 = nullptr; sp<IGnssNi> gnssNiIface = nullptr; sp<IGnssPowerIndication> gnssPowerIndicationIface = nullptr; sp<IMeasurementCorrections_V1_0> gnssCorrectionsIface_V1_0 = nullptr; @@ -224,6 +220,7 @@ std::unique_ptr<android::gnss::GnssBatchingInterface> gnssBatchingIface = nullpt std::unique_ptr<android::gnss::GnssGeofenceInterface> gnssGeofencingIface = nullptr; std::unique_ptr<android::gnss::AGnssInterface> agnssIface = nullptr; std::unique_ptr<android::gnss::GnssDebugInterface> gnssDebugIface = nullptr; +std::unique_ptr<android::gnss::AGnssRilInterface> agnssRilIface = nullptr; #define WAKE_LOCK_NAME "GPS" @@ -909,29 +906,6 @@ Return<bool> GnssVisibilityControlCallback::isInEmergencySession() { return result; } -/* - * AGnssRilCallback implements the callback methods required by the AGnssRil - * interface. - */ -struct AGnssRilCallback : IAGnssRilCallback { - Return<void> requestSetIdCb(uint32_t setIdFlag) override; - Return<void> requestRefLocCb() override; -}; - -Return<void> AGnssRilCallback::requestSetIdCb(uint32_t setIdFlag) { - JNIEnv* env = getJniEnv(); - env->CallVoidMethod(mCallbacksObj, method_requestSetID, setIdFlag); - checkAndClearExceptionFromCallback(env, __FUNCTION__); - return Void(); -} - -Return<void> AGnssRilCallback::requestRefLocCb() { - JNIEnv* env = getJniEnv(); - env->CallVoidMethod(mCallbacksObj, method_requestRefLocation); - checkAndClearExceptionFromCallback(env, __FUNCTION__); - return Void(); -} - /* Initializes the GNSS service handle. */ static void android_location_gnss_hal_GnssNative_set_gps_service_handle() { gnssHalAidl = waitForVintfService<IGnssAidl>(); @@ -990,8 +964,6 @@ static void android_location_gnss_hal_GnssNative_class_init_once(JNIEnv* env, jc method_reportNiNotification = env->GetMethodID(clazz, "reportNiNotification", "(IIIIILjava/lang/String;Ljava/lang/String;II)V"); method_requestLocation = env->GetMethodID(clazz, "requestLocation", "(ZZ)V"); - method_requestRefLocation = env->GetMethodID(clazz, "requestRefLocation", "()V"); - method_requestSetID = env->GetMethodID(clazz, "requestSetID", "(I)V"); method_requestUtcTime = env->GetMethodID(clazz, "requestUtcTime", "()V"); method_reportGnssServiceDied = env->GetMethodID(clazz, "reportGnssServiceDied", "()V"); method_reportNfwNotification = env->GetMethodID(clazz, "reportNfwNotification", @@ -1069,6 +1041,7 @@ static void android_location_gnss_hal_GnssNative_class_init_once(JNIEnv* env, jc gnss::GnssMeasurement_class_init_once(env, clazz); gnss::GnssNavigationMessage_class_init_once(env, clazz); gnss::AGnss_class_init_once(env, clazz); + gnss::AGnssRil_class_init_once(env, clazz); gnss::Utils_class_init_once(env); } @@ -1124,20 +1097,21 @@ static void android_location_gnss_hal_GnssNative_init_once(JNIEnv* env, jobject } } - if (gnssHal_V2_0 != nullptr) { + if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) { + sp<IAGnssRilAidl> agnssRilAidl; + auto status = gnssHalAidl->getExtensionAGnssRil(&agnssRilAidl); + if (checkAidlStatus(status, "Unable to get a handle to AGnssRil interface.")) { + agnssRilIface = std::make_unique<gnss::AGnssRil>(agnssRilAidl); + } + } else if (gnssHal_V2_0 != nullptr) { auto agnssRil_V2_0 = gnssHal_V2_0->getExtensionAGnssRil_2_0(); - if (!agnssRil_V2_0.isOk()) { - ALOGD("Unable to get a handle to AGnssRil_V2_0"); - } else { - agnssRilIface_V2_0 = agnssRil_V2_0; - agnssRilIface = agnssRilIface_V2_0; + if (checkHidlReturn(agnssRil_V2_0, "Unable to get a handle to AGnssRil_V2_0")) { + agnssRilIface = std::make_unique<gnss::AGnssRil_V2_0>(agnssRil_V2_0); } } else if (gnssHal != nullptr) { auto agnssRil_V1_0 = gnssHal->getExtensionAGnssRil(); - if (!agnssRil_V1_0.isOk()) { - ALOGD("Unable to get a handle to AGnssRil"); - } else { - agnssRilIface = agnssRil_V1_0; + if (checkHidlReturn(agnssRil_V1_0, "Unable to get a handle to AGnssRil_V1_0")) { + agnssRilIface = std::make_unique<gnss::AGnssRil_V1_0>(agnssRil_V1_0); } } @@ -1472,12 +1446,9 @@ static jboolean android_location_gnss_hal_GnssNative_init(JNIEnv* /* env */, jcl ALOGI("Unable to initialize IGnssNi interface."); } - // Set IAGnssRil.hal callback. - sp<IAGnssRilCallback> aGnssRilCbIface = new AGnssRilCallback(); - if (agnssRilIface != nullptr) { - auto status = agnssRilIface->setCallback(aGnssRilCbIface); - checkHidlReturn(status, "IAGnssRil setCallback() failed."); - } else { + // Set IAGnssRil callback. + if (agnssRilIface == nullptr || + !agnssRilIface->setCallback(std::make_unique<gnss::AGnssRilCallback>())) { ALOGI("Unable to initialize IAGnssRil interface."); } @@ -1605,31 +1576,13 @@ static void android_location_gnss_hal_GnssNative_delete_aiding_data(JNIEnv* /* e } static void android_location_gnss_hal_GnssNative_agps_set_reference_location_cellid( - JNIEnv* /* env */, jclass, jint type, jint mcc, jint mnc, jint lac, jint cid) { - IAGnssRil_V1_0::AGnssRefLocation location; - + JNIEnv* /* env */, jclass, jint type, jint mcc, jint mnc, jint lac, jlong cid, jint tac, + jint pcid, jint arfcn) { if (agnssRilIface == nullptr) { ALOGE("%s: IAGnssRil interface not available.", __func__); return; } - - switch (static_cast<IAGnssRil_V1_0::AGnssRefLocationType>(type)) { - case IAGnssRil_V1_0::AGnssRefLocationType::GSM_CELLID: - case IAGnssRil_V1_0::AGnssRefLocationType::UMTS_CELLID: - location.type = static_cast<IAGnssRil_V1_0::AGnssRefLocationType>(type); - location.cellID.mcc = mcc; - location.cellID.mnc = mnc; - location.cellID.lac = lac; - location.cellID.cid = cid; - break; - default: - ALOGE("Neither a GSM nor a UMTS cellid (%s:%d).", __FUNCTION__, __LINE__); - return; - break; - } - - auto result = agnssRilIface->setRefLocation(location); - checkHidlReturn(result, "IAGnssRil setRefLocation() failed."); + agnssRilIface->setRefLocation(type, mcc, mnc, lac, cid, tac, pcid, arfcn); } static void android_location_gnss_hal_GnssNative_agps_set_id(JNIEnv* env, jclass, jint type, @@ -1638,10 +1591,7 @@ static void android_location_gnss_hal_GnssNative_agps_set_id(JNIEnv* env, jclass ALOGE("%s: IAGnssRil interface not available.", __func__); return; } - - ScopedJniString jniSetId{env, setid_string}; - auto result = agnssRilIface->setSetId((IAGnssRil_V1_0::SetIDType)type, jniSetId); - checkHidlReturn(result, "IAGnssRil setSetId() failed."); + agnssRilIface->setSetId(type, setid_string); } static jint android_location_gnss_hal_GnssNative_read_nmea(JNIEnv* env, jclass, @@ -1878,31 +1828,12 @@ static void android_location_GnssNetworkConnectivityHandler_update_network_state jstring apn, jlong networkHandle, jshort capabilities) { - if (agnssRilIface_V2_0 != nullptr) { - ScopedJniString jniApn{env, apn}; - IAGnssRil_V2_0::NetworkAttributes networkAttributes = { - .networkHandle = static_cast<uint64_t>(networkHandle), - .isConnected = static_cast<bool>(connected), - .capabilities = static_cast<uint16_t>(capabilities), - .apn = jniApn - }; - - auto result = agnssRilIface_V2_0->updateNetworkState_2_0(networkAttributes); - checkHidlReturn(result, "IAGnssRil updateNetworkState_2_0() failed."); - } else if (agnssRilIface != nullptr) { - ScopedJniString jniApn{env, apn}; - hidl_string hidlApn{jniApn}; - auto result = agnssRilIface->updateNetworkState(connected, - static_cast<IAGnssRil_V1_0::NetworkType>(type), roaming); - checkHidlReturn(result, "IAGnssRil updateNetworkState() failed."); - - if (!hidlApn.empty()) { - result = agnssRilIface->updateNetworkAvailability(available, hidlApn); - checkHidlReturn(result, "IAGnssRil updateNetworkAvailability() failed."); - } - } else { + if (agnssRilIface == nullptr) { ALOGE("%s: IAGnssRil interface not available.", __func__); + return; } + agnssRilIface->updateNetworkState(connected, type, roaming, available, apn, networkHandle, + capabilities); } static jboolean android_location_gnss_hal_GnssNative_is_geofence_supported(JNIEnv* /* env */, @@ -2342,11 +2273,12 @@ static void android_location_gnss_hal_GnssNative_cleanup_batching(JNIEnv*, jclas } static jboolean android_location_gnss_hal_GnssNative_start_batch(JNIEnv*, jclass, jlong periodNanos, + jfloat minUpdateDistanceMeters, jboolean wakeOnFifoFull) { if (gnssBatchingIface == nullptr) { return JNI_FALSE; // batching not supported } - return gnssBatchingIface->start(periodNanos, wakeOnFifoFull); + return gnssBatchingIface->start(periodNanos, minUpdateDistanceMeters, wakeOnFifoFull); } static void android_location_gnss_hal_GnssNative_flush_batch(JNIEnv*, jclass) { @@ -2418,7 +2350,7 @@ static const JNINativeMethod sLocationProviderMethods[] = { reinterpret_cast<void*>(android_location_gnss_hal_GnssNative_inject_psds_data)}, {"native_agps_set_id", "(ILjava/lang/String;)V", reinterpret_cast<void*>(android_location_gnss_hal_GnssNative_agps_set_id)}, - {"native_agps_set_ref_location_cellid", "(IIIII)V", + {"native_agps_set_ref_location_cellid", "(IIIIJIII)V", reinterpret_cast<void*>( android_location_gnss_hal_GnssNative_agps_set_reference_location_cellid)}, {"native_set_agps_server", "(ILjava/lang/String;I)V", @@ -2436,7 +2368,7 @@ static const JNINativeMethod sBatchingMethods[] = { /* name, signature, funcPtr */ {"native_get_batch_size", "()I", reinterpret_cast<void*>(android_location_gnss_hal_GnssNative_get_batch_size)}, - {"native_start_batch", "(JZ)Z", + {"native_start_batch", "(JFZ)Z", reinterpret_cast<void*>(android_location_gnss_hal_GnssNative_start_batch)}, {"native_flush_batch", "()V", reinterpret_cast<void*>(android_location_gnss_hal_GnssNative_flush_batch)}, diff --git a/services/core/jni/com_android_server_vibrator_VibratorController.cpp b/services/core/jni/com_android_server_vibrator_VibratorController.cpp index b484796af6e2..f5e6c45c75b8 100644 --- a/services/core/jni/com_android_server_vibrator_VibratorController.cpp +++ b/services/core/jni/com_android_server_vibrator_VibratorController.cpp @@ -39,8 +39,8 @@ namespace android { static JavaVM* sJvm = nullptr; static jmethodID sMethodIdOnComplete; -static jclass sFrequencyMappingClass; -static jmethodID sFrequencyMappingCtor; +static jclass sFrequencyProfileClass; +static jmethodID sFrequencyProfileCtor; static struct { jmethodID setCapabilities; jmethodID setSupportedEffects; @@ -51,7 +51,7 @@ static struct { jmethodID setPrimitiveDelayMax; jmethodID setCompositionSizeMax; jmethodID setQFactor; - jmethodID setFrequencyMapping; + jmethodID setFrequencyProfile; } sVibratorInfoBuilderClassInfo; static struct { jfieldID id; @@ -437,11 +437,11 @@ static jboolean vibratorGetInfo(JNIEnv* env, jclass /* clazz */, jlong ptr, env->SetFloatArrayRegion(maxAmplitudes, 0, amplitudes.size(), reinterpret_cast<jfloat*>(amplitudes.data())); } - jobject frequencyMapping = - env->NewObject(sFrequencyMappingClass, sFrequencyMappingCtor, resonantFrequency, + jobject frequencyProfile = + env->NewObject(sFrequencyProfileClass, sFrequencyProfileCtor, resonantFrequency, minFrequency, frequencyResolution, maxAmplitudes); - env->CallObjectMethod(vibratorInfoBuilder, sVibratorInfoBuilderClassInfo.setFrequencyMapping, - frequencyMapping); + env->CallObjectMethod(vibratorInfoBuilder, sVibratorInfoBuilderClassInfo.setFrequencyProfile, + frequencyProfile); return info.isFailedLogged("vibratorGetInfo") ? JNI_FALSE : JNI_TRUE; } @@ -485,9 +485,9 @@ int register_android_server_vibrator_VibratorController(JavaVM* jvm, JNIEnv* env sRampClassInfo.endFrequencyHz = GetFieldIDOrDie(env, rampClass, "mEndFrequencyHz", "F"); sRampClassInfo.duration = GetFieldIDOrDie(env, rampClass, "mDuration", "I"); - jclass frequencyMappingClass = FindClassOrDie(env, "android/os/VibratorInfo$FrequencyMapping"); - sFrequencyMappingClass = static_cast<jclass>(env->NewGlobalRef(frequencyMappingClass)); - sFrequencyMappingCtor = GetMethodIDOrDie(env, sFrequencyMappingClass, "<init>", "(FFF[F)V"); + jclass frequencyProfileClass = FindClassOrDie(env, "android/os/VibratorInfo$FrequencyProfile"); + sFrequencyProfileClass = static_cast<jclass>(env->NewGlobalRef(frequencyProfileClass)); + sFrequencyProfileCtor = GetMethodIDOrDie(env, sFrequencyProfileClass, "<init>", "(FFF[F)V"); jclass vibratorInfoBuilderClass = FindClassOrDie(env, "android/os/VibratorInfo$Builder"); sVibratorInfoBuilderClassInfo.setCapabilities = @@ -517,9 +517,9 @@ int register_android_server_vibrator_VibratorController(JavaVM* jvm, JNIEnv* env sVibratorInfoBuilderClassInfo.setQFactor = GetMethodIDOrDie(env, vibratorInfoBuilderClass, "setQFactor", "(F)Landroid/os/VibratorInfo$Builder;"); - sVibratorInfoBuilderClassInfo.setFrequencyMapping = - GetMethodIDOrDie(env, vibratorInfoBuilderClass, "setFrequencyMapping", - "(Landroid/os/VibratorInfo$FrequencyMapping;)" + sVibratorInfoBuilderClassInfo.setFrequencyProfile = + GetMethodIDOrDie(env, vibratorInfoBuilderClass, "setFrequencyProfile", + "(Landroid/os/VibratorInfo$FrequencyProfile;)" "Landroid/os/VibratorInfo$Builder;"); return jniRegisterNativeMethods(env, diff --git a/core/jni/android_view_SurfaceControlFpsListener.cpp b/services/core/jni/com_android_server_wm_TaskFpsCallbackController.cpp index 0b15acd77689..0202306fc395 100644 --- a/core/jni/android_view_SurfaceControlFpsListener.cpp +++ b/services/core/jni/com_android_server_wm_TaskFpsCallbackController.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2021 The Android Open Source Project + * Copyright 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -#define LOG_TAG "SurfaceControlFpsListener" +#define LOG_TAG "TaskFpsCallbackController" #include <android/gui/BnFpsListener.h> #include <android_runtime/AndroidRuntime.h> @@ -35,11 +35,10 @@ namespace { struct { jclass mClass; jmethodID mDispatchOnFpsReported; -} gListenerClassInfo; +} gCallbackClassInfo; -struct SurfaceControlFpsListener : public gui::BnFpsListener { - SurfaceControlFpsListener(JNIEnv* env, jobject listener) - : mListener(env->NewWeakGlobalRef(listener)) {} +struct TaskFpsCallback : public gui::BnFpsListener { + TaskFpsCallback(JNIEnv* env, jobject listener) : mListener(env->NewWeakGlobalRef(listener)) {} binder::Status onFpsReported(float fps) override { JNIEnv* env = AndroidRuntime::getJNIEnv(); @@ -50,13 +49,13 @@ struct SurfaceControlFpsListener : public gui::BnFpsListener { // Weak reference went out of scope return binder::Status::ok(); } - env->CallStaticVoidMethod(gListenerClassInfo.mClass, - gListenerClassInfo.mDispatchOnFpsReported, listener, + env->CallStaticVoidMethod(gCallbackClassInfo.mClass, + gCallbackClassInfo.mDispatchOnFpsReported, listener, static_cast<jfloat>(fps)); env->DeleteGlobalRef(listener); if (env->ExceptionCheck()) { - ALOGE("SurfaceControlFpsListener.onFpsReported() failed."); + ALOGE("TaskFpsCallback.onFpsReported() failed."); LOGE_EX(env); env->ExceptionClear(); } @@ -64,7 +63,7 @@ struct SurfaceControlFpsListener : public gui::BnFpsListener { } protected: - virtual ~SurfaceControlFpsListener() { + virtual ~TaskFpsCallback() { JNIEnv* env = AndroidRuntime::getJNIEnv(); env->DeleteWeakGlobalRef(mListener); } @@ -73,55 +72,48 @@ private: jweak mListener; }; -jlong nativeCreate(JNIEnv* env, jclass clazz, jobject obj) { - SurfaceControlFpsListener* listener = new SurfaceControlFpsListener(env, obj); - listener->incStrong((void*)nativeCreate); - return reinterpret_cast<jlong>(listener); -} - -void nativeDestroy(JNIEnv* env, jclass clazz, jlong ptr) { - SurfaceControlFpsListener* listener = reinterpret_cast<SurfaceControlFpsListener*>(ptr); - listener->decStrong((void*)nativeCreate); -} +jlong nativeRegister(JNIEnv* env, jclass clazz, jobject obj, jint taskId) { + TaskFpsCallback* callback = new TaskFpsCallback(env, obj); -void nativeRegister(JNIEnv* env, jclass clazz, jlong ptr, jint taskId) { - sp<SurfaceControlFpsListener> listener = reinterpret_cast<SurfaceControlFpsListener*>(ptr); - if (SurfaceComposerClient::addFpsListener(taskId, listener) != OK) { + if (SurfaceComposerClient::addFpsListener(taskId, callback) != OK) { constexpr auto error_msg = "Couldn't addFpsListener"; ALOGE(error_msg); jniThrowRuntimeException(env, error_msg); } + callback->incStrong((void*)nativeRegister); + + return reinterpret_cast<jlong>(callback); } void nativeUnregister(JNIEnv* env, jclass clazz, jlong ptr) { - sp<SurfaceControlFpsListener> listener = reinterpret_cast<SurfaceControlFpsListener*>(ptr); + sp<TaskFpsCallback> callback = reinterpret_cast<TaskFpsCallback*>(ptr); - if (SurfaceComposerClient::removeFpsListener(listener) != OK) { + if (SurfaceComposerClient::removeFpsListener(callback) != OK) { constexpr auto error_msg = "Couldn't removeFpsListener"; ALOGE(error_msg); jniThrowRuntimeException(env, error_msg); } + + callback->decStrong((void*)nativeRegister); } -const JNINativeMethod gMethods[] = { +static const JNINativeMethod gMethods[] = { /* name, signature, funcPtr */ - {"nativeCreate", "(Landroid/view/SurfaceControlFpsListener;)J", (void*)nativeCreate}, - {"nativeDestroy", "(J)V", (void*)nativeDestroy}, - {"nativeRegister", "(JI)V", (void*)nativeRegister}, + {"nativeRegister", "(Landroid/window/IOnFpsCallbackListener;I)J", (void*)nativeRegister}, {"nativeUnregister", "(J)V", (void*)nativeUnregister}}; } // namespace -int register_android_view_SurfaceControlFpsListener(JNIEnv* env) { - int res = jniRegisterNativeMethods(env, "android/view/SurfaceControlFpsListener", gMethods, - NELEM(gMethods)); +int register_com_android_server_wm_TaskFpsCallbackController(JNIEnv* env) { + int res = jniRegisterNativeMethods(env, "com/android/server/wm/TaskFpsCallbackController", + gMethods, NELEM(gMethods)); LOG_ALWAYS_FATAL_IF(res < 0, "Unable to register native methods."); - jclass clazz = env->FindClass("android/view/SurfaceControlFpsListener"); - gListenerClassInfo.mClass = MakeGlobalRefOrDie(env, clazz); - gListenerClassInfo.mDispatchOnFpsReported = + jclass clazz = env->FindClass("android/window/TaskFpsCallback"); + gCallbackClassInfo.mClass = MakeGlobalRefOrDie(env, clazz); + gCallbackClassInfo.mDispatchOnFpsReported = env->GetStaticMethodID(clazz, "dispatchOnFpsReported", - "(Landroid/view/SurfaceControlFpsListener;F)V"); + "(Landroid/window/IOnFpsCallbackListener;F)V"); return 0; } diff --git a/services/core/jni/gnss/AGnssRil.cpp b/services/core/jni/gnss/AGnssRil.cpp new file mode 100644 index 000000000000..d760b4d2195e --- /dev/null +++ b/services/core/jni/gnss/AGnssRil.cpp @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Define LOG_TAG before <log/log.h> to overwrite the default value. +#define LOG_TAG "AGnssRilJni" + +#include "AGnssRil.h" + +#include "Utils.h" + +using android::hardware::gnss::IAGnssRil; +using IAGnssRil_V1_0 = android::hardware::gnss::V1_0::IAGnssRil; +using IAGnssRil_V2_0 = android::hardware::gnss::V2_0::IAGnssRil; + +namespace android::gnss { + +// Implementation of AGnssRil (AIDL HAL) + +AGnssRil::AGnssRil(const sp<IAGnssRil>& iAGnssRil) : mIAGnssRil(iAGnssRil) { + assert(mIAGnssRil != nullptr); +} + +jboolean AGnssRil::setCallback(const std::unique_ptr<AGnssRilCallback>& callback) { + auto status = mIAGnssRil->setCallback(callback->getAidl()); + return checkAidlStatus(status, "IAGnssRilAidl setCallback() failed."); +} + +jboolean AGnssRil::setSetId(jint type, const jstring& setid_string) { + JNIEnv* env = getJniEnv(); + ScopedJniString jniSetId{env, setid_string}; + auto status = mIAGnssRil->setSetId((IAGnssRil::SetIDType)type, jniSetId.c_str()); + return checkAidlStatus(status, "IAGnssRilAidl setSetId() failed."); +} + +jboolean AGnssRil::setRefLocation(jint type, jint mcc, jint mnc, jint lac, jlong cid, jint tac, + jint pcid, jint arfcn) { + IAGnssRil::AGnssRefLocation location; + location.type = static_cast<IAGnssRil::AGnssRefLocationType>(type); + + switch (location.type) { + case IAGnssRil::AGnssRefLocationType::GSM_CELLID: + case IAGnssRil::AGnssRefLocationType::UMTS_CELLID: + case IAGnssRil::AGnssRefLocationType::LTE_CELLID: + case IAGnssRil::AGnssRefLocationType::NR_CELLID: + location.cellID.mcc = mcc; + location.cellID.mnc = mnc; + location.cellID.lac = lac; + location.cellID.cid = cid; + location.cellID.tac = tac; + location.cellID.pcid = pcid; + location.cellID.arfcn = arfcn; + break; + default: + ALOGE("Unknown cellid (%s:%d).", __FUNCTION__, __LINE__); + return JNI_FALSE; + break; + } + + auto status = mIAGnssRil->setRefLocation(location); + return checkAidlStatus(status, "IAGnssRilAidl dataConnClosed() failed."); +} + +jboolean AGnssRil::updateNetworkState(jboolean connected, jint type, jboolean roaming, + jboolean available, const jstring& apn, jlong networkHandle, + jshort capabilities) { + JNIEnv* env = getJniEnv(); + ScopedJniString jniApn{env, apn}; + IAGnssRil::NetworkAttributes networkAttributes; + networkAttributes.networkHandle = static_cast<int64_t>(networkHandle), + networkAttributes.isConnected = static_cast<bool>(connected), + networkAttributes.capabilities = static_cast<int32_t>(capabilities), + networkAttributes.apn = jniApn.c_str(); + + auto result = mIAGnssRil->updateNetworkState(networkAttributes); + return checkAidlStatus(result, "IAGnssRilAidl updateNetworkState() failed."); +} + +// Implementation of AGnssRil_V1_0 + +AGnssRil_V1_0::AGnssRil_V1_0(const sp<IAGnssRil_V1_0>& iAGnssRil) : mAGnssRil_V1_0(iAGnssRil) { + assert(mIAGnssRil_V1_0 != nullptr); +} + +jboolean AGnssRil_V1_0::setCallback(const std::unique_ptr<AGnssRilCallback>& callback) { + auto result = mAGnssRil_V1_0->setCallback(callback->getV1_0()); + return checkHidlReturn(result, "IAGnssRil_V1_0 setCallback() failed."); +} + +jboolean AGnssRil_V1_0::setSetId(jint type, const jstring& setid_string) { + JNIEnv* env = getJniEnv(); + ScopedJniString jniSetId{env, setid_string}; + auto result = mAGnssRil_V1_0->setSetId((IAGnssRil_V1_0::SetIDType)type, jniSetId); + return checkHidlReturn(result, "IAGnssRil_V1_0 setSetId() failed."); +} + +jboolean AGnssRil_V1_0::setRefLocation(jint type, jint mcc, jint mnc, jint lac, jlong cid, jint, + jint, jint) { + IAGnssRil_V1_0::AGnssRefLocation location; + switch (static_cast<IAGnssRil_V1_0::AGnssRefLocationType>(type)) { + case IAGnssRil_V1_0::AGnssRefLocationType::GSM_CELLID: + case IAGnssRil_V1_0::AGnssRefLocationType::UMTS_CELLID: + location.type = static_cast<IAGnssRil_V1_0::AGnssRefLocationType>(type); + location.cellID.mcc = mcc; + location.cellID.mnc = mnc; + location.cellID.lac = lac; + location.cellID.cid = cid; + break; + default: + ALOGE("Neither a GSM nor a UMTS cellid (%s:%d).", __FUNCTION__, __LINE__); + return JNI_FALSE; + break; + } + + auto result = mAGnssRil_V1_0->setRefLocation(location); + return checkHidlReturn(result, "IAGnssRil_V1_0 setRefLocation() failed."); +} + +jboolean AGnssRil_V1_0::updateNetworkState(jboolean connected, jint type, jboolean roaming, + jboolean available, const jstring& apn, + jlong networkHandle, jshort capabilities) { + JNIEnv* env = getJniEnv(); + ScopedJniString jniApn{env, apn}; + hardware::hidl_string hidlApn{jniApn}; + hardware::Return<bool> result(false); + + if (!hidlApn.empty()) { + result = mAGnssRil_V1_0->updateNetworkAvailability(available, hidlApn); + checkHidlReturn(result, "IAGnssRil_V1_0 updateNetworkAvailability() failed."); + } + + result = mAGnssRil_V1_0->updateNetworkState(connected, + static_cast<IAGnssRil_V1_0::NetworkType>(type), + roaming); + return checkHidlReturn(result, "IAGnssRil_V1_0 updateNetworkState() failed."); +} + +// Implementation of AGnssRil_V2_0 + +AGnssRil_V2_0::AGnssRil_V2_0(const sp<IAGnssRil_V2_0>& iAGnssRil) + : AGnssRil_V1_0{iAGnssRil}, mAGnssRil_V2_0(iAGnssRil) { + assert(mIAGnssRil_V2_0 != nullptr); +} + +jboolean AGnssRil_V2_0::updateNetworkState(jboolean connected, jint type, jboolean roaming, + jboolean available, const jstring& apn, + jlong networkHandle, jshort capabilities) { + JNIEnv* env = getJniEnv(); + ScopedJniString jniApn{env, apn}; + IAGnssRil_V2_0::NetworkAttributes networkAttributes = + {.networkHandle = static_cast<uint64_t>(networkHandle), + .isConnected = static_cast<bool>(connected), + .capabilities = static_cast<uint16_t>(capabilities), + .apn = jniApn.c_str()}; + + auto result = mAGnssRil_V2_0->updateNetworkState_2_0(networkAttributes); + return checkHidlReturn(result, "AGnssRil_V2_0 updateNetworkState_2_0() failed."); +} + +} // namespace android::gnss diff --git a/services/core/jni/gnss/AGnssRil.h b/services/core/jni/gnss/AGnssRil.h new file mode 100644 index 000000000000..ce14a77d56c4 --- /dev/null +++ b/services/core/jni/gnss/AGnssRil.h @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _ANDROID_SERVER_GNSS_AGNSSRIL_H +#define _ANDROID_SERVER_GNSS_AGNSSRIL_H + +#pragma once + +#ifndef LOG_TAG +#error LOG_TAG must be defined before including this file. +#endif + +#include <android/hardware/gnss/1.0/IAGnssRil.h> +#include <android/hardware/gnss/2.0/IAGnssRil.h> +#include <android/hardware/gnss/BnAGnssRil.h> +#include <log/log.h> + +#include "AGnssRilCallback.h" +#include "jni.h" + +namespace android::gnss { + +class AGnssRilInterface { +public: + virtual ~AGnssRilInterface() {} + virtual jboolean setCallback(const std::unique_ptr<AGnssRilCallback>& callback) = 0; + virtual jboolean setSetId(jint type, const jstring& setid_string) = 0; + virtual jboolean setRefLocation(jint type, jint mcc, jint mnc, jint lac, jlong cid, jint tac, + jint pcid, jint arfcn) = 0; + virtual jboolean updateNetworkState(jboolean connected, jint type, jboolean roaming, + jboolean available, const jstring& apn, jlong networkHandle, + jshort capabilities) = 0; +}; + +class AGnssRil : public AGnssRilInterface { +public: + AGnssRil(const sp<android::hardware::gnss::IAGnssRil>& iAGnssRil); + jboolean setCallback(const std::unique_ptr<AGnssRilCallback>& callback) override; + jboolean setSetId(jint type, const jstring& setid_string) override; + jboolean setRefLocation(jint type, jint mcc, jint mnc, jint lac, jlong cid, jint tac, jint pcid, + jint arfcn) override; + jboolean updateNetworkState(jboolean connected, jint type, jboolean roaming, jboolean available, + const jstring& apn, jlong networkHandle, + jshort capabilities) override; + +private: + const sp<android::hardware::gnss::IAGnssRil> mIAGnssRil; +}; + +class AGnssRil_V1_0 : public AGnssRilInterface { +public: + AGnssRil_V1_0(const sp<android::hardware::gnss::V1_0::IAGnssRil>& iAGnssRil); + jboolean setCallback(const std::unique_ptr<AGnssRilCallback>& callback) override; + jboolean setSetId(jint type, const jstring& setid_string) override; + jboolean setRefLocation(jint type, jint mcc, jint mnc, jint lac, jlong cid, jint, jint, + jint) override; + jboolean updateNetworkState(jboolean connected, jint type, jboolean roaming, jboolean available, + const jstring& apn, jlong networkHandle, + jshort capabilities) override; + +private: + const sp<android::hardware::gnss::V1_0::IAGnssRil> mAGnssRil_V1_0; +}; + +class AGnssRil_V2_0 : public AGnssRil_V1_0 { +public: + AGnssRil_V2_0(const sp<android::hardware::gnss::V2_0::IAGnssRil>& iAGnssRil); + jboolean updateNetworkState(jboolean connected, jint type, jboolean roaming, jboolean available, + const jstring& apn, jlong networkHandle, + jshort capabilities) override; + +private: + const sp<android::hardware::gnss::V2_0::IAGnssRil> mAGnssRil_V2_0; +}; + +} // namespace android::gnss + +#endif // _ANDROID_SERVER_GNSS_AGNSSRIL_H diff --git a/services/core/jni/gnss/AGnssRilCallback.cpp b/services/core/jni/gnss/AGnssRilCallback.cpp new file mode 100644 index 000000000000..b63ccc281aa9 --- /dev/null +++ b/services/core/jni/gnss/AGnssRilCallback.cpp @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "AGnssRilCbJni" + +#include "AGnssRilCallback.h" + +namespace android::gnss { + +jmethodID method_requestSetID; +jmethodID method_requestRefLocation; + +using binder::Status; +using hardware::Return; +using hardware::Void; + +void AGnssRil_class_init_once(JNIEnv* env, jclass clazz) { + method_requestSetID = env->GetMethodID(clazz, "requestSetID", "(I)V"); + method_requestRefLocation = env->GetMethodID(clazz, "requestRefLocation", "()V"); +} + +Status AGnssRilCallbackAidl::requestSetIdCb(int setIdflag) { + AGnssRilCallbackUtil::requestSetIdCb(setIdflag); + return Status::ok(); +} + +Status AGnssRilCallbackAidl::requestRefLocCb() { + AGnssRilCallbackUtil::requestRefLocCb(); + return Status::ok(); +} + +Return<void> AGnssRilCallback_V1_0::requestSetIdCb(uint32_t setIdflag) { + AGnssRilCallbackUtil::requestSetIdCb(setIdflag); + return Void(); +} + +Return<void> AGnssRilCallback_V1_0::requestRefLocCb() { + AGnssRilCallbackUtil::requestRefLocCb(); + return Void(); +} + +void AGnssRilCallbackUtil::requestSetIdCb(int setIdflag) { + ALOGD("%s. setIdflag: %d, ", __func__, setIdflag); + JNIEnv* env = getJniEnv(); + env->CallVoidMethod(mCallbacksObj, method_requestSetID, setIdflag); + checkAndClearExceptionFromCallback(env, __FUNCTION__); +} + +void AGnssRilCallbackUtil::requestRefLocCb() { + ALOGD("%s.", __func__); + JNIEnv* env = getJniEnv(); + env->CallVoidMethod(mCallbacksObj, method_requestRefLocation); + checkAndClearExceptionFromCallback(env, __FUNCTION__); +} + +} // namespace android::gnss diff --git a/services/core/jni/gnss/AGnssRilCallback.h b/services/core/jni/gnss/AGnssRilCallback.h new file mode 100644 index 000000000000..2d12089fd6e3 --- /dev/null +++ b/services/core/jni/gnss/AGnssRilCallback.h @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _ANDROID_SERVER_GNSS_AGNSSRILCALLBACK_H +#define _ANDROID_SERVER_GNSS_AGNSSRILCALLBACK_H + +#pragma once + +#ifndef LOG_TAG +#error LOG_TAG must be defined before including this file. +#endif + +#include <android/hardware/gnss/1.0/IAGnssRil.h> +#include <android/hardware/gnss/BnAGnssRilCallback.h> +#include <log/log.h> + +#include "Utils.h" +#include "jni.h" + +namespace android::gnss { + +void AGnssRil_class_init_once(JNIEnv* env, jclass clazz); + +/* + * AGnssRilCallbackAidl class implements the callback methods required by the + * android::hardware::gnss::IAGnssRil interface. + */ +class AGnssRilCallbackAidl : public android::hardware::gnss::BnAGnssRilCallback { +public: + binder::Status requestSetIdCb(int setIdflag) override; + binder::Status requestRefLocCb() override; +}; + +/* + * AGnssRilCallback_V1_0 implements callback methods required by the IAGnssRilCallback 1.0 + * interface. + */ +class AGnssRilCallback_V1_0 : public android::hardware::gnss::V1_0::IAGnssRilCallback { +public: + // Methods from ::android::hardware::gps::V1_0::IAGnssRilCallback follow. + hardware::Return<void> requestSetIdCb(uint32_t setIdflag) override; + hardware::Return<void> requestRefLocCb() override; +}; + +class AGnssRilCallback { +public: + AGnssRilCallback() {} + sp<AGnssRilCallbackAidl> getAidl() { + if (callbackAidl == nullptr) { + callbackAidl = sp<AGnssRilCallbackAidl>::make(); + } + return callbackAidl; + } + + sp<AGnssRilCallback_V1_0> getV1_0() { + if (callbackV1_0 == nullptr) { + callbackV1_0 = sp<AGnssRilCallback_V1_0>::make(); + } + return callbackV1_0; + } + +private: + sp<AGnssRilCallbackAidl> callbackAidl; + sp<AGnssRilCallback_V1_0> callbackV1_0; +}; + +struct AGnssRilCallbackUtil { + static void requestSetIdCb(int setIdflag); + static void requestRefLocCb(); + +private: + AGnssRilCallbackUtil() = delete; +}; + +} // namespace android::gnss + +#endif // _ANDROID_SERVER_GNSS_AGNSSRILCALLBACK_H
\ No newline at end of file diff --git a/services/core/jni/gnss/Android.bp b/services/core/jni/gnss/Android.bp index 63f5f526db17..d8de5a604b3d 100644 --- a/services/core/jni/gnss/Android.bp +++ b/services/core/jni/gnss/Android.bp @@ -25,6 +25,8 @@ cc_library_shared { srcs: [ "AGnss.cpp", "AGnssCallback.cpp", + "AGnssRil.cpp", + "AGnssRilCallback.cpp", "GnssAntennaInfoCallback.cpp", "GnssBatching.cpp", "GnssBatchingCallback.cpp", diff --git a/services/core/jni/gnss/GnssBatching.cpp b/services/core/jni/gnss/GnssBatching.cpp index b66bf21381c7..7f936b9a510d 100644 --- a/services/core/jni/gnss/GnssBatching.cpp +++ b/services/core/jni/gnss/GnssBatching.cpp @@ -47,9 +47,12 @@ jint GnssBatching::getBatchSize() { return size; } -jboolean GnssBatching::start(long periodNanos, bool wakeOnFifoFull) { - int flags = (wakeOnFifoFull) ? IGnssBatching::WAKEUP_ON_FIFO_FULL : 0; - auto status = mIGnssBatching->start(periodNanos, flags); +jboolean GnssBatching::start(long periodNanos, float minUpdateDistanceMeters, bool wakeOnFifoFull) { + IGnssBatching::Options options; + options.flags = (wakeOnFifoFull) ? IGnssBatching::WAKEUP_ON_FIFO_FULL : 0; + options.periodNanos = periodNanos; + options.minDistanceMeters = minUpdateDistanceMeters; + auto status = mIGnssBatching->start(options); return checkAidlStatus(status, "IGnssBatchingAidl start() failed."); } @@ -88,9 +91,13 @@ jint GnssBatching_V1_0::getBatchSize() { return static_cast<jint>(result); } -jboolean GnssBatching_V1_0::start(long periodNanos, bool wakeOnFifoFull) { +jboolean GnssBatching_V1_0::start(long periodNanos, float minUpdateDistanceMeters, + bool wakeOnFifoFull) { IGnssBatching_V1_0::Options options; options.periodNanos = periodNanos; + if (minUpdateDistanceMeters > 0) { + ALOGW("minUpdateDistanceMeters is not supported in 1.0 GNSS HAL."); + } if (wakeOnFifoFull) { options.flags = static_cast<uint8_t>(IGnssBatching_V1_0::Flag::WAKEUP_ON_FIFO_FULL); } else { diff --git a/services/core/jni/gnss/GnssBatching.h b/services/core/jni/gnss/GnssBatching.h index a98ca9b0e492..eda02ce39551 100644 --- a/services/core/jni/gnss/GnssBatching.h +++ b/services/core/jni/gnss/GnssBatching.h @@ -38,7 +38,8 @@ public: virtual ~GnssBatchingInterface() {} virtual jboolean init(const std::unique_ptr<GnssBatchingCallback>& callback) = 0; virtual jint getBatchSize() = 0; - virtual jboolean start(long periodNanos, bool wakeupOnFifoFull) = 0; + virtual jboolean start(long periodNanos, float minUpdateDistanceMeters, + bool wakeupOnFifoFull) = 0; virtual jboolean stop() = 0; virtual jboolean flush() = 0; virtual jboolean cleanup() = 0; @@ -49,7 +50,7 @@ public: GnssBatching(const sp<android::hardware::gnss::IGnssBatching>& iGnssBatching); jboolean init(const std::unique_ptr<GnssBatchingCallback>& callback) override; jint getBatchSize() override; - jboolean start(long periodNanos, bool wakeupOnFifoFull) override; + jboolean start(long periodNanos, float minUpdateDistanceMeters, bool wakeupOnFifoFull) override; jboolean stop() override; jboolean flush() override; jboolean cleanup() override; @@ -63,7 +64,7 @@ public: GnssBatching_V1_0(const sp<android::hardware::gnss::V1_0::IGnssBatching>& iGnssBatching); jboolean init(const std::unique_ptr<GnssBatchingCallback>& callback) override; jint getBatchSize() override; - jboolean start(long periodNanos, bool wakeupOnFifoFull) override; + jboolean start(long periodNanos, float minUpdateDistanceMeters, bool wakeupOnFifoFull) override; jboolean stop() override; jboolean flush() override; jboolean cleanup() override; diff --git a/services/core/jni/gnss/GnssMeasurementCallback.cpp b/services/core/jni/gnss/GnssMeasurementCallback.cpp index 5a7cee9db5bb..fbdeec6b897e 100644 --- a/services/core/jni/gnss/GnssMeasurementCallback.cpp +++ b/services/core/jni/gnss/GnssMeasurementCallback.cpp @@ -366,6 +366,7 @@ void GnssMeasurementCallbackAidl::translateAndSetGnssData(const GnssData& data) env->DeleteLocalRef(clock); env->DeleteLocalRef(measurementArray); + env->DeleteLocalRef(gnssAgcArray); } void GnssMeasurementCallbackAidl::translateSingleGnssMeasurement(JNIEnv* env, diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp index d339ef1154c5..ba5b3f54efa1 100644 --- a/services/core/jni/onload.cpp +++ b/services/core/jni/onload.cpp @@ -64,6 +64,8 @@ int register_android_server_GpuService(JNIEnv* env); int register_android_server_stats_pull_StatsPullAtomService(JNIEnv* env); int register_android_server_sensor_SensorService(JavaVM* vm, JNIEnv* env); int register_android_server_companion_virtual_InputController(JNIEnv* env); +int register_android_server_app_GameManagerService(JNIEnv* env); +int register_com_android_server_wm_TaskFpsCallbackController(JNIEnv* env); }; using namespace android; @@ -121,5 +123,7 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) register_android_server_stats_pull_StatsPullAtomService(env); register_android_server_sensor_SensorService(vm, env); register_android_server_companion_virtual_InputController(env); + register_android_server_app_GameManagerService(env); + register_com_android_server_wm_TaskFpsCallbackController(env); return JNI_VERSION_1_4; } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java index df9ab5003122..f19202aff2f0 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java @@ -145,6 +145,10 @@ class ActiveAdmin { private static final String TAG_PREFERENTIAL_NETWORK_SERVICE_ENABLED = "preferential-network-service-enabled"; private static final String TAG_USB_DATA_SIGNALING = "usb-data-signaling"; + private static final String TAG_WIFI_MIN_SECURITY = "wifi-min-security"; + private static final String TAG_SSID_ALLOWLIST = "ssid-allowlist"; + private static final String TAG_SSID_DENYLIST = "ssid-denylist"; + private static final String TAG_SSID = "ssid"; private static final String ATTR_VALUE = "value"; private static final String ATTR_LAST_NETWORK_LOGGING_NOTIFICATION = "last-notification"; private static final String ATTR_NUM_NETWORK_LOGGING_NOTIFICATIONS = "num-notifications"; @@ -237,6 +241,14 @@ class ActiveAdmin { // List of package names to keep cached. List<String> keepUninstalledPackages; + // The allowlist of SSIDs the device may connect to. + // By default, the allowlist restriction is deactivated. + List<String> mSsidAllowlist; + + // The denylist of SSIDs the device may not connect to. + // By default, the denylist restriction is deactivated. + List<String> mSsidDenylist; + // TODO: review implementation decisions with frameworks team boolean specifiesGlobalProxy = false; String globalProxySpec = null; @@ -298,6 +310,8 @@ class ActiveAdmin { private static final boolean USB_DATA_SIGNALING_ENABLED_DEFAULT = true; boolean mUsbDataSignalingEnabled = USB_DATA_SIGNALING_ENABLED_DEFAULT; + int mWifiMinimumSecurityLevel = DevicePolicyManager.WIFI_SECURITY_OPEN; + ActiveAdmin(DeviceAdminInfo info, boolean isParent) { this.info = info; this.isParent = isParent; @@ -574,6 +588,15 @@ class ActiveAdmin { if (mUsbDataSignalingEnabled != USB_DATA_SIGNALING_ENABLED_DEFAULT) { writeAttributeValueToXml(out, TAG_USB_DATA_SIGNALING, mUsbDataSignalingEnabled); } + if (mWifiMinimumSecurityLevel != DevicePolicyManager.WIFI_SECURITY_OPEN) { + writeAttributeValueToXml(out, TAG_WIFI_MIN_SECURITY, mWifiMinimumSecurityLevel); + } + if (mSsidAllowlist != null && !mSsidAllowlist.isEmpty()) { + writeAttributeValuesToXml(out, TAG_SSID_ALLOWLIST, TAG_SSID, mSsidAllowlist); + } + if (mSsidDenylist != null && !mSsidDenylist.isEmpty()) { + writeAttributeValuesToXml(out, TAG_SSID_DENYLIST, TAG_SSID, mSsidDenylist); + } } void writeTextToXml(TypedXmlSerializer out, String tag, String text) throws IOException { @@ -826,6 +849,14 @@ class ActiveAdmin { } else if (TAG_USB_DATA_SIGNALING.equals(tag)) { mUsbDataSignalingEnabled = parser.getAttributeBoolean(null, ATTR_VALUE, USB_DATA_SIGNALING_ENABLED_DEFAULT); + } else if (TAG_WIFI_MIN_SECURITY.equals(tag)) { + mWifiMinimumSecurityLevel = parser.getAttributeInt(null, ATTR_VALUE); + } else if (TAG_SSID_ALLOWLIST.equals(tag)) { + mSsidAllowlist = new ArrayList<>(); + readAttributeValues(parser, TAG_SSID, mSsidAllowlist); + } else if (TAG_SSID_DENYLIST.equals(tag)) { + mSsidDenylist = new ArrayList<>(); + readAttributeValues(parser, TAG_SSID, mSsidDenylist); } else { Slogf.w(LOG_TAG, "Unknown admin tag: %s", tag); XmlUtils.skipCurrentTag(parser); @@ -1184,5 +1215,14 @@ class ActiveAdmin { pw.print("mUsbDataSignaling="); pw.println(mUsbDataSignalingEnabled); + + pw.print("mWifiMinimumSecurityLevel="); + pw.println(mWifiMinimumSecurityLevel); + + pw.print("mSsidAllowlist="); + pw.println(mSsidAllowlist); + + pw.print("mSsidDenylist="); + pw.println(mSsidDenylist); } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java index ebe9f9327032..9b87b9d6104b 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java @@ -19,6 +19,7 @@ import android.annotation.NonNull; import android.annotation.UserIdInt; import android.app.admin.DevicePolicyDrawableResource; import android.app.admin.DevicePolicySafetyChecker; +import android.app.admin.DevicePolicyStringResource; import android.app.admin.FullyManagedDeviceProvisioningParams; import android.app.admin.IDevicePolicyManager; import android.app.admin.ManagedProfileProvisioningParams; @@ -177,4 +178,15 @@ abstract class BaseIDevicePolicyManager extends IDevicePolicyManager.Stub { int drawableId, int drawableStyle, int drawableSource) { return null; } + + @Override + public void setStrings(@NonNull List<DevicePolicyStringResource> strings){} + + @Override + public void resetStrings(String[] stringIds){} + + @Override + public ParcelableResource getString(String stringId) { + return null; + } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DeviceManagementResourcesProvider.java b/services/devicepolicy/java/com/android/server/devicepolicy/DeviceManagementResourcesProvider.java index 534229402888..9a982357afca 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DeviceManagementResourcesProvider.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DeviceManagementResourcesProvider.java @@ -20,6 +20,7 @@ import static android.app.admin.DevicePolicyResources.Drawable.Source.UPDATABLE_ import static android.app.admin.DevicePolicyResources.Drawable.Style; import static android.app.admin.DevicePolicyResources.Drawable.Style.UPDATABLE_DRAWABLE_STYLES; import static android.app.admin.DevicePolicyResources.Drawable.UPDATABLE_DRAWABLE_IDS; +import static android.app.admin.DevicePolicyResources.Strings.UPDATABLE_STRING_IDS; import static java.util.Objects.requireNonNull; @@ -27,6 +28,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.admin.DevicePolicyDrawableResource; import android.app.admin.DevicePolicyResources; +import android.app.admin.DevicePolicyStringResource; import android.app.admin.ParcelableResource; import android.os.Environment; import android.util.AtomicFile; @@ -64,14 +66,26 @@ class DeviceManagementResourcesProvider { private static final String ATTR_DRAWABLE_STYLE = "drawable-style"; private static final String ATTR_DRAWABLE_SOURCE = "drawable-source"; private static final String ATTR_DRAWABLE_ID = "drawable-id"; + private static final String TAG_STRING_ENTRY = "string-entry"; + private static final String ATTR_SOURCE_ID = "source-id"; - + /** + * Map of <drawable_id, <style_id, resource_value>> + */ private final Map<Integer, Map<Integer, ParcelableResource>> mUpdatedDrawablesForStyle = new HashMap<>(); + /** + * Map of <drawable_id, <source_id, resource_value>> + */ private final Map<Integer, Map<Integer, ParcelableResource>> mUpdatedDrawablesForSource = new HashMap<>(); + /** + * Map of <string_id, resource_value> + */ + private final Map<String, ParcelableResource> mUpdatedStrings = new HashMap<>(); + private final Object mLock = new Object(); private final Injector mInjector; @@ -114,12 +128,10 @@ class DeviceManagementResourcesProvider { private boolean updateDrawable( int drawableId, int drawableStyle, ParcelableResource updatableResource) { if (!UPDATABLE_DRAWABLE_IDS.contains(drawableId)) { - throw new IllegalArgumentException( - "Can't update drawable resource, invalid drawable " + "id " + drawableId); + Log.w(TAG, "Updating a resource for an unknown drawable id " + drawableId); } if (!UPDATABLE_DRAWABLE_STYLES.contains(drawableStyle)) { - throw new IllegalArgumentException( - "Can't update drawable resource, invalid style id " + drawableStyle); + Log.w(TAG, "Updating a resource for an unknown style id " + drawableStyle); } synchronized (mLock) { if (!mUpdatedDrawablesForStyle.containsKey(drawableId)) { @@ -139,12 +151,10 @@ class DeviceManagementResourcesProvider { private boolean updateDrawableForSource( int drawableId, int drawableSource, ParcelableResource updatableResource) { if (!UPDATABLE_DRAWABLE_IDS.contains(drawableId)) { - throw new IllegalArgumentException("Can't update drawable resource, invalid drawable " - + "id " + drawableId); + Log.w(TAG, "Updating a resource for an unknown drawable id " + drawableId); } if (!UPDATABLE_DRAWABLE_SOURCES.contains(drawableSource)) { - throw new IllegalArgumentException("Can't update drawable resource, invalid source id " - + drawableSource); + Log.w(TAG, "Updating a resource for an unknown source id " + drawableSource); } synchronized (mLock) { if (!mUpdatedDrawablesForSource.containsKey(drawableId)) { @@ -183,19 +193,15 @@ class DeviceManagementResourcesProvider { ParcelableResource getDrawable( int drawableId, int drawableStyle, int drawableSource) { if (!UPDATABLE_DRAWABLE_IDS.contains(drawableId)) { - Log.e(TAG, "Can't get updated drawable resource, invalid drawable id " - + drawableId); - return null; + Log.w(TAG, "Getting an updated resource for an unknown drawable id " + drawableId); } if (!UPDATABLE_DRAWABLE_STYLES.contains(drawableStyle)) { - Log.e(TAG, "Can't get updated drawable resource, invalid style id " + Log.w(TAG, "Getting an updated resource for an unknown drawable style " + drawableStyle); - return null; } if (!UPDATABLE_DRAWABLE_SOURCES.contains(drawableSource)) { - Log.e(TAG, "Can't get updated drawable resource, invalid source id " + Log.w(TAG, "Getting an updated resource for an unknown drawable Source " + drawableSource); - return null; } if (mUpdatedDrawablesForSource.containsKey(drawableId) && mUpdatedDrawablesForSource.get(drawableId).containsKey(drawableSource)) { @@ -216,6 +222,73 @@ class DeviceManagementResourcesProvider { return null; } + /** + * Returns {@code false} if no resources were updated. + */ + boolean updateStrings(@NonNull List<DevicePolicyStringResource> strings) { + boolean updated = false; + for (int i = 0; i < strings.size(); i++) { + String stringId = strings.get(i).getStringId(); + ParcelableResource resource = strings.get(i).getResource(); + + Objects.requireNonNull(resource, "ParcelableResource must be provided."); + updated |= updateString(stringId, resource); + } + if (!updated) { + return false; + } + synchronized (mLock) { + write(); + return true; + } + } + + private boolean updateString(String stringId, ParcelableResource updatableResource) { + if (!UPDATABLE_STRING_IDS.contains(stringId)) { + Log.w(TAG, "Updating a resource for an unknown string id " + stringId); + } + synchronized (mLock) { + ParcelableResource current = mUpdatedStrings.get(stringId); + if (updatableResource.equals(current)) { + return false; + } + mUpdatedStrings.put(stringId, updatableResource); + return true; + } + } + + /** + * Returns {@code false} if no resources were removed. + */ + boolean removeStrings(@NonNull String[] stringIds) { + synchronized (mLock) { + boolean removed = false; + for (int i = 0; i < stringIds.length; i++) { + String stringId = stringIds[i]; + removed |= mUpdatedStrings.remove(stringId) != null; + } + if (!removed) { + return false; + } + write(); + return true; + } + } + + @Nullable + ParcelableResource getString(String stringId) { + if (!UPDATABLE_STRING_IDS.contains(stringId)) { + Log.w(TAG, "Getting an updated resource for an unknown string id " + stringId); + } + + if (mUpdatedStrings.containsKey(stringId)) { + return mUpdatedStrings.get(stringId); + } + + Log.d(TAG, "No updated string found for string id " + stringId); + return null; + } + private void write() { Log.d(TAG, "Writing updated resources to file."); new ResourcesReaderWriter().writeToFileLocked(); @@ -362,6 +435,18 @@ class DeviceManagementResourcesProvider { out.endTag(/* namespace= */ null, TAG_DRAWABLE_SOURCE_ENTRY); } } + if (mUpdatedStrings != null && !mUpdatedStrings.isEmpty()) { + for (Map.Entry<String, ParcelableResource> entry + : mUpdatedStrings.entrySet()) { + out.startTag(/* namespace= */ null, TAG_STRING_ENTRY); + out.attribute( + /* namespace= */ null, + ATTR_SOURCE_ID, + entry.getKey()); + entry.getValue().writeToXmlFile(out); + out.endTag(/* namespace= */ null, TAG_STRING_ENTRY); + } + } } private boolean readInner( @@ -401,6 +486,12 @@ class DeviceManagementResourcesProvider { ParcelableResource.createFromXml(parser)); } break; + case TAG_STRING_ENTRY: + String sourceId = parser.getAttributeValue( + /* namespace= */ null, ATTR_SOURCE_ID); + mUpdatedStrings.put( + sourceId, ParcelableResource.createFromXml(parser)); + break; default: Log.e(TAG, "Unexpected tag: " + tag); return false; diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 0f15db182981..40196dbbd8b5 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -59,6 +59,7 @@ import static android.app.admin.DevicePolicyManager.DELEGATION_SECURITY_LOGGING; import static android.app.admin.DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE_PER_USER; import static android.app.admin.DevicePolicyManager.EXTRA_RESOURCE_ID; import static android.app.admin.DevicePolicyManager.EXTRA_RESOURCE_TYPE_DRAWABLE; +import static android.app.admin.DevicePolicyManager.EXTRA_RESOURCE_TYPE_STRING; import static android.app.admin.DevicePolicyManager.ID_TYPE_BASE_INFO; import static android.app.admin.DevicePolicyManager.ID_TYPE_IMEI; import static android.app.admin.DevicePolicyManager.ID_TYPE_INDIVIDUAL_ATTESTATION; @@ -99,6 +100,18 @@ import static android.app.admin.DevicePolicyManager.WIPE_EUICC; import static android.app.admin.DevicePolicyManager.WIPE_EXTERNAL_STORAGE; import static android.app.admin.DevicePolicyManager.WIPE_RESET_PROTECTION_DATA; import static android.app.admin.DevicePolicyManager.WIPE_SILENTLY; +import static android.app.admin.DevicePolicyResources.Strings.Core.LOCATION_CHANGED_MESSAGE; +import static android.app.admin.DevicePolicyResources.Strings.Core.LOCATION_CHANGED_TITLE; +import static android.app.admin.DevicePolicyResources.Strings.Core.NETWORK_LOGGING_MESSAGE; +import static android.app.admin.DevicePolicyResources.Strings.Core.NETWORK_LOGGING_TITLE; +import static android.app.admin.DevicePolicyResources.Strings.Core.NOTIFICATION_WORK_PROFILE_CONTENT_DESCRIPTION; +import static android.app.admin.DevicePolicyResources.Strings.Core.PERSONAL_APP_SUSPENSION_TITLE; +import static android.app.admin.DevicePolicyResources.Strings.Core.PERSONAL_APP_SUSPENSION_TURN_ON_PROFILE; +import static android.app.admin.DevicePolicyResources.Strings.Core.PRINTING_DISABLED_NAMED_ADMIN; +import static android.app.admin.DevicePolicyResources.Strings.Core.WORK_PROFILE_DELETED_FAILED_PASSWORD_ATTEMPTS_MESSAGE; +import static android.app.admin.DevicePolicyResources.Strings.Core.WORK_PROFILE_DELETED_GENERIC_MESSAGE; +import static android.app.admin.DevicePolicyResources.Strings.Core.WORK_PROFILE_DELETED_ORG_OWNED_MESSAGE; +import static android.app.admin.DevicePolicyResources.Strings.Core.WORK_PROFILE_DELETED_TITLE; import static android.app.admin.ProvisioningException.ERROR_ADMIN_PACKAGE_INSTALLATION_FAILED; import static android.app.admin.ProvisioningException.ERROR_PRE_CONDITION_FAILED; import static android.app.admin.ProvisioningException.ERROR_PROFILE_CREATION_FAILED; @@ -112,6 +125,7 @@ import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE; import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES; import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_DEFAULT; import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE; +import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_1; import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK; import static android.provider.Settings.Global.PRIVATE_DNS_SPECIFIER; import static android.provider.Settings.Secure.USER_SETUP_COMPLETE; @@ -175,6 +189,7 @@ import android.app.admin.DevicePolicyManager.PersonalAppsSuspensionReason; import android.app.admin.DevicePolicyManagerInternal; import android.app.admin.DevicePolicyManagerLiteInternal; import android.app.admin.DevicePolicySafetyChecker; +import android.app.admin.DevicePolicyStringResource; import android.app.admin.DeviceStateCache; import android.app.admin.FactoryResetProtectionPolicy; import android.app.admin.FullyManagedDeviceProvisioningParams; @@ -2234,7 +2249,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { * a managed profile. */ @GuardedBy("getLockObject()") - private void applyManagedProfileRestrictionIfDeviceOwnerLocked() { + private void applyProfileRestrictionsIfDeviceOwnerLocked() { final int doUserId = mOwners.getDeviceOwnerUserId(); if (doUserId == UserHandle.USER_NULL) { if (VERBOSE_LOG) Slogf.d(LOG_TAG, "No DO found, skipping application of restriction."); @@ -2242,7 +2257,17 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } final UserHandle doUserHandle = UserHandle.of(doUserId); - // Set the restriction if not set. + + // Based on CDD : https://source.android.com/compatibility/12/android-12-cdd#95_multi-user_support, + // creation of clone profile is not allowed in case device owner is set. + // Enforcing this restriction on setting up of device owner. + if (!mUserManager.hasUserRestriction( + UserManager.DISALLOW_ADD_CLONE_PROFILE, doUserHandle)) { + mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_CLONE_PROFILE, true, + doUserHandle); + } + // Creation of managed profile is restricted in case device owner is set, enforcing this + // restriction by setting user level restriction at time of device owner setup. if (!mUserManager.hasUserRestriction( UserManager.DISALLOW_ADD_MANAGED_PROFILE, doUserHandle)) { mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_MANAGED_PROFILE, true, @@ -3151,7 +3176,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { case SystemService.PHASE_ACTIVITY_MANAGER_READY: synchronized (getLockObject()) { migrateToProfileOnOrganizationOwnedDeviceIfCompLocked(); - applyManagedProfileRestrictionIfDeviceOwnerLocked(); + applyProfileRestrictionsIfDeviceOwnerLocked(); } maybeStartSecurityLogMonitorOnActivityManagerReady(); break; @@ -3776,6 +3801,12 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_MANAGED_PROFILE, false, userHandle); } + // When a device owner is set, the system automatically restricts adding a clone profile. + // Remove this restriction when the device owner is cleared. + if (mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_CLONE_PROFILE, userHandle)) { + mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_CLONE_PROFILE, false, + userHandle); + } } /** @@ -6927,12 +6958,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_WIPE_DATA); if (TextUtils.isEmpty(wipeReasonForUser)) { - if (calledByProfileOwnerOnOrgOwnedDevice && !calledOnParentInstance) { - wipeReasonForUser = mContext.getString(R.string.device_ownership_relinquished); - } else { - wipeReasonForUser = mContext.getString( - R.string.work_profile_deleted_description_dpm_wipe); - } + wipeReasonForUser = getGenericWipeReason( + calledByProfileOwnerOnOrgOwnedDevice, calledOnParentInstance); } int userId = admin != null ? admin.getUserHandle().getIdentifier() @@ -6983,6 +7010,18 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { wipeDataNoLock(adminComp, flags, internalReason, wipeReasonForUser, userId); } + private String getGenericWipeReason( + boolean calledByProfileOwnerOnOrgOwnedDevice, boolean calledOnParentInstance) { + DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class); + return calledByProfileOwnerOnOrgOwnedDevice && !calledOnParentInstance + ? dpm.getString(WORK_PROFILE_DELETED_ORG_OWNED_MESSAGE, + () -> mContext.getString( + R.string.device_ownership_relinquished)) + : dpm.getString(WORK_PROFILE_DELETED_GENERIC_MESSAGE, + () -> mContext.getString( + R.string.work_profile_deleted_description_dpm_wipe)); + } + /** * Clears device wide policies enforced by COPE PO when relinquishing the device. This method * should be invoked once the admin is gone, so that all methods that rely on calculating @@ -7067,7 +7106,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { Notification notification = new Notification.Builder(mContext, SystemNotificationChannels.DEVICE_ADMIN) .setSmallIcon(android.R.drawable.stat_sys_warning) - .setContentTitle(mContext.getString(R.string.work_profile_deleted)) + .setContentTitle(getWorkProfileDeletedTitle()) .setContentText(wipeReasonForUser) .setColor(mContext.getColor(R.color.system_notification_accent_color)) .setStyle(new Notification.BigTextStyle().bigText(wipeReasonForUser)) @@ -7075,6 +7114,12 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { mInjector.getNotificationManager().notify(SystemMessage.NOTE_PROFILE_WIPED, notification); } + private String getWorkProfileDeletedTitle() { + DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class); + return dpm.getString(WORK_PROFILE_DELETED_TITLE, + () -> mContext.getString(R.string.work_profile_deleted)); + } + private void clearWipeProfileNotification() { mInjector.getNotificationManager().cancel(SystemMessage.NOTE_PROFILE_WIPED); } @@ -7305,12 +7350,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { // able to do so). // IMPORTANT: Call without holding the lock to prevent deadlock. try { - String wipeReasonForUser = mContext.getString( - R.string.work_profile_deleted_reason_maximum_password_failure); wipeDataNoLock(strictestAdmin.info.getComponent(), /*flags=*/ 0, /*reason=*/ "reportFailedPasswordAttempt()", - wipeReasonForUser, + getFailedPasswordAttemptWipeMessage(), userId); } catch (SecurityException e) { Slogf.w(LOG_TAG, "Failed to wipe user " + userId @@ -7324,6 +7367,13 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } } + private String getFailedPasswordAttemptWipeMessage() { + DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class); + return dpm.getString(WORK_PROFILE_DELETED_FAILED_PASSWORD_ATTEMPTS_MESSAGE, + () -> mContext.getString( + R.string.work_profile_deleted_reason_maximum_password_failure)); + } + /** * Returns which user should be wiped if this admin's maximum filed password attempts policy is * violated. @@ -8468,6 +8518,12 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { // on the primary profile). mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_MANAGED_PROFILE, true, UserHandle.of(userId)); + // Restrict adding a clone profile when a device owner is set on the device. + // That is to prevent the co-existence of a clone profile and a device owner + // on the same device. + // CDD for reference : https://source.android.com/compatibility/12/android-12-cdd#95_multi-user_support + mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_CLONE_PROFILE, true, + UserHandle.of(userId)); // TODO Send to system too? sendOwnerChangedBroadcast(DevicePolicyManager.ACTION_DEVICE_OWNER_CHANGED, userId); }); @@ -8940,7 +8996,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { mOwners.writeProfileOwner(userId); deleteTransferOwnershipBundleLocked(userId); toggleBackupServiceActive(userId, true); - applyManagedProfileRestrictionIfDeviceOwnerLocked(); + applyProfileRestrictionsIfDeviceOwnerLocked(); setNetworkLoggingActiveInternal(false); } @@ -12395,8 +12451,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { Notification notification = new Notification.Builder(mContext, SystemNotificationChannels.DEVICE_ADMIN) .setSmallIcon(R.drawable.ic_info_outline) - .setContentTitle(mContext.getString(R.string.location_changed_notification_title)) - .setContentText(mContext.getString(R.string.location_changed_notification_text)) + .setContentTitle(getLocationChangedTitle()) + .setContentText(getLocationChangedText()) .setColor(mContext.getColor(R.color.system_notification_accent_color)) .setShowWhen(true) .setContentIntent(locationSettingsIntent) @@ -12406,6 +12462,18 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { notification); } + private String getLocationChangedTitle() { + DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class); + return dpm.getString(LOCATION_CHANGED_TITLE, + () -> mContext.getString(R.string.location_changed_notification_title)); + } + + private String getLocationChangedText() { + DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class); + return dpm.getString(LOCATION_CHANGED_MESSAGE, + () -> mContext.getString(R.string.location_changed_notification_text)); + } + @Override public boolean setTime(ComponentName who, long millis) { Objects.requireNonNull(who, "ComponentName is null"); @@ -12996,11 +13064,19 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { Slogf.e(LOG_TAG, "appLabel is inexplicably null"); return null; } - return ((Context) ActivityThread.currentActivityThread().getSystemUiContext()) - .getResources().getString(R.string.printing_disabled_by, appLabel); + DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class); + return dpm.getString( + PRINTING_DISABLED_NAMED_ADMIN, + () -> getDefaultPrintingDisabledMsg(appLabel), + appLabel); } } + private String getDefaultPrintingDisabledMsg(CharSequence appLabel) { + return ((Context) ActivityThread.currentActivityThread().getSystemUiContext()) + .getResources().getString(R.string.printing_disabled_by, appLabel); + } + @Override protected DevicePolicyCache getDevicePolicyCache() { return mPolicyCache; @@ -15532,16 +15608,18 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { // Simple notification clicks are immutable final PendingIntent pendingIntent = PendingIntent.getBroadcastAsUser(mContext, 0, intent, PendingIntent.FLAG_IMMUTABLE, UserHandle.CURRENT); + + final String title = getNetworkLoggingTitle(); + final String text = getNetworkLoggingText(); Notification notification = new Notification.Builder(mContext, SystemNotificationChannels.DEVICE_ADMIN) .setSmallIcon(R.drawable.ic_info_outline) - .setContentTitle(mContext.getString(R.string.network_logging_notification_title)) - .setContentText(mContext.getString(R.string.network_logging_notification_text)) - .setTicker(mContext.getString(R.string.network_logging_notification_title)) + .setContentTitle(title) + .setContentText(text) + .setTicker(title) .setShowWhen(true) .setContentIntent(pendingIntent) - .setStyle(new Notification.BigTextStyle() - .bigText(mContext.getString(R.string.network_logging_notification_text))) + .setStyle(new Notification.BigTextStyle().bigText(text)) .build(); Slogf.i(LOG_TAG, "Sending network logging notification to user %d", mNetworkLoggingNotificationUserId); @@ -15550,6 +15628,18 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { UserHandle.of(mNetworkLoggingNotificationUserId)); } + private String getNetworkLoggingTitle() { + DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class); + return dpm.getString(NETWORK_LOGGING_TITLE, + () -> mContext.getString(R.string.network_logging_notification_title)); + } + + private String getNetworkLoggingText() { + DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class); + return dpm.getString(NETWORK_LOGGING_MESSAGE, + () -> mContext.getString(R.string.network_logging_notification_text)); + } + private void handleCancelNetworkLoggingNotification() { if (mNetworkLoggingNotificationUserId == UserHandle.USER_NULL) { // Happens when setNetworkLoggingActive(false) is called before called with true @@ -17042,10 +17132,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { 0 /* requestCode */, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); - final String buttonText = - mContext.getString(R.string.personal_apps_suspended_turn_profile_on); - final Notification.Action turnProfileOnButton = - new Notification.Action.Builder(null /* icon */, buttonText, pendingIntent).build(); + final Notification.Action turnProfileOnButton = new Notification.Action.Builder( + /* icon= */ null, getPersonalAppSuspensionButtonText(), pendingIntent).build(); final String text; final boolean ongoing; @@ -17057,26 +17145,24 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { mContext, profileOwner.mProfileOffDeadline, DateUtils.FORMAT_SHOW_DATE); final String time = DateUtils.formatDateTime( mContext, profileOwner.mProfileOffDeadline, DateUtils.FORMAT_SHOW_TIME); - text = mContext.getString( - R.string.personal_apps_suspension_soon_text, date, time, maxDays); + text = getPersonalAppSuspensionSoonText(date, time, maxDays); ongoing = false; } else { - text = mContext.getString(R.string.personal_apps_suspension_text); + text = getPersonalAppSuspensionText(); ongoing = true; } final int color = mContext.getColor(R.color.personal_apps_suspension_notification_color); final Bundle extras = new Bundle(); // TODO: Create a separate string for this. - extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME, - mContext.getString(R.string.notification_work_profile_content_description)); + extras.putString( + Notification.EXTRA_SUBSTITUTE_APP_NAME, getWorkProfileContentDescription()); final Notification notification = new Notification.Builder(mContext, SystemNotificationChannels.DEVICE_ADMIN) .setSmallIcon(R.drawable.ic_corp_badge_no_background) .setOngoing(ongoing) .setAutoCancel(false) - .setContentTitle(mContext.getString( - R.string.personal_apps_suspension_title)) + .setContentTitle(getPersonalAppSuspensionTitle()) .setContentText(text) .setStyle(new Notification.BigTextStyle().bigText(text)) .setColor(color) @@ -17087,6 +17173,38 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { SystemMessage.NOTE_PERSONAL_APPS_SUSPENDED, notification); } + private String getPersonalAppSuspensionButtonText() { + DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class); + return dpm.getString(PERSONAL_APP_SUSPENSION_TURN_ON_PROFILE, + () -> mContext.getString(R.string.personal_apps_suspended_turn_profile_on)); + } + + private String getPersonalAppSuspensionTitle() { + DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class); + return dpm.getString(PERSONAL_APP_SUSPENSION_TITLE, + () -> mContext.getString(R.string.personal_apps_suspension_title)); + } + + private String getPersonalAppSuspensionText() { + DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class); + return dpm.getString(PERSONAL_APP_SUSPENSION_TITLE, + () -> mContext.getString(R.string.personal_apps_suspension_text)); + } + + private String getPersonalAppSuspensionSoonText(String date, String time, int maxDays) { + DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class); + return dpm.getString(PERSONAL_APP_SUSPENSION_TITLE, + () -> mContext.getString( + R.string.personal_apps_suspension_soon_text, date, time, maxDays), + date, time, maxDays); + } + + private String getWorkProfileContentDescription() { + DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class); + return dpm.getString(NOTIFICATION_WORK_PROFILE_CONTENT_DESCRIPTION, + () -> mContext.getString(R.string.notification_work_profile_content_description)); + } + @Override public void setManagedProfileMaximumTimeOff(ComponentName who, long timeoutMillis) { Objects.requireNonNull(who, "ComponentName is null"); @@ -17850,7 +17968,12 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { ? PROFILE_NETWORK_PREFERENCE_ENTERPRISE : PROFILE_NETWORK_PREFERENCE_DEFAULT; ProfileNetworkPreference.Builder preferenceBuilder = new ProfileNetworkPreference.Builder(); - preferenceBuilder.setPreference(networkPreference); + if (preferentialNetworkServiceEnabled) { + preferenceBuilder.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE); + preferenceBuilder.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1); + } else { + preferenceBuilder.setPreference(PROFILE_NETWORK_PREFERENCE_DEFAULT); + } List<ProfileNetworkPreference> preferences = new ArrayList<>(); preferences.add(preferenceBuilder.build()); mInjector.binderWithCleanCallingIdentity(() -> @@ -17977,6 +18100,117 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { ); } + private void validateCurrentWifiMeetsAdminRequirements() { + mInjector.binderWithCleanCallingIdentity( + () -> mInjector.getWifiManager().validateCurrentWifiMeetsAdminRequirements()); + } + + @Override + public void setMinimumRequiredWifiSecurityLevel(int level) { + final CallerIdentity caller = getCallerIdentity(); + Preconditions.checkCallAuthorization( + isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller), + "Wi-Fi minimum security level can only be controlled by a device owner or " + + "a profile owner on an organization-owned device."); + + boolean valueChanged = false; + synchronized (getLockObject()) { + final ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller); + if (admin.mWifiMinimumSecurityLevel != level) { + admin.mWifiMinimumSecurityLevel = level; + saveSettingsLocked(caller.getUserId()); + valueChanged = true; + } + } + if (valueChanged) validateCurrentWifiMeetsAdminRequirements(); + } + + @Override + public int getMinimumRequiredWifiSecurityLevel() { + synchronized (getLockObject()) { + final ActiveAdmin admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked( + UserHandle.USER_SYSTEM); + return (admin == null) ? DevicePolicyManager.WIFI_SECURITY_OPEN + : admin.mWifiMinimumSecurityLevel; + } + } + + @Override + public void setSsidAllowlist(List<String> ssids) { + final CallerIdentity caller = getCallerIdentity(); + Preconditions.checkCallAuthorization( + isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller), + "SSID allowlist can only be controlled by a device owner or " + + "a profile owner on an organization-owned device."); + + Collections.sort(ssids); + boolean changed = false; + synchronized (getLockObject()) { + final ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller); + if (!ssids.equals(admin.mSsidAllowlist)) { + admin.mSsidAllowlist = ssids; + admin.mSsidDenylist = null; + changed = true; + } + if (changed) saveSettingsLocked(caller.getUserId()); + } + if (changed) validateCurrentWifiMeetsAdminRequirements(); + } + + @Override + public List<String> getSsidAllowlist() { + final CallerIdentity caller = getCallerIdentity(); + Preconditions.checkCallAuthorization( + isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller) + || isSystemUid(caller), + "SSID allowlist can only be retrieved by a device owner or " + + "a profile owner on an organization-owned device or a system app."); + synchronized (getLockObject()) { + final ActiveAdmin admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked( + UserHandle.USER_SYSTEM); + return (admin == null || admin.mSsidAllowlist == null) ? new ArrayList<>() + : admin.mSsidAllowlist; + } + } + + @Override + public void setSsidDenylist(List<String> ssids) { + final CallerIdentity caller = getCallerIdentity(); + Preconditions.checkCallAuthorization( + isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller), + "SSID denylist can only be controlled by a device owner or " + + "a profile owner on an organization-owned device."); + + Collections.sort(ssids); + boolean changed = false; + synchronized (getLockObject()) { + final ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller); + if (!ssids.equals(admin.mSsidDenylist)) { + admin.mSsidDenylist = ssids; + admin.mSsidAllowlist = null; + changed = true; + } + if (changed) saveSettingsLocked(caller.getUserId()); + } + if (changed) validateCurrentWifiMeetsAdminRequirements(); + } + + @Override + public List<String> getSsidDenylist() { + final CallerIdentity caller = getCallerIdentity(); + Preconditions.checkCallAuthorization( + isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller) + || isSystemUid(caller), + "SSID denylist can only be retrieved by a device owner or " + + "a profile owner on an organization-owned device or a system app."); + synchronized (getLockObject()) { + final ActiveAdmin admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked( + UserHandle.USER_SYSTEM); + return (admin == null || admin.mSsidDenylist == null) ? new ArrayList<>() + : admin.mSsidDenylist; + } + } + @Override public void setDrawables(@NonNull List<DevicePolicyDrawableResource> drawables) { Preconditions.checkCallAuthorization(hasCallingOrSelfPermission( @@ -18026,4 +18260,50 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { mContext.sendBroadcastAsUser(intent, user); } } + + @Override + public void setStrings(@NonNull List<DevicePolicyStringResource> strings) { + Preconditions.checkCallAuthorization(hasCallingOrSelfPermission( + android.Manifest.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES)); + + Objects.requireNonNull(strings, "strings must be provided."); + + mInjector.binderWithCleanCallingIdentity(() -> { + if (mDeviceManagementResourcesProvider.updateStrings(strings)) + sendStringsUpdatedBroadcast( + strings.stream().map(s -> s.getStringId()).toArray(String[]::new)); + }); + } + + @Override + public void resetStrings(String[] stringIds) { + Preconditions.checkCallAuthorization(hasCallingOrSelfPermission( + android.Manifest.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES)); + + mInjector.binderWithCleanCallingIdentity(() -> { + if (mDeviceManagementResourcesProvider.removeStrings(stringIds)) { + sendStringsUpdatedBroadcast(stringIds); + } + }); + } + + @Override + public ParcelableResource getString(String stringId) { + return mInjector.binderWithCleanCallingIdentity(() -> + mDeviceManagementResourcesProvider.getString(stringId)); + } + + private void sendStringsUpdatedBroadcast(String[] stringIds) { + final Intent intent = new Intent(ACTION_DEVICE_POLICY_RESOURCE_UPDATED); + intent.putExtra(EXTRA_RESOURCE_ID, stringIds); + intent.putExtra(EXTRA_RESOURCE_TYPE_STRING, /* value= */ true); + intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND); + intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); + + List<UserInfo> users = mUserManager.getAliveUsers(); + for (int i = 0; i < users.size(); i++) { + UserHandle user = users.get(i).getUserHandle(); + mContext.sendBroadcastAsUser(intent, user); + } + } } diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 4b21454bdc00..1fe71f8e13a1 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -54,6 +54,7 @@ import android.hardware.display.DisplayManagerInternal; import android.net.ConnectivityManager; import android.net.ConnectivityModuleConnector; import android.net.NetworkStackClient; +import android.net.TrafficStats; import android.os.BaseBundle; import android.os.Binder; import android.os.Build; @@ -103,6 +104,7 @@ import com.android.internal.util.EmergencyAffordanceManager; import com.android.internal.util.FrameworkStatsLog; import com.android.internal.widget.ILockSettings; import com.android.server.am.ActivityManagerService; +import com.android.server.ambientcontext.AmbientContextManagerService; import com.android.server.appbinding.AppBindingService; import com.android.server.art.ArtManagerLocal; import com.android.server.attention.AttentionManagerService; @@ -136,6 +138,7 @@ import com.android.server.integrity.AppIntegrityManagerService; import com.android.server.lights.LightsService; import com.android.server.locales.LocaleManagerService; import com.android.server.location.LocationManagerService; +import com.android.server.logcat.LogcatManagerService; import com.android.server.media.MediaRouterService; import com.android.server.media.metrics.MediaMetricsManagerService; import com.android.server.media.projection.MediaProjectionManagerService; @@ -194,7 +197,7 @@ import com.android.server.tracing.TracingServiceProxy; import com.android.server.trust.TrustManagerService; import com.android.server.tv.TvInputManagerService; import com.android.server.tv.TvRemoteService; -import com.android.server.tv.interactive.TvIAppManagerService; +import com.android.server.tv.interactive.TvInteractiveAppManagerService; import com.android.server.tv.tunerresourcemanager.TunerResourceManagerService; import com.android.server.twilight.TwilightService; import com.android.server.uri.UriGrantsManagerService; @@ -261,6 +264,8 @@ public final class SystemServer implements Dumpable { "/apex/com.android.os.statsd/javalib/service-statsd.jar"; private static final String CONNECTIVITY_SERVICE_APEX_PATH = "/apex/com.android.tethering/javalib/service-connectivity.jar"; + private static final String NEARBY_SERVICE_APEX_PATH = + "/apex/com.android.nearby/javalib/service-nearby.jar"; private static final String STATS_COMPANION_LIFECYCLE_CLASS = "com.android.server.stats.StatsCompanion$Lifecycle"; private static final String STATS_PULL_ATOM_SERVICE_CLASS = @@ -271,6 +276,8 @@ public final class SystemServer implements Dumpable { "com.android.server.usb.UsbService$Lifecycle"; private static final String MIDI_SERVICE_CLASS = "com.android.server.midi.MidiService$Lifecycle"; + private static final String NEARBY_SERVICE_CLASS = + "com.android.server.nearby.NearbyService"; private static final String WIFI_APEX_SERVICE_JAR_PATH = "/apex/com.android.wifi/javalib/service-wifi.jar"; private static final String WIFI_SERVICE_CLASS = @@ -403,8 +410,6 @@ public final class SystemServer implements Dumpable { private static final String SAFETY_CENTER_SERVICE_CLASS = "com.android.safetycenter.SafetyCenterService"; - private static final String SUPPLEMENTALPROCESS_APEX_PATH = - "/apex/com.android.supplementalprocess/javalib/service-supplementalprocess.jar"; private static final String SUPPLEMENTALPROCESS_SERVICE_CLASS = "com.android.server.supplementalprocess.SupplementalProcessManagerService$Lifecycle"; @@ -1627,6 +1632,10 @@ public final class SystemServer implements Dumpable { mSystemServiceManager.startService(AppIntegrityManagerService.class); t.traceEnd(); + t.traceBegin("StartLogcatManager"); + mSystemServiceManager.startService(LogcatManagerService.class); + t.traceEnd(); + } catch (Throwable e) { Slog.e("System", "******************************************"); Slog.e("System", "************ Failure starting core service"); @@ -1807,6 +1816,7 @@ public final class SystemServer implements Dumpable { startRotationResolverService(context, t); startSystemCaptionsManagerService(context, t); startTextToSpeechManagerService(context, t); + startAmbientContextService(t); // System Speech Recognition Service t.traceBegin("StartSpeechRecognitionManagerService"); @@ -1901,6 +1911,7 @@ public final class SystemServer implements Dumpable { try { networkStats = NetworkStatsService.create(context); ServiceManager.addService(Context.NETWORK_STATS_SERVICE, networkStats); + TrafficStats.init(context); } catch (Throwable e) { reportWtf("starting NetworkStats Service", e); } @@ -1976,6 +1987,16 @@ public final class SystemServer implements Dumpable { } t.traceEnd(); + // Start Nearby Service. + t.traceBegin("StartNearbyService"); + try { + mSystemServiceManager.startServiceFromJar(NEARBY_SERVICE_CLASS, + NEARBY_SERVICE_APEX_PATH); + } catch (Throwable e) { + reportWtf("starting NearbyService", e); + } + t.traceEnd(); + t.traceBegin("StartConnectivityService"); // This has to be called after NetworkManagementService, NetworkStatsService // and NetworkPolicyManager because ConnectivityService needs to take these @@ -2367,8 +2388,8 @@ public final class SystemServer implements Dumpable { if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_LIVE_TV) || mPackageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)) { - t.traceBegin("StartTvIAppManager"); - mSystemServiceManager.startService(TvIAppManagerService.class); + t.traceBegin("StartTvInteractiveAppManager"); + mSystemServiceManager.startService(TvInteractiveAppManagerService.class); t.traceEnd(); } @@ -2558,8 +2579,7 @@ public final class SystemServer implements Dumpable { // Supplemental Process t.traceBegin("StartSupplementalProcessManagerService"); - mSystemServiceManager.startServiceFromJar(SUPPLEMENTALPROCESS_SERVICE_CLASS, - SUPPLEMENTALPROCESS_APEX_PATH); + mSystemServiceManager.startService(SUPPLEMENTALPROCESS_SERVICE_CLASS); t.traceEnd(); if (safeMode) { @@ -3149,6 +3169,17 @@ public final class SystemServer implements Dumpable { } + private void startAmbientContextService(@NonNull TimingsTraceAndSlog t) { + if (!AmbientContextManagerService.isDetectionServiceConfigured()) { + Slog.d(TAG, "AmbientContextDetectionService is not configured on this device"); + return; + } + + t.traceBegin("StartAmbientContextService"); + mSystemServiceManager.startService(AmbientContextManagerService.class); + t.traceEnd(); + } + private static void startSystemUi(Context context, WindowManagerService windowManager) { PackageManagerInternal pm = LocalServices.getService(PackageManagerInternal.class); Intent intent = new Intent(); diff --git a/services/midi/java/com/android/server/midi/MidiService.java b/services/midi/java/com/android/server/midi/MidiService.java index ca31efcdf3d2..d56278629bf2 100644 --- a/services/midi/java/com/android/server/midi/MidiService.java +++ b/services/midi/java/com/android/server/midi/MidiService.java @@ -18,9 +18,11 @@ package com.android.server.midi; import android.annotation.NonNull; import android.bluetooth.BluetoothDevice; +import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; import android.content.ServiceConnection; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; @@ -33,6 +35,7 @@ import android.media.midi.IMidiDeviceListener; import android.media.midi.IMidiDeviceOpenCallback; import android.media.midi.IMidiDeviceServer; import android.media.midi.IMidiManager; +import android.media.midi.MidiDevice; import android.media.midi.MidiDeviceInfo; import android.media.midi.MidiDeviceService; import android.media.midi.MidiDeviceStatus; @@ -55,6 +58,7 @@ import com.android.server.SystemService.TargetUser; import org.xmlpull.v1.XmlPullParser; import java.io.FileDescriptor; +import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashMap; @@ -96,9 +100,12 @@ public class MidiService extends IMidiManager.Stub { = new HashMap<MidiDeviceInfo, Device>(); // list of all Bluetooth devices, keyed by BluetoothDevice - private final HashMap<BluetoothDevice, Device> mBluetoothDevices + private final HashMap<BluetoothDevice, Device> mBluetoothDevices = new HashMap<BluetoothDevice, Device>(); + private final HashMap<BluetoothDevice, MidiDevice> mBleMidiDeviceMap = + new HashMap<BluetoothDevice, MidiDevice>(); + // list of all devices, keyed by IMidiDeviceServer private final HashMap<IBinder, Device> mDevicesByServer = new HashMap<IBinder, Device>(); @@ -569,10 +576,45 @@ public class MidiService extends IMidiManager.Stub { } } + private final BroadcastReceiver mBleMidiReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (action == null) { + Log.w(TAG, "MidiService, action is null"); + return; + } + + switch (action) { + case BluetoothDevice.ACTION_ACL_CONNECTED: { + Log.d(TAG, "ACTION_ACL_CONNECTED"); + BluetoothDevice btDevice = + intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); + openBluetoothDevice(btDevice); + } + break; + + case BluetoothDevice.ACTION_ACL_DISCONNECTED: { + Log.d(TAG, "ACTION_ACL_DISCONNECTED"); + BluetoothDevice btDevice = + intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); + closeBluetoothDevice(btDevice); + } + break; + } + } + }; + public MidiService(Context context) { mContext = context; mPackageManager = context.getPackageManager(); + // Setup broadcast receivers + IntentFilter filter = new IntentFilter(); + filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED); + filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED); + context.registerReceiver(mBleMidiReceiver, filter); + mBluetoothServiceUid = -1; } @@ -643,13 +685,31 @@ public class MidiService extends IMidiManager.Stub { private static final MidiDeviceInfo[] EMPTY_DEVICE_INFO_ARRAY = new MidiDeviceInfo[0]; public MidiDeviceInfo[] getDevices() { + return getDevicesForTransport(MidiManager.TRANSPORT_MIDI_BYTE_STREAM); + } + + /** + * @hide + */ + public MidiDeviceInfo[] getDevicesForTransport(int transport) { ArrayList<MidiDeviceInfo> deviceInfos = new ArrayList<MidiDeviceInfo>(); int uid = Binder.getCallingUid(); synchronized (mDevicesByInfo) { for (Device device : mDevicesByInfo.values()) { if (device.isUidAllowed(uid)) { - deviceInfos.add(device.getDeviceInfo()); + // UMP devices have protocols that are not PROTOCOL_UNKNOWN + if (transport == MidiManager.TRANSPORT_UNIVERSAL_MIDI_PACKETS) { + if (device.getDeviceInfo().getDefaultProtocol() + != MidiDeviceInfo.PROTOCOL_UNKNOWN) { + deviceInfos.add(device.getDeviceInfo()); + } + } else if (transport == MidiManager.TRANSPORT_MIDI_BYTE_STREAM) { + if (device.getDeviceInfo().getDefaultProtocol() + == MidiDeviceInfo.PROTOCOL_UNKNOWN) { + deviceInfos.add(device.getDeviceInfo()); + } + } } } } @@ -683,9 +743,43 @@ public class MidiService extends IMidiManager.Stub { } } + private void openBluetoothDevice(BluetoothDevice bluetoothDevice) { + Log.d(TAG, "openBluetoothDevice() device: " + bluetoothDevice); + + MidiManager midiManager = mContext.getSystemService(MidiManager.class); + midiManager.openBluetoothDevice(bluetoothDevice, + new MidiManager.OnDeviceOpenedListener() { + @Override + public void onDeviceOpened(MidiDevice device) { + synchronized (mBleMidiDeviceMap) { + mBleMidiDeviceMap.put(bluetoothDevice, device); + } + } + }, null); + } + + private void closeBluetoothDevice(BluetoothDevice bluetoothDevice) { + Log.d(TAG, "closeBluetoothDevice() device: " + bluetoothDevice); + + MidiDevice midiDevice; + synchronized (mBleMidiDeviceMap) { + midiDevice = mBleMidiDeviceMap.remove(bluetoothDevice); + } + + if (midiDevice != null) { + try { + midiDevice.close(); + } catch (IOException ex) { + Log.e(TAG, "Exception closing BLE-MIDI device" + ex); + } + } + } + @Override public void openBluetoothDevice(IBinder token, BluetoothDevice bluetoothDevice, IMidiDeviceOpenCallback callback) { + Log.d(TAG, "openBluetoothDevice()"); + Client client = getClient(token); if (client == null) return; @@ -718,7 +812,7 @@ public class MidiService extends IMidiManager.Stub { @Override public MidiDeviceInfo registerDeviceServer(IMidiDeviceServer server, int numInputPorts, int numOutputPorts, String[] inputPortNames, String[] outputPortNames, - Bundle properties, int type) { + Bundle properties, int type, int defaultProtocol) { int uid = Binder.getCallingUid(); if (type == MidiDeviceInfo.TYPE_USB && uid != Process.SYSTEM_UID) { throw new SecurityException("only system can create USB devices"); @@ -728,7 +822,8 @@ public class MidiService extends IMidiManager.Stub { synchronized (mDevicesByInfo) { return addDeviceLocked(type, numInputPorts, numOutputPorts, inputPortNames, - outputPortNames, properties, server, null, false, uid); + outputPortNames, properties, server, null, false, uid, + defaultProtocol); } } @@ -769,7 +864,15 @@ public class MidiService extends IMidiManager.Stub { if (device == null) { throw new IllegalArgumentException("no such device for " + deviceInfo); } - return device.getDeviceStatus(); + int uid = Binder.getCallingUid(); + if (device.isUidAllowed(uid)) { + return device.getDeviceStatus(); + } else { + Log.e(TAG, "getDeviceStatus() invalid UID = " + uid); + EventLog.writeEvent(0x534e4554, "203549963", + uid, "getDeviceStatus: invalid uid"); + return null; + } } @Override @@ -797,11 +900,12 @@ public class MidiService extends IMidiManager.Stub { private MidiDeviceInfo addDeviceLocked(int type, int numInputPorts, int numOutputPorts, String[] inputPortNames, String[] outputPortNames, Bundle properties, IMidiDeviceServer server, ServiceInfo serviceInfo, - boolean isPrivate, int uid) { + boolean isPrivate, int uid, int defaultProtocol) { int id = mNextDeviceId++; MidiDeviceInfo deviceInfo = new MidiDeviceInfo(type, id, numInputPorts, numOutputPorts, - inputPortNames, outputPortNames, properties, isPrivate); + inputPortNames, outputPortNames, properties, isPrivate, + defaultProtocol); if (server != null) { try { @@ -988,10 +1092,11 @@ public class MidiService extends IMidiManager.Stub { synchronized (mDevicesByInfo) { addDeviceLocked(MidiDeviceInfo.TYPE_VIRTUAL, - numInputPorts, numOutputPorts, - inputPortNames.toArray(EMPTY_STRING_ARRAY), - outputPortNames.toArray(EMPTY_STRING_ARRAY), - properties, null, serviceInfo, isPrivate, uid); + numInputPorts, numOutputPorts, + inputPortNames.toArray(EMPTY_STRING_ARRAY), + outputPortNames.toArray(EMPTY_STRING_ARRAY), + properties, null, serviceInfo, isPrivate, uid, + MidiDeviceInfo.PROTOCOL_UNKNOWN); } // setting properties to null signals that we are no longer // processing a <device> diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt index a0f3bbf928ab..cc663d955612 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt @@ -503,6 +503,11 @@ class AndroidPackageTest : ParcelableComponentTest(AndroidPackage::class, Packag PackageManager.Property::getString ) } + ), + getSetByValue( + AndroidPackage::shouldInheritKeyStoreKeys, + ParsingPackage::setInheritKeyStoreKeys, + true ) ) diff --git a/services/tests/mockingservicestests/Android.bp b/services/tests/mockingservicestests/Android.bp index 635f1360ff73..36246e5f3857 100644 --- a/services/tests/mockingservicestests/Android.bp +++ b/services/tests/mockingservicestests/Android.bp @@ -14,7 +14,7 @@ java_defaults { name: "FrameworkMockingServicesTests-jni-defaults", jni_libs: [ - "libactivitymanagermockingservicestestjni", + "libmockingservicestestjni", ], } diff --git a/services/tests/mockingservicestests/jni/Android.bp b/services/tests/mockingservicestests/jni/Android.bp index a32bf2c3b260..89b204b9c999 100644 --- a/services/tests/mockingservicestests/jni/Android.bp +++ b/services/tests/mockingservicestests/jni/Android.bp @@ -8,7 +8,7 @@ package { } cc_library_shared { - name: "libactivitymanagermockingservicestestjni", + name: "libmockingservicestestjni", cflags: [ "-Wall", @@ -19,12 +19,15 @@ cc_library_shared { srcs: [ ":lib_cachedAppOptimizer_native", + ":lib_gameManagerService_native", "onload.cpp", ], include_dirs: [ "frameworks/base/libs", "frameworks/native/services", + "frameworks/native/libs/math/include", + "frameworks/native/libs/ui/include", "system/memory/libmeminfo/include", ], @@ -33,10 +36,19 @@ cc_library_shared { "libandroid_runtime", "libbase", "libbinder", + "libgralloctypes", + "libgui", + "libhidlbase", "liblog", "libmeminfo", "libnativehelper", "libprocessgroup", "libutils", + "android.hardware.graphics.bufferqueue@1.0", + "android.hardware.graphics.bufferqueue@2.0", + "android.hardware.graphics.common@1.2", + "android.hardware.graphics.common-V3-ndk", + "android.hardware.graphics.mapper@4.0", + "android.hidl.token@1.0-utils", ], } diff --git a/services/tests/mockingservicestests/jni/onload.cpp b/services/tests/mockingservicestests/jni/onload.cpp index 147cc479be18..23ccb22321b2 100644 --- a/services/tests/mockingservicestests/jni/onload.cpp +++ b/services/tests/mockingservicestests/jni/onload.cpp @@ -25,6 +25,7 @@ namespace android { int register_android_server_am_CachedAppOptimizer(JNIEnv* env); +int register_android_server_app_GameManagerService(JNIEnv* env); }; using namespace android; @@ -40,6 +41,7 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) } ALOG_ASSERT(env, "Could not retrieve the env!"); register_android_server_am_CachedAppOptimizer(env); + register_android_server_app_GameManagerService(env); return JNI_VERSION_1_4; } diff --git a/services/tests/mockingservicestests/src/android/service/games/GameSessionTest.java b/services/tests/mockingservicestests/src/android/service/games/GameSessionTest.java new file mode 100644 index 000000000000..fec9b1249d17 --- /dev/null +++ b/services/tests/mockingservicestests/src/android/service/games/GameSessionTest.java @@ -0,0 +1,391 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.games; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; +import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR; + +import static com.google.common.truth.Truth.assertThat; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; + +import android.graphics.Bitmap; +import android.platform.test.annotations.Presubmit; +import android.service.games.GameSession.ScreenshotCallback; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.view.SurfaceControlViewHost; + +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.SmallTest; + +import com.android.internal.infra.AndroidFuture; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoSession; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +/** + * Unit tests for the {@link android.service.games.GameSession}. + */ +@RunWith(AndroidTestingRunner.class) +@SmallTest +@Presubmit +@TestableLooper.RunWithLooper(setAsMainLooper = true) +public final class GameSessionTest { + private static final long WAIT_FOR_CALLBACK_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(1); + private static final Bitmap TEST_BITMAP = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888); + + @Mock + private IGameSessionController mMockGameSessionController; + @Mock + SurfaceControlViewHost mSurfaceControlViewHost; + private LifecycleTrackingGameSession mGameSession; + private MockitoSession mMockitoSession; + + @Before + public void setUp() { + mMockitoSession = mockitoSession() + .initMocks(this) + .startMocking(); + + mGameSession = new LifecycleTrackingGameSession() {}; + mGameSession.attach(mMockGameSessionController, /* taskId= */ 10, + InstrumentationRegistry.getContext(), + mSurfaceControlViewHost, + /* widthPx= */ 0, /* heightPx= */0); + } + + @After + public void tearDown() { + mMockitoSession.finishMocking(); + } + + @Test + public void takeScreenshot_attachNotCalled_throwsIllegalStateException() throws Exception { + GameSession gameSession = new GameSession() {}; + + try { + gameSession.takeScreenshot(DIRECT_EXECUTOR, + new ScreenshotCallback() { + @Override + public void onFailure(int statusCode) { + fail(); + } + + @Override + public void onSuccess(Bitmap bitmap) { + fail(); + } + }); + fail(); + } catch (IllegalStateException expected) { + + } + } + + @Test + public void takeScreenshot_gameManagerException_returnsInternalError() throws Exception { + doAnswer(invocation -> { + AndroidFuture result = invocation.getArgument(1); + result.completeExceptionally(new Exception()); + return null; + }).when(mMockGameSessionController).takeScreenshot(anyInt(), any()); + + CountDownLatch countDownLatch = new CountDownLatch(1); + + mGameSession.takeScreenshot(DIRECT_EXECUTOR, + new ScreenshotCallback() { + @Override + public void onFailure(int statusCode) { + assertEquals(ScreenshotCallback.ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR, + statusCode); + countDownLatch.countDown(); + } + + @Override + public void onSuccess(Bitmap bitmap) { + fail(); + } + }); + + assertTrue(countDownLatch.await( + WAIT_FOR_CALLBACK_TIMEOUT_MS, TimeUnit.MILLISECONDS)); + } + + @Test + public void takeScreenshot_gameManagerError_returnsInternalError() throws Exception { + doAnswer(invocation -> { + AndroidFuture result = invocation.getArgument(1); + result.complete(GameScreenshotResult.createInternalErrorResult()); + return null; + }).when(mMockGameSessionController).takeScreenshot(anyInt(), any()); + + CountDownLatch countDownLatch = new CountDownLatch(1); + + mGameSession.takeScreenshot(DIRECT_EXECUTOR, + new ScreenshotCallback() { + @Override + public void onFailure(int statusCode) { + assertEquals(ScreenshotCallback.ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR, + statusCode); + countDownLatch.countDown(); + } + + @Override + public void onSuccess(Bitmap bitmap) { + fail(); + } + }); + + assertTrue(countDownLatch.await( + WAIT_FOR_CALLBACK_TIMEOUT_MS, TimeUnit.MILLISECONDS)); + } + + @Test + public void takeScreenshot_gameManagerSuccess_returnsBitmap() throws Exception { + doAnswer(invocation -> { + AndroidFuture result = invocation.getArgument(1); + result.complete(GameScreenshotResult.createSuccessResult(TEST_BITMAP)); + return null; + }).when(mMockGameSessionController).takeScreenshot(anyInt(), any()); + + CountDownLatch countDownLatch = new CountDownLatch(1); + + mGameSession.takeScreenshot(DIRECT_EXECUTOR, + new ScreenshotCallback() { + @Override + public void onFailure(int statusCode) { + fail(); + } + + @Override + public void onSuccess(Bitmap bitmap) { + assertEquals(TEST_BITMAP, bitmap); + countDownLatch.countDown(); + } + }); + + assertTrue(countDownLatch.await( + WAIT_FOR_CALLBACK_TIMEOUT_MS, TimeUnit.MILLISECONDS)); + } + + @Test + public void moveState_InitializedToInitialized_noLifecycleCalls() throws Exception { + mGameSession.moveToState(GameSession.LifecycleState.INITIALIZED); + + assertThat(mGameSession.mLifecycleMethodCalls.isEmpty()).isTrue(); + } + + @Test + public void moveState_FullLifecycle_ExpectedLifecycleCalls() throws Exception { + mGameSession.moveToState(GameSession.LifecycleState.CREATED); + mGameSession.moveToState(GameSession.LifecycleState.TASK_FOCUSED); + mGameSession.moveToState(GameSession.LifecycleState.CREATED); + mGameSession.moveToState(GameSession.LifecycleState.DESTROYED); + + assertThat(mGameSession.mLifecycleMethodCalls).containsExactly( + LifecycleTrackingGameSession.LifecycleMethodCall.ON_CREATE, + LifecycleTrackingGameSession.LifecycleMethodCall.ON_GAME_TASK_FOCUSED, + LifecycleTrackingGameSession.LifecycleMethodCall.ON_GAME_TASK_UNFOCUSED, + LifecycleTrackingGameSession.LifecycleMethodCall.ON_DESTROY).inOrder(); + } + + @Test + public void moveState_DestroyedWhenInitialized_ExpectedLifecycleCalls() throws Exception { + mGameSession.moveToState(GameSession.LifecycleState.DESTROYED); + + // ON_CREATE is always called before ON_DESTROY. + assertThat(mGameSession.mLifecycleMethodCalls).containsExactly( + LifecycleTrackingGameSession.LifecycleMethodCall.ON_CREATE, + LifecycleTrackingGameSession.LifecycleMethodCall.ON_DESTROY).inOrder(); + } + + @Test + public void moveState_DestroyedWhenFocused_ExpectedLifecycleCalls() throws Exception { + mGameSession.moveToState(GameSession.LifecycleState.CREATED); + mGameSession.moveToState(GameSession.LifecycleState.TASK_FOCUSED); + mGameSession.moveToState(GameSession.LifecycleState.DESTROYED); + + // The ON_GAME_TASK_UNFOCUSED lifecycle event is implied because the session is destroyed + // while in focus. + assertThat(mGameSession.mLifecycleMethodCalls).containsExactly( + LifecycleTrackingGameSession.LifecycleMethodCall.ON_CREATE, + LifecycleTrackingGameSession.LifecycleMethodCall.ON_GAME_TASK_FOCUSED, + LifecycleTrackingGameSession.LifecycleMethodCall.ON_GAME_TASK_UNFOCUSED, + LifecycleTrackingGameSession.LifecycleMethodCall.ON_DESTROY).inOrder(); + } + + @Test + public void moveState_FocusCycled_ExpectedLifecycleCalls() throws Exception { + mGameSession.moveToState(GameSession.LifecycleState.CREATED); + mGameSession.moveToState(GameSession.LifecycleState.TASK_FOCUSED); + mGameSession.moveToState(GameSession.LifecycleState.TASK_UNFOCUSED); + mGameSession.moveToState(GameSession.LifecycleState.TASK_FOCUSED); + mGameSession.moveToState(GameSession.LifecycleState.TASK_UNFOCUSED); + + // Both cycles from focus and unfocus are captured. + assertThat(mGameSession.mLifecycleMethodCalls).containsExactly( + LifecycleTrackingGameSession.LifecycleMethodCall.ON_CREATE, + LifecycleTrackingGameSession.LifecycleMethodCall.ON_GAME_TASK_FOCUSED, + LifecycleTrackingGameSession.LifecycleMethodCall.ON_GAME_TASK_UNFOCUSED, + LifecycleTrackingGameSession.LifecycleMethodCall.ON_GAME_TASK_FOCUSED, + LifecycleTrackingGameSession.LifecycleMethodCall.ON_GAME_TASK_UNFOCUSED).inOrder(); + } + + @Test + public void moveState_MultipleFocusAndUnfocusCalls_ExpectedLifecycleCalls() throws Exception { + mGameSession.moveToState(GameSession.LifecycleState.CREATED); + mGameSession.moveToState(GameSession.LifecycleState.TASK_FOCUSED); + mGameSession.moveToState(GameSession.LifecycleState.TASK_FOCUSED); + mGameSession.moveToState(GameSession.LifecycleState.TASK_UNFOCUSED); + mGameSession.moveToState(GameSession.LifecycleState.TASK_UNFOCUSED); + + // The second TASK_FOCUSED call and the second TASK_UNFOCUSED call are ignored. + assertThat(mGameSession.mLifecycleMethodCalls).containsExactly( + LifecycleTrackingGameSession.LifecycleMethodCall.ON_CREATE, + LifecycleTrackingGameSession.LifecycleMethodCall.ON_GAME_TASK_FOCUSED, + LifecycleTrackingGameSession.LifecycleMethodCall.ON_GAME_TASK_UNFOCUSED).inOrder(); + } + + @Test + public void moveState_CreatedAfterFocused_ExpectedLifecycleCalls() throws Exception { + mGameSession.moveToState(GameSession.LifecycleState.CREATED); + mGameSession.moveToState(GameSession.LifecycleState.TASK_FOCUSED); + mGameSession.moveToState(GameSession.LifecycleState.CREATED); + + // The second CREATED call is ignored. + assertThat(mGameSession.mLifecycleMethodCalls).containsExactly( + LifecycleTrackingGameSession.LifecycleMethodCall.ON_CREATE, + LifecycleTrackingGameSession.LifecycleMethodCall.ON_GAME_TASK_FOCUSED).inOrder(); + } + + @Test + public void moveState_UnfocusedWithoutFocused_ExpectedLifecycleCalls() throws Exception { + mGameSession.moveToState(GameSession.LifecycleState.CREATED); + mGameSession.moveToState(GameSession.LifecycleState.TASK_UNFOCUSED); + + // The TASK_UNFOCUSED call without an earlier TASK_FOCUSED call is ignored. + assertThat(mGameSession.mLifecycleMethodCalls).containsExactly( + LifecycleTrackingGameSession.LifecycleMethodCall.ON_CREATE).inOrder(); + } + + @Test + public void moveState_NeverFocused_ExpectedLifecycleCalls() throws Exception { + mGameSession.moveToState(GameSession.LifecycleState.CREATED); + mGameSession.moveToState(GameSession.LifecycleState.DESTROYED); + + assertThat(mGameSession.mLifecycleMethodCalls).containsExactly( + LifecycleTrackingGameSession.LifecycleMethodCall.ON_CREATE, + LifecycleTrackingGameSession.LifecycleMethodCall.ON_DESTROY).inOrder(); + } + + @Test + public void moveState_MultipleFocusCalls_ExpectedLifecycleCalls() throws Exception { + mGameSession.moveToState(GameSession.LifecycleState.CREATED); + mGameSession.moveToState(GameSession.LifecycleState.TASK_FOCUSED); + mGameSession.moveToState(GameSession.LifecycleState.TASK_FOCUSED); + mGameSession.moveToState(GameSession.LifecycleState.TASK_FOCUSED); + + // The extra TASK_FOCUSED moves are ignored. + assertThat(mGameSession.mLifecycleMethodCalls).containsExactly( + LifecycleTrackingGameSession.LifecycleMethodCall.ON_CREATE, + LifecycleTrackingGameSession.LifecycleMethodCall.ON_GAME_TASK_FOCUSED).inOrder(); + } + + @Test + public void moveState_MultipleCreateCalls_ExpectedLifecycleCalls() throws Exception { + mGameSession.moveToState(GameSession.LifecycleState.CREATED); + mGameSession.moveToState(GameSession.LifecycleState.CREATED); + mGameSession.moveToState(GameSession.LifecycleState.CREATED); + + // The extra CREATE moves are ignored. + assertThat(mGameSession.mLifecycleMethodCalls).containsExactly( + LifecycleTrackingGameSession.LifecycleMethodCall.ON_CREATE).inOrder(); + } + + @Test + public void moveState_FocusBeforeCreate_ExpectedLifecycleCalls() throws Exception { + mGameSession.moveToState(GameSession.LifecycleState.TASK_FOCUSED); + + // The TASK_FOCUSED move before CREATE is ignored. + assertThat(mGameSession.mLifecycleMethodCalls.isEmpty()).isTrue(); + } + + @Test + public void moveState_UnfocusBeforeCreate_ExpectedLifecycleCalls() throws Exception { + mGameSession.moveToState(GameSession.LifecycleState.TASK_UNFOCUSED); + + // The TASK_UNFOCUSED move before CREATE is ignored. + assertThat(mGameSession.mLifecycleMethodCalls.isEmpty()).isTrue(); + } + + @Test + public void moveState_FocusWhenDestroyed_ExpectedLifecycleCalls() throws Exception { + mGameSession.moveToState(GameSession.LifecycleState.CREATED); + mGameSession.moveToState(GameSession.LifecycleState.DESTROYED); + mGameSession.moveToState(GameSession.LifecycleState.TASK_FOCUSED); + + // The TASK_FOCUSED move after DESTROYED is ignored. + assertThat(mGameSession.mLifecycleMethodCalls).containsExactly( + LifecycleTrackingGameSession.LifecycleMethodCall.ON_CREATE, + LifecycleTrackingGameSession.LifecycleMethodCall.ON_DESTROY).inOrder(); + } + + private static class LifecycleTrackingGameSession extends GameSession { + private enum LifecycleMethodCall { + ON_CREATE, + ON_DESTROY, + ON_GAME_TASK_FOCUSED, + ON_GAME_TASK_UNFOCUSED + } + + final List<LifecycleMethodCall> mLifecycleMethodCalls = new ArrayList<>(); + + @Override + public void onCreate() { + mLifecycleMethodCalls.add(LifecycleMethodCall.ON_CREATE); + } + + @Override + public void onDestroy() { + mLifecycleMethodCalls.add(LifecycleMethodCall.ON_DESTROY); + } + + @Override + public void onGameTaskFocusChanged(boolean focused) { + if (focused) { + mLifecycleMethodCalls.add(LifecycleMethodCall.ON_GAME_TASK_FOCUSED); + } else { + mLifecycleMethodCalls.add(LifecycleMethodCall.ON_GAME_TASK_UNFOCUSED); + } + } + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java index 1c21645c1626..0198253e2586 100644 --- a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java +++ b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java @@ -20,6 +20,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSess import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.anyInt; @@ -28,6 +29,7 @@ import static org.mockito.Mockito.when; import android.Manifest; import android.app.GameManager; +import android.app.GameModeInfo; import android.content.Context; import android.content.ContextWrapper; import android.content.pm.ApplicationInfo; @@ -190,7 +192,7 @@ public class GameManagerServiceTests { } private void mockDeviceConfigPerformance() { - String configString = "mode=2,downscaleFactor=0.5,useAngle=false"; + String configString = "mode=2,downscaleFactor=0.5,useAngle=false,fps=90"; when(DeviceConfig.getProperty(anyString(), anyString())) .thenReturn(configString); } @@ -203,13 +205,13 @@ public class GameManagerServiceTests { } private void mockDeviceConfigBattery() { - String configString = "mode=3,downscaleFactor=0.7"; + String configString = "mode=3,downscaleFactor=0.7,fps=30"; when(DeviceConfig.getProperty(anyString(), anyString())) .thenReturn(configString); } private void mockDeviceConfigAll() { - String configString = "mode=3,downscaleFactor=0.7:mode=2,downscaleFactor=0.5"; + String configString = "mode=3,downscaleFactor=0.7,fps=30:mode=2,downscaleFactor=0.5,fps=90"; when(DeviceConfig.getProperty(anyString(), anyString())) .thenReturn(configString); } @@ -454,12 +456,12 @@ public class GameManagerServiceTests { gameManagerService.getGameMode(mPackageName, USER_ID_2)); } - private void checkReportedModes(int ...requiredModes) { - GameManagerService gameManagerService = - new GameManagerService(mMockContext, mTestLooper.getLooper()); - - startUser(gameManagerService, USER_ID_1); - gameManagerService.updateConfigsForUser(USER_ID_1, mPackageName); + private void checkReportedModes(GameManagerService gameManagerService, int ...requiredModes) { + if (gameManagerService == null) { + gameManagerService = new GameManagerService(mMockContext, mTestLooper.getLooper()); + startUser(gameManagerService, USER_ID_1); + gameManagerService.updateConfigsForUser(USER_ID_1, mPackageName); + } ArraySet<Integer> reportedModes = new ArraySet<>(); int[] modes = gameManagerService.getAvailableGameModes(mPackageName); for (int mode : modes) { @@ -472,12 +474,13 @@ public class GameManagerServiceTests { } } - private void checkDownscaling(int gameMode, String scaling) { - GameManagerService gameManagerService = - new GameManagerService(mMockContext, mTestLooper.getLooper()); - - startUser(gameManagerService, USER_ID_1); - gameManagerService.updateConfigsForUser(USER_ID_1, mPackageName); + private void checkDownscaling(GameManagerService gameManagerService, + int gameMode, String scaling) { + if (gameManagerService == null) { + gameManagerService = new GameManagerService(mMockContext, mTestLooper.getLooper()); + startUser(gameManagerService, USER_ID_1); + gameManagerService.updateConfigsForUser(USER_ID_1, mPackageName); + } GameManagerService.GamePackageConfiguration config = gameManagerService.getConfig(mPackageName); assertEquals(config.getGameModeConfiguration(gameMode).getScaling(), scaling); @@ -496,6 +499,17 @@ public class GameManagerServiceTests { assertEquals(gameManagerService.getAngleEnabled(mPackageName, USER_ID_1), angleEnabled); } + private void checkFps(GameManagerService gameManagerService, int gameMode, int fps) { + if (gameManagerService == null) { + gameManagerService = new GameManagerService(mMockContext, mTestLooper.getLooper()); + startUser(gameManagerService, USER_ID_1); + gameManagerService.updateConfigsForUser(USER_ID_1, mPackageName); + } + GameManagerService.GamePackageConfiguration config = + gameManagerService.getConfig(mPackageName); + assertEquals(config.getGameModeConfiguration(gameMode).getFps(), fps); + } + /** * Phenotype device config exists, but is only propagating the default value. */ @@ -503,7 +517,7 @@ public class GameManagerServiceTests { public void testDeviceConfigDefault() { mockDeviceConfigDefault(); mockModifyGameModeGranted(); - checkReportedModes(GameManager.GAME_MODE_UNSUPPORTED); + checkReportedModes(null); } /** @@ -513,7 +527,7 @@ public class GameManagerServiceTests { public void testDeviceConfigNone() { mockDeviceConfigNone(); mockModifyGameModeGranted(); - checkReportedModes(GameManager.GAME_MODE_UNSUPPORTED); + checkReportedModes(null); } /** @@ -523,7 +537,7 @@ public class GameManagerServiceTests { public void testDeviceConfigPerformance() { mockDeviceConfigPerformance(); mockModifyGameModeGranted(); - checkReportedModes(GameManager.GAME_MODE_PERFORMANCE, GameManager.GAME_MODE_STANDARD); + checkReportedModes(null, GameManager.GAME_MODE_PERFORMANCE, GameManager.GAME_MODE_STANDARD); } /** @@ -533,7 +547,7 @@ public class GameManagerServiceTests { public void testDeviceConfigBattery() { mockDeviceConfigBattery(); mockModifyGameModeGranted(); - checkReportedModes(GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_STANDARD); + checkReportedModes(null, GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_STANDARD); } /** @@ -543,7 +557,7 @@ public class GameManagerServiceTests { public void testDeviceConfigAll() { mockDeviceConfigAll(); mockModifyGameModeGranted(); - checkReportedModes(GameManager.GAME_MODE_PERFORMANCE, GameManager.GAME_MODE_BATTERY, + checkReportedModes(null, GameManager.GAME_MODE_PERFORMANCE, GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_STANDARD); } @@ -554,7 +568,7 @@ public class GameManagerServiceTests { public void testDeviceConfigInvalid() { mockDeviceConfigInvalid(); mockModifyGameModeGranted(); - checkReportedModes(GameManager.GAME_MODE_UNSUPPORTED); + checkReportedModes(null); } /** @@ -564,7 +578,171 @@ public class GameManagerServiceTests { public void testDeviceConfigMalformed() { mockDeviceConfigMalformed(); mockModifyGameModeGranted(); - checkReportedModes(GameManager.GAME_MODE_UNSUPPORTED); + checkReportedModes(null); + } + + /** + * Override device config for performance mode exists and is valid. + */ + @Test + public void testSetDeviceConfigOverridePerformance() { + mockDeviceConfigPerformance(); + mockModifyGameModeGranted(); + + GameManagerService gameManagerService = new GameManagerService( + mMockContext, mTestLooper.getLooper()); + startUser(gameManagerService, USER_ID_1); + gameManagerService.setGameModeConfigOverride(mPackageName, USER_ID_1, + GameManager.GAME_MODE_PERFORMANCE, "120", "0.3"); + + checkReportedModes(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, + GameManager.GAME_MODE_STANDARD); + checkDownscaling(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, "0.3"); + checkFps(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, 120); + } + + /** + * Override device config for battery mode exists and is valid. + */ + @Test + public void testSetDeviceConfigOverrideBattery() { + mockDeviceConfigBattery(); + mockModifyGameModeGranted(); + + GameManagerService gameManagerService = new GameManagerService( + mMockContext, mTestLooper.getLooper()); + startUser(gameManagerService, USER_ID_1); + gameManagerService.setGameModeConfigOverride(mPackageName, USER_ID_1, + GameManager.GAME_MODE_BATTERY, "60", "0.5"); + + checkReportedModes(gameManagerService, GameManager.GAME_MODE_BATTERY, + GameManager.GAME_MODE_STANDARD); + checkDownscaling(gameManagerService, GameManager.GAME_MODE_BATTERY, "0.5"); + checkFps(gameManagerService, GameManager.GAME_MODE_BATTERY, 60); + } + + /** + * Override device configs for both battery and performance modes exists and are valid. + */ + @Test + public void testSetDeviceOverrideConfigAll() { + mockDeviceConfigAll(); + mockModifyGameModeGranted(); + + GameManagerService gameManagerService = new GameManagerService( + mMockContext, mTestLooper.getLooper()); + startUser(gameManagerService, USER_ID_1); + gameManagerService.setGameModeConfigOverride(mPackageName, USER_ID_1, + GameManager.GAME_MODE_PERFORMANCE, "120", "0.3"); + gameManagerService.setGameModeConfigOverride(mPackageName, USER_ID_1, + GameManager.GAME_MODE_BATTERY, "60", "0.5"); + + checkReportedModes(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, + GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_STANDARD); + checkDownscaling(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, "0.3"); + checkFps(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, 120); + checkDownscaling(gameManagerService, GameManager.GAME_MODE_BATTERY, "0.5"); + checkFps(gameManagerService, GameManager.GAME_MODE_BATTERY, 60); + } + + /** + * Override device config for performance mode exists and is valid. + */ + @Test + public void testResetDeviceConfigOverridePerformance() { + mockDeviceConfigPerformance(); + mockModifyGameModeGranted(); + + GameManagerService gameManagerService = new GameManagerService( + mMockContext, mTestLooper.getLooper()); + startUser(gameManagerService, USER_ID_1); + gameManagerService.setGameModeConfigOverride(mPackageName, USER_ID_1, + GameManager.GAME_MODE_PERFORMANCE, "120", "0.3"); + + gameManagerService.resetGameModeConfigOverride(mPackageName, USER_ID_1, + GameManager.GAME_MODE_PERFORMANCE); + + checkReportedModes(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, + GameManager.GAME_MODE_STANDARD); + checkDownscaling(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, "0.5"); + checkFps(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, 90); + } + + /** + * Override device config for battery mode exists and is valid. + */ + @Test + public void testResetDeviceConfigOverrideBattery() { + mockDeviceConfigBattery(); + mockModifyGameModeGranted(); + + GameManagerService gameManagerService = new GameManagerService( + mMockContext, mTestLooper.getLooper()); + startUser(gameManagerService, USER_ID_1); + gameManagerService.setGameModeConfigOverride(mPackageName, USER_ID_1, + GameManager.GAME_MODE_BATTERY, "60", "0.5"); + + gameManagerService.resetGameModeConfigOverride(mPackageName, USER_ID_1, + GameManager.GAME_MODE_BATTERY); + + checkReportedModes(gameManagerService, GameManager.GAME_MODE_BATTERY, + GameManager.GAME_MODE_STANDARD); + checkDownscaling(gameManagerService, GameManager.GAME_MODE_BATTERY, "0.7"); + checkFps(gameManagerService, GameManager.GAME_MODE_BATTERY, 30); + } + + /** + * Override device configs for both battery and performance modes exists and are valid. + */ + @Test + public void testResetDeviceOverrideConfigAll() { + mockDeviceConfigAll(); + mockModifyGameModeGranted(); + + GameManagerService gameManagerService = new GameManagerService( + mMockContext, mTestLooper.getLooper()); + startUser(gameManagerService, USER_ID_1); + gameManagerService.setGameModeConfigOverride(mPackageName, USER_ID_1, + GameManager.GAME_MODE_PERFORMANCE, "120", "0.3"); + gameManagerService.setGameModeConfigOverride(mPackageName, USER_ID_1, + GameManager.GAME_MODE_BATTERY, "60", "0.5"); + + gameManagerService.resetGameModeConfigOverride(mPackageName, USER_ID_1, -1); + + checkReportedModes(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, + GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_STANDARD); + checkDownscaling(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, "0.5"); + checkFps(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, 90); + checkDownscaling(gameManagerService, GameManager.GAME_MODE_BATTERY, "0.7"); + checkFps(gameManagerService, GameManager.GAME_MODE_BATTERY, 30); + } + + /** + * Override device configs for both battery and performance modes exists and are valid. + * Only one mode is reset, and the other mode still has overridden config + */ + @Test + public void testResetDeviceOverrideConfigPartial() { + mockDeviceConfigAll(); + mockModifyGameModeGranted(); + + GameManagerService gameManagerService = new GameManagerService( + mMockContext, mTestLooper.getLooper()); + startUser(gameManagerService, USER_ID_1); + gameManagerService.setGameModeConfigOverride(mPackageName, USER_ID_1, + GameManager.GAME_MODE_PERFORMANCE, "120", "0.3"); + gameManagerService.setGameModeConfigOverride(mPackageName, USER_ID_1, + GameManager.GAME_MODE_BATTERY, "60", "0.5"); + + gameManagerService.resetGameModeConfigOverride(mPackageName, USER_ID_1, + GameManager.GAME_MODE_BATTERY); + + checkReportedModes(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, + GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_STANDARD); + checkDownscaling(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, "0.3"); + checkFps(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, 120); + checkDownscaling(gameManagerService, GameManager.GAME_MODE_BATTERY, "0.7"); + checkFps(gameManagerService, GameManager.GAME_MODE_BATTERY, 30); } /** @@ -575,10 +753,12 @@ public class GameManagerServiceTests { mockGameModeOptInAll(); mockDeviceConfigNone(); mockModifyGameModeGranted(); - checkReportedModes(GameManager.GAME_MODE_PERFORMANCE, GameManager.GAME_MODE_BATTERY, + checkReportedModes(null, GameManager.GAME_MODE_PERFORMANCE, GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_STANDARD); } + + /** * BATTERY game mode is available through the app manifest opt-in. */ @@ -587,7 +767,7 @@ public class GameManagerServiceTests { mockGameModeOptInBattery(); mockDeviceConfigNone(); mockModifyGameModeGranted(); - checkReportedModes(GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_STANDARD); + checkReportedModes(null, GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_STANDARD); } /** @@ -598,7 +778,7 @@ public class GameManagerServiceTests { mockGameModeOptInPerformance(); mockDeviceConfigNone(); mockModifyGameModeGranted(); - checkReportedModes(GameManager.GAME_MODE_PERFORMANCE, GameManager.GAME_MODE_STANDARD); + checkReportedModes(null, GameManager.GAME_MODE_PERFORMANCE, GameManager.GAME_MODE_STANDARD); } /** @@ -610,7 +790,7 @@ public class GameManagerServiceTests { mockGameModeOptInBattery(); mockDeviceConfigPerformance(); mockModifyGameModeGranted(); - checkReportedModes(GameManager.GAME_MODE_PERFORMANCE, GameManager.GAME_MODE_BATTERY, + checkReportedModes(null, GameManager.GAME_MODE_PERFORMANCE, GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_STANDARD); } @@ -623,7 +803,7 @@ public class GameManagerServiceTests { mockGameModeOptInPerformance(); mockDeviceConfigBattery(); mockModifyGameModeGranted(); - checkReportedModes(GameManager.GAME_MODE_PERFORMANCE, GameManager.GAME_MODE_BATTERY, + checkReportedModes(null, GameManager.GAME_MODE_PERFORMANCE, GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_STANDARD); } @@ -634,7 +814,7 @@ public class GameManagerServiceTests { public void testInterventionAllowScalingDefault() throws Exception { mockDeviceConfigPerformance(); mockModifyGameModeGranted(); - checkDownscaling(GameManager.GAME_MODE_PERFORMANCE, "0.5"); + checkDownscaling(null, GameManager.GAME_MODE_PERFORMANCE, "0.5"); } /** @@ -645,7 +825,7 @@ public class GameManagerServiceTests { mockDeviceConfigPerformance(); mockInterventionAllowDownscaleFalse(); mockModifyGameModeGranted(); - checkDownscaling(GameManager.GAME_MODE_PERFORMANCE, "1.0"); + checkDownscaling(null, GameManager.GAME_MODE_PERFORMANCE, "1.0"); } /** @@ -657,7 +837,7 @@ public class GameManagerServiceTests { mockDeviceConfigPerformance(); mockInterventionAllowDownscaleTrue(); mockModifyGameModeGranted(); - checkDownscaling(GameManager.GAME_MODE_PERFORMANCE, "0.5"); + checkDownscaling(null, GameManager.GAME_MODE_PERFORMANCE, "0.5"); } /** @@ -708,6 +888,14 @@ public class GameManagerServiceTests { checkAngleEnabled(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, true); } + @Test + public void testInterventionFps() throws Exception { + mockDeviceConfigAll(); + mockModifyGameModeGranted(); + checkFps(null, GameManager.GAME_MODE_PERFORMANCE, 90); + checkFps(null, GameManager.GAME_MODE_BATTERY, 30); + } + /** * PERFORMANCE game mode is configured through Phenotype, but the app has also opted into the * same mode. No interventions for this game mode should be available in this case. @@ -776,4 +964,88 @@ public class GameManagerServiceTests { assertEquals(GameManager.GAME_MODE_STANDARD, gameManagerService.getGameMode(mPackageName, USER_ID_1)); } + + static { + System.loadLibrary("mockingservicestestjni"); + } + @Test + public void testGetGameModeInfoPermissionDenied() { + mockDeviceConfigAll(); + GameManagerService gameManagerService = + new GameManagerService(mMockContext, mTestLooper.getLooper()); + startUser(gameManagerService, USER_ID_1); + + // Deny permission.MANAGE_GAME_MODE and verify the game mode is not updated. + mockModifyGameModeDenied(); + assertThrows(SecurityException.class, + () -> gameManagerService.getGameModeInfo(mPackageName, USER_ID_1)); + } + + @Test + public void testGetGameModeInfoWithAllGameModesDefault() { + mockDeviceConfigAll(); + mockModifyGameModeGranted(); + GameManagerService gameManagerService = + new GameManagerService(mMockContext, mTestLooper.getLooper()); + startUser(gameManagerService, USER_ID_1); + GameModeInfo gameModeInfo = gameManagerService.getGameModeInfo(mPackageName, USER_ID_1); + + assertEquals(GameManager.GAME_MODE_STANDARD, gameModeInfo.getActiveGameMode()); + assertEquals(3, gameModeInfo.getAvailableGameModes().length); + } + + @Test + public void testGetGameModeInfoWithAllGameModes() { + mockDeviceConfigAll(); + mockModifyGameModeGranted(); + GameManagerService gameManagerService = + new GameManagerService(mMockContext, mTestLooper.getLooper()); + startUser(gameManagerService, USER_ID_1); + gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1); + GameModeInfo gameModeInfo = gameManagerService.getGameModeInfo(mPackageName, USER_ID_1); + + assertEquals(GameManager.GAME_MODE_PERFORMANCE, gameModeInfo.getActiveGameMode()); + assertEquals(3, gameModeInfo.getAvailableGameModes().length); + } + + @Test + public void testGetGameModeInfoWithBatteryMode() { + mockDeviceConfigBattery(); + mockModifyGameModeGranted(); + GameManagerService gameManagerService = + new GameManagerService(mMockContext, mTestLooper.getLooper()); + startUser(gameManagerService, USER_ID_1); + gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_BATTERY, USER_ID_1); + GameModeInfo gameModeInfo = gameManagerService.getGameModeInfo(mPackageName, USER_ID_1); + + assertEquals(GameManager.GAME_MODE_BATTERY, gameModeInfo.getActiveGameMode()); + assertEquals(2, gameModeInfo.getAvailableGameModes().length); + } + + @Test + public void testGetGameModeInfoWithPerformanceMode() { + mockDeviceConfigPerformance(); + mockModifyGameModeGranted(); + GameManagerService gameManagerService = + new GameManagerService(mMockContext, mTestLooper.getLooper()); + startUser(gameManagerService, USER_ID_1); + gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1); + GameModeInfo gameModeInfo = gameManagerService.getGameModeInfo(mPackageName, USER_ID_1); + + assertEquals(GameManager.GAME_MODE_PERFORMANCE, gameModeInfo.getActiveGameMode()); + assertEquals(2, gameModeInfo.getAvailableGameModes().length); + } + + @Test + public void testGetGameModeInfoWithUnsupportedGameMode() { + mockDeviceConfigNone(); + mockModifyGameModeGranted(); + GameManagerService gameManagerService = + new GameManagerService(mMockContext, mTestLooper.getLooper()); + startUser(gameManagerService, USER_ID_1); + GameModeInfo gameModeInfo = gameManagerService.getGameModeInfo(mPackageName, USER_ID_1); + + assertEquals(GameManager.GAME_MODE_UNSUPPORTED, gameModeInfo.getActiveGameMode()); + assertEquals(0, gameModeInfo.getAvailableGameModes().length); + } } diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java b/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java index 0d513bb2d68c..bdfa3bfedb55 100644 --- a/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java @@ -18,29 +18,43 @@ package com.android.server.app; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; +import static com.android.server.app.GameServiceProviderInstanceImplTest.FakeGameService.GameServiceState; +import static com.google.common.collect.Iterables.getOnlyElement; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; import android.annotation.Nullable; +import android.app.ActivityManager.RunningTaskInfo; +import android.app.ActivityTaskManager; import android.app.IActivityTaskManager; import android.app.ITaskStackListener; import android.content.ComponentName; import android.content.pm.PackageManager; -import android.os.IBinder; +import android.graphics.Bitmap; +import android.graphics.Rect; import android.os.RemoteException; import android.os.UserHandle; import android.platform.test.annotations.Presubmit; import android.service.games.CreateGameSessionRequest; +import android.service.games.CreateGameSessionResult; +import android.service.games.GameScreenshotResult; +import android.service.games.GameSessionViewHostConfiguration; import android.service.games.GameStartedEvent; import android.service.games.IGameService; import android.service.games.IGameServiceController; import android.service.games.IGameSession; +import android.service.games.IGameSessionController; import android.service.games.IGameSessionService; +import android.view.SurfaceControlViewHost.SurfacePackage; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; @@ -48,20 +62,21 @@ import androidx.test.runner.AndroidJUnit4; import com.android.internal.infra.AndroidFuture; import com.android.internal.util.ConcurrentUtils; import com.android.internal.util.FunctionalUtils.ThrowingConsumer; +import com.android.internal.util.Preconditions; +import com.android.server.wm.WindowManagerInternal; +import com.android.server.wm.WindowManagerService; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.InOrder; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.MockitoSession; import org.mockito.quality.Strictness; import java.util.ArrayList; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Supplier; +import java.util.HashMap; /** @@ -72,6 +87,9 @@ import java.util.function.Supplier; @Presubmit public final class GameServiceProviderInstanceImplTest { + private static final GameSessionViewHostConfiguration + DEFAULT_GAME_SESSION_VIEW_HOST_CONFIGURATION = + new GameSessionViewHostConfiguration(1, 500, 800); private static final int USER_ID = 10; private static final String APP_A_PACKAGE = "com.package.app.a"; private static final ComponentName APP_A_MAIN_ACTIVITY = @@ -81,19 +99,23 @@ public final class GameServiceProviderInstanceImplTest { private static final ComponentName GAME_A_MAIN_ACTIVITY = new ComponentName(GAME_A_PACKAGE, "com.package.game.a.MainActivity"); + private static final Bitmap TEST_BITMAP = Bitmap.createBitmap(32, 32, Bitmap.Config.ARGB_8888); + private MockitoSession mMockingSession; private GameServiceProviderInstance mGameServiceProviderInstance; @Mock private IActivityTaskManager mMockActivityTaskManager; @Mock - private IGameService mMockGameService; + private WindowManagerService mMockWindowManagerService; @Mock - private IGameSessionService mMockGameSessionService; + private WindowManagerInternal mMockWindowManagerInternal; private FakeGameClassifier mFakeGameClassifier; + private FakeGameService mFakeGameService; private FakeServiceConnector<IGameService> mFakeGameServiceConnector; + private FakeGameSessionService mFakeGameSessionService; private FakeServiceConnector<IGameSessionService> mFakeGameSessionServiceConnector; private ArrayList<ITaskStackListener> mTaskStackListeners; - private InOrder mInOrder; + private ArrayList<RunningTaskInfo> mRunningTaskInfos; @Before public void setUp() throws PackageManager.NameNotFoundException, RemoteException { @@ -102,13 +124,13 @@ public final class GameServiceProviderInstanceImplTest { .strictness(Strictness.LENIENT) .startMocking(); - mInOrder = inOrder(mMockGameService, mMockGameSessionService); - mFakeGameClassifier = new FakeGameClassifier(); mFakeGameClassifier.recordGamePackage(GAME_A_PACKAGE); - mFakeGameServiceConnector = new FakeServiceConnector<>(mMockGameService); - mFakeGameSessionServiceConnector = new FakeServiceConnector<>(mMockGameSessionService); + mFakeGameService = new FakeGameService(); + mFakeGameServiceConnector = new FakeServiceConnector<>(mFakeGameService); + mFakeGameSessionService = new FakeGameSessionService(); + mFakeGameSessionServiceConnector = new FakeServiceConnector<>(mFakeGameSessionService); mTaskStackListeners = new ArrayList<>(); doAnswer(invocation -> { @@ -116,6 +138,10 @@ public final class GameServiceProviderInstanceImplTest { return null; }).when(mMockActivityTaskManager).registerTaskStackListener(any()); + mRunningTaskInfos = new ArrayList<>(); + when(mMockActivityTaskManager.getTasks(anyInt(), anyBoolean(), anyBoolean())).thenReturn( + mRunningTaskInfos); + doAnswer(invocation -> { mTaskStackListeners.remove(invocation.getArgument(0)); return null; @@ -126,6 +152,8 @@ public final class GameServiceProviderInstanceImplTest { ConcurrentUtils.DIRECT_EXECUTOR, mFakeGameClassifier, mMockActivityTaskManager, + mMockWindowManagerService, + mMockWindowManagerInternal, mFakeGameServiceConnector, mFakeGameSessionServiceConnector); } @@ -139,8 +167,7 @@ public final class GameServiceProviderInstanceImplTest { public void start_startsGameSession() throws Exception { mGameServiceProviderInstance.start(); - mInOrder.verify(mMockGameService).connected(any()); - mInOrder.verifyNoMoreInteractions(); + assertThat(mFakeGameService.getState()).isEqualTo(GameServiceState.CONNECTED); assertThat(mFakeGameServiceConnector.getIsConnected()).isTrue(); assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1); assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(0); @@ -149,9 +176,9 @@ public final class GameServiceProviderInstanceImplTest { @Test public void start_multipleTimes_startsGameSessionOnce() throws Exception { mGameServiceProviderInstance.start(); + mGameServiceProviderInstance.start(); - mInOrder.verify(mMockGameService).connected(any()); - mInOrder.verifyNoMoreInteractions(); + assertThat(mFakeGameService.getState()).isEqualTo(GameServiceState.CONNECTED); assertThat(mFakeGameServiceConnector.getIsConnected()).isTrue(); assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1); assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(0); @@ -161,9 +188,10 @@ public final class GameServiceProviderInstanceImplTest { public void stop_neverStarted_doesNothing() throws Exception { mGameServiceProviderInstance.stop(); + + assertThat(mFakeGameService.getState()).isEqualTo(GameServiceState.DISCONNECTED); assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(0); assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(0); - mInOrder.verifyNoMoreInteractions(); } @Test @@ -171,9 +199,8 @@ public final class GameServiceProviderInstanceImplTest { mGameServiceProviderInstance.start(); mGameServiceProviderInstance.stop(); - mInOrder.verify(mMockGameService).connected(any()); - mInOrder.verify(mMockGameService).disconnected(); - mInOrder.verifyNoMoreInteractions(); + assertThat(mFakeGameService.getState()).isEqualTo(GameServiceState.DISCONNECTED); + assertThat(mFakeGameService.getConnectedCount()).isEqualTo(1); assertThat(mFakeGameServiceConnector.getIsConnected()).isFalse(); assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1); assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(0); @@ -187,11 +214,8 @@ public final class GameServiceProviderInstanceImplTest { mGameServiceProviderInstance.start(); mGameServiceProviderInstance.stop(); - mInOrder.verify(mMockGameService).connected(any()); - mInOrder.verify(mMockGameService).disconnected(); - mInOrder.verify(mMockGameService).connected(any()); - mInOrder.verify(mMockGameService).disconnected(); - mInOrder.verifyNoMoreInteractions(); + assertThat(mFakeGameService.getState()).isEqualTo(GameServiceState.DISCONNECTED); + assertThat(mFakeGameService.getConnectedCount()).isEqualTo(2); assertThat(mFakeGameServiceConnector.getIsConnected()).isFalse(); assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(2); assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(0); @@ -203,9 +227,8 @@ public final class GameServiceProviderInstanceImplTest { mGameServiceProviderInstance.stop(); mGameServiceProviderInstance.stop(); - mInOrder.verify(mMockGameService).connected(any()); - mInOrder.verify(mMockGameService).disconnected(); - mInOrder.verifyNoMoreInteractions(); + assertThat(mFakeGameService.getState()).isEqualTo(GameServiceState.DISCONNECTED); + assertThat(mFakeGameService.getConnectedCount()).isEqualTo(1); assertThat(mFakeGameServiceConnector.getIsConnected()).isFalse(); assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1); assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(0); @@ -215,7 +238,6 @@ public final class GameServiceProviderInstanceImplTest { public void gameTaskStarted_neverStarted_doesNothing() throws Exception { dispatchTaskCreated(10, GAME_A_MAIN_ACTIVITY); - mInOrder.verifyNoMoreInteractions(); assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(0); assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(0); } @@ -224,35 +246,25 @@ public final class GameServiceProviderInstanceImplTest { public void gameTaskRemoved_neverStarted_doesNothing() throws Exception { dispatchTaskRemoved(10); - mInOrder.verifyNoMoreInteractions(); assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(0); assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(0); } @Test - public void gameTaskStarted_afterStopped_doesNothing() throws Exception { + public void gameTaskStarted_afterStopped_doesNotSendGameStartedEvent() throws Exception { mGameServiceProviderInstance.start(); mGameServiceProviderInstance.stop(); dispatchTaskCreated(10, GAME_A_MAIN_ACTIVITY); - mInOrder.verify(mMockGameService).connected(any()); - mInOrder.verify(mMockGameService).disconnected(); - mInOrder.verifyNoMoreInteractions(); - assertThat(mFakeGameServiceConnector.getIsConnected()).isFalse(); - assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1); - assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(0); + assertThat(mFakeGameService.getGameStartedEvents()).isEmpty(); } @Test - public void appTaskStarted_doesNothing() throws Exception { + public void appTaskStarted_doesNotSendGameStartedEvent() throws Exception { mGameServiceProviderInstance.start(); dispatchTaskCreated(10, APP_A_MAIN_ACTIVITY); - mInOrder.verify(mMockGameService).connected(any()); - mInOrder.verifyNoMoreInteractions(); - assertThat(mFakeGameServiceConnector.getIsConnected()).isTrue(); - assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1); - assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(0); + assertThat(mFakeGameService.getGameStartedEvents()).isEmpty(); } @Test @@ -260,26 +272,17 @@ public final class GameServiceProviderInstanceImplTest { mGameServiceProviderInstance.start(); dispatchTaskCreated(10, null); - mInOrder.verify(mMockGameService).connected(any()); - mInOrder.verifyNoMoreInteractions(); - assertThat(mFakeGameServiceConnector.getIsConnected()).isTrue(); - assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1); - assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(0); + assertThat(mFakeGameService.getGameStartedEvents()).isEmpty(); } @Test - public void gameSessionRequested_withoutTaskDispatch_ignoredAndDoesNotCrash() throws Exception { + public void gameSessionRequested_withoutTaskDispatch_doesNotCrashAndDoesNotCreateGameSession() + throws Exception { mGameServiceProviderInstance.start(); - ArgumentCaptor<IGameServiceController> controllerArgumentCaptor = ArgumentCaptor.forClass( - IGameServiceController.class); - verify(mMockGameService).connected(controllerArgumentCaptor.capture()); - controllerArgumentCaptor.getValue().createGameSession(10); - mInOrder.verify(mMockGameService).connected(any()); - mInOrder.verifyNoMoreInteractions(); - assertThat(mFakeGameServiceConnector.getIsConnected()).isTrue(); - assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1); - assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(0); + mFakeGameService.requestCreateGameSession(10); + + assertThat(mFakeGameSessionService.getCapturedCreateInvocations()).isEmpty(); } @Test @@ -287,433 +290,410 @@ public final class GameServiceProviderInstanceImplTest { mGameServiceProviderInstance.start(); dispatchTaskCreated(10, GAME_A_MAIN_ACTIVITY); - mInOrder.verify(mMockGameService).connected(any()); - mInOrder.verify(mMockGameService).gameStarted( - eq(new GameStartedEvent(10, GAME_A_PACKAGE))); - mInOrder.verifyNoMoreInteractions(); - assertThat(mFakeGameServiceConnector.getIsConnected()).isTrue(); - assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1); - assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(0); + GameStartedEvent expectedGameStartedEvent = new GameStartedEvent(10, GAME_A_PACKAGE); + assertThat(mFakeGameService.getGameStartedEvents()) + .containsExactly(expectedGameStartedEvent).inOrder(); } @Test - public void gameTaskStartedAndSessionRequested_createsGameSession() throws Exception { - CreateGameSessionRequest createGameSessionRequest = - new CreateGameSessionRequest(10, GAME_A_PACKAGE); - Supplier<AndroidFuture<IBinder>> gameSession10Future = - captureCreateGameSessionFuture(createGameSessionRequest); + public void gameTaskStarted_requestToCreateGameSessionIncludesTaskConfiguration() + throws Exception { + mGameServiceProviderInstance.start(); + startTask(10, GAME_A_MAIN_ACTIVITY); + mFakeGameService.requestCreateGameSession(10); + + FakeGameSessionService.CapturedCreateInvocation capturedCreateInvocation = + getOnlyElement(mFakeGameSessionService.getCapturedCreateInvocations()); + assertThat(capturedCreateInvocation.mGameSessionViewHostConfiguration) + .isEqualTo(DEFAULT_GAME_SESSION_VIEW_HOST_CONFIGURATION); + } + + @Test + public void gameTaskStarted_failsToDetermineTaskOverlayConfiguration_gameSessionNotCreated() + throws Exception { + mGameServiceProviderInstance.start(); + dispatchTaskCreated(10, GAME_A_MAIN_ACTIVITY); + + mFakeGameService.requestCreateGameSession(10); + + assertThat(mFakeGameSessionService.getCapturedCreateInvocations()).isEmpty(); + } + + @Test + public void gameTaskStartedAndSessionRequested_createsGameSession() throws Exception { mGameServiceProviderInstance.start(); - ArgumentCaptor<IGameServiceController> controllerArgumentCaptor = ArgumentCaptor.forClass( - IGameServiceController.class); - verify(mMockGameService).connected(controllerArgumentCaptor.capture()); - dispatchTaskCreatedAndTriggerSessionRequest(10, GAME_A_MAIN_ACTIVITY, - controllerArgumentCaptor.getValue()); - IGameSessionStub gameSession10 = new IGameSessionStub(); - gameSession10Future.get().complete(gameSession10); - - mInOrder.verify(mMockGameService).connected(any()); - mInOrder.verify(mMockGameService).gameStarted( - eq(new GameStartedEvent(10, GAME_A_PACKAGE))); - mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest), any()); - mInOrder.verifyNoMoreInteractions(); + startTask(10, GAME_A_MAIN_ACTIVITY); + mFakeGameService.requestCreateGameSession(10); + + FakeGameSession gameSession10 = new FakeGameSession(); + SurfacePackage mockSurfacePackage10 = Mockito.mock(SurfacePackage.class); + mFakeGameSessionService.removePendingFutureForTaskId(10) + .complete(new CreateGameSessionResult(gameSession10, mockSurfacePackage10)); + assertThat(gameSession10.mIsDestroyed).isFalse(); - assertThat(mFakeGameServiceConnector.getIsConnected()).isTrue(); - assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1); - assertThat(mFakeGameSessionServiceConnector.getIsConnected()).isTrue(); - assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(1); + assertThat(gameSession10.mIsFocused).isFalse(); } @Test public void gameTaskStartedAndSessionRequested_secondSessionRequest_ignoredAndDoesNotCrash() throws Exception { - CreateGameSessionRequest createGameSessionRequest = - new CreateGameSessionRequest(10, GAME_A_PACKAGE); - Supplier<AndroidFuture<IBinder>> gameSession10Future = - captureCreateGameSessionFuture(createGameSessionRequest); + mGameServiceProviderInstance.start(); + startTask(10, GAME_A_MAIN_ACTIVITY); + mFakeGameService.requestCreateGameSession(10); + mFakeGameService.requestCreateGameSession(10); + + CreateGameSessionRequest expectedCreateGameSessionRequest = new CreateGameSessionRequest(10, + GAME_A_PACKAGE); + assertThat(getOnlyElement( + mFakeGameSessionService.getCapturedCreateInvocations()).mCreateGameSessionRequest) + .isEqualTo(expectedCreateGameSessionRequest); + } + + @Test + public void gameSessionSuccessfullyCreated_createsTaskOverlay() throws Exception { mGameServiceProviderInstance.start(); - ArgumentCaptor<IGameServiceController> controllerArgumentCaptor = ArgumentCaptor.forClass( - IGameServiceController.class); - verify(mMockGameService).connected(controllerArgumentCaptor.capture()); - dispatchTaskCreatedAndTriggerSessionRequest(10, GAME_A_MAIN_ACTIVITY, - controllerArgumentCaptor.getValue()); - IGameSessionStub gameSession10 = new IGameSessionStub(); - gameSession10Future.get().complete(gameSession10); - - controllerArgumentCaptor.getValue().createGameSession(10); - - mInOrder.verify(mMockGameService).connected(any()); - mInOrder.verify(mMockGameService).gameStarted( - eq(new GameStartedEvent(10, GAME_A_PACKAGE))); - mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest), any()); - mInOrder.verifyNoMoreInteractions(); - assertThat(gameSession10.mIsDestroyed).isFalse(); - assertThat(mFakeGameServiceConnector.getIsConnected()).isTrue(); - assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1); - assertThat(mFakeGameSessionServiceConnector.getIsConnected()).isTrue(); - assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(1); + startTask(10, GAME_A_MAIN_ACTIVITY); + mFakeGameService.requestCreateGameSession(10); + + FakeGameSession gameSession10 = new FakeGameSession(); + SurfacePackage mockSurfacePackage10 = Mockito.mock(SurfacePackage.class); + mFakeGameSessionService.removePendingFutureForTaskId(10) + .complete(new CreateGameSessionResult(gameSession10, mockSurfacePackage10)); + + verify(mMockWindowManagerInternal).addTaskOverlay(eq(10), eq(mockSurfacePackage10)); + verifyNoMoreInteractions(mMockWindowManagerInternal); } @Test - public void gameTaskRemoved_whileAwaitingGameSessionAttached_destroysGameSession() + public void gameTaskFocused_propagatedToGameSession() throws Exception { + mGameServiceProviderInstance.start(); + startTask(10, GAME_A_MAIN_ACTIVITY); + mFakeGameService.requestCreateGameSession(10); + + FakeGameSession gameSession10 = new FakeGameSession(); + SurfacePackage mockSurfacePackage10 = Mockito.mock(SurfacePackage.class); + mFakeGameSessionService.removePendingFutureForTaskId(10) + .complete(new CreateGameSessionResult(gameSession10, mockSurfacePackage10)); + + assertThat(gameSession10.mIsFocused).isFalse(); + + dispatchTaskFocused(10, /*focused=*/ true); + assertThat(gameSession10.mIsFocused).isTrue(); + + dispatchTaskFocused(10, /*focused=*/ false); + assertThat(gameSession10.mIsFocused).isFalse(); + } + + @Test + public void gameTaskAlreadyFocusedWhenGameSessionCreated_propagatedToGameSession() throws Exception { - CreateGameSessionRequest createGameSessionRequest = - new CreateGameSessionRequest(10, GAME_A_PACKAGE); - Supplier<AndroidFuture<IBinder>> gameSession10Future = - captureCreateGameSessionFuture(createGameSessionRequest); + ActivityTaskManager.RootTaskInfo gameATaskInfo = new ActivityTaskManager.RootTaskInfo(); + gameATaskInfo.taskId = 10; + when(mMockActivityTaskManager.getFocusedRootTaskInfo()).thenReturn(gameATaskInfo); mGameServiceProviderInstance.start(); - ArgumentCaptor<IGameServiceController> controllerArgumentCaptor = ArgumentCaptor.forClass( - IGameServiceController.class); - verify(mMockGameService).connected(controllerArgumentCaptor.capture()); - dispatchTaskCreatedAndTriggerSessionRequest(10, GAME_A_MAIN_ACTIVITY, - controllerArgumentCaptor.getValue()); + startTask(10, GAME_A_MAIN_ACTIVITY); + mFakeGameService.requestCreateGameSession(10); + + FakeGameSession gameSession10 = new FakeGameSession(); + SurfacePackage mockSurfacePackage10 = Mockito.mock(SurfacePackage.class); + mFakeGameSessionService.removePendingFutureForTaskId(10) + .complete(new CreateGameSessionResult(gameSession10, mockSurfacePackage10)); + + assertThat(gameSession10.mIsFocused).isTrue(); + } + + @Test + public void gameTaskRemoved_whileAwaitingGameSessionAttached_destroysGameSession() + throws Exception { + mGameServiceProviderInstance.start(); + + startTask(10, GAME_A_MAIN_ACTIVITY); + mFakeGameService.requestCreateGameSession(10); + dispatchTaskRemoved(10); - IGameSessionStub gameSession10 = new IGameSessionStub(); - gameSession10Future.get().complete(gameSession10); - - mInOrder.verify(mMockGameService).connected(any()); - mInOrder.verify(mMockGameService).gameStarted( - eq(new GameStartedEvent(10, GAME_A_PACKAGE))); - mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest), any()); - mInOrder.verifyNoMoreInteractions(); + + FakeGameSession gameSession10 = new FakeGameSession(); + SurfacePackage mockSurfacePackage10 = Mockito.mock(SurfacePackage.class); + mFakeGameSessionService.removePendingFutureForTaskId(10) + .complete(new CreateGameSessionResult(gameSession10, mockSurfacePackage10)); + assertThat(gameSession10.mIsDestroyed).isTrue(); - assertThat(mFakeGameServiceConnector.getIsConnected()).isTrue(); - assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1); - assertThat(mFakeGameSessionServiceConnector.getIsConnected()).isFalse(); - assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(1); } @Test - public void gameTaskRemoved_destroysGameSession() throws Exception { - CreateGameSessionRequest createGameSessionRequest = - new CreateGameSessionRequest(10, GAME_A_PACKAGE); - Supplier<AndroidFuture<IBinder>> gameSession10Future = - captureCreateGameSessionFuture(createGameSessionRequest); - + public void gameTaskRemoved_whileGameSessionAttached_destroysGameSession() throws Exception { mGameServiceProviderInstance.start(); - ArgumentCaptor<IGameServiceController> controllerArgumentCaptor = ArgumentCaptor.forClass( - IGameServiceController.class); - verify(mMockGameService).connected(controllerArgumentCaptor.capture()); - dispatchTaskCreatedAndTriggerSessionRequest(10, GAME_A_MAIN_ACTIVITY, - controllerArgumentCaptor.getValue()); - IGameSessionStub gameSession10 = new IGameSessionStub(); - gameSession10Future.get().complete(gameSession10); + + startTask(10, GAME_A_MAIN_ACTIVITY); + mFakeGameService.requestCreateGameSession(10); + + FakeGameSession gameSession10 = new FakeGameSession(); + SurfacePackage mockSurfacePackage10 = Mockito.mock(SurfacePackage.class); + mFakeGameSessionService.removePendingFutureForTaskId(10) + .complete(new CreateGameSessionResult(gameSession10, mockSurfacePackage10)); + dispatchTaskRemoved(10); - mInOrder.verify(mMockGameService).connected(any()); - mInOrder.verify(mMockGameService).gameStarted( - eq(new GameStartedEvent(10, GAME_A_PACKAGE))); - mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest), any()); - mInOrder.verifyNoMoreInteractions(); assertThat(gameSession10.mIsDestroyed).isTrue(); - assertThat(mFakeGameServiceConnector.getIsConnected()).isTrue(); - assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1); - assertThat(mFakeGameSessionServiceConnector.getIsConnected()).isFalse(); - assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(1); + } + + @Test + public void gameTaskRemoved_removesTaskOverlay() throws Exception { + mGameServiceProviderInstance.start(); + + startTask(10, GAME_A_MAIN_ACTIVITY); + mFakeGameService.requestCreateGameSession(10); + + FakeGameSession gameSession10 = new FakeGameSession(); + SurfacePackage mockSurfacePackage10 = Mockito.mock(SurfacePackage.class); + mFakeGameSessionService.removePendingFutureForTaskId(10) + .complete(new CreateGameSessionResult(gameSession10, mockSurfacePackage10)); + + stopTask(10); + + verify(mMockWindowManagerInternal).addTaskOverlay(eq(10), eq(mockSurfacePackage10)); + verify(mMockWindowManagerInternal).removeTaskOverlay(eq(10), eq(mockSurfacePackage10)); + verifyNoMoreInteractions(mMockWindowManagerInternal); } @Test public void gameTaskStartedAndSessionRequested_multipleTimes_createsMultipleGameSessions() throws Exception { - CreateGameSessionRequest createGameSessionRequest10 = - new CreateGameSessionRequest(10, GAME_A_PACKAGE); - Supplier<AndroidFuture<IBinder>> gameSession10Future = - captureCreateGameSessionFuture(createGameSessionRequest10); + mGameServiceProviderInstance.start(); - CreateGameSessionRequest createGameSessionRequest11 = - new CreateGameSessionRequest(11, GAME_A_PACKAGE); - Supplier<AndroidFuture<IBinder>> gameSession11Future = - captureCreateGameSessionFuture(createGameSessionRequest11); + startTask(10, GAME_A_MAIN_ACTIVITY); + mFakeGameService.requestCreateGameSession(10); + + FakeGameSession gameSession10 = new FakeGameSession(); + SurfacePackage mockSurfacePackage10 = Mockito.mock(SurfacePackage.class); + mFakeGameSessionService.removePendingFutureForTaskId(10) + .complete(new CreateGameSessionResult(gameSession10, mockSurfacePackage10)); + + startTask(11, GAME_A_MAIN_ACTIVITY); + mFakeGameService.requestCreateGameSession(11); + + FakeGameSession gameSession11 = new FakeGameSession(); + SurfacePackage mockSurfacePackage11 = Mockito.mock(SurfacePackage.class); + mFakeGameSessionService.removePendingFutureForTaskId(11) + .complete(new CreateGameSessionResult(gameSession11, mockSurfacePackage11)); - mGameServiceProviderInstance.start(); - ArgumentCaptor<IGameServiceController> controllerArgumentCaptor = ArgumentCaptor.forClass( - IGameServiceController.class); - verify(mMockGameService).connected(controllerArgumentCaptor.capture()); - dispatchTaskCreatedAndTriggerSessionRequest(10, GAME_A_MAIN_ACTIVITY, - controllerArgumentCaptor.getValue()); - IGameSessionStub gameSession10 = new IGameSessionStub(); - gameSession10Future.get().complete(gameSession10); - - dispatchTaskCreatedAndTriggerSessionRequest(11, GAME_A_MAIN_ACTIVITY, - controllerArgumentCaptor.getValue()); - IGameSessionStub gameSession11 = new IGameSessionStub(); - gameSession11Future.get().complete(gameSession11); - - mInOrder.verify(mMockGameService).connected(any()); - mInOrder.verify(mMockGameService).gameStarted( - eq(new GameStartedEvent(10, GAME_A_PACKAGE))); - mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest10), any()); - mInOrder.verify(mMockGameService).gameStarted( - eq(new GameStartedEvent(11, GAME_A_PACKAGE))); - mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest11), any()); - mInOrder.verifyNoMoreInteractions(); assertThat(gameSession10.mIsDestroyed).isFalse(); assertThat(gameSession11.mIsDestroyed).isFalse(); - assertThat(mFakeGameServiceConnector.getIsConnected()).isTrue(); - assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1); - assertThat(mFakeGameSessionServiceConnector.getIsConnected()).isTrue(); - assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(1); } @Test - public void gameTaskStartedTwice_sessionRequestedSecondTimeOnly_createsOneGameSessions() + public void gameTaskStartedTwice_sessionRequestedSecondTimeOnly_createsOneGameSession() throws Exception { - CreateGameSessionRequest createGameSessionRequest11 = - new CreateGameSessionRequest(11, GAME_A_PACKAGE); - Supplier<AndroidFuture<IBinder>> gameSession11Future = - captureCreateGameSessionFuture(createGameSessionRequest11); - - // The game task is started twice, but a session is requested only for the second one. mGameServiceProviderInstance.start(); - ArgumentCaptor<IGameServiceController> controllerArgumentCaptor = ArgumentCaptor.forClass( - IGameServiceController.class); - verify(mMockGameService).connected(controllerArgumentCaptor.capture()); - dispatchTaskCreated(10, GAME_A_MAIN_ACTIVITY); - dispatchTaskCreatedAndTriggerSessionRequest(11, GAME_A_MAIN_ACTIVITY, - controllerArgumentCaptor.getValue()); - IGameSessionStub gameSession11 = new IGameSessionStub(); - gameSession11Future.get().complete(gameSession11); - - mInOrder.verify(mMockGameService).connected(any()); - mInOrder.verify(mMockGameService).gameStarted( - eq(new GameStartedEvent(10, GAME_A_PACKAGE))); - mInOrder.verify(mMockGameService).gameStarted( - eq(new GameStartedEvent(11, GAME_A_PACKAGE))); - mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest11), any()); - mInOrder.verifyNoMoreInteractions(); - assertThat(gameSession11.mIsDestroyed).isFalse(); - assertThat(mFakeGameServiceConnector.getIsConnected()).isTrue(); - assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1); - assertThat(mFakeGameSessionServiceConnector.getIsConnected()).isTrue(); - assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(1); + startTask(10, GAME_A_MAIN_ACTIVITY); + startTask(11, GAME_A_MAIN_ACTIVITY); + + mFakeGameService.requestCreateGameSession(10); + + FakeGameSession gameSession10 = new FakeGameSession(); + SurfacePackage mockSurfacePackage10 = Mockito.mock(SurfacePackage.class); + mFakeGameSessionService.removePendingFutureForTaskId(10) + .complete(new CreateGameSessionResult(gameSession10, mockSurfacePackage10)); + + assertThat(gameSession10.mIsDestroyed).isFalse(); + assertThat(mFakeGameSessionService.getCapturedCreateInvocations()).hasSize(1); } @Test - public void gameTaskRemoved_afterMultipleCreated_destroysOnlyThatGameSession() + public void gameTaskRemoved_multipleSessions_destroysOnlyThatGameSession() throws Exception { - CreateGameSessionRequest createGameSessionRequest10 = - new CreateGameSessionRequest(10, GAME_A_PACKAGE); - Supplier<AndroidFuture<IBinder>> gameSession10Future = - captureCreateGameSessionFuture(createGameSessionRequest10); + mGameServiceProviderInstance.start(); - CreateGameSessionRequest createGameSessionRequest11 = - new CreateGameSessionRequest(11, GAME_A_PACKAGE); - Supplier<AndroidFuture<IBinder>> gameSession11Future = - captureCreateGameSessionFuture(createGameSessionRequest11); + startTask(10, GAME_A_MAIN_ACTIVITY); + mFakeGameService.requestCreateGameSession(10); - mGameServiceProviderInstance.start(); - ArgumentCaptor<IGameServiceController> controllerArgumentCaptor = ArgumentCaptor.forClass( - IGameServiceController.class); - verify(mMockGameService).connected(controllerArgumentCaptor.capture()); - dispatchTaskCreatedAndTriggerSessionRequest(10, GAME_A_MAIN_ACTIVITY, - controllerArgumentCaptor.getValue()); - IGameSessionStub gameSession10 = new IGameSessionStub(); - gameSession10Future.get().complete(gameSession10); - - dispatchTaskCreatedAndTriggerSessionRequest(11, GAME_A_MAIN_ACTIVITY, - controllerArgumentCaptor.getValue()); - IGameSessionStub gameSession11 = new IGameSessionStub(); - gameSession11Future.get().complete(gameSession11); + FakeGameSession gameSession10 = new FakeGameSession(); + SurfacePackage mockSurfacePackage10 = Mockito.mock(SurfacePackage.class); + mFakeGameSessionService.removePendingFutureForTaskId(10) + .complete(new CreateGameSessionResult(gameSession10, mockSurfacePackage10)); + + startTask(11, GAME_A_MAIN_ACTIVITY); + mFakeGameService.requestCreateGameSession(11); + + FakeGameSession gameSession11 = new FakeGameSession(); + SurfacePackage mockSurfacePackage11 = Mockito.mock(SurfacePackage.class); + mFakeGameSessionService.removePendingFutureForTaskId(11) + .complete(new CreateGameSessionResult(gameSession11, mockSurfacePackage11)); dispatchTaskRemoved(10); - mInOrder.verify(mMockGameService).connected(any()); - mInOrder.verify(mMockGameService).gameStarted( - eq(new GameStartedEvent(10, GAME_A_PACKAGE))); - mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest10), any()); - mInOrder.verify(mMockGameService).gameStarted( - eq(new GameStartedEvent(11, GAME_A_PACKAGE))); - mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest11), any()); - mInOrder.verifyNoMoreInteractions(); assertThat(gameSession10.mIsDestroyed).isTrue(); assertThat(gameSession11.mIsDestroyed).isFalse(); - assertThat(mFakeGameServiceConnector.getIsConnected()).isTrue(); - assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1); assertThat(mFakeGameSessionServiceConnector.getIsConnected()).isTrue(); - assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(1); } @Test - public void allGameTasksRemoved_destroysAllGameSessions() throws Exception { - CreateGameSessionRequest createGameSessionRequest10 = - new CreateGameSessionRequest(10, GAME_A_PACKAGE); - Supplier<AndroidFuture<IBinder>> gameSession10Future = - captureCreateGameSessionFuture(createGameSessionRequest10); + public void allGameTasksRemoved_destroysAllGameSessionsAndGameSessionServiceIsDisconnected() { + mGameServiceProviderInstance.start(); - CreateGameSessionRequest createGameSessionRequest11 = - new CreateGameSessionRequest(11, GAME_A_PACKAGE); - Supplier<AndroidFuture<IBinder>> gameSession11Future = - captureCreateGameSessionFuture(createGameSessionRequest11); + startTask(10, GAME_A_MAIN_ACTIVITY); + mFakeGameService.requestCreateGameSession(10); - mGameServiceProviderInstance.start(); - ArgumentCaptor<IGameServiceController> controllerArgumentCaptor = ArgumentCaptor.forClass( - IGameServiceController.class); - verify(mMockGameService).connected(controllerArgumentCaptor.capture()); - dispatchTaskCreatedAndTriggerSessionRequest(10, GAME_A_MAIN_ACTIVITY, - controllerArgumentCaptor.getValue()); - IGameSessionStub gameSession10 = new IGameSessionStub(); - gameSession10Future.get().complete(gameSession10); - - dispatchTaskCreatedAndTriggerSessionRequest(11, GAME_A_MAIN_ACTIVITY, - controllerArgumentCaptor.getValue()); - IGameSessionStub gameSession11 = new IGameSessionStub(); - gameSession11Future.get().complete(gameSession11); + FakeGameSession gameSession10 = new FakeGameSession(); + SurfacePackage mockSurfacePackage10 = Mockito.mock(SurfacePackage.class); + mFakeGameSessionService.removePendingFutureForTaskId(10) + .complete(new CreateGameSessionResult(gameSession10, mockSurfacePackage10)); + + startTask(11, GAME_A_MAIN_ACTIVITY); + mFakeGameService.requestCreateGameSession(11); + + FakeGameSession gameSession11 = new FakeGameSession(); + SurfacePackage mockSurfacePackage11 = Mockito.mock(SurfacePackage.class); + mFakeGameSessionService.removePendingFutureForTaskId(11) + .complete(new CreateGameSessionResult(gameSession11, mockSurfacePackage11)); dispatchTaskRemoved(10); dispatchTaskRemoved(11); - mInOrder.verify(mMockGameService).connected(any()); - mInOrder.verify(mMockGameService).gameStarted( - eq(new GameStartedEvent(10, GAME_A_PACKAGE))); - mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest10), any()); - mInOrder.verify(mMockGameService).gameStarted( - eq(new GameStartedEvent(11, GAME_A_PACKAGE))); - mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest11), any()); - mInOrder.verifyNoMoreInteractions(); assertThat(gameSession10.mIsDestroyed).isTrue(); assertThat(gameSession11.mIsDestroyed).isTrue(); - assertThat(mFakeGameServiceConnector.getIsConnected()).isTrue(); - assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1); assertThat(mFakeGameSessionServiceConnector.getIsConnected()).isFalse(); - assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(1); } @Test - public void gameTasksCreatedAndSessionsReq_afterAllPreviousSessionsDestroyed_createsSession() + public void createSessionRequested_afterAllPreviousSessionsDestroyed_createsSession() throws Exception { - CreateGameSessionRequest createGameSessionRequest10 = - new CreateGameSessionRequest(10, GAME_A_PACKAGE); - Supplier<AndroidFuture<IBinder>> gameSession10Future = - captureCreateGameSessionFuture(createGameSessionRequest10); + mGameServiceProviderInstance.start(); - CreateGameSessionRequest createGameSessionRequest11 = - new CreateGameSessionRequest(11, GAME_A_PACKAGE); - Supplier<AndroidFuture<IBinder>> gameSession11Future = - captureCreateGameSessionFuture(createGameSessionRequest11); + startTask(10, GAME_A_MAIN_ACTIVITY); + mFakeGameService.requestCreateGameSession(10); - CreateGameSessionRequest createGameSessionRequest12 = - new CreateGameSessionRequest(12, GAME_A_PACKAGE); - Supplier<AndroidFuture<IBinder>> unusedGameSession12Future = - captureCreateGameSessionFuture(createGameSessionRequest12); + FakeGameSession gameSession10 = new FakeGameSession(); + SurfacePackage mockSurfacePackage10 = Mockito.mock(SurfacePackage.class); + mFakeGameSessionService.removePendingFutureForTaskId(10) + .complete(new CreateGameSessionResult(gameSession10, mockSurfacePackage10)); - mGameServiceProviderInstance.start(); - ArgumentCaptor<IGameServiceController> controllerArgumentCaptor = ArgumentCaptor.forClass( - IGameServiceController.class); - verify(mMockGameService).connected(controllerArgumentCaptor.capture()); - dispatchTaskCreatedAndTriggerSessionRequest(10, GAME_A_MAIN_ACTIVITY, - controllerArgumentCaptor.getValue()); - IGameSessionStub gameSession10 = new IGameSessionStub(); - gameSession10Future.get().complete(gameSession10); - - dispatchTaskCreatedAndTriggerSessionRequest(11, GAME_A_MAIN_ACTIVITY, - controllerArgumentCaptor.getValue()); - IGameSessionStub gameSession11 = new IGameSessionStub(); - gameSession11Future.get().complete(gameSession11); + startTask(11, GAME_A_MAIN_ACTIVITY); + mFakeGameService.requestCreateGameSession(11); + + FakeGameSession gameSession11 = new FakeGameSession(); + SurfacePackage mockSurfacePackage11 = Mockito.mock(SurfacePackage.class); + mFakeGameSessionService.removePendingFutureForTaskId(11) + .complete(new CreateGameSessionResult(gameSession11, mockSurfacePackage11)); dispatchTaskRemoved(10); dispatchTaskRemoved(11); - dispatchTaskCreatedAndTriggerSessionRequest(12, GAME_A_MAIN_ACTIVITY, - controllerArgumentCaptor.getValue()); - IGameSessionStub gameSession12 = new IGameSessionStub(); - gameSession11Future.get().complete(gameSession12); - - mInOrder.verify(mMockGameService).connected(any()); - mInOrder.verify(mMockGameService).gameStarted( - eq(new GameStartedEvent(10, GAME_A_PACKAGE))); - mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest10), any()); - mInOrder.verify(mMockGameService).gameStarted( - eq(new GameStartedEvent(11, GAME_A_PACKAGE))); - mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest11), any()); - mInOrder.verify(mMockGameService).gameStarted( - eq(new GameStartedEvent(12, GAME_A_PACKAGE))); - mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest12), any()); - mInOrder.verifyNoMoreInteractions(); + startTask(12, GAME_A_MAIN_ACTIVITY); + mFakeGameService.requestCreateGameSession(12); + + FakeGameSession gameSession12 = new FakeGameSession(); + SurfacePackage mockSurfacePackage12 = Mockito.mock(SurfacePackage.class); + mFakeGameSessionService.removePendingFutureForTaskId(12) + .complete(new CreateGameSessionResult(gameSession12, mockSurfacePackage12)); + assertThat(gameSession10.mIsDestroyed).isTrue(); assertThat(gameSession11.mIsDestroyed).isTrue(); assertThat(gameSession12.mIsDestroyed).isFalse(); - assertThat(mFakeGameServiceConnector.getIsConnected()).isTrue(); - assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1); assertThat(mFakeGameSessionServiceConnector.getIsConnected()).isTrue(); - assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(2); } @Test public void stop_severalActiveGameSessions_destroysGameSessionsAndUnbinds() throws Exception { - CreateGameSessionRequest createGameSessionRequest10 = - new CreateGameSessionRequest(10, GAME_A_PACKAGE); - Supplier<AndroidFuture<IBinder>> gameSession10Future = - captureCreateGameSessionFuture(createGameSessionRequest10); + mGameServiceProviderInstance.start(); - CreateGameSessionRequest createGameSessionRequest11 = - new CreateGameSessionRequest(11, GAME_A_PACKAGE); - Supplier<AndroidFuture<IBinder>> gameSession11Future = - captureCreateGameSessionFuture(createGameSessionRequest11); + startTask(10, GAME_A_MAIN_ACTIVITY); + mFakeGameService.requestCreateGameSession(10); + + FakeGameSession gameSession10 = new FakeGameSession(); + SurfacePackage mockSurfacePackage10 = Mockito.mock(SurfacePackage.class); + mFakeGameSessionService.removePendingFutureForTaskId(10) + .complete(new CreateGameSessionResult(gameSession10, mockSurfacePackage10)); + + startTask(11, GAME_A_MAIN_ACTIVITY); + mFakeGameService.requestCreateGameSession(11); + + FakeGameSession gameSession11 = new FakeGameSession(); + SurfacePackage mockSurfacePackage11 = Mockito.mock(SurfacePackage.class); + mFakeGameSessionService.removePendingFutureForTaskId(11) + .complete(new CreateGameSessionResult(gameSession11, mockSurfacePackage11)); - mGameServiceProviderInstance.start(); - ArgumentCaptor<IGameServiceController> controllerArgumentCaptor = ArgumentCaptor.forClass( - IGameServiceController.class); - verify(mMockGameService).connected(controllerArgumentCaptor.capture()); - dispatchTaskCreatedAndTriggerSessionRequest(10, GAME_A_MAIN_ACTIVITY, - controllerArgumentCaptor.getValue()); - IGameSessionStub gameSession10 = new IGameSessionStub(); - gameSession10Future.get().complete(gameSession10); - dispatchTaskCreatedAndTriggerSessionRequest(11, GAME_A_MAIN_ACTIVITY, - controllerArgumentCaptor.getValue()); - IGameSessionStub gameSession11 = new IGameSessionStub(); - gameSession11Future.get().complete(gameSession11); mGameServiceProviderInstance.stop(); - mInOrder.verify(mMockGameService).connected(any()); - mInOrder.verify(mMockGameService).gameStarted( - eq(new GameStartedEvent(10, GAME_A_PACKAGE))); - mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest10), any()); - mInOrder.verify(mMockGameService).gameStarted( - eq(new GameStartedEvent(11, GAME_A_PACKAGE))); - mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest11), any()); - mInOrder.verify(mMockGameService).disconnected(); - mInOrder.verifyNoMoreInteractions(); assertThat(gameSession10.mIsDestroyed).isTrue(); assertThat(gameSession11.mIsDestroyed).isTrue(); assertThat(mFakeGameServiceConnector.getIsConnected()).isFalse(); - assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1); assertThat(mFakeGameSessionServiceConnector.getIsConnected()).isFalse(); - assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(1); } - private Supplier<AndroidFuture<IBinder>> captureCreateGameSessionFuture( - CreateGameSessionRequest expectedCreateGameSessionRequest) throws Exception { - final AtomicReference<AndroidFuture<IBinder>> gameSessionFuture = new AtomicReference<>(); - doAnswer(invocation -> { - gameSessionFuture.set(invocation.getArgument(1)); - return null; - }).when(mMockGameSessionService).create(eq(expectedCreateGameSessionRequest), any()); + @Test + public void takeScreenshot_failureNoBitmapCaptured() throws Exception { + mGameServiceProviderInstance.start(); + startTask(10, GAME_A_MAIN_ACTIVITY); + mFakeGameService.requestCreateGameSession(10); + + IGameSessionController gameSessionController = getOnlyElement( + mFakeGameSessionService.getCapturedCreateInvocations()).mGameSessionController; + AndroidFuture<GameScreenshotResult> resultFuture = new AndroidFuture<>(); + gameSessionController.takeScreenshot(10, resultFuture); + + GameScreenshotResult result = resultFuture.get(); + assertEquals(GameScreenshotResult.GAME_SCREENSHOT_ERROR_INTERNAL_ERROR, + result.getStatus()); + verify(mMockWindowManagerService).captureTaskBitmap(10); + } + + @Test + public void takeScreenshot_success() throws Exception { + when(mMockWindowManagerService.captureTaskBitmap(10)).thenReturn(TEST_BITMAP); + + mGameServiceProviderInstance.start(); + startTask(10, GAME_A_MAIN_ACTIVITY); + mFakeGameService.requestCreateGameSession(10); + + IGameSessionController gameSessionController = getOnlyElement( + mFakeGameSessionService.getCapturedCreateInvocations()).mGameSessionController; + AndroidFuture<GameScreenshotResult> resultFuture = new AndroidFuture<>(); + gameSessionController.takeScreenshot(10, resultFuture); + + GameScreenshotResult result = resultFuture.get(); + assertEquals(GameScreenshotResult.GAME_SCREENSHOT_SUCCESS, result.getStatus()); + assertEquals(TEST_BITMAP, result.getBitmap()); + } + + private void startTask(int taskId, ComponentName componentName) { + RunningTaskInfo runningTaskInfo = new RunningTaskInfo(); + runningTaskInfo.taskId = taskId; + runningTaskInfo.displayId = 1; + runningTaskInfo.configuration.windowConfiguration.setBounds(new Rect(0, 0, 500, 800)); + mRunningTaskInfos.add(runningTaskInfo); + + dispatchTaskCreated(taskId, componentName); + } - return gameSessionFuture::get; + private void stopTask(int taskId) { + mRunningTaskInfos.removeIf(runningTaskInfo -> runningTaskInfo.taskId == taskId); + dispatchTaskRemoved(taskId); } + private void dispatchTaskRemoved(int taskId) { dispatchTaskChangeEvent(taskStackListener -> { taskStackListener.onTaskRemoved(taskId); }); } - private void dispatchTaskCreatedAndTriggerSessionRequest(int taskId, - @Nullable ComponentName componentName, IGameServiceController gameServiceController) - throws Exception { - dispatchTaskCreated(taskId, componentName); - gameServiceController.createGameSession(taskId); - } - private void dispatchTaskCreated(int taskId, @Nullable ComponentName componentName) { dispatchTaskChangeEvent(taskStackListener -> { taskStackListener.onTaskCreated(taskId, componentName); }); } + private void dispatchTaskFocused(int taskId, boolean focused) { + dispatchTaskChangeEvent(taskStackListener -> { + taskStackListener.onTaskFocusChanged(taskId, focused); + }); + } + private void dispatchTaskChangeEvent( ThrowingConsumer<ITaskStackListener> taskStackListenerConsumer) { for (ITaskStackListener taskStackListener : mTaskStackListeners) { @@ -721,12 +701,129 @@ public final class GameServiceProviderInstanceImplTest { } } - private static class IGameSessionStub extends IGameSession.Stub { + static final class FakeGameService extends IGameService.Stub { + private IGameServiceController mGameServiceController; + + public enum GameServiceState { + DISCONNECTED, + CONNECTED, + } + + private ArrayList<GameStartedEvent> mGameStartedEvents = new ArrayList<>(); + private int mConnectedCount = 0; + private GameServiceState mGameServiceState = GameServiceState.DISCONNECTED; + + public GameServiceState getState() { + return mGameServiceState; + } + + public int getConnectedCount() { + return mConnectedCount; + } + + public ArrayList<GameStartedEvent> getGameStartedEvents() { + return mGameStartedEvents; + } + + @Override + public void connected(IGameServiceController gameServiceController) { + Preconditions.checkState(mGameServiceState == GameServiceState.DISCONNECTED); + + mGameServiceState = GameServiceState.CONNECTED; + mConnectedCount += 1; + mGameServiceController = gameServiceController; + } + + @Override + public void disconnected() { + Preconditions.checkState(mGameServiceState == GameServiceState.CONNECTED); + + mGameServiceState = GameServiceState.DISCONNECTED; + mGameServiceController = null; + } + + @Override + public void gameStarted(GameStartedEvent gameStartedEvent) { + Preconditions.checkState(mGameServiceState == GameServiceState.CONNECTED); + + mGameStartedEvents.add(gameStartedEvent); + } + + public void requestCreateGameSession(int task) { + Preconditions.checkState(mGameServiceState == GameServiceState.CONNECTED); + + try { + mGameServiceController.createGameSession(task); + } catch (RemoteException ex) { + throw new AssertionError(ex); + } + } + } + + static final class FakeGameSessionService extends IGameSessionService.Stub { + + private final ArrayList<CapturedCreateInvocation> mCapturedCreateInvocations = + new ArrayList<>(); + private final HashMap<Integer, AndroidFuture<CreateGameSessionResult>> + mPendingCreateGameSessionResultFutures = + new HashMap<>(); + + public static final class CapturedCreateInvocation { + private final IGameSessionController mGameSessionController; + private final CreateGameSessionRequest mCreateGameSessionRequest; + private final GameSessionViewHostConfiguration mGameSessionViewHostConfiguration; + + CapturedCreateInvocation( + IGameSessionController gameSessionController, + CreateGameSessionRequest createGameSessionRequest, + GameSessionViewHostConfiguration gameSessionViewHostConfiguration) { + mGameSessionController = gameSessionController; + mCreateGameSessionRequest = createGameSessionRequest; + mGameSessionViewHostConfiguration = gameSessionViewHostConfiguration; + } + } + + public ArrayList<CapturedCreateInvocation> getCapturedCreateInvocations() { + return mCapturedCreateInvocations; + } + + public AndroidFuture<CreateGameSessionResult> removePendingFutureForTaskId(int taskId) { + return mPendingCreateGameSessionResultFutures.remove(taskId); + } + + @Override + public void create( + IGameSessionController gameSessionController, + CreateGameSessionRequest createGameSessionRequest, + GameSessionViewHostConfiguration gameSessionViewHostConfiguration, + AndroidFuture createGameSessionResultFuture) { + + mCapturedCreateInvocations.add( + new CapturedCreateInvocation( + gameSessionController, + createGameSessionRequest, + gameSessionViewHostConfiguration)); + + Preconditions.checkState(!mPendingCreateGameSessionResultFutures.containsKey( + createGameSessionRequest.getTaskId())); + mPendingCreateGameSessionResultFutures.put( + createGameSessionRequest.getTaskId(), + createGameSessionResultFuture); + } + } + + private static class FakeGameSession extends IGameSession.Stub { boolean mIsDestroyed = false; + boolean mIsFocused = false; @Override - public void destroy() { + public void onDestroyed() { mIsDestroyed = true; } + + @Override + public void onTaskFocusChanged(boolean focused) { + mIsFocused = focused; + } } -} +}
\ No newline at end of file diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java index d7414591f83c..8a954caad939 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java @@ -383,6 +383,10 @@ public class PrefetchControllerTest { inOrder.verify(mUsageStatsManagerInternal, timeout(DEFAULT_WAIT_MS).times(0)) .getEstimatedPackageLaunchTime(SOURCE_PACKAGE, SOURCE_USER_ID); + verify(mAlarmManager, timeout(DEFAULT_WAIT_MS).times(1)) + .setWindow( + anyInt(), eq(sElapsedRealtimeClock.millis() + 3 * HOUR_IN_MILLIS), + anyLong(), eq(TAG_PREFETCH), any(), any()); assertFalse(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH)); } diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java index cfae9a3d586a..153ce17ec9dd 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java @@ -16,6 +16,11 @@ package com.android.server.job.controllers; +import static android.app.job.JobInfo.PRIORITY_DEFAULT; +import static android.app.job.JobInfo.PRIORITY_HIGH; +import static android.app.job.JobInfo.PRIORITY_LOW; +import static android.app.job.JobInfo.PRIORITY_MIN; + import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; @@ -269,14 +274,14 @@ public class QuotaControllerTest { } private void setCharging() { - doReturn(true).when(mJobSchedulerService).isBatteryCharging(); + when(mJobSchedulerService.isBatteryCharging()).thenReturn(true); synchronized (mQuotaController.mLock) { mQuotaController.onBatteryStateChangedLocked(); } } private void setDischarging() { - doReturn(false).when(mJobSchedulerService).isBatteryCharging(); + when(mJobSchedulerService.isBatteryCharging()).thenReturn(false); synchronized (mQuotaController.mLock) { mQuotaController.onBatteryStateChangedLocked(); } @@ -407,6 +412,14 @@ public class QuotaControllerTest { } } + private void setDeviceConfigFloat(String key, float val) { + mDeviceConfigPropertiesBuilder.setFloat(key, val); + synchronized (mQuotaController.mLock) { + mQuotaController.prepareForUpdatedConstantsLocked(); + mQcConstants.processConstantLocked(mDeviceConfigPropertiesBuilder.build(), key); + } + } + private void waitForNonDelayedMessagesProcessed() { mQuotaController.getHandler().runWithScissors(() -> {}, 15_000); } @@ -839,7 +852,7 @@ public class QuotaControllerTest { SOURCE_USER_ID, SOURCE_PACKAGE, inputStats); assertEquals(expectedStats, inputStats); assertTrue(mQuotaController.isWithinQuotaLocked( - SOURCE_USER_ID, SOURCE_PACKAGE, RARE_INDEX)); + SOURCE_USER_ID, SOURCE_PACKAGE, RARE_INDEX, PRIORITY_DEFAULT)); } assertTrue("Job not ready: " + jobStatus, jobStatus.isReady()); } @@ -863,7 +876,7 @@ public class QuotaControllerTest { assertEquals(expectedStats, inputStats); assertFalse( mQuotaController.isWithinQuotaLocked( - SOURCE_USER_ID, SOURCE_PACKAGE, RARE_INDEX)); + SOURCE_USER_ID, SOURCE_PACKAGE, RARE_INDEX, PRIORITY_DEFAULT)); } // Quota should be exceeded due to activity in active timer. @@ -888,7 +901,7 @@ public class QuotaControllerTest { assertEquals(expectedStats, inputStats); assertFalse( mQuotaController.isWithinQuotaLocked( - SOURCE_USER_ID, SOURCE_PACKAGE, RARE_INDEX)); + SOURCE_USER_ID, SOURCE_PACKAGE, RARE_INDEX, PRIORITY_DEFAULT)); assertFalse("Job unexpectedly ready: " + jobStatus, jobStatus.isReady()); } } @@ -1484,7 +1497,7 @@ public class QuotaControllerTest { SOURCE_USER_ID, SOURCE_PACKAGE)); assertEquals(MINUTE_IN_MILLIS, mQuotaController.getTimeUntilQuotaConsumedLocked( - SOURCE_USER_ID, SOURCE_PACKAGE)); + SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_DEFAULT)); } setStandbyBucket(FREQUENT_INDEX); @@ -1494,7 +1507,7 @@ public class QuotaControllerTest { SOURCE_USER_ID, SOURCE_PACKAGE)); assertEquals(MINUTE_IN_MILLIS, mQuotaController.getTimeUntilQuotaConsumedLocked( - SOURCE_USER_ID, SOURCE_PACKAGE)); + SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_DEFAULT)); } setStandbyBucket(WORKING_INDEX); @@ -1504,7 +1517,7 @@ public class QuotaControllerTest { SOURCE_USER_ID, SOURCE_PACKAGE)); assertEquals(7 * MINUTE_IN_MILLIS, mQuotaController.getTimeUntilQuotaConsumedLocked( - SOURCE_USER_ID, SOURCE_PACKAGE)); + SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_DEFAULT)); } // ACTIVE window = allowed time, so jobs can essentially run non-stop until they reach the @@ -1516,7 +1529,7 @@ public class QuotaControllerTest { SOURCE_USER_ID, SOURCE_PACKAGE)); assertEquals(mQcConstants.MAX_EXECUTION_TIME_MS - 9 * MINUTE_IN_MILLIS, mQuotaController.getTimeUntilQuotaConsumedLocked( - SOURCE_USER_ID, SOURCE_PACKAGE)); + SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_DEFAULT)); } } @@ -1540,7 +1553,7 @@ public class QuotaControllerTest { // Max time will phase out, so should use bucket limit. assertEquals(10 * MINUTE_IN_MILLIS, mQuotaController.getTimeUntilQuotaConsumedLocked( - SOURCE_USER_ID, SOURCE_PACKAGE)); + SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_DEFAULT)); } mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear(); @@ -1556,7 +1569,7 @@ public class QuotaControllerTest { SOURCE_USER_ID, SOURCE_PACKAGE)); assertEquals(10 * MINUTE_IN_MILLIS, mQuotaController.getTimeUntilQuotaConsumedLocked( - SOURCE_USER_ID, SOURCE_PACKAGE)); + SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_DEFAULT)); } mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear(); @@ -1573,7 +1586,7 @@ public class QuotaControllerTest { SOURCE_USER_ID, SOURCE_PACKAGE)); assertEquals(3 * MINUTE_IN_MILLIS, mQuotaController.getTimeUntilQuotaConsumedLocked( - SOURCE_USER_ID, SOURCE_PACKAGE)); + SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_DEFAULT)); } } @@ -1606,7 +1619,7 @@ public class QuotaControllerTest { // window time. assertEquals(10 * MINUTE_IN_MILLIS, mQuotaController.getTimeUntilQuotaConsumedLocked( - SOURCE_USER_ID, SOURCE_PACKAGE)); + SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_DEFAULT)); } mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear(); @@ -1633,15 +1646,115 @@ public class QuotaControllerTest { // Max time only has one minute phase out. Bucket time has 2 minute phase out. assertEquals(9 * MINUTE_IN_MILLIS, mQuotaController.getTimeUntilQuotaConsumedLocked( + SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_DEFAULT)); + } + } + + /** + * Test getTimeUntilQuotaConsumedLocked when the determination is based on the job's priority. + */ + @Test + public void testGetTimeUntilQuotaConsumedLocked_Priority() { + final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); + // Close to RARE boundary. + mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, + createTimingSession(now - (24 * HOUR_IN_MILLIS - 30 * SECOND_IN_MILLIS), + 150 * SECOND_IN_MILLIS, 5), false); + // Far away from FREQUENT boundary. + mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, + createTimingSession(now - (7 * HOUR_IN_MILLIS), 2 * MINUTE_IN_MILLIS, 5), false); + // Overlap WORKING_SET boundary. + mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, + createTimingSession(now - (2 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS), + 2 * MINUTE_IN_MILLIS, 5), false); + // Close to ACTIVE boundary. + mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, + createTimingSession(now - (9 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), false); + + setStandbyBucket(RARE_INDEX); + synchronized (mQuotaController.mLock) { + assertEquals(30 * SECOND_IN_MILLIS, + mQuotaController.getRemainingExecutionTimeLocked( + SOURCE_USER_ID, SOURCE_PACKAGE)); + assertEquals(3 * MINUTE_IN_MILLIS, + mQuotaController.getTimeUntilQuotaConsumedLocked( + SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_HIGH)); + assertEquals(3 * MINUTE_IN_MILLIS, + mQuotaController.getTimeUntilQuotaConsumedLocked( + SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_DEFAULT)); + assertEquals(0, + mQuotaController.getTimeUntilQuotaConsumedLocked( + SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_LOW)); + assertEquals(0, + mQuotaController.getTimeUntilQuotaConsumedLocked( + SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_MIN)); + } + + setStandbyBucket(FREQUENT_INDEX); + synchronized (mQuotaController.mLock) { + assertEquals(3 * MINUTE_IN_MILLIS, + mQuotaController.getRemainingExecutionTimeLocked( + SOURCE_USER_ID, SOURCE_PACKAGE)); + assertEquals(3 * MINUTE_IN_MILLIS, + mQuotaController.getTimeUntilQuotaConsumedLocked( + SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_HIGH)); + assertEquals(3 * MINUTE_IN_MILLIS, + mQuotaController.getTimeUntilQuotaConsumedLocked( + SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_DEFAULT)); + assertEquals(30 * SECOND_IN_MILLIS, + mQuotaController.getTimeUntilQuotaConsumedLocked( + SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_LOW)); + assertEquals(0, + mQuotaController.getTimeUntilQuotaConsumedLocked( + SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_MIN)); + } + + setStandbyBucket(WORKING_INDEX); + synchronized (mQuotaController.mLock) { + assertEquals(6 * MINUTE_IN_MILLIS, + mQuotaController.getRemainingExecutionTimeLocked( + SOURCE_USER_ID, SOURCE_PACKAGE)); + assertEquals(7 * MINUTE_IN_MILLIS, + mQuotaController.getTimeUntilQuotaConsumedLocked( + SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_HIGH)); + assertEquals(7 * MINUTE_IN_MILLIS, + mQuotaController.getTimeUntilQuotaConsumedLocked( + SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_DEFAULT)); + assertEquals(4 * MINUTE_IN_MILLIS + 30 * SECOND_IN_MILLIS, + mQuotaController.getTimeUntilQuotaConsumedLocked( + SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_LOW)); + assertEquals(2 * MINUTE_IN_MILLIS, + mQuotaController.getTimeUntilQuotaConsumedLocked( + SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_MIN)); + } + + // ACTIVE window = allowed time, so jobs can essentially run non-stop until they reach the + // max execution time. + setStandbyBucket(ACTIVE_INDEX); + synchronized (mQuotaController.mLock) { + assertEquals(7 * MINUTE_IN_MILLIS, + mQuotaController.getRemainingExecutionTimeLocked( SOURCE_USER_ID, SOURCE_PACKAGE)); + assertEquals(mQcConstants.MAX_EXECUTION_TIME_MS - 7 * MINUTE_IN_MILLIS, + mQuotaController.getTimeUntilQuotaConsumedLocked( + SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_HIGH)); + assertEquals(mQcConstants.MAX_EXECUTION_TIME_MS - 7 * MINUTE_IN_MILLIS, + mQuotaController.getTimeUntilQuotaConsumedLocked( + SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_DEFAULT)); + assertEquals(mQcConstants.MAX_EXECUTION_TIME_MS - 7 * MINUTE_IN_MILLIS, + mQuotaController.getTimeUntilQuotaConsumedLocked( + SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_LOW)); + assertEquals(mQcConstants.MAX_EXECUTION_TIME_MS - 7 * MINUTE_IN_MILLIS, + mQuotaController.getTimeUntilQuotaConsumedLocked( + SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_MIN)); } } @Test public void testIsWithinQuotaLocked_NeverApp() { synchronized (mQuotaController.mLock) { - assertFalse( - mQuotaController.isWithinQuotaLocked(0, "com.android.test.never", NEVER_INDEX)); + assertFalse(mQuotaController.isWithinQuotaLocked( + 0, "com.android.test.never", NEVER_INDEX, PRIORITY_DEFAULT)); } } @@ -1649,7 +1762,8 @@ public class QuotaControllerTest { public void testIsWithinQuotaLocked_Charging() { setCharging(); synchronized (mQuotaController.mLock) { - assertTrue(mQuotaController.isWithinQuotaLocked(0, "com.android.test", RARE_INDEX)); + assertTrue(mQuotaController.isWithinQuotaLocked( + 0, "com.android.test", RARE_INDEX, PRIORITY_DEFAULT)); } } @@ -1663,7 +1777,8 @@ public class QuotaControllerTest { createTimingSession(now - (5 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), false); synchronized (mQuotaController.mLock) { mQuotaController.incrementJobCountLocked(0, "com.android.test", 5); - assertTrue(mQuotaController.isWithinQuotaLocked(0, "com.android.test", WORKING_INDEX)); + assertTrue(mQuotaController.isWithinQuotaLocked( + 0, "com.android.test", WORKING_INDEX, PRIORITY_DEFAULT)); } } @@ -1680,7 +1795,7 @@ public class QuotaControllerTest { synchronized (mQuotaController.mLock) { mQuotaController.incrementJobCountLocked(0, "com.android.test.spam", jobCount); assertFalse(mQuotaController.isWithinQuotaLocked( - 0, "com.android.test.spam", WORKING_INDEX)); + 0, "com.android.test.spam", WORKING_INDEX, PRIORITY_DEFAULT)); } mQuotaController.saveTimingSession(0, "com.android.test.frequent", @@ -1690,7 +1805,7 @@ public class QuotaControllerTest { createTimingSession(now - (HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 500), false); synchronized (mQuotaController.mLock) { assertFalse(mQuotaController.isWithinQuotaLocked( - 0, "com.android.test.frequent", FREQUENT_INDEX)); + 0, "com.android.test.frequent", FREQUENT_INDEX, PRIORITY_DEFAULT)); } } @@ -1706,7 +1821,8 @@ public class QuotaControllerTest { createTimingSession(now - (5 * MINUTE_IN_MILLIS), 4 * MINUTE_IN_MILLIS, 5), false); synchronized (mQuotaController.mLock) { mQuotaController.incrementJobCountLocked(0, "com.android.test", 5); - assertFalse(mQuotaController.isWithinQuotaLocked(0, "com.android.test", WORKING_INDEX)); + assertFalse(mQuotaController.isWithinQuotaLocked( + 0, "com.android.test", WORKING_INDEX, PRIORITY_DEFAULT)); } } @@ -1722,7 +1838,8 @@ public class QuotaControllerTest { false); synchronized (mQuotaController.mLock) { mQuotaController.incrementJobCountLocked(0, "com.android.test", jobCount); - assertFalse(mQuotaController.isWithinQuotaLocked(0, "com.android.test", WORKING_INDEX)); + assertFalse(mQuotaController.isWithinQuotaLocked( + 0, "com.android.test", WORKING_INDEX, PRIORITY_DEFAULT)); } } @@ -1875,22 +1992,66 @@ public class QuotaControllerTest { assertEquals("Rare has incorrect quota status with " + (i + 1) + " sessions", i < 2, - mQuotaController.isWithinQuotaLocked(0, "com.android.test", RARE_INDEX)); + mQuotaController.isWithinQuotaLocked( + 0, "com.android.test", RARE_INDEX, PRIORITY_DEFAULT)); assertEquals("Frequent has incorrect quota status with " + (i + 1) + " sessions", i < 3, mQuotaController.isWithinQuotaLocked( - 0, "com.android.test", FREQUENT_INDEX)); + 0, "com.android.test", FREQUENT_INDEX, PRIORITY_DEFAULT)); assertEquals("Working has incorrect quota status with " + (i + 1) + " sessions", i < 4, - mQuotaController.isWithinQuotaLocked(0, "com.android.test", WORKING_INDEX)); + mQuotaController.isWithinQuotaLocked( + 0, "com.android.test", WORKING_INDEX, PRIORITY_DEFAULT)); assertEquals("Active has incorrect quota status with " + (i + 1) + " sessions", i < 5, - mQuotaController.isWithinQuotaLocked(0, "com.android.test", ACTIVE_INDEX)); + mQuotaController.isWithinQuotaLocked( + 0, "com.android.test", ACTIVE_INDEX, PRIORITY_DEFAULT)); } } } @Test + public void testIsWithinQuotaLocked_Priority() { + setDischarging(); + final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); + mQuotaController.saveTimingSession(0, "com.android.test", + createTimingSession(now - (7 * HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), false); + mQuotaController.saveTimingSession(0, "com.android.test", + createTimingSession(now - (HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), false); + mQuotaController.saveTimingSession(0, "com.android.test", + createTimingSession(now - (5 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), false); + synchronized (mQuotaController.mLock) { + mQuotaController.incrementJobCountLocked(0, "com.android.test", 5); + assertTrue(mQuotaController.isWithinQuotaLocked( + 0, "com.android.test", FREQUENT_INDEX, PRIORITY_HIGH)); + assertTrue(mQuotaController.isWithinQuotaLocked( + 0, "com.android.test", FREQUENT_INDEX, PRIORITY_DEFAULT)); + assertFalse(mQuotaController.isWithinQuotaLocked( + 0, "com.android.test", FREQUENT_INDEX, PRIORITY_LOW)); + assertFalse(mQuotaController.isWithinQuotaLocked( + 0, "com.android.test", FREQUENT_INDEX, PRIORITY_MIN)); + + assertTrue(mQuotaController.isWithinQuotaLocked( + 0, "com.android.test", WORKING_INDEX, PRIORITY_HIGH)); + assertTrue(mQuotaController.isWithinQuotaLocked( + 0, "com.android.test", WORKING_INDEX, PRIORITY_DEFAULT)); + assertTrue(mQuotaController.isWithinQuotaLocked( + 0, "com.android.test", WORKING_INDEX, PRIORITY_LOW)); + assertFalse(mQuotaController.isWithinQuotaLocked( + 0, "com.android.test", WORKING_INDEX, PRIORITY_MIN)); + + assertTrue(mQuotaController.isWithinQuotaLocked( + 0, "com.android.test", ACTIVE_INDEX, PRIORITY_HIGH)); + assertTrue(mQuotaController.isWithinQuotaLocked( + 0, "com.android.test", ACTIVE_INDEX, PRIORITY_DEFAULT)); + assertTrue(mQuotaController.isWithinQuotaLocked( + 0, "com.android.test", ACTIVE_INDEX, PRIORITY_LOW)); + assertTrue(mQuotaController.isWithinQuotaLocked( + 0, "com.android.test", ACTIVE_INDEX, PRIORITY_MIN)); + } + } + + @Test public void testIsWithinEJQuotaLocked_NeverApp() { JobStatus js = createExpeditedJobStatus("testIsWithinEJQuotaLocked_NeverApp", 1); setStandbyBucket(NEVER_INDEX, js); @@ -2116,6 +2277,12 @@ public class QuotaControllerTest { final int standbyBucket = ACTIVE_INDEX; setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND); + JobStatus jobStatus = createJobStatus("testMaybeScheduleStartAlarmLocked_Active", 1); + setStandbyBucket(standbyBucket, jobStatus); + synchronized (mQuotaController.mLock) { + mQuotaController.maybeStartTrackingJobLocked(jobStatus, null); + } + // No sessions saved yet. synchronized (mQuotaController.mLock) { mQuotaController.maybeScheduleStartAlarmLocked( @@ -2150,10 +2317,7 @@ public class QuotaControllerTest { verify(mAlarmManager, timeout(1000).times(0)).setWindow( anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); - JobStatus jobStatus = createJobStatus("testMaybeScheduleStartAlarmLocked_Active", 1); - setStandbyBucket(standbyBucket, jobStatus); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStartTrackingJobLocked(jobStatus, null); mQuotaController.prepareForExecutionLocked(jobStatus); } advanceElapsedClock(5 * MINUTE_IN_MILLIS); @@ -2179,19 +2343,24 @@ public class QuotaControllerTest { // Working set window size is 2 hours. final int standbyBucket = WORKING_INDEX; + JobStatus jobStatus = createJobStatus("testMaybeScheduleStartAlarmLocked_WorkingSet", 1); + setStandbyBucket(standbyBucket, jobStatus); synchronized (mQuotaController.mLock) { + mQuotaController.maybeStartTrackingJobLocked(jobStatus, null); // No sessions saved yet. - mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket); + mQuotaController.maybeScheduleStartAlarmLocked( + SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); } verify(mAlarmManager, timeout(1000).times(0)).setWindow( anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); // Test with timing sessions out of window. final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); - mQuotaController.saveTimingSession(0, "com.android.test", + mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, createTimingSession(now - 10 * HOUR_IN_MILLIS, 5 * MINUTE_IN_MILLIS, 1), false); synchronized (mQuotaController.mLock) { - mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket); + mQuotaController.maybeScheduleStartAlarmLocked( + SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); } verify(mAlarmManager, timeout(1000).times(0)).setWindow( anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); @@ -2201,37 +2370,41 @@ public class QuotaControllerTest { // Counting backwards, the quota will come back one minute before the end. final long expectedAlarmTime = end - MINUTE_IN_MILLIS + 2 * HOUR_IN_MILLIS + mQcConstants.IN_QUOTA_BUFFER_MS; - mQuotaController.saveTimingSession(0, "com.android.test", + mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, new TimingSession(now - 2 * HOUR_IN_MILLIS, end, 1), false); synchronized (mQuotaController.mLock) { - mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket); + mQuotaController.maybeScheduleStartAlarmLocked( + SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); } verify(mAlarmManager, timeout(1000).times(0)).setWindow( anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); // Add some more sessions, but still in quota. - mQuotaController.saveTimingSession(0, "com.android.test", + mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, createTimingSession(now - HOUR_IN_MILLIS, MINUTE_IN_MILLIS, 1), false); - mQuotaController.saveTimingSession(0, "com.android.test", + mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, createTimingSession(now - (50 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 1), false); synchronized (mQuotaController.mLock) { - mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket); + mQuotaController.maybeScheduleStartAlarmLocked( + SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); } verify(mAlarmManager, timeout(1000).times(0)).setWindow( anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); // Test when out of quota. - mQuotaController.saveTimingSession(0, "com.android.test", + mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, createTimingSession(now - 30 * MINUTE_IN_MILLIS, 5 * MINUTE_IN_MILLIS, 1), false); synchronized (mQuotaController.mLock) { - mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket); + mQuotaController.maybeScheduleStartAlarmLocked( + SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); } verify(mAlarmManager, timeout(1000).times(1)).setWindow( anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); // Alarm already scheduled, so make sure it's not scheduled again. synchronized (mQuotaController.mLock) { - mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket); + mQuotaController.maybeScheduleStartAlarmLocked( + SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); } verify(mAlarmManager, timeout(1000).times(1)).setWindow( anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); @@ -2244,22 +2417,29 @@ public class QuotaControllerTest { spyOn(mQuotaController); doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked(); + synchronized (mQuotaController.mLock) { + mQuotaController.maybeStartTrackingJobLocked( + createJobStatus("testMaybeScheduleStartAlarmLocked_Frequent", 1), null); + } + // Frequent window size is 8 hours. final int standbyBucket = FREQUENT_INDEX; // No sessions saved yet. synchronized (mQuotaController.mLock) { - mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket); + mQuotaController.maybeScheduleStartAlarmLocked( + SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); } verify(mAlarmManager, timeout(1000).times(0)).setWindow( anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); // Test with timing sessions out of window. final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); - mQuotaController.saveTimingSession(0, "com.android.test", + mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, createTimingSession(now - 10 * HOUR_IN_MILLIS, 5 * MINUTE_IN_MILLIS, 1), false); synchronized (mQuotaController.mLock) { - mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket); + mQuotaController.maybeScheduleStartAlarmLocked( + SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); } verify(mAlarmManager, timeout(1000).times(0)).setWindow( anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); @@ -2267,37 +2447,41 @@ public class QuotaControllerTest { // Test with timing sessions in window but still in quota. final long start = now - (6 * HOUR_IN_MILLIS); final long expectedAlarmTime = start + 8 * HOUR_IN_MILLIS + mQcConstants.IN_QUOTA_BUFFER_MS; - mQuotaController.saveTimingSession(0, "com.android.test", + mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, createTimingSession(start, 5 * MINUTE_IN_MILLIS, 1), false); synchronized (mQuotaController.mLock) { - mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket); + mQuotaController.maybeScheduleStartAlarmLocked( + SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); } verify(mAlarmManager, timeout(1000).times(0)).setWindow( anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); // Add some more sessions, but still in quota. - mQuotaController.saveTimingSession(0, "com.android.test", + mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, createTimingSession(now - 3 * HOUR_IN_MILLIS, MINUTE_IN_MILLIS, 1), false); - mQuotaController.saveTimingSession(0, "com.android.test", + mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, createTimingSession(now - HOUR_IN_MILLIS, 3 * MINUTE_IN_MILLIS, 1), false); synchronized (mQuotaController.mLock) { - mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket); + mQuotaController.maybeScheduleStartAlarmLocked( + SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); } verify(mAlarmManager, timeout(1000).times(0)).setWindow( anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); // Test when out of quota. - mQuotaController.saveTimingSession(0, "com.android.test", + mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, createTimingSession(now - HOUR_IN_MILLIS, MINUTE_IN_MILLIS, 1), false); synchronized (mQuotaController.mLock) { - mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket); + mQuotaController.maybeScheduleStartAlarmLocked( + SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); } verify(mAlarmManager, timeout(1000).times(1)).setWindow( anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); // Alarm already scheduled, so make sure it's not scheduled again. synchronized (mQuotaController.mLock) { - mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket); + mQuotaController.maybeScheduleStartAlarmLocked( + SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); } verify(mAlarmManager, timeout(1000).times(1)).setWindow( anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); @@ -2314,6 +2498,11 @@ public class QuotaControllerTest { spyOn(mQuotaController); doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked(); + synchronized (mQuotaController.mLock) { + mQuotaController.maybeStartTrackingJobLocked( + createJobStatus("testMaybeScheduleStartAlarmLocked_Never", 1), null); + } + // The app is really in the NEVER bucket but is elevated somehow (eg via uidActive). setStandbyBucket(NEVER_INDEX); final int effectiveStandbyBucket = FREQUENT_INDEX; @@ -2390,22 +2579,30 @@ public class QuotaControllerTest { // Rare window size is 24 hours. final int standbyBucket = RARE_INDEX; + JobStatus jobStatus = createJobStatus("testMaybeScheduleStartAlarmLocked_Rare", 1); + setStandbyBucket(standbyBucket, jobStatus); + synchronized (mQuotaController.mLock) { + mQuotaController.maybeStartTrackingJobLocked(jobStatus, null); + } + // Prevent timing session throttling from affecting the test. setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_RARE, 50); // No sessions saved yet. synchronized (mQuotaController.mLock) { - mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket); + mQuotaController.maybeScheduleStartAlarmLocked( + SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); } verify(mAlarmManager, timeout(1000).times(0)).setWindow( anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); // Test with timing sessions out of window. final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); - mQuotaController.saveTimingSession(0, "com.android.test", + mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, createTimingSession(now - 25 * HOUR_IN_MILLIS, 5 * MINUTE_IN_MILLIS, 1), false); synchronized (mQuotaController.mLock) { - mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket); + mQuotaController.maybeScheduleStartAlarmLocked( + SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); } verify(mAlarmManager, timeout(1000).times(0)).setWindow( anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); @@ -2417,42 +2614,168 @@ public class QuotaControllerTest { final long expectedAlarmTime = start + MINUTE_IN_MILLIS + 24 * HOUR_IN_MILLIS + mQcConstants.IN_QUOTA_BUFFER_MS; - mQuotaController.saveTimingSession(0, "com.android.test", + mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, createTimingSession(start, 5 * MINUTE_IN_MILLIS, 1), false); synchronized (mQuotaController.mLock) { - mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket); + mQuotaController.maybeScheduleStartAlarmLocked( + SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); } verify(mAlarmManager, timeout(1000).times(0)).setWindow( anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); // Add some more sessions, but still in quota. - mQuotaController.saveTimingSession(0, "com.android.test", + mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, createTimingSession(now - 3 * HOUR_IN_MILLIS, MINUTE_IN_MILLIS, 1), false); - mQuotaController.saveTimingSession(0, "com.android.test", + mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, createTimingSession(now - HOUR_IN_MILLIS, 3 * MINUTE_IN_MILLIS, 1), false); synchronized (mQuotaController.mLock) { - mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket); + mQuotaController.maybeScheduleStartAlarmLocked( + SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); } verify(mAlarmManager, timeout(1000).times(0)).setWindow( anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); // Test when out of quota. - mQuotaController.saveTimingSession(0, "com.android.test", + mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, createTimingSession(now - HOUR_IN_MILLIS, 2 * MINUTE_IN_MILLIS, 1), false); synchronized (mQuotaController.mLock) { - mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket); + mQuotaController.maybeScheduleStartAlarmLocked( + SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); } verify(mAlarmManager, timeout(1000).times(1)).setWindow( anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); // Alarm already scheduled, so make sure it's not scheduled again. synchronized (mQuotaController.mLock) { - mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket); + mQuotaController.maybeScheduleStartAlarmLocked( + SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); } verify(mAlarmManager, timeout(1000).times(1)).setWindow( anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); } + @Test + public void testMaybeScheduleStartAlarmLocked_Priority() { + // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests + // because it schedules an alarm too. Prevent it from doing so. + spyOn(mQuotaController); + doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked(); + + setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_RARE, 5); + + final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); + mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, + createTimingSession(now - (24 * HOUR_IN_MILLIS), MINUTE_IN_MILLIS, 1), false); + mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, + createTimingSession(now - (7 * HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 1), false); + mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, + createTimingSession(now - (HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 1), false); + mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, + createTimingSession(now - (5 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 1), false); + + InOrder inOrder = inOrder(mAlarmManager); + + JobStatus jobDef = createJobStatus("testMaybeScheduleStartAlarmLocked_Priority", + SOURCE_PACKAGE, CALLING_UID, + new JobInfo.Builder(1, new ComponentName(mContext, "TestQuotaJobService")) + .setPriority(PRIORITY_DEFAULT) + .build()); + JobStatus jobLow = createJobStatus("testMaybeScheduleStartAlarmLocked_Priority", + SOURCE_PACKAGE, CALLING_UID, + new JobInfo.Builder(2, new ComponentName(mContext, "TestQuotaJobService")) + .setPriority(PRIORITY_LOW) + .build()); + JobStatus jobMin = createJobStatus("testMaybeScheduleStartAlarmLocked_Priority", + SOURCE_PACKAGE, CALLING_UID, + new JobInfo.Builder(3, new ComponentName(mContext, "TestQuotaJobService")) + .setPriority(PRIORITY_MIN) + .build()); + + setStandbyBucket(RARE_INDEX, jobDef, jobLow, jobMin); + synchronized (mQuotaController.mLock) { + mQuotaController.maybeStartTrackingJobLocked(jobMin, null); + mQuotaController.maybeScheduleStartAlarmLocked( + SOURCE_USER_ID, SOURCE_PACKAGE, RARE_INDEX); + // Min job requires 5 mins of surplus. + long expectedAlarmTime = now + 23 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS; + inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow( + anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); + + mQuotaController.maybeStartTrackingJobLocked(jobLow, null); + mQuotaController.maybeScheduleStartAlarmLocked( + SOURCE_USER_ID, SOURCE_PACKAGE, RARE_INDEX); + // Low job requires 2.5 mins of surplus. + expectedAlarmTime = now + 17 * HOUR_IN_MILLIS + 90 * SECOND_IN_MILLIS; + inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow( + anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); + + mQuotaController.maybeStartTrackingJobLocked(jobDef, null); + mQuotaController.maybeScheduleStartAlarmLocked( + SOURCE_USER_ID, SOURCE_PACKAGE, RARE_INDEX); + // Default+ jobs require IN_QUOTA_BUFFER_MS. + expectedAlarmTime = now + mQcConstants.IN_QUOTA_BUFFER_MS; + inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow( + anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); + + mQuotaController.maybeStopTrackingJobLocked(jobMin, null, false); + mQuotaController.maybeStopTrackingJobLocked(jobLow, null, false); + mQuotaController.maybeStopTrackingJobLocked(jobDef, null, false); + + setStandbyBucket(FREQUENT_INDEX, jobDef, jobLow, jobMin); + + mQuotaController.maybeStartTrackingJobLocked(jobMin, null); + mQuotaController.maybeScheduleStartAlarmLocked( + SOURCE_USER_ID, SOURCE_PACKAGE, FREQUENT_INDEX); + // Min job requires 5 mins of surplus. + expectedAlarmTime = now + 7 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS; + inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow( + anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); + + mQuotaController.maybeStartTrackingJobLocked(jobLow, null); + mQuotaController.maybeScheduleStartAlarmLocked( + SOURCE_USER_ID, SOURCE_PACKAGE, FREQUENT_INDEX); + // Low job requires 2.5 mins of surplus. + expectedAlarmTime = now + HOUR_IN_MILLIS + 90 * SECOND_IN_MILLIS; + inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow( + anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); + + mQuotaController.maybeStartTrackingJobLocked(jobDef, null); + mQuotaController.maybeScheduleStartAlarmLocked( + SOURCE_USER_ID, SOURCE_PACKAGE, FREQUENT_INDEX); + // Default+ jobs already have enough quota. + inOrder.verify(mAlarmManager, timeout(1000).times(0)).setWindow( + anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); + + mQuotaController.maybeStopTrackingJobLocked(jobMin, null, false); + mQuotaController.maybeStopTrackingJobLocked(jobLow, null, false); + mQuotaController.maybeStopTrackingJobLocked(jobDef, null, false); + + setStandbyBucket(WORKING_INDEX, jobDef, jobLow, jobMin); + + mQuotaController.maybeStartTrackingJobLocked(jobMin, null); + mQuotaController.maybeScheduleStartAlarmLocked( + SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX); + // Min job requires 5 mins of surplus. + expectedAlarmTime = now + HOUR_IN_MILLIS + MINUTE_IN_MILLIS; + inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow( + anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); + + mQuotaController.maybeStartTrackingJobLocked(jobLow, null); + mQuotaController.maybeScheduleStartAlarmLocked( + SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX); + // Low job has enough surplus. + inOrder.verify(mAlarmManager, timeout(1000).times(0)).setWindow( + anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); + + mQuotaController.maybeStartTrackingJobLocked(jobDef, null); + mQuotaController.maybeScheduleStartAlarmLocked( + SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX); + // Default+ jobs already have enough quota. + inOrder.verify(mAlarmManager, timeout(1000).times(0)).setWindow( + anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); + } + } + /** Tests that the start alarm is properly rescheduled if the app's bucket is changed. */ @Test public void testMaybeScheduleStartAlarmLocked_BucketChange() { @@ -2464,24 +2787,29 @@ public class QuotaControllerTest { final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); // Affects rare bucket - mQuotaController.saveTimingSession(0, "com.android.test", + mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, createTimingSession(now - 12 * HOUR_IN_MILLIS, 9 * MINUTE_IN_MILLIS, 3), false); // Affects frequent and rare buckets - mQuotaController.saveTimingSession(0, "com.android.test", + mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, createTimingSession(now - 4 * HOUR_IN_MILLIS, 4 * MINUTE_IN_MILLIS, 3), false); // Affects working, frequent, and rare buckets final long outOfQuotaTime = now - HOUR_IN_MILLIS; - mQuotaController.saveTimingSession(0, "com.android.test", + mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, createTimingSession(outOfQuotaTime, 7 * MINUTE_IN_MILLIS, 10), false); // Affects all buckets - mQuotaController.saveTimingSession(0, "com.android.test", + mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, createTimingSession(now - 5 * MINUTE_IN_MILLIS, 3 * MINUTE_IN_MILLIS, 3), false); InOrder inOrder = inOrder(mAlarmManager); + JobStatus jobStatus = createJobStatus("testMaybeScheduleStartAlarmLocked_BucketChange", 1); + // Start in ACTIVE bucket. + setStandbyBucket(ACTIVE_INDEX, jobStatus); synchronized (mQuotaController.mLock) { - mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", ACTIVE_INDEX); + mQuotaController.maybeStartTrackingJobLocked(jobStatus, null); + mQuotaController.maybeScheduleStartAlarmLocked( + SOURCE_USER_ID, SOURCE_PACKAGE, ACTIVE_INDEX); } inOrder.verify(mAlarmManager, timeout(1000).times(0)) .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); @@ -2492,8 +2820,10 @@ public class QuotaControllerTest { final long expectedWorkingAlarmTime = outOfQuotaTime + (2 * HOUR_IN_MILLIS) + mQcConstants.IN_QUOTA_BUFFER_MS; + setStandbyBucket(WORKING_INDEX, jobStatus); synchronized (mQuotaController.mLock) { - mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", WORKING_INDEX); + mQuotaController.maybeScheduleStartAlarmLocked( + SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX); } inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow( anyInt(), eq(expectedWorkingAlarmTime), anyLong(), @@ -2502,8 +2832,10 @@ public class QuotaControllerTest { final long expectedFrequentAlarmTime = outOfQuotaTime + (8 * HOUR_IN_MILLIS) + mQcConstants.IN_QUOTA_BUFFER_MS; + setStandbyBucket(FREQUENT_INDEX, jobStatus); synchronized (mQuotaController.mLock) { - mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", FREQUENT_INDEX); + mQuotaController.maybeScheduleStartAlarmLocked( + SOURCE_USER_ID, SOURCE_PACKAGE, FREQUENT_INDEX); } inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow( anyInt(), eq(expectedFrequentAlarmTime), anyLong(), @@ -2512,29 +2844,37 @@ public class QuotaControllerTest { final long expectedRareAlarmTime = outOfQuotaTime + (24 * HOUR_IN_MILLIS) + mQcConstants.IN_QUOTA_BUFFER_MS; + setStandbyBucket(RARE_INDEX, jobStatus); synchronized (mQuotaController.mLock) { - mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", RARE_INDEX); + mQuotaController.maybeScheduleStartAlarmLocked( + SOURCE_USER_ID, SOURCE_PACKAGE, RARE_INDEX); } inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow( anyInt(), eq(expectedRareAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); // And back up again. + setStandbyBucket(FREQUENT_INDEX, jobStatus); synchronized (mQuotaController.mLock) { - mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", FREQUENT_INDEX); + mQuotaController.maybeScheduleStartAlarmLocked( + SOURCE_USER_ID, SOURCE_PACKAGE, FREQUENT_INDEX); } inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow( anyInt(), eq(expectedFrequentAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); + setStandbyBucket(WORKING_INDEX, jobStatus); synchronized (mQuotaController.mLock) { - mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", WORKING_INDEX); + mQuotaController.maybeScheduleStartAlarmLocked( + SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX); } inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow( anyInt(), eq(expectedWorkingAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); + setStandbyBucket(ACTIVE_INDEX, jobStatus); synchronized (mQuotaController.mLock) { - mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", ACTIVE_INDEX); + mQuotaController.maybeScheduleStartAlarmLocked( + SOURCE_USER_ID, SOURCE_PACKAGE, ACTIVE_INDEX); } inOrder.verify(mAlarmManager, timeout(1000).times(0)) .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); @@ -2551,6 +2891,13 @@ public class QuotaControllerTest { final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); final int standbyBucket = WORKING_INDEX; + + JobStatus jobStatus = createJobStatus("testMaybeScheduleStartAlarmLocked", 1); + setStandbyBucket(standbyBucket, jobStatus); + synchronized (mQuotaController.mLock) { + mQuotaController.maybeStartTrackingJobLocked(jobStatus, null); + } + ExecutionStats stats; synchronized (mQuotaController.mLock) { stats = mQuotaController.getExecutionStatsLocked( @@ -2646,6 +2993,11 @@ public class QuotaControllerTest { spyOn(mQuotaController); doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked(); + synchronized (mQuotaController.mLock) { + mQuotaController.maybeStartTrackingJobLocked( + createJobStatus("testMaybeScheduleStartAlarmLocked", 1), null); + } + final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); // Working set window size is 2 hours. final int standbyBucket = WORKING_INDEX; @@ -2671,13 +3023,17 @@ public class QuotaControllerTest { anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); } - private void runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_MaxTimeCheck() { // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests // because it schedules an alarm too. Prevent it from doing so. spyOn(mQuotaController); doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked(); + synchronized (mQuotaController.mLock) { + mQuotaController.maybeStartTrackingJobLocked( + createJobStatus("testMaybeScheduleStartAlarmLocked", 1), null); + } + final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); // Working set window size is 2 hours. final int standbyBucket = WORKING_INDEX; @@ -2708,6 +3064,8 @@ public class QuotaControllerTest { public void testConstantsUpdating_ValidValues() { setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_MS, 5 * MINUTE_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_IN_QUOTA_BUFFER_MS, 2 * MINUTE_IN_MILLIS); + setDeviceConfigFloat(QcConstants.KEY_ALLOWED_TIME_SURPLUS_PRIORITY_LOW, .7f); + setDeviceConfigFloat(QcConstants.KEY_ALLOWED_TIME_SURPLUS_PRIORITY_MIN, .2f); setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_ACTIVE_MS, 15 * MINUTE_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_WORKING_MS, 30 * MINUTE_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_FREQUENT_MS, 45 * MINUTE_IN_MILLIS); @@ -2748,6 +3106,8 @@ public class QuotaControllerTest { assertEquals(5 * MINUTE_IN_MILLIS, mQuotaController.getAllowedTimePerPeriodMs()); assertEquals(2 * MINUTE_IN_MILLIS, mQuotaController.getInQuotaBufferMs()); + assertEquals(.7f, mQuotaController.getAllowedTimeSurplusPriorityLow(), 1e-6); + assertEquals(.2f, mQuotaController.getAllowedTimeSurplusPriorityMin(), 1e-6); assertEquals(15 * MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[ACTIVE_INDEX]); assertEquals(30 * MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[WORKING_INDEX]); assertEquals(45 * MINUTE_IN_MILLIS, @@ -2793,6 +3153,8 @@ public class QuotaControllerTest { // Test negatives/too low. setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_MS, -MINUTE_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_IN_QUOTA_BUFFER_MS, -MINUTE_IN_MILLIS); + setDeviceConfigFloat(QcConstants.KEY_ALLOWED_TIME_SURPLUS_PRIORITY_LOW, -.1f); + setDeviceConfigFloat(QcConstants.KEY_ALLOWED_TIME_SURPLUS_PRIORITY_MIN, -.01f); setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_ACTIVE_MS, -MINUTE_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_WORKING_MS, -MINUTE_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_FREQUENT_MS, -MINUTE_IN_MILLIS); @@ -2831,6 +3193,8 @@ public class QuotaControllerTest { assertEquals(MINUTE_IN_MILLIS, mQuotaController.getAllowedTimePerPeriodMs()); assertEquals(0, mQuotaController.getInQuotaBufferMs()); + assertEquals(0f, mQuotaController.getAllowedTimeSurplusPriorityLow(), 1e-6); + assertEquals(0f, mQuotaController.getAllowedTimeSurplusPriorityMin(), 1e-6); assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[ACTIVE_INDEX]); assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[WORKING_INDEX]); assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[FREQUENT_INDEX]); @@ -2878,6 +3242,8 @@ public class QuotaControllerTest { // Test larger than a day. Controller should cap at one day. setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_MS, 25 * HOUR_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_IN_QUOTA_BUFFER_MS, 25 * HOUR_IN_MILLIS); + setDeviceConfigFloat(QcConstants.KEY_ALLOWED_TIME_SURPLUS_PRIORITY_LOW, 1f); + setDeviceConfigFloat(QcConstants.KEY_ALLOWED_TIME_SURPLUS_PRIORITY_MIN, .95f); setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_ACTIVE_MS, 25 * HOUR_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_WORKING_MS, 25 * HOUR_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_FREQUENT_MS, 25 * HOUR_IN_MILLIS); @@ -2905,6 +3271,8 @@ public class QuotaControllerTest { assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getAllowedTimePerPeriodMs()); assertEquals(5 * MINUTE_IN_MILLIS, mQuotaController.getInQuotaBufferMs()); + assertEquals(.9f, mQuotaController.getAllowedTimeSurplusPriorityLow(), 1e-6); + assertEquals(.9f, mQuotaController.getAllowedTimeSurplusPriorityMin(), 1e-6); assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getBucketWindowSizes()[ACTIVE_INDEX]); assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getBucketWindowSizes()[WORKING_INDEX]); assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getBucketWindowSizes()[FREQUENT_INDEX]); @@ -3669,8 +4037,8 @@ public class QuotaControllerTest { // Wait for some extra time to allow for job processing. inOrder.verify(mJobSchedulerService, - timeout(remainingTimeMs + 2 * SECOND_IN_MILLIS).times(0)) - .onControllerStateChanged(any()); + timeout(remainingTimeMs + 2 * SECOND_IN_MILLIS).times(0)) + .onControllerStateChanged(argThat(jobs -> jobs.size() > 0)); synchronized (mQuotaController.mLock) { assertEquals(remainingTimeMs / 2, mQuotaController.getRemainingExecutionTimeLocked(jobBg)); @@ -3681,8 +4049,8 @@ public class QuotaControllerTest { setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING); advanceElapsedClock(remainingTimeMs / 2 + 1); inOrder.verify(mJobSchedulerService, - timeout(remainingTimeMs / 2 + 2 * SECOND_IN_MILLIS).times(1)) - .onControllerStateChanged(any()); + timeout(remainingTimeMs / 2 + 2 * SECOND_IN_MILLIS).times(1)) + .onControllerStateChanged(argThat(jobs -> jobs.size() == 1)); // Top job should still be allowed to run. assertFalse(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); assertTrue(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); @@ -3696,7 +4064,7 @@ public class QuotaControllerTest { advanceElapsedClock(20 * SECOND_IN_MILLIS); setProcessState(ActivityManager.PROCESS_STATE_TOP); inOrder.verify(mJobSchedulerService, timeout(SECOND_IN_MILLIS).times(1)) - .onControllerStateChanged(any()); + .onControllerStateChanged(argThat(jobs -> jobs.size() == 1)); trackJobs(jobFg, jobTop); synchronized (mQuotaController.mLock) { mQuotaController.prepareForExecutionLocked(jobTop); @@ -3715,7 +4083,7 @@ public class QuotaControllerTest { advanceElapsedClock(20 * SECOND_IN_MILLIS); setProcessState(ActivityManager.PROCESS_STATE_SERVICE); inOrder.verify(mJobSchedulerService, timeout(SECOND_IN_MILLIS).times(1)) - .onControllerStateChanged(any()); + .onControllerStateChanged(argThat(jobs -> jobs.size() == 2)); // App is now in background and out of quota. Fg should now change to out of quota since it // wasn't started. Top should remain in quota since it started when the app was in TOP. assertTrue(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); @@ -3753,7 +4121,7 @@ public class QuotaControllerTest { // Wait for some extra time to allow for job processing. verify(mJobSchedulerService, timeout(remainingTimeMs + 2 * SECOND_IN_MILLIS).times(1)) - .onControllerStateChanged(any()); + .onControllerStateChanged(argThat(jobs -> jobs.size() == 1)); assertFalse(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); assertEquals(JobSchedulerService.sElapsedRealtimeClock.millis(), jobStatus.getWhenStandbyDeferred()); @@ -3797,7 +4165,7 @@ public class QuotaControllerTest { // Wait for some extra time to allow for job processing. verify(mJobSchedulerService, timeout(remainingTimeMs + 2 * SECOND_IN_MILLIS).times(0)) - .onControllerStateChanged(any()); + .onControllerStateChanged(argThat(jobs -> jobs.size() > 0)); assertTrue(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); // The job used up the remaining quota, but in that time, the same amount of time in the // old TimingSession also fell out of the quota window, so it should still have the same @@ -3819,7 +4187,7 @@ public class QuotaControllerTest { // Wait for some extra time to allow for job processing. verify(mJobSchedulerService, timeout(12 * SECOND_IN_MILLIS).times(1)) - .onControllerStateChanged(any()); + .onControllerStateChanged(argThat(jobs -> jobs.size() == 1)); assertFalse(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); verify(handler, never()).sendMessageDelayed(any(), anyInt()); } @@ -4406,6 +4774,11 @@ public class QuotaControllerTest { spyOn(mQuotaController); doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked(); + synchronized (mQuotaController.mLock) { + mQuotaController.maybeStartTrackingJobLocked( + createJobStatus("testMaybeScheduleStartAlarmLocked_EJ", 1), null); + } + final int standbyBucket = WORKING_INDEX; setStandbyBucket(standbyBucket); setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_WORKING_MS, 20 * MINUTE_IN_MILLIS); @@ -4482,6 +4855,11 @@ public class QuotaControllerTest { spyOn(mQuotaController); doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked(); + synchronized (mQuotaController.mLock) { + mQuotaController.maybeStartTrackingJobLocked( + createJobStatus("testMaybeScheduleStartAlarmLocked_Ej_BucketChange", 1), null); + } + setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_ACTIVE_MS, 30 * MINUTE_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_WORKING_MS, 20 * MINUTE_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_FREQUENT_MS, 15 * MINUTE_IN_MILLIS); @@ -4590,6 +4968,11 @@ public class QuotaControllerTest { spyOn(mQuotaController); doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked(); + synchronized (mQuotaController.mLock) { + mQuotaController.maybeStartTrackingJobLocked( + createJobStatus("testMaybeScheduleStartAlarmLocked_Ej_SRQ", 1), null); + } + final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); setStandbyBucket(WORKING_INDEX); final long contributionMs = mQcConstants.IN_QUOTA_BUFFER_MS / 2; @@ -5437,8 +5820,8 @@ public class QuotaControllerTest { // Wait for some extra time to allow for job processing. inOrder.verify(mJobSchedulerService, - timeout(remainingTimeMs + 2 * SECOND_IN_MILLIS).times(0)) - .onControllerStateChanged(any()); + timeout(remainingTimeMs + 2 * SECOND_IN_MILLIS).times(0)) + .onControllerStateChanged(argThat(jobs -> jobs.size() > 0)); synchronized (mQuotaController.mLock) { assertEquals(remainingTimeMs / 2, mQuotaController.getRemainingEJExecutionTimeLocked( @@ -5448,8 +5831,8 @@ public class QuotaControllerTest { setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING); advanceElapsedClock(remainingTimeMs / 2 + 1); inOrder.verify(mJobSchedulerService, - timeout(remainingTimeMs / 2 + 2 * SECOND_IN_MILLIS).times(1)) - .onControllerStateChanged(any()); + timeout(remainingTimeMs / 2 + 2 * SECOND_IN_MILLIS).times(1)) + .onControllerStateChanged(argThat(jobs -> jobs.size() == 2)); // Top should still be "in quota" since it started before the app ran on top out of quota. assertFalse(jobBg.isExpeditedQuotaApproved()); assertTrue(jobTop.isExpeditedQuotaApproved()); @@ -5473,7 +5856,7 @@ public class QuotaControllerTest { setProcessState(ActivityManager.PROCESS_STATE_TOP); // Confirm QC recognizes that jobUnstarted has changed from out-of-quota to in-quota. inOrder.verify(mJobSchedulerService, timeout(SECOND_IN_MILLIS).times(1)) - .onControllerStateChanged(any()); + .onControllerStateChanged(argThat(jobs -> jobs.size() == 2)); trackJobs(jobTop2, jobFg); synchronized (mQuotaController.mLock) { mQuotaController.prepareForExecutionLocked(jobTop2); @@ -5494,7 +5877,7 @@ public class QuotaControllerTest { advanceElapsedClock(20 * SECOND_IN_MILLIS); setProcessState(ActivityManager.PROCESS_STATE_SERVICE); inOrder.verify(mJobSchedulerService, timeout(SECOND_IN_MILLIS).times(1)) - .onControllerStateChanged(any()); + .onControllerStateChanged(argThat(jobs -> jobs.size() == 3)); // App is now in background and out of quota. Fg should now change to out of quota since it // wasn't started. Top should remain in quota since it started when the app was in TOP. assertTrue(jobTop2.isExpeditedQuotaApproved()); @@ -5633,7 +6016,7 @@ public class QuotaControllerTest { // Wait for some extra time to allow for job processing. verify(mJobSchedulerService, timeout(remainingTimeMs + 2 * SECOND_IN_MILLIS).times(0)) - .onControllerStateChanged(any()); + .onControllerStateChanged(argThat(jobs -> jobs.size() > 0)); assertTrue(jobStatus.isExpeditedQuotaApproved()); // The job used up the remaining quota, but in that time, the same amount of time in the // old TimingSession also fell out of the quota window, so it should still have the same diff --git a/services/tests/mockingservicestests/src/com/android/server/location/gnss/hal/FakeGnssHal.java b/services/tests/mockingservicestests/src/com/android/server/location/gnss/hal/FakeGnssHal.java index 93a2d317c40e..b7ab6f80167e 100644 --- a/services/tests/mockingservicestests/src/com/android/server/location/gnss/hal/FakeGnssHal.java +++ b/services/tests/mockingservicestests/src/com/android/server/location/gnss/hal/FakeGnssHal.java @@ -109,15 +109,19 @@ public final class FakeGnssHal extends GnssNative.GnssHal { public static class GnssHalBatchingMode { public final long PeriodNanos; + public final float MinUpdateDistanceMeters; public final boolean WakeOnFifoFull; GnssHalBatchingMode() { PeriodNanos = 0; + MinUpdateDistanceMeters = 0.0f; WakeOnFifoFull = false; } - public GnssHalBatchingMode(long periodNanos, boolean wakeOnFifoFull) { + public GnssHalBatchingMode(long periodNanos, float minUpdateDistanceMeters, + boolean wakeOnFifoFull) { PeriodNanos = periodNanos; + MinUpdateDistanceMeters = minUpdateDistanceMeters; WakeOnFifoFull = wakeOnFifoFull; } @@ -132,12 +136,13 @@ public final class FakeGnssHal extends GnssNative.GnssHal { GnssHalBatchingMode that = (GnssHalBatchingMode) o; return PeriodNanos == that.PeriodNanos + && MinUpdateDistanceMeters == that.MinUpdateDistanceMeters && WakeOnFifoFull == that.WakeOnFifoFull; } @Override public int hashCode() { - return Objects.hash(PeriodNanos, WakeOnFifoFull); + return Objects.hash(PeriodNanos, MinUpdateDistanceMeters, WakeOnFifoFull); } } @@ -570,9 +575,11 @@ public final class FakeGnssHal extends GnssNative.GnssHal { protected void cleanupBatching() {} @Override - protected boolean startBatch(long periodNanos, boolean wakeOnFifoFull) { + protected boolean startBatch(long periodNanos, float minUpdateDistanceMeters, + boolean wakeOnFifoFull) { mState.mBatchingStarted = true; - mState.mBatchingMode = new GnssHalBatchingMode(periodNanos, wakeOnFifoFull); + mState.mBatchingMode = new GnssHalBatchingMode(periodNanos, minUpdateDistanceMeters, + wakeOnFifoFull); return true; } @@ -673,7 +680,8 @@ public final class FakeGnssHal extends GnssNative.GnssHal { protected void setAgpsSetId(int type, String setId) {} @Override - protected void setAgpsReferenceLocationCellId(int type, int mcc, int mnc, int lac, int cid) {} + protected void setAgpsReferenceLocationCellId(int type, int mcc, int mnc, int lac, long cid, + int tac, int pcid, int arfcn) {} @Override protected boolean isPsdsSupported() { diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt index c2e0a04e3caa..555f4b8b5cac 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt +++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt @@ -216,6 +216,7 @@ class MockSystem(withSession: (StaticMockitoSessionBuilder) -> Unit = {}) { val displayMetrics: DisplayMetrics = mock() val domainVerificationManagerInternal: DomainVerificationManagerInternal = mock() val handler = TestHandler(null) + val defaultAppProvider: DefaultAppProvider = mock() } companion object { @@ -294,6 +295,7 @@ class MockSystem(withSession: (StaticMockitoSessionBuilder) -> Unit = {}) { whenever(mocks.injector.domainVerificationManagerInternal) .thenReturn(mocks.domainVerificationManagerInternal) whenever(mocks.injector.handler) { mocks.handler } + whenever(mocks.injector.defaultAppProvider) { mocks.defaultAppProvider } wheneverStatic { SystemConfig.getInstance() }.thenReturn(mocks.systemConfig) whenever(mocks.systemConfig.availableFeatures).thenReturn(DEFAULT_AVAILABLE_FEATURES_MAP) whenever(mocks.systemConfig.sharedLibraries).thenReturn(DEFAULT_SHARED_LIBRARIES_LIST) diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt new file mode 100644 index 000000000000..fe7e2d9eb047 --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt @@ -0,0 +1,507 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.pm + +import android.content.Intent +import android.content.pm.PackageManagerInternal +import android.content.pm.SuspendDialogInfo +import android.os.Binder +import android.os.Build +import android.os.Bundle +import android.os.PersistableBundle +import android.os.UserHandle +import android.os.UserManager +import android.util.ArrayMap +import android.util.SparseArray +import com.android.server.pm.pkg.PackageStateInternal +import com.android.server.testutils.TestHandler +import com.android.server.testutils.any +import com.android.server.testutils.eq +import com.android.server.testutils.nullable +import com.android.server.testutils.whenever +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.mockito.ArgumentCaptor +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.Captor +import org.mockito.Mock +import org.mockito.Mockito +import org.mockito.Mockito.argThat +import org.mockito.Mockito.spy +import org.mockito.Mockito.times +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@RunWith(JUnit4::class) +class SuspendPackageHelperTest { + + companion object { + const val TEST_PACKAGE_1 = "com.android.test.package1" + const val TEST_PACKAGE_2 = "com.android.test.package2" + const val DEVICE_OWNER_PACKAGE = "com.android.test.owner" + const val NONEXISTENT_PACKAGE = "com.android.test.nonexistent" + const val DEVICE_ADMIN_PACKAGE = "com.android.test.known.device.admin" + const val DEFAULT_HOME_PACKAGE = "com.android.test.known.home" + const val DIALER_PACKAGE = "com.android.test.known.dialer" + const val INSTALLER_PACKAGE = "com.android.test.known.installer" + const val UNINSTALLER_PACKAGE = "com.android.test.known.uninstaller" + const val VERIFIER_PACKAGE = "com.android.test.known.verifier" + const val PERMISSION_CONTROLLER_PACKAGE = "com.android.test.known.permission" + const val TEST_USER_ID = 0 + } + + lateinit var pms: PackageManagerService + lateinit var suspendPackageHelper: SuspendPackageHelper + lateinit var testHandler: TestHandler + lateinit var defaultAppProvider: DefaultAppProvider + lateinit var packageSetting1: PackageStateInternal + lateinit var packageSetting2: PackageStateInternal + lateinit var ownerSetting: PackageStateInternal + lateinit var packagesToSuspend: Array<String> + lateinit var uidsToSuspend: IntArray + + @Mock + lateinit var broadcastHelper: BroadcastHelper + @Mock + lateinit var protectedPackages: ProtectedPackages + + @Captor + lateinit var bundleCaptor: ArgumentCaptor<Bundle> + + @Rule + @JvmField + val rule = MockSystemRule() + var deviceOwnerUid = 0 + + @Before + @Throws(Exception::class) + fun setup() { + MockitoAnnotations.initMocks(this) + rule.system().stageNominalSystemState() + pms = spy(createPackageManagerService( + TEST_PACKAGE_1, TEST_PACKAGE_2, DEVICE_OWNER_PACKAGE, DEVICE_ADMIN_PACKAGE, + DEFAULT_HOME_PACKAGE, DIALER_PACKAGE, INSTALLER_PACKAGE, UNINSTALLER_PACKAGE, + VERIFIER_PACKAGE, PERMISSION_CONTROLLER_PACKAGE)) + suspendPackageHelper = SuspendPackageHelper( + pms, rule.mocks().injector, broadcastHelper, protectedPackages) + defaultAppProvider = rule.mocks().defaultAppProvider + testHandler = rule.mocks().handler + packageSetting1 = pms.getPackageStateInternal(TEST_PACKAGE_1)!! + packageSetting2 = pms.getPackageStateInternal(TEST_PACKAGE_2)!! + ownerSetting = pms.getPackageStateInternal(DEVICE_OWNER_PACKAGE)!! + deviceOwnerUid = UserHandle.getUid(TEST_USER_ID, ownerSetting.appId) + packagesToSuspend = arrayOf(TEST_PACKAGE_1, TEST_PACKAGE_2) + uidsToSuspend = intArrayOf(packageSetting1.appId, packageSetting2.appId) + + whenever(protectedPackages.getDeviceOwnerOrProfileOwnerPackage(eq(TEST_USER_ID))) + .thenReturn(DEVICE_OWNER_PACKAGE) + whenever(rule.mocks().userManagerService.hasUserRestriction( + eq(UserManager.DISALLOW_APPS_CONTROL), eq(TEST_USER_ID))).thenReturn(true) + whenever(rule.mocks().userManagerService.hasUserRestriction( + eq(UserManager.DISALLOW_UNINSTALL_APPS), eq(TEST_USER_ID))).thenReturn(true) + mockKnownPackages(pms) + } + + @Test + fun setPackagesSuspended() { + val targetPackages = arrayOf(TEST_PACKAGE_1, TEST_PACKAGE_2) + val failedNames = suspendPackageHelper.setPackagesSuspended(targetPackages, + true /* suspended */, null /* appExtras */, null /* launcherExtras */, + null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid) + testHandler.flush() + + verify(pms).scheduleWritePackageRestrictionsLocked(eq(TEST_USER_ID)) + verify(broadcastHelper).sendPackageBroadcast(eq(Intent.ACTION_PACKAGES_SUSPENDED), + nullable(), bundleCaptor.capture(), anyInt(), nullable(), nullable(), any(), + nullable(), nullable(), nullable()) + verify(broadcastHelper).doSendBroadcast(eq(Intent.ACTION_MY_PACKAGE_SUSPENDED), nullable(), + nullable(), any(), eq(TEST_PACKAGE_1), nullable(), any(), any(), nullable(), nullable()) + verify(broadcastHelper).doSendBroadcast(eq(Intent.ACTION_MY_PACKAGE_SUSPENDED), nullable(), + nullable(), any(), eq(TEST_PACKAGE_2), nullable(), any(), any(), nullable(), nullable()) + + var modifiedPackages = bundleCaptor.value.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST) + assertThat(modifiedPackages).asList().containsExactly(TEST_PACKAGE_1, TEST_PACKAGE_2) + assertThat(failedNames).isEmpty() + } + + @Test + fun setPackagesSuspended_emptyPackageName() { + var failedNames = suspendPackageHelper.setPackagesSuspended(null /* packageNames */, + true /* suspended */, null /* appExtras */, null /* launcherExtras */, + null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid) + + assertThat(failedNames).isNull() + + failedNames = suspendPackageHelper.setPackagesSuspended(arrayOfNulls(0), + true /* suspended */, null /* appExtras */, null /* launcherExtras */, + null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid) + + assertThat(failedNames).isEmpty() + } + + @Test + fun setPackagesSuspended_callerIsNotAllowed() { + val failedNames = suspendPackageHelper.setPackagesSuspended(arrayOf(TEST_PACKAGE_2), + true /* suspended */, null /* appExtras */, null /* launcherExtras */, + null /* dialogInfo */, TEST_PACKAGE_1, TEST_USER_ID, Binder.getCallingUid()) + + assertThat(failedNames).asList().hasSize(1) + assertThat(failedNames).asList().contains(TEST_PACKAGE_2) + } + + @Test + fun setPackagesSuspended_callerSuspendItself() { + val failedNames = suspendPackageHelper.setPackagesSuspended(arrayOf(DEVICE_OWNER_PACKAGE), + true /* suspended */, null /* appExtras */, null /* launcherExtras */, + null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid) + + assertThat(failedNames).asList().hasSize(1) + assertThat(failedNames).asList().contains(DEVICE_OWNER_PACKAGE) + } + + @Test + fun setPackagesSuspended_nonexistentPackage() { + val failedNames = suspendPackageHelper.setPackagesSuspended(arrayOf(NONEXISTENT_PACKAGE), + true /* suspended */, null /* appExtras */, null /* launcherExtras */, + null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid) + + assertThat(failedNames).asList().hasSize(1) + assertThat(failedNames).asList().contains(NONEXISTENT_PACKAGE) + } + + @Test + fun setPackagesSuspended_knownPackages() { + val knownPackages = arrayOf(DEVICE_ADMIN_PACKAGE, DEFAULT_HOME_PACKAGE, DIALER_PACKAGE, + INSTALLER_PACKAGE, UNINSTALLER_PACKAGE, VERIFIER_PACKAGE, PERMISSION_CONTROLLER_PACKAGE) + val failedNames = suspendPackageHelper.setPackagesSuspended(knownPackages, + true /* suspended */, null /* appExtras */, null /* launcherExtras */, + null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid)!! + + assertThat(failedNames.size).isEqualTo(knownPackages.size) + for (pkg in knownPackages) { + assertThat(failedNames).asList().contains(pkg) + } + } + + @Test + fun setPackagesUnsuspended() { + val targetPackages = arrayOf(TEST_PACKAGE_1, TEST_PACKAGE_2) + var failedNames = suspendPackageHelper.setPackagesSuspended(targetPackages, + true /* suspended */, null /* appExtras */, null /* launcherExtras */, + null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid) + testHandler.flush() + assertThat(failedNames).isEmpty() + failedNames = suspendPackageHelper.setPackagesSuspended(targetPackages, + false /* suspended */, null /* appExtras */, null /* launcherExtras */, + null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid) + testHandler.flush() + + verify(pms, times(2)).scheduleWritePackageRestrictionsLocked(eq(TEST_USER_ID)) + verify(broadcastHelper).sendPackageBroadcast(eq(Intent.ACTION_PACKAGES_UNSUSPENDED), + nullable(), bundleCaptor.capture(), anyInt(), nullable(), nullable(), any(), + nullable(), nullable(), nullable()) + verify(broadcastHelper).doSendBroadcast(eq(Intent.ACTION_MY_PACKAGE_UNSUSPENDED), + nullable(), nullable(), any(), eq(TEST_PACKAGE_1), nullable(), any(), any(), + nullable(), nullable()) + verify(broadcastHelper).doSendBroadcast(eq(Intent.ACTION_MY_PACKAGE_UNSUSPENDED), + nullable(), nullable(), any(), eq(TEST_PACKAGE_2), nullable(), any(), any(), + nullable(), nullable()) + + var modifiedPackages = bundleCaptor.value.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST) + assertThat(modifiedPackages).asList().containsExactly(TEST_PACKAGE_1, TEST_PACKAGE_2) + assertThat(failedNames).isEmpty() + } + + @Test + fun getUnsuspendablePackagesForUser() { + val suspendables = arrayOf(TEST_PACKAGE_1, TEST_PACKAGE_2) + val unsuspendables = arrayOf(DEVICE_ADMIN_PACKAGE, DEFAULT_HOME_PACKAGE, DIALER_PACKAGE, + INSTALLER_PACKAGE, UNINSTALLER_PACKAGE, VERIFIER_PACKAGE, PERMISSION_CONTROLLER_PACKAGE) + val results = suspendPackageHelper.getUnsuspendablePackagesForUser( + suspendables + unsuspendables, TEST_USER_ID, deviceOwnerUid) + + assertThat(results.size).isEqualTo(unsuspendables.size) + for (pkg in unsuspendables) { + assertThat(results).asList().contains(pkg) + } + } + + @Test + fun getUnsuspendablePackagesForUser_callerIsNotAllowed() { + val suspendables = arrayOf(TEST_PACKAGE_1, TEST_PACKAGE_2) + val results = suspendPackageHelper.getUnsuspendablePackagesForUser( + suspendables, TEST_USER_ID, Binder.getCallingUid()) + + assertThat(results.size).isEqualTo(suspendables.size) + for (pkg in suspendables) { + assertThat(results).asList().contains(pkg) + } + } + + @Test + fun getSuspendedPackageAppExtras() { + val appExtras = PersistableBundle() + appExtras.putString(TEST_PACKAGE_1, TEST_PACKAGE_1) + var failedNames = suspendPackageHelper.setPackagesSuspended(arrayOf(TEST_PACKAGE_1), + true /* suspended */, appExtras, null /* launcherExtras */, + null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid) + testHandler.flush() + assertThat(failedNames).isEmpty() + + val result = suspendPackageHelper.getSuspendedPackageAppExtras( + TEST_PACKAGE_1, TEST_USER_ID, deviceOwnerUid)!! + + assertThat(result.getString(TEST_PACKAGE_1)).isEqualTo(TEST_PACKAGE_1) + } + + @Test + fun removeSuspensionsBySuspendingPackage() { + val appExtras = PersistableBundle() + appExtras.putString(TEST_PACKAGE_1, TEST_PACKAGE_2) + val targetPackages = arrayOf(TEST_PACKAGE_1, TEST_PACKAGE_2) + var failedNames = suspendPackageHelper.setPackagesSuspended(targetPackages, + true /* suspended */, appExtras, null /* launcherExtras */, + null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid) + testHandler.flush() + assertThat(failedNames).isEmpty() + assertThat(suspendPackageHelper.getSuspendingPackage( + TEST_PACKAGE_1, TEST_USER_ID, deviceOwnerUid)).isEqualTo(DEVICE_OWNER_PACKAGE) + assertThat(suspendPackageHelper.getSuspendingPackage( + TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isEqualTo(DEVICE_OWNER_PACKAGE) + assertThat(suspendPackageHelper.getSuspendedPackageAppExtras( + TEST_PACKAGE_1, TEST_USER_ID, deviceOwnerUid)).isNotNull() + assertThat(suspendPackageHelper.getSuspendedPackageAppExtras( + TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isNotNull() + + suspendPackageHelper.removeSuspensionsBySuspendingPackage(targetPackages, + { suspendingPackage -> suspendingPackage == DEVICE_OWNER_PACKAGE }, TEST_USER_ID) + + testHandler.flush() + verify(pms, times(2)).scheduleWritePackageRestrictionsLocked(eq(TEST_USER_ID)) + verify(broadcastHelper).sendPackageBroadcast(eq(Intent.ACTION_PACKAGES_UNSUSPENDED), + nullable(), bundleCaptor.capture(), anyInt(), nullable(), nullable(), any(), + nullable(), nullable(), nullable()) + verify(broadcastHelper).doSendBroadcast(eq(Intent.ACTION_MY_PACKAGE_UNSUSPENDED), + nullable(), nullable(), any(), eq(TEST_PACKAGE_1), nullable(), any(), any(), + nullable(), nullable()) + verify(broadcastHelper).doSendBroadcast(eq(Intent.ACTION_MY_PACKAGE_UNSUSPENDED), + nullable(), nullable(), any(), eq(TEST_PACKAGE_2), nullable(), any(), any(), + nullable(), nullable()) + + assertThat(suspendPackageHelper.getSuspendingPackage( + TEST_PACKAGE_1, TEST_USER_ID, deviceOwnerUid)).isNull() + assertThat(suspendPackageHelper.getSuspendingPackage( + TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isNull() + assertThat(suspendPackageHelper.getSuspendedPackageAppExtras( + TEST_PACKAGE_1, TEST_USER_ID, deviceOwnerUid)).isNull() + assertThat(suspendPackageHelper.getSuspendedPackageAppExtras( + TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isNull() + } + + @Test + fun getSuspendedPackageLauncherExtras() { + val launcherExtras = PersistableBundle() + launcherExtras.putString(TEST_PACKAGE_2, TEST_PACKAGE_2) + var failedNames = suspendPackageHelper.setPackagesSuspended(arrayOf(TEST_PACKAGE_2), + true /* suspended */, null /* appExtras */, launcherExtras, + null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid) + testHandler.flush() + assertThat(failedNames).isEmpty() + + val result = suspendPackageHelper.getSuspendedPackageLauncherExtras( + TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)!! + + assertThat(result.getString(TEST_PACKAGE_2)).isEqualTo(TEST_PACKAGE_2) + } + + @Test + fun isPackageSuspended() { + var failedNames = suspendPackageHelper.setPackagesSuspended(arrayOf(TEST_PACKAGE_1), + true /* suspended */, null /* appExtras */, null /* launcherExtras */, + null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid) + testHandler.flush() + assertThat(failedNames).isEmpty() + + assertThat(suspendPackageHelper.isPackageSuspended( + TEST_PACKAGE_1, TEST_USER_ID, deviceOwnerUid)).isTrue() + } + + @Test + fun getSuspendingPackage() { + val launcherExtras = PersistableBundle() + launcherExtras.putString(TEST_PACKAGE_2, TEST_PACKAGE_2) + var failedNames = suspendPackageHelper.setPackagesSuspended(arrayOf(TEST_PACKAGE_2), + true /* suspended */, null /* appExtras */, launcherExtras, + null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid) + testHandler.flush() + assertThat(failedNames).isEmpty() + + assertThat(suspendPackageHelper.getSuspendingPackage( + TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isEqualTo(DEVICE_OWNER_PACKAGE) + } + + @Test + fun getSuspendedDialogInfo() { + val dialogInfo = SuspendDialogInfo.Builder() + .setTitle(TEST_PACKAGE_1).build() + var failedNames = suspendPackageHelper.setPackagesSuspended(arrayOf(TEST_PACKAGE_1), + true /* suspended */, null /* appExtras */, null /* launcherExtras */, + dialogInfo, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid) + testHandler.flush() + assertThat(failedNames).isEmpty() + + val result = suspendPackageHelper.getSuspendedDialogInfo( + TEST_PACKAGE_1, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid)!! + + assertThat(result.title).isEqualTo(TEST_PACKAGE_1) + } + + @Test + @Throws(Exception::class) + fun sendPackagesSuspendedForUser_withSameVisibilityAllowList() { + mockAllowList(packageSetting1, allowList(10001, 10002, 10003)) + mockAllowList(packageSetting2, allowList(10001, 10002, 10003)) + + suspendPackageHelper.sendPackagesSuspendedForUser(Intent.ACTION_PACKAGES_SUSPENDED, + packagesToSuspend, uidsToSuspend, TEST_USER_ID) + testHandler.flush() + verify(broadcastHelper).sendPackageBroadcast(any(), nullable(), bundleCaptor.capture(), + anyInt(), nullable(), nullable(), any(), nullable(), any(), nullable()) + + var changedPackages = bundleCaptor.value.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST) + var changedUids = bundleCaptor.value.getIntArray(Intent.EXTRA_CHANGED_UID_LIST) + assertThat(changedPackages).asList().containsExactly(TEST_PACKAGE_1, TEST_PACKAGE_2) + assertThat(changedUids).asList().containsExactly( + packageSetting1.appId, packageSetting2.appId) + } + + @Test + @Throws(Exception::class) + fun sendPackagesSuspendedForUser_withDifferentVisibilityAllowList() { + mockAllowList(packageSetting1, allowList(10001, 10002, 10003)) + mockAllowList(packageSetting2, allowList(10001, 10002, 10007)) + + suspendPackageHelper.sendPackagesSuspendedForUser(Intent.ACTION_PACKAGES_SUSPENDED, + packagesToSuspend, uidsToSuspend, TEST_USER_ID) + testHandler.flush() + verify(broadcastHelper, times(2)).sendPackageBroadcast( + any(), nullable(), bundleCaptor.capture(), anyInt(), nullable(), nullable(), any(), + nullable(), any(), nullable()) + + bundleCaptor.allValues.forEach { + var changedPackages = it.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST) + var changedUids = it.getIntArray(Intent.EXTRA_CHANGED_UID_LIST) + assertThat(changedPackages?.size).isEqualTo(1) + assertThat(changedUids?.size).isEqualTo(1) + assertThat(changedPackages?.get(0)).isAnyOf(TEST_PACKAGE_1, TEST_PACKAGE_2) + assertThat(changedUids?.get(0)).isAnyOf(packageSetting1.appId, packageSetting2.appId) + } + } + + @Test + @Throws(Exception::class) + fun sendPackagesSuspendedForUser_withNullVisibilityAllowList() { + mockAllowList(packageSetting1, allowList(10001, 10002, 10003)) + mockAllowList(packageSetting2, null) + + suspendPackageHelper.sendPackagesSuspendedForUser(Intent.ACTION_PACKAGES_SUSPENDED, + packagesToSuspend, uidsToSuspend, TEST_USER_ID) + testHandler.flush() + verify(broadcastHelper, times(2)).sendPackageBroadcast( + any(), nullable(), bundleCaptor.capture(), anyInt(), nullable(), nullable(), any(), + nullable(), nullable(), nullable()) + + bundleCaptor.allValues.forEach { + var changedPackages = it.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST) + var changedUids = it.getIntArray(Intent.EXTRA_CHANGED_UID_LIST) + assertThat(changedPackages?.size).isEqualTo(1) + assertThat(changedUids?.size).isEqualTo(1) + assertThat(changedPackages?.get(0)).isAnyOf(TEST_PACKAGE_1, TEST_PACKAGE_2) + assertThat(changedUids?.get(0)).isAnyOf(packageSetting1.appId, packageSetting2.appId) + } + } + + @Test + @Throws(Exception::class) + fun sendPackagesSuspendModifiedForUser() { + suspendPackageHelper.sendPackagesSuspendedForUser(Intent.ACTION_PACKAGES_SUSPENSION_CHANGED, + packagesToSuspend, uidsToSuspend, TEST_USER_ID) + testHandler.flush() + verify(broadcastHelper).sendPackageBroadcast( + eq(Intent.ACTION_PACKAGES_SUSPENSION_CHANGED), nullable(), bundleCaptor.capture(), + anyInt(), nullable(), nullable(), any(), nullable(), nullable(), nullable()) + + var modifiedPackages = bundleCaptor.value.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST) + var modifiedUids = bundleCaptor.value.getIntArray(Intent.EXTRA_CHANGED_UID_LIST) + assertThat(modifiedPackages).asList().containsExactly(TEST_PACKAGE_1, TEST_PACKAGE_2) + assertThat(modifiedUids).asList().containsExactly( + packageSetting1.appId, packageSetting2.appId) + } + + private fun allowList(vararg uids: Int) = SparseArray<IntArray>().apply { + this.put(TEST_USER_ID, uids) + } + + private fun mockAllowList(pkgSetting: PackageStateInternal, list: SparseArray<IntArray>?) { + whenever(rule.mocks().appsFilter.getVisibilityAllowList( + argThat { it?.packageName == pkgSetting.packageName }, any(IntArray::class.java), + any() as ArrayMap<String, out PackageStateInternal> + )) + .thenReturn(list) + } + + private fun mockKnownPackages(pms: PackageManagerService) { + Mockito.doAnswer { it.arguments[0] == DEVICE_ADMIN_PACKAGE }.`when`(pms) + .isPackageDeviceAdmin(any(), any()) + Mockito.doReturn(DEFAULT_HOME_PACKAGE).`when`(defaultAppProvider) + .getDefaultHome(eq(TEST_USER_ID)) + Mockito.doReturn(DIALER_PACKAGE).`when`(defaultAppProvider) + .getDefaultDialer(eq(TEST_USER_ID)) + Mockito.doReturn(arrayOf(INSTALLER_PACKAGE)).`when`(pms).getKnownPackageNamesInternal( + eq(PackageManagerInternal.PACKAGE_INSTALLER), eq(TEST_USER_ID)) + Mockito.doReturn(arrayOf(UNINSTALLER_PACKAGE)).`when`(pms).getKnownPackageNamesInternal( + eq(PackageManagerInternal.PACKAGE_UNINSTALLER), eq(TEST_USER_ID)) + Mockito.doReturn(arrayOf(VERIFIER_PACKAGE)).`when`(pms).getKnownPackageNamesInternal( + eq(PackageManagerInternal.PACKAGE_VERIFIER), eq(TEST_USER_ID)) + Mockito.doReturn(arrayOf(PERMISSION_CONTROLLER_PACKAGE)).`when`(pms) + .getKnownPackageNamesInternal( + eq(PackageManagerInternal.PACKAGE_PERMISSION_CONTROLLER), eq(TEST_USER_ID)) + } + + private fun createPackageManagerService(vararg stageExistingPackages: String): + PackageManagerService { + stageExistingPackages.forEach { + rule.system().stageScanExistingPackage(it, 1L, + rule.system().dataAppDirectory) + } + var pms = PackageManagerService(rule.mocks().injector, + false /*coreOnly*/, + false /*factoryTest*/, + MockSystem.DEFAULT_VERSION_INFO.fingerprint, + false /*isEngBuild*/, + false /*isUserDebugBuild*/, + Build.VERSION_CODES.CUR_DEVELOPMENT, + Build.VERSION.INCREMENTAL, + false /*snapshotEnabled*/) + rule.system().validateFinalState() + return pms + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackagesBroadcastTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackagesBroadcastTest.kt deleted file mode 100644 index 02ee35b9e7a8..000000000000 --- a/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackagesBroadcastTest.kt +++ /dev/null @@ -1,184 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.pm - -import android.content.Intent -import android.os.Build -import android.os.Bundle -import android.util.ArrayMap -import android.util.SparseArray -import com.android.server.pm.pkg.PackageStateInternal -import com.android.server.testutils.any -import com.android.server.testutils.eq -import com.android.server.testutils.nullable -import com.android.server.testutils.whenever -import com.google.common.truth.Truth.assertThat -import org.junit.Before -import org.junit.Rule -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.JUnit4 -import org.mockito.ArgumentCaptor -import org.mockito.ArgumentMatchers.anyInt -import org.mockito.Captor -import org.mockito.Mockito.argThat -import org.mockito.Mockito.spy -import org.mockito.Mockito.times -import org.mockito.Mockito.verify -import org.mockito.MockitoAnnotations - -@RunWith(JUnit4::class) -class SuspendPackagesBroadcastTest { - - companion object { - const val TEST_PACKAGE_1 = "com.android.test.package1" - const val TEST_PACKAGE_2 = "com.android.test.package2" - const val TEST_USER_ID = 0 - } - - lateinit var pms: PackageManagerService - lateinit var packageSetting1: PackageStateInternal - lateinit var packageSetting2: PackageStateInternal - lateinit var packagesToSuspend: Array<String> - lateinit var uidsToSuspend: IntArray - - @Captor - lateinit var bundleCaptor: ArgumentCaptor<Bundle> - - @Rule - @JvmField - val rule = MockSystemRule() - - @Before - @Throws(Exception::class) - fun setup() { - MockitoAnnotations.initMocks(this) - rule.system().stageNominalSystemState() - pms = spy(createPackageManagerService(TEST_PACKAGE_1, TEST_PACKAGE_2)) - packageSetting1 = pms.getPackageStateInternal(TEST_PACKAGE_1)!! - packageSetting2 = pms.getPackageStateInternal(TEST_PACKAGE_2)!! - packagesToSuspend = arrayOf(TEST_PACKAGE_1, TEST_PACKAGE_2) - uidsToSuspend = intArrayOf(packageSetting1.appId, packageSetting2.appId) - } - - @Test - @Throws(Exception::class) - fun sendPackagesSuspendedForUser_withSameVisibilityAllowList() { - mockAllowList(packageSetting1, allowList(10001, 10002, 10003)) - mockAllowList(packageSetting2, allowList(10001, 10002, 10003)) - - pms.sendPackagesSuspendedForUser(Intent.ACTION_PACKAGES_SUSPENDED, - packagesToSuspend, uidsToSuspend, TEST_USER_ID) - verify(pms).sendPackageBroadcast(any(), nullable(), bundleCaptor.capture(), - anyInt(), nullable(), nullable(), any(), nullable(), any(), nullable()) - - var changedPackages = bundleCaptor.value.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST) - var changedUids = bundleCaptor.value.getIntArray(Intent.EXTRA_CHANGED_UID_LIST) - assertThat(changedPackages).asList().containsExactly(TEST_PACKAGE_1, TEST_PACKAGE_2) - assertThat(changedUids).asList().containsExactly( - packageSetting1.appId, packageSetting2.appId) - } - - @Test - @Throws(Exception::class) - fun sendPackagesSuspendedForUser_withDifferentVisibilityAllowList() { - mockAllowList(packageSetting1, allowList(10001, 10002, 10003)) - mockAllowList(packageSetting2, allowList(10001, 10002, 10007)) - - pms.sendPackagesSuspendedForUser(Intent.ACTION_PACKAGES_SUSPENDED, - packagesToSuspend, uidsToSuspend, TEST_USER_ID) - verify(pms, times(2)).sendPackageBroadcast(any(), nullable(), bundleCaptor.capture(), - anyInt(), nullable(), nullable(), any(), nullable(), any(), nullable()) - - bundleCaptor.allValues.forEach { - var changedPackages = it.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST) - var changedUids = it.getIntArray(Intent.EXTRA_CHANGED_UID_LIST) - assertThat(changedPackages?.size).isEqualTo(1) - assertThat(changedUids?.size).isEqualTo(1) - assertThat(changedPackages?.get(0)).isAnyOf(TEST_PACKAGE_1, TEST_PACKAGE_2) - assertThat(changedUids?.get(0)).isAnyOf(packageSetting1.appId, packageSetting2.appId) - } - } - - @Test - @Throws(Exception::class) - fun sendPackagesSuspendedForUser_withNullVisibilityAllowList() { - mockAllowList(packageSetting1, allowList(10001, 10002, 10003)) - mockAllowList(packageSetting2, null) - - pms.sendPackagesSuspendedForUser(Intent.ACTION_PACKAGES_SUSPENDED, - packagesToSuspend, uidsToSuspend, TEST_USER_ID) - verify(pms, times(2)).sendPackageBroadcast(any(), nullable(), bundleCaptor.capture(), - anyInt(), nullable(), nullable(), any(), nullable(), nullable(), nullable()) - - bundleCaptor.allValues.forEach { - var changedPackages = it.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST) - var changedUids = it.getIntArray(Intent.EXTRA_CHANGED_UID_LIST) - assertThat(changedPackages?.size).isEqualTo(1) - assertThat(changedUids?.size).isEqualTo(1) - assertThat(changedPackages?.get(0)).isAnyOf(TEST_PACKAGE_1, TEST_PACKAGE_2) - assertThat(changedUids?.get(0)).isAnyOf(packageSetting1.appId, packageSetting2.appId) - } - } - - @Test - @Throws(Exception::class) - fun sendPackagesSuspendModifiedForUser() { - pms.sendPackagesSuspendedForUser(Intent.ACTION_PACKAGES_SUSPENSION_CHANGED, - packagesToSuspend, uidsToSuspend, TEST_USER_ID) - verify(pms).sendPackageBroadcast( - eq(Intent.ACTION_PACKAGES_SUSPENSION_CHANGED), nullable(), bundleCaptor.capture(), - anyInt(), nullable(), nullable(), any(), nullable(), nullable(), nullable()) - - var modifiedPackages = bundleCaptor.value.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST) - var modifiedUids = bundleCaptor.value.getIntArray(Intent.EXTRA_CHANGED_UID_LIST) - assertThat(modifiedPackages).asList().containsExactly(TEST_PACKAGE_1, TEST_PACKAGE_2) - assertThat(modifiedUids).asList().containsExactly( - packageSetting1.appId, packageSetting2.appId) - } - - private fun allowList(vararg uids: Int) = SparseArray<IntArray>().apply { - this.put(TEST_USER_ID, uids) - } - - private fun mockAllowList(pkgSetting: PackageStateInternal, list: SparseArray<IntArray>?) { - whenever(rule.mocks().appsFilter.getVisibilityAllowList( - argThat { it?.packageName == pkgSetting.packageName }, any(IntArray::class.java), - any() as ArrayMap<String, out PackageStateInternal> - )) - .thenReturn(list) - } - - private fun createPackageManagerService(vararg stageExistingPackages: String): - PackageManagerService { - stageExistingPackages.forEach { - rule.system().stageScanExistingPackage(it, 1L, - rule.system().dataAppDirectory) - } - var pms = PackageManagerService(rule.mocks().injector, - false /*coreOnly*/, - false /*factoryTest*/, - MockSystem.DEFAULT_VERSION_INFO.fingerprint, - false /*isEngBuild*/, - false /*isUserDebugBuild*/, - Build.VERSION_CODES.CUR_DEVELOPMENT, - Build.VERSION.INCREMENTAL, - false /*snapshotEnabled*/) - rule.system().validateFinalState() - return pms - } -} diff --git a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java index 966675819069..0b488b2add8e 100644 --- a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java +++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java @@ -153,6 +153,9 @@ public class WallpaperManagerServiceTests { sContext.getTestablePermissions().setPermission( android.Manifest.permission.SET_WALLPAPER, PackageManager.PERMISSION_GRANTED); + sContext.getTestablePermissions().setPermission( + android.Manifest.permission.SET_WALLPAPER_DIM_AMOUNT, + PackageManager.PERMISSION_GRANTED); doNothing().when(sContext).sendBroadcastAsUser(any(), any()); //Wallpaper components @@ -433,6 +436,15 @@ public class WallpaperManagerServiceTests { assertTrue(timestamps[1] > timestamps[0]); } + @Test + public void testSetWallpaperDimAmount() throws RemoteException { + mService.switchUser(USER_SYSTEM, null); + float dimAmount = 0.7f; + mService.setWallpaperDimAmount(dimAmount); + assertEquals("Getting dim amount should match after setting the dim amount", + mService.getWallpaperDimAmount(), dimAmount, 0.0); + } + // Verify that after continue switch user from userId 0 to lastUserId, the wallpaper data for // non-current user must not bind to wallpaper service. private void verifyNoConnectionBeforeLastUser(int lastUserId) { diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java index 36c37c4dbf2a..677f0f642e6e 100644 --- a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java @@ -541,11 +541,14 @@ public class ActivityManagerServiceTest { | ActivityManager.UID_OBSERVER_CAPABILITY }; final IUidObserver[] observers = new IUidObserver.Stub[changesToObserve.length]; + doReturn(Process.myUid()).when(sPackageManagerInternal) + .getPackageUid(mContext.getOpPackageName(), 0 /* flags */, mContext.getUserId()); for (int i = 0; i < observers.length; ++i) { observers[i] = mock(IUidObserver.Stub.class); when(observers[i].asBinder()).thenReturn((IBinder) observers[i]); mAms.registerUidObserver(observers[i], changesToObserve[i] /* which */, - ActivityManager.PROCESS_STATE_UNKNOWN /* cutpoint */, null /* caller */); + ActivityManager.PROCESS_STATE_UNKNOWN /* cutpoint */, + mContext.getOpPackageName()); // When we invoke AMS.registerUidObserver, there are some interactions with observers[i] // mock in RemoteCallbackList class. We don't want to test those interactions and @@ -674,10 +677,12 @@ public class ActivityManagerServiceTest { mockNoteOperation(); final IUidObserver observer = mock(IUidObserver.Stub.class); - when(observer.asBinder()).thenReturn((IBinder) observer); + doReturn(Process.myUid()).when(sPackageManagerInternal) + .getPackageUid(mContext.getOpPackageName(), 0 /* flags */, mContext.getUserId()); mAms.registerUidObserver(observer, ActivityManager.UID_OBSERVER_PROCSTATE /* which */, - ActivityManager.PROCESS_STATE_SERVICE /* cutpoint */, null /* callingPackage */); + ActivityManager.PROCESS_STATE_SERVICE /* cutpoint */, + mContext.getOpPackageName()); // When we invoke AMS.registerUidObserver, there are some interactions with observer // mock in RemoteCallbackList class. We don't want to test those interactions and // at the same time, we don't want those to interfere with verifyNoMoreInteractions. @@ -771,7 +776,9 @@ public class ActivityManagerServiceTest { final IUidObserver observer = mock(IUidObserver.Stub.class); when(observer.asBinder()).thenReturn((IBinder) observer); - mAms.registerUidObserver(observer, 0, 0, null); + doReturn(Process.myUid()).when(sPackageManagerInternal) + .getPackageUid(mContext.getOpPackageName(), 0 /* flags */, mContext.getUserId()); + mAms.registerUidObserver(observer, 0, 0, mContext.getOpPackageName()); // Verify that when observers are registered, then validateUids is correctly updated. addPendingUidChanges(pendingItemsForUids); mAms.mUidObserverController.dispatchUidsChanged(); diff --git a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java new file mode 100644 index 000000000000..2b72fabe7cca --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java @@ -0,0 +1,255 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics.log; + +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyBoolean; +import static org.mockito.Mockito.anyFloat; +import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.anyLong; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.hardware.Sensor; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; +import android.hardware.biometrics.BiometricsProtoEnums; +import android.hardware.input.InputSensorInfo; +import android.platform.test.annotations.Presubmit; +import android.testing.TestableContext; + +import androidx.test.filters.SmallTest; +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.server.biometrics.sensors.BaseClientMonitor; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@Presubmit +@SmallTest +public class BiometricLoggerTest { + + private static final int DEFAULT_MODALITY = BiometricsProtoEnums.MODALITY_FINGERPRINT; + private static final int DEFAULT_ACTION = BiometricsProtoEnums.ACTION_AUTHENTICATE; + private static final int DEFAULT_CLIENT = BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT; + + @Rule + public TestableContext mContext = new TestableContext( + InstrumentationRegistry.getInstrumentation().getContext()); + @Mock + private BiometricFrameworkStatsLogger mSink; + @Mock + private SensorManager mSensorManager; + @Mock + private BaseClientMonitor mClient; + + private BiometricLogger mLogger; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + mContext.addMockSystemService(SensorManager.class, mSensorManager); + when(mSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT)).thenReturn( + new Sensor(new InputSensorInfo("", "", 0, 0, Sensor.TYPE_LIGHT, 0, 0, 0, 0, 0, 0, + "", "", 0, 0, 0)) + ); + } + + private BiometricLogger createLogger() { + return createLogger(DEFAULT_MODALITY, DEFAULT_ACTION, DEFAULT_CLIENT); + } + + private BiometricLogger createLogger(int statsModality, int statsAction, int statsClient) { + return new BiometricLogger(statsModality, statsAction, statsClient, mSink, mSensorManager); + } + + @Test + public void testAcquired() { + mLogger = createLogger(); + + final int acquiredInfo = 2; + final int vendorCode = 3; + final boolean isCrypto = true; + final int targetUserId = 9; + + mLogger.logOnAcquired(mContext, acquiredInfo, vendorCode, isCrypto, targetUserId); + + verify(mSink).acquired( + eq(DEFAULT_MODALITY), eq(DEFAULT_ACTION), eq(DEFAULT_CLIENT), anyBoolean(), + eq(acquiredInfo), eq(vendorCode), eq(isCrypto), eq(targetUserId)); + } + + @Test + public void testAuth() { + mLogger = createLogger(); + + final boolean authenticated = true; + final boolean requireConfirmation = false; + final boolean isCrypto = false; + final int targetUserId = 11; + final boolean isBiometricPrompt = true; + + mLogger.logOnAuthenticated(mContext, + authenticated, requireConfirmation, isCrypto, targetUserId, isBiometricPrompt); + + verify(mSink).authenticate( + eq(DEFAULT_MODALITY), eq(DEFAULT_ACTION), eq(DEFAULT_CLIENT), anyBoolean(), + anyLong(), eq(authenticated), anyInt(), eq(requireConfirmation), eq(isCrypto), + eq(targetUserId), eq(isBiometricPrompt), anyFloat()); + } + + @Test + public void testEnroll() { + mLogger = createLogger(); + + final int targetUserId = 4; + final long latency = 44; + final boolean enrollSuccessful = true; + + mLogger.logOnEnrolled(targetUserId, latency, enrollSuccessful); + + verify(mSink).enroll( + eq(DEFAULT_MODALITY), eq(DEFAULT_ACTION), eq(DEFAULT_CLIENT), + eq(targetUserId), eq(latency), eq(enrollSuccessful), anyFloat()); + } + + @Test + public void testError() { + mLogger = createLogger(); + + final int error = 7; + final int vendorCode = 11; + final boolean isCrypto = false; + final int targetUserId = 9; + + mLogger.logOnError(mContext, error, vendorCode, isCrypto, targetUserId); + + verify(mSink).error( + eq(DEFAULT_MODALITY), eq(DEFAULT_ACTION), eq(DEFAULT_CLIENT), anyBoolean(), + anyLong(), eq(error), eq(vendorCode), eq(isCrypto), eq(targetUserId)); + } + + @Test + public void testBadModalityActsDisabled() { + mLogger = createLogger( + BiometricsProtoEnums.MODALITY_UNKNOWN, DEFAULT_ACTION, DEFAULT_CLIENT); + testDisabledMetrics(true /* isBadConfig */); + } + + @Test + public void testBadActionActsDisabled() { + mLogger = createLogger( + DEFAULT_MODALITY, BiometricsProtoEnums.ACTION_UNKNOWN, DEFAULT_CLIENT); + testDisabledMetrics(true /* isBadConfig */); + } + + @Test + public void testDisableLogger() { + mLogger = createLogger(); + testDisabledMetrics(false /* isBadConfig */); + } + + private void testDisabledMetrics(boolean isBadConfig) { + mLogger.disableMetrics(); + mLogger.logOnAcquired(mContext, + 0 /* acquiredInfo */, + 1 /* vendorCode */, + true /* isCrypto */, + 8 /* targetUserId */); + mLogger.logOnAuthenticated(mContext, + true /* authenticated */, + true /* requireConfirmation */, + false /* isCrypto */, + 4 /* targetUserId */, + true/* isBiometricPrompt */); + mLogger.logOnEnrolled(2 /* targetUserId */, + 10 /* latency */, + true /* enrollSuccessful */); + mLogger.logOnError(mContext, + 4 /* error */, + 0 /* vendorCode */, + false /* isCrypto */, + 6 /* targetUserId */); + + verify(mSink, never()).acquired( + anyInt(), anyInt(), anyInt(), anyBoolean(), + anyInt(), anyInt(), anyBoolean(), anyInt()); + verify(mSink, never()).authenticate( + anyInt(), anyInt(), anyInt(), anyBoolean(), + anyLong(), anyBoolean(), anyInt(), anyBoolean(), + anyBoolean(), anyInt(), anyBoolean(), anyFloat()); + verify(mSink, never()).enroll( + anyInt(), anyInt(), anyInt(), anyInt(), anyLong(), anyBoolean(), anyFloat()); + verify(mSink, never()).error( + anyInt(), anyInt(), anyInt(), anyBoolean(), + anyLong(), anyInt(), anyInt(), anyBoolean(), anyInt()); + + mLogger.logUnknownEnrollmentInFramework(); + mLogger.logUnknownEnrollmentInHal(); + + verify(mSink, times(isBadConfig ? 0 : 1)) + .reportUnknownTemplateEnrolledHal(eq(DEFAULT_MODALITY)); + verify(mSink, times(isBadConfig ? 0 : 1)) + .reportUnknownTemplateEnrolledFramework(eq(DEFAULT_MODALITY)); + } + + @Test + public void systemHealthBadHalTemplate() { + mLogger = createLogger(); + mLogger.logUnknownEnrollmentInHal(); + verify(mSink).reportUnknownTemplateEnrolledHal(eq(DEFAULT_MODALITY)); + } + + @Test + public void systemHealthBadFrameworkTemplate() { + mLogger = createLogger(); + mLogger.logUnknownEnrollmentInFramework(); + verify(mSink).reportUnknownTemplateEnrolledFramework(eq(DEFAULT_MODALITY)); + } + + @Test + public void testALSCallback() { + mLogger = createLogger(); + final CallbackWithProbe<Probe> callback = + mLogger.createALSCallback(true /* startWithClient */); + + callback.onClientStarted(mClient); + verify(mSensorManager).registerListener(any(), any(), anyInt()); + + callback.onClientFinished(mClient, true /* success */); + verify(mSensorManager).unregisterListener(any(SensorEventListener.class)); + } + + @Test + public void testALSCallbackDoesNotStart() { + mLogger = createLogger(); + final CallbackWithProbe<Probe> callback = + mLogger.createALSCallback(false /* startWithClient */); + + callback.onClientStarted(mClient); + callback.onClientFinished(mClient, true /* success */); + verify(mSensorManager, never()).registerListener(any(), any(), anyInt()); + } +} diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/AcquisitionClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/AcquisitionClientTest.java index a06a78288535..fc55a9f4cf80 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/AcquisitionClientTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/AcquisitionClientTest.java @@ -52,7 +52,7 @@ public class AcquisitionClientTest { @Mock private ClientMonitorCallbackConverter mClientCallback; @Mock - private BaseClientMonitor.Callback mSchedulerCallback; + private ClientMonitorCallback mSchedulerCallback; @Before public void setUp() { @@ -96,7 +96,7 @@ public class AcquisitionClientTest { } @Override - public void start(@NonNull Callback callback) { + public void start(@NonNull ClientMonitorCallback callback) { super.start(callback); startHalOperation(); } diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BaseClientMonitorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BaseClientMonitorTest.java new file mode 100644 index 000000000000..51d234d5afeb --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BaseClientMonitorTest.java @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics.sensors; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.verify; + +import android.content.Context; +import android.os.IBinder; +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; + +import androidx.annotation.NonNull; +import androidx.test.filters.SmallTest; + +import com.android.server.biometrics.log.BiometricLogger; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@Presubmit +@SmallTest +public class BaseClientMonitorTest { + + @Mock + private Context mContext; + @Mock + private IBinder mToken; + private @Mock ClientMonitorCallbackConverter mListener; + @Mock + private BiometricLogger mLogger; + @Mock + private ClientMonitorCallback mCallback; + + private TestClientMonitor mClientMonitor; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + mClientMonitor = new TestClientMonitor(); + } + + @Test + public void preparesForDeath() throws RemoteException { + verify(mToken).linkToDeath(eq(mClientMonitor), anyInt()); + + mClientMonitor.binderDied(); + + assertThat(mClientMonitor.mCanceled).isTrue(); + assertThat(mClientMonitor.getListener()).isNull(); + } + + @Test + public void ignoresDeathWhenDone() { + mClientMonitor.markAlreadyDone(); + mClientMonitor.binderDied(); + + assertThat(mClientMonitor.mCanceled).isFalse(); + } + + @Test + public void start() { + mClientMonitor.start(mCallback); + + verify(mCallback).onClientStarted(eq(mClientMonitor)); + } + + @Test + public void destroy() { + mClientMonitor.destroy(); + mClientMonitor.destroy(); + + assertThat(mClientMonitor.isAlreadyDone()).isTrue(); + verify(mToken).unlinkToDeath(eq(mClientMonitor), anyInt()); + } + + @Test + public void hasRequestId() { + assertThat(mClientMonitor.hasRequestId()).isFalse(); + + final int id = 200; + mClientMonitor.setRequestId(id); + assertThat(mClientMonitor.hasRequestId()).isTrue(); + assertThat(mClientMonitor.getRequestId()).isEqualTo(id); + } + + private class TestClientMonitor extends BaseClientMonitor implements Interruptable { + public boolean mCanceled = false; + + TestClientMonitor() { + super(mContext, mToken, mListener, 9 /* userId */, "foo" /* owner */, 2 /* cookie */, + 5 /* sensorId */, mLogger); + } + + @Override + public int getProtoEnum() { + return 0; + } + + @Override + public void cancel() { + mCanceled = true; + } + + @Override + public void cancelWithoutStarting(@NonNull ClientMonitorCallback callback) { + mCanceled = true; + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java index d4bac2c0402d..8751cf3810bf 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java @@ -61,11 +61,11 @@ public class BiometricSchedulerOperationTest { @Mock private InterruptableMonitor<FakeHal> mClientMonitor; @Mock - private BaseClientMonitor.Callback mClientCallback; + private ClientMonitorCallback mClientCallback; @Mock private FakeHal mHal; @Captor - ArgumentCaptor<BaseClientMonitor.Callback> mStartCallback; + ArgumentCaptor<ClientMonitorCallback> mStartCallback; private Handler mHandler; private BiometricSchedulerOperation mOperation; @@ -89,7 +89,7 @@ public class BiometricSchedulerOperationTest { assertThat(mOperation.isFinished()).isFalse(); final boolean started = mOperation.startWithCookie( - mock(BaseClientMonitor.Callback.class), cookie); + mock(ClientMonitorCallback.class), cookie); assertThat(started).isTrue(); verify(mClientMonitor).start(mStartCallback.capture()); @@ -106,7 +106,7 @@ public class BiometricSchedulerOperationTest { assertThat(mOperation.isReadyToStart()).isEqualTo(goodCookie); final boolean started = mOperation.startWithCookie( - mock(BaseClientMonitor.Callback.class), badCookie); + mock(ClientMonitorCallback.class), badCookie); assertThat(started).isFalse(); assertThat(mOperation.isStarted()).isFalse(); @@ -119,7 +119,7 @@ public class BiometricSchedulerOperationTest { when(mClientMonitor.getCookie()).thenReturn(0); when(mClientMonitor.getFreshDaemon()).thenReturn(mHal); - final BaseClientMonitor.Callback cb = mock(BaseClientMonitor.Callback.class); + final ClientMonitorCallback cb = mock(ClientMonitorCallback.class); mOperation.start(cb); verify(mClientMonitor).start(mStartCallback.capture()); mStartCallback.getValue().onClientStarted(mClientMonitor); @@ -146,7 +146,7 @@ public class BiometricSchedulerOperationTest { when(mClientMonitor.getCookie()).thenReturn(0); when(mClientMonitor.getFreshDaemon()).thenReturn(null); - final BaseClientMonitor.Callback cb = mock(BaseClientMonitor.Callback.class); + final ClientMonitorCallback cb = mock(ClientMonitorCallback.class); mOperation.start(cb); verify(mClientMonitor, never()).start(any()); @@ -164,17 +164,17 @@ public class BiometricSchedulerOperationTest { public void doesNotStartWithCookie() { when(mClientMonitor.getCookie()).thenReturn(9); assertThrows(IllegalStateException.class, - () -> mOperation.start(mock(BaseClientMonitor.Callback.class))); + () -> mOperation.start(mock(ClientMonitorCallback.class))); } @Test public void cannotRestart() { when(mClientMonitor.getFreshDaemon()).thenReturn(mHal); - mOperation.start(mock(BaseClientMonitor.Callback.class)); + mOperation.start(mock(ClientMonitorCallback.class)); assertThrows(IllegalStateException.class, - () -> mOperation.start(mock(BaseClientMonitor.Callback.class))); + () -> mOperation.start(mock(ClientMonitorCallback.class))); } @Test @@ -187,14 +187,14 @@ public class BiometricSchedulerOperationTest { verify(mClientMonitor).unableToStart(); verify(mClientMonitor).destroy(); assertThrows(IllegalStateException.class, - () -> mOperation.start(mock(BaseClientMonitor.Callback.class))); + () -> mOperation.start(mock(ClientMonitorCallback.class))); } @Test public void cannotAbortRunning() { when(mClientMonitor.getFreshDaemon()).thenReturn(mHal); - mOperation.start(mock(BaseClientMonitor.Callback.class)); + mOperation.start(mock(ClientMonitorCallback.class)); assertThrows(IllegalStateException.class, () -> mOperation.abort()); } @@ -203,8 +203,8 @@ public class BiometricSchedulerOperationTest { public void cancel() { when(mClientMonitor.getFreshDaemon()).thenReturn(mHal); - final BaseClientMonitor.Callback startCb = mock(BaseClientMonitor.Callback.class); - final BaseClientMonitor.Callback cancelCb = mock(BaseClientMonitor.Callback.class); + final ClientMonitorCallback startCb = mock(ClientMonitorCallback.class); + final ClientMonitorCallback cancelCb = mock(ClientMonitorCallback.class); mOperation.start(startCb); verify(mClientMonitor).start(mStartCallback.capture()); mStartCallback.getValue().onClientStarted(mClientMonitor); @@ -230,12 +230,12 @@ public class BiometricSchedulerOperationTest { public void cancelWithoutStarting() { when(mClientMonitor.getFreshDaemon()).thenReturn(mHal); - final BaseClientMonitor.Callback cancelCb = mock(BaseClientMonitor.Callback.class); + final ClientMonitorCallback cancelCb = mock(ClientMonitorCallback.class); mOperation.cancel(mHandler, cancelCb); assertThat(mOperation.isCanceling()).isTrue(); - ArgumentCaptor<BaseClientMonitor.Callback> cbCaptor = - ArgumentCaptor.forClass(BaseClientMonitor.Callback.class); + ArgumentCaptor<ClientMonitorCallback> cbCaptor = + ArgumentCaptor.forClass(ClientMonitorCallback.class); verify(mClientMonitor).cancelWithoutStarting(cbCaptor.capture()); cbCaptor.getValue().onClientFinished(mClientMonitor, true); @@ -278,7 +278,7 @@ public class BiometricSchedulerOperationTest { } mOperation.markCanceling(); - final BaseClientMonitor.Callback cb = mock(BaseClientMonitor.Callback.class); + final ClientMonitorCallback cb = mock(ClientMonitorCallback.class); if (withCookie != null) { mOperation.startWithCookie(cb, withCookie); } else { @@ -307,12 +307,12 @@ public class BiometricSchedulerOperationTest { private void cancelWatchdog(boolean start) { when(mClientMonitor.getFreshDaemon()).thenReturn(mHal); - mOperation.start(mock(BaseClientMonitor.Callback.class)); + mOperation.start(mock(ClientMonitorCallback.class)); if (start) { verify(mClientMonitor).start(mStartCallback.capture()); mStartCallback.getValue().onClientStarted(mClientMonitor); } - mOperation.cancel(mHandler, mock(BaseClientMonitor.Callback.class)); + mOperation.cancel(mHandler, mock(ClientMonitorCallback.class)); assertThat(mOperation.isCanceling()).isTrue(); diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java index ac0831983262..c99d656892f4 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java @@ -114,13 +114,13 @@ public class BiometricSchedulerTest { final TestHalClientMonitor client2 = new TestHalClientMonitor( mContext, mToken, () -> mock(Object.class)); - final BaseClientMonitor.Callback callback1 = mock(BaseClientMonitor.Callback.class); - final BaseClientMonitor.Callback callback2 = mock(BaseClientMonitor.Callback.class); + final ClientMonitorCallback callback1 = mock(ClientMonitorCallback.class); + final ClientMonitorCallback callback2 = mock(ClientMonitorCallback.class); // Pretend the scheduler is busy so the first operation doesn't start right away. We want // to pretend like there are two operations in the queue before kicking things off mScheduler.mCurrentOperation = new BiometricSchedulerOperation( - mock(BaseClientMonitor.class), mock(BaseClientMonitor.Callback.class)); + mock(BaseClientMonitor.class), mock(ClientMonitorCallback.class)); mScheduler.scheduleClientMonitor(client1, callback1); assertEquals(1, mScheduler.mPendingOperations.size()); @@ -152,13 +152,13 @@ public class BiometricSchedulerTest { final TestHalClientMonitor client2 = new TestHalClientMonitor(mContext, mToken, () -> daemon2); - final BaseClientMonitor.Callback callback1 = mock(BaseClientMonitor.Callback.class); - final BaseClientMonitor.Callback callback2 = mock(BaseClientMonitor.Callback.class); + final ClientMonitorCallback callback1 = mock(ClientMonitorCallback.class); + final ClientMonitorCallback callback2 = mock(ClientMonitorCallback.class); // Pretend the scheduler is busy so the first operation doesn't start right away. We want // to pretend like there are two operations in the queue before kicking things off mScheduler.mCurrentOperation = new BiometricSchedulerOperation( - mock(BaseClientMonitor.class), mock(BaseClientMonitor.Callback.class)); + mock(BaseClientMonitor.class), mock(ClientMonitorCallback.class)); mScheduler.scheduleClientMonitor(client1, callback1); assertEquals(1, mScheduler.mPendingOperations.size()); @@ -187,7 +187,7 @@ public class BiometricSchedulerTest { final HalClientMonitor.LazyDaemon<Object> lazyDaemon1 = () -> mock(Object.class); final TestAuthenticationClient client1 = new TestAuthenticationClient(mContext, lazyDaemon1, mToken, mock(ClientMonitorCallbackConverter.class)); - final BaseClientMonitor.Callback callback1 = mock(BaseClientMonitor.Callback.class); + final ClientMonitorCallback callback1 = mock(ClientMonitorCallback.class); // Schedule a BiometricPrompt authentication request mScheduler.scheduleClientMonitor(client1, callback1); @@ -628,7 +628,7 @@ public class BiometricSchedulerTest { } @Override - public void start(@NonNull Callback callback) { + public void start(@NonNull ClientMonitorCallback callback) { super.start(callback); assertFalse(mStarted); mStarted = true; diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/CompositeCallbackTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/CompositeCallbackTest.java index 09b5c5cac466..587bb600f409 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/CompositeCallbackTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/CompositeCallbackTest.java @@ -17,36 +17,62 @@ package com.android.server.biometrics.sensors; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; import android.platform.test.annotations.Presubmit; import androidx.test.filters.SmallTest; +import org.junit.Before; import org.junit.Test; +import org.mockito.InOrder; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Objects; @Presubmit @SmallTest public class CompositeCallbackTest { + @Mock + private BaseClientMonitor mClientMonitor; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + } + @Test - public void testNullCallback() { - BaseClientMonitor.Callback callback1 = mock(BaseClientMonitor.Callback.class); - BaseClientMonitor.Callback callback2 = mock(BaseClientMonitor.Callback.class); - BaseClientMonitor.Callback callback3 = null; + public void testCallbacks() { + testCallbacks(mock(ClientMonitorCallback.class), mock(ClientMonitorCallback.class)); + } + + @Test + public void testNullCallbacks() { + testCallbacks(null, mock(ClientMonitorCallback.class), + null, mock(ClientMonitorCallback.class)); + } - BaseClientMonitor.CompositeCallback callback = new BaseClientMonitor.CompositeCallback( - callback1, callback2, callback3); + private void testCallbacks(ClientMonitorCallback... callbacks) { + final ClientMonitorCallback[] expected = Arrays.stream(callbacks) + .filter(Objects::nonNull).toArray(ClientMonitorCallback[]::new); - BaseClientMonitor clientMonitor = mock(BaseClientMonitor.class); + ClientMonitorCompositeCallback callback = new ClientMonitorCompositeCallback(callbacks); - callback.onClientStarted(clientMonitor); - verify(callback1).onClientStarted(eq(clientMonitor)); - verify(callback2).onClientStarted(eq(clientMonitor)); + callback.onClientStarted(mClientMonitor); + final InOrder order = inOrder(expected); + for (ClientMonitorCallback cb : expected) { + order.verify(cb).onClientStarted(eq(mClientMonitor)); + } - callback.onClientFinished(clientMonitor, true /* success */); - verify(callback1).onClientFinished(eq(clientMonitor), eq(true)); - verify(callback2).onClientFinished(eq(clientMonitor), eq(true)); + callback.onClientFinished(mClientMonitor, true /* success */); + Collections.reverse(Arrays.asList(expected)); + for (ClientMonitorCallback cb : expected) { + order.verify(cb).onClientFinished(eq(mClientMonitor), eq(true)); + } } } diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/UserAwareBiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/UserAwareBiometricSchedulerTest.java index 407f5fb04adf..a11709aff87f 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/UserAwareBiometricSchedulerTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/UserAwareBiometricSchedulerTest.java @@ -155,7 +155,7 @@ public class UserAwareBiometricSchedulerTest { assertNull(mScheduler.mCurrentOperation); final BiometricSchedulerOperation fakeOperation = new BiometricSchedulerOperation( - mock(BaseClientMonitor.class), new BaseClientMonitor.Callback() {}); + mock(BaseClientMonitor.class), new ClientMonitorCallback() {}); mScheduler.mCurrentOperation = fakeOperation; startUserClient.mCallback.onClientFinished(startUserClient, true); assertSame(fakeOperation, mScheduler.mCurrentOperation); @@ -234,7 +234,7 @@ public class UserAwareBiometricSchedulerTest { } @Override - public void start(@NonNull Callback callback) { + public void start(@NonNull ClientMonitorCallback callback) { super.start(callback); onUserStopped(); } @@ -248,7 +248,7 @@ public class UserAwareBiometricSchedulerTest { private static class TestStartUserClient extends StartUserClient<Object, Object> { private final boolean mShouldFinish; - Callback mCallback; + ClientMonitorCallback mCallback; public TestStartUserClient(@NonNull Context context, @NonNull LazyDaemon<Object> lazyDaemon, @Nullable IBinder token, int userId, @@ -263,7 +263,7 @@ public class UserAwareBiometricSchedulerTest { } @Override - public void start(@NonNull Callback callback) { + public void start(@NonNull ClientMonitorCallback callback) { super.start(callback); mCallback = callback; diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClientTest.java index 55dc03595b3d..931fad14888e 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClientTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClientTest.java @@ -34,7 +34,7 @@ import android.platform.test.annotations.Presubmit; import androidx.test.core.app.ApplicationProvider; import androidx.test.filters.SmallTest; -import com.android.server.biometrics.sensors.BaseClientMonitor; +import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; import org.junit.Before; @@ -61,7 +61,7 @@ public class FaceGenerateChallengeClientTest { @Mock private IFaceServiceReceiver mOtherReceiver; @Mock - private BaseClientMonitor.Callback mMonitorCallback; + private ClientMonitorCallback mMonitorCallback; private FaceGenerateChallengeClient mClient; diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java new file mode 100644 index 000000000000..53468c81a1e2 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.companion.virtual; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.verify; + +import android.hardware.input.InputManagerInternal; +import android.os.Binder; +import android.os.IBinder; +import android.platform.test.annotations.Presubmit; +import android.view.Display; + +import androidx.test.runner.AndroidJUnit4; + +import com.android.server.LocalServices; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@Presubmit +@RunWith(AndroidJUnit4.class) +public class InputControllerTest { + + @Mock + private InputManagerInternal mInputManagerInternalMock; + @Mock + private InputController.NativeWrapper mNativeWrapperMock; + + private InputController mInputController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + doNothing().when(mInputManagerInternalMock).setVirtualMousePointerDisplayId(anyInt()); + LocalServices.removeServiceForTest(InputManagerInternal.class); + LocalServices.addService(InputManagerInternal.class, mInputManagerInternalMock); + + mInputController = new InputController(new Object(), mNativeWrapperMock); + } + + @Test + public void unregisterInputDevice_allMiceUnregistered_unsetValues() { + final IBinder deviceToken = new Binder(); + mInputController.createMouse("name", /*vendorId= */ 1, /*productId= */ 1, deviceToken, + /* displayId= */ 1); + mInputController.unregisterInputDevice(deviceToken); + verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId( + eq(Display.INVALID_DISPLAY)); + } + + @Test + public void unregisterInputDevice_anotherMouseExists_setPointerDisplayIdOverride() { + final IBinder deviceToken = new Binder(); + mInputController.createMouse("name", /*vendorId= */ 1, /*productId= */ 1, deviceToken, + /* displayId= */ 1); + verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId(eq(1)); + mInputController.createMouse("name", /*vendorId= */ 1, /*productId= */ 1, deviceToken, + /* displayId= */ 2); + verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId(eq(2)); + mInputController.unregisterInputDevice(deviceToken); + verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId(eq(1)); + } +} diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java index 0a0f7d7a0ae0..72100e44b3eb 100644 --- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java @@ -18,19 +18,24 @@ package com.android.server.companion.virtual; import static com.google.common.truth.Truth.assertWithMessage; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doCallRealMethod; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import static org.testng.Assert.assertThrows; import android.Manifest; +import android.app.admin.DevicePolicyManager; +import android.companion.virtual.IVirtualDeviceActivityListener; import android.companion.virtual.VirtualDeviceParams; import android.content.Context; import android.content.ContextWrapper; import android.graphics.Point; import android.hardware.display.DisplayManagerInternal; +import android.hardware.input.InputManagerInternal; import android.hardware.input.VirtualKeyEvent; import android.hardware.input.VirtualMouseButtonEvent; import android.hardware.input.VirtualMouseRelativeEvent; @@ -73,6 +78,12 @@ public class VirtualDeviceManagerServiceTest { private DisplayManagerInternal mDisplayManagerInternalMock; @Mock private VirtualDeviceImpl.PendingTrampolineCallback mPendingTrampolineCallback; + @Mock + private DevicePolicyManager mDevicePolicyManagerMock; + @Mock + private InputManagerInternal mInputManagerInternalMock; + @Mock + private IVirtualDeviceActivityListener mActivityListener; @Before public void setUp() { @@ -81,17 +92,32 @@ public class VirtualDeviceManagerServiceTest { LocalServices.removeServiceForTest(DisplayManagerInternal.class); LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternalMock); + doNothing().when(mInputManagerInternalMock).setVirtualMousePointerDisplayId(anyInt()); + LocalServices.removeServiceForTest(InputManagerInternal.class); + LocalServices.addService(InputManagerInternal.class, mInputManagerInternalMock); + mContext = Mockito.spy(new ContextWrapper(InstrumentationRegistry.getTargetContext())); doNothing().when(mContext).enforceCallingOrSelfPermission( eq(Manifest.permission.CREATE_VIRTUAL_DEVICE), anyString()); + when(mContext.getSystemService(Context.DEVICE_POLICY_SERVICE)).thenReturn( + mDevicePolicyManagerMock); + mInputController = new InputController(new Object(), mNativeWrapperMock); mDeviceImpl = new VirtualDeviceImpl(mContext, /* association info */ null, new Binder(), /* uid */ 0, mInputController, - (int associationId) -> {}, mPendingTrampolineCallback, + (int associationId) -> {}, mPendingTrampolineCallback, mActivityListener, new VirtualDeviceParams.Builder().build()); } @Test + public void onVirtualDisplayRemovedLocked_doesNotThrowException() { + final int displayId = 2; + mDeviceImpl.onVirtualDisplayCreatedLocked(displayId); + // This call should not throw any exceptions. + mDeviceImpl.onVirtualDisplayRemovedLocked(displayId); + } + + @Test public void createVirtualKeyboard_noDisplay_failsSecurityException() { assertThrows( SecurityException.class, @@ -154,7 +180,7 @@ public class VirtualDeviceManagerServiceTest { mDeviceImpl.createVirtualKeyboard(DISPLAY_ID, DEVICE_NAME, VENDOR_ID, PRODUCT_ID, BINDER); assertWithMessage("Virtual keyboard should register fd when the display matches") - .that(mInputController.mInputDeviceFds).isNotEmpty(); + .that(mInputController.mInputDeviceDescriptors).isNotEmpty(); verify(mNativeWrapperMock).openUinputKeyboard(DEVICE_NAME, VENDOR_ID, PRODUCT_ID); } @@ -164,7 +190,7 @@ public class VirtualDeviceManagerServiceTest { mDeviceImpl.createVirtualMouse(DISPLAY_ID, DEVICE_NAME, VENDOR_ID, PRODUCT_ID, BINDER); assertWithMessage("Virtual keyboard should register fd when the display matches") - .that(mInputController.mInputDeviceFds).isNotEmpty(); + .that(mInputController.mInputDeviceDescriptors).isNotEmpty(); verify(mNativeWrapperMock).openUinputMouse(DEVICE_NAME, VENDOR_ID, PRODUCT_ID); } @@ -174,7 +200,7 @@ public class VirtualDeviceManagerServiceTest { mDeviceImpl.createVirtualTouchscreen(DISPLAY_ID, DEVICE_NAME, VENDOR_ID, PRODUCT_ID, BINDER, new Point(WIDTH, HEIGHT)); assertWithMessage("Virtual keyboard should register fd when the display matches") - .that(mInputController.mInputDeviceFds).isNotEmpty(); + .that(mInputController.mInputDeviceDescriptors).isNotEmpty(); verify(mNativeWrapperMock).openUinputTouchscreen(DEVICE_NAME, VENDOR_ID, PRODUCT_ID, HEIGHT, WIDTH); } @@ -194,7 +220,9 @@ public class VirtualDeviceManagerServiceTest { final int fd = 1; final int keyCode = KeyEvent.KEYCODE_A; final int action = VirtualKeyEvent.ACTION_UP; - mInputController.mInputDeviceFds.put(BINDER, fd); + mInputController.mInputDeviceDescriptors.put(BINDER, + new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 1, + /* displayId= */ 1)); mDeviceImpl.sendKeyEvent(BINDER, new VirtualKeyEvent.Builder().setKeyCode(keyCode) .setAction(action).build()); verify(mNativeWrapperMock).writeKeyEvent(fd, keyCode, action); @@ -217,7 +245,10 @@ public class VirtualDeviceManagerServiceTest { final int fd = 1; final int buttonCode = VirtualMouseButtonEvent.BUTTON_BACK; final int action = VirtualMouseButtonEvent.ACTION_BUTTON_PRESS; - mInputController.mInputDeviceFds.put(BINDER, fd); + mInputController.mInputDeviceDescriptors.put(BINDER, + new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 2, + /* displayId= */ 1)); + mInputController.mActivePointerDisplayId = 1; mDeviceImpl.sendButtonEvent(BINDER, new VirtualMouseButtonEvent.Builder() .setButtonCode(buttonCode) .setAction(action).build()); @@ -225,6 +256,22 @@ public class VirtualDeviceManagerServiceTest { } @Test + public void sendButtonEvent_hasFd_wrongDisplay_throwsIllegalStateException() { + final int fd = 1; + final int buttonCode = VirtualMouseButtonEvent.BUTTON_BACK; + final int action = VirtualMouseButtonEvent.ACTION_BUTTON_PRESS; + mInputController.mInputDeviceDescriptors.put(BINDER, + new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 2, + /* displayId= */ 1)); + assertThrows( + IllegalStateException.class, + () -> + mDeviceImpl.sendButtonEvent(BINDER, new VirtualMouseButtonEvent.Builder() + .setButtonCode(buttonCode) + .setAction(action).build())); + } + + @Test public void sendRelativeEvent_noFd() { assertThrows( IllegalArgumentException.class, @@ -239,13 +286,32 @@ public class VirtualDeviceManagerServiceTest { final int fd = 1; final float x = -0.2f; final float y = 0.7f; - mInputController.mInputDeviceFds.put(BINDER, fd); + mInputController.mInputDeviceDescriptors.put(BINDER, + new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 2, + /* displayId= */ 1)); + mInputController.mActivePointerDisplayId = 1; mDeviceImpl.sendRelativeEvent(BINDER, new VirtualMouseRelativeEvent.Builder() .setRelativeX(x).setRelativeY(y).build()); verify(mNativeWrapperMock).writeRelativeEvent(fd, x, y); } @Test + public void sendRelativeEvent_hasFd_wrongDisplay_throwsIllegalStateException() { + final int fd = 1; + final float x = -0.2f; + final float y = 0.7f; + mInputController.mInputDeviceDescriptors.put(BINDER, + new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 2, + /* displayId= */ 1)); + assertThrows( + IllegalStateException.class, + () -> + mDeviceImpl.sendRelativeEvent(BINDER, + new VirtualMouseRelativeEvent.Builder() + .setRelativeX(x).setRelativeY(y).build())); + } + + @Test public void sendScrollEvent_noFd() { assertThrows( IllegalArgumentException.class, @@ -261,7 +327,10 @@ public class VirtualDeviceManagerServiceTest { final int fd = 1; final float x = 0.5f; final float y = 1f; - mInputController.mInputDeviceFds.put(BINDER, fd); + mInputController.mInputDeviceDescriptors.put(BINDER, + new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 2, + /* displayId= */ 1)); + mInputController.mActivePointerDisplayId = 1; mDeviceImpl.sendScrollEvent(BINDER, new VirtualMouseScrollEvent.Builder() .setXAxisMovement(x) .setYAxisMovement(y).build()); @@ -269,6 +338,22 @@ public class VirtualDeviceManagerServiceTest { } @Test + public void sendScrollEvent_hasFd_wrongDisplay_throwsIllegalStateException() { + final int fd = 1; + final float x = 0.5f; + final float y = 1f; + mInputController.mInputDeviceDescriptors.put(BINDER, + new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 2, + /* displayId= */ 1)); + assertThrows( + IllegalStateException.class, + () -> + mDeviceImpl.sendScrollEvent(BINDER, new VirtualMouseScrollEvent.Builder() + .setXAxisMovement(x) + .setYAxisMovement(y).build())); + } + + @Test public void sendTouchEvent_noFd() { assertThrows( IllegalArgumentException.class, @@ -290,7 +375,9 @@ public class VirtualDeviceManagerServiceTest { final float x = 100.5f; final float y = 200.5f; final int action = VirtualTouchEvent.ACTION_UP; - mInputController.mInputDeviceFds.put(BINDER, fd); + mInputController.mInputDeviceDescriptors.put(BINDER, + new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 3, + /* displayId= */ 1)); mDeviceImpl.sendTouchEvent(BINDER, new VirtualTouchEvent.Builder().setX(x) .setY(y).setAction(action).setPointerId(pointerId).setToolType(toolType).build()); verify(mNativeWrapperMock).writeTouchEvent(fd, pointerId, toolType, action, x, y, Float.NaN, @@ -307,7 +394,9 @@ public class VirtualDeviceManagerServiceTest { final int action = VirtualTouchEvent.ACTION_UP; final float pressure = 1.0f; final float majorAxisSize = 10.0f; - mInputController.mInputDeviceFds.put(BINDER, fd); + mInputController.mInputDeviceDescriptors.put(BINDER, + new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 3, + /* displayId= */ 1)); mDeviceImpl.sendTouchEvent(BINDER, new VirtualTouchEvent.Builder().setX(x) .setY(y).setAction(action).setPointerId(pointerId).setToolType(toolType) .setPressure(pressure).setMajorAxisSize(majorAxisSize).build()); diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java index 1228d625325f..842a4381bc8a 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -18,7 +18,6 @@ package com.android.server.devicepolicy; import static android.app.AppOpsManager.MODE_ALLOWED; import static android.app.AppOpsManager.MODE_DEFAULT; import static android.app.AppOpsManager.OP_ACTIVATE_VPN; -import static android.app.Notification.EXTRA_TEXT; import static android.app.Notification.EXTRA_TITLE; import static android.app.admin.DevicePolicyManager.ACTION_CHECK_POLICY_COMPLIANCE; import static android.app.admin.DevicePolicyManager.DELEGATION_APP_RESTRICTIONS; @@ -34,12 +33,17 @@ import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_LOW; import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_MEDIUM; import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE; import static android.app.admin.DevicePolicyManager.PRIVATE_DNS_SET_NO_ERROR; +import static android.app.admin.DevicePolicyManager.WIFI_SECURITY_ENTERPRISE_192; +import static android.app.admin.DevicePolicyManager.WIFI_SECURITY_ENTERPRISE_EAP; +import static android.app.admin.DevicePolicyManager.WIFI_SECURITY_OPEN; +import static android.app.admin.DevicePolicyManager.WIFI_SECURITY_PERSONAL; import static android.app.admin.DevicePolicyManager.WIPE_EUICC; import static android.app.admin.PasswordMetrics.computeForPasswordOrPin; import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_DIRECT_BOOT_AWARE; import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_DEFAULT; import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE; import static android.net.InetAddresses.parseNumericAddress; +import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_1; import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE; import static com.android.internal.widget.LockPatternUtils.EscrowTokenStateChangeCallback; @@ -91,6 +95,7 @@ import android.app.admin.DevicePolicyManagerLiteInternal; import android.app.admin.FactoryResetProtectionPolicy; import android.app.admin.PasswordMetrics; import android.app.admin.SystemUpdatePolicy; +import android.app.admin.WifiSsidPolicy; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Intent; @@ -1112,6 +1117,10 @@ public class DevicePolicyManagerTest extends DpmTestBase { eq(UserManager.DISALLOW_ADD_MANAGED_PROFILE), eq(true), eq(UserHandle.SYSTEM)); + verify(getServices().userManager, times(1)).setUserRestriction( + eq(UserManager.DISALLOW_ADD_CLONE_PROFILE), + eq(true), eq(UserHandle.SYSTEM)); + verify(mContext.spiedContext, times(1)).sendBroadcastAsUser( MockUtils.checkIntentAction(DevicePolicyManager.ACTION_DEVICE_OWNER_CHANGED), MockUtils.checkUserHandle(UserHandle.USER_SYSTEM)); @@ -1393,6 +1402,10 @@ public class DevicePolicyManagerTest extends DpmTestBase { eq(false), MockUtils.checkUserHandle(UserHandle.USER_SYSTEM)); + verify(getServices().userManager) + .setUserRestriction(eq(UserManager.DISALLOW_ADD_CLONE_PROFILE), eq(false), + MockUtils.checkUserHandle(UserHandle.USER_SYSTEM)); + verify(getServices().userManagerInternal).setDevicePolicyUserRestrictions( eq(UserHandle.USER_SYSTEM), MockUtils.checkUserRestrictions(), MockUtils.checkUserRestrictions(UserHandle.USER_SYSTEM), eq(true)); @@ -4123,6 +4136,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { ProfileNetworkPreference preferenceDetails2 = new ProfileNetworkPreference.Builder() .setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE) + .setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1) .build(); List<ProfileNetworkPreference> preferences2 = new ArrayList<>(); preferences2.add(preferenceDetails); @@ -7209,8 +7223,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { verify(getServices().alarmManager, times(1)).set(anyInt(), eq(PROFILE_OFF_DEADLINE), any()); // Now the user should see a warning notification. verify(getServices().notificationManager, times(1)) - .notify(anyInt(), argThat(hasExtra(EXTRA_TITLE, PROFILE_OFF_SUSPENSION_TITLE, - EXTRA_TEXT, PROFILE_OFF_SUSPENSION_SOON_TEXT))); + .notify(anyInt(), any()); // Apps shouldn't be suspended yet. verifyZeroInteractions(getServices().ipackageManager); clearInvocations(getServices().alarmManager); @@ -7224,8 +7237,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { verifyZeroInteractions(getServices().alarmManager); // Now the user should see a notification about suspended apps. verify(getServices().notificationManager, times(1)) - .notify(anyInt(), argThat(hasExtra(EXTRA_TITLE, PROFILE_OFF_SUSPENSION_TITLE, - EXTRA_TEXT, PROFILE_OFF_SUSPENSION_TEXT))); + .notify(anyInt(), any()); // Verify that the apps are suspended. verify(getServices().ipackageManager, times(1)).setPackagesSuspendedAsUser( any(), eq(true), any(), any(), any(), any(), anyInt()); @@ -7828,6 +7840,128 @@ public class DevicePolicyManagerTest extends DpmTestBase { () -> dpm.getOrganizationNameForUser(UserHandle.USER_SYSTEM)); } + @Test + public void testSetWifiMinimumSecurity_noDeviceOwnerOrPoOfOrgOwnedDevice() { + assertThrows(SecurityException.class, () -> dpm.setMinimumRequiredWifiSecurityLevel( + DevicePolicyManager.WIFI_SECURITY_PERSONAL)); + } + + @Test + public void testSetWifiMinimumSecurity_asDeviceOwner() throws Exception { + setDeviceOwner(); + + final Set<Integer> allowedLevels = Set.of(WIFI_SECURITY_OPEN, WIFI_SECURITY_PERSONAL, + WIFI_SECURITY_ENTERPRISE_EAP, WIFI_SECURITY_ENTERPRISE_192); + for (int level : allowedLevels) { + dpm.setMinimumRequiredWifiSecurityLevel(level); + assertThat(dpm.getMinimumRequiredWifiSecurityLevel()).isEqualTo(level); + } + } + + @Test + public void testSetWifiMinimumSecurity_asPoOfOrgOwnedDevice() throws Exception { + final int managedProfileUserId = 15; + final int managedProfileAdminUid = UserHandle.getUid(managedProfileUserId, 19436); + addManagedProfile(admin1, managedProfileAdminUid, admin1); + configureProfileOwnerOfOrgOwnedDevice(admin1, managedProfileUserId); + mContext.binder.callingUid = managedProfileAdminUid; + + final Set<Integer> allowedLevels = Set.of(WIFI_SECURITY_OPEN, WIFI_SECURITY_PERSONAL, + WIFI_SECURITY_ENTERPRISE_EAP, WIFI_SECURITY_ENTERPRISE_192); + for (int level : allowedLevels) { + dpm.setMinimumRequiredWifiSecurityLevel(level); + assertThat(dpm.getMinimumRequiredWifiSecurityLevel()).isEqualTo(level); + } + } + + @Test + public void testSetSsidAllowlist_noDeviceOwnerOrPoOfOrgOwnedDevice() { + final Set<String> ssids = Collections.singleton("ssid1"); + WifiSsidPolicy policy = WifiSsidPolicy.createAllowlistPolicy(ssids); + assertThrows(SecurityException.class, () -> dpm.setWifiSsidPolicy(policy)); + } + + @Test + public void testSetSsidAllowlist_asDeviceOwner() throws Exception { + setDeviceOwner(); + + final Set<String> ssids = Collections.singleton("ssid1"); + WifiSsidPolicy policy = WifiSsidPolicy.createAllowlistPolicy(ssids); + dpm.setWifiSsidPolicy(policy); + assertThat(dpm.getWifiSsidPolicy().getSsids()).isEqualTo(ssids); + assertThat(dpm.getWifiSsidPolicy().getPolicyType()).isEqualTo( + WifiSsidPolicy.WIFI_SSID_POLICY_TYPE_ALLOWLIST); + } + + @Test + public void testSetSsidAllowlist_asPoOfOrgOwnedDevice() throws Exception { + final int managedProfileUserId = 15; + final int managedProfileAdminUid = UserHandle.getUid(managedProfileUserId, 19436); + addManagedProfile(admin1, managedProfileAdminUid, admin1); + configureProfileOwnerOfOrgOwnedDevice(admin1, managedProfileUserId); + mContext.binder.callingUid = managedProfileAdminUid; + + final Set<String> ssids = new ArraySet<>(Arrays.asList("ssid1", "ssid2", "ssid3")); + WifiSsidPolicy policy = WifiSsidPolicy.createAllowlistPolicy(ssids); + dpm.setWifiSsidPolicy(policy); + assertThat(dpm.getWifiSsidPolicy().getSsids()).isEqualTo(ssids); + assertThat(dpm.getWifiSsidPolicy().getPolicyType()).isEqualTo( + WifiSsidPolicy.WIFI_SSID_POLICY_TYPE_ALLOWLIST); + } + + @Test + public void testSetSsidAllowlist_emptyList() throws Exception { + setDeviceOwner(); + + final Set<String> ssids = new ArraySet<>(); + assertThrows(IllegalArgumentException.class, + () -> WifiSsidPolicy.createAllowlistPolicy(ssids)); + } + + @Test + public void testSetSsidDenylist_noDeviceOwnerOrPoOfOrgOwnedDevice() { + final Set<String> ssids = Collections.singleton("ssid1"); + WifiSsidPolicy policy = WifiSsidPolicy.createDenylistPolicy(ssids); + assertThrows(SecurityException.class, () -> dpm.setWifiSsidPolicy(policy)); + } + + @Test + public void testSetSsidDenylist_asDeviceOwner() throws Exception { + setDeviceOwner(); + + final Set<String> ssids = Collections.singleton("ssid1"); + WifiSsidPolicy policy = WifiSsidPolicy.createDenylistPolicy(ssids); + dpm.setWifiSsidPolicy(policy); + assertThat(dpm.getWifiSsidPolicy().getSsids()).isEqualTo(ssids); + assertThat(dpm.getWifiSsidPolicy().getPolicyType()).isEqualTo( + WifiSsidPolicy.WIFI_SSID_POLICY_TYPE_DENYLIST); + } + + @Test + public void testSetSsidDenylist_asPoOfOrgOwnedDevice() throws Exception { + final int managedProfileUserId = 15; + final int managedProfileAdminUid = UserHandle.getUid(managedProfileUserId, 19436); + addManagedProfile(admin1, managedProfileAdminUid, admin1); + configureProfileOwnerOfOrgOwnedDevice(admin1, managedProfileUserId); + mContext.binder.callingUid = managedProfileAdminUid; + + final Set<String> ssids = new ArraySet<>(Arrays.asList("ssid1", "ssid2", "ssid3")); + WifiSsidPolicy policy = WifiSsidPolicy.createDenylistPolicy(ssids); + dpm.setWifiSsidPolicy(policy); + assertThat(dpm.getWifiSsidPolicy().getSsids()).isEqualTo(ssids); + assertThat(dpm.getWifiSsidPolicy().getPolicyType()).isEqualTo( + WifiSsidPolicy.WIFI_SSID_POLICY_TYPE_DENYLIST); + } + + @Test + public void testSetSsidDenylist_emptyList() throws Exception { + setDeviceOwner(); + + final Set<String> ssids = new ArraySet<>(); + assertThrows(IllegalArgumentException.class, + () -> WifiSsidPolicy.createDenylistPolicy(ssids)); + } + private void setupVpnAuthorization(String userVpnPackage, int userVpnUid) { final AppOpsManager.PackageOps vpnOp = new AppOpsManager.PackageOps(userVpnPackage, userVpnUid, List.of(new AppOpsManager.OpEntry( @@ -7863,18 +7997,6 @@ public class DevicePolicyManagerTest extends DpmTestBase { // To allow creation of Notification via Notification.Builder mContext.applicationInfo = mRealTestContext.getApplicationInfo(); - // Setup resources to render notification titles and texts. - when(mServiceContext.resources - .getString(R.string.personal_apps_suspension_title)) - .thenReturn(PROFILE_OFF_SUSPENSION_TITLE); - when(mServiceContext.resources - .getString(R.string.personal_apps_suspension_text)) - .thenReturn(PROFILE_OFF_SUSPENSION_TEXT); - when(mServiceContext.resources - .getString(eq(R.string.personal_apps_suspension_soon_text), - anyString(), anyString(), anyInt())) - .thenReturn(PROFILE_OFF_SUSPENSION_SOON_TEXT); - // Make locale available for date formatting: when(mServiceContext.resources.getConfiguration()) .thenReturn(mRealTestContext.getResources().getConfiguration()); diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java index d4b1165f6a08..6eb2085d24bb 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java @@ -232,6 +232,8 @@ public class DpmMockContext extends MockContext { return mMockSystemServices.crossProfileApps; case Context.VPN_MANAGEMENT_SERVICE: return mMockSystemServices.vpnManager; + case Context.DEVICE_POLICY_SERVICE: + return mMockSystemServices.devicePolicyManager; } throw new UnsupportedOperationException(); } diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java index 8a2919d55216..597a165cbb0f 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java @@ -31,6 +31,7 @@ import android.app.AppOpsManager; import android.app.IActivityManager; import android.app.IActivityTaskManager; import android.app.NotificationManager; +import android.app.admin.DevicePolicyManager; import android.app.backup.IBackupManager; import android.app.usage.UsageStatsManagerInternal; import android.content.BroadcastReceiver; @@ -125,6 +126,7 @@ public class MockSystemServices { public final AppOpsManager appOpsManager; public final UsbManager usbManager; public final VpnManager vpnManager; + public final DevicePolicyManager devicePolicyManager; /** Note this is a partial mock, not a real mock. */ public final PackageManager packageManager; public final BuildMock buildMock = new BuildMock(); @@ -172,6 +174,7 @@ public class MockSystemServices { appOpsManager = mock(AppOpsManager.class); usbManager = mock(UsbManager.class); vpnManager = mock(VpnManager.class); + devicePolicyManager = mock(DevicePolicyManager.class); // Package manager is huge, so we use a partial mock instead. packageManager = spy(realContext.getPackageManager()); diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java index 9a6f61e7c6cf..81c98717d2e7 100644 --- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java @@ -1746,7 +1746,7 @@ public class NetworkPolicyManagerServiceTest { } private void triggerOnStatsProviderWarningOrLimitReached() throws InterruptedException { - mService.onStatsProviderWarningOrLimitReached(); + mService.notifyStatsProviderWarningOrLimitReached(); // Wait for processing of MSG_STATS_PROVIDER_WARNING_OR_LIMIT_REACHED. postMsgAndWaitForCompletion(); verify(mStatsManager).forceUpdate(); @@ -2046,7 +2046,7 @@ public class NetworkPolicyManagerServiceTest { private static NetworkStateSnapshot buildWifi() { WifiInfo mockWifiInfo = mock(WifiInfo.class); when(mockWifiInfo.makeCopy(anyLong())).thenReturn(mockWifiInfo); - when(mockWifiInfo.getCurrentNetworkKey()).thenReturn(TEST_WIFI_NETWORK_KEY); + when(mockWifiInfo.getNetworkKey()).thenReturn(TEST_WIFI_NETWORK_KEY); final LinkProperties prop = new LinkProperties(); prop.setInterfaceName(TEST_IFACE); final NetworkCapabilities networkCapabilities = new NetworkCapabilities.Builder() @@ -2092,7 +2092,7 @@ public class NetworkPolicyManagerServiceTest { } private void verifyAdvisePersistThreshold() throws Exception { - verify(mStatsManager).advisePersistThreshold(anyLong()); + verify(mStatsManager).setDefaultGlobalAlert(anyLong()); } private static class TestAbstractFuture<T> extends AbstractFuture<T> { diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java index 408d2c525f70..99edecfeed30 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java @@ -16,6 +16,7 @@ package com.android.server.pm; import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertBundlesEqual; +import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertEmpty; import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertExpectException; import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertWith; import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.list; @@ -257,6 +258,10 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { .setLongLived(true) .setExtras(pb) .setStartingTheme(android.R.style.Theme_Black_NoTitleBar_Fullscreen) + .addCapabilityBinding("action.intent.START_EXERCISE", + "exercise.type", list("running", "jogging")) + .addCapabilityBinding("action.intent.START_EXERCISE", + "exercise.duration", list("10m")) .build(); si.addFlags(ShortcutInfo.FLAG_PINNED); si.setBitmapPath("abc"); @@ -294,6 +299,13 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { assertEquals(null, si.getDisabledMessageResName()); assertEquals("android:style/Theme.Black.NoTitleBar.Fullscreen", si.getStartingThemeResName()); + assertTrue(si.hasCapability("action.intent.START_EXERCISE")); + assertFalse(si.hasCapability("")); + assertFalse(si.hasCapability("random")); + assertEquals(list("running", "jogging"), si.getCapabilityParameterValues( + "action.intent.START_EXERCISE", "exercise.type")); + assertEmpty(si.getCapabilityParameterValues("action.intent.START_EXERCISE", "")); + assertEmpty(si.getCapabilityParameterValues("action.intent.START_EXERCISE", "random")); } public void testShortcutInfoParcel_resId() { @@ -947,6 +959,10 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { .setRank(123) .setExtras(pb) .setLocusId(new LocusId("1.2.3.4.5")) + .addCapabilityBinding("action.intent.START_EXERCISE", + "exercise.type", list("running", "jogging")) + .addCapabilityBinding("action.intent.START_EXERCISE", + "exercise.duration", list("10m")) .build(); sorig.setTimestamp(mInjectedCurrentTimeMillis); @@ -1008,6 +1024,14 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { assertNull(si.getIconUri()); assertTrue(si.getLastChangedTimestamp() < now); + assertTrue(si.hasCapability("action.intent.START_EXERCISE")); + assertFalse(si.hasCapability("")); + assertFalse(si.hasCapability("random")); + assertEquals(list("running", "jogging"), si.getCapabilityParameterValues( + "action.intent.START_EXERCISE", "exercise.type")); + assertEmpty(si.getCapabilityParameterValues("action.intent.START_EXERCISE", "")); + assertEmpty(si.getCapabilityParameterValues("action.intent.START_EXERCISE", "random")); + // Make sure ranks are saved too. Because of the auto-adjusting, we need two shortcuts // to test it. si = mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "id2", USER_10); 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 429445f80dbb..7e5fe0496a1c 100644 --- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java @@ -664,6 +664,24 @@ public final class UserManagerTest { } } + // Make sure createProfile would fail if we have DISALLOW_ADD_CLONE_PROFILE. + @MediumTest + @Test + public void testCreateUser_disallowAddClonedUserProfile() throws Exception { + final int primaryUserId = ActivityManager.getCurrentUser(); + final UserHandle primaryUserHandle = asHandle(primaryUserId); + mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_CLONE_PROFILE, + true, primaryUserHandle); + try { + UserInfo cloneProfileUserInfo = createProfileForUser("Clone", + UserManager.USER_TYPE_PROFILE_CLONE, primaryUserId); + assertThat(cloneProfileUserInfo).isNull(); + } finally { + mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_CLONE_PROFILE, false, + primaryUserHandle); + } + } + // Make sure createProfile would fail if we have DISALLOW_ADD_MANAGED_PROFILE. @MediumTest @Test diff --git a/services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java index d164d2a4f581..0e98b5e79aa0 100644 --- a/services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java @@ -29,6 +29,7 @@ import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.anyString; import static org.mockito.Mockito.argThat; import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -38,6 +39,7 @@ import android.app.ActivityManagerInternal; import android.app.StatusBarManager; import android.content.ComponentName; import android.content.Intent; +import android.content.om.IOverlayManager; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; @@ -96,6 +98,10 @@ public class StatusBarManagerServiceTest { private ApplicationInfo mApplicationInfo; @Mock private IStatusBar.Stub mMockStatusBar; + @Mock + private IOverlayManager mOverlayManager; + @Mock + private PackageManager mPackageManager; @Captor private ArgumentCaptor<IAddTileResultCallback> mAddTileResultCallbackCaptor; @@ -130,6 +136,7 @@ public class StatusBarManagerServiceTest { mStatusBarManagerService); mStatusBarManagerService.registerStatusBar(mMockStatusBar); + mStatusBarManagerService.registerOverlayManager(mOverlayManager); mIcon = Icon.createWithResource(mContext, android.R.drawable.btn_plus); } @@ -577,27 +584,56 @@ public class StatusBarManagerServiceTest { } @Test - public void testSetNavBarModeOverride_setsOverrideModeKids() { + public void testSetNavBarModeOverride_setsOverrideModeKids() throws RemoteException { int navBarModeOverrideKids = StatusBarManager.NAV_BAR_MODE_OVERRIDE_KIDS; + mStatusBarManagerService.setNavBarModeOverride(navBarModeOverrideKids); assertEquals(navBarModeOverrideKids, mStatusBarManagerService.getNavBarModeOverride()); + verify(mOverlayManager).setEnabledExclusiveInCategory(anyString(), anyInt()); } @Test - public void testSetNavBarModeOverride_setsOverrideModeNone() { + public void testSetNavBarModeOverride_setsOverrideModeNone() throws RemoteException { int navBarModeOverrideNone = StatusBarManager.NAV_BAR_MODE_OVERRIDE_NONE; + mStatusBarManagerService.setNavBarModeOverride(navBarModeOverrideNone); assertEquals(navBarModeOverrideNone, mStatusBarManagerService.getNavBarModeOverride()); + verify(mOverlayManager, never()).setEnabledExclusiveInCategory(anyString(), anyInt()); } @Test - public void testSetNavBarModeOverride_invalidInputThrowsError() { + public void testSetNavBarModeOverride_invalidInputThrowsError() throws RemoteException { int navBarModeOverrideInvalid = -1; assertThrows(UnsupportedOperationException.class, () -> mStatusBarManagerService.setNavBarModeOverride(navBarModeOverrideInvalid)); + verify(mOverlayManager, never()).setEnabledExclusiveInCategory(anyString(), anyInt()); + } + + @Test + public void testSetNavBarModeOverride_noOverlayManagerDoesNotEnable() throws RemoteException { + mOverlayManager = null; + int navBarModeOverrideKids = StatusBarManager.NAV_BAR_MODE_OVERRIDE_KIDS; + + mStatusBarManagerService.setNavBarModeOverride(navBarModeOverrideKids); + + assertEquals(navBarModeOverrideKids, mStatusBarManagerService.getNavBarModeOverride()); + verify(mOverlayManager, never()).setEnabledExclusiveInCategory(anyString(), anyInt()); + } + + @Test + public void testSetNavBarModeOverride_noPackageDoesNotEnable() throws Exception { + mContext.setMockPackageManager(mPackageManager); + when(mPackageManager.getPackageInfo(anyString(), + any(PackageManager.PackageInfoFlags.class))).thenReturn(null); + int navBarModeOverrideKids = StatusBarManager.NAV_BAR_MODE_OVERRIDE_KIDS; + + mStatusBarManagerService.setNavBarModeOverride(navBarModeOverrideKids); + + assertEquals(navBarModeOverrideKids, mStatusBarManagerService.getNavBarModeOverride()); + verify(mOverlayManager, never()).setEnabledExclusiveInCategory(anyString(), anyInt()); } private void mockUidCheck() { diff --git a/services/tests/servicestests/src/com/android/server/usage/AppIdleHistoryTests.java b/services/tests/servicestests/src/com/android/server/usage/AppIdleHistoryTests.java index 88f368e403a6..06726b031a36 100644 --- a/services/tests/servicestests/src/com/android/server/usage/AppIdleHistoryTests.java +++ b/services/tests/servicestests/src/com/android/server/usage/AppIdleHistoryTests.java @@ -22,16 +22,20 @@ import static android.app.usage.UsageStatsManager.REASON_MAIN_TIMEOUT; import static android.app.usage.UsageStatsManager.REASON_MAIN_USAGE; import static android.app.usage.UsageStatsManager.REASON_SUB_FORCED_SYSTEM_FLAG_BACKGROUND_RESOURCE_USAGE; import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_MOVE_TO_FOREGROUND; +import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_NOTIFICATION_SEEN; +import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_SLICE_PINNED; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_ACTIVE; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_FREQUENT; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RARE; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RESTRICTED; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_WORKING_SET; +import static android.app.usage.UsageStatsManager.standbyBucketToString; import android.os.FileUtils; import android.test.AndroidTestCase; import java.io.File; +import java.util.Map; public class AppIdleHistoryTests extends AndroidTestCase { @@ -65,7 +69,7 @@ public class AppIdleHistoryTests extends AndroidTestCase { // Screen On time file should be written right away assertTrue(aih.getScreenOnTimeFile().exists()); - aih.writeAppIdleTimes(USER_ID); + aih.writeAppIdleTimes(USER_ID, /* elapsedRealtime= */ 2000); // stats file should be written now assertTrue(new File(new File(mStorageDir, "users/" + USER_ID), AppIdleHistory.APP_IDLE_FILENAME).exists()); @@ -128,7 +132,7 @@ public class AppIdleHistoryTests extends AndroidTestCase { // Check persistence aih.writeAppIdleDurations(); - aih.writeAppIdleTimes(USER_ID); + aih.writeAppIdleTimes(USER_ID, /* elapsedRealtime= */ 3000); aih = new AppIdleHistory(mStorageDir, 4000); assertEquals(aih.getAppStandbyBucket(PACKAGE_1, USER_ID, 5000), STANDBY_BUCKET_RARE); assertEquals(aih.getAppStandbyBucket(PACKAGE_2, USER_ID, 5000), STANDBY_BUCKET_ACTIVE); @@ -165,7 +169,7 @@ public class AppIdleHistoryTests extends AndroidTestCase { aih.getAppStandbyReason(PACKAGE_1, USER_ID, 3000)); aih.setAppStandbyBucket(PACKAGE_1, USER_ID, 4000, STANDBY_BUCKET_WORKING_SET, REASON_MAIN_TIMEOUT); - aih.writeAppIdleTimes(USER_ID); + aih.writeAppIdleTimes(USER_ID, /* elapsedRealtime= */ 4000); aih = new AppIdleHistory(mStorageDir, 5000); assertEquals(REASON_MAIN_TIMEOUT, aih.getAppStandbyReason(PACKAGE_1, USER_ID, 5000)); @@ -180,11 +184,63 @@ public class AppIdleHistoryTests extends AndroidTestCase { aih.reportUsage(null, USER_ID, STANDBY_BUCKET_ACTIVE, REASON_SUB_USAGE_MOVE_TO_FOREGROUND, 2000, 0); // Persist data - aih.writeAppIdleTimes(USER_ID); + aih.writeAppIdleTimes(USER_ID, /* elapsedRealtime= */ 2000); // Recover data from disk aih = new AppIdleHistory(mStorageDir, 5000); // Verify data is intact assertEquals(REASON_MAIN_USAGE | REASON_SUB_USAGE_MOVE_TO_FOREGROUND, aih.getAppStandbyReason(PACKAGE_1, USER_ID, 3000)); } + + public void testBucketExpiryTimes() throws Exception { + AppIdleHistory aih = new AppIdleHistory(mStorageDir, 1000 /* elapsedRealtime */); + aih.reportUsage(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET, + REASON_SUB_USAGE_SLICE_PINNED, + 2000 /* elapsedRealtime */, 6000 /* expiryRealtime */); + assertEquals(5000 /* expectedExpiryTimeMs */, aih.getBucketExpiryTimeMs(PACKAGE_1, USER_ID, + STANDBY_BUCKET_WORKING_SET, 2000 /* elapsedRealtime */)); + aih.reportUsage(PACKAGE_2, USER_ID, STANDBY_BUCKET_FREQUENT, + REASON_SUB_USAGE_NOTIFICATION_SEEN, + 2000 /* elapsedRealtime */, 3000 /* expiryRealtime */); + assertEquals(2000 /* expectedExpiryTimeMs */, aih.getBucketExpiryTimeMs(PACKAGE_2, USER_ID, + STANDBY_BUCKET_FREQUENT, 2000 /* elapsedRealtime */)); + aih.writeAppIdleTimes(USER_ID, 4000 /* elapsedRealtime */); + + // Persist data + aih = new AppIdleHistory(mStorageDir, 5000 /* elapsedRealtime */); + final Map<Integer, Long> expectedExpiryTimes1 = Map.of( + STANDBY_BUCKET_ACTIVE, 0L, + STANDBY_BUCKET_WORKING_SET, 5000L, + STANDBY_BUCKET_FREQUENT, 0L, + STANDBY_BUCKET_RARE, 0L, + STANDBY_BUCKET_RESTRICTED, 0L + ); + // For PACKAGE_1, only WORKING_SET bucket should have an expiry time. + verifyBucketExpiryTimes(aih, PACKAGE_1, USER_ID, 5000 /* elapsedRealtime */, + expectedExpiryTimes1); + final Map<Integer, Long> expectedExpiryTimes2 = Map.of( + STANDBY_BUCKET_ACTIVE, 0L, + STANDBY_BUCKET_WORKING_SET, 0L, + STANDBY_BUCKET_FREQUENT, 0L, + STANDBY_BUCKET_RARE, 0L, + STANDBY_BUCKET_RESTRICTED, 0L + ); + // For PACKAGE_2, there shouldn't be any expiry time since the one set earlier would have + // elapsed by the time the data was persisted to disk + verifyBucketExpiryTimes(aih, PACKAGE_2, USER_ID, 5000 /* elapsedRealtime */, + expectedExpiryTimes2); + } + + private void verifyBucketExpiryTimes(AppIdleHistory aih, String packageName, int userId, + long elapsedRealtimeMs, Map<Integer, Long> expectedExpiryTimesMs) throws Exception { + for (Map.Entry<Integer, Long> entry : expectedExpiryTimesMs.entrySet()) { + final int bucket = entry.getKey(); + final long expectedExpiryTimeMs = entry.getValue(); + final long actualExpiryTimeMs = aih.getBucketExpiryTimeMs(packageName, userId, bucket, + elapsedRealtimeMs); + assertEquals("Unexpected expiry time for pkg=" + packageName + ", userId=" + userId + + ", bucket=" + standbyBucketToString(bucket), + expectedExpiryTimeMs, actualExpiryTimeMs); + } + } }
\ No newline at end of file diff --git a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java index 949ee01d6872..18d3f3d0e805 100644 --- a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java +++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java @@ -63,7 +63,6 @@ import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Matchers.anyInt; -import static org.mockito.Matchers.anyLong; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; @@ -828,7 +827,6 @@ public class AppStandbyControllerTests { } @Test - @FlakyTest(bugId = 185169504) public void testNotificationEvent() throws Exception { reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1); assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1)); @@ -842,6 +840,22 @@ public class AppStandbyControllerTests { } @Test + public void testNotificationEvent_changePromotedBucket() throws Exception { + mController.forceIdleState(PACKAGE_1, USER_ID, true); + reportEvent(mController, NOTIFICATION_SEEN, mInjector.mElapsedRealtime, PACKAGE_1); + assertEquals(STANDBY_BUCKET_WORKING_SET, getStandbyBucket(mController, PACKAGE_1)); + + // TODO: Avoid hardcoding these string constants. + mInjector.mSettingsBuilder.setInt("notification_seen_promoted_bucket", + STANDBY_BUCKET_FREQUENT); + mInjector.mPropertiesChangedListener.onPropertiesChanged( + mInjector.getDeviceConfigProperties()); + mController.forceIdleState(PACKAGE_1, USER_ID, true); + reportEvent(mController, NOTIFICATION_SEEN, mInjector.mElapsedRealtime, PACKAGE_1); + assertEquals(STANDBY_BUCKET_FREQUENT, getStandbyBucket(mController, PACKAGE_1)); + } + + @Test @FlakyTest(bugId = 185169504) public void testSlicePinnedEvent() throws Exception { reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1); diff --git a/services/tests/servicestests/src/com/android/server/vibrator/DeviceVibrationEffectAdapterTest.java b/services/tests/servicestests/src/com/android/server/vibrator/DeviceVibrationEffectAdapterTest.java index 739b3b179de7..5e9e16a0baf8 100644 --- a/services/tests/servicestests/src/com/android/server/vibrator/DeviceVibrationEffectAdapterTest.java +++ b/services/tests/servicestests/src/com/android/server/vibrator/DeviceVibrationEffectAdapterTest.java @@ -53,10 +53,10 @@ public class DeviceVibrationEffectAdapterTest { private static final float[] TEST_AMPLITUDE_MAP = new float[]{ /* 50Hz= */ 0.1f, 0.2f, 0.4f, 0.8f, /* 150Hz= */ 1f, 0.9f, /* 200Hz= */ 0.8f}; - private static final VibratorInfo.FrequencyMapping EMPTY_FREQUENCY_MAPPING = - new VibratorInfo.FrequencyMapping(Float.NaN, Float.NaN, Float.NaN, null); - private static final VibratorInfo.FrequencyMapping TEST_FREQUENCY_MAPPING = - new VibratorInfo.FrequencyMapping(TEST_RESONANT_FREQUENCY, TEST_MIN_FREQUENCY, + private static final VibratorInfo.FrequencyProfile EMPTY_FREQUENCY_PROFILE = + new VibratorInfo.FrequencyProfile(Float.NaN, Float.NaN, Float.NaN, null); + private static final VibratorInfo.FrequencyProfile TEST_FREQUENCY_PROFILE = + new VibratorInfo.FrequencyProfile(TEST_RESONANT_FREQUENCY, TEST_MIN_FREQUENCY, TEST_FREQUENCY_RESOLUTION, TEST_AMPLITUDE_MAP); private DeviceVibrationEffectAdapter mAdapter; @@ -79,8 +79,8 @@ public class DeviceVibrationEffectAdapterTest { new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_SPIN, 0.5f, 100)), /* repeatIndex= */ -1); - assertEquals(effect, mAdapter.apply(effect, createVibratorInfo(EMPTY_FREQUENCY_MAPPING))); - assertEquals(effect, mAdapter.apply(effect, createVibratorInfo(TEST_FREQUENCY_MAPPING))); + assertEquals(effect, mAdapter.apply(effect, createVibratorInfo(EMPTY_FREQUENCY_PROFILE))); + assertEquals(effect, mAdapter.apply(effect, createVibratorInfo(TEST_FREQUENCY_PROFILE))); } @Test @@ -97,7 +97,7 @@ public class DeviceVibrationEffectAdapterTest { /* repeatIndex= */ 3); VibrationEffect.Composed adaptedEffect = (VibrationEffect.Composed) mAdapter.apply(effect, - createVibratorInfo(EMPTY_FREQUENCY_MAPPING)); + createVibratorInfo(EMPTY_FREQUENCY_PROFILE)); assertTrue(adaptedEffect.getSegments().size() > effect.getSegments().size()); assertTrue(adaptedEffect.getRepeatIndex() >= effect.getRepeatIndex()); @@ -128,7 +128,7 @@ public class DeviceVibrationEffectAdapterTest { /* startFrequencyHz= */ 200, /* endFrequencyHz= */ 50, /* duration= */ 20)), /* repeatIndex= */ 2); - VibratorInfo info = createVibratorInfo(TEST_FREQUENCY_MAPPING, + VibratorInfo info = createVibratorInfo(TEST_FREQUENCY_PROFILE, IVibrator.CAP_COMPOSE_PWLE_EFFECTS); assertEquals(expected, mAdapter.apply(effect, info)); } @@ -159,7 +159,7 @@ public class DeviceVibrationEffectAdapterTest { /* duration= */ 20)), /* repeatIndex= */ 2); - VibratorInfo info = createVibratorInfo(EMPTY_FREQUENCY_MAPPING, + VibratorInfo info = createVibratorInfo(EMPTY_FREQUENCY_PROFILE, IVibrator.CAP_COMPOSE_PWLE_EFFECTS); assertEquals(expected, mAdapter.apply(effect, info)); } @@ -188,17 +188,17 @@ public class DeviceVibrationEffectAdapterTest { /* startFrequencyHz= */ 200, /* endFrequencyHz= */ 50, /* duration= */ 20)), /* repeatIndex= */ 2); - VibratorInfo info = createVibratorInfo(TEST_FREQUENCY_MAPPING, + VibratorInfo info = createVibratorInfo(TEST_FREQUENCY_PROFILE, IVibrator.CAP_COMPOSE_PWLE_EFFECTS); assertEquals(expected, mAdapter.apply(effect, info)); } - private static VibratorInfo createVibratorInfo(VibratorInfo.FrequencyMapping frequencyMapping, + private static VibratorInfo createVibratorInfo(VibratorInfo.FrequencyProfile frequencyProfile, int... capabilities) { int cap = IntStream.of(capabilities).reduce((a, b) -> a | b).orElse(0); return new VibratorInfo.Builder(0) .setCapabilities(cap) - .setFrequencyMapping(frequencyMapping) + .setFrequencyProfile(frequencyProfile) .build(); } } diff --git a/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java b/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java index 2ad0e93dd1fb..e88e9881181c 100644 --- a/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java +++ b/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java @@ -170,7 +170,7 @@ final class FakeVibratorControllerProvider { } infoBuilder.setCompositionSizeMax(mCompositionSizeMax); infoBuilder.setQFactor(mQFactor); - infoBuilder.setFrequencyMapping(new VibratorInfo.FrequencyMapping( + infoBuilder.setFrequencyProfile(new VibratorInfo.FrequencyProfile( mResonantFrequency, mMinFrequency, mFrequencyResolution, mMaxAmplitudes)); return mIsInfoLoadSuccessful; } diff --git a/services/tests/servicestests/src/com/android/server/vibrator/RampToStepAdapterTest.java b/services/tests/servicestests/src/com/android/server/vibrator/RampToStepAdapterTest.java index 22db91736756..a9f37f35e35b 100644 --- a/services/tests/servicestests/src/com/android/server/vibrator/RampToStepAdapterTest.java +++ b/services/tests/servicestests/src/com/android/server/vibrator/RampToStepAdapterTest.java @@ -47,8 +47,8 @@ public class RampToStepAdapterTest { private static final int TEST_STEP_DURATION = 5; private static final float[] TEST_AMPLITUDE_MAP = new float[]{ /* 50Hz= */ 0.1f, 0.2f, 0.4f, 0.8f, /* 150Hz= */ 1f, 0.9f, /* 200Hz= */ 0.8f}; - private static final VibratorInfo.FrequencyMapping TEST_FREQUENCY_MAPPING = - new VibratorInfo.FrequencyMapping( + private static final VibratorInfo.FrequencyProfile TEST_FREQUENCY_PROFILE = + new VibratorInfo.FrequencyProfile( /* resonantFrequencyHz= */ 150f, /* minFrequencyHz= */ 50f, /* frequencyResolutionHz= */ 25f, TEST_AMPLITUDE_MAP); @@ -124,7 +124,7 @@ public class RampToStepAdapterTest { private static VibratorInfo createVibratorInfo(int... capabilities) { return new VibratorInfo.Builder(0) .setCapabilities(IntStream.of(capabilities).reduce((a, b) -> a | b).orElse(0)) - .setFrequencyMapping(TEST_FREQUENCY_MAPPING) + .setFrequencyProfile(TEST_FREQUENCY_PROFILE) .build(); } } diff --git a/services/tests/servicestests/src/com/android/server/vibrator/StepToRampAdapterTest.java b/services/tests/servicestests/src/com/android/server/vibrator/StepToRampAdapterTest.java index 18ff953446a2..54627c40c7e0 100644 --- a/services/tests/servicestests/src/com/android/server/vibrator/StepToRampAdapterTest.java +++ b/services/tests/servicestests/src/com/android/server/vibrator/StepToRampAdapterTest.java @@ -46,8 +46,8 @@ import java.util.stream.IntStream; public class StepToRampAdapterTest { private static final float[] TEST_AMPLITUDE_MAP = new float[]{ /* 50Hz= */ 0.1f, 0.2f, 0.4f, 0.8f, /* 150Hz= */ 1f, 0.9f, /* 200Hz= */ 0.8f}; - private static final VibratorInfo.FrequencyMapping TEST_FREQUENCY_MAPPING = - new VibratorInfo.FrequencyMapping( + private static final VibratorInfo.FrequencyProfile TEST_FREQUENCY_PROFILE = + new VibratorInfo.FrequencyProfile( /* resonantFrequencyHz= */ 150f, /* minFrequencyHz= */ 50f, /* frequencyResolutionHz= */ 25f, TEST_AMPLITUDE_MAP); @@ -99,7 +99,7 @@ public class StepToRampAdapterTest { VibratorInfo vibratorInfo = new VibratorInfo.Builder(0) .setCapabilities(IVibrator.CAP_COMPOSE_PWLE_EFFECTS) .setPwlePrimitiveDurationMax(10) - .setFrequencyMapping(TEST_FREQUENCY_MAPPING) + .setFrequencyProfile(TEST_FREQUENCY_PROFILE) .build(); // Update repeat index to skip the ramp splits. @@ -191,7 +191,7 @@ public class StepToRampAdapterTest { private static VibratorInfo createVibratorInfo(int... capabilities) { return new VibratorInfo.Builder(0) .setCapabilities(IntStream.of(capabilities).reduce((a, b) -> a | b).orElse(0)) - .setFrequencyMapping(TEST_FREQUENCY_MAPPING) + .setFrequencyProfile(TEST_FREQUENCY_PROFILE) .build(); } } diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibrationScalerTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibrationScalerTest.java index 6369dbc6b171..81677101c139 100644 --- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationScalerTest.java +++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationScalerTest.java @@ -133,13 +133,14 @@ public class VibrationScalerTest { mVibrationScaler.getExternalVibrationScale(USAGE_TOUCH)); setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF); - // Unexpected vibration intensity will be treated as SCALE_NONE. + // Vibration setting being bypassed will use default setting and not scale. assertEquals(IExternalVibratorService.SCALE_NONE, mVibrationScaler.getExternalVibrationScale(USAGE_TOUCH)); } @Test public void scale_withPrebakedSegment_setsEffectStrengthBasedOnSettings() { + setDefaultIntensity(USAGE_NOTIFICATION, VIBRATION_INTENSITY_MEDIUM); setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_HIGH); PrebakedSegment effect = new PrebakedSegment(VibrationEffect.EFFECT_CLICK, /* shouldFallback= */ false, VibrationEffect.EFFECT_STRENGTH_MEDIUM); @@ -158,35 +159,33 @@ public class VibrationScalerTest { setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_OFF); scaled = mVibrationScaler.scale(effect, USAGE_NOTIFICATION); - // Unexpected intensity setting will be mapped to STRONG. - assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_STRONG); + // Vibration setting being bypassed will use default setting. + assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_MEDIUM); } @Test public void scale_withPrebakedEffect_setsEffectStrengthBasedOnSettings() { + setDefaultIntensity(USAGE_NOTIFICATION, VIBRATION_INTENSITY_LOW); setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_HIGH); VibrationEffect effect = VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK); - PrebakedSegment scaled = getFirstSegment(mVibrationScaler.scale( - effect, USAGE_NOTIFICATION)); + PrebakedSegment scaled = + getFirstSegment(mVibrationScaler.scale(effect, USAGE_NOTIFICATION)); assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_STRONG); setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_MEDIUM); - scaled = getFirstSegment(mVibrationScaler.scale( - effect, USAGE_NOTIFICATION)); + scaled = getFirstSegment(mVibrationScaler.scale(effect, USAGE_NOTIFICATION)); assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_MEDIUM); setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_LOW); - scaled = getFirstSegment(mVibrationScaler.scale( - effect, USAGE_NOTIFICATION)); + scaled = getFirstSegment(mVibrationScaler.scale(effect, USAGE_NOTIFICATION)); assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_LIGHT); setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_OFF); - scaled = getFirstSegment(mVibrationScaler.scale( - effect, USAGE_NOTIFICATION)); - // Unexpected intensity setting will be mapped to STRONG. - assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_STRONG); + scaled = getFirstSegment(mVibrationScaler.scale(effect, USAGE_NOTIFICATION)); + // Vibration setting being bypassed will use default setting. + assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_LIGHT); } @Test diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java index ff59d0f22c3c..5d4ffbb6aca2 100644 --- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java +++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java @@ -319,6 +319,34 @@ public class VibrationSettingsTest { } } + + @Test + public void shouldIgnoreVibration_vibrateOnDisabled_ignoresUsagesNotAccessibility() { + setUserSetting(Settings.System.VIBRATE_ON, 0); + + for (int usage : ALL_USAGES) { + if (usage == USAGE_ACCESSIBILITY) { + assertVibrationNotIgnoredForUsage(usage); + } else { + assertVibrationIgnoredForUsage(usage, Vibration.Status.IGNORED_FOR_SETTINGS); + } + assertVibrationNotIgnoredForUsageAndFlags(usage, + VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF); + } + } + + @Test + public void shouldIgnoreVibration_vibrateOnEnabledOrUnset_allowsAnyUsage() { + deleteUserSetting(Settings.System.VIBRATE_ON); + for (int usage : ALL_USAGES) { + assertVibrationNotIgnoredForUsage(usage); + } + + setUserSetting(Settings.System.VIBRATE_ON, 1); + for (int usage : ALL_USAGES) { + assertVibrationNotIgnoredForUsage(usage); + } + } @Test public void shouldIgnoreVibration_withRingSettingsOff_disableRingtoneVibrations() { setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0); @@ -330,6 +358,8 @@ public class VibrationSettingsTest { } else { assertVibrationNotIgnoredForUsage(usage); } + assertVibrationNotIgnoredForUsageAndFlags(usage, + VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF); } } @@ -363,6 +393,8 @@ public class VibrationSettingsTest { } else { assertVibrationNotIgnoredForUsage(usage); } + assertVibrationNotIgnoredForUsageAndFlags(usage, + VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF); } } @@ -376,6 +408,8 @@ public class VibrationSettingsTest { } else { assertVibrationNotIgnoredForUsage(usage); } + assertVibrationNotIgnoredForUsageAndFlags(usage, + VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF); } } @@ -389,6 +423,8 @@ public class VibrationSettingsTest { } else { assertVibrationNotIgnoredForUsage(usage); } + assertVibrationNotIgnoredForUsageAndFlags(usage, + VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF); } } @@ -402,6 +438,8 @@ public class VibrationSettingsTest { } else { assertVibrationNotIgnoredForUsage(usage); } + assertVibrationNotIgnoredForUsageAndFlags(usage, + VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF); } } @@ -419,6 +457,8 @@ public class VibrationSettingsTest { } else { assertVibrationNotIgnoredForUsage(usage); } + assertVibrationNotIgnoredForUsageAndFlags(usage, + VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF); } } @@ -522,9 +562,17 @@ public class VibrationSettingsTest { } private void assertVibrationNotIgnoredForUsage(@VibrationAttributes.Usage int usage) { + assertVibrationNotIgnoredForUsageAndFlags(usage, /* flags= */ 0); + } + + private void assertVibrationNotIgnoredForUsageAndFlags(@VibrationAttributes.Usage int usage, + @VibrationAttributes.Flag int flags) { assertNull(errorMessageForUsage(usage), mVibrationSettings.shouldIgnoreVibration(UID, - VibrationAttributes.createForUsage(usage))); + new VibrationAttributes.Builder() + .setUsage(usage) + .setFlags(flags, VibrationAttributes.FLAG_ALL_SUPPORTED) + .build())); } private String errorMessageForUsage(int usage) { @@ -540,10 +588,17 @@ public class VibrationSettingsTest { when(mVibrationConfigMock.getDefaultVibrationIntensity(eq(usage))).thenReturn(intensity); } + private void deleteUserSetting(String settingName) { + Settings.System.putStringForUser( + mContextSpy.getContentResolver(), settingName, null, UserHandle.USER_CURRENT); + // FakeSettingsProvider doesn't support testing triggering ContentObserver yet. + mVibrationSettings.updateSettings(); + } + private void setUserSetting(String settingName, int value) { Settings.System.putIntForUser( mContextSpy.getContentResolver(), settingName, value, UserHandle.USER_CURRENT); - // FakeSettingsProvider don't support testing triggering ContentObserver yet. + // FakeSettingsProvider doesn't support testing triggering ContentObserver yet. mVibrationSettings.updateSettings(); } diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibratorControllerTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibratorControllerTest.java index cb4982be40c3..f2c1874de392 100644 --- a/services/tests/servicestests/src/com/android/server/vibrator/VibratorControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/vibrator/VibratorControllerTest.java @@ -310,13 +310,13 @@ public class VibratorControllerTest { } private void mockVibratorCapabilities(int capabilities) { - VibratorInfo.FrequencyMapping frequencyMapping = new VibratorInfo.FrequencyMapping( + VibratorInfo.FrequencyProfile frequencyProfile = new VibratorInfo.FrequencyProfile( Float.NaN, Float.NaN, Float.NaN, null); when(mNativeWrapperMock.getInfo(any(VibratorInfo.Builder.class))) .then(invocation -> { ((VibratorInfo.Builder) invocation.getArgument(0)) .setCapabilities(capabilities) - .setFrequencyMapping(frequencyMapping); + .setFrequencyProfile(frequencyProfile); return true; }); } diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java index b0bdaf084b1a..52975ef8bfe1 100644 --- a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java @@ -316,7 +316,7 @@ public class VibratorManagerServiceTest { assertNotNull(info); assertEquals(1, info.getId()); - assertEquals(123.f, info.getResonantFrequency(), 0.01 /*tolerance*/); + assertEquals(123.f, info.getResonantFrequencyHz(), 0.01 /*tolerance*/); } @Test @@ -341,7 +341,7 @@ public class VibratorManagerServiceTest { info.isEffectSupported(VibrationEffect.EFFECT_TICK)); assertTrue(info.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_CLICK)); assertFalse(info.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_TICK)); - assertEquals(123.f, info.getResonantFrequency(), 0.01 /*tolerance*/); + assertEquals(123.f, info.getResonantFrequencyHz(), 0.01 /*tolerance*/); assertTrue(Float.isNaN(info.getQFactor())); } @@ -360,7 +360,7 @@ public class VibratorManagerServiceTest { VibratorInfo info = createService().getVibratorInfo(1); assertNotNull(info); assertEquals(1, info.getId()); - assertEquals(123.f, info.getResonantFrequency(), 0.01 /*tolerance*/); + assertEquals(123.f, info.getResonantFrequencyHz(), 0.01 /*tolerance*/); } @Test @@ -1207,6 +1207,33 @@ public class VibratorManagerServiceTest { assertNotEquals(IExternalVibratorService.SCALE_MUTE, scale); } + @Test + public void onExternalVibration_withBypassMuteAudioFlag_ignoresUserSettings() { + mockVibrators(1); + mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL); + setUserSetting(Settings.System.ALARM_VIBRATION_INTENSITY, + Vibrator.VIBRATION_INTENSITY_OFF); + AudioAttributes audioAttrs = new AudioAttributes.Builder() + .setUsage(AudioAttributes.USAGE_ALARM) + .build(); + AudioAttributes flaggedAudioAttrs = new AudioAttributes.Builder() + .setUsage(AudioAttributes.USAGE_ALARM) + .setFlags(AudioAttributes.FLAG_BYPASS_MUTE) + .build(); + createSystemReadyService(); + + int scale = mExternalVibratorService.onExternalVibrationStart( + new ExternalVibration(UID, PACKAGE_NAME, audioAttrs, + mock(IExternalVibrationController.class))); + assertEquals(IExternalVibratorService.SCALE_MUTE, scale); + + createSystemReadyService(); + scale = mExternalVibratorService.onExternalVibrationStart( + new ExternalVibration(UID, PACKAGE_NAME, flaggedAudioAttrs, + mock(IExternalVibrationController.class))); + assertNotEquals(IExternalVibratorService.SCALE_MUTE, scale); + } + private VibrationEffectSegment expectedPrebaked(int effectId) { return new PrebakedSegment(effectId, false, VibrationEffect.EFFECT_STRENGTH_MEDIUM); } diff --git a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java index f21991defbec..a12bc3b4c59f 100644 --- a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java @@ -16,14 +16,20 @@ package com.android.server; +import static android.Manifest.permission.MODIFY_DAY_NIGHT_MODE; import static android.app.UiModeManager.MODE_NIGHT_AUTO; import static android.app.UiModeManager.MODE_NIGHT_CUSTOM; +import static android.app.UiModeManager.MODE_NIGHT_CUSTOM_TYPE_BEDTIME; +import static android.app.UiModeManager.MODE_NIGHT_CUSTOM_TYPE_SCHEDULE; +import static android.app.UiModeManager.MODE_NIGHT_CUSTOM_TYPE_UNKNOWN; import static android.app.UiModeManager.MODE_NIGHT_NO; import static android.app.UiModeManager.MODE_NIGHT_YES; import static android.app.UiModeManager.PROJECTION_TYPE_ALL; import static android.app.UiModeManager.PROJECTION_TYPE_AUTOMOTIVE; import static android.app.UiModeManager.PROJECTION_TYPE_NONE; +import static com.google.common.truth.Truth.assertThat; + import static junit.framework.TestCase.assertFalse; import static junit.framework.TestCase.assertTrue; @@ -194,7 +200,7 @@ public class UiModeManagerServiceTest extends UiServiceTestCase { @Ignore // b/152719290 - Fails on stage-aosp-master @Test - public void setNightMoveActivated_overridesFunctionCorrectly() throws RemoteException { + public void setNightModeActivated_overridesFunctionCorrectly() throws RemoteException { // set up when(mPowerManager.isInteractive()).thenReturn(false); mService.setNightMode(MODE_NIGHT_NO); @@ -225,6 +231,29 @@ public class UiModeManagerServiceTest extends UiServiceTestCase { } @Test + public void setNightModeActivated_true_withCustomModeBedtime_shouldOverrideNightModeCorrectly() + throws RemoteException { + mService.setNightModeCustomType(MODE_NIGHT_CUSTOM_TYPE_BEDTIME); + assertFalse(mUiManagerService.getConfiguration().isNightModeActive()); + + mService.setNightModeActivated(true); + + assertThat(mUiManagerService.getConfiguration().isNightModeActive()).isTrue(); + } + + @Test + public void setNightModeActivated_false_withCustomModeBedtime_shouldOverrideNightModeCorrectly() + throws RemoteException { + mService.setNightModeCustomType(MODE_NIGHT_CUSTOM_TYPE_BEDTIME); + assertFalse(mUiManagerService.getConfiguration().isNightModeActive()); + + mService.setNightModeActivated(true); + mService.setNightModeActivated(false); + + assertThat(mUiManagerService.getConfiguration().isNightModeActive()).isFalse(); + } + + @Test public void setAutoMode_screenOffRegistered() throws RemoteException { try { mService.setNightMode(MODE_NIGHT_NO); @@ -247,7 +276,44 @@ public class UiModeManagerServiceTest extends UiServiceTestCase { } @Test - public void setNightModeActivated_fromNoToYesAndBAck() throws RemoteException { + public void setNightModeCustomType_bedtime_shouldNotActivateNightMode() throws RemoteException { + try { + mService.setNightMode(MODE_NIGHT_NO); + } catch (SecurityException e) { /* we should ignore this update config exception*/ } + mService.setNightModeCustomType(MODE_NIGHT_CUSTOM_TYPE_BEDTIME); + + assertThat(isNightModeActivated()).isFalse(); + } + + @Test + public void setNightModeCustomType_noPermission_shouldThrow() throws RemoteException { + when(mContext.checkCallingOrSelfPermission(eq(MODIFY_DAY_NIGHT_MODE))) + .thenReturn(PackageManager.PERMISSION_DENIED); + + assertThrows(SecurityException.class, + () -> mService.setNightModeCustomType(MODE_NIGHT_CUSTOM_TYPE_BEDTIME)); + } + + @Test + public void setNightModeCustomType_bedtime_shouldHaveNoScreenOffRegistered() + throws RemoteException { + try { + mService.setNightMode(MODE_NIGHT_NO); + } catch (SecurityException e) { /* we should ignore this update config exception*/ } + mService.setNightModeCustomType(MODE_NIGHT_CUSTOM_TYPE_BEDTIME); + ArgumentCaptor<IntentFilter> intentFiltersCaptor = ArgumentCaptor.forClass( + IntentFilter.class); + verify(mContext, atLeastOnce()).registerReceiver(any(BroadcastReceiver.class), + intentFiltersCaptor.capture()); + + List<IntentFilter> intentFilters = intentFiltersCaptor.getAllValues(); + for (IntentFilter intentFilter : intentFilters) { + assertThat(intentFilter.hasAction(Intent.ACTION_SCREEN_OFF)).isFalse(); + } + } + + @Test + public void setNightModeActivated_fromNoToYesAndBack() throws RemoteException { mService.setNightMode(MODE_NIGHT_NO); mService.setNightModeActivated(true); assertTrue(isNightModeActivated()); @@ -256,7 +322,7 @@ public class UiModeManagerServiceTest extends UiServiceTestCase { } @Test - public void setNightModeActivated_permissiontoChangeOtherUsers() throws RemoteException { + public void setNightModeActivated_permissionToChangeOtherUsers() throws RemoteException { SystemService.TargetUser user = mock(SystemService.TargetUser.class); doReturn(9).when(user).getUserIdentifier(); mUiManagerService.onUserSwitching(user, user); @@ -267,6 +333,89 @@ public class UiModeManagerServiceTest extends UiServiceTestCase { } @Test + public void setNightModeActivatedForCustomMode_customTypeBedtime_withParamOnAndBedtime_shouldActivate() + throws RemoteException { + mService.setNightModeCustomType(MODE_NIGHT_CUSTOM_TYPE_BEDTIME); + mService.setNightModeActivatedForCustomMode( + MODE_NIGHT_CUSTOM_TYPE_BEDTIME, true /* active */); + + assertThat(isNightModeActivated()).isTrue(); + } + + @Test + public void setNightModeActivatedForCustomMode_customTypeBedtime_withParamOffAndBedtime_shouldDeactivate() + throws RemoteException { + mService.setNightModeCustomType(MODE_NIGHT_CUSTOM_TYPE_BEDTIME); + mService.setNightModeActivatedForCustomMode( + MODE_NIGHT_CUSTOM_TYPE_BEDTIME, false /* active */); + + assertThat(isNightModeActivated()).isFalse(); + } + + @Test + public void setNightModeActivatedForCustomMode_customTypeBedtime_withParamOnAndSchedule_shouldNotActivate() + throws RemoteException { + mService.setNightModeCustomType(MODE_NIGHT_CUSTOM_TYPE_BEDTIME); + mService.setNightModeActivatedForCustomMode( + MODE_NIGHT_CUSTOM_TYPE_SCHEDULE, true /* active */); + + assertThat(isNightModeActivated()).isFalse(); + } + + @Test + public void setNightModeActivatedForCustomMode_customTypeSchedule_withParamOnAndBedtime_shouldNotActivate() + throws RemoteException { + mService.setNightMode(MODE_NIGHT_CUSTOM); + mService.setNightModeActivatedForCustomMode( + MODE_NIGHT_CUSTOM_TYPE_BEDTIME, true /* active */); + + assertThat(isNightModeActivated()).isFalse(); + } + + @Test + public void setNightModeActivatedForCustomMode_customTypeSchedule_withParamOnAndBedtime_thenCustomTypeBedtime_shouldActivate() + throws RemoteException { + mService.setNightMode(MODE_NIGHT_CUSTOM); + mService.setNightModeActivatedForCustomMode( + MODE_NIGHT_CUSTOM_TYPE_BEDTIME, true /* active */); + + mService.setNightModeCustomType(MODE_NIGHT_CUSTOM_TYPE_BEDTIME); + + assertThat(isNightModeActivated()).isTrue(); + } + + @Test + public void setNightModeActivatedForCustomMode_customTypeBedtime_withParamOnAndBedtime_thenCustomTypeSchedule_shouldKeepNightModeActivate() + throws RemoteException { + mService.setNightModeCustomType(MODE_NIGHT_CUSTOM_TYPE_BEDTIME); + mService.setNightModeActivatedForCustomMode( + MODE_NIGHT_CUSTOM_TYPE_BEDTIME, true /* active */); + + mService.setNightMode(MODE_NIGHT_CUSTOM); + LocalTime now = LocalTime.now(); + mService.setCustomNightModeStart(now.plusHours(1L).toNanoOfDay() / 1000); + mService.setCustomNightModeEnd(now.plusHours(2L).toNanoOfDay() / 1000); + + assertThat(isNightModeActivated()).isTrue(); + } + + @Test + public void setNightModeActivatedForCustomMode_customTypeBedtime_withParamOnAndBedtime_thenCustomTypeScheduleAndScreenOff_shouldDeactivateNightMode() + throws RemoteException { + mService.setNightModeCustomType(MODE_NIGHT_CUSTOM_TYPE_BEDTIME); + mService.setNightModeActivatedForCustomMode( + MODE_NIGHT_CUSTOM_TYPE_BEDTIME, true /* active */); + + mService.setNightMode(MODE_NIGHT_CUSTOM); + LocalTime now = LocalTime.now(); + mService.setCustomNightModeStart(now.plusHours(1L).toNanoOfDay() / 1000); + mService.setCustomNightModeEnd(now.plusHours(2L).toNanoOfDay() / 1000); + mScreenOffCallback.onReceive(mContext, new Intent(Intent.ACTION_SCREEN_OFF)); + + assertThat(isNightModeActivated()).isFalse(); + } + + @Test public void autoNightModeSwitch_batterySaverOn() throws RemoteException { mService.setNightMode(MODE_NIGHT_NO); when(mTwilightState.isNight()).thenReturn(false); @@ -283,6 +432,191 @@ public class UiModeManagerServiceTest extends UiServiceTestCase { } @Test + public void nightModeCustomBedtime_batterySaverOn_notInBedtime_shouldActivateNightMode() + throws RemoteException { + mService.setNightModeCustomType(MODE_NIGHT_CUSTOM_TYPE_BEDTIME); + + mPowerSaveConsumer.accept( + new PowerSaveState.Builder().setBatterySaverEnabled(true).build()); + + assertThat(isNightModeActivated()).isTrue(); + } + + @Test + public void nightModeCustomBedtime_batterySaverOn_afterBedtime_shouldKeepNightModeActivated() + throws RemoteException { + mService.setNightModeCustomType(MODE_NIGHT_CUSTOM_TYPE_BEDTIME); + mPowerSaveConsumer.accept( + new PowerSaveState.Builder().setBatterySaverEnabled(true).build()); + + mService.setNightModeActivatedForCustomMode( + MODE_NIGHT_CUSTOM_TYPE_BEDTIME, false /* active */); + + assertThat(isNightModeActivated()).isTrue(); + } + + @Test + public void nightModeBedtime_duringBedtime_batterySaverOnThenOff_shouldKeepNightModeActivated() + throws RemoteException { + mService.setNightModeCustomType(MODE_NIGHT_CUSTOM_TYPE_BEDTIME); + mService.setNightModeActivatedForCustomMode( + MODE_NIGHT_CUSTOM_TYPE_BEDTIME, true /* active */); + + mPowerSaveConsumer.accept( + new PowerSaveState.Builder().setBatterySaverEnabled(true).build()); + mPowerSaveConsumer.accept( + new PowerSaveState.Builder().setBatterySaverEnabled(false).build()); + + assertThat(isNightModeActivated()).isTrue(); + } + + @Test + public void nightModeCustomBedtime_duringBedtime_batterySaverOnThenOff_finallyAfterBedtime_shouldDeactivateNightMode() + throws RemoteException { + mService.setNightModeCustomType(MODE_NIGHT_CUSTOM_TYPE_BEDTIME); + mService.setNightModeActivatedForCustomMode( + MODE_NIGHT_CUSTOM_TYPE_BEDTIME, true /* active */); + mPowerSaveConsumer.accept( + new PowerSaveState.Builder().setBatterySaverEnabled(true).build()); + mPowerSaveConsumer.accept( + new PowerSaveState.Builder().setBatterySaverEnabled(false).build()); + + mService.setNightModeActivatedForCustomMode( + MODE_NIGHT_CUSTOM_TYPE_BEDTIME, false /* active */); + + assertThat(isNightModeActivated()).isFalse(); + } + + @Test + public void nightModeCustomBedtime_duringBedtime_changeModeToNo_shouldDeactivateNightMode() + throws RemoteException { + mService.setNightModeCustomType(MODE_NIGHT_CUSTOM_TYPE_BEDTIME); + mService.setNightModeActivatedForCustomMode( + MODE_NIGHT_CUSTOM_TYPE_BEDTIME, true /* active */); + + mService.setNightMode(MODE_NIGHT_NO); + + assertThat(isNightModeActivated()).isFalse(); + } + + @Test + public void nightModeCustomBedtime_duringBedtime_changeModeToNoAndThenExitBedtime_shouldKeepNightModeDeactivated() + throws RemoteException { + mService.setNightModeCustomType(MODE_NIGHT_CUSTOM_TYPE_BEDTIME); + mService.setNightModeActivatedForCustomMode( + MODE_NIGHT_CUSTOM_TYPE_BEDTIME, true /* active */); + mService.setNightMode(MODE_NIGHT_NO); + + mService.setNightModeActivatedForCustomMode( + MODE_NIGHT_CUSTOM_TYPE_BEDTIME, false /* active */); + + assertThat(isNightModeActivated()).isFalse(); + } + + @Test + public void nightModeCustomBedtime_duringBedtime_changeModeToYes_shouldKeepNightModeActivated() + throws RemoteException { + mService.setNightModeCustomType(MODE_NIGHT_CUSTOM_TYPE_BEDTIME); + mService.setNightModeActivatedForCustomMode( + MODE_NIGHT_CUSTOM_TYPE_BEDTIME, true /* active */); + + mService.setNightMode(MODE_NIGHT_YES); + + assertThat(isNightModeActivated()).isTrue(); + } + + @Test + public void nightModeCustomBedtime_duringBedtime_changeModeToYesAndThenExitBedtime_shouldKeepNightModeActivated() + throws RemoteException { + mService.setNightModeCustomType(MODE_NIGHT_CUSTOM_TYPE_BEDTIME); + mService.setNightModeActivatedForCustomMode( + MODE_NIGHT_CUSTOM_TYPE_BEDTIME, true /* active */); + + mService.setNightMode(MODE_NIGHT_YES); + mService.setNightModeActivatedForCustomMode( + MODE_NIGHT_CUSTOM_TYPE_BEDTIME, false /* active */); + + assertThat(isNightModeActivated()).isTrue(); + } + + @Test + public void nightModeNo_duringBedtime_shouldKeepNightModeDeactivated() + throws RemoteException { + mService.setNightMode(MODE_NIGHT_NO); + + mService.setNightModeActivatedForCustomMode( + MODE_NIGHT_CUSTOM_TYPE_BEDTIME, true /* active */); + + assertThat(isNightModeActivated()).isFalse(); + } + + @Test + public void nightModeNo_thenChangeToCustomTypeBedtimeAndActivate_shouldActivateNightMode() + throws RemoteException { + mService.setNightMode(MODE_NIGHT_NO); + + mService.setNightModeCustomType(MODE_NIGHT_CUSTOM_TYPE_BEDTIME); + mService.setNightModeActivatedForCustomMode( + MODE_NIGHT_CUSTOM_TYPE_BEDTIME, true /* active */); + + assertThat(isNightModeActivated()).isTrue(); + } + + @Test + public void nightModeYes_thenChangeToCustomTypeBedtime_shouldDeactivateNightMode() + throws RemoteException { + mService.setNightMode(MODE_NIGHT_YES); + + mService.setNightModeCustomType(MODE_NIGHT_CUSTOM_TYPE_BEDTIME); + + assertThat(isNightModeActivated()).isFalse(); + } + + @Test + public void nightModeYes_thenChangeToCustomTypeBedtimeAndActivate_shouldActivateNightMode() + throws RemoteException { + mService.setNightMode(MODE_NIGHT_YES); + + mService.setNightModeCustomType(MODE_NIGHT_CUSTOM_TYPE_BEDTIME); + mService.setNightModeActivatedForCustomMode( + MODE_NIGHT_CUSTOM_TYPE_BEDTIME, true /* active */); + + assertThat(isNightModeActivated()).isTrue(); + } + + @Test + public void nightModeAuto_thenChangeToCustomTypeBedtime_notInBedtime_shouldDeactivateNightMode() + throws RemoteException { + // set mode to auto + mService.setNightMode(MODE_NIGHT_AUTO); + mService.setNightModeActivated(true); + // now it is night time + doReturn(true).when(mTwilightState).isNight(); + mTwilightListener.onTwilightStateChanged(mTwilightState); + + mService.setNightModeCustomType(MODE_NIGHT_CUSTOM_TYPE_BEDTIME); + + assertThat(isNightModeActivated()).isFalse(); + } + + @Test + public void nightModeAuto_thenChangeToCustomTypeBedtime_duringBedtime_shouldActivateNightMode() + throws RemoteException { + // set mode to auto + mService.setNightMode(MODE_NIGHT_AUTO); + mService.setNightModeActivated(true); + // now it is night time + doReturn(true).when(mTwilightState).isNight(); + mTwilightListener.onTwilightStateChanged(mTwilightState); + + mService.setNightModeCustomType(MODE_NIGHT_CUSTOM_TYPE_BEDTIME); + mService.setNightModeActivatedForCustomMode( + MODE_NIGHT_CUSTOM_TYPE_BEDTIME, true /* active */); + + assertThat(isNightModeActivated()).isTrue(); + } + + @Test public void setAutoMode_clearCache() throws RemoteException { try { mService.setNightMode(MODE_NIGHT_AUTO); @@ -327,6 +661,62 @@ public class UiModeManagerServiceTest extends UiServiceTestCase { } @Test + public void getNightModeCustomType_nightModeNo_shouldReturnUnknown() throws RemoteException { + try { + mService.setNightMode(MODE_NIGHT_NO); + } catch (SecurityException e) { /* we should ignore this update config exception*/ } + + assertThat(mService.getNightModeCustomType()).isEqualTo(MODE_NIGHT_CUSTOM_TYPE_UNKNOWN); + } + + @Test + public void getNightModeCustomType_nightModeYes_shouldReturnUnknown() throws RemoteException { + try { + mService.setNightMode(MODE_NIGHT_YES); + } catch (SecurityException e) { /* we should ignore this update config exception*/ } + + assertThat(mService.getNightModeCustomType()).isEqualTo(MODE_NIGHT_CUSTOM_TYPE_UNKNOWN); + } + + @Test + public void getNightModeCustomType_nightModeAuto_shouldReturnUnknown() throws RemoteException { + try { + mService.setNightMode(MODE_NIGHT_AUTO); + } catch (SecurityException e) { /* we should ignore this update config exception*/ } + + assertThat(mService.getNightModeCustomType()).isEqualTo(MODE_NIGHT_CUSTOM_TYPE_UNKNOWN); + } + + @Test + public void getNightModeCustomType_nightModeCustom_shouldReturnSchedule() + throws RemoteException { + try { + mService.setNightMode(MODE_NIGHT_CUSTOM); + } catch (SecurityException e) { /* we should ignore this update config exception*/ } + + assertThat(mService.getNightModeCustomType()).isEqualTo(MODE_NIGHT_CUSTOM_TYPE_SCHEDULE); + } + + @Test + public void getNightModeCustomType_nightModeCustomBedtime_shouldReturnBedtime() + throws RemoteException { + try { + mService.setNightModeCustomType(MODE_NIGHT_CUSTOM_TYPE_BEDTIME); + } catch (SecurityException e) { /* we should ignore this update config exception*/ } + + assertThat(mService.getNightModeCustomType()).isEqualTo(MODE_NIGHT_CUSTOM_TYPE_BEDTIME); + } + + @Test + public void getNightModeCustomType_permissionNotGranted_shouldThrow() + throws RemoteException { + when(mContext.checkCallingOrSelfPermission(eq(MODIFY_DAY_NIGHT_MODE))) + .thenReturn(PackageManager.PERMISSION_DENIED); + + assertThrows(SecurityException.class, () -> mService.getNightModeCustomType()); + } + + @Test public void isNightModeActive_nightModeYes() throws RemoteException { try { mService.setNightMode(MODE_NIGHT_YES); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java index a834e2b6cc5a..12cd834d1d66 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java @@ -394,7 +394,7 @@ public class NotificationListenerServiceTest extends UiServiceTestCase { "disabledMessage", 0, "disabledMessageResName", null, null, 0, null, 0, 0, 0, "iconResName", "bitmapPath", null, 0, - null, null, null); + null, null, null, null); return si; } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index a192bf87cce4..9f92294135c0 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -483,7 +483,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mAppUsageStats, mock(DevicePolicyManagerInternal.class), mUgm, mUgmInternal, mAppOpsManager, mAppOpsService, mUm, mHistoryManager, mStatsManager, mock(TelephonyManager.class), - mAmi, mToastRateLimiter, mPermissionHelper); + mAmi, mToastRateLimiter, mPermissionHelper, mock(UsageStatsManagerInternal.class)); // Return first true for RoleObserver main-thread check when(mMainLooper.isCurrentThread()).thenReturn(true).thenReturn(false); mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY, mMainLooper); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java index f6400b65bc4d..da5496da1bfa 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java @@ -371,7 +371,8 @@ public class NotificationPermissionMigrationTest extends UiServiceTestCase { mSnoozeHelper, mUsageStats, mPolicyFile, mActivityManager, mGroupHelper, mAm, mAtm, mAppUsageStats, mock(DevicePolicyManagerInternal.class), mUgm, mUgmInternal, mAppOpsManager, mock(IAppOpsService.class), mUm, mHistoryManager, mStatsManager, - mock(TelephonyManager.class), mAmi, mToastRateLimiter, mPermissionHelper); + mock(TelephonyManager.class), mAmi, mToastRateLimiter, mPermissionHelper, + mock(UsageStatsManagerInternal.class)); // Return first true for RoleObserver main-thread check when(mMainLooper.isCurrentThread()).thenReturn(true).thenReturn(false); mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY, mMainLooper); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java index b48c9c3d4bcc..736fbd9d50ef 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java @@ -46,6 +46,7 @@ import static com.android.os.AtomsProto.PackageNotificationChannelPreferences.IS import static com.android.os.AtomsProto.PackageNotificationChannelPreferences.UID_FIELD_NUMBER; import static com.android.server.notification.PreferencesHelper.DEFAULT_BUBBLE_PREFERENCE; import static com.android.server.notification.PreferencesHelper.NOTIFICATION_CHANNEL_COUNT_LIMIT; +import static com.android.server.notification.PreferencesHelper.NOTIFICATION_CHANNEL_GROUP_COUNT_LIMIT; import static com.android.server.notification.PreferencesHelper.UNKNOWN_UID; import static com.google.common.truth.Truth.assertThat; @@ -4250,6 +4251,52 @@ public class PreferencesHelperTest extends UiServiceTestCase { } @Test + public void testTooManyGroups() { + for (int i = 0; i < NOTIFICATION_CHANNEL_GROUP_COUNT_LIMIT; i++) { + NotificationChannelGroup group = new NotificationChannelGroup(String.valueOf(i), + String.valueOf(i)); + mHelper.createNotificationChannelGroup(PKG_O, UID_O, group, true); + } + try { + NotificationChannelGroup group = new NotificationChannelGroup( + String.valueOf(NOTIFICATION_CHANNEL_GROUP_COUNT_LIMIT), + String.valueOf(NOTIFICATION_CHANNEL_GROUP_COUNT_LIMIT)); + mHelper.createNotificationChannelGroup(PKG_O, UID_O, group, true); + fail("Allowed to create too many notification channel groups"); + } catch (IllegalStateException e) { + // great + } + } + + @Test + public void testTooManyGroups_xml() throws Exception { + String extraGroup = "EXTRA"; + String extraGroup1 = "EXTRA1"; + + // create first... many... directly so we don't need a big xml blob in this test + for (int i = 0; i < NOTIFICATION_CHANNEL_GROUP_COUNT_LIMIT; i++) { + NotificationChannelGroup group = new NotificationChannelGroup(String.valueOf(i), + String.valueOf(i)); + mHelper.createNotificationChannelGroup(PKG_O, UID_O, group, true); + } + + final String xml = "<ranking version=\"1\">\n" + + "<package name=\"" + PKG_O + "\" uid=\"" + UID_O + "\" >\n" + + "<channelGroup id=\"" + extraGroup + "\" name=\"hi\"/>" + + "<channelGroup id=\"" + extraGroup1 + "\" name=\"hi2\"/>" + + "</package>" + + "</ranking>"; + TypedXmlPullParser parser = Xml.newFastPullParser(); + parser.setInput(new BufferedInputStream(new ByteArrayInputStream(xml.getBytes())), + null); + parser.nextTag(); + mHelper.readXml(parser, false, UserHandle.USER_ALL); + + assertNull(mHelper.getNotificationChannelGroup(extraGroup, PKG_O, UID_O)); + assertNull(mHelper.getNotificationChannelGroup(extraGroup1, PKG_O, UID_O)); + } + + @Test public void testRestoreMultiUser() throws Exception { String pkg = "restore_pkg"; String channelId = "channelId"; diff --git a/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java b/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java index 59d9a35c4471..1d25b54207c4 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java @@ -166,7 +166,8 @@ public class RoleObserverTest extends UiServiceTestCase { mUm, mock(NotificationHistoryManager.class), mock(StatsManager.class), mock(TelephonyManager.class), mock(ActivityManagerInternal.class), - mock(MultiRateLimiter.class), mock(PermissionHelper.class)); + mock(MultiRateLimiter.class), mock(PermissionHelper.class), + mock(UsageStatsManagerInternal.class)); } catch (SecurityException e) { if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) { throw e; diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java index 0dcf7992e02b..774e5b9c7fe3 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -24,7 +24,6 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; -import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION; import static android.content.pm.ActivityInfo.CONFIG_SCREEN_LAYOUT; import static android.content.pm.ActivityInfo.FLAG_SUPPORTS_PICTURE_IN_PICTURE; @@ -571,7 +570,7 @@ public class ActivityRecordTests extends WindowTestsBase { final ActivityRecord activity = createActivityWith2LevelTask(); final Task task = activity.getTask(); final Task rootTask = activity.getRootTask(); - rootTask.setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); + rootTask.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); final Rect stableRect = new Rect(); rootTask.mDisplayContent.getStableRect(stableRect); @@ -616,7 +615,7 @@ public class ActivityRecordTests extends WindowTestsBase { spyOn(tda); doReturn(true).when(tda).supportsNonResizableMultiWindow(); final Task rootTask = mDisplayContent.getDefaultTaskDisplayArea().createRootTask( - WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, true /* onTop */); + WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD, true /* onTop */); rootTask.setBounds(0, 0, 1000, 500); final ActivityRecord activity = new ActivityBuilder(mAtm) .setParentTask(rootTask) diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java index a2b04c295944..7c340ecac2c7 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java @@ -77,6 +77,7 @@ import org.mockito.Mockito; import org.mockito.MockitoSession; import java.util.ArrayList; +import java.util.List; import java.util.function.Consumer; /** @@ -188,6 +189,9 @@ public class ActivityTaskManagerServiceTests extends WindowTestsBase { @Override public void onFixedRotationFinished(int displayId) {} + + @Override + public void onKeepClearAreasChanged(int displayId, List<Rect> keepClearAreas) {} }; int[] displayIds = mAtm.mWindowManager.registerDisplayWindowListener(listener); for (int i = 0; i < displayIds.length; i++) { diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java new file mode 100644 index 000000000000..687779d686d1 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm; + +import static android.window.BackNavigationInfo.typeToString; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.annotation.NonNull; +import android.hardware.HardwareBuffer; +import android.platform.test.annotations.Presubmit; +import android.window.BackNavigationInfo; +import android.window.TaskSnapshot; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@Presubmit +@RunWith(WindowTestRunner.class) +public class BackNavigationControllerTests extends WindowTestsBase { + + private BackNavigationController mBackNavigationController; + + @Before + public void setUp() throws Exception { + mBackNavigationController = new BackNavigationController(); + } + + @Test + public void backTypeHomeWhenBackToLauncher() { + Task task = createTopTaskWithActivity(); + BackNavigationInfo backNavigationInfo = + mBackNavigationController.startBackNavigation(task, new StubTransaction()); + assertThat(backNavigationInfo).isNotNull(); + assertThat(typeToString(backNavigationInfo.getType())) + .isEqualTo(typeToString(BackNavigationInfo.TYPE_RETURN_TO_HOME)); + } + + @Test + public void backTypeCrossTaskWhenBackToPreviousTask() { + Task taskA = createTask(mDefaultDisplay); + createActivityRecord(taskA); + Task task = createTopTaskWithActivity(); + BackNavigationInfo backNavigationInfo = + mBackNavigationController.startBackNavigation(task, new StubTransaction()); + assertThat(backNavigationInfo).isNotNull(); + assertThat(typeToString(backNavigationInfo.getType())) + .isEqualTo(typeToString(BackNavigationInfo.TYPE_CROSS_TASK)); + } + + @Test + public void backTypeCrossActivityWhenBackToPreviousActivity() { + Task task = createTopTaskWithActivity(); + mAtm.setFocusedTask(task.mTaskId, createActivityRecord(task)); + BackNavigationInfo backNavigationInfo = + mBackNavigationController.startBackNavigation(task, new StubTransaction()); + assertThat(backNavigationInfo).isNotNull(); + assertThat(typeToString(backNavigationInfo.getType())) + .isEqualTo(typeToString(BackNavigationInfo.TYPE_CROSS_ACTIVITY)); + } + + /** + * Checks that we are able to fill all the field of the {@link BackNavigationInfo} object. + */ + @Test + public void backNavInfoFullyPopulated() { + Task task = createTopTaskWithActivity(); + createActivityRecord(task); + + // We need a mock screenshot so + TaskSnapshotController taskSnapshotController = createMockTaskSnapshotController(); + + mBackNavigationController.setTaskSnapshotController(taskSnapshotController); + + BackNavigationInfo backNavigationInfo = + mBackNavigationController.startBackNavigation(task, new StubTransaction()); + assertThat(backNavigationInfo).isNotNull(); + assertThat(backNavigationInfo.getDepartingWindowContainer()).isNotNull(); + assertThat(backNavigationInfo.getScreenshotSurface()).isNotNull(); + assertThat(backNavigationInfo.getScreenshotHardwareBuffer()).isNotNull(); + assertThat(backNavigationInfo.getTaskWindowConfiguration()).isNotNull(); + } + + @NonNull + private TaskSnapshotController createMockTaskSnapshotController() { + TaskSnapshotController taskSnapshotController = mock(TaskSnapshotController.class); + TaskSnapshot taskSnapshot = mock(TaskSnapshot.class); + when(taskSnapshot.getHardwareBuffer()).thenReturn(mock(HardwareBuffer.class)); + when(taskSnapshotController.getSnapshot(anyInt(), anyInt(), anyBoolean(), anyBoolean())) + .thenReturn(taskSnapshot); + return taskSnapshotController; + } + + @NonNull + private Task createTopTaskWithActivity() { + Task task = createTask(mDefaultDisplay); + ActivityRecord record = createActivityRecord(task); + when(record.mSurfaceControl.isValid()).thenReturn(true); + mAtm.setFocusedTask(task.mTaskId, record); + return task; + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java index 2f78b588f305..8d58ec00df96 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -119,6 +119,7 @@ import android.os.IBinder; import android.os.RemoteException; import android.os.SystemClock; import android.platform.test.annotations.Presubmit; +import android.util.ArraySet; import android.util.DisplayMetrics; import android.view.DisplayCutout; import android.view.DisplayInfo; @@ -1101,7 +1102,7 @@ public class DisplayContentTests extends WindowTestsBase { final DisplayContent dc = createNewDisplay(); dc.setImeLayeringTarget(createWindow(null, TYPE_STATUS_BAR, "app")); dc.getImeTarget(IME_TARGET_LAYERING).getWindow().setWindowingMode( - WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); + WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW); assertEquals(dc.getImeContainer().getParentSurfaceControl(), dc.computeImeParent()); } @@ -1152,7 +1153,7 @@ public class DisplayContentTests extends WindowTestsBase { final DisplayContent dc = createNewDisplay(); dc.setImeInputTarget(createWindow(null, TYPE_BASE_APPLICATION, "app")); dc.getImeTarget(IME_TARGET_INPUT).getWindow().setWindowingMode( - WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); + WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW); dc.setImeLayeringTarget(dc.getImeTarget(IME_TARGET_INPUT).getWindow()); dc.setRemoteInsetsController(createDisplayWindowInsetsController()); assertNotEquals(dc.getImeTarget(IME_TARGET_INPUT).getWindow(), @@ -1982,6 +1983,7 @@ public class DisplayContentTests extends WindowTestsBase { // Test step 1: appWin1 is the current IME target and soft-keyboard is visible. mDisplayContent.computeImeTarget(true); assertEquals(appWin1, mDisplayContent.getImeTarget(IME_TARGET_LAYERING)); + mDisplayContent.setImeInputTarget(appWin1); spyOn(mDisplayContent.mInputMethodWindow); doReturn(true).when(mDisplayContent.mInputMethodWindow).isVisible(); mDisplayContent.getInsetsStateController().getImeSourceProvider().setImeShowing(true); @@ -1998,7 +2000,6 @@ public class DisplayContentTests extends WindowTestsBase { // be shown at this time. final Transaction t = mDisplayContent.getPendingTransaction(); spyOn(t); - mDisplayContent.setImeInputTarget(appWin2); mDisplayContent.computeImeTarget(true); assertEquals(appWin2, mDisplayContent.getImeTarget(IME_TARGET_LAYERING)); assertTrue(mDisplayContent.shouldImeAttachedToApp()); @@ -2436,6 +2437,31 @@ public class DisplayContentTests extends WindowTestsBase { mockSession.finishMocking(); } + @Test + public void testKeepClearAreasMultipleWindows() { + final WindowState w1 = createWindow(null, TYPE_NAVIGATION_BAR, mDisplayContent, "w1"); + final Rect rect1 = new Rect(0, 0, 10, 10); + w1.setKeepClearAreas(Arrays.asList(rect1)); + final WindowState w2 = createWindow(null, TYPE_NOTIFICATION_SHADE, mDisplayContent, "w2"); + final Rect rect2 = new Rect(10, 10, 20, 20); + w2.setKeepClearAreas(Arrays.asList(rect2)); + + // No keep clear areas on display, because the windows are not visible + assertEquals(Arrays.asList(), mDisplayContent.getKeepClearAreas()); + + makeWindowVisible(w1); + + // The returned keep-clear areas contain the areas just from the visible window + assertEquals(new ArraySet(Arrays.asList(rect1)), + new ArraySet(mDisplayContent.getKeepClearAreas())); + + makeWindowVisible(w1, w2); + + // The returned keep-clear areas contain the areas from all visible windows + assertEquals(new ArraySet(Arrays.asList(rect1, rect2)), + new ArraySet(mDisplayContent.getKeepClearAreas())); + } + private class TestToken extends Binder { } diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java index acf6dc58a597..497ae1defb61 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java @@ -31,10 +31,8 @@ import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; -import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.spy; @@ -44,7 +42,6 @@ import android.graphics.Insets; import android.graphics.Rect; import android.platform.test.annotations.Presubmit; import android.util.Pair; -import android.view.DisplayCutout; import android.view.DisplayInfo; import android.view.InsetsState; import android.view.PrivacyIndicatorBounds; @@ -210,24 +207,6 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { expectThrows(IllegalArgumentException.class, () -> addWindow(win2)); } - @Test - public void layoutHint_appWindow() { - mWindow.mAttrs.setFitInsetsTypes(0); - - final DisplayCutout.ParcelableWrapper outDisplayCutout = - new DisplayCutout.ParcelableWrapper(); - final InsetsState outState = new InsetsState(); - - mDisplayPolicy.getLayoutHint(mWindow.mAttrs, null /* windowToken */, outState, - true /* localClient */); - - assertThat(outDisplayCutout, is(new DisplayCutout.ParcelableWrapper())); - assertThat(outState.getSource(ITYPE_STATUS_BAR).getFrame(), - is(new Rect(0, 0, DISPLAY_WIDTH, STATUS_BAR_HEIGHT))); - assertThat(outState.getSource(ITYPE_NAVIGATION_BAR).getFrame(), - is(new Rect(0, DISPLAY_HEIGHT - NAV_BAR_HEIGHT, DISPLAY_WIDTH, DISPLAY_HEIGHT))); - } - /** * Verify that {@link DisplayPolicy#simulateLayoutDisplay} outputs the same display frames as * the real one. diff --git a/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java index 407f9cfdbe3e..d64bf121736f 100644 --- a/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java @@ -26,11 +26,14 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertNotNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.verify; import android.app.ActivityThread; import android.content.Context; @@ -43,6 +46,7 @@ import android.view.Display; import android.view.IWindowManager; import android.view.WindowManager; import android.view.WindowManagerGlobal; +import android.window.WindowTokenClient; import com.android.server.inputmethod.InputMethodManagerService; import com.android.server.inputmethod.InputMethodMenuController; @@ -130,15 +134,31 @@ public class InputMethodMenuControllerTest extends WindowTestsBase { @Test public void testGetSettingsContextOnDualDisplayContent() { final Context context = mController.getSettingsContext(mSecondaryDisplay.getDisplayId()); + final WindowTokenClient tokenClient = (WindowTokenClient) context.getWindowContextToken(); + assertNotNull(tokenClient); + spyOn(tokenClient); final DisplayArea.Tokens imeContainer = mSecondaryDisplay.getImeContainer(); + spyOn(imeContainer); assertThat(imeContainer.getRootDisplayArea()).isEqualTo(mSecondaryDisplay); mSecondaryDisplay.mFirstRoot.placeImeContainer(imeContainer); + + verify(imeContainer, atLeastOnce()).onConfigurationChanged( + eq(mSecondaryDisplay.mFirstRoot.getConfiguration())); + verify(tokenClient, atLeastOnce()).onConfigurationChanged( + eq(mSecondaryDisplay.mFirstRoot.getConfiguration()), + eq(mSecondaryDisplay.mDisplayId)); assertThat(imeContainer.getRootDisplayArea()).isEqualTo(mSecondaryDisplay.mFirstRoot); assertImeSwitchContextMetricsValidity(context, mSecondaryDisplay); mSecondaryDisplay.mSecondRoot.placeImeContainer(imeContainer); + + verify(imeContainer, atLeastOnce()).onConfigurationChanged( + eq(mSecondaryDisplay.mSecondRoot.getConfiguration())); + verify(tokenClient, atLeastOnce()).onConfigurationChanged( + eq(mSecondaryDisplay.mSecondRoot.getConfiguration()), + eq(mSecondaryDisplay.mDisplayId)); assertThat(imeContainer.getRootDisplayArea()).isEqualTo(mSecondaryDisplay.mSecondRoot); assertImeSwitchContextMetricsValidity(context, mSecondaryDisplay); } diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java index 94bc7f2dc0d1..2eece4c2ca4d 100644 --- a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java @@ -19,7 +19,6 @@ package com.android.server.wm; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; -import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; import static android.view.InsetsState.ITYPE_CLIMATE_BAR; import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR; import static android.view.InsetsState.ITYPE_IME; @@ -246,7 +245,7 @@ public class InsetsStateControllerTest extends WindowTestsBase { final WindowState child = createWindow(app, TYPE_APPLICATION, "child"); app.mAboveInsetsState.addSource(getController().getRawInsetsState().peekSource(ITYPE_IME)); child.mAttrs.flags |= FLAG_NOT_FOCUSABLE; - child.setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); + child.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); mDisplayContent.computeImeTarget(true); mDisplayContent.setLayoutNeeded(); diff --git a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsControllerTests.java index fc298b0d96a1..0c2de5c6031b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsControllerTests.java @@ -20,7 +20,6 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; -import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; import static android.view.Display.INVALID_DISPLAY; import static com.android.dx.mockito.inline.extended.ExtendedMockito.any; @@ -334,7 +333,7 @@ public class LaunchParamsControllerTests extends WindowTestsBase { params.mWindowingMode = windowingMode; final InstrumentedPositioner positioner = new InstrumentedPositioner(RESULT_DONE, params); final Task task = new TaskBuilder(mAtm.mTaskSupervisor).setCreateParentTask(true).build(); - task.getRootTask().setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY); + task.getRootTask().setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); mController.registerModifier(positioner); diff --git a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java index 3cb0bed32493..65b5cf5f13c4 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java @@ -88,6 +88,7 @@ import android.platform.test.annotations.Presubmit; import androidx.test.filters.SmallTest; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; @@ -529,6 +530,7 @@ public class RootTaskTests extends WindowTestsBase { // TODO(b/199236198): check this is unnecessary or need to migrate after remove legacy split. @Test + @Ignore public void testShouldBeVisible_SplitScreen() { // task not supporting split should be fullscreen for this test. final Task notSupportingSplitTask = createTaskForShouldBeVisibleTest( diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java index 4069f0f41d90..f4abf883c219 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java @@ -21,8 +21,8 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; -import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; import static android.content.pm.ActivityInfo.FLAG_ALWAYS_FOCUSABLE; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.TYPE_VIRTUAL; @@ -458,7 +458,7 @@ public class RootWindowContainerTests extends WindowTestsBase { final TaskDisplayArea defaultTaskDisplayArea = mRootWindowContainer .getDefaultTaskDisplayArea(); final Task task = defaultTaskDisplayArea.createRootTask( - WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, true /* onTop */); + WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD, true /* onTop */); final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(task).build(); // Created tasks are focusable by default. diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java index c722b0a70c0a..b815c38b7a8a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -77,6 +77,7 @@ import android.content.pm.ActivityInfo; import android.content.pm.ActivityInfo.ScreenOrientation; import android.content.res.Configuration; import android.graphics.Rect; +import android.os.UserHandle; import android.platform.test.annotations.Presubmit; import android.provider.DeviceConfig; import android.provider.DeviceConfig.Properties; @@ -2182,6 +2183,29 @@ public class SizeCompatTests extends WindowTestsBase { .computeAspectRatio(sizeCompatAppBounds), delta); } + @Test + public void testClearSizeCompat_resetOverrideConfig() { + final int origDensity = 480; + final int newDensity = 520; + final DisplayContent display = new TestDisplayContent.Builder(mAtm, 600, 800) + .setDensityDpi(origDensity) + .build(); + setUpApp(display); + prepareUnresizable(mActivity, -1.f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT); + + // Activity should enter size compat with old density after display density change. + display.setForcedDensity(newDensity, UserHandle.USER_CURRENT); + + assertScaled(); + assertEquals(origDensity, mActivity.getConfiguration().densityDpi); + + // Activity should exit size compat with new density. + mActivity.clearSizeCompatMode(); + + assertFitted(); + assertEquals(newDensity, mActivity.getConfiguration().densityDpi); + } + private void assertHorizontalPositionForDifferentDisplayConfigsForLandscapeActivity( float letterboxHorizontalPositionMultiplier) { // Set up a display in landscape and ignoring orientation request. diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java index cdf6b59d4737..80f6bceb884c 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java @@ -25,8 +25,6 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; -import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; -import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.content.Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT; import static android.content.pm.ActivityInfo.FLAG_ALWAYS_FOCUSABLE; @@ -355,14 +353,10 @@ public class TaskDisplayAreaTests extends WindowTestsBase { true /* reuseCandidate */); assertGetOrCreateRootTask(WINDOWING_MODE_UNDEFINED, type, candidateTask, true /* reuseCandidate */); - assertGetOrCreateRootTask(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, type, candidateTask, - true /* reuseCandidate */); assertGetOrCreateRootTask(WINDOWING_MODE_FREEFORM, type, candidateTask, true /* reuseCandidate */); assertGetOrCreateRootTask(WINDOWING_MODE_MULTI_WINDOW, type, candidateTask, true /* reuseCandidate */); - assertGetOrCreateRootTask(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, type, candidateTask, - false /* reuseCandidate */); assertGetOrCreateRootTask(WINDOWING_MODE_PINNED, type, candidateTask, true /* reuseCandidate */); @@ -388,7 +382,7 @@ public class TaskDisplayAreaTests extends WindowTestsBase { final Task primarySplitTask = new TaskBuilder(mSupervisor) .setTaskDisplayArea(defaultTaskDisplayArea) - .setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) + .setWindowingMode(WINDOWING_MODE_MULTI_WINDOW) .setActivityType(ACTIVITY_TYPE_STANDARD) .setOnTop(true) .setCreateActivity(true) diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTraversalTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTraversalTests.java index f13847559aa0..64959f240887 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTraversalTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTraversalTests.java @@ -17,8 +17,7 @@ package com.android.server.wm; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; -import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; -import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; +import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; @@ -50,11 +49,8 @@ public class WindowContainerTraversalTests extends WindowTestsBase { @Test public void testDockedDividerPosition() { final WindowState splitScreenWindow = createWindow(null, - WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, TYPE_BASE_APPLICATION, + WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD, TYPE_BASE_APPLICATION, mDisplayContent, "splitScreenWindow"); - final WindowState splitScreenSecondaryWindow = createWindow(null, - WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD, - TYPE_BASE_APPLICATION, mDisplayContent, "splitScreenSecondaryWindow"); mDisplayContent.setImeLayeringTarget(splitScreenWindow); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java index 75a87ba9e04d..4d5fb6dc5813 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java @@ -24,8 +24,6 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; -import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; -import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET; @@ -520,16 +518,16 @@ public class WindowOrganizerTests extends WindowTestsBase { DisplayContent dc = mWm.mRoot.getDisplayContent(Display.DEFAULT_DISPLAY); Task task1 = mWm.mAtmService.mTaskOrganizerController.createRootTask( - dc, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, null); + dc, WINDOWING_MODE_FULLSCREEN, null); RunningTaskInfo info1 = task1.getTaskInfo(); - assertEquals(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, + assertEquals(WINDOWING_MODE_FULLSCREEN, info1.configuration.windowConfiguration.getWindowingMode()); assertEquals(ACTIVITY_TYPE_UNDEFINED, info1.topActivityType); Task task2 = mWm.mAtmService.mTaskOrganizerController.createRootTask( - dc, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, null); + dc, WINDOWING_MODE_MULTI_WINDOW, null); RunningTaskInfo info2 = task2.getTaskInfo(); - assertEquals(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, + assertEquals(WINDOWING_MODE_MULTI_WINDOW, info2.configuration.windowConfiguration.getWindowingMode()); assertEquals(ACTIVITY_TYPE_UNDEFINED, info2.topActivityType); @@ -539,7 +537,7 @@ public class WindowOrganizerTests extends WindowTestsBase { assertTrue(mWm.mAtmService.mTaskOrganizerController.deleteRootTask(info1.token)); infos = getTasksCreatedByOrganizer(dc); assertEquals(1, infos.size()); - assertEquals(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, infos.get(0).getWindowingMode()); + assertEquals(WINDOWING_MODE_MULTI_WINDOW, infos.get(0).getWindowingMode()); } @Test @@ -577,7 +575,7 @@ public class WindowOrganizerTests extends WindowTestsBase { final StubOrganizer listener = new StubOrganizer(); mWm.mAtmService.mTaskOrganizerController.registerTaskOrganizer(listener); Task task = mWm.mAtmService.mTaskOrganizerController.createRootTask( - mDisplayContent, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, null); + mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, null); RunningTaskInfo info1 = task.getTaskInfo(); final Task rootTask = createTask( @@ -626,7 +624,7 @@ public class WindowOrganizerTests extends WindowTestsBase { }; mWm.mAtmService.mTaskOrganizerController.registerTaskOrganizer(listener); Task task = mWm.mAtmService.mTaskOrganizerController.createRootTask( - mDisplayContent, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, null); + mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, null); RunningTaskInfo info1 = task.getTaskInfo(); // Ensure events dispatch to organizer. mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents(); @@ -684,10 +682,10 @@ public class WindowOrganizerTests extends WindowTestsBase { mWm.mAtmService.mTaskOrganizerController.registerTaskOrganizer(listener); Task task1 = mWm.mAtmService.mTaskOrganizerController.createRootTask( - mDisplayContent, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, null); + mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, null); RunningTaskInfo info1 = task1.getTaskInfo(); Task task2 = mWm.mAtmService.mTaskOrganizerController.createRootTask( - mDisplayContent, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, null); + mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, null); RunningTaskInfo info2 = task2.getTaskInfo(); // Ensure events dispatch to organizer. mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents(); @@ -1056,7 +1054,7 @@ public class WindowOrganizerTests extends WindowTestsBase { public void testReparentToOrganizedTask() { final ITaskOrganizer organizer = registerMockOrganizer(); Task rootTask = mWm.mAtmService.mTaskOrganizerController.createRootTask( - mDisplayContent, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, null); + mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, null); final Task task1 = createRootTask(); final Task task2 = createTask(rootTask, false /* fakeDraw */); WindowContainerTransaction wct = new WindowContainerTransaction(); @@ -1223,7 +1221,7 @@ public class WindowOrganizerTests extends WindowTestsBase { final Task rootTask = activity.getRootTask(); rootTask.setResizeMode(activity.info.resizeMode); final Task splitPrimaryRootTask = mWm.mAtmService.mTaskOrganizerController.createRootTask( - mDisplayContent, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, null); + mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, null); final WindowContainerTransaction wct = new WindowContainerTransaction(); wct.reparent(rootTask.mRemoteToken.toWindowContainerToken(), splitPrimaryRootTask.mRemoteToken.toWindowContainerToken(), true /* onTop */); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java index ec8ec2b31918..80192f7b5d60 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java @@ -20,7 +20,6 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; -import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; import static android.view.InsetsState.ITYPE_IME; import static android.view.InsetsState.ITYPE_NAVIGATION_BAR; import static android.view.InsetsState.ITYPE_STATUS_BAR; @@ -83,6 +82,7 @@ import android.graphics.Rect; import android.os.IBinder; import android.os.RemoteException; import android.platform.test.annotations.Presubmit; +import android.util.ArraySet; import android.view.Gravity; import android.view.InputWindowHandle; import android.view.InsetsSource; @@ -265,22 +265,19 @@ public class WindowStateTests extends WindowTestsBase { assertFalse(appWindow.canBeImeTarget()); assertFalse(imeWindow.canBeImeTarget()); - // Simulate the window is in split screen primary root task and the current state is - // minimized and home root task is resizable, so that we should ignore input for the - // root task. + // Simulate the window is in split screen root task. final DockedTaskDividerController controller = mDisplayContent.getDockedDividerController(); final Task rootTask = createTask(mDisplayContent, - WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD); + WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD); spyOn(appWindow); spyOn(controller); spyOn(rootTask); rootTask.setFocusable(false); doReturn(rootTask).when(appWindow).getRootTask(); - // Make sure canBeImeTarget is false due to shouldIgnoreInput is true; + // Make sure canBeImeTarget is false; assertFalse(appWindow.canBeImeTarget()); - assertTrue(rootTask.shouldIgnoreInput()); } @Test @@ -727,8 +724,9 @@ public class WindowStateTests extends WindowTestsBase { @Test public void testCantReceiveTouchWhenNotFocusable() { final WindowState win0 = createWindow(null, TYPE_APPLICATION, "win0"); - win0.mActivityRecord.getRootTask().setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); - win0.mActivityRecord.getRootTask().setFocusable(false); + final Task rootTask = win0.mActivityRecord.getRootTask(); + spyOn(rootTask); + when(rootTask.shouldIgnoreInput()).thenReturn(true); assertFalse(win0.canReceiveTouchInput()); } @@ -928,8 +926,8 @@ public class WindowStateTests extends WindowTestsBase { mDisplayContent.setImeLayeringTarget(mAppWindow); // Simulate entering multi-window mode and verify if the IME control target is remote. - app.mActivityRecord.getRootTask().setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); - assertEquals(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, app.getWindowingMode()); + app.mActivityRecord.getRootTask().setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); + assertEquals(WINDOWING_MODE_MULTI_WINDOW, app.getWindowingMode()); assertEquals(mDisplayContent.mRemoteInsetsControlTarget, mDisplayContent.computeImeControlTarget()); @@ -944,14 +942,13 @@ public class WindowStateTests extends WindowTestsBase { @UseTestDisplay(addWindows = { W_ACTIVITY, W_INPUT_METHOD, W_NOTIFICATION_SHADE }) @Test - public void testNotificationShadeHasImeInsetsWhenSplitscreenActivated() { + public void testNotificationShadeHasImeInsetsWhenMultiWindow() { WindowState app = createWindow(null, TYPE_BASE_APPLICATION, mAppWindow.mToken, "app"); - // Simulate entering multi-window mode and verify if the split-screen is activated. - app.mActivityRecord.getRootTask().setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); - assertEquals(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, app.getWindowingMode()); - assertTrue(mDisplayContent.getDefaultTaskDisplayArea().isSplitScreenModeActivated()); + // Simulate entering multi-window mode and windowing mode is multi-window. + app.mActivityRecord.getRootTask().setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); + assertEquals(WINDOWING_MODE_MULTI_WINDOW, app.getWindowingMode()); // Simulate notificationShade is shown and being IME layering target. mNotificationShadeWindow.setHasSurface(true); @@ -965,7 +962,7 @@ public class WindowStateTests extends WindowTestsBase { mDisplayContent.getInsetsStateController().getRawInsetsState() .setSourceVisible(ITYPE_IME, true); - // Verify notificationShade can still get IME insets even the split-screen is activated. + // Verify notificationShade can still get IME insets even windowing mode is multi-window. InsetsState state = mDisplayContent.getInsetsStateController().getInsetsForWindow( mNotificationShadeWindow); assertNotNull(state.peekSource(ITYPE_IME)); @@ -986,4 +983,40 @@ public class WindowStateTests extends WindowTestsBase { assertFalse(app.isVisible()); assertTrue(app.isVisibleRequested()); } + + @Test + public void testKeepClearAreas() { + final WindowState window = createWindow(null, TYPE_APPLICATION, "window"); + makeWindowVisible(window); + + final Rect keepClearArea1 = new Rect(0, 0, 10, 10); + final Rect keepClearArea2 = new Rect(5, 10, 15, 20); + final List<Rect> keepClearAreas = Arrays.asList(keepClearArea1, keepClearArea2); + window.setKeepClearAreas(keepClearAreas); + + // Test that the keep-clear rects are stored and returned + assertEquals(new ArraySet(keepClearAreas), new ArraySet(window.getKeepClearAreas())); + + // Test that keep-clear rects are overwritten + window.setKeepClearAreas(Arrays.asList()); + assertEquals(0, window.getKeepClearAreas().size()); + + // Move the window position + final SurfaceControl.Transaction t = spy(StubTransaction.class); + window.mSurfaceControl = mock(SurfaceControl.class); + final Rect frame = window.getFrame(); + frame.set(10, 20, 60, 80); + window.updateSurfacePosition(t); + assertEquals(new Point(frame.left, frame.top), window.mLastSurfacePosition); + + // Test that the returned keep-clear rects are translated to display space + window.setKeepClearAreas(keepClearAreas); + Rect expectedArea1 = new Rect(keepClearArea1); + expectedArea1.offset(frame.left, frame.top); + Rect expectedArea2 = new Rect(keepClearArea2); + expectedArea2.offset(frame.left, frame.top); + + assertEquals(new ArraySet(Arrays.asList(expectedArea1, expectedArea2)), + new ArraySet(window.getKeepClearAreas())); + } } diff --git a/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java b/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java index 4dffe7e8345b..0f223ca037ee 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java @@ -22,7 +22,6 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; -import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ABOVE_SUB_PANEL; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG; @@ -363,7 +362,7 @@ public class ZOrderingTests extends WindowTestsBase { ACTIVITY_TYPE_STANDARD, TYPE_BASE_APPLICATION, mDisplayContent, "pinnedStackWindow"); final WindowState dockedStackWindow = createWindow(null, - WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, TYPE_BASE_APPLICATION, + WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD, TYPE_BASE_APPLICATION, mDisplayContent, "dockedStackWindow"); final WindowState assistantStackWindow = createWindow(null, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_ASSISTANT, TYPE_BASE_APPLICATION, diff --git a/services/usage/java/com/android/server/usage/StorageStatsService.java b/services/usage/java/com/android/server/usage/StorageStatsService.java index 88725a6ea1b9..0d88a0d485ff 100644 --- a/services/usage/java/com/android/server/usage/StorageStatsService.java +++ b/services/usage/java/com/android/server/usage/StorageStatsService.java @@ -61,6 +61,7 @@ import android.os.storage.CrateMetadata; import android.os.storage.StorageEventListener; import android.os.storage.StorageManager; import android.os.storage.VolumeInfo; +import android.provider.DeviceConfig; import android.provider.Settings; import android.text.TextUtils; import android.text.format.DateUtils; @@ -70,6 +71,7 @@ import android.util.Pair; import android.util.Slog; import android.util.SparseLongArray; +import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; import com.android.internal.util.Preconditions; @@ -128,6 +130,12 @@ public class StorageStatsService extends IStorageStatsManager.Stub { private final CopyOnWriteArrayList<Pair<String, StorageStatsAugmenter>> mStorageStatsAugmenters = new CopyOnWriteArrayList<>(); + @GuardedBy("mLock") + private int + mStorageThresholdPercentHigh = StorageManager.DEFAULT_STORAGE_THRESHOLD_PERCENT_HIGH; + + private final Object mLock = new Object(); + public StorageStatsService(Context context) { mContext = Preconditions.checkNotNull(context); mAppOps = Preconditions.checkNotNull(context.getSystemService(AppOpsManager.class)); @@ -173,6 +181,19 @@ public class StorageStatsService extends IStorageStatsManager.Stub { } } }, prFilter); + + updateConfig(); + DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT, + mContext.getMainExecutor(), properties -> updateConfig()); + } + + private void updateConfig() { + synchronized (mLock) { + mStorageThresholdPercentHigh = DeviceConfig.getInt( + DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT, + StorageManager.STORAGE_THRESHOLD_PERCENT_HIGH_KEY, + StorageManager.DEFAULT_STORAGE_THRESHOLD_PERCENT_HIGH); + } } private void invalidateMounts() { @@ -554,7 +575,7 @@ public class StorageStatsService extends IStorageStatsManager.Stub { * By only triggering a re-calculation after the storage has changed sizes, we can avoid * recalculating quotas too often. Minimum change delta high and low define the * percentage of change we need to see before we recalculate quotas when the device has - * enough storage space (more than StorageManager.STORAGE_THRESHOLD_PERCENT_HIGH of total + * enough storage space (more than mStorageThresholdPercentHigh of total * free) and in low storage condition respectively. */ private static final long MINIMUM_CHANGE_DELTA_PERCENT_HIGH = 5; @@ -588,11 +609,15 @@ public class StorageStatsService extends IStorageStatsManager.Stub { mStats.restat(Environment.getDataDirectory().getAbsolutePath()); long bytesDelta = Math.abs(mPreviousBytes - mStats.getAvailableBytes()); long bytesDeltaThreshold; - if (mStats.getAvailableBytes() > mTotalBytes - * StorageManager.STORAGE_THRESHOLD_PERCENT_HIGH / 100) { - bytesDeltaThreshold = mTotalBytes * MINIMUM_CHANGE_DELTA_PERCENT_HIGH / 100; - } else { - bytesDeltaThreshold = mTotalBytes * MINIMUM_CHANGE_DELTA_PERCENT_LOW / 100; + synchronized (mLock) { + if (mStats.getAvailableBytes() > mTotalBytes + * mStorageThresholdPercentHigh / 100) { + bytesDeltaThreshold = mTotalBytes + * MINIMUM_CHANGE_DELTA_PERCENT_HIGH / 100; + } else { + bytesDeltaThreshold = mTotalBytes + * MINIMUM_CHANGE_DELTA_PERCENT_LOW / 100; + } } if (bytesDelta > bytesDeltaThreshold) { mPreviousBytes = mStats.getAvailableBytes(); diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java index 997c8832f4a9..559eb388fe57 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UsageStatsService.java @@ -33,6 +33,7 @@ import static android.text.format.DateUtils.HOUR_IN_MILLIS; import android.Manifest; import android.annotation.CurrentTimeMillisLong; +import android.annotation.ElapsedRealtimeLong; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; @@ -2947,6 +2948,27 @@ public class UsageStatsService extends SystemService implements @NonNull EstimatedLaunchTimeChangedListener listener) { UsageStatsService.this.unregisterLaunchTimeChangedListener(listener); } + + @Override + public void reportBroadcastDispatched(int sourceUid, @NonNull String targetPackage, + @NonNull UserHandle targetUser, long idForResponseEvent, + @ElapsedRealtimeLong long timestampMs) { + } + + @Override + public void reportNotificationPosted(@NonNull String packageName, + @NonNull UserHandle user, @ElapsedRealtimeLong long timestampMs) { + } + + @Override + public void reportNotificationUpdated(@NonNull String packageName, + @NonNull UserHandle user, @ElapsedRealtimeLong long timestampMs) { + } + + @Override + public void reportNotificationRemoved(@NonNull String packageName, + @NonNull UserHandle user, @ElapsedRealtimeLong long timestampMs) { + } } private class MyPackageMonitor extends PackageMonitor { diff --git a/services/usb/Android.bp b/services/usb/Android.bp index 01feacd826c5..3b50fa43536c 100644 --- a/services/usb/Android.bp +++ b/services/usb/Android.bp @@ -29,6 +29,7 @@ java_library_static { "android.hardware.usb-V1.1-java", "android.hardware.usb-V1.2-java", "android.hardware.usb-V1.3-java", + "android.hardware.usb-V1-java", "android.hardware.usb.gadget-V1.0-java", "android.hardware.usb.gadget-V1.1-java", "android.hardware.usb.gadget-V1.2-java", diff --git a/services/usb/java/com/android/server/usb/UsbAlsaDevice.java b/services/usb/java/com/android/server/usb/UsbAlsaDevice.java index 9d4db003a297..85b1de5478e1 100644 --- a/services/usb/java/com/android/server/usb/UsbAlsaDevice.java +++ b/services/usb/java/com/android/server/usb/UsbAlsaDevice.java @@ -41,6 +41,7 @@ public final class UsbAlsaDevice { private final boolean mIsInputHeadset; private final boolean mIsOutputHeadset; + private final boolean mIsDock; private boolean mSelected = false; private int mOutputState; @@ -53,7 +54,7 @@ public final class UsbAlsaDevice { public UsbAlsaDevice(IAudioService audioService, int card, int device, String deviceAddress, boolean hasOutput, boolean hasInput, - boolean isInputHeadset, boolean isOutputHeadset) { + boolean isInputHeadset, boolean isOutputHeadset, boolean isDock) { mAudioService = audioService; mCardNum = card; mDeviceNum = device; @@ -62,31 +63,32 @@ public final class UsbAlsaDevice { mHasInput = hasInput; mIsInputHeadset = isInputHeadset; mIsOutputHeadset = isOutputHeadset; + mIsDock = isDock; } /** - * @returns the ALSA card number associated with this peripheral. + * @return the ALSA card number associated with this peripheral. */ public int getCardNum() { return mCardNum; } /** - * @returns the ALSA device number associated with this peripheral. + * @return the ALSA device number associated with this peripheral. */ public int getDeviceNum() { return mDeviceNum; } /** - * @returns the USB device device address associated with this peripheral. + * @return the USB device device address associated with this peripheral. */ public String getDeviceAddress() { return mDeviceAddress; } /** - * @returns the ALSA card/device address string. + * @return the ALSA card/device address string. */ public String getAlsaCardDeviceString() { if (mCardNum < 0 || mDeviceNum < 0) { @@ -98,35 +100,42 @@ public final class UsbAlsaDevice { } /** - * @returns true if the device supports output. + * @return true if the device supports output. */ public boolean hasOutput() { return mHasOutput; } /** - * @returns true if the device supports input (recording). + * @return true if the device supports input (recording). */ public boolean hasInput() { return mHasInput; } /** - * @returns true if the device is a headset for purposes of input. + * @return true if the device is a headset for purposes of input. */ public boolean isInputHeadset() { return mIsInputHeadset; } /** - * @returns true if the device is a headset for purposes of output. + * @return true if the device is a headset for purposes of output. */ public boolean isOutputHeadset() { return mIsOutputHeadset; } /** - * @returns true if input jack is detected or jack detection is not supported. + * @return true if the device is a USB dock. + */ + public boolean isDock() { + return mIsDock; + } + + /** + * @return true if input jack is detected or jack detection is not supported. */ private synchronized boolean isInputJackConnected() { if (mJackDetector == null) { @@ -136,7 +145,7 @@ public final class UsbAlsaDevice { } /** - * @returns true if input jack is detected or jack detection is not supported. + * @return true if input jack is detected or jack detection is not supported. */ private synchronized boolean isOutputJackConnected() { if (mJackDetector == null) { @@ -190,9 +199,10 @@ public final class UsbAlsaDevice { try { // Output Device if (mHasOutput) { - int device = mIsOutputHeadset - ? AudioSystem.DEVICE_OUT_USB_HEADSET - : AudioSystem.DEVICE_OUT_USB_DEVICE; + int device = mIsDock ? AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET + : (mIsOutputHeadset + ? AudioSystem.DEVICE_OUT_USB_HEADSET + : AudioSystem.DEVICE_OUT_USB_DEVICE); if (DEBUG) { Slog.d(TAG, "pre-call device:0x" + Integer.toHexString(device) + " addr:" + alsaCardDeviceString @@ -231,7 +241,7 @@ public final class UsbAlsaDevice { /** * @Override - * @returns a string representation of the object. + * @return a string representation of the object. */ public synchronized String toString() { return "UsbAlsaDevice: [card: " + mCardNum @@ -273,7 +283,7 @@ public final class UsbAlsaDevice { /** * @Override - * @returns true if the objects are equivalent. + * @return true if the objects are equivalent. */ public boolean equals(Object obj) { if (!(obj instanceof UsbAlsaDevice)) { @@ -285,12 +295,13 @@ public final class UsbAlsaDevice { && mHasOutput == other.mHasOutput && mHasInput == other.mHasInput && mIsInputHeadset == other.mIsInputHeadset - && mIsOutputHeadset == other.mIsOutputHeadset); + && mIsOutputHeadset == other.mIsOutputHeadset + && mIsDock == other.mIsDock); } /** * @Override - * @returns a hash code generated from the object contents. + * @return a hash code generated from the object contents. */ public int hashCode() { final int prime = 31; @@ -301,6 +312,7 @@ public final class UsbAlsaDevice { result = prime * result + (mHasInput ? 0 : 1); result = prime * result + (mIsInputHeadset ? 0 : 1); result = prime * result + (mIsOutputHeadset ? 0 : 1); + result = prime * result + (mIsDock ? 0 : 1); return result; } diff --git a/services/usb/java/com/android/server/usb/UsbAlsaManager.java b/services/usb/java/com/android/server/usb/UsbAlsaManager.java index 0aa62c53e269..fd9b9952331a 100644 --- a/services/usb/java/com/android/server/usb/UsbAlsaManager.java +++ b/services/usb/java/com/android/server/usb/UsbAlsaManager.java @@ -237,6 +237,7 @@ public final class UsbAlsaManager { if (hasInput || hasOutput) { boolean isInputHeadset = parser.isInputHeadset(); boolean isOutputHeadset = parser.isOutputHeadset(); + boolean isDock = parser.isDock(); if (mAudioService == null) { Slog.e(TAG, "no AudioService"); @@ -246,7 +247,7 @@ public final class UsbAlsaManager { UsbAlsaDevice alsaDevice = new UsbAlsaDevice(mAudioService, cardRec.getCardNum(), 0 /*device*/, deviceAddress, hasOutput, hasInput, - isInputHeadset, isOutputHeadset); + isInputHeadset, isOutputHeadset, isDock); if (alsaDevice != null) { alsaDevice.setDeviceNameAndDescription( cardRec.getCardName(), cardRec.getCardDescription()); @@ -257,10 +258,11 @@ public final class UsbAlsaManager { // look for MIDI devices boolean hasMidi = parser.hasMIDIInterface(); - int midiNumInputs = parser.calculateNumMidiInputs(); - int midiNumOutputs = parser.calculateNumMidiOutputs(); + int midiNumInputs = parser.calculateNumLegacyMidiInputs(); + int midiNumOutputs = parser.calculateNumLegacyMidiOutputs(); if (DEBUG) { Slog.d(TAG, "hasMidi: " + hasMidi + " mHasMidiFeature:" + mHasMidiFeature); + Slog.d(TAG, "midiNumInputs: " + midiNumInputs + " midiNumOutputs:" + midiNumOutputs); } if (hasMidi && mHasMidiFeature) { int device = 0; diff --git a/services/usb/java/com/android/server/usb/UsbHostManager.java b/services/usb/java/com/android/server/usb/UsbHostManager.java index f33001c9241e..94cc826ffc43 100644 --- a/services/usb/java/com/android/server/usb/UsbHostManager.java +++ b/services/usb/java/com/android/server/usb/UsbHostManager.java @@ -23,6 +23,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ComponentName; import android.content.Context; +import android.content.pm.PackageManager; import android.hardware.usb.UsbConstants; import android.hardware.usb.UsbDevice; import android.os.Bundle; @@ -46,6 +47,8 @@ import com.android.server.usb.descriptors.UsbInterfaceDescriptor; import com.android.server.usb.descriptors.report.TextReportCanvas; import com.android.server.usb.descriptors.tree.UsbDescriptorsTree; +import libcore.io.IoUtils; + import java.text.SimpleDateFormat; import java.util.Date; import java.util.HashMap; @@ -90,6 +93,13 @@ public class UsbHostManager { private ConnectionRecord mLastConnect; private final ArrayMap<String, ConnectionRecord> mConnected = new ArrayMap<>(); + /** + * List of connected MIDI devices + */ + private final HashMap<String, UsbUniversalMidiDevice> + mMidiDevices = new HashMap<String, UsbUniversalMidiDevice>(); + private final boolean mHasMidiFeature; + /* * ConnectionRecord * Stores connection/disconnection data. @@ -155,7 +165,7 @@ public class UsbHostManager { pw.println("manfacturer:0x" + Integer.toHexString(deviceDescriptor.getVendorID()) + " product:" + Integer.toHexString(deviceDescriptor.getProductID())); pw.println("isHeadset[in: " + parser.isInputHeadset() - + " , out: " + parser.isOutputHeadset() + "]"); + + " , out: " + parser.isOutputHeadset() + "], isDock: " + parser.isDock()); } else { pw.println(formatTime() + " Disconnect " + mDeviceAddress); } @@ -169,9 +179,8 @@ public class UsbHostManager { UsbDescriptorsTree descriptorTree = new UsbDescriptorsTree(); descriptorTree.parse(parser); descriptorTree.report(new TextReportCanvas(parser, stringBuilder)); - stringBuilder.append("isHeadset[in: " + parser.isInputHeadset() - + " , out: " + parser.isOutputHeadset() + "]"); + + " , out: " + parser.isOutputHeadset() + "], isDock: " + parser.isDock()); pw.println(stringBuilder.toString()); } else { pw.println(formatTime() + " Disconnect " + mDeviceAddress); @@ -188,9 +197,8 @@ public class UsbHostManager { descriptor.report(canvas); } pw.println(stringBuilder.toString()); - pw.println("isHeadset[in: " + parser.isInputHeadset() - + " , out: " + parser.isOutputHeadset() + "]"); + + " , out: " + parser.isOutputHeadset() + "], isDock: " + parser.isDock()); } else { pw.println(formatTime() + " Disconnect " + mDeviceAddress); } @@ -245,6 +253,7 @@ public class UsbHostManager { setUsbDeviceConnectionHandler(ComponentName.unflattenFromString( deviceConnectionHandler)); } + mHasMidiFeature = context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_MIDI); } public void setCurrentUserSettings(UsbProfileGroupSettingsManager settings) { @@ -413,6 +422,18 @@ public class UsbHostManager { mUsbAlsaManager.usbDeviceAdded(deviceAddress, newDevice, parser); + if (mHasMidiFeature) { + if (parser.containsUniversalMidiDeviceEndpoint()) { + UsbUniversalMidiDevice midiDevice = UsbUniversalMidiDevice.create(mContext, + newDevice, parser); + if (midiDevice != null) { + mMidiDevices.put(deviceAddress, midiDevice); + } else { + Slog.e(TAG, "Universal Midi Device is null."); + } + } + } + // Tracking addConnectionRecord(deviceAddress, ConnectionRecord.CONNECT, parser.getRawDescriptors()); @@ -446,6 +467,14 @@ public class UsbHostManager { Slog.d(TAG, "Removed device at " + deviceAddress + ": " + device.getProductName()); mUsbAlsaManager.usbDeviceRemoved(deviceAddress); mPermissionManager.usbDeviceRemoved(device); + + // MIDI + UsbUniversalMidiDevice midiDevice = mMidiDevices.remove(deviceAddress); + if (midiDevice != null) { + Slog.i(TAG, "USB Universal MIDI Device Removed: " + deviceAddress); + IoUtils.closeQuietly(midiDevice); + } + getCurrentUserSettings().usbDeviceRemoved(device); ConnectionRecord current = mConnected.get(deviceAddress); // Tracking diff --git a/services/usb/java/com/android/server/usb/UsbMidiDevice.java b/services/usb/java/com/android/server/usb/UsbMidiDevice.java index b61e93b36e58..275f21755141 100644 --- a/services/usb/java/com/android/server/usb/UsbMidiDevice.java +++ b/services/usb/java/com/android/server/usb/UsbMidiDevice.java @@ -303,7 +303,8 @@ public final class UsbMidiDevice implements Closeable { } mServer = midiManager.createDeviceServer(mMidiInputPortReceivers, mNumInputs, - null, null, properties, MidiDeviceInfo.TYPE_USB, mCallback); + null, null, properties, MidiDeviceInfo.TYPE_USB, + MidiDeviceInfo.PROTOCOL_UNKNOWN, mCallback); if (mServer == null) { return false; } diff --git a/services/usb/java/com/android/server/usb/UsbPortManager.java b/services/usb/java/com/android/server/usb/UsbPortManager.java index ec28040f82d8..65b79bfbb36f 100644 --- a/services/usb/java/com/android/server/usb/UsbPortManager.java +++ b/services/usb/java/com/android/server/usb/UsbPortManager.java @@ -16,6 +16,8 @@ package com.android.server.usb; +import static android.hardware.usb.UsbOperationInternal.USB_OPERATION_ERROR_PORT_MISMATCH; +import static android.hardware.usb.UsbOperationInternal.USB_OPERATION_ERROR_INTERNAL; import static android.hardware.usb.UsbPortStatus.CONTAMINANT_DETECTION_NOT_SUPPORTED; import static android.hardware.usb.UsbPortStatus.CONTAMINANT_PROTECTION_NONE; import static android.hardware.usb.UsbPortStatus.DATA_ROLE_DEVICE; @@ -25,6 +27,12 @@ import static android.hardware.usb.UsbPortStatus.MODE_DUAL; import static android.hardware.usb.UsbPortStatus.MODE_UFP; import static android.hardware.usb.UsbPortStatus.POWER_ROLE_SINK; import static android.hardware.usb.UsbPortStatus.POWER_ROLE_SOURCE; +import static com.android.server.usb.hal.port.UsbPortHal.HAL_POWER_ROLE_SOURCE; +import static com.android.server.usb.hal.port.UsbPortHal.HAL_POWER_ROLE_SINK; +import static com.android.server.usb.hal.port.UsbPortHal.HAL_DATA_ROLE_HOST; +import static com.android.server.usb.hal.port.UsbPortHal.HAL_DATA_ROLE_DEVICE; +import static com.android.server.usb.hal.port.UsbPortHal.HAL_MODE_DFP; +import static com.android.server.usb.hal.port.UsbPortHal.HAL_MODE_UFP; import static com.android.internal.usb.DumpUtils.writePort; import static com.android.internal.usb.DumpUtils.writePortStatus; @@ -38,6 +46,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.res.Resources; +import android.hardware.usb.IUsbOperationInternal; import android.hardware.usb.ParcelableUsbPort; import android.hardware.usb.UsbManager; import android.hardware.usb.UsbPort; @@ -74,9 +83,13 @@ import com.android.internal.util.FrameworkStatsLog; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.dump.DualDumpOutputStream; import com.android.server.FgThread; +import com.android.server.usb.hal.port.RawPortInfo; +import com.android.server.usb.hal.port.UsbPortHal; +import com.android.server.usb.hal.port.UsbPortHalInstance; import java.util.ArrayList; import java.util.NoSuchElementException; +import java.util.Objects; /** * Allows trusted components to control the properties of physical USB ports @@ -109,16 +122,9 @@ public class UsbPortManager { // The system context. private final Context mContext; - // Proxy object for the usb hal daemon. - @GuardedBy("mLock") - private IUsb mProxy = null; - // Callback when the UsbPort status is changed by the kernel. // Mostly due a command sent by the remote Usb device. - private HALCallback mHALCallback = new HALCallback(null, this); - - // Cookie sent for usb hal death notification. - private static final int USB_HAL_DEATH_COOKIE = 1000; + //private HALCallback mHALCallback = new HALCallback(null, this); // Used as the key while sending the bundle to Main thread. private static final String PORT_INFO = "port_info"; @@ -156,36 +162,23 @@ public class UsbPortManager { */ private int mIsPortContaminatedNotificationId; - private boolean mEnableUsbDataSignaling; - protected int mCurrentUsbHalVersion; + private UsbPortHal mUsbPortHal; + + private long mTransactionId; public UsbPortManager(Context context) { mContext = context; - try { - ServiceNotification serviceNotification = new ServiceNotification(); - - boolean ret = IServiceManager.getService() - .registerForNotifications("android.hardware.usb@1.0::IUsb", - "", serviceNotification); - if (!ret) { - logAndPrint(Log.ERROR, null, - "Failed to register service start notification"); - } - } catch (RemoteException e) { - logAndPrintException(null, - "Failed to register service start notification", e); - return; - } - connectToProxy(null); + mUsbPortHal = UsbPortHalInstance.getInstance(this, null); + logAndPrint(Log.DEBUG, null, "getInstance done"); } public void systemReady() { - mSystemReady = true; - if (mProxy != null) { + mSystemReady = true; + if (mUsbPortHal != null) { + mUsbPortHal.systemReady(); try { - mProxy.queryPortStatus(); - mEnableUsbDataSignaling = true; - } catch (RemoteException e) { + mUsbPortHal.queryPortStatus(++mTransactionId); + } catch (Exception e) { logAndPrintException(null, "ServiceStart: Failed to query port status", e); } @@ -233,6 +226,7 @@ public class UsbPortManager { intent.setComponent(ComponentName.unflattenFromString(r.getString( com.android.internal.R.string.config_usbContaminantActivity))); intent.putExtra(UsbManager.EXTRA_PORT, ParcelableUsbPort.of(currentPortInfo.mUsbPort)); + intent.putExtra(UsbManager.EXTRA_PORT_STATUS, currentPortInfo.mUsbPortStatus); // Simple notification clicks are immutable PendingIntent pi = PendingIntent.getActivityAsUser(mContext, 0, @@ -340,61 +334,173 @@ public class UsbPortManager { } try { - // Oneway call into the hal. Use the castFrom method from HIDL. - android.hardware.usb.V1_2.IUsb proxy = android.hardware.usb.V1_2.IUsb.castFrom(mProxy); - proxy.enableContaminantPresenceDetection(portId, enable); - } catch (RemoteException e) { + mUsbPortHal.enableContaminantPresenceDetection(portId, enable, ++mTransactionId); + } catch (Exception e) { logAndPrintException(pw, "Failed to set contaminant detection", e); - } catch (ClassCastException e) { - logAndPrintException(pw, "Method only applicable to V1.2 or above implementation", e); } } /** - * Enable/disable the USB data signaling + * Limits power transfer in/out of USB-C port. * - * @param enable enable or disable USB data signaling + * @param portId port identifier. + * @param limit limit power transfer when true. */ - public boolean enableUsbDataSignal(boolean enable) { + public void enableLimitPowerTransfer(@NonNull String portId, boolean limit, long transactionId, + IUsbOperationInternal callback, IndentingPrintWriter pw) { + Objects.requireNonNull(portId); + final PortInfo portInfo = mPorts.get(portId); + if (portInfo == null) { + logAndPrint(Log.ERROR, pw, "enableLimitPowerTransfer: No such port: " + portId + + " opId:" + transactionId); + try { + if (callback != null) { + callback.onOperationComplete(USB_OPERATION_ERROR_PORT_MISMATCH); + } + } catch (RemoteException e) { + logAndPrintException(pw, + "enableLimitPowerTransfer: Failed to call OperationComplete. opId:" + + transactionId, e); + } + return; + } + try { - mEnableUsbDataSignaling = enable; - // Call into the hal. Use the castFrom method from HIDL. - android.hardware.usb.V1_3.IUsb proxy = android.hardware.usb.V1_3.IUsb.castFrom(mProxy); - return proxy.enableUsbDataSignal(enable); + try { + mUsbPortHal.enableLimitPowerTransfer(portId, limit, transactionId, callback); + } catch (Exception e) { + logAndPrintException(pw, + "enableLimitPowerTransfer: Failed to limit power transfer. opId:" + + transactionId , e); + if (callback != null) { + callback.onOperationComplete(USB_OPERATION_ERROR_INTERNAL); + } + } } catch (RemoteException e) { - logAndPrintException(null, "Failed to set USB data signaling", e); - return false; - } catch (ClassCastException e) { - logAndPrintException(null, "Method only applicable to V1.3 or above implementation", e); - return false; + logAndPrintException(pw, + "enableLimitPowerTransfer:Failed to call onOperationComplete. opId:" + + transactionId, e); } } /** - * Get USB HAL version + * Enables USB data when disabled due to {@link UsbPortStatus#USB_DATA_STATUS_DISABLED_DOCK} + */ + public void enableUsbDataWhileDocked(@NonNull String portId, long transactionId, + IUsbOperationInternal callback, IndentingPrintWriter pw) { + Objects.requireNonNull(portId); + final PortInfo portInfo = mPorts.get(portId); + if (portInfo == null) { + logAndPrint(Log.ERROR, pw, "enableUsbDataWhileDocked: No such port: " + portId + + " opId:" + transactionId); + try { + if (callback != null) { + callback.onOperationComplete(USB_OPERATION_ERROR_PORT_MISMATCH); + } + } catch (RemoteException e) { + logAndPrintException(pw, + "enableUsbDataWhileDocked: Failed to call OperationComplete. opId:" + + transactionId, e); + } + return; + } + + try { + try { + mUsbPortHal.enableUsbDataWhileDocked(portId, transactionId, callback); + } catch (Exception e) { + logAndPrintException(pw, + "enableUsbDataWhileDocked: Failed to limit power transfer. opId:" + + transactionId , e); + if (callback != null) { + callback.onOperationComplete(USB_OPERATION_ERROR_INTERNAL); + } + } + } catch (RemoteException e) { + logAndPrintException(pw, + "enableUsbDataWhileDocked:Failed to call onOperationComplete. opId:" + + transactionId, e); + } + } + + /** + * Enable/disable the USB data signaling * - * @param none + * @param enable enable or disable USB data signaling */ - public int getUsbHalVersion() { - return mCurrentUsbHalVersion; + public boolean enableUsbData(@NonNull String portId, boolean enable, int transactionId, + @NonNull IUsbOperationInternal callback, IndentingPrintWriter pw) { + Objects.requireNonNull(callback); + Objects.requireNonNull(portId); + final PortInfo portInfo = mPorts.get(portId); + if (portInfo == null) { + logAndPrint(Log.ERROR, pw, "enableUsbData: No such port: " + portId + + " opId:" + transactionId); + try { + callback.onOperationComplete(USB_OPERATION_ERROR_PORT_MISMATCH); + } catch (RemoteException e) { + logAndPrintException(pw, + "enableUsbData: Failed to call OperationComplete. opId:" + + transactionId, e); + } + return false; + } + + try { + try { + return mUsbPortHal.enableUsbData(portId, enable, transactionId, callback); + } catch (Exception e) { + logAndPrintException(pw, + "enableUsbData: Failed to invoke enableUsbData. opId:" + + transactionId , e); + callback.onOperationComplete(USB_OPERATION_ERROR_INTERNAL); + } + } catch (RemoteException e) { + logAndPrintException(pw, + "enableUsbData: Failed to call onOperationComplete. opId:" + + transactionId, e); + } + + return false; } /** - * update USB HAL version + * Get USB HAL version * * @param none + * @return {@link UsbManager#USB_HAL_RETRY} returned when hal version + * is yet to be determined. */ - private void updateUsbHalVersion() { - if (android.hardware.usb.V1_3.IUsb.castFrom(mProxy) != null) { - mCurrentUsbHalVersion = UsbManager.USB_HAL_V1_3; - } else if (android.hardware.usb.V1_2.IUsb.castFrom(mProxy) != null) { - mCurrentUsbHalVersion = UsbManager.USB_HAL_V1_2; - } else if (android.hardware.usb.V1_1.IUsb.castFrom(mProxy) != null) { - mCurrentUsbHalVersion = UsbManager.USB_HAL_V1_1; - } else { - mCurrentUsbHalVersion = UsbManager.USB_HAL_V1_0; + public int getUsbHalVersion() { + if (mUsbPortHal != null) { + try { + return mUsbPortHal.getUsbHalVersion(); + } catch (RemoteException e) { + return UsbManager.USB_HAL_RETRY; + } } - logAndPrint(Log.INFO, null, "USB HAL version: " + mCurrentUsbHalVersion); + return UsbManager.USB_HAL_RETRY; + } + + private int toHalUsbDataRole(int usbDataRole) { + if (usbDataRole == DATA_ROLE_DEVICE) + return HAL_DATA_ROLE_DEVICE; + else + return HAL_DATA_ROLE_HOST; + } + + private int toHalUsbPowerRole(int usbPowerRole) { + if (usbPowerRole == POWER_ROLE_SINK) + return HAL_POWER_ROLE_SINK; + else + return HAL_POWER_ROLE_SOURCE; + } + + private int toHalUsbMode(int usbMode) { + if (usbMode == MODE_UFP) + return HAL_MODE_UFP; + else + return HAL_MODE_DFP; } public void setPortRoles(String portId, int newPowerRole, int newDataRole, @@ -473,7 +579,7 @@ public class UsbPortManager { sim.currentPowerRole = newPowerRole; sim.currentDataRole = newDataRole; updatePortsLocked(pw, null); - } else if (mProxy != null) { + } else if (mUsbPortHal != null) { if (currentMode != newMode) { // Changing the mode will have the side-effect of also changing // the power and data roles but it might take some time to apply @@ -485,44 +591,37 @@ public class UsbPortManager { logAndPrint(Log.ERROR, pw, "Trying to set the USB port mode: " + "portId=" + portId + ", newMode=" + UsbPort.modeToString(newMode)); - PortRole newRole = new PortRole(); - newRole.type = PortRoleType.MODE; - newRole.role = newMode; try { - mProxy.switchRole(portId, newRole); - } catch (RemoteException e) { + mUsbPortHal.switchMode(portId, toHalUsbMode(newMode), ++mTransactionId); + } catch (Exception e) { logAndPrintException(pw, "Failed to set the USB port mode: " + "portId=" + portId - + ", newMode=" + UsbPort.modeToString(newRole.role), e); + + ", newMode=" + UsbPort.modeToString(newMode), e); } } else { // Change power and data role independently as needed. if (currentPowerRole != newPowerRole) { - PortRole newRole = new PortRole(); - newRole.type = PortRoleType.POWER_ROLE; - newRole.role = newPowerRole; try { - mProxy.switchRole(portId, newRole); - } catch (RemoteException e) { + mUsbPortHal.switchPowerRole(portId, toHalUsbPowerRole(newPowerRole), + ++mTransactionId); + } catch (Exception e) { logAndPrintException(pw, "Failed to set the USB port power role: " + "portId=" + portId + ", newPowerRole=" + UsbPort.powerRoleToString - (newRole.role), + (newPowerRole), e); return; } } if (currentDataRole != newDataRole) { - PortRole newRole = new PortRole(); - newRole.type = PortRoleType.DATA_ROLE; - newRole.role = newDataRole; try { - mProxy.switchRole(portId, newRole); - } catch (RemoteException e) { + mUsbPortHal.switchDataRole(portId, toHalUsbDataRole(newDataRole), + ++mTransactionId); + } catch (Exception e) { logAndPrintException(pw, "Failed to set the USB port data role: " + "portId=" + portId - + ", newDataRole=" + UsbPort.dataRoleToString(newRole - .role), + + ", newDataRole=" + UsbPort.dataRoleToString + (newDataRole), e); } } @@ -531,6 +630,15 @@ public class UsbPortManager { } } + public void updatePorts(ArrayList<RawPortInfo> newPortInfo) { + Message message = mHandler.obtainMessage(); + Bundle bundle = new Bundle(); + bundle.putParcelableArrayList(PORT_INFO, newPortInfo); + message.what = MSG_UPDATE_PORTS; + message.setData(bundle); + mHandler.sendMessage(message); + } + public void addSimulatedPort(String portId, int supportedModes, IndentingPrintWriter pw) { synchronized (mLock) { if (mSimulatedPorts.containsKey(portId)) { @@ -662,191 +770,12 @@ public class UsbPortManager { portInfo.dump(dump, "usb_ports", UsbPortManagerProto.USB_PORTS); } - dump.write("enable_usb_data_signaling", UsbPortManagerProto.ENABLE_USB_DATA_SIGNALING, - mEnableUsbDataSignaling); + dump.write("usb_hal_version", UsbPortManagerProto.HAL_VERSION, getUsbHalVersion()); } dump.end(token); } - private static class HALCallback extends IUsbCallback.Stub { - public IndentingPrintWriter pw; - public UsbPortManager portManager; - - HALCallback(IndentingPrintWriter pw, UsbPortManager portManager) { - this.pw = pw; - this.portManager = portManager; - } - - public void notifyPortStatusChange( - ArrayList<android.hardware.usb.V1_0.PortStatus> currentPortStatus, int retval) { - if (!portManager.mSystemReady) { - return; - } - - if (retval != Status.SUCCESS) { - logAndPrint(Log.ERROR, pw, "port status enquiry failed"); - return; - } - - ArrayList<RawPortInfo> newPortInfo = new ArrayList<>(); - - for (android.hardware.usb.V1_0.PortStatus current : currentPortStatus) { - RawPortInfo temp = new RawPortInfo(current.portName, - current.supportedModes, CONTAMINANT_PROTECTION_NONE, - current.currentMode, - current.canChangeMode, current.currentPowerRole, - current.canChangePowerRole, - current.currentDataRole, current.canChangeDataRole, - false, CONTAMINANT_PROTECTION_NONE, - false, CONTAMINANT_DETECTION_NOT_SUPPORTED); - newPortInfo.add(temp); - logAndPrint(Log.INFO, pw, "ClientCallback V1_0: " + current.portName); - } - - Message message = portManager.mHandler.obtainMessage(); - Bundle bundle = new Bundle(); - bundle.putParcelableArrayList(PORT_INFO, newPortInfo); - message.what = MSG_UPDATE_PORTS; - message.setData(bundle); - portManager.mHandler.sendMessage(message); - } - - - public void notifyPortStatusChange_1_1(ArrayList<PortStatus_1_1> currentPortStatus, - int retval) { - if (!portManager.mSystemReady) { - return; - } - - if (retval != Status.SUCCESS) { - logAndPrint(Log.ERROR, pw, "port status enquiry failed"); - return; - } - - ArrayList<RawPortInfo> newPortInfo = new ArrayList<>(); - - int numStatus = currentPortStatus.size(); - for (int i = 0; i < numStatus; i++) { - PortStatus_1_1 current = currentPortStatus.get(i); - RawPortInfo temp = new RawPortInfo(current.status.portName, - current.supportedModes, CONTAMINANT_PROTECTION_NONE, - current.currentMode, - current.status.canChangeMode, current.status.currentPowerRole, - current.status.canChangePowerRole, - current.status.currentDataRole, current.status.canChangeDataRole, - false, CONTAMINANT_PROTECTION_NONE, - false, CONTAMINANT_DETECTION_NOT_SUPPORTED); - newPortInfo.add(temp); - logAndPrint(Log.INFO, pw, "ClientCallback V1_1: " + current.status.portName); - } - - Message message = portManager.mHandler.obtainMessage(); - Bundle bundle = new Bundle(); - bundle.putParcelableArrayList(PORT_INFO, newPortInfo); - message.what = MSG_UPDATE_PORTS; - message.setData(bundle); - portManager.mHandler.sendMessage(message); - } - - public void notifyPortStatusChange_1_2( - ArrayList<PortStatus> currentPortStatus, int retval) { - if (!portManager.mSystemReady) { - return; - } - - if (retval != Status.SUCCESS) { - logAndPrint(Log.ERROR, pw, "port status enquiry failed"); - return; - } - - ArrayList<RawPortInfo> newPortInfo = new ArrayList<>(); - - int numStatus = currentPortStatus.size(); - for (int i = 0; i < numStatus; i++) { - PortStatus current = currentPortStatus.get(i); - RawPortInfo temp = new RawPortInfo(current.status_1_1.status.portName, - current.status_1_1.supportedModes, - current.supportedContaminantProtectionModes, - current.status_1_1.currentMode, - current.status_1_1.status.canChangeMode, - current.status_1_1.status.currentPowerRole, - current.status_1_1.status.canChangePowerRole, - current.status_1_1.status.currentDataRole, - current.status_1_1.status.canChangeDataRole, - current.supportsEnableContaminantPresenceProtection, - current.contaminantProtectionStatus, - current.supportsEnableContaminantPresenceDetection, - current.contaminantDetectionStatus); - newPortInfo.add(temp); - logAndPrint(Log.INFO, pw, "ClientCallback V1_2: " - + current.status_1_1.status.portName); - } - - Message message = portManager.mHandler.obtainMessage(); - Bundle bundle = new Bundle(); - bundle.putParcelableArrayList(PORT_INFO, newPortInfo); - message.what = MSG_UPDATE_PORTS; - message.setData(bundle); - portManager.mHandler.sendMessage(message); - } - - public void notifyRoleSwitchStatus(String portName, PortRole role, int retval) { - if (retval == Status.SUCCESS) { - logAndPrint(Log.INFO, pw, portName + " role switch successful"); - } else { - logAndPrint(Log.ERROR, pw, portName + " role switch failed"); - } - } - } - - final class DeathRecipient implements HwBinder.DeathRecipient { - public IndentingPrintWriter pw; - - DeathRecipient(IndentingPrintWriter pw) { - this.pw = pw; - } - - @Override - public void serviceDied(long cookie) { - if (cookie == USB_HAL_DEATH_COOKIE) { - logAndPrint(Log.ERROR, pw, "Usb hal service died cookie: " + cookie); - synchronized (mLock) { - mProxy = null; - } - } - } - } - - final class ServiceNotification extends IServiceNotification.Stub { - @Override - public void onRegistration(String fqName, String name, boolean preexisting) { - logAndPrint(Log.INFO, null, "Usb hal service started " + fqName + " " + name); - connectToProxy(null); - } - } - - private void connectToProxy(IndentingPrintWriter pw) { - synchronized (mLock) { - if (mProxy != null) { - return; - } - - try { - mProxy = IUsb.getService(); - mProxy.linkToDeath(new DeathRecipient(pw), USB_HAL_DEATH_COOKIE); - mProxy.setCallback(mHALCallback); - mProxy.queryPortStatus(); - updateUsbHalVersion(); - } catch (NoSuchElementException e) { - logAndPrintException(pw, "connectToProxy: usb hal service not found." - + " Did the service fail to start?", e); - } catch (RemoteException e) { - logAndPrintException(pw, "connectToProxy: usb hal service not responding", e); - } - } - } - /** * Simulated ports directly add the new roles to mSimulatedPorts before calling. * USB hal callback populates and sends the newPortInfo. @@ -869,7 +798,10 @@ public class UsbPortManager { portInfo.supportsEnableContaminantPresenceProtection, portInfo.contaminantProtectionStatus, portInfo.supportsEnableContaminantPresenceDetection, - portInfo.contaminantDetectionStatus, pw); + portInfo.contaminantDetectionStatus, + portInfo.usbDataStatus, + portInfo.powerTransferLimited, + portInfo.powerBrickStatus, pw); } } else { for (RawPortInfo currentPortInfo : newPortInfo) { @@ -881,7 +813,10 @@ public class UsbPortManager { currentPortInfo.supportsEnableContaminantPresenceProtection, currentPortInfo.contaminantProtectionStatus, currentPortInfo.supportsEnableContaminantPresenceDetection, - currentPortInfo.contaminantDetectionStatus, pw); + currentPortInfo.contaminantDetectionStatus, + currentPortInfo.usbDataStatus, + currentPortInfo.powerTransferLimited, + currentPortInfo.powerBrickStatus, pw); } } @@ -917,6 +852,9 @@ public class UsbPortManager { int contaminantProtectionStatus, boolean supportsEnableContaminantPresenceDetection, int contaminantDetectionStatus, + int[] usbDataStatus, + boolean powerTransferLimited, + int powerBrickStatus, IndentingPrintWriter pw) { // Only allow mode switch capability for dual role ports. // Validate that the current mode matches the supported modes we expect. @@ -975,7 +913,8 @@ public class UsbPortManager { currentPowerRole, canChangePowerRole, currentDataRole, canChangeDataRole, supportedRoleCombinations, contaminantProtectionStatus, - contaminantDetectionStatus); + contaminantDetectionStatus, usbDataStatus, + powerTransferLimited, powerBrickStatus); mPorts.put(portId, portInfo); } else { // Validate that ports aren't changing definition out from under us. @@ -1012,7 +951,8 @@ public class UsbPortManager { currentPowerRole, canChangePowerRole, currentDataRole, canChangeDataRole, supportedRoleCombinations, contaminantProtectionStatus, - contaminantDetectionStatus)) { + contaminantDetectionStatus, usbDataStatus, + powerTransferLimited, powerBrickStatus)) { portInfo.mDisposition = PortInfo.DISPOSITION_CHANGED; } else { portInfo.mDisposition = PortInfo.DISPOSITION_READY; @@ -1034,6 +974,7 @@ public class UsbPortManager { private void handlePortChangedLocked(PortInfo portInfo, IndentingPrintWriter pw) { logAndPrint(Log.INFO, pw, "USB port changed: " + portInfo); enableContaminantDetectionIfNeeded(portInfo, pw); + disableLimitPowerTransferIfNeeded(portInfo, pw); handlePortLocked(portInfo, pw); } @@ -1090,6 +1031,19 @@ public class UsbPortManager { } } + private void disableLimitPowerTransferIfNeeded(PortInfo portInfo, IndentingPrintWriter pw) { + if (!mConnected.containsKey(portInfo.mUsbPort.getId())) { + return; + } + + if (mConnected.get(portInfo.mUsbPort.getId()) + && !portInfo.mUsbPortStatus.isConnected() + && portInfo.mUsbPortStatus.isPowerTransferLimited()) { + // Relax enableLimitPowerTransfer upon unplug. + enableLimitPowerTransfer(portInfo.mUsbPort.getId(), false, ++mTransactionId, null, pw); + } + } + private void logToStatsd(PortInfo portInfo, IndentingPrintWriter pw) { // Port is removed if (portInfo.mUsbPortStatus == null) { @@ -1141,14 +1095,14 @@ public class UsbPortManager { } } - private static void logAndPrint(int priority, IndentingPrintWriter pw, String msg) { + public static void logAndPrint(int priority, IndentingPrintWriter pw, String msg) { Slog.println(priority, TAG, msg); if (pw != null) { pw.println(msg); } } - private static void logAndPrintException(IndentingPrintWriter pw, String msg, Exception e) { + public static void logAndPrintException(IndentingPrintWriter pw, String msg, Exception e) { Slog.e(TAG, msg, e); if (pw != null) { pw.println(msg + e); @@ -1179,7 +1133,7 @@ public class UsbPortManager { /** * Describes a USB port. */ - private static final class PortInfo { + public static final class PortInfo { public static final int DISPOSITION_ADDED = 0; public static final int DISPOSITION_CHANGED = 1; public static final int DISPOSITION_READY = 2; @@ -1224,7 +1178,9 @@ public class UsbPortManager { != supportedRoleCombinations) { mUsbPortStatus = new UsbPortStatus(currentMode, currentPowerRole, currentDataRole, supportedRoleCombinations, UsbPortStatus.CONTAMINANT_PROTECTION_NONE, - UsbPortStatus.CONTAMINANT_DETECTION_NOT_SUPPORTED); + UsbPortStatus.CONTAMINANT_DETECTION_NOT_SUPPORTED, + new int[]{UsbPortStatus.USB_DATA_STATUS_UNKNOWN}, false, + UsbPortStatus.POWER_BRICK_STATUS_UNKNOWN); dispositionChanged = true; } @@ -1239,11 +1195,31 @@ public class UsbPortManager { return dispositionChanged; } + private boolean dataStatusEquals(int[] dataStatusL, int[] dataStatusR) { + if (dataStatusL == null && dataStatusR == null) { + return true; + } + if ((dataStatusL == null && dataStatusR != null) + || (dataStatusL != null && dataStatusR == null)) { + return false; + } + if (dataStatusL.length != dataStatusR.length) { + return false; + } + for (int i = 0; i < dataStatusL.length; i++) { + if (dataStatusL[i] != dataStatusR[i]) { + return false; + } + } + return true; + } + public boolean setStatus(int currentMode, boolean canChangeMode, int currentPowerRole, boolean canChangePowerRole, int currentDataRole, boolean canChangeDataRole, int supportedRoleCombinations, int contaminantProtectionStatus, - int contaminantDetectionStatus) { + int contaminantDetectionStatus, int[] usbDataStatus, + boolean powerTransferLimited, int powerBrickStatus) { boolean dispositionChanged = false; mCanChangeMode = canChangeMode; @@ -1258,10 +1234,16 @@ public class UsbPortManager { || mUsbPortStatus.getContaminantProtectionStatus() != contaminantProtectionStatus || mUsbPortStatus.getContaminantDetectionStatus() - != contaminantDetectionStatus) { + != contaminantDetectionStatus + || !dataStatusEquals(mUsbPortStatus.getUsbDataStatus(), usbDataStatus) + || mUsbPortStatus.isPowerTransferLimited() + != powerTransferLimited + || mUsbPortStatus.getPowerBrickStatus() + != powerBrickStatus) { mUsbPortStatus = new UsbPortStatus(currentMode, currentPowerRole, currentDataRole, supportedRoleCombinations, contaminantProtectionStatus, - contaminantDetectionStatus); + contaminantDetectionStatus, usbDataStatus, + powerTransferLimited, powerBrickStatus); dispositionChanged = true; } @@ -1290,7 +1272,6 @@ public class UsbPortManager { UsbPortInfoProto.CONNECTED_AT_MILLIS, mConnectedAtMillis); dump.write("last_connect_duration_millis", UsbPortInfoProto.LAST_CONNECT_DURATION_MILLIS, mLastConnectDurationMillis); - dump.end(token); } @@ -1304,115 +1285,4 @@ public class UsbPortManager { + ", lastConnectDurationMillis=" + mLastConnectDurationMillis; } } - - /** - * Used for storing the raw data from the kernel - * Values of the member variables mocked directly incase of emulation. - */ - private static final class RawPortInfo implements Parcelable { - public final String portId; - public final int supportedModes; - public final int supportedContaminantProtectionModes; - public int currentMode; - public boolean canChangeMode; - public int currentPowerRole; - public boolean canChangePowerRole; - public int currentDataRole; - public boolean canChangeDataRole; - public boolean supportsEnableContaminantPresenceProtection; - public int contaminantProtectionStatus; - public boolean supportsEnableContaminantPresenceDetection; - public int contaminantDetectionStatus; - - RawPortInfo(String portId, int supportedModes) { - this.portId = portId; - this.supportedModes = supportedModes; - this.supportedContaminantProtectionModes = UsbPortStatus.CONTAMINANT_PROTECTION_NONE; - this.supportsEnableContaminantPresenceProtection = false; - this.contaminantProtectionStatus = UsbPortStatus.CONTAMINANT_PROTECTION_NONE; - this.supportsEnableContaminantPresenceDetection = false; - this.contaminantDetectionStatus = UsbPortStatus.CONTAMINANT_DETECTION_NOT_SUPPORTED; - } - - RawPortInfo(String portId, int supportedModes, int supportedContaminantProtectionModes, - int currentMode, boolean canChangeMode, - int currentPowerRole, boolean canChangePowerRole, - int currentDataRole, boolean canChangeDataRole, - boolean supportsEnableContaminantPresenceProtection, - int contaminantProtectionStatus, - boolean supportsEnableContaminantPresenceDetection, - int contaminantDetectionStatus) { - this.portId = portId; - this.supportedModes = supportedModes; - this.supportedContaminantProtectionModes = supportedContaminantProtectionModes; - this.currentMode = currentMode; - this.canChangeMode = canChangeMode; - this.currentPowerRole = currentPowerRole; - this.canChangePowerRole = canChangePowerRole; - this.currentDataRole = currentDataRole; - this.canChangeDataRole = canChangeDataRole; - this.supportsEnableContaminantPresenceProtection = - supportsEnableContaminantPresenceProtection; - this.contaminantProtectionStatus = contaminantProtectionStatus; - this.supportsEnableContaminantPresenceDetection = - supportsEnableContaminantPresenceDetection; - this.contaminantDetectionStatus = contaminantDetectionStatus; - } - - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeString(portId); - dest.writeInt(supportedModes); - dest.writeInt(supportedContaminantProtectionModes); - dest.writeInt(currentMode); - dest.writeByte((byte) (canChangeMode ? 1 : 0)); - dest.writeInt(currentPowerRole); - dest.writeByte((byte) (canChangePowerRole ? 1 : 0)); - dest.writeInt(currentDataRole); - dest.writeByte((byte) (canChangeDataRole ? 1 : 0)); - dest.writeBoolean(supportsEnableContaminantPresenceProtection); - dest.writeInt(contaminantProtectionStatus); - dest.writeBoolean(supportsEnableContaminantPresenceDetection); - dest.writeInt(contaminantDetectionStatus); - } - - public static final Parcelable.Creator<RawPortInfo> CREATOR = - new Parcelable.Creator<RawPortInfo>() { - @Override - public RawPortInfo createFromParcel(Parcel in) { - String id = in.readString(); - int supportedModes = in.readInt(); - int supportedContaminantProtectionModes = in.readInt(); - int currentMode = in.readInt(); - boolean canChangeMode = in.readByte() != 0; - int currentPowerRole = in.readInt(); - boolean canChangePowerRole = in.readByte() != 0; - int currentDataRole = in.readInt(); - boolean canChangeDataRole = in.readByte() != 0; - boolean supportsEnableContaminantPresenceProtection = in.readBoolean(); - int contaminantProtectionStatus = in.readInt(); - boolean supportsEnableContaminantPresenceDetection = in.readBoolean(); - int contaminantDetectionStatus = in.readInt(); - return new RawPortInfo(id, supportedModes, - supportedContaminantProtectionModes, currentMode, canChangeMode, - currentPowerRole, canChangePowerRole, - currentDataRole, canChangeDataRole, - supportsEnableContaminantPresenceProtection, - contaminantProtectionStatus, - supportsEnableContaminantPresenceDetection, - contaminantDetectionStatus); - } - - @Override - public RawPortInfo[] newArray(int size) { - return new RawPortInfo[size]; - } - }; - } } diff --git a/services/usb/java/com/android/server/usb/UsbService.java b/services/usb/java/com/android/server/usb/UsbService.java index 3d3538d7ae49..88ffc7d613e2 100644 --- a/services/usb/java/com/android/server/usb/UsbService.java +++ b/services/usb/java/com/android/server/usb/UsbService.java @@ -16,6 +16,7 @@ package com.android.server.usb; +import static android.hardware.usb.UsbOperationInternal.USB_OPERATION_ERROR_INTERNAL; import static android.hardware.usb.UsbPortStatus.DATA_ROLE_DEVICE; import static android.hardware.usb.UsbPortStatus.DATA_ROLE_HOST; import static android.hardware.usb.UsbPortStatus.MODE_DFP; @@ -35,6 +36,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; import android.hardware.usb.IUsbManager; +import android.hardware.usb.IUsbOperationInternal; import android.hardware.usb.ParcelableUsbPort; import android.hardware.usb.UsbAccessory; import android.hardware.usb.UsbDevice; @@ -44,6 +46,7 @@ import android.hardware.usb.UsbPortStatus; import android.os.Binder; import android.os.Bundle; import android.os.ParcelFileDescriptor; +import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; import android.service.usb.UsbServiceDumpProto; @@ -731,6 +734,28 @@ public class UsbService extends IUsbManager.Stub { } @Override + public void enableLimitPowerTransfer(String portId, boolean limit, int operationId, + IUsbOperationInternal callback) { + Objects.requireNonNull(portId, "portId must not be null. opID:" + operationId); + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); + + final long ident = Binder.clearCallingIdentity(); + try { + if (mPortManager != null) { + mPortManager.enableLimitPowerTransfer(portId, limit, operationId, callback, null); + } else { + try { + callback.onOperationComplete(USB_OPERATION_ERROR_INTERNAL); + } catch (RemoteException e) { + Slog.e(TAG, "enableLimitPowerTransfer: Failed to call onOperationComplete", e); + } + } + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + @Override public void enableContaminantDetection(String portId, boolean enable) { Objects.requireNonNull(portId, "portId must not be null"); mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); @@ -762,15 +787,52 @@ public class UsbService extends IUsbManager.Stub { } @Override - public boolean enableUsbDataSignal(boolean enable) { + public boolean enableUsbData(String portId, boolean enable, int operationId, + IUsbOperationInternal callback) { + Objects.requireNonNull(portId, "enableUsbData: portId must not be null. opId:" + + operationId); + Objects.requireNonNull(callback, "enableUsbData: callback must not be null. opId:" + + operationId); mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); + final long ident = Binder.clearCallingIdentity(); + boolean wait; + try { + if (mPortManager != null) { + wait = mPortManager.enableUsbData(portId, enable, operationId, callback, null); + } else { + wait = false; + try { + callback.onOperationComplete(USB_OPERATION_ERROR_INTERNAL); + } catch (RemoteException e) { + Slog.e(TAG, "enableUsbData: Failed to call onOperationComplete", e); + } + } + } finally { + Binder.restoreCallingIdentity(ident); + } + return wait; + } + @Override + public void enableUsbDataWhileDocked(String portId, int operationId, + IUsbOperationInternal callback) { + Objects.requireNonNull(portId, "enableUsbDataWhileDocked: portId must not be null. opId:" + + operationId); + Objects.requireNonNull(callback, + "enableUsbDataWhileDocked: callback must not be null. opId:" + + operationId); + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); final long ident = Binder.clearCallingIdentity(); + boolean wait; try { if (mPortManager != null) { - return mPortManager.enableUsbDataSignal(enable); + mPortManager.enableUsbDataWhileDocked(portId, operationId, callback, null); } else { - return false; + try { + callback.onOperationComplete(USB_OPERATION_ERROR_INTERNAL); + } catch (RemoteException e) { + Slog.e(TAG, "enableUsbData: Failed to call onOperationComplete", e); + } } } finally { Binder.restoreCallingIdentity(ident); diff --git a/services/usb/java/com/android/server/usb/UsbUniversalMidiDevice.java b/services/usb/java/com/android/server/usb/UsbUniversalMidiDevice.java new file mode 100644 index 000000000000..db0c80f189d3 --- /dev/null +++ b/services/usb/java/com/android/server/usb/UsbUniversalMidiDevice.java @@ -0,0 +1,469 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.usb; + +import android.content.Context; +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbDeviceConnection; +import android.hardware.usb.UsbEndpoint; +import android.hardware.usb.UsbManager; +import android.media.midi.MidiDeviceInfo; +import android.media.midi.MidiDeviceServer; +import android.media.midi.MidiDeviceStatus; +import android.media.midi.MidiManager; +import android.media.midi.MidiReceiver; +import android.os.Bundle; +import android.util.Log; + +import com.android.internal.midi.MidiEventScheduler; +import com.android.internal.midi.MidiEventScheduler.MidiEvent; +import com.android.server.usb.descriptors.UsbDescriptorParser; +import com.android.server.usb.descriptors.UsbEndpointDescriptor; +import com.android.server.usb.descriptors.UsbInterfaceDescriptor; +import com.android.server.usb.descriptors.UsbMidiBlockParser; + +import libcore.io.IoUtils; + +import java.io.Closeable; +import java.io.IOException; +import java.util.ArrayList; + +/** + * A MIDI device that opens device connections to MIDI 2.0 endpoints. + */ +public final class UsbUniversalMidiDevice implements Closeable { + private static final String TAG = "UsbUniversalMidiDevice"; + private static final boolean DEBUG = false; + + private Context mContext; + private UsbDevice mUsbDevice; + private UsbDescriptorParser mParser; + private ArrayList<UsbInterfaceDescriptor> mUsbInterfaces; + + // USB outputs are MIDI inputs + private final InputReceiverProxy[] mMidiInputPortReceivers; + private final int mNumInputs; + private final int mNumOutputs; + + private MidiDeviceServer mServer; + + // event schedulers for each input port of the physical device + private MidiEventScheduler[] mEventSchedulers; + + private ArrayList<UsbDeviceConnection> mUsbDeviceConnections; + private ArrayList<ArrayList<UsbEndpoint>> mInputUsbEndpoints; + private ArrayList<ArrayList<UsbEndpoint>> mOutputUsbEndpoints; + + private UsbMidiBlockParser mMidiBlockParser = new UsbMidiBlockParser(); + private int mDefaultMidiProtocol = MidiDeviceInfo.PROTOCOL_UMP_MIDI_1_0_UP_TO_64_BITS; + + private final Object mLock = new Object(); + private boolean mIsOpen; + + private final MidiDeviceServer.Callback mCallback = new MidiDeviceServer.Callback() { + + @Override + public void onDeviceStatusChanged(MidiDeviceServer server, MidiDeviceStatus status) { + MidiDeviceInfo deviceInfo = status.getDeviceInfo(); + int numInputPorts = deviceInfo.getInputPortCount(); + int numOutputPorts = deviceInfo.getOutputPortCount(); + boolean hasOpenPorts = false; + + for (int i = 0; i < numInputPorts; i++) { + if (status.isInputPortOpen(i)) { + hasOpenPorts = true; + break; + } + } + + if (!hasOpenPorts) { + for (int i = 0; i < numOutputPorts; i++) { + if (status.getOutputPortOpenCount(i) > 0) { + hasOpenPorts = true; + break; + } + } + } + + synchronized (mLock) { + if (hasOpenPorts && !mIsOpen) { + openLocked(); + } else if (!hasOpenPorts && mIsOpen) { + closeLocked(); + } + } + } + + @Override + public void onClose() { + } + }; + + // This class acts as a proxy for our MidiEventScheduler receivers, which do not exist + // until the device has active clients + private static final class InputReceiverProxy extends MidiReceiver { + private MidiReceiver mReceiver; + + @Override + public void onSend(byte[] msg, int offset, int count, long timestamp) throws IOException { + MidiReceiver receiver = mReceiver; + if (receiver != null) { + receiver.send(msg, offset, count, timestamp); + } + } + + public void setReceiver(MidiReceiver receiver) { + mReceiver = receiver; + } + + @Override + public void onFlush() throws IOException { + MidiReceiver receiver = mReceiver; + if (receiver != null) { + receiver.flush(); + } + } + } + + /** + * Creates an UsbUniversalMidiDevice based on the input parameters. Read/Write streams + * will be created individually as some devices don't have the same number of + * inputs and outputs. + */ + public static UsbUniversalMidiDevice create(Context context, UsbDevice usbDevice, + UsbDescriptorParser parser) { + UsbUniversalMidiDevice midiDevice = new UsbUniversalMidiDevice(usbDevice, parser); + if (!midiDevice.register(context)) { + IoUtils.closeQuietly(midiDevice); + Log.e(TAG, "createDeviceServer failed"); + return null; + } + return midiDevice; + } + + private UsbUniversalMidiDevice(UsbDevice usbDevice, UsbDescriptorParser parser) { + mUsbDevice = usbDevice; + mParser = parser; + + mUsbInterfaces = parser.findUniversalMidiInterfaceDescriptors(); + + int numInputs = 0; + int numOutputs = 0; + + for (int interfaceIndex = 0; interfaceIndex < mUsbInterfaces.size(); interfaceIndex++) { + UsbInterfaceDescriptor interfaceDescriptor = mUsbInterfaces.get(interfaceIndex); + for (int endpointIndex = 0; endpointIndex < interfaceDescriptor.getNumEndpoints(); + endpointIndex++) { + UsbEndpointDescriptor endpoint = + interfaceDescriptor.getEndpointDescriptor(endpointIndex); + // 0 is output, 1 << 7 is input. + if (endpoint.getDirection() == 0) { + numOutputs++; + } else { + numInputs++; + } + } + } + + mNumInputs = numInputs; + mNumOutputs = numOutputs; + + Log.d(TAG, "Created UsbUniversalMidiDevice with " + numInputs + " inputs and " + + numOutputs + " outputs"); + + // Create MIDI port receivers based on the number of output ports. The + // output of USB is the input of MIDI. + mMidiInputPortReceivers = new InputReceiverProxy[numOutputs]; + for (int port = 0; port < numOutputs; port++) { + mMidiInputPortReceivers[port] = new InputReceiverProxy(); + } + } + + private int calculateDefaultMidiProtocol() { + UsbManager manager = mContext.getSystemService(UsbManager.class); + + for (int interfaceIndex = 0; interfaceIndex < mUsbInterfaces.size(); interfaceIndex++) { + UsbInterfaceDescriptor interfaceDescriptor = mUsbInterfaces.get(interfaceIndex); + boolean doesInterfaceContainInput = false; + boolean doesInterfaceContainOutput = false; + for (int endpointIndex = 0; (endpointIndex < interfaceDescriptor.getNumEndpoints()) + && !(doesInterfaceContainInput && doesInterfaceContainOutput); + endpointIndex++) { + UsbEndpointDescriptor endpoint = + interfaceDescriptor.getEndpointDescriptor(endpointIndex); + // 0 is output, 1 << 7 is input. + if (endpoint.getDirection() == 0) { + doesInterfaceContainOutput = true; + } else { + doesInterfaceContainInput = true; + } + } + + // Intentionally open the device connection to query the default MIDI type for + // a connection with both the input and output set. + if (doesInterfaceContainInput + && doesInterfaceContainOutput) { + UsbDeviceConnection connection = manager.openDevice(mUsbDevice); + if (!connection.claimInterface(interfaceDescriptor.toAndroid(mParser), true)) { + Log.d(TAG, "Can't claim control interface"); + continue; + } + int defaultMidiProtocol = mMidiBlockParser.calculateMidiType(connection, + interfaceDescriptor.getInterfaceNumber(), + interfaceDescriptor.getAlternateSetting()); + + connection.close(); + return defaultMidiProtocol; + } + } + + Log.d(TAG, "Cannot find interface with both input and output endpoints"); + return MidiDeviceInfo.PROTOCOL_UMP_MIDI_1_0_UP_TO_64_BITS; + } + + private boolean openLocked() { + UsbManager manager = mContext.getSystemService(UsbManager.class); + + mUsbDeviceConnections = new ArrayList<UsbDeviceConnection>(mUsbInterfaces.size()); + mInputUsbEndpoints = new ArrayList<ArrayList<UsbEndpoint>>(mUsbInterfaces.size()); + mOutputUsbEndpoints = new ArrayList<ArrayList<UsbEndpoint>>(mUsbInterfaces.size()); + + for (int interfaceIndex = 0; interfaceIndex < mUsbInterfaces.size(); interfaceIndex++) { + ArrayList<UsbEndpoint> inputEndpoints = new ArrayList<UsbEndpoint>(); + ArrayList<UsbEndpoint> outputEndpoints = new ArrayList<UsbEndpoint>(); + UsbInterfaceDescriptor interfaceDescriptor = mUsbInterfaces.get(interfaceIndex); + for (int endpointIndex = 0; endpointIndex < interfaceDescriptor.getNumEndpoints(); + endpointIndex++) { + UsbEndpointDescriptor endpoint = + interfaceDescriptor.getEndpointDescriptor(endpointIndex); + // 0 is output, 1 << 7 is input. + if (endpoint.getDirection() == 0) { + outputEndpoints.add(endpoint.toAndroid(mParser)); + } else { + inputEndpoints.add(endpoint.toAndroid(mParser)); + } + } + if (!outputEndpoints.isEmpty() || !inputEndpoints.isEmpty()) { + UsbDeviceConnection connection = manager.openDevice(mUsbDevice); + connection.setInterface(interfaceDescriptor.toAndroid(mParser)); + connection.claimInterface(interfaceDescriptor.toAndroid(mParser), true); + mUsbDeviceConnections.add(connection); + mInputUsbEndpoints.add(inputEndpoints); + mOutputUsbEndpoints.add(outputEndpoints); + } + } + + mEventSchedulers = new MidiEventScheduler[mNumOutputs]; + + for (int i = 0; i < mNumOutputs; i++) { + MidiEventScheduler scheduler = new MidiEventScheduler(); + mEventSchedulers[i] = scheduler; + mMidiInputPortReceivers[i].setReceiver(scheduler.getReceiver()); + } + + final MidiReceiver[] outputReceivers = mServer.getOutputPortReceivers(); + + // Create input thread for each input port of the physical device + int portNumber = 0; + for (int connectionIndex = 0; connectionIndex < mInputUsbEndpoints.size(); + connectionIndex++) { + for (int endpointIndex = 0; + endpointIndex < mInputUsbEndpoints.get(connectionIndex).size(); + endpointIndex++) { + final UsbDeviceConnection connectionF = mUsbDeviceConnections.get(connectionIndex); + final UsbEndpoint epF = mInputUsbEndpoints.get(connectionIndex).get(endpointIndex); + final int portF = portNumber; + + new Thread("UsbUniversalMidiDevice input thread " + portF) { + @Override + public void run() { + byte[] inputBuffer = new byte[epF.getMaxPacketSize()]; + try { + while (true) { + // Record time of event immediately after waking. + long timestamp = System.nanoTime(); + synchronized (mLock) { + if (!mIsOpen) break; + + int nRead = connectionF.bulkTransfer(epF, inputBuffer, + inputBuffer.length, 0); + + // For USB, each 32 bit word of a UMP is + // sent with the least significant byte first. + swapEndiannessPerWord(inputBuffer, inputBuffer.length); + + if (nRead > 0) { + if (DEBUG) { + logByteArray("Input ", inputBuffer, 0, + nRead); + } + outputReceivers[portF].send(inputBuffer, 0, nRead, + timestamp); + } + } + } + } catch (IOException e) { + Log.d(TAG, "reader thread exiting"); + } + Log.d(TAG, "input thread exit"); + } + }.start(); + + portNumber++; + } + } + + // Create output thread for each output port of the physical device + portNumber = 0; + for (int connectionIndex = 0; connectionIndex < mOutputUsbEndpoints.size(); + connectionIndex++) { + for (int endpointIndex = 0; + endpointIndex < mOutputUsbEndpoints.get(connectionIndex).size(); + endpointIndex++) { + final UsbDeviceConnection connectionF = mUsbDeviceConnections.get(connectionIndex); + final UsbEndpoint epF = + mOutputUsbEndpoints.get(connectionIndex).get(endpointIndex); + final int portF = portNumber; + final MidiEventScheduler eventSchedulerF = mEventSchedulers[portF]; + + new Thread("UsbUniversalMidiDevice output thread " + portF) { + @Override + public void run() { + while (true) { + MidiEvent event; + try { + event = (MidiEvent) eventSchedulerF.waitNextEvent(); + } catch (InterruptedException e) { + // try again + continue; + } + if (event == null) { + break; + } + + // For USB, each 32 bit word of a UMP is + // sent with the least significant byte first. + swapEndiannessPerWord(event.data, event.count); + + if (DEBUG) { + logByteArray("Output ", event.data, 0, + event.count); + } + connectionF.bulkTransfer(epF, event.data, event.count, 0); + eventSchedulerF.addEventToPool(event); + } + Log.d(TAG, "output thread exit"); + } + }.start(); + + portNumber++; + } + } + + mIsOpen = true; + return true; + } + + private boolean register(Context context) { + mContext = context; + MidiManager midiManager = context.getSystemService(MidiManager.class); + if (midiManager == null) { + Log.e(TAG, "No MidiManager in UsbUniversalMidiDevice.create()"); + return false; + } + + mDefaultMidiProtocol = calculateDefaultMidiProtocol(); + + Bundle properties = new Bundle(); + String manufacturer = mUsbDevice.getManufacturerName(); + String product = mUsbDevice.getProductName(); + String version = mUsbDevice.getVersion(); + String name; + if (manufacturer == null || manufacturer.isEmpty()) { + name = product; + } else if (product == null || product.isEmpty()) { + name = manufacturer; + } else { + name = manufacturer + " " + product + " MIDI 2.0"; + } + properties.putString(MidiDeviceInfo.PROPERTY_NAME, name); + properties.putString(MidiDeviceInfo.PROPERTY_MANUFACTURER, manufacturer); + properties.putString(MidiDeviceInfo.PROPERTY_PRODUCT, product); + properties.putString(MidiDeviceInfo.PROPERTY_VERSION, version); + properties.putString(MidiDeviceInfo.PROPERTY_SERIAL_NUMBER, + mUsbDevice.getSerialNumber()); + properties.putParcelable(MidiDeviceInfo.PROPERTY_USB_DEVICE, mUsbDevice); + + mServer = midiManager.createDeviceServer(mMidiInputPortReceivers, mNumInputs, + null, null, properties, MidiDeviceInfo.TYPE_USB, mDefaultMidiProtocol, mCallback); + if (mServer == null) { + return false; + } + + return true; + } + + @Override + public void close() throws IOException { + synchronized (mLock) { + if (mIsOpen) { + closeLocked(); + } + } + + if (mServer != null) { + IoUtils.closeQuietly(mServer); + } + } + + private void closeLocked() { + for (int i = 0; i < mEventSchedulers.length; i++) { + mMidiInputPortReceivers[i].setReceiver(null); + mEventSchedulers[i].close(); + } + for (UsbDeviceConnection connection : mUsbDeviceConnections) { + connection.close(); + } + mUsbDeviceConnections = null; + mInputUsbEndpoints = null; + mOutputUsbEndpoints = null; + + mIsOpen = false; + } + + private void swapEndiannessPerWord(byte[] array, int size) { + for (int i = 0; i + 3 < size; i += 4) { + byte tmp = array[i]; + array[i] = array[i + 3]; + array[i + 3] = tmp; + tmp = array[i + 1]; + array[i + 1] = array[i + 2]; + array[i + 2] = tmp; + } + } + + private static void logByteArray(String prefix, byte[] value, int offset, int count) { + StringBuilder builder = new StringBuilder(prefix); + for (int i = offset; i < offset + count; i++) { + builder.append(String.format("0x%02X", value[i])); + if (i != value.length - 1) { + builder.append(", "); + } + } + Log.d(TAG, builder.toString()); + } +} diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbACAudioControlEndpoint.java b/services/usb/java/com/android/server/usb/descriptors/UsbACAudioControlEndpoint.java index 409e605c3c2f..bfcf62147f75 100644 --- a/services/usb/java/com/android/server/usb/descriptors/UsbACAudioControlEndpoint.java +++ b/services/usb/java/com/android/server/usb/descriptors/UsbACAudioControlEndpoint.java @@ -38,8 +38,8 @@ public class UsbACAudioControlEndpoint extends UsbACEndpoint { static final byte ATTRIBSMASK_SYNC = 0x0C; static final byte ATTRIBMASK_TRANS = 0x03; - public UsbACAudioControlEndpoint(int length, byte type, int subclass) { - super(length, type, subclass); + public UsbACAudioControlEndpoint(int length, byte type, int subclass, byte subtype) { + super(length, type, subclass, subtype); } public byte getAddress() { diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbACAudioStreamEndpoint.java b/services/usb/java/com/android/server/usb/descriptors/UsbACAudioStreamEndpoint.java index e63bb74abdf7..ae9ca0d827f5 100644 --- a/services/usb/java/com/android/server/usb/descriptors/UsbACAudioStreamEndpoint.java +++ b/services/usb/java/com/android/server/usb/descriptors/UsbACAudioStreamEndpoint.java @@ -24,8 +24,8 @@ public class UsbACAudioStreamEndpoint extends UsbACEndpoint { private static final String TAG = "UsbACAudioStreamEndpoint"; //TODO data fields... - public UsbACAudioStreamEndpoint(int length, byte type, int subclass) { - super(length, type, subclass); + public UsbACAudioStreamEndpoint(int length, byte type, int subclass, byte subtype) { + super(length, type, subclass, subtype); } @Override diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbACEndpoint.java b/services/usb/java/com/android/server/usb/descriptors/UsbACEndpoint.java index ff7f3934086c..b7f9ac334954 100644 --- a/services/usb/java/com/android/server/usb/descriptors/UsbACEndpoint.java +++ b/services/usb/java/com/android/server/usb/descriptors/UsbACEndpoint.java @@ -25,13 +25,17 @@ import android.util.Log; abstract class UsbACEndpoint extends UsbDescriptor { private static final String TAG = "UsbACEndpoint"; + public static final byte MS_GENERAL = 1; + public static final byte MS_GENERAL_2_0 = 2; + protected final int mSubclass; // from the mSubclass member of the "enclosing" // Interface Descriptor, not the stream. - protected byte mSubtype; // 2:1 HEADER descriptor subtype + protected final byte mSubtype; // 2:1 HEADER descriptor subtype - UsbACEndpoint(int length, byte type, int subclass) { + UsbACEndpoint(int length, byte type, int subclass, byte subtype) { super(length, type); mSubclass = subclass; + mSubtype = subtype; } public int getSubclass() { @@ -44,33 +48,39 @@ abstract class UsbACEndpoint extends UsbDescriptor { @Override public int parseRawDescriptors(ByteStream stream) { - mSubtype = stream.getByte(); return mLength; } public static UsbDescriptor allocDescriptor(UsbDescriptorParser parser, - int length, byte type) { + int length, byte type, byte subType) { UsbInterfaceDescriptor interfaceDesc = parser.getCurInterface(); int subClass = interfaceDesc.getUsbSubclass(); - // TODO shouldn't this switch on subtype? switch (subClass) { case AUDIO_AUDIOCONTROL: if (UsbDescriptorParser.DEBUG) { Log.d(TAG, "---> AUDIO_AUDIOCONTROL"); } - return new UsbACAudioControlEndpoint(length, type, subClass); + return new UsbACAudioControlEndpoint(length, type, subClass, subType); case AUDIO_AUDIOSTREAMING: if (UsbDescriptorParser.DEBUG) { Log.d(TAG, "---> AUDIO_AUDIOSTREAMING"); } - return new UsbACAudioStreamEndpoint(length, type, subClass); + return new UsbACAudioStreamEndpoint(length, type, subClass, subType); case AUDIO_MIDISTREAMING: if (UsbDescriptorParser.DEBUG) { Log.d(TAG, "---> AUDIO_MIDISTREAMING"); } - return new UsbACMidiEndpoint(length, type, subClass); + switch (subType) { + case MS_GENERAL: + return new UsbACMidi10Endpoint(length, type, subClass, subType); + case MS_GENERAL_2_0: + return new UsbACMidi20Endpoint(length, type, subClass, subType); + default: + Log.w(TAG, "Unknown Midi Endpoint id:0x" + Integer.toHexString(subType)); + return null; + } default: Log.w(TAG, "Unknown Audio Class Endpoint id:0x" + Integer.toHexString(subClass)); diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbACMidiEndpoint.java b/services/usb/java/com/android/server/usb/descriptors/UsbACMidi10Endpoint.java index 42ee88922edd..49b9d7b30d29 100644 --- a/services/usb/java/com/android/server/usb/descriptors/UsbACMidiEndpoint.java +++ b/services/usb/java/com/android/server/usb/descriptors/UsbACMidi10Endpoint.java @@ -22,14 +22,14 @@ import com.android.server.usb.descriptors.report.ReportCanvas; * An audio class-specific Midi Endpoint. * see midi10.pdf section 6.2.2 */ -public final class UsbACMidiEndpoint extends UsbACEndpoint { - private static final String TAG = "UsbACMidiEndpoint"; +public final class UsbACMidi10Endpoint extends UsbACEndpoint { + private static final String TAG = "UsbACMidi10Endpoint"; private byte mNumJacks; - private byte[] mJackIds; + private byte[] mJackIds = new byte[0]; - public UsbACMidiEndpoint(int length, byte type, int subclass) { - super(length, type, subclass); + public UsbACMidi10Endpoint(int length, byte type, int subclass, byte subtype) { + super(length, type, subclass, subtype); } public byte getNumJacks() { @@ -45,9 +45,11 @@ public final class UsbACMidiEndpoint extends UsbACEndpoint { super.parseRawDescriptors(stream); mNumJacks = stream.getByte(); - mJackIds = new byte[mNumJacks]; - for (int jack = 0; jack < mNumJacks; jack++) { - mJackIds[jack] = stream.getByte(); + if (mNumJacks > 0) { + mJackIds = new byte[mNumJacks]; + for (int jack = 0; jack < mNumJacks; jack++) { + mJackIds[jack] = stream.getByte(); + } } return mLength; } @@ -56,10 +58,10 @@ public final class UsbACMidiEndpoint extends UsbACEndpoint { public void report(ReportCanvas canvas) { super.report(canvas); - canvas.writeHeader(3, "AC Midi Endpoint: " + ReportCanvas.getHexString(getType()) + canvas.writeHeader(3, "ACMidi10Endpoint: " + ReportCanvas.getHexString(getType()) + " Length: " + getLength()); canvas.openList(); canvas.writeListItem("" + getNumJacks() + " Jacks."); canvas.closeList(); } -}
\ No newline at end of file +} diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbACMidi20Endpoint.java b/services/usb/java/com/android/server/usb/descriptors/UsbACMidi20Endpoint.java new file mode 100644 index 000000000000..1024a5bf55a6 --- /dev/null +++ b/services/usb/java/com/android/server/usb/descriptors/UsbACMidi20Endpoint.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2017 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.usb.descriptors; + +import com.android.server.usb.descriptors.report.ReportCanvas; + +/** + * @hide + * An audio class-specific Midi Endpoint. + * see midi10.pdf section 6.2.2 + */ +public final class UsbACMidi20Endpoint extends UsbACEndpoint { + private static final String TAG = "UsbACMidi20Endpoint"; + + private byte mNumGroupTerminals; + private byte[] mBlockIds = new byte[0]; + + public UsbACMidi20Endpoint(int length, byte type, int subclass, byte subtype) { + super(length, type, subclass, subtype); + } + + public byte getNumGroupTerminals() { + return mNumGroupTerminals; + } + + public byte[] getBlockIds() { + return mBlockIds; + } + + @Override + public int parseRawDescriptors(ByteStream stream) { + super.parseRawDescriptors(stream); + + mNumGroupTerminals = stream.getByte(); + if (mNumGroupTerminals > 0) { + mBlockIds = new byte[mNumGroupTerminals]; + for (int block = 0; block < mNumGroupTerminals; block++) { + mBlockIds[block] = stream.getByte(); + } + } + return mLength; + } + + @Override + public void report(ReportCanvas canvas) { + super.report(canvas); + + canvas.writeHeader(3, "AC Midi20 Endpoint: " + ReportCanvas.getHexString(getType()) + + " Length: " + getLength()); + canvas.openList(); + canvas.writeListItem("" + getNumGroupTerminals() + " Group Terminals."); + canvas.closeList(); + } +} diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java b/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java index 7250a071835d..6e68a9174cb5 100644 --- a/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java +++ b/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java @@ -30,6 +30,9 @@ public final class UsbDescriptorParser { private final String mDeviceAddr; + private static final int MS_MIDI_1_0 = 0x0100; + private static final int MS_MIDI_2_0 = 0x0200; + // Descriptor Objects private static final int DESCRIPTORS_ALLOC_SIZE = 128; private final ArrayList<UsbDescriptor> mDescriptors; @@ -215,6 +218,7 @@ public final class UsbDescriptorParser { Log.w(TAG, " Unparsed Class-specific"); break; } + mCurInterfaceDescriptor.setClassSpecificInterfaceDescriptor(descriptor); } break; @@ -222,17 +226,25 @@ public final class UsbDescriptorParser { if (mCurInterfaceDescriptor != null) { int subClass = mCurInterfaceDescriptor.getUsbClass(); switch (subClass) { - case UsbDescriptor.CLASSID_AUDIO: - descriptor = UsbACEndpoint.allocDescriptor(this, length, type); + case UsbDescriptor.CLASSID_AUDIO: { + Byte subType = stream.getByte(); + if (DEBUG) { + Log.d(TAG, "UsbDescriptor.CLASSID_AUDIO type:0x" + + Integer.toHexString(type)); + } + descriptor = UsbACEndpoint.allocDescriptor(this, length, type, + subType); + } break; case UsbDescriptor.CLASSID_VIDEO: { - Byte subtype = stream.getByte(); + Byte subType = stream.getByte(); if (DEBUG) { Log.d(TAG, "UsbDescriptor.CLASSID_VIDEO type:0x" + Integer.toHexString(type)); } - descriptor = UsbVCEndpoint.allocDescriptor(this, length, type, subtype); + descriptor = UsbVCEndpoint.allocDescriptor(this, length, type, + subType); } break; @@ -644,8 +656,8 @@ public final class UsbDescriptorParser { for (UsbDescriptor descriptor : descriptors) { // enusure that this isn't an unrecognized interface descriptor if (descriptor instanceof UsbInterfaceDescriptor) { - UsbInterfaceDescriptor interfaceDescr = (UsbInterfaceDescriptor) descriptor; - if (interfaceDescr.getUsbSubclass() == UsbDescriptor.AUDIO_MIDISTREAMING) { + UsbInterfaceDescriptor interfaceDescriptor = (UsbInterfaceDescriptor) descriptor; + if (interfaceDescriptor.getUsbSubclass() == UsbDescriptor.AUDIO_MIDISTREAMING) { return true; } } else { @@ -656,17 +668,90 @@ public final class UsbDescriptorParser { return false; } - private int calculateNumMidiPorts(boolean isOutput) { + /** + * @hide + */ + public boolean containsUniversalMidiDeviceEndpoint() { + ArrayList<UsbInterfaceDescriptor> interfaceDescriptors = + findUniversalMidiInterfaceDescriptors(); + int outputCount = 0; + int inputCount = 0; + for (int interfaceIndex = 0; interfaceIndex < interfaceDescriptors.size(); + interfaceIndex++) { + UsbInterfaceDescriptor interfaceDescriptor = interfaceDescriptors.get(interfaceIndex); + for (int endpointIndex = 0; endpointIndex < interfaceDescriptor.getNumEndpoints(); + endpointIndex++) { + UsbEndpointDescriptor endpoint = + interfaceDescriptor.getEndpointDescriptor(endpointIndex); + // 0 is output, 1 << 7 is input. + if (endpoint.getDirection() == 0) { + outputCount++; + } else { + inputCount++; + } + } + } + return (outputCount > 0) || (inputCount > 0); + } + + /** + * @hide + */ + public ArrayList<UsbInterfaceDescriptor> findUniversalMidiInterfaceDescriptors() { + int count = 0; + ArrayList<UsbDescriptor> descriptors = + getInterfaceDescriptorsForClass(UsbDescriptor.CLASSID_AUDIO); + ArrayList<UsbInterfaceDescriptor> universalMidiInterfaces = + new ArrayList<UsbInterfaceDescriptor>(); + + for (UsbDescriptor descriptor : descriptors) { + // ensure that this isn't an unrecognized interface descriptor + if (descriptor instanceof UsbInterfaceDescriptor) { + UsbInterfaceDescriptor interfaceDescriptor = (UsbInterfaceDescriptor) descriptor; + if (interfaceDescriptor.getUsbSubclass() == UsbDescriptor.AUDIO_MIDISTREAMING) { + UsbDescriptor classSpecificDescriptor = + interfaceDescriptor.getClassSpecificInterfaceDescriptor(); + if (classSpecificDescriptor != null) { + if (classSpecificDescriptor instanceof UsbMSMidiHeader) { + UsbMSMidiHeader midiHeader = + (UsbMSMidiHeader) classSpecificDescriptor; + if (midiHeader.getMidiStreamingClass() == MS_MIDI_2_0) { + universalMidiInterfaces.add(interfaceDescriptor); + } + } + } + } + } else { + Log.w(TAG, "Undefined Audio Class Interface l: " + descriptor.getLength() + + " t:0x" + Integer.toHexString(descriptor.getType())); + } + } + return universalMidiInterfaces; + } + + private int calculateNumLegacyMidiPorts(boolean isOutput) { int count = 0; ArrayList<UsbDescriptor> descriptors = getInterfaceDescriptorsForClass(UsbDescriptor.CLASSID_AUDIO); for (UsbDescriptor descriptor : descriptors) { // ensure that this isn't an unrecognized interface descriptor if (descriptor instanceof UsbInterfaceDescriptor) { - UsbInterfaceDescriptor interfaceDescr = (UsbInterfaceDescriptor) descriptor; - if (interfaceDescr.getUsbSubclass() == UsbDescriptor.AUDIO_MIDISTREAMING) { - for (int i = 0; i < interfaceDescr.getNumEndpoints(); i++) { - UsbEndpointDescriptor endpoint = interfaceDescr.getEndpointDescriptor(i); + UsbInterfaceDescriptor interfaceDescriptor = (UsbInterfaceDescriptor) descriptor; + if (interfaceDescriptor.getUsbSubclass() == UsbDescriptor.AUDIO_MIDISTREAMING) { + UsbDescriptor classSpecificDescriptor = + interfaceDescriptor.getClassSpecificInterfaceDescriptor(); + if (classSpecificDescriptor != null) { + if (classSpecificDescriptor instanceof UsbMSMidiHeader) { + UsbMSMidiHeader midiHeader = + (UsbMSMidiHeader) classSpecificDescriptor; + if (midiHeader.getMidiStreamingClass() != MS_MIDI_1_0) { + continue; + } + } + } + for (int i = 0; i < interfaceDescriptor.getNumEndpoints(); i++) { + UsbEndpointDescriptor endpoint = + interfaceDescriptor.getEndpointDescriptor(i); // 0 is output, 1 << 7 is input. if ((endpoint.getDirection() == 0) == isOutput) { count++; @@ -684,15 +769,15 @@ public final class UsbDescriptorParser { /** * @hide */ - public int calculateNumMidiInputs() { - return calculateNumMidiPorts(false /*isOutput*/); + public int calculateNumLegacyMidiInputs() { + return calculateNumLegacyMidiPorts(false /*isOutput*/); } /** * @hide */ - public int calculateNumMidiOutputs() { - return calculateNumMidiPorts(true /*isOutput*/); + public int calculateNumLegacyMidiOutputs() { + return calculateNumLegacyMidiPorts(true /*isOutput*/); } /** @@ -785,4 +870,35 @@ public final class UsbDescriptorParser { return getOutputHeadsetProbability() >= OUT_HEADSET_TRIGGER; } + /** + * isDock() indicates if the connected USB output peripheral is a docking station with + * audio output. + * A valid audio dock must declare only one audio output control terminal of type + * TERMINAL_EXTERN_DIGITAL. + */ + public boolean isDock() { + if (hasMIDIInterface() || hasHIDInterface()) { + return false; + } + + ArrayList<UsbDescriptor> acDescriptors = + getACInterfaceDescriptors(UsbACInterface.ACI_OUTPUT_TERMINAL, + UsbACInterface.AUDIO_AUDIOCONTROL); + + if (acDescriptors.size() != 1) { + return false; + } + + if (acDescriptors.get(0) instanceof UsbACTerminal) { + UsbACTerminal outDescr = (UsbACTerminal) acDescriptors.get(0); + if (outDescr.getTerminalType() == UsbTerminalTypes.TERMINAL_EXTERN_DIGITAL) { + return true; + } + } else { + Log.w(TAG, "Undefined Audio Output terminal l: " + acDescriptors.get(0).getLength() + + " t:0x" + Integer.toHexString(acDescriptors.get(0).getType())); + } + return false; + } + } diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbEndpointDescriptor.java b/services/usb/java/com/android/server/usb/descriptors/UsbEndpointDescriptor.java index 4d0cfea98630..ab07ce7fdb7a 100644 --- a/services/usb/java/com/android/server/usb/descriptors/UsbEndpointDescriptor.java +++ b/services/usb/java/com/android/server/usb/descriptors/UsbEndpointDescriptor.java @@ -112,7 +112,10 @@ public class UsbEndpointDescriptor extends UsbDescriptor { return mEndpointAddress & UsbEndpointDescriptor.MASK_ENDPOINT_DIRECTION; } - /* package */ UsbEndpoint toAndroid(UsbDescriptorParser parser) { + /** + * Returns a UsbEndpoint that this UsbEndpointDescriptor is describing. + */ + public UsbEndpoint toAndroid(UsbDescriptorParser parser) { if (UsbDescriptorParser.DEBUG) { Log.d(TAG, "toAndroid() type:" + Integer.toHexString(mAttributes & MASK_ATTRIBS_TRANSTYPE) diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbInterfaceDescriptor.java b/services/usb/java/com/android/server/usb/descriptors/UsbInterfaceDescriptor.java index 64dbd971cc67..ca4613b17873 100644 --- a/services/usb/java/com/android/server/usb/descriptors/UsbInterfaceDescriptor.java +++ b/services/usb/java/com/android/server/usb/descriptors/UsbInterfaceDescriptor.java @@ -42,6 +42,8 @@ public class UsbInterfaceDescriptor extends UsbDescriptor { private ArrayList<UsbEndpointDescriptor> mEndpointDescriptors = new ArrayList<UsbEndpointDescriptor>(); + private UsbDescriptor mClassSpecificInterfaceDescriptor; + UsbInterfaceDescriptor(int length, byte type) { super(length, type); mHierarchyLevel = 3; @@ -105,7 +107,18 @@ public class UsbInterfaceDescriptor extends UsbDescriptor { mEndpointDescriptors.add(endpoint); } - UsbInterface toAndroid(UsbDescriptorParser parser) { + public void setClassSpecificInterfaceDescriptor(UsbDescriptor descriptor) { + mClassSpecificInterfaceDescriptor = descriptor; + } + + public UsbDescriptor getClassSpecificInterfaceDescriptor() { + return mClassSpecificInterfaceDescriptor; + } + + /** + * Returns a UsbInterface that this UsbInterfaceDescriptor is describing. + */ + public UsbInterface toAndroid(UsbDescriptorParser parser) { if (UsbDescriptorParser.DEBUG) { Log.d(TAG, "toAndroid() class:" + Integer.toHexString(mUsbClass) + " subclass:" + Integer.toHexString(mUsbSubclass) diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbMSMidiHeader.java b/services/usb/java/com/android/server/usb/descriptors/UsbMSMidiHeader.java index d0ca6db87d38..76535612f54d 100644 --- a/services/usb/java/com/android/server/usb/descriptors/UsbMSMidiHeader.java +++ b/services/usb/java/com/android/server/usb/descriptors/UsbMSMidiHeader.java @@ -25,13 +25,19 @@ import com.android.server.usb.descriptors.report.ReportCanvas; public final class UsbMSMidiHeader extends UsbACInterface { private static final String TAG = "UsbMSMidiHeader"; + private int mMidiStreamingClass; // MSC Specification Release (BCD). + public UsbMSMidiHeader(int length, byte type, byte subtype, int subclass) { super(length, type, subtype, subclass); } + public int getMidiStreamingClass() { + return mMidiStreamingClass; + } + @Override public int parseRawDescriptors(ByteStream stream) { - // TODO - read data memebers + mMidiStreamingClass = stream.unpackUsbShort(); stream.advance(mLength - stream.getReadCount()); return mLength; } @@ -42,6 +48,7 @@ public final class UsbMSMidiHeader extends UsbACInterface { canvas.writeHeader(3, "MS Midi Header: " + ReportCanvas.getHexString(getType()) + " SubType: " + ReportCanvas.getHexString(getSubclass()) - + " Length: " + getLength()); + + " Length: " + getLength() + + " MidiStreamingClass :" + getMidiStreamingClass()); } } diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbMidiBlockParser.java b/services/usb/java/com/android/server/usb/descriptors/UsbMidiBlockParser.java new file mode 100644 index 000000000000..37bd0f8f6f7b --- /dev/null +++ b/services/usb/java/com/android/server/usb/descriptors/UsbMidiBlockParser.java @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2017 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.usb.descriptors; + +import android.hardware.usb.UsbConstants; +import android.hardware.usb.UsbDeviceConnection; +import android.util.Log; + +import java.util.ArrayList; + +/** + * @hide + * A class to parse Block Descriptors + * see midi20.pdf section 5.4 + */ +public class UsbMidiBlockParser { + private static final String TAG = "UsbMidiBlockParser"; + + // Block header size + public static final int MIDI_BLOCK_HEADER_SIZE = 5; + public static final int MIDI_BLOCK_SIZE = 13; + public static final int REQ_GET_DESCRIPTOR = 0x06; + public static final int CS_GR_TRM_BLOCK = 0x26; // Class-specific GR_TRM_BLK + public static final int GR_TRM_BLOCK_HEADER = 0x01; // Group block header + public static final int REQ_TIMEOUT_MS = 2000; // 2 second timeout + public static final int DEFAULT_MIDI_TYPE = 1; // Default MIDI type + + protected int mHeaderLength; // 0:1 Size of header descriptor + protected int mHeaderDescriptorType; // 1:1 Descriptor Type + protected int mHeaderDescriptorSubtype; // 2:1 Descriptor Subtype + protected int mTotalLength; // 3:2 Total Length of header and blocks + + static class GroupTerminalBlock { + protected int mLength; // 0:1 Size of descriptor + protected int mDescriptorType; // 1:1 Descriptor Type + protected int mDescriptorSubtype; // 2:1 Descriptor Subtype + protected int mGroupBlockId; // 3:1 Id of Group Terminal Block + protected int mGroupTerminalBlockType; // 4:1 bi-directional, IN, or OUT + protected int mGroupTerminal; // 5:1 Group Terminal Number + protected int mNumGroupTerminals; // 6:1 Number of Group Terminals + protected int mBlockItem; // 7:1 ID of STRING descriptor of Block item + protected int mMidiProtocol; // 8:1 MIDI protocol + protected int mMaxInputBandwidth; // 9:2 Max Input Bandwidth + protected int mMaxOutputBandwidth; // 11:2 Max Output Bandwidth + + public int parseRawDescriptors(ByteStream stream) { + mLength = stream.getUnsignedByte(); + mDescriptorType = stream.getUnsignedByte(); + mDescriptorSubtype = stream.getUnsignedByte(); + mGroupBlockId = stream.getUnsignedByte(); + mGroupTerminalBlockType = stream.getUnsignedByte(); + mGroupTerminal = stream.getUnsignedByte(); + mNumGroupTerminals = stream.getUnsignedByte(); + mBlockItem = stream.getUnsignedByte(); + mMidiProtocol = stream.getUnsignedByte(); + mMaxInputBandwidth = stream.unpackUsbShort(); + mMaxOutputBandwidth = stream.unpackUsbShort(); + return mLength; + } + } + + private ArrayList<GroupTerminalBlock> mGroupTerminalBlocks = + new ArrayList<GroupTerminalBlock>(); + + public UsbMidiBlockParser() { + } + + /** + * Parses a raw ByteStream into a block terminal descriptor. + * The header is parsed before each block is parsed. + * @param stream ByteStream to parse + * @return The total length that has been parsed. + */ + public int parseRawDescriptors(ByteStream stream) { + mHeaderLength = stream.getUnsignedByte(); + mHeaderDescriptorType = stream.getUnsignedByte(); + mHeaderDescriptorSubtype = stream.getUnsignedByte(); + mTotalLength = stream.unpackUsbShort(); + + while (stream.available() >= MIDI_BLOCK_SIZE) { + GroupTerminalBlock block = new GroupTerminalBlock(); + block.parseRawDescriptors(stream); + mGroupTerminalBlocks.add(block); + } + + return mTotalLength; + } + + /** + * Calculates the MIDI type through querying the device twice, once for the size + * of the block descriptor and once for the block descriptor. This descriptor is + * then parsed to return the MIDI type. + * See the MIDI 2.0 USB doc for more info. + * @param connection UsbDeviceConnection to send the request + * @param interfaceNumber The interface number to query + * @param alternateInterfaceNumber The alternate interface of the interface + * @return The MIDI type as an int. + */ + public int calculateMidiType(UsbDeviceConnection connection, int interfaceNumber, + int alternateInterfaceNumber) { + byte[] byteArray = new byte[MIDI_BLOCK_HEADER_SIZE]; + try { + // This first request is simply to get the full size of the descriptor. + // This info is stored in the last two bytes of the header. + int rdo = connection.controlTransfer( + UsbConstants.USB_DIR_IN | UsbConstants.USB_TYPE_STANDARD + | UsbConstants.USB_CLASS_AUDIO, + REQ_GET_DESCRIPTOR, + (CS_GR_TRM_BLOCK << 8) + alternateInterfaceNumber, + interfaceNumber, + byteArray, + MIDI_BLOCK_HEADER_SIZE, + REQ_TIMEOUT_MS); + if (rdo > 0) { + if (byteArray[1] != CS_GR_TRM_BLOCK) { + Log.e(TAG, "Incorrect descriptor type: " + byteArray[1]); + return DEFAULT_MIDI_TYPE; + } + if (byteArray[2] != GR_TRM_BLOCK_HEADER) { + Log.e(TAG, "Incorrect descriptor subtype: " + byteArray[2]); + return DEFAULT_MIDI_TYPE; + } + int newSize = (((int) byteArray[3]) & (0xff)) + + ((((int) byteArray[4]) & (0xff)) << 8); + if (newSize <= 0) { + Log.e(TAG, "Parsed a non-positive block terminal size: " + newSize); + return DEFAULT_MIDI_TYPE; + } + byteArray = new byte[newSize]; + rdo = connection.controlTransfer( + UsbConstants.USB_DIR_IN | UsbConstants.USB_TYPE_STANDARD + | UsbConstants.USB_CLASS_AUDIO, + REQ_GET_DESCRIPTOR, + (CS_GR_TRM_BLOCK << 8) + alternateInterfaceNumber, + interfaceNumber, + byteArray, + newSize, + REQ_TIMEOUT_MS); + if (rdo > 0) { + ByteStream stream = new ByteStream(byteArray); + parseRawDescriptors(stream); + if (mGroupTerminalBlocks.isEmpty()) { + Log.e(TAG, "Group Terminal Blocks failed parsing: " + DEFAULT_MIDI_TYPE); + return DEFAULT_MIDI_TYPE; + } else { + Log.d(TAG, "MIDI protocol: " + mGroupTerminalBlocks.get(0).mMidiProtocol); + return mGroupTerminalBlocks.get(0).mMidiProtocol; + } + } else { + Log.e(TAG, "second transfer failed: " + rdo); + } + } else { + Log.e(TAG, "first transfer failed: " + rdo); + } + } catch (Exception e) { + Log.e(TAG, "Can not communicate with USB device", e); + } + return DEFAULT_MIDI_TYPE; + } +} diff --git a/services/usb/java/com/android/server/usb/hal/port/RawPortInfo.java b/services/usb/java/com/android/server/usb/hal/port/RawPortInfo.java new file mode 100644 index 000000000000..dd256205c220 --- /dev/null +++ b/services/usb/java/com/android/server/usb/hal/port/RawPortInfo.java @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.usb.hal.port; + +import android.hardware.usb.UsbPortStatus; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Used for storing the raw data from the HAL. + * Values of the member variables mocked directly in case of emulation. + */ +public final class RawPortInfo implements Parcelable { + public final String portId; + public final int supportedModes; + public final int supportedContaminantProtectionModes; + public int currentMode; + public boolean canChangeMode; + public int currentPowerRole; + public boolean canChangePowerRole; + public int currentDataRole; + public boolean canChangeDataRole; + public boolean supportsEnableContaminantPresenceProtection; + public int contaminantProtectionStatus; + public boolean supportsEnableContaminantPresenceDetection; + public int contaminantDetectionStatus; + public int[] usbDataStatus; + public boolean powerTransferLimited; + public int powerBrickStatus; + + public RawPortInfo(String portId, int supportedModes) { + this.portId = portId; + this.supportedModes = supportedModes; + this.supportedContaminantProtectionModes = UsbPortStatus.CONTAMINANT_PROTECTION_NONE; + this.supportsEnableContaminantPresenceProtection = false; + this.contaminantProtectionStatus = UsbPortStatus.CONTAMINANT_PROTECTION_NONE; + this.supportsEnableContaminantPresenceDetection = false; + this.contaminantDetectionStatus = UsbPortStatus.CONTAMINANT_DETECTION_NOT_SUPPORTED; + this.usbDataStatus[0] = UsbPortStatus.USB_DATA_STATUS_UNKNOWN; + this.powerTransferLimited = false; + this.powerBrickStatus = UsbPortStatus.POWER_BRICK_STATUS_UNKNOWN; + } + + public RawPortInfo(String portId, int supportedModes, int supportedContaminantProtectionModes, + int currentMode, boolean canChangeMode, + int currentPowerRole, boolean canChangePowerRole, + int currentDataRole, boolean canChangeDataRole, + boolean supportsEnableContaminantPresenceProtection, + int contaminantProtectionStatus, + boolean supportsEnableContaminantPresenceDetection, + int contaminantDetectionStatus, + int[] usbDataStatus, + boolean powerTransferLimited, + int powerBrickStatus) { + this.portId = portId; + this.supportedModes = supportedModes; + this.supportedContaminantProtectionModes = supportedContaminantProtectionModes; + this.currentMode = currentMode; + this.canChangeMode = canChangeMode; + this.currentPowerRole = currentPowerRole; + this.canChangePowerRole = canChangePowerRole; + this.currentDataRole = currentDataRole; + this.canChangeDataRole = canChangeDataRole; + this.supportsEnableContaminantPresenceProtection = + supportsEnableContaminantPresenceProtection; + this.contaminantProtectionStatus = contaminantProtectionStatus; + this.supportsEnableContaminantPresenceDetection = + supportsEnableContaminantPresenceDetection; + this.contaminantDetectionStatus = contaminantDetectionStatus; + this.usbDataStatus = usbDataStatus; + this.powerTransferLimited = powerTransferLimited; + this.powerBrickStatus = powerBrickStatus; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(portId); + dest.writeInt(supportedModes); + dest.writeInt(supportedContaminantProtectionModes); + dest.writeInt(currentMode); + dest.writeByte((byte) (canChangeMode ? 1 : 0)); + dest.writeInt(currentPowerRole); + dest.writeByte((byte) (canChangePowerRole ? 1 : 0)); + dest.writeInt(currentDataRole); + dest.writeByte((byte) (canChangeDataRole ? 1 : 0)); + dest.writeBoolean(supportsEnableContaminantPresenceProtection); + dest.writeInt(contaminantProtectionStatus); + dest.writeBoolean(supportsEnableContaminantPresenceDetection); + dest.writeInt(contaminantDetectionStatus); + dest.writeInt(usbDataStatus.length); + dest.writeIntArray(usbDataStatus); + dest.writeBoolean(powerTransferLimited); + dest.writeInt(powerBrickStatus); + } + + public static final Parcelable.Creator<RawPortInfo> CREATOR = + new Parcelable.Creator<RawPortInfo>() { + @Override + public RawPortInfo createFromParcel(Parcel in) { + String id = in.readString(); + int supportedModes = in.readInt(); + int supportedContaminantProtectionModes = in.readInt(); + int currentMode = in.readInt(); + boolean canChangeMode = in.readByte() != 0; + int currentPowerRole = in.readInt(); + boolean canChangePowerRole = in.readByte() != 0; + int currentDataRole = in.readInt(); + boolean canChangeDataRole = in.readByte() != 0; + boolean supportsEnableContaminantPresenceProtection = in.readBoolean(); + int contaminantProtectionStatus = in.readInt(); + boolean supportsEnableContaminantPresenceDetection = in.readBoolean(); + int contaminantDetectionStatus = in.readInt(); + int[] usbDataStatus = new int[in.readInt()]; + in.readIntArray(usbDataStatus); + boolean powerTransferLimited = in.readBoolean(); + int powerBrickStatus = in.readInt(); + return new RawPortInfo(id, supportedModes, + supportedContaminantProtectionModes, currentMode, canChangeMode, + currentPowerRole, canChangePowerRole, + currentDataRole, canChangeDataRole, + supportsEnableContaminantPresenceProtection, + contaminantProtectionStatus, + supportsEnableContaminantPresenceDetection, + contaminantDetectionStatus, usbDataStatus, + powerTransferLimited, powerBrickStatus); + } + + @Override + public RawPortInfo[] newArray(int size) { + return new RawPortInfo[size]; + } + }; +} diff --git a/services/usb/java/com/android/server/usb/hal/port/UsbPortAidl.java b/services/usb/java/com/android/server/usb/hal/port/UsbPortAidl.java new file mode 100644 index 000000000000..558260073733 --- /dev/null +++ b/services/usb/java/com/android/server/usb/hal/port/UsbPortAidl.java @@ -0,0 +1,609 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.usb.hal.port; + +import static android.hardware.usb.UsbManager.USB_HAL_V2_0; +import static android.hardware.usb.UsbOperationInternal.USB_OPERATION_ERROR_INTERNAL; +import static android.hardware.usb.UsbOperationInternal.USB_OPERATION_SUCCESS; + +import static com.android.server.usb.UsbPortManager.logAndPrint; +import static com.android.server.usb.UsbPortManager.logAndPrintException; + +import android.annotation.Nullable; +import android.hardware.usb.ContaminantProtectionStatus; +import android.hardware.usb.IUsb; +import android.hardware.usb.IUsbOperationInternal; +import android.hardware.usb.UsbManager.UsbHalVersion; +import android.hardware.usb.UsbPort; +import android.hardware.usb.UsbPortStatus; +import android.hardware.usb.PortMode; +import android.hardware.usb.Status; +import android.hardware.usb.IUsbCallback; +import android.hardware.usb.PortRole; +import android.hardware.usb.PortStatus; +import android.os.ServiceManager; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Log; +import android.util.LongSparseArray; +import android.util.Slog; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.util.IndentingPrintWriter; +import com.android.server.usb.UsbPortManager; +import com.android.server.usb.hal.port.RawPortInfo; + +import java.util.ArrayList; +import java.util.concurrent.ThreadLocalRandom; +import java.util.NoSuchElementException; +import java.util.Objects; + +/** + * Implements the methods to interact with AIDL USB HAL. + */ +public final class UsbPortAidl implements UsbPortHal { + private static final String TAG = UsbPortAidl.class.getSimpleName(); + private static final String USB_AIDL_SERVICE = + "android.hardware.usb.IUsb/default"; + private static final LongSparseArray<IUsbOperationInternal> + sCallbacks = new LongSparseArray<>(); + // Proxy object for the usb hal daemon. + @GuardedBy("mLock") + private IUsb mProxy; + private UsbPortManager mPortManager; + public IndentingPrintWriter mPw; + // Mutex for all mutable shared state. + private final Object mLock = new Object(); + // Callback when the UsbPort status is changed by the kernel. + private HALCallback mHALCallback; + private IBinder mBinder; + private boolean mSystemReady; + private long mTransactionId; + + public @UsbHalVersion int getUsbHalVersion() throws RemoteException { + synchronized (mLock) { + if (mProxy == null) { + throw new RemoteException("IUsb not initialized yet"); + } + } + logAndPrint(Log.INFO, null, "USB HAL AIDL version: USB_HAL_V2_0"); + return USB_HAL_V2_0; + } + + @Override + public void systemReady() { + mSystemReady = true; + } + + public void serviceDied() { + logAndPrint(Log.ERROR, mPw, "Usb AIDL hal service died"); + synchronized (mLock) { + mProxy = null; + } + connectToProxy(null); + } + + private void connectToProxy(IndentingPrintWriter pw) { + synchronized (mLock) { + if (mProxy != null) { + return; + } + + try { + mBinder = ServiceManager.waitForService(USB_AIDL_SERVICE); + mProxy = IUsb.Stub.asInterface(mBinder); + mBinder.linkToDeath(this::serviceDied, 0); + mProxy.setCallback(mHALCallback); + mProxy.queryPortStatus(++mTransactionId); + } catch (NoSuchElementException e) { + logAndPrintException(pw, "connectToProxy: usb hal service not found." + + " Did the service fail to start?", e); + } catch (RemoteException e) { + logAndPrintException(pw, "connectToProxy: usb hal service not responding", e); + } + } + } + + static boolean isServicePresent(IndentingPrintWriter pw) { + try { + return ServiceManager.isDeclared(USB_AIDL_SERVICE); + } catch (NoSuchElementException e) { + logAndPrintException(pw, "connectToProxy: usb Aidl hal service not found.", e); + } + + return false; + } + + public UsbPortAidl(UsbPortManager portManager, IndentingPrintWriter pw) { + mPortManager = Objects.requireNonNull(portManager); + mPw = pw; + mHALCallback = new HALCallback(null, mPortManager, this); + connectToProxy(mPw); + } + + @Override + public void enableContaminantPresenceDetection(String portName, boolean enable, + long operationID) { + synchronized (mLock) { + if (mProxy == null) { + logAndPrint(Log.ERROR, mPw, "Proxy is null. Retry ! opID: " + + operationID); + return; + } + + try { + // Oneway call into the hal. Use the castFrom method from HIDL. + mProxy.enableContaminantPresenceDetection(portName, enable, operationID); + } catch (RemoteException e) { + logAndPrintException(mPw, "Failed to set contaminant detection. opID:" + + operationID, e); + } + } + } + + @Override + public void queryPortStatus(long operationID) { + synchronized (mLock) { + if (mProxy == null) { + logAndPrint(Log.ERROR, mPw, "Proxy is null. Retry ! opID:" + + operationID); + return; + } + + try { + mProxy.queryPortStatus(operationID); + } catch (RemoteException e) { + logAndPrintException(null, "ServiceStart: Failed to query port status. opID:" + + operationID, e); + } + } + } + + @Override + public void switchMode(String portId, @HalUsbPortMode int newMode, long operationID) { + synchronized (mLock) { + if (mProxy == null) { + logAndPrint(Log.ERROR, mPw, "Proxy is null. Retry ! opID:" + + operationID); + return; + } + + PortRole newRole = new PortRole(); + newRole.setMode((byte)newMode); + try { + mProxy.switchRole(portId, newRole, operationID); + } catch (RemoteException e) { + logAndPrintException(mPw, "Failed to set the USB port mode: " + + "portId=" + portId + + ", newMode=" + UsbPort.modeToString(newMode) + + "opID:" + operationID, e); + } + } + } + + @Override + public void switchPowerRole(String portId, @HalUsbPowerRole int newPowerRole, + long operationID) { + synchronized (mLock) { + if (mProxy == null) { + logAndPrint(Log.ERROR, mPw, "Proxy is null. Retry ! opID:" + + operationID); + return; + } + + PortRole newRole = new PortRole(); + newRole.setPowerRole((byte)newPowerRole); + try { + mProxy.switchRole(portId, newRole, operationID); + } catch (RemoteException e) { + logAndPrintException(mPw, "Failed to set the USB power role: portId=" + portId + + ", newPowerRole=" + UsbPort.powerRoleToString(newPowerRole) + + "opID:" + operationID, e); + } + } + } + + @Override + public void switchDataRole(String portId, @HalUsbDataRole int newDataRole, long operationID) { + synchronized (mLock) { + if (mProxy == null) { + logAndPrint(Log.ERROR, mPw, "Proxy is null. Retry ! opID:" + + operationID); + return; + } + + PortRole newRole = new PortRole(); + newRole.setDataRole((byte)newDataRole); + try { + mProxy.switchRole(portId, newRole, operationID); + } catch (RemoteException e) { + logAndPrintException(mPw, "Failed to set the USB data role: portId=" + portId + + ", newDataRole=" + UsbPort.dataRoleToString(newDataRole) + + "opID:" + operationID, e); + } + } + } + + @Override + public boolean enableUsbData(String portName, boolean enable, long operationID, + IUsbOperationInternal callback) { + Objects.requireNonNull(portName); + Objects.requireNonNull(callback); + long key = operationID; + synchronized (mLock) { + try { + if (mProxy == null) { + logAndPrint(Log.ERROR, mPw, + "enableUsbData: Proxy is null. Retry !opID:" + + operationID); + callback.onOperationComplete(USB_OPERATION_ERROR_INTERNAL); + return false; + } + while (sCallbacks.get(key) != null) { + key = ThreadLocalRandom.current().nextInt(); + } + if (key != operationID) { + logAndPrint(Log.INFO, mPw, "enableUsbData: operationID exists ! opID:" + + operationID + " key:" + key); + } + try { + sCallbacks.put(key, callback); + mProxy.enableUsbData(portName, enable, key); + } catch (RemoteException e) { + logAndPrintException(mPw, + "enableUsbData: Failed to invoke enableUsbData: portID=" + + portName + "opID:" + operationID, e); + callback.onOperationComplete(USB_OPERATION_ERROR_INTERNAL); + sCallbacks.remove(key); + return false; + } + } catch (RemoteException e) { + logAndPrintException(mPw, + "enableUsbData: Failed to call onOperationComplete portID=" + + portName + "opID:" + operationID, e); + sCallbacks.remove(key); + return false; + } + return true; + } + } + + @Override + public void enableLimitPowerTransfer(String portName, boolean limit, long operationID, + IUsbOperationInternal callback) { + Objects.requireNonNull(portName); + long key = operationID; + synchronized (mLock) { + try { + if (mProxy == null) { + logAndPrint(Log.ERROR, mPw, + "enableLimitPowerTransfer: Proxy is null. Retry !opID:" + + operationID); + callback.onOperationComplete(USB_OPERATION_ERROR_INTERNAL); + return; + } + while (sCallbacks.get(key) != null) { + key = ThreadLocalRandom.current().nextInt(); + } + if (key != operationID) { + logAndPrint(Log.INFO, mPw, "enableUsbData: operationID exists ! opID:" + + operationID + " key:" + key); + } + try { + sCallbacks.put(key, callback); + mProxy.limitPowerTransfer(portName, limit, key); + } catch (RemoteException e) { + logAndPrintException(mPw, + "enableLimitPowerTransfer: Failed while invoking AIDL HAL" + + " portID=" + portName + " opID:" + operationID, e); + if (callback != null) { + callback.onOperationComplete(USB_OPERATION_ERROR_INTERNAL); + } + sCallbacks.remove(key); + } + } catch (RemoteException e) { + logAndPrintException(mPw, + "enableLimitPowerTransfer: Failed to call onOperationComplete portID=" + + portName + " opID:" + operationID, e); + } + } + } + + @Override + public void enableUsbDataWhileDocked(String portName, long operationID, + IUsbOperationInternal callback) { + Objects.requireNonNull(portName); + long key = operationID; + synchronized (mLock) { + try { + if (mProxy == null) { + logAndPrint(Log.ERROR, mPw, + "enableUsbDataWhileDocked: Proxy is null. Retry !opID:" + + operationID); + callback.onOperationComplete(USB_OPERATION_ERROR_INTERNAL); + return; + } + while (sCallbacks.get(key) != null) { + key = ThreadLocalRandom.current().nextInt(); + } + if (key != operationID) { + logAndPrint(Log.INFO, mPw, + "enableUsbDataWhileDocked: operationID exists ! opID:" + + operationID + " key:" + key); + } + try { + sCallbacks.put(key, callback); + mProxy.enableUsbDataWhileDocked(portName, key); + } catch (RemoteException e) { + logAndPrintException(mPw, + "enableUsbDataWhileDocked: error while invoking hal" + + "portID=" + portName + " opID:" + operationID, e); + if (callback != null) { + callback.onOperationComplete(USB_OPERATION_ERROR_INTERNAL); + } + sCallbacks.remove(key); + } + } catch (RemoteException e) { + logAndPrintException(mPw, + "enableUsbDataWhileDocked: Failed to call onOperationComplete portID=" + + portName + " opID:" + operationID, e); + } + } + } + + private static class HALCallback extends IUsbCallback.Stub { + public IndentingPrintWriter mPw; + public UsbPortManager mPortManager; + public UsbPortAidl mUsbPortAidl; + + HALCallback(IndentingPrintWriter pw, UsbPortManager portManager, UsbPortAidl usbPortAidl) { + this.mPw = pw; + this.mPortManager = portManager; + this.mUsbPortAidl = usbPortAidl; + } + + /** + * Converts from AIDL defined mode constants to UsbPortStatus constants. + * AIDL does not gracefully support bitfield when combined with enums. + */ + private int toPortMode(byte aidlPortMode) { + switch (aidlPortMode) { + case PortMode.NONE: + return UsbPortStatus.MODE_NONE; + case PortMode.UFP: + return UsbPortStatus.MODE_UFP; + case PortMode.DFP: + return UsbPortStatus.MODE_DFP; + case PortMode.DRP: + return UsbPortStatus.MODE_DUAL; + case PortMode.AUDIO_ACCESSORY: + return UsbPortStatus.MODE_AUDIO_ACCESSORY; + case PortMode.DEBUG_ACCESSORY: + return UsbPortStatus.MODE_DEBUG_ACCESSORY; + default: + UsbPortManager.logAndPrint(Log.ERROR, mPw, "Unrecognized aidlPortMode:" + + aidlPortMode); + return UsbPortStatus.MODE_NONE; + } + } + + private int toSupportedModes(byte[] aidlPortModes) { + int supportedModes = UsbPortStatus.MODE_NONE; + + for (byte aidlPortMode : aidlPortModes) { + supportedModes |= toPortMode(aidlPortMode); + } + + return supportedModes; + } + + /** + * Converts from AIDL defined contaminant protection constants to UsbPortStatus constants. + * AIDL does not gracefully support bitfield when combined with enums. + * Common to both ContaminantProtectionMode and ContaminantProtectionStatus. + */ + private int toContaminantProtectionStatus(byte aidlContaminantProtection) { + switch (aidlContaminantProtection) { + case ContaminantProtectionStatus.NONE: + return UsbPortStatus.CONTAMINANT_PROTECTION_NONE; + case ContaminantProtectionStatus.FORCE_SINK: + return UsbPortStatus.CONTAMINANT_PROTECTION_SINK; + case ContaminantProtectionStatus.FORCE_SOURCE: + return UsbPortStatus.CONTAMINANT_PROTECTION_SOURCE; + case ContaminantProtectionStatus.FORCE_DISABLE: + return UsbPortStatus.CONTAMINANT_PROTECTION_FORCE_DISABLE; + case ContaminantProtectionStatus.DISABLED: + return UsbPortStatus.CONTAMINANT_PROTECTION_DISABLED; + default: + UsbPortManager.logAndPrint(Log.ERROR, mPw, + "Unrecognized aidlContaminantProtection:" + + aidlContaminantProtection); + return UsbPortStatus.CONTAMINANT_PROTECTION_NONE; + } + } + + private int toSupportedContaminantProtectionModes(byte[] aidlModes) { + int supportedContaminantProtectionModes = UsbPortStatus.CONTAMINANT_PROTECTION_NONE; + + for (byte aidlMode : aidlModes) { + supportedContaminantProtectionModes |= toContaminantProtectionStatus(aidlMode); + } + + return supportedContaminantProtectionModes; + } + + private int[] toIntArray(byte[] input) { + int[] output = new int[input.length]; + for (int i = 0; i < input.length; i++) { + output[i] = input[i]; + } + return output; + } + + @Override + public void notifyPortStatusChange( + android.hardware.usb.PortStatus[] currentPortStatus, int retval) { + if (!mUsbPortAidl.mSystemReady) { + return; + } + + if (retval != Status.SUCCESS) { + UsbPortManager.logAndPrint(Log.ERROR, mPw, "port status enquiry failed"); + return; + } + + ArrayList<RawPortInfo> newPortInfo = new ArrayList<>(); + + int numStatus = currentPortStatus.length; + for (int i = 0; i < numStatus; i++) { + PortStatus current = currentPortStatus[i]; + RawPortInfo temp = new RawPortInfo(current.portName, + toSupportedModes(current.supportedModes), + toSupportedContaminantProtectionModes(current + .supportedContaminantProtectionModes), + toPortMode(current.currentMode), + current.canChangeMode, + current.currentPowerRole, + current.canChangePowerRole, + current.currentDataRole, + current.canChangeDataRole, + current.supportsEnableContaminantPresenceProtection, + toContaminantProtectionStatus(current.contaminantProtectionStatus), + current.supportsEnableContaminantPresenceDetection, + current.contaminantDetectionStatus, + toIntArray(current.usbDataStatus), + current.powerTransferLimited, + current.powerBrickStatus); + newPortInfo.add(temp); + UsbPortManager.logAndPrint(Log.INFO, mPw, "ClientCallback AIDL V1: " + + current.portName); + } + mPortManager.updatePorts(newPortInfo); + } + + @Override + public void notifyRoleSwitchStatus(String portName, PortRole role, int retval, + long operationID) { + if (retval == Status.SUCCESS) { + UsbPortManager.logAndPrint(Log.INFO, mPw, portName + + " role switch successful. opID:" + + operationID); + } else { + UsbPortManager.logAndPrint(Log.ERROR, mPw, portName + " role switch failed. err:" + + retval + + "opID:" + operationID); + } + } + + @Override + public void notifyQueryPortStatus(String portName, int retval, long operationID) { + if (retval == Status.SUCCESS) { + UsbPortManager.logAndPrint(Log.INFO, mPw, portName + ": opID:" + + operationID + " successful"); + } else { + UsbPortManager.logAndPrint(Log.ERROR, mPw, portName + ": opID:" + + operationID + " failed. err:" + retval); + } + } + + @Override + public void notifyEnableUsbDataStatus(String portName, boolean enable, int retval, + long operationID) { + if (retval == Status.SUCCESS) { + UsbPortManager.logAndPrint(Log.INFO, mPw, "notifyEnableUsbDataStatus:" + + portName + ": opID:" + + operationID + " enable:" + enable); + } else { + UsbPortManager.logAndPrint(Log.ERROR, mPw, portName + + "notifyEnableUsbDataStatus: opID:" + + operationID + " failed. err:" + retval); + } + try { + sCallbacks.get(operationID).onOperationComplete(retval == Status.SUCCESS + ? USB_OPERATION_SUCCESS + : USB_OPERATION_ERROR_INTERNAL); + } catch (RemoteException e) { + logAndPrintException(mPw, + "notifyEnableUsbDataStatus: Failed to call onOperationComplete", + e); + } + } + + @Override + public void notifyContaminantEnabledStatus(String portName, boolean enable, int retval, + long operationID) { + if (retval == Status.SUCCESS) { + UsbPortManager.logAndPrint(Log.INFO, mPw, "notifyContaminantEnabledStatus:" + + portName + ": opID:" + + operationID + " enable:" + enable); + } else { + UsbPortManager.logAndPrint(Log.ERROR, mPw, portName + + "notifyContaminantEnabledStatus: opID:" + + operationID + " failed. err:" + retval); + } + } + + @Override + public void notifyLimitPowerTransferStatus(String portName, boolean limit, int retval, + long operationID) { + if (retval == Status.SUCCESS) { + UsbPortManager.logAndPrint(Log.INFO, mPw, portName + ": opID:" + + operationID + " successful"); + } else { + UsbPortManager.logAndPrint(Log.ERROR, mPw, portName + + "notifyLimitPowerTransferStatus: opID:" + + operationID + " failed. err:" + retval); + } + try { + IUsbOperationInternal callback = sCallbacks.get(operationID); + if (callback != null) { + sCallbacks.get(operationID).onOperationComplete(retval == Status.SUCCESS + ? USB_OPERATION_SUCCESS + : USB_OPERATION_ERROR_INTERNAL); + } + } catch (RemoteException e) { + logAndPrintException(mPw, + "enableLimitPowerTransfer: Failed to call onOperationComplete", + e); + } + } + + @Override + public void notifyEnableUsbDataWhileDockedStatus(String portName, int retval, + long operationID) { + if (retval == Status.SUCCESS) { + UsbPortManager.logAndPrint(Log.INFO, mPw, portName + ": opID:" + + operationID + " successful"); + } else { + UsbPortManager.logAndPrint(Log.ERROR, mPw, portName + + "notifyEnableUsbDataWhileDockedStatus: opID:" + + operationID + " failed. err:" + retval); + } + try { + IUsbOperationInternal callback = sCallbacks.get(operationID); + if (callback != null) { + sCallbacks.get(operationID).onOperationComplete(retval == Status.SUCCESS + ? USB_OPERATION_SUCCESS + : USB_OPERATION_ERROR_INTERNAL); + } + } catch (RemoteException e) { + logAndPrintException(mPw, + "notifyEnableUsbDataWhileDockedStatus: Failed to call onOperationComplete", + e); + } + } + } +} diff --git a/services/usb/java/com/android/server/usb/hal/port/UsbPortHal.java b/services/usb/java/com/android/server/usb/hal/port/UsbPortHal.java new file mode 100644 index 000000000000..abfdd6f517cd --- /dev/null +++ b/services/usb/java/com/android/server/usb/hal/port/UsbPortHal.java @@ -0,0 +1,196 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.usb.hal.port; + +import android.annotation.IntDef; +import android.hardware.usb.IUsbOperationInternal; +import android.hardware.usb.UsbManager.UsbHalVersion; +import android.os.RemoteException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.String; + +/** + * @hide + */ +public interface UsbPortHal { + /** + * Power role: This USB port can act as a source (provide power). + * @hide + */ + public static final int HAL_POWER_ROLE_SOURCE = 1; + + /** + * Power role: This USB port can act as a sink (receive power). + * @hide + */ + public static final int HAL_POWER_ROLE_SINK = 2; + + @IntDef(prefix = { "HAL_POWER_ROLE_" }, value = { + HAL_POWER_ROLE_SOURCE, + HAL_POWER_ROLE_SINK + }) + @Retention(RetentionPolicy.SOURCE) + @interface HalUsbPowerRole{} + + /** + * Data role: This USB port can act as a host (access data services). + * @hide + */ + public static final int HAL_DATA_ROLE_HOST = 1; + + /** + * Data role: This USB port can act as a device (offer data services). + * @hide + */ + public static final int HAL_DATA_ROLE_DEVICE = 2; + + @IntDef(prefix = { "HAL_DATA_ROLE_" }, value = { + HAL_DATA_ROLE_HOST, + HAL_DATA_ROLE_DEVICE + }) + @Retention(RetentionPolicy.SOURCE) + @interface HalUsbDataRole{} + + /** + * This USB port can act as a downstream facing port (host). + * + * @hide + */ + public static final int HAL_MODE_DFP = 1; + + /** + * This USB port can act as an upstream facing port (device). + * + * @hide + */ + public static final int HAL_MODE_UFP = 2; + @IntDef(prefix = { "HAL_MODE_" }, value = { + HAL_MODE_DFP, + HAL_MODE_UFP, + }) + @Retention(RetentionPolicy.SOURCE) + @interface HalUsbPortMode{} + + /** + * UsbPortManager would call this when the system is done booting. + */ + public void systemReady(); + + /** + * Invoked to enable/disable contaminant presence detection on the USB port. + * + * @param portName Port Identifier. + * @param enable Enable contaminant presence detection when true. + * Disable when false. + * @param transactionId Used for tracking the current request and is passed down to the HAL + * implementation as needed. + */ + public void enableContaminantPresenceDetection(String portName, boolean enable, + long transactionId); + + /** + * Invoked to query port status of all the ports. + * + * @param transactionId Used for tracking the current request and is passed down to the HAL + * implementation as needed. + */ + public void queryPortStatus(long transactionId); + + /** + * Invoked to switch USB port mode. + * + * @param portName Port Identifier. + * @param mode New mode that the port is switching into. + * @param transactionId Used for tracking the current request and is passed down to the HAL + * implementation as needed. + */ + public void switchMode(String portName, @HalUsbPortMode int mode, long transactionId); + + /** + * Invoked to switch USB port power role. + * + * @param portName Port Identifier. + * @param powerRole New power role that the port is switching into. + * @param transactionId Used for tracking the current request and is passed down to the HAL + * implementation as needed. + */ + public void switchPowerRole(String portName, @HalUsbPowerRole int powerRole, + long transactionId); + + /** + * Invoked to switch USB port data role. + * + * @param portName Port Identifier. + * @param dataRole New data role that the port is switching into. + * @param transactionId Used for tracking the current request and is passed down to the HAL + * implementation as needed. + */ + public void switchDataRole(String portName, @HalUsbDataRole int dataRole, long transactionId); + + /** + * Invoked to query the version of current hal implementation. + */ + public @UsbHalVersion int getUsbHalVersion() throws RemoteException; + + /** + * Invoked to enable/disable UsbData on the specified port. + * + * @param portName Port Identifier. + * @param enable Enable USB data when true. + * Disable when false. + * @param transactionId Used for tracking the current request and is passed down to the HAL + * implementation as needed. + * @param callback callback object to be invoked to invoke the status of the operation upon + * completion. + * @param callback callback object to be invoked when the operation is complete. + * @return True when the operation is asynchronous. The caller of + * {@link UsbOperationInternal} must therefore call + * {@link UsbOperationInternal#waitForOperationComplete} for processing + * the result. + * False when the operation is synchronous. Caller can proceed reading the result + * through {@link UsbOperationInternal#getStatus} + */ + public boolean enableUsbData(String portName, boolean enable, long transactionId, + IUsbOperationInternal callback); + + /** + * Invoked to enable UsbData when disabled due to docking event. + * + * @param portName Port Identifier. + * @param transactionId Used for tracking the current request and is passed down to the HAL + * implementation as needed. + * @param callback callback object to be invoked to invoke the status of the operation upon + * completion. + */ + public void enableUsbDataWhileDocked(String portName, long transactionId, + IUsbOperationInternal callback); + + /** + * Invoked to enableLimitPowerTransfer on the specified port. + * + * @param portName Port Identifier. + * @param limit limit power transfer when true. Port wouldn't charge or power USB accessoried + * when set. + * Lift power transfer restrictions when false. + * @param transactionId Used for tracking the current request and is passed down to the HAL + * implementation as needed. + * @param callback callback object to be invoked to invoke the status of the operation upon + * completion. + */ + public void enableLimitPowerTransfer(String portName, boolean limit, long transactionId, + IUsbOperationInternal callback); +} diff --git a/services/usb/java/com/android/server/usb/hal/port/UsbPortHalInstance.java b/services/usb/java/com/android/server/usb/hal/port/UsbPortHalInstance.java new file mode 100644 index 000000000000..41f9faef99df --- /dev/null +++ b/services/usb/java/com/android/server/usb/hal/port/UsbPortHalInstance.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.usb.hal.port; + +import static com.android.server.usb.UsbPortManager.logAndPrint; + +import com.android.internal.util.IndentingPrintWriter; +import com.android.server.usb.hal.port.UsbPortHidl; +import com.android.server.usb.hal.port.UsbPortAidl; +import com.android.server.usb.UsbPortManager; + +import android.util.Log; +/** + * Helper class that queries the underlying hal layer to populate UsbPortHal instance. + */ +public final class UsbPortHalInstance { + + public static UsbPortHal getInstance(UsbPortManager portManager, IndentingPrintWriter pw) { + + logAndPrint(Log.DEBUG, null, "Querying USB HAL version"); + if (UsbPortHidl.isServicePresent(null)) { + logAndPrint(Log.INFO, null, "USB HAL HIDL present"); + return new UsbPortHidl(portManager, pw); + } + if (UsbPortAidl.isServicePresent(null)) { + logAndPrint(Log.INFO, null, "USB HAL AIDL present"); + return new UsbPortAidl(portManager, pw); + } + + return null; + } +} diff --git a/services/usb/java/com/android/server/usb/hal/port/UsbPortHidl.java b/services/usb/java/com/android/server/usb/hal/port/UsbPortHidl.java new file mode 100644 index 000000000000..c1d76355e75b --- /dev/null +++ b/services/usb/java/com/android/server/usb/hal/port/UsbPortHidl.java @@ -0,0 +1,500 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.usb.hal.port; + +import static android.hardware.usb.UsbManager.USB_HAL_NOT_SUPPORTED; +import static android.hardware.usb.UsbManager.USB_HAL_V1_0; +import static android.hardware.usb.UsbManager.USB_HAL_V1_1; +import static android.hardware.usb.UsbManager.USB_HAL_V1_2; +import static android.hardware.usb.UsbManager.USB_HAL_V1_3; +import static android.hardware.usb.UsbOperationInternal.USB_OPERATION_ERROR_INTERNAL; +import static android.hardware.usb.UsbOperationInternal.USB_OPERATION_ERROR_NOT_SUPPORTED; +import static android.hardware.usb.UsbOperationInternal.USB_OPERATION_SUCCESS; +import static android.hardware.usb.UsbPortStatus.CONTAMINANT_DETECTION_NOT_SUPPORTED; +import static android.hardware.usb.UsbPortStatus.CONTAMINANT_PROTECTION_NONE; +import static android.hardware.usb.UsbPortStatus.DATA_ROLE_DEVICE; +import static android.hardware.usb.UsbPortStatus.DATA_ROLE_HOST; +import static android.hardware.usb.UsbPortStatus.MODE_DFP; +import static android.hardware.usb.UsbPortStatus.MODE_DUAL; +import static android.hardware.usb.UsbPortStatus.MODE_UFP; +import static android.hardware.usb.UsbPortStatus.POWER_BRICK_STATUS_UNKNOWN; +import static android.hardware.usb.UsbPortStatus.POWER_ROLE_SINK; +import static android.hardware.usb.UsbPortStatus.POWER_ROLE_SOURCE; +import static android.hardware.usb.UsbPortStatus.USB_DATA_STATUS_DISABLED_FORCE; +import static android.hardware.usb.UsbPortStatus.USB_DATA_STATUS_UNKNOWN; + + +import static com.android.server.usb.UsbPortManager.logAndPrint; +import static com.android.server.usb.UsbPortManager.logAndPrintException; + +import android.annotation.Nullable; +import android.hardware.usb.IUsbOperationInternal; +import android.hardware.usb.UsbManager.UsbHalVersion; +import android.hardware.usb.UsbPort; +import android.hardware.usb.UsbPortStatus; +import android.hardware.usb.V1_0.IUsb; +import android.hardware.usb.V1_0.PortRoleType; +import android.hardware.usb.V1_0.Status; +import android.hardware.usb.V1_1.PortStatus_1_1; +import android.hardware.usb.V1_2.IUsbCallback; +import android.hardware.usb.V1_0.PortRole; +import android.hardware.usb.V1_2.PortStatus; +import android.hidl.manager.V1_0.IServiceManager; +import android.hidl.manager.V1_0.IServiceNotification; +import android.os.IHwBinder; +import android.os.RemoteException; +import android.util.Log; +import android.util.Slog; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.util.IndentingPrintWriter; +import com.android.server.usb.UsbPortManager; +import com.android.server.usb.hal.port.RawPortInfo; + +import java.util.ArrayList; +import java.util.NoSuchElementException; +import java.util.Objects; +/** + * + */ +public final class UsbPortHidl implements UsbPortHal { + private static final String TAG = UsbPortHidl.class.getSimpleName(); + // Cookie sent for usb hal death notification. + private static final int USB_HAL_DEATH_COOKIE = 1000; + // Proxy object for the usb hal daemon. + @GuardedBy("mLock") + private IUsb mProxy; + private UsbPortManager mPortManager; + public IndentingPrintWriter mPw; + // Mutex for all mutable shared state. + private final Object mLock = new Object(); + // Callback when the UsbPort status is changed by the kernel. + private HALCallback mHALCallback; + private boolean mSystemReady; + // Workaround since HIDL HAL versions report UsbDataEnabled status in UsbPortStatus; + private static int sUsbDataStatus = USB_DATA_STATUS_UNKNOWN; + + public @UsbHalVersion int getUsbHalVersion() throws RemoteException { + int version; + synchronized(mLock) { + if (mProxy == null) { + throw new RemoteException("IUsb not initialized yet"); + } + if (android.hardware.usb.V1_3.IUsb.castFrom(mProxy) != null) { + version = USB_HAL_V1_3; + } else if (android.hardware.usb.V1_2.IUsb.castFrom(mProxy) != null) { + version = USB_HAL_V1_2; + } else if (android.hardware.usb.V1_1.IUsb.castFrom(mProxy) != null) { + version = USB_HAL_V1_1; + } else { + version = USB_HAL_V1_0; + } + logAndPrint(Log.INFO, null, "USB HAL HIDL version: " + version); + return version; + } + } + + final class DeathRecipient implements IHwBinder.DeathRecipient { + public IndentingPrintWriter pw; + + DeathRecipient(IndentingPrintWriter pw) { + this.pw = pw; + } + + @Override + public void serviceDied(long cookie) { + if (cookie == USB_HAL_DEATH_COOKIE) { + logAndPrint(Log.ERROR, pw, "Usb hal service died cookie: " + cookie); + synchronized (mLock) { + mProxy = null; + } + } + } + } + + final class ServiceNotification extends IServiceNotification.Stub { + @Override + public void onRegistration(String fqName, String name, boolean preexisting) { + logAndPrint(Log.INFO, null, "Usb hal service started " + fqName + " " + name); + connectToProxy(null); + } + } + + private void connectToProxy(IndentingPrintWriter pw) { + synchronized (mLock) { + if (mProxy != null) { + return; + } + + try { + mProxy = IUsb.getService(); + mProxy.linkToDeath(new DeathRecipient(pw), USB_HAL_DEATH_COOKIE); + mProxy.setCallback(mHALCallback); + mProxy.queryPortStatus(); + //updateUsbHalVersion(); + } catch (NoSuchElementException e) { + logAndPrintException(pw, "connectToProxy: usb hal service not found." + + " Did the service fail to start?", e); + } catch (RemoteException e) { + logAndPrintException(pw, "connectToProxy: usb hal service not responding", e); + } + } + } + + @Override + public void systemReady() { + mSystemReady = true; + } + + static boolean isServicePresent(IndentingPrintWriter pw) { + try { + IUsb.getService(true); + } catch (NoSuchElementException e) { + logAndPrintException(pw, "connectToProxy: usb hidl hal service not found.", e); + return false; + } catch (RemoteException e) { + logAndPrintException(pw, "IUSB hal service present but failed to get service", e); + } + + return true; + } + + public UsbPortHidl(UsbPortManager portManager, IndentingPrintWriter pw) { + mPortManager = Objects.requireNonNull(portManager); + mPw = pw; + mHALCallback = new HALCallback(null, mPortManager, this); + try { + ServiceNotification serviceNotification = new ServiceNotification(); + + boolean ret = IServiceManager.getService() + .registerForNotifications("android.hardware.usb@1.0::IUsb", + "", serviceNotification); + if (!ret) { + logAndPrint(Log.ERROR, null, + "Failed to register service start notification"); + } + } catch (RemoteException e) { + logAndPrintException(null, + "Failed to register service start notification", e); + return; + } + connectToProxy(mPw); + } + + @Override + public void enableContaminantPresenceDetection(String portName, boolean enable, + long transactionId) { + synchronized (mLock) { + if (mProxy == null) { + logAndPrint(Log.ERROR, mPw, "Proxy is null. Retry !"); + return; + } + + try { + // Oneway call into the hal. Use the castFrom method from HIDL. + android.hardware.usb.V1_2.IUsb proxy = + android.hardware.usb.V1_2.IUsb.castFrom(mProxy); + proxy.enableContaminantPresenceDetection(portName, enable); + } catch (RemoteException e) { + logAndPrintException(mPw, "Failed to set contaminant detection", e); + } catch (ClassCastException e) { + logAndPrintException(mPw, "Method only applicable to V1.2 or above implementation", + e); + } + } + } + + @Override + public void queryPortStatus(long transactionId) { + synchronized (mLock) { + if (mProxy == null) { + logAndPrint(Log.ERROR, mPw, "Proxy is null. Retry !"); + return; + } + + try { + mProxy.queryPortStatus(); + } catch (RemoteException e) { + logAndPrintException(null, "ServiceStart: Failed to query port status", e); + } + } + } + + @Override + public void switchMode(String portId, @HalUsbPortMode int newMode, long transactionId) { + synchronized (mLock) { + if (mProxy == null) { + logAndPrint(Log.ERROR, mPw, "Proxy is null. Retry !"); + return; + } + + PortRole newRole = new PortRole(); + newRole.type = PortRoleType.MODE; + newRole.role = newMode; + try { + mProxy.switchRole(portId, newRole); + } catch (RemoteException e) { + logAndPrintException(mPw, "Failed to set the USB port mode: " + + "portId=" + portId + + ", newMode=" + UsbPort.modeToString(newRole.role), e); + } + } + } + + @Override + public void switchPowerRole(String portId, @HalUsbPowerRole int newPowerRole, + long transactionId) { + synchronized (mLock) { + if (mProxy == null) { + logAndPrint(Log.ERROR, mPw, "Proxy is null. Retry !"); + return; + } + + PortRole newRole = new PortRole(); + newRole.type = PortRoleType.POWER_ROLE; + newRole.role = newPowerRole; + try { + mProxy.switchRole(portId, newRole); + } catch (RemoteException e) { + logAndPrintException(mPw, "Failed to set the USB power role: portId=" + portId + + ", newPowerRole=" + UsbPort.powerRoleToString(newRole.role), e); + } + } + } + + @Override + public void enableLimitPowerTransfer(String portName, boolean limit, long transactionId, + IUsbOperationInternal callback) { + /* Not supported in HIDL hals*/ + try { + callback.onOperationComplete(USB_OPERATION_ERROR_NOT_SUPPORTED); + } catch (RemoteException e) { + logAndPrintException(mPw, "Failed to call onOperationComplete", e); + } + } + + @Override + public void enableUsbDataWhileDocked(String portName, long transactionId, + IUsbOperationInternal callback) { + /* Not supported in HIDL hals*/ + try { + callback.onOperationComplete(USB_OPERATION_ERROR_NOT_SUPPORTED); + } catch (RemoteException e) { + logAndPrintException(mPw, "Failed to call onOperationComplete", e); + } + } + + @Override + public void switchDataRole(String portId, @HalUsbDataRole int newDataRole, long transactionId) { + synchronized (mLock) { + if (mProxy == null) { + logAndPrint(Log.ERROR, mPw, "Proxy is null. Retry !"); + return; + } + + PortRole newRole = new PortRole(); + newRole.type = PortRoleType.DATA_ROLE; + newRole.role = newDataRole; + try { + mProxy.switchRole(portId, newRole); + } catch (RemoteException e) { + logAndPrintException(mPw, "Failed to set the USB data role: portId=" + portId + + ", newDataRole=" + UsbPort.dataRoleToString(newRole.role), e); + } + } + } + + @Override + public boolean enableUsbData(String portName, boolean enable, long transactionId, + IUsbOperationInternal callback) { + int halVersion; + + try { + halVersion = getUsbHalVersion(); + } catch (RemoteException e) { + logAndPrintException(mPw, "Failed to query USB HAL version. opID:" + + transactionId + + " portId:" + portName, e); + return false; + } + + if (halVersion != USB_HAL_V1_3) { + try { + callback.onOperationComplete(USB_OPERATION_ERROR_NOT_SUPPORTED); + } catch (RemoteException e) { + logAndPrintException(mPw, "Failed to call onOperationComplete. opID:" + + transactionId + + " portId:" + portName, e); + } + return false; + } + + boolean success; + synchronized(mLock) { + try { + android.hardware.usb.V1_3.IUsb proxy + = android.hardware.usb.V1_3.IUsb.castFrom(mProxy); + success = proxy.enableUsbDataSignal(enable); + } catch (RemoteException e) { + logAndPrintException(mPw, "Failed enableUsbData: opId:" + transactionId + + " portId=" + portName , e); + try { + callback.onOperationComplete(USB_OPERATION_ERROR_INTERNAL); + } catch (RemoteException r) { + logAndPrintException(mPw, "Failed to call onOperationComplete. opID:" + + transactionId + + " portId:" + portName, r); + } + return false; + } + } + if (success) { + sUsbDataStatus = enable ? USB_DATA_STATUS_UNKNOWN : USB_DATA_STATUS_DISABLED_FORCE; + } + try { + callback.onOperationComplete(success + ? USB_OPERATION_SUCCESS + : USB_OPERATION_ERROR_INTERNAL); + } catch (RemoteException r) { + logAndPrintException(mPw, "Failed to call onOperationComplete. opID:" + + transactionId + + " portId:" + portName, r); + } + return false; + } + + private static class HALCallback extends IUsbCallback.Stub { + public IndentingPrintWriter mPw; + public UsbPortManager mPortManager; + public UsbPortHidl mUsbPortHidl; + + HALCallback(IndentingPrintWriter pw, UsbPortManager portManager, UsbPortHidl usbPortHidl) { + this.mPw = pw; + this.mPortManager = portManager; + this.mUsbPortHidl = usbPortHidl; + } + + public void notifyPortStatusChange( + ArrayList<android.hardware.usb.V1_0.PortStatus> currentPortStatus, int retval) { + if (!mUsbPortHidl.mSystemReady) { + return; + } + + if (retval != Status.SUCCESS) { + UsbPortManager.logAndPrint(Log.ERROR, mPw, "port status enquiry failed"); + return; + } + + ArrayList<RawPortInfo> newPortInfo = new ArrayList<>(); + + for (android.hardware.usb.V1_0.PortStatus current : currentPortStatus) { + RawPortInfo temp = new RawPortInfo(current.portName, + current.supportedModes, CONTAMINANT_PROTECTION_NONE, + current.currentMode, + current.canChangeMode, current.currentPowerRole, + current.canChangePowerRole, + current.currentDataRole, current.canChangeDataRole, + false, CONTAMINANT_PROTECTION_NONE, + false, CONTAMINANT_DETECTION_NOT_SUPPORTED, new int[sUsbDataStatus], + false, POWER_BRICK_STATUS_UNKNOWN); + newPortInfo.add(temp); + UsbPortManager.logAndPrint(Log.INFO, mPw, "ClientCallback V1_0: " + + current.portName); + } + + mPortManager.updatePorts(newPortInfo); + } + + + public void notifyPortStatusChange_1_1(ArrayList<PortStatus_1_1> currentPortStatus, + int retval) { + if (!mUsbPortHidl.mSystemReady) { + return; + } + + if (retval != Status.SUCCESS) { + UsbPortManager.logAndPrint(Log.ERROR, mPw, "port status enquiry failed"); + return; + } + + ArrayList<RawPortInfo> newPortInfo = new ArrayList<>(); + + int numStatus = currentPortStatus.size(); + for (int i = 0; i < numStatus; i++) { + PortStatus_1_1 current = currentPortStatus.get(i); + RawPortInfo temp = new RawPortInfo(current.status.portName, + current.supportedModes, CONTAMINANT_PROTECTION_NONE, + current.currentMode, + current.status.canChangeMode, current.status.currentPowerRole, + current.status.canChangePowerRole, + current.status.currentDataRole, current.status.canChangeDataRole, + false, CONTAMINANT_PROTECTION_NONE, + false, CONTAMINANT_DETECTION_NOT_SUPPORTED, new int[sUsbDataStatus], + false, POWER_BRICK_STATUS_UNKNOWN); + newPortInfo.add(temp); + UsbPortManager.logAndPrint(Log.INFO, mPw, "ClientCallback V1_1: " + + current.status.portName); + } + mPortManager.updatePorts(newPortInfo); + } + + public void notifyPortStatusChange_1_2( + ArrayList<PortStatus> currentPortStatus, int retval) { + if (!mUsbPortHidl.mSystemReady) { + return; + } + + if (retval != Status.SUCCESS) { + UsbPortManager.logAndPrint(Log.ERROR, mPw, "port status enquiry failed"); + return; + } + + ArrayList<RawPortInfo> newPortInfo = new ArrayList<>(); + + int numStatus = currentPortStatus.size(); + for (int i = 0; i < numStatus; i++) { + PortStatus current = currentPortStatus.get(i); + RawPortInfo temp = new RawPortInfo(current.status_1_1.status.portName, + current.status_1_1.supportedModes, + current.supportedContaminantProtectionModes, + current.status_1_1.currentMode, + current.status_1_1.status.canChangeMode, + current.status_1_1.status.currentPowerRole, + current.status_1_1.status.canChangePowerRole, + current.status_1_1.status.currentDataRole, + current.status_1_1.status.canChangeDataRole, + current.supportsEnableContaminantPresenceProtection, + current.contaminantProtectionStatus, + current.supportsEnableContaminantPresenceDetection, + current.contaminantDetectionStatus, + new int[sUsbDataStatus], + false, POWER_BRICK_STATUS_UNKNOWN); + newPortInfo.add(temp); + UsbPortManager.logAndPrint(Log.INFO, mPw, "ClientCallback V1_2: " + + current.status_1_1.status.portName); + } + mPortManager.updatePorts(newPortInfo); + } + + public void notifyRoleSwitchStatus(String portName, PortRole role, int retval) { + if (retval == Status.SUCCESS) { + UsbPortManager.logAndPrint(Log.INFO, mPw, portName + " role switch successful"); + } else { + UsbPortManager.logAndPrint(Log.ERROR, mPw, portName + " role switch failed"); + } + } + } +} diff --git a/services/wallpapereffectsgeneration/OWNERS b/services/wallpapereffectsgeneration/OWNERS new file mode 100644 index 000000000000..d2d3e2c0a7b6 --- /dev/null +++ b/services/wallpapereffectsgeneration/OWNERS @@ -0,0 +1,4 @@ +susharon@google.com +shanh@google.com +huiwu@google.com +srazdan@google.com diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java index ce9530c196ef..02c137990202 100644 --- a/telecomm/java/android/telecom/Call.java +++ b/telecomm/java/android/telecom/Call.java @@ -43,6 +43,7 @@ import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; /** @@ -571,7 +572,7 @@ public final class Call { public static final int CAPABILITY_REMOTE_PARTY_SUPPORTS_RTT = 0x10000000; //****************************************************************************************** - // Next CAPABILITY value: 0x20000000 + // Next CAPABILITY value: 0x40000000 //****************************************************************************************** /** @@ -733,6 +734,8 @@ public final class Call { private final String mContactDisplayName; private final @CallDirection int mCallDirection; private final @Connection.VerificationStatus int mCallerNumberVerificationStatus; + private final CallEndpoint mActiveCallEndpoint; + private final Set<CallEndpoint> mAvailableCallEndpoint; /** * Whether the supplied capabilities supports the specified capability. @@ -1116,32 +1119,52 @@ public final class Call { return mCallerNumberVerificationStatus; } + /** + * Return set of available {@link CallEndpoint} which can be used to push or answer this + * call via {@link #pushCall(CallEndpoint)} or {@link #answerCall(CallEndpoint, int)}. + * @return Set of available call endpoints. + */ + public @NonNull Set<CallEndpoint> getAvailableCallEndpoints() { + return mAvailableCallEndpoint; + } + + /** + * Return the {@link CallEndpoint} which is currently active for a call. If the call does + * not take place via any {@link CallEndpoint}, return {@code null}. + * @return Current active endpoint. + */ + public @Nullable CallEndpoint getActiveCallEndpoint() { + return mActiveCallEndpoint; + } + @Override public boolean equals(Object o) { if (o instanceof Details) { Details d = (Details) o; return - Objects.equals(mState, d.mState) && - Objects.equals(mHandle, d.mHandle) && - Objects.equals(mHandlePresentation, d.mHandlePresentation) && - Objects.equals(mCallerDisplayName, d.mCallerDisplayName) && - Objects.equals(mCallerDisplayNamePresentation, - d.mCallerDisplayNamePresentation) && - Objects.equals(mAccountHandle, d.mAccountHandle) && - Objects.equals(mCallCapabilities, d.mCallCapabilities) && - Objects.equals(mCallProperties, d.mCallProperties) && - Objects.equals(mDisconnectCause, d.mDisconnectCause) && - Objects.equals(mConnectTimeMillis, d.mConnectTimeMillis) && - Objects.equals(mGatewayInfo, d.mGatewayInfo) && - Objects.equals(mVideoState, d.mVideoState) && - Objects.equals(mStatusHints, d.mStatusHints) && - areBundlesEqual(mExtras, d.mExtras) && - areBundlesEqual(mIntentExtras, d.mIntentExtras) && - Objects.equals(mCreationTimeMillis, d.mCreationTimeMillis) && - Objects.equals(mContactDisplayName, d.mContactDisplayName) && - Objects.equals(mCallDirection, d.mCallDirection) && - Objects.equals(mCallerNumberVerificationStatus, - d.mCallerNumberVerificationStatus); + Objects.equals(mState, d.mState) + && Objects.equals(mHandle, d.mHandle) + && Objects.equals(mHandlePresentation, d.mHandlePresentation) + && Objects.equals(mCallerDisplayName, d.mCallerDisplayName) + && Objects.equals(mCallerDisplayNamePresentation, + d.mCallerDisplayNamePresentation) + && Objects.equals(mAccountHandle, d.mAccountHandle) + && Objects.equals(mCallCapabilities, d.mCallCapabilities) + && Objects.equals(mCallProperties, d.mCallProperties) + && Objects.equals(mDisconnectCause, d.mDisconnectCause) + && Objects.equals(mConnectTimeMillis, d.mConnectTimeMillis) + && Objects.equals(mGatewayInfo, d.mGatewayInfo) + && Objects.equals(mVideoState, d.mVideoState) + && Objects.equals(mStatusHints, d.mStatusHints) + && areBundlesEqual(mExtras, d.mExtras) + && areBundlesEqual(mIntentExtras, d.mIntentExtras) + && Objects.equals(mCreationTimeMillis, d.mCreationTimeMillis) + && Objects.equals(mContactDisplayName, d.mContactDisplayName) + && Objects.equals(mCallDirection, d.mCallDirection) + && Objects.equals(mCallerNumberVerificationStatus, + d.mCallerNumberVerificationStatus) + && Objects.equals(mActiveCallEndpoint, d.mActiveCallEndpoint) + && Objects.equals(mAvailableCallEndpoint, d.mAvailableCallEndpoint); } return false; } @@ -1190,7 +1213,9 @@ public final class Call { long creationTimeMillis, String contactDisplayName, int callDirection, - int callerNumberVerificationStatus) { + int callerNumberVerificationStatus, + CallEndpoint activeCallEndpoint, + Set<CallEndpoint> availableCallEndpoints) { mState = state; mTelecomCallId = telecomCallId; mHandle = handle; @@ -1211,6 +1236,8 @@ public final class Call { mContactDisplayName = contactDisplayName; mCallDirection = callDirection; mCallerNumberVerificationStatus = callerNumberVerificationStatus; + mActiveCallEndpoint = activeCallEndpoint; + mAvailableCallEndpoint = availableCallEndpoints; } /** {@hide} */ @@ -1235,7 +1262,9 @@ public final class Call { parcelableCall.getCreationTimeMillis(), parcelableCall.getContactDisplayName(), parcelableCall.getCallDirection(), - parcelableCall.getCallerNumberVerificationStatus()); + parcelableCall.getCallerNumberVerificationStatus(), + parcelableCall.getActiveCallEndpoint(), + parcelableCall.getAvailableCallEndpoints()); } @Override @@ -1257,6 +1286,10 @@ public final class Call { sb.append(capabilitiesToString(mCallCapabilities)); sb.append(", props: "); sb.append(propertiesToString(mCallProperties)); + sb.append(", activeEndpoint: "); + sb.append(mActiveCallEndpoint); + sb.append(", availableEndpoints: "); + sb.append(mAvailableCallEndpoint); sb.append("]"); return sb.toString(); } @@ -1356,6 +1389,121 @@ public final class Call { public static final int HANDOVER_FAILURE_UNKNOWN = 5; /** + * @hide + */ + @IntDef(prefix = { "PUSH_FAILED_" }, + value = {PUSH_FAILED_UNKNOWN_REASON, PUSH_FAILED_ENDPOINT_UNAVAILABLE, + PUSH_FAILED_ENDPOINT_TIMEOUT, PUSH_FAILED_ENDPOINT_REJECTED}) + @Retention(RetentionPolicy.SOURCE) + public @interface PushFailedReason {} + + /** + * Answer failure reason returned via {@link #onAnswerFailed(CallEndpoint, int)} when a push + * fails due to unknown reason. + * <p> + * For more information on push call, see {@link #pushCall(CallEndpoint)}. + */ + public static final int PUSH_FAILED_UNKNOWN_REASON = 0; + + /** + * Push failure reason returned via {@link #onCallPushFailed(CallEndpoint, int)} when a push + * fails due to requested endpoint is unavailable. + * <p> + * For more information on push call, see {@link #pushCall(CallEndpoint)}. + */ + public static final int PUSH_FAILED_ENDPOINT_UNAVAILABLE = 1; + + /** + * Push failure reason returned via {@link #onCallPushFailed(CallEndpoint, int)} when a push + * fails due to requested endpoint takes too long to handle the request. + * <p> + * For more information on push call, see {@link #pushCall(CallEndpoint)}. + */ + public static final int PUSH_FAILED_ENDPOINT_TIMEOUT = 2; + + /** + * Push failure reason returned via {@link #onCallPushFailed(CallEndpoint, int)} when a push + * fails due to endpoint rejected the request. + * <p> + * For more information on push call, see {@link #pushCall(CallEndpoint)}. + */ + public static final int PUSH_FAILED_ENDPOINT_REJECTED = 3; + + /** + * @hide + */ + @IntDef(prefix = { "ANSWER_FAILED_" }, + value = {ANSWER_FAILED_UNKNOWN_REASON, ANSWER_FAILED_ENDPOINT_UNAVAILABLE, + ANSWER_FAILED_ENDPOINT_TIMEOUT, ANSWER_FAILED_ENDPOINT_REJECTED}) + @Retention(RetentionPolicy.SOURCE) + public @interface AnswerFailedReason {} + + /** + * Answer failure reason returned via {@link #onAnswerFailed(CallEndpoint, int)} when it + * fails due to unknown reason. + * <p> + * For more information on answer call, see {@link #answerCall(CallEndpoint, int)}. + */ + public static final int ANSWER_FAILED_UNKNOWN_REASON = 0; + + /** + * Answer failure reason returned via {@link #onAnswerFailed(CallEndpoint, int)} when it + * fails due to requested endpoint is unavailable. + * <p> + * For more information on answer call, see {@link #answerCall(CallEndpoint, int)}. + */ + public static final int ANSWER_FAILED_ENDPOINT_UNAVAILABLE = 1; + + /** + * Answer failure reason returned via {@link #onAnswerFailed(CallEndpoint, int)} when it + * fails due to requested endpoint takes too long to handle the request. + * <p> + * For more information on answer call, see {@link #answerCall(CallEndpoint, int)}. + */ + public static final int ANSWER_FAILED_ENDPOINT_TIMEOUT = 2; + + /** + * Answer failure reason returned via {@link #onAnswerFailed(CallEndpoint, int)} when it + * fails due to endpoint rejected the request. + * <p> + * For more information on answer call, see {@link #answerCall(CallEndpoint, int)}. + */ + public static final int ANSWER_FAILED_ENDPOINT_REJECTED = 3; + + /** + * @hide + */ + @IntDef(prefix = { "PULL_FAILED_" }, + value = {PULL_FAILED_UNKNOWN_REASON, PULL_FAILED_ENDPOINT_TIMEOUT, + PULL_FAILED_ENDPOINT_REJECTED}) + @Retention(RetentionPolicy.SOURCE) + public @interface PullFailedReason {} + + /** + * Pull failure reason returned via {@link #onCallPullFailed(int)} when it fails due to + * unknown reason. + * <p> + * For more information on pull call, see {@link #pullCall()}. + */ + public static final int PULL_FAILED_UNKNOWN_REASON = 0; + + /** + * Pull failure reason returned via {@link #onCallPullFailed(int)} when it fails due to + * requested endpoint takes too long to handle the request. + * <p> + * For more information on pull call, see {@link #pullCall()}. + */ + public static final int PULL_FAILED_ENDPOINT_TIMEOUT = 1; + + /** + * Pull failure reason returned via {@link #onCallPullFailed(int)} when it fails due to + * endpoint rejected the request. + * <p> + * For more information on pull call, see {@link #pullCall()}. + */ + public static final int PULL_FAILED_ENDPOINT_REJECTED = 2; + + /** * Invoked when the state of this {@code Call} has changed. See {@link #getState()}. * * @param call The {@code Call} invoking this method. @@ -1515,6 +1663,31 @@ public final class Call { * @param failureReason Error reason for failure. */ public void onHandoverFailed(Call call, @HandoverFailureErrors int failureReason) {} + + /** + * Invoked when call push request via {@link #pushCall(CallEndpoint)} has failed. + * + * @param endpoint The endpoint requested to push the call to. + * @param reason Failed reason. + */ + public void onCallPushFailed(@NonNull CallEndpoint endpoint, @PushFailedReason int reason) + {} + + /** + * Invoked when answer call request via {@link #answerCall(CallEndpoint, int)} has failed. + * + * @param endpoint The endpoint requested to answer the call. + * @param reason Failed reason + */ + public void onAnswerFailed(@NonNull CallEndpoint endpoint, @AnswerFailedReason int reason) + {} + + /** + * Invoked when pull call request via {@link #pullCall()} has failed. + * + * @param reason Failed reason + */ + public void onCallPullFailed(@PullFailedReason int reason) {} } /** @@ -1936,8 +2109,21 @@ public final class Call { } /** + * @deprecated Use {@link #pullCall()} instead + */ + @Deprecated + public void pullExternalCall() { + // If this isn't an external call, ignore the request. + if (!mDetails.hasProperty(Details.PROPERTY_IS_EXTERNAL_CALL)) { + return; + } + + mInCallAdapter.pullExternalCall(mTelecomCallId); + } + + /** * Initiates a request to the {@link ConnectionService} to pull an external call to the local - * device. + * device, or to bring a tethered call back to the local device. * <p> * Calls to this method are ignored if the call does not have the * {@link Call.Details#PROPERTY_IS_EXTERNAL_CALL} property set. @@ -1946,13 +2132,34 @@ public final class Call { * {@link TelecomManager#METADATA_INCLUDE_EXTERNAL_CALLS} metadata set to {@code true} * in its manifest. */ - public void pullExternalCall() { - // If this isn't an external call, ignore the request. - if (!mDetails.hasProperty(Details.PROPERTY_IS_EXTERNAL_CALL)) { - return; - } + public void pullCall() { + pullExternalCall(); + } - mInCallAdapter.pullExternalCall(mTelecomCallId); + /** + * Initiates a request to the {@link ConnectionService} to push a call to a + * {@link CallEndpoint}. + * <p> + * + * @param endpoint The call endpoint to which the call will be pushed. + */ + public void pushCall(@NonNull CallEndpoint endpoint) { + mInCallAdapter.pushCall(mTelecomCallId, endpoint); + } + + /** + * Initiates a request to the {@link ConnectionService} to answer a call to a + * {@link CallEndpoint}. + * <p> + * Calls to this method are ignored if the call does not have the + * {@link Call.Details#CAPABILITY_CAN_PULL_CALL} capability set. + * + * @param endpoint The call endpoint on which to answer the call. + * @param videoState The video state in which to answer the call. + */ + public void answerCall(@NonNull CallEndpoint endpoint, + @VideoProfile.VideoState int videoState) { + mInCallAdapter.answerCall(mTelecomCallId, endpoint, videoState); } /** @@ -2633,7 +2840,9 @@ public final class Call { mDetails.getCreationTimeMillis(), mDetails.getContactDisplayName(), mDetails.getCallDirection(), - mDetails.getCallerNumberVerificationStatus() + mDetails.getCallerNumberVerificationStatus(), + mDetails.getActiveCallEndpoint(), + mDetails.getAvailableCallEndpoints() ); fireDetailsChanged(mDetails); } @@ -2675,7 +2884,7 @@ public final class Call { } /** {@hide} */ - final void internalOnHandoverComplete() { + void internalOnHandoverComplete() { for (CallbackRecord<Callback> record : mCallbackRecords) { final Call call = this; final Callback callback = record.getCallback(); @@ -2683,6 +2892,32 @@ public final class Call { } } + /** {@hide} */ + void internalOnCallPullFailed(@Callback.PullFailedReason int reason) { + for (CallbackRecord<Callback> record : mCallbackRecords) { + final Callback callback = record.getCallback(); + record.getHandler().post(() -> callback.onCallPullFailed(reason)); + } + } + + /** {@hide} */ + void internalOnCallPushFailed(CallEndpoint callEndpoint, + @Callback.PushFailedReason int reason) { + for (CallbackRecord<Callback> record : mCallbackRecords) { + final Callback callback = record.getCallback(); + record.getHandler().post(() -> callback.onCallPushFailed(callEndpoint, reason)); + } + } + + /** {@hide} */ + void internalOnAnswerFailed(CallEndpoint callEndpoint, + @Callback.AnswerFailedReason int reason) { + for (CallbackRecord<Callback> record : mCallbackRecords) { + final Callback callback = record.getCallback(); + record.getHandler().post(() -> callback.onAnswerFailed(callEndpoint, reason)); + } + } + private void fireStateChanged(final int newState) { for (CallbackRecord<Callback> record : mCallbackRecords) { final Call call = this; diff --git a/telecomm/java/android/telecom/CallAudioState.java b/telecomm/java/android/telecom/CallAudioState.java index 55957bd85eaa..389df80497df 100644 --- a/telecomm/java/android/telecom/CallAudioState.java +++ b/telecomm/java/android/telecom/CallAudioState.java @@ -259,10 +259,10 @@ public final class CallAudioState implements Parcelable { int route = source.readInt(); int supportedRouteMask = source.readInt(); BluetoothDevice activeBluetoothDevice = source.readParcelable( - ClassLoader.getSystemClassLoader()); + ClassLoader.getSystemClassLoader(), android.bluetooth.BluetoothDevice.class); List<BluetoothDevice> supportedBluetoothDevices = new ArrayList<>(); source.readParcelableList(supportedBluetoothDevices, - ClassLoader.getSystemClassLoader()); + ClassLoader.getSystemClassLoader(), android.bluetooth.BluetoothDevice.class); return new CallAudioState(isMuted, route, supportedRouteMask, activeBluetoothDevice, supportedBluetoothDevices); } diff --git a/telecomm/java/android/telecom/CallEndpoint.aidl b/telecomm/java/android/telecom/CallEndpoint.aidl new file mode 100644 index 000000000000..5030ffd62d7a --- /dev/null +++ b/telecomm/java/android/telecom/CallEndpoint.aidl @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.telecom; + +/** + * {@hide} + */ +parcelable CallEndpoint; diff --git a/telecomm/java/android/telecom/CallEndpoint.java b/telecomm/java/android/telecom/CallEndpoint.java new file mode 100644 index 000000000000..dc70656983bc --- /dev/null +++ b/telecomm/java/android/telecom/CallEndpoint.java @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.telecom; + + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.content.ComponentName; +import android.os.Parcel; +import android.os.ParcelUuid; +import android.os.Parcelable; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Objects; + +/** + * Represents the endpoint on which a call can be carried by the user. + * + * For example, the user may be able to carry out a call on another device on their local network + * using a call streaming solution, or may be able to carry out a call on another device registered + * with the same mobile line of service. + */ +public final class CallEndpoint implements Parcelable { + /** + * @hide + */ + @IntDef(prefix = {"ENDPOINT_TYPE_"}, + value = {ENDPOINT_TYPE_TETHERED, ENDPOINT_TYPE_UNTETHERED}) + @Retention(RetentionPolicy.SOURCE) + public @interface EndpointType {} + + /** Indicates the endpoint contains a complete calling stack and is capable of carrying out a + * call on its own. Untethered endpoints are typically other devices which share the same + * mobile line of service as the current device. + */ + public static final int ENDPOINT_TYPE_UNTETHERED = 1; + + /** Indicates the endpoint itself doesn't have the required calling infrastructure in order to + * complete a call on its own. Tethered endpoints depend on a call streaming solution to + * transport the media and control for a call to another device, while depending on the current + * device to connect the call to the mobile network. + */ + public static final int ENDPOINT_TYPE_TETHERED = 2; + + private final ParcelUuid mUuid; + private CharSequence mDescription; + private final int mType; + private final ComponentName mComponentName; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + mUuid.writeToParcel(dest, flags); + dest.writeCharSequence(mDescription); + dest.writeInt(mType); + mComponentName.writeToParcel(dest, flags); + } + + public static final @android.annotation.NonNull Creator<CallEndpoint> CREATOR = + new Creator<CallEndpoint>() { + @Override + public CallEndpoint createFromParcel(Parcel in) { + return new CallEndpoint(in); + } + + @Override + public CallEndpoint[] newArray(int size) { + return new CallEndpoint[size]; + } + }; + + public CallEndpoint(@NonNull ParcelUuid uuid, @NonNull CharSequence description, int type, + @NonNull ComponentName componentName) { + mUuid = uuid; + mDescription = description; + mType = type; + mComponentName = componentName; + } + + private CallEndpoint(@NonNull Parcel in) { + this(ParcelUuid.CREATOR.createFromParcel(in), in.readCharSequence(), in.readInt(), + ComponentName.CREATOR.createFromParcel(in)); + } + + /** + * A unique identifier for this call endpoint. An endpoint provider should take care to use an + * identifier which is stable for the current association between an endpoint and the current + * device, but which is not globally identifying. + * @return the unique identifier. + */ + public @NonNull ParcelUuid getIdentifier() { + return mUuid; + } + + /** + * A human-readable description of this {@link CallEndpoint}. An {@link InCallService} uses + * when informing the user of the endpoint. + * @return the description. + */ + public @NonNull CharSequence getDescription() { + return mDescription; + } + + public @EndpointType int getType() { + return mType; + } + + /** + * @hide + */ + public @NonNull ComponentName getComponentName() { + return mComponentName; + } + + @Override + public boolean equals(Object o) { + if (o instanceof CallEndpoint) { + CallEndpoint d = (CallEndpoint) o; + return Objects.equals(mUuid, d.mUuid) + && Objects.equals(mDescription, d.mDescription) + && Objects.equals(mType, d.mType) + && Objects.equals(mComponentName, d.mComponentName); + } + return false; + } + + @Override + public int hashCode() { + return Objects.hash(mUuid, mDescription, mType, mComponentName); + } +} diff --git a/telecomm/java/android/telecom/CallEndpointCallback.java b/telecomm/java/android/telecom/CallEndpointCallback.java new file mode 100644 index 000000000000..6ba55f103d5c --- /dev/null +++ b/telecomm/java/android/telecom/CallEndpointCallback.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.telecom; + +/** + * Provides callbacks from telecom to the cross device call streaming app with lifecycle events + * related to an {@link CallEndpointSession}. + */ +public interface CallEndpointCallback { + /** + * Invoked by telecom when a {@link CallEndpointSession} is started but the streaming app has + * not activated the endpoint in a timely manner and the framework deems the activation request + * to have timed out. + */ + void onCallEndpointSessionActivationTimeout(); + + /** + * Invoked by telecom when {@link CallEndpointSession#setCallEndpointSessionDeactivated()} + * called by a cross device call streaming app, or when the app uninstalled. When a tethered + * {@link CallEndpoint} is deactivated, the call streaming app should clean up any + * audio/network resources and stop relaying call controls from the endpoint. + */ + void onCallEndpointSessionDeactivated(); +} diff --git a/telecomm/java/android/telecom/CallEndpointSession.java b/telecomm/java/android/telecom/CallEndpointSession.java new file mode 100644 index 000000000000..1e7b30c79ee0 --- /dev/null +++ b/telecomm/java/android/telecom/CallEndpointSession.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.telecom; + +import android.annotation.IntDef; +import android.net.Uri; +import android.os.Bundle; +import android.os.RemoteException; + +import com.android.internal.telecom.ICallEndpointSession; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + + +/** + * Provides method and necessary information for cross device call streaming app to streams calls + * and updates to the status of the endpoint. + * + */ +public class CallEndpointSession { + /** + * Indicates that this call endpoint session is activated by + * {@link Call#answerCall(CallEndpoint, int)} from the original device. + */ + public static final int ANSWER_REQUEST = 1; + + /** + * Indicates that this call endpoint session is activated by {@link Call#pushCall(CallEndpoint)} + * from the original device. + */ + public static final int PUSH_REQUEST = 2; + + /** + * Indicates that this call endpoint session is activated by + * {@link TelecomManager#placeCall(Uri, Bundle)} with extra + * {@link TelecomManager#EXTRA_START_CALL_ON_ENDPOINT} set. + */ + public static final int PLACE_REQUEST = 3; + + /** + * @hide + */ + @IntDef(prefix = {"ACTIVATION_FAILURE_"}, + value = {ACTIVATION_FAILURE_REJECTED, ACTIVATION_FAILURE_UNAVAILABLE}) + @Retention(RetentionPolicy.SOURCE) + public @interface ActivationFailureReason {} + /** + * Used as reason for {@link #setCallEndpointSessionActivationFailed(int)} to inform the + * endpoint is no longer present on the network. + */ + public static final int ACTIVATION_FAILURE_UNAVAILABLE = 0; + + /** + * Used as reason for {@link #setCallEndpointSessionActivationFailed(int)} to inform the + * remote endpoint rejected the request to start streaming a cross device call. + */ + public static final int ACTIVATION_FAILURE_REJECTED = 1; + + private final ICallEndpointSession mCallEndpointSession; + + /** + * {@hide} + */ + public CallEndpointSession(ICallEndpointSession callEndpointSession) { + mCallEndpointSession = callEndpointSession; + } + + /** + * Invoked by cross device call streaming app to inform telecom stack that the call endpoint is + * now activated and that the call is being streamed to the endpoint. + */ + public void setCallEndpointSessionActivated() { + try { + mCallEndpointSession.setCallEndpointSessionActivated(); + } catch (RemoteException e) { + } + } + + /** + * Invoked by cross device call streaming app to inform telecom stack that the call endpoint + * could not be activated due to error. + * Possible errors are: + * <ul> + * <li>{@link #ACTIVATION_FAILURE_UNAVAILABLE}</li> + * <li>{@link #ACTIVATION_FAILURE_REJECTED}</li> + * </ul> + * + * @param reason The reason for activation failure + */ + public void setCallEndpointSessionActivationFailed(@ActivationFailureReason int reason) { + try { + mCallEndpointSession.setCallEndpointSessionActivationFailed(reason); + } catch (RemoteException e) { + } + } + + /** + * Invoked by cross device call streaming app to inform telecom stack that the call endpoint is + * no longer active. + */ + public void setCallEndpointSessionDeactivated() { + try { + mCallEndpointSession.setCallEndpointSessionDeactivated(); + } catch (RemoteException e) { + } + } +} diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java index d63cdc004a3d..21a180459978 100644 --- a/telecomm/java/android/telecom/Connection.java +++ b/telecomm/java/android/telecom/Connection.java @@ -561,15 +561,6 @@ public abstract class Connection extends Conferenceable { */ public static final int PROPERTY_CROSS_SIM = 1 << 13; - /** - * Connection is a tethered external call. - * <p> - * Indicates that the {@link Connection} is fixed on this device but the audio streams are - * re-routed to another device. - * <p> - */ - public static final int PROPERTY_TETHERED_CALL = 1 << 14; - //********************************************************************************************** // Next PROPERTY value: 1<<14 //********************************************************************************************** @@ -3546,9 +3537,9 @@ public abstract class Connection extends Conferenceable { mIsBlocked = in.readByte() != 0; mIsInContacts = in.readByte() != 0; CallScreeningService.ParcelableCallResponse response - = in.readParcelable(CallScreeningService.class.getClassLoader()); + = in.readParcelable(CallScreeningService.class.getClassLoader(), android.telecom.CallScreeningService.ParcelableCallResponse.class); mCallResponse = response == null ? null : response.toCallResponse(); - mCallScreeningComponent = in.readParcelable(ComponentName.class.getClassLoader()); + mCallScreeningComponent = in.readParcelable(ComponentName.class.getClassLoader(), android.content.ComponentName.class); } @NonNull diff --git a/telecomm/java/android/telecom/ConnectionRequest.java b/telecomm/java/android/telecom/ConnectionRequest.java index be5fae488d5e..1172e1392ef8 100644 --- a/telecomm/java/android/telecom/ConnectionRequest.java +++ b/telecomm/java/android/telecom/ConnectionRequest.java @@ -272,17 +272,17 @@ public final class ConnectionRequest implements Parcelable { } private ConnectionRequest(Parcel in) { - mAccountHandle = in.readParcelable(getClass().getClassLoader()); - mAddress = in.readParcelable(getClass().getClassLoader()); - mExtras = in.readParcelable(getClass().getClassLoader()); + mAccountHandle = in.readParcelable(getClass().getClassLoader(), android.telecom.PhoneAccountHandle.class); + mAddress = in.readParcelable(getClass().getClassLoader(), android.net.Uri.class); + mExtras = in.readParcelable(getClass().getClassLoader(), android.os.Bundle.class); mVideoState = in.readInt(); mTelecomCallId = in.readString(); mShouldShowIncomingCallUi = in.readInt() == 1; - mRttPipeFromInCall = in.readParcelable(getClass().getClassLoader()); - mRttPipeToInCall = in.readParcelable(getClass().getClassLoader()); + mRttPipeFromInCall = in.readParcelable(getClass().getClassLoader(), android.os.ParcelFileDescriptor.class); + mRttPipeToInCall = in.readParcelable(getClass().getClassLoader(), android.os.ParcelFileDescriptor.class); mParticipants = new ArrayList<Uri>(); - in.readList(mParticipants, getClass().getClassLoader()); + in.readList(mParticipants, getClass().getClassLoader(), android.net.Uri.class); mIsAdhocConference = in.readInt() == 1; } diff --git a/telecomm/java/android/telecom/DisconnectCause.java b/telecomm/java/android/telecom/DisconnectCause.java index ed7b79f62753..63b954850a9e 100644 --- a/telecomm/java/android/telecom/DisconnectCause.java +++ b/telecomm/java/android/telecom/DisconnectCause.java @@ -111,6 +111,22 @@ public final class DisconnectCause implements Parcelable { */ public static final String REASON_EMERGENCY_CALL_PLACED = "REASON_EMERGENCY_CALL_PLACED"; + /** + * This reason is set when an call is ended due to {@link CallEndpoint} rejection. + * This reason string should only be associated with the {@link #LOCAL} disconnect code returned + * from {@link #getCode()}. + */ + public static final String REASON_ENDPOINT_REJECTED = "REASON_ENDPOINT_REJECTED"; + + /** + * This reason is set when a call is ended due to {@link CallEndpoint} deactivated by + * call disconnection or user terminated streaming. + * This reason string should only be associated with the {@link #LOCAL} disconnect code returned + * from {@link #getCode()} + */ + public static final String REASON_ENDPOINT_SESSION_DEACTIVATED = + "REASON_ENDPOINT_SESSION_DEACTIVATED"; + private int mDisconnectCode; private CharSequence mDisconnectLabel; private CharSequence mDisconnectDescription; @@ -287,7 +303,7 @@ public final class DisconnectCause implements Parcelable { int tone = source.readInt(); int telephonyDisconnectCause = source.readInt(); int telephonyPreciseDisconnectCause = source.readInt(); - ImsReasonInfo imsReasonInfo = source.readParcelable(null); + ImsReasonInfo imsReasonInfo = source.readParcelable(null, android.telephony.ims.ImsReasonInfo.class); return new DisconnectCause(code, label, description, reason, tone, telephonyDisconnectCause, telephonyPreciseDisconnectCause, imsReasonInfo); } diff --git a/telecomm/java/android/telecom/InCallAdapter.java b/telecomm/java/android/telecom/InCallAdapter.java index ab35affe9099..34e9942a53c9 100755 --- a/telecomm/java/android/telecom/InCallAdapter.java +++ b/telecomm/java/android/telecom/InCallAdapter.java @@ -373,6 +373,34 @@ public final class InCallAdapter { } /** + * Instructs Telecom to push a call to the given endpoint. + * + * @param callId The callId to push. + * @param callEndpoint The endpoint to which the call will be pushed. + */ + public void pushCall(String callId, CallEndpoint callEndpoint) { + try { + mAdapter.pushCall(callId, callEndpoint); + } catch (RemoteException ignored) { + } + } + + /** + * Instructs Telecom to answer a call via the given endpoint. + * + * @param callId The callId to push. + * @param callEndpoint The endpoint on which the call will be answered. + * @param videoState The video state in which to answer the call. + */ + public void answerCall(String callId, CallEndpoint callEndpoint, + @VideoProfile.VideoState int videoState) { + try { + mAdapter.answerCallViaEndpoint(callId, callEndpoint, videoState); + } catch (RemoteException ignored) { + } + } + + /** * Intructs Telecom to send a call event. * * @param callId The callId to send the event for. diff --git a/telecomm/java/android/telecom/InCallService.java b/telecomm/java/android/telecom/InCallService.java index 0ddd52dfc76d..ecd6596b0e7d 100644 --- a/telecomm/java/android/telecom/InCallService.java +++ b/telecomm/java/android/telecom/InCallService.java @@ -30,9 +30,12 @@ import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.Message; +import android.os.RemoteException; import android.view.Surface; import com.android.internal.os.SomeArgs; +import com.android.internal.telecom.ICallEndpointCallback; +import com.android.internal.telecom.ICallEndpointSession; import com.android.internal.telecom.IInCallAdapter; import com.android.internal.telecom.IInCallService; @@ -258,6 +261,10 @@ public abstract class InCallService extends Service { private static final int MSG_ON_RTT_INITIATION_FAILURE = 11; private static final int MSG_ON_HANDOVER_FAILED = 12; private static final int MSG_ON_HANDOVER_COMPLETE = 13; + private static final int MSG_ON_PUSH_FAILED = 14; + private static final int MSG_ON_PULL_FAILED = 15; + private static final int MSG_ON_ANSWER_EXTERNAL_FAILED = 16; + private static final int MSG_ON_CALL_ENDPOINT_ACTIVATION_REQUEST = 17; /** Default Handler used to consolidate binder method calls onto a single thread. */ private final Handler mHandler = new Handler(Looper.getMainLooper()) { @@ -339,6 +346,66 @@ public abstract class InCallService extends Service { mPhone.internalOnHandoverComplete(callId); break; } + case MSG_ON_PUSH_FAILED: { + SomeArgs args = (SomeArgs) msg.obj; + try { + String callId = (String) args.arg1; + CallEndpoint callEndpoint = (CallEndpoint) args.arg2; + int reason = (int) args.arg3; + mPhone.internalOnCallPushFailed(callId, callEndpoint, reason); + } finally { + args.recycle(); + } + break; + } + case MSG_ON_PULL_FAILED: { + SomeArgs args = (SomeArgs) msg.obj; + try { + String callId = (String) args.arg1; + int reason = (int) args.arg2; + mPhone.internalOnCallPullFailed(callId, reason); + } finally { + args.recycle(); + } + break; + } + case MSG_ON_ANSWER_EXTERNAL_FAILED: { + SomeArgs args = (SomeArgs) msg.obj; + try { + String callId = (String) args.arg1; + CallEndpoint callEndpoint = (CallEndpoint) args.arg2; + int reason = (int) args.arg3; + mPhone.internalOnAnswerFailed(callId, callEndpoint, reason); + } finally { + args.recycle(); + } + break; + } + case MSG_ON_CALL_ENDPOINT_ACTIVATION_REQUEST: { + SomeArgs args = (SomeArgs) msg.obj; + try { + CallEndpoint callEndpoint = (CallEndpoint) args.arg1; + ICallEndpointSession iCallEndpointSession = + (ICallEndpointSession) args.arg2; + try { + mCallEndpointCallback = onCallEndpointActivationRequested(callEndpoint, + new CallEndpointSession(iCallEndpointSession)); + } catch (UnsupportedOperationException e) { + // This InCallService neglected to implement + // onCallEndpointActivationRequested, immediately signal back to Telecom + // that the activation failed. + try { + iCallEndpointSession.setCallEndpointSessionActivationFailed( + CallEndpointSession.ACTIVATION_FAILURE_UNAVAILABLE); + } catch (RemoteException re) { + // Ignore + } + } + } finally { + args.recycle(); + } + break; + } default: break; } @@ -353,6 +420,36 @@ public abstract class InCallService extends Service { } @Override + public ICallEndpointCallback requestCallEndpointActivation(CallEndpoint callEndpoint, + ICallEndpointSession callEndpointSession) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = callEndpoint; + args.arg2 = callEndpointSession; + mHandler.obtainMessage(MSG_ON_CALL_ENDPOINT_ACTIVATION_REQUEST, args).sendToTarget(); + + return new ICallEndpointCallback.Stub() { + @Override + public void onCallEndpointSessionActivationTimeout() throws RemoteException { + if (mCallEndpointCallback != null) { + mCallEndpointCallback.onCallEndpointSessionActivationTimeout(); + } + } + + @Override + public void onCallEndpointSessionDeactivated() throws RemoteException { + if (mCallEndpointCallback != null) { + mCallEndpointCallback.onCallEndpointSessionDeactivated(); + } + } + + @Override + public IBinder asBinder() { + return this; + } + }; + } + + @Override public void addCall(ParcelableCall call) { mHandler.obtainMessage(MSG_ADD_CALL, call).sendToTarget(); } @@ -424,6 +521,32 @@ public abstract class InCallService extends Service { public void onHandoverComplete(String callId) { mHandler.obtainMessage(MSG_ON_HANDOVER_COMPLETE, callId).sendToTarget(); } + + @Override + public void onCallPullFailed(String callId, int reason) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = callId; + args.arg2 = reason; + mHandler.obtainMessage(MSG_ON_PULL_FAILED, args).sendToTarget(); + } + + @Override + public void onCallPushFailed(String callId, CallEndpoint endpoint, int reason) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = callId; + args.arg2 = endpoint; + args.arg3 = reason; + mHandler.obtainMessage(MSG_ON_PUSH_FAILED, args).sendToTarget(); + } + + @Override + public void onAnswerFailed(String callId, CallEndpoint endpoint, int reason) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = callId; + args.arg2 = endpoint; + args.arg3 = reason; + mHandler.obtainMessage(MSG_ON_ANSWER_EXTERNAL_FAILED, args).sendToTarget(); + } } private Phone.Listener mPhoneListener = new Phone.Listener() { @@ -470,6 +593,8 @@ public abstract class InCallService extends Service { }; private Phone mPhone; + private CallEndpointSession mCallEndpointSession; + private CallEndpointCallback mCallEndpointCallback; public InCallService() { } @@ -494,6 +619,14 @@ public abstract class InCallService extends Service { onPhoneDestroyed(oldPhone); } + if (mCallEndpointCallback != null) { + mCallEndpointCallback = null; + } + + if (mCallEndpointSession != null) { + mCallEndpointSession = null; + } + return false; } @@ -704,6 +837,21 @@ public abstract class InCallService extends Service { } /** + * To handle the request from telecom to activate an endpoint session. Streaming app with + * meta-data {@link TelecomManager#METADATA_STREAMING_TETHERED_CALLS}. + * @param endpoint The endpoint which is to be activated. + * @param session An instance of {@link CallEndpointSession} to let streaming app report updates + * of the endpoint. + * @return CallEndpointCallback The implementation provided by streaming app. Telecom use this + * to report events related to the call endpoint session. + */ + public @NonNull CallEndpointCallback onCallEndpointActivationRequested( + @NonNull CallEndpoint endpoint, @NonNull CallEndpointSession session) + throws UnsupportedOperationException { + throw new UnsupportedOperationException(); + } + + /** * Used to issue commands to the {@link Connection.VideoProvider} associated with a * {@link Call}. */ diff --git a/telecomm/java/android/telecom/ParcelableCall.java b/telecomm/java/android/telecom/ParcelableCall.java index 320308c9e926..c42918324e23 100644 --- a/telecomm/java/android/telecom/ParcelableCall.java +++ b/telecomm/java/android/telecom/ParcelableCall.java @@ -16,6 +16,7 @@ package android.telecom; +import android.annotation.NonNull; import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; import android.net.Uri; @@ -29,8 +30,11 @@ import android.telecom.Call.Details.CallDirection; import com.android.internal.telecom.IVideoProvider; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; +import java.util.HashSet; import java.util.List; +import java.util.Set; /** * Information about a call that is used between InCallService and Telecom. @@ -69,6 +73,8 @@ public final class ParcelableCall implements Parcelable { private int mCallerNumberVerificationStatus; private String mContactDisplayName; private String mActiveChildCallId; + private CallEndpoint mActiveCallEndpoint; + private Set<CallEndpoint> mAvailableCallEndpoints = new HashSet<>(); public ParcelableCallBuilder setId(String id) { mId = id; @@ -224,6 +230,27 @@ public final class ParcelableCall implements Parcelable { return this; } + /** + * Set active call endpoint + * @param callEndpoint + * @return + */ + public ParcelableCallBuilder setActiveCallEndpoint(CallEndpoint callEndpoint) { + mActiveCallEndpoint = callEndpoint; + return this; + } + + /** + * Set available call endpoints + * @param availableCallEndpoints + * @return + */ + public ParcelableCallBuilder setAvailableCallEndpoints( + Set<CallEndpoint> availableCallEndpoints) { + mAvailableCallEndpoints = availableCallEndpoints; + return this; + } + public ParcelableCall createParcelableCall() { return new ParcelableCall( mId, @@ -255,7 +282,9 @@ public final class ParcelableCall implements Parcelable { mCallDirection, mCallerNumberVerificationStatus, mContactDisplayName, - mActiveChildCallId); + mActiveChildCallId, + mActiveCallEndpoint, + mAvailableCallEndpoints); } public static ParcelableCallBuilder fromParcelableCall(ParcelableCall parcelableCall) { @@ -292,6 +321,8 @@ public final class ParcelableCall implements Parcelable { parcelableCall.mCallerNumberVerificationStatus; newBuilder.mContactDisplayName = parcelableCall.mContactDisplayName; newBuilder.mActiveChildCallId = parcelableCall.mActiveChildCallId; + newBuilder.mActiveCallEndpoint = parcelableCall.mActiveCallEndpoint; + newBuilder.mAvailableCallEndpoints = parcelableCall.mAvailableCallEndpoints; return newBuilder; } } @@ -327,6 +358,8 @@ public final class ParcelableCall implements Parcelable { private final int mCallerNumberVerificationStatus; private final String mContactDisplayName; private final String mActiveChildCallId; // Only valid for CDMA conferences + private final CallEndpoint mActiveCallEndpoint; + private final Set<CallEndpoint> mAvailableCallEndpoints; public ParcelableCall( String id, @@ -358,7 +391,9 @@ public final class ParcelableCall implements Parcelable { int callDirection, int callerNumberVerificationStatus, String contactDisplayName, - String activeChildCallId + String activeChildCallId, + CallEndpoint activeCallEndpoint, + Set<CallEndpoint> availableCallEndpoints ) { mId = id; mState = state; @@ -390,6 +425,8 @@ public final class ParcelableCall implements Parcelable { mCallerNumberVerificationStatus = callerNumberVerificationStatus; mContactDisplayName = contactDisplayName; mActiveChildCallId = activeChildCallId; + mActiveCallEndpoint = activeCallEndpoint; + mAvailableCallEndpoints = availableCallEndpoints; } /** The unique ID of the call. */ @@ -614,6 +651,21 @@ public final class ParcelableCall implements Parcelable { return mActiveChildCallId; } + /** + * @return The {@link CallEndpoint} which is currently active for this call, or null if the call + * does not take place via an {@link CallEndpoint}. + */ + public @Nullable CallEndpoint getActiveCallEndpoint() { + return mActiveCallEndpoint; + } + + /** + * @return A set of available {@link CallEndpoint} + */ + public @NonNull Set<CallEndpoint> getAvailableCallEndpoints() { + return mAvailableCallEndpoints; + } + /** Responsible for creating ParcelableCall objects for deserialized Parcels. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) public static final @android.annotation.NonNull Parcelable.Creator<ParcelableCall> CREATOR = @@ -623,9 +675,9 @@ public final class ParcelableCall implements Parcelable { ClassLoader classLoader = ParcelableCall.class.getClassLoader(); String id = source.readString(); int state = source.readInt(); - DisconnectCause disconnectCause = source.readParcelable(classLoader); + DisconnectCause disconnectCause = source.readParcelable(classLoader, android.telecom.DisconnectCause.class); List<String> cannedSmsResponses = new ArrayList<>(); - source.readList(cannedSmsResponses, classLoader); + source.readList(cannedSmsResponses, classLoader, java.lang.String.class); int capabilities = source.readInt(); int properties = source.readInt(); long connectTimeMillis = source.readLong(); @@ -633,28 +685,31 @@ public final class ParcelableCall implements Parcelable { int handlePresentation = source.readInt(); String callerDisplayName = source.readString(); int callerDisplayNamePresentation = source.readInt(); - GatewayInfo gatewayInfo = source.readParcelable(classLoader); - PhoneAccountHandle accountHandle = source.readParcelable(classLoader); + GatewayInfo gatewayInfo = source.readParcelable(classLoader, android.telecom.GatewayInfo.class); + PhoneAccountHandle accountHandle = source.readParcelable(classLoader, android.telecom.PhoneAccountHandle.class); boolean isVideoCallProviderChanged = source.readByte() == 1; IVideoProvider videoCallProvider = IVideoProvider.Stub.asInterface(source.readStrongBinder()); String parentCallId = source.readString(); List<String> childCallIds = new ArrayList<>(); - source.readList(childCallIds, classLoader); - StatusHints statusHints = source.readParcelable(classLoader); + source.readList(childCallIds, classLoader, java.lang.String.class); + StatusHints statusHints = source.readParcelable(classLoader, android.telecom.StatusHints.class); int videoState = source.readInt(); List<String> conferenceableCallIds = new ArrayList<>(); - source.readList(conferenceableCallIds, classLoader); + source.readList(conferenceableCallIds, classLoader, java.lang.String.class); Bundle intentExtras = source.readBundle(classLoader); Bundle extras = source.readBundle(classLoader); int supportedAudioRoutes = source.readInt(); boolean isRttCallChanged = source.readByte() == 1; - ParcelableRttCall rttCall = source.readParcelable(classLoader); + ParcelableRttCall rttCall = source.readParcelable(classLoader, android.telecom.ParcelableRttCall.class); long creationTimeMillis = source.readLong(); int callDirection = source.readInt(); int callerNumberVerificationStatus = source.readInt(); String contactDisplayName = source.readString(); String activeChildCallId = source.readString(); + CallEndpoint activeCallEndpoint = source.readParcelable(classLoader); + List<CallEndpoint> availablableCallEndpoints = new ArrayList<>(); + source.readList(availablableCallEndpoints, classLoader); return new ParcelableCallBuilder() .setId(id) .setState(state) @@ -686,6 +741,8 @@ public final class ParcelableCall implements Parcelable { .setCallerNumberVerificationStatus(callerNumberVerificationStatus) .setContactDisplayName(contactDisplayName) .setActiveChildCallId(activeChildCallId) + .setActiveCallEndpoint(activeCallEndpoint) + .setAvailableCallEndpoints(new HashSet<>(availablableCallEndpoints)) .createParcelableCall(); } @@ -735,6 +792,8 @@ public final class ParcelableCall implements Parcelable { destination.writeInt(mCallerNumberVerificationStatus); destination.writeString(mContactDisplayName); destination.writeString(mActiveChildCallId); + destination.writeParcelable(mActiveCallEndpoint, 0); + destination.writeList(Arrays.asList(mAvailableCallEndpoints.toArray())); } @Override diff --git a/telecomm/java/android/telecom/ParcelableConference.java b/telecomm/java/android/telecom/ParcelableConference.java index 1f8aafbca476..e57c833e930e 100644 --- a/telecomm/java/android/telecom/ParcelableConference.java +++ b/telecomm/java/android/telecom/ParcelableConference.java @@ -292,24 +292,24 @@ public final class ParcelableConference implements Parcelable { @Override public ParcelableConference createFromParcel(Parcel source) { ClassLoader classLoader = ParcelableConference.class.getClassLoader(); - PhoneAccountHandle phoneAccount = source.readParcelable(classLoader); + PhoneAccountHandle phoneAccount = source.readParcelable(classLoader, android.telecom.PhoneAccountHandle.class); int state = source.readInt(); int capabilities = source.readInt(); List<String> connectionIds = new ArrayList<>(2); - source.readList(connectionIds, classLoader); + source.readList(connectionIds, classLoader, java.lang.String.class); long connectTimeMillis = source.readLong(); IVideoProvider videoCallProvider = IVideoProvider.Stub.asInterface(source.readStrongBinder()); int videoState = source.readInt(); - StatusHints statusHints = source.readParcelable(classLoader); + StatusHints statusHints = source.readParcelable(classLoader, android.telecom.StatusHints.class); Bundle extras = source.readBundle(classLoader); int properties = source.readInt(); long connectElapsedTimeMillis = source.readLong(); - Uri address = source.readParcelable(classLoader); + Uri address = source.readParcelable(classLoader, android.net.Uri.class); int addressPresentation = source.readInt(); String callerDisplayName = source.readString(); int callerDisplayNamePresentation = source.readInt(); - DisconnectCause disconnectCause = source.readParcelable(classLoader); + DisconnectCause disconnectCause = source.readParcelable(classLoader, android.telecom.DisconnectCause.class); boolean isRingbackRequested = source.readInt() == 1; int callDirection = source.readInt(); diff --git a/telecomm/java/android/telecom/ParcelableConnection.java b/telecomm/java/android/telecom/ParcelableConnection.java index 2b9ce9b46ad7..7b8333870eaf 100644 --- a/telecomm/java/android/telecom/ParcelableConnection.java +++ b/telecomm/java/android/telecom/ParcelableConnection.java @@ -261,10 +261,10 @@ public final class ParcelableConnection implements Parcelable { public ParcelableConnection createFromParcel(Parcel source) { ClassLoader classLoader = ParcelableConnection.class.getClassLoader(); - PhoneAccountHandle phoneAccount = source.readParcelable(classLoader); + PhoneAccountHandle phoneAccount = source.readParcelable(classLoader, android.telecom.PhoneAccountHandle.class); int state = source.readInt(); int capabilities = source.readInt(); - Uri address = source.readParcelable(classLoader); + Uri address = source.readParcelable(classLoader, android.net.Uri.class); int addressPresentation = source.readInt(); String callerDisplayName = source.readString(); int callerDisplayNamePresentation = source.readInt(); @@ -274,8 +274,8 @@ public final class ParcelableConnection implements Parcelable { boolean ringbackRequested = source.readByte() == 1; boolean audioModeIsVoip = source.readByte() == 1; long connectTimeMillis = source.readLong(); - StatusHints statusHints = source.readParcelable(classLoader); - DisconnectCause disconnectCause = source.readParcelable(classLoader); + StatusHints statusHints = source.readParcelable(classLoader, android.telecom.StatusHints.class); + DisconnectCause disconnectCause = source.readParcelable(classLoader, android.telecom.DisconnectCause.class); List<String> conferenceableConnectionIds = new ArrayList<>(); source.readStringList(conferenceableConnectionIds); Bundle extras = Bundle.setDefusable(source.readBundle(classLoader), true); diff --git a/telecomm/java/android/telecom/ParcelableRttCall.java b/telecomm/java/android/telecom/ParcelableRttCall.java index fbcf486151f9..b88473a8a63b 100644 --- a/telecomm/java/android/telecom/ParcelableRttCall.java +++ b/telecomm/java/android/telecom/ParcelableRttCall.java @@ -46,8 +46,8 @@ public class ParcelableRttCall implements Parcelable { protected ParcelableRttCall(Parcel in) { mRttMode = in.readInt(); - mTransmitStream = in.readParcelable(ParcelFileDescriptor.class.getClassLoader()); - mReceiveStream = in.readParcelable(ParcelFileDescriptor.class.getClassLoader()); + mTransmitStream = in.readParcelable(ParcelFileDescriptor.class.getClassLoader(), android.os.ParcelFileDescriptor.class); + mReceiveStream = in.readParcelable(ParcelFileDescriptor.class.getClassLoader(), android.os.ParcelFileDescriptor.class); } public static final @android.annotation.NonNull Creator<ParcelableRttCall> CREATOR = new Creator<ParcelableRttCall>() { diff --git a/telecomm/java/android/telecom/Phone.java b/telecomm/java/android/telecom/Phone.java index bc0a14667307..ac91a926e267 100644 --- a/telecomm/java/android/telecom/Phone.java +++ b/telecomm/java/android/telecom/Phone.java @@ -292,6 +292,29 @@ public final class Phone { } } + void internalOnCallPullFailed(String callId, @Call.Callback.PullFailedReason int reason) { + Call call = getCallById(callId); + if (call != null) { + call.internalOnCallPullFailed(reason); + } + } + + void internalOnAnswerFailed(String callId, CallEndpoint callEndpoint, + @Call.Callback.AnswerFailedReason int reason) { + Call call = getCallById(callId); + if (call != null) { + call.internalOnAnswerFailed(callEndpoint, reason); + } + } + + void internalOnCallPushFailed(String callId, CallEndpoint callEndpoint, + @Call.Callback.PushFailedReason int reason) { + Call call = getCallById(callId); + if (call != null) { + call.internalOnCallPushFailed(callEndpoint, reason); + } + } + /** * Called to destroy the phone and cleanup any lingering calls. */ diff --git a/telecomm/java/android/telecom/PhoneAccountSuggestion.java b/telecomm/java/android/telecom/PhoneAccountSuggestion.java index 2589d9504f6d..d9f89d544f40 100644 --- a/telecomm/java/android/telecom/PhoneAccountSuggestion.java +++ b/telecomm/java/android/telecom/PhoneAccountSuggestion.java @@ -84,7 +84,7 @@ public final class PhoneAccountSuggestion implements Parcelable { } private PhoneAccountSuggestion(Parcel in) { - mHandle = in.readParcelable(PhoneAccountHandle.class.getClassLoader()); + mHandle = in.readParcelable(PhoneAccountHandle.class.getClassLoader(), android.telecom.PhoneAccountHandle.class); mReason = in.readInt(); mShouldAutoSelect = in.readByte() != 0; } diff --git a/telecomm/java/android/telecom/StatusHints.java b/telecomm/java/android/telecom/StatusHints.java index 762c93a49022..2faecc2e3468 100644 --- a/telecomm/java/android/telecom/StatusHints.java +++ b/telecomm/java/android/telecom/StatusHints.java @@ -132,8 +132,8 @@ public final class StatusHints implements Parcelable { private StatusHints(Parcel in) { mLabel = in.readCharSequence(); - mIcon = in.readParcelable(getClass().getClassLoader()); - mExtras = in.readParcelable(getClass().getClassLoader()); + mIcon = in.readParcelable(getClass().getClassLoader(), android.graphics.drawable.Icon.class); + mExtras = in.readParcelable(getClass().getClassLoader(), android.os.Bundle.class); } @Override diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java index 6279bf88ab1c..e560f34cfcd8 100644 --- a/telecomm/java/android/telecom/TelecomManager.java +++ b/telecomm/java/android/telecom/TelecomManager.java @@ -54,8 +54,10 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Objects; +import java.util.Set; import java.util.concurrent.Executor; /** @@ -586,6 +588,14 @@ public class TelecomManager { "android.telecom.extra.START_CALL_WITH_RTT"; /** + * A parcelable extra, which when set on the bundle passed into {@link #placeCall(Uri, Bundle)}, + * indicates that the call should be initiated with an active {@link CallEndpoint} to stream + * the call as a tethered call. + */ + public static final String EXTRA_START_CALL_ON_ENDPOINT = + "android.telecom.extra.START_CALL_ON_ENDPOINT"; + + /** * Start an activity indicating that the completion of an outgoing call or an incoming call * which was not blocked by the {@link CallScreeningService}, and which was NOT terminated * while the call was in {@link Call#STATE_AUDIO_PROCESSING}. @@ -745,6 +755,23 @@ public class TelecomManager { "android.telecom.INCLUDE_SELF_MANAGED_CALLS"; /** + * A boolean meta-data value indicating this {@link InCallService} implementation is aimed at + * working as a streaming app for a tethered call. When there's a tethered call + * requesting to a {@link CallEndpoint} registered with this app, Telecom will bind to this + * streaming app and let the app streaming the call to the requested endpoint. + * <p> + * This meta-data can only be set for an {@link InCallService} which doesn't set neither + * {@link #METADATA_IN_CALL_SERVICE_UI} nor {@link #METADATA_IN_CALL_SERVICE_CAR_MODE_UI}. + * Otherwise, the app will be treated as a phone/dialer app or a car-mode app. + * <p> + * The {@link InCallService} declared this meta-data must implement + * {@link InCallService#onCallEndpointActivationRequested(CallEndpoint, CallEndpointSession)}. + * See this method for more information. + */ + public static final String METADATA_STREAMING_TETHERED_CALLS = + "android.telecom.STREAMING_TETHERED_CALLS"; + + /** * The dual tone multi-frequency signaling character sent to indicate the dialing system should * pause for a predefined period. */ @@ -1294,14 +1321,24 @@ public class TelecomManager { * {@link PhoneAccount#CAPABILITY_SELF_MANAGED}. * <p> * Requires permission {@link android.Manifest.permission#READ_PHONE_STATE}, or that the caller - * is the default dialer app. + * is the default dialer app to get all phone account handles. + * <P> + * If the caller doesn't meet any of the above requirements and has {@link + * android.Manifest.permission#MANAGE_OWN_CALLS}, the caller can get only the phone account + * handles they have registered. * <p> - * A {@link SecurityException} will be thrown if a called is not the default dialer, or lacks - * the {@link android.Manifest.permission#READ_PHONE_STATE} permission. + * A {@link SecurityException} will be thrown if the caller is not the default dialer + * or the caller does not have at least one of the following permissions: + * {@link android.Manifest.permission#READ_PHONE_STATE} permission, + * {@link android.Manifest.permission#MANAGE_OWN_CALLS} permission * * @return A list of {@code PhoneAccountHandle} objects. */ - @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) + @RequiresPermission(anyOf = { + READ_PRIVILEGED_PHONE_STATE, + android.Manifest.permission.READ_PHONE_STATE, + android.Manifest.permission.MANAGE_OWN_CALLS + }) public List<PhoneAccountHandle> getSelfManagedPhoneAccounts() { ITelecomService service = getTelecomService(); if (service != null) { @@ -2250,6 +2287,7 @@ public class TelecomManager { * <li>{@link #EXTRA_PHONE_ACCOUNT_HANDLE}</li> * <li>{@link #EXTRA_START_CALL_WITH_SPEAKERPHONE}</li> * <li>{@link #EXTRA_START_CALL_WITH_VIDEO_STATE}</li> + * <li>{@link #EXTRA_START_CALL_ON_ENDPOINT}</li> * </ul> * <p> * An app which implements the self-managed {@link ConnectionService} API uses @@ -2579,6 +2617,79 @@ public class TelecomManager { } } + /** + * Register a set of {@link CallEndpoint} to telecom. All registered {@link CallEndpoint} can + * be provided as options for push, place or answer call externally. + * + * @param endpoints Endpoints to be registered. + */ + // TODO: add permission requirements + // @RequiresPermission{} + public void registerCallEndpoints(@NonNull Set<CallEndpoint> endpoints) { + ITelecomService service = getTelecomService(); + List<CallEndpoint> endpointList = new ArrayList<>(endpoints); + if (service != null) { + try { + service.registerCallEndpoints(endpointList, mContext.getOpPackageName()); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException registerCallEndpoints: " + e); + e.rethrowAsRuntimeException(); + } + } else { + throw new IllegalStateException("Telecom service is null."); + } + } + + /** + * Unregister all {@link CallEndpoint} from telecom in the set provided. After un-registration, + * telecom will stop tracking and maintaining these {@link CallEndpoint}, user can no longer + * carry a call on them. + * + * @param endpoints + */ + // TODO: add permission requirements + // @RequiresPermission{} + public void unregisterCallEndpoints(@NonNull Set<CallEndpoint> endpoints) { + ITelecomService service = getTelecomService(); + List<CallEndpoint> endpointList = new ArrayList<>(endpoints); + if (service != null) { + try { + service.unregisterCallEndpoints(endpointList, mContext.getOpPackageName()); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException unregisterCallEndpoints: " + e); + e.rethrowAsRuntimeException(); + } + } else { + throw new IllegalStateException("Telecom service is null."); + } + } + + /** + * Return a set all registered {@link CallEndpoint} that can be used to stream and carry an + * external call. + * + * @return A set of all available {@link CallEndpoint}. + */ + // TODO: add permission requirements + // @RequiresPermission{} + public @NonNull Set<CallEndpoint> getCallEndpoints() { + Set<CallEndpoint> endpoints = new HashSet<>(); + List<CallEndpoint> endpointList; + ITelecomService service = getTelecomService(); + if (service != null) { + try { + endpointList = service.getCallEndpoints(mContext.getOpPackageName()); + return new HashSet<>(endpointList); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException registerCallEndpoints: " + e); + e.rethrowAsRuntimeException(); + } + } else { + throw new IllegalStateException("Telecom service is null."); + } + return endpoints; + } + private boolean isSystemProcess() { return Process.myUid() == Process.SYSTEM_UID; } diff --git a/telecomm/java/com/android/internal/telecom/ICallEndpointCallback.aidl b/telecomm/java/com/android/internal/telecom/ICallEndpointCallback.aidl new file mode 100644 index 000000000000..dc1cc0f5a3c9 --- /dev/null +++ b/telecomm/java/com/android/internal/telecom/ICallEndpointCallback.aidl @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.telecom; + +/** + * Internal remote CallEndpointCallback interface for Telecom framework to report event related to + * the endpoint session. + * + * {@hide} + */ +oneway interface ICallEndpointCallback { + void onCallEndpointSessionActivationTimeout(); + + void onCallEndpointSessionDeactivated(); +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackPreviewHandler.java b/telecomm/java/com/android/internal/telecom/ICallEndpointSession.aidl index dc20f7b72a2b..1c1c29a22f29 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackPreviewHandler.java +++ b/telecomm/java/com/android/internal/telecom/ICallEndpointSession.aidl @@ -14,28 +14,21 @@ * limitations under the License. */ -package com.android.wm.shell.back; - -import android.os.SystemProperties; -import android.view.IWindowManager; - -import javax.inject.Inject; +package com.android.internal.telecom; /** - * Handle the preview of what a back gesture will lead to. + * Internal remote CallEndpointSession interface for streaming app to update the status of the + * endpoint. + * + * @see android.telecom.CallEndpointSession + * + * {@hide} */ -public class BackPreviewHandler { - - private static final String BACK_PREDICTABILITY_PROP = "persist.debug.back_predictability"; - public static boolean isEnabled() { - return SystemProperties.getInt(BACK_PREDICTABILITY_PROP, 0) > 0; - } +oneway interface ICallEndpointSession { + void setCallEndpointSessionActivated(); - private final IWindowManager mWmService; + void setCallEndpointSessionActivationFailed(int reason); - @Inject - public BackPreviewHandler(IWindowManager windowManagerService) { - mWmService = windowManagerService; - } -} + void setCallEndpointSessionDeactivated(); +}
\ No newline at end of file diff --git a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl index d72f8aa82ddb..986871fd0377 100644 --- a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl +++ b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl @@ -20,6 +20,7 @@ import android.net.Uri; import android.os.Bundle; import android.os.ParcelFileDescriptor; import android.telecom.CallAudioState; +import android.telecom.CallEndpoint; import android.telecom.Connection; import android.telecom.ConnectionRequest; import android.telecom.Logging.Session; diff --git a/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl b/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl index edf1cf4cdb18..ecca835a45b2 100755 --- a/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl +++ b/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl @@ -18,6 +18,7 @@ package com.android.internal.telecom; import android.net.Uri; import android.os.Bundle; +import android.telecom.CallEndpoint; import android.telecom.PhoneAccountHandle; /** @@ -95,4 +96,8 @@ oneway interface IInCallAdapter { void handoverTo(String callId, in PhoneAccountHandle destAcct, int videoState, in Bundle extras); + + void pushCall(String callId, in CallEndpoint endpoint); + + void answerCallViaEndpoint(String callId, in CallEndpoint endpoint, int videoState); } diff --git a/telecomm/java/com/android/internal/telecom/IInCallService.aidl b/telecomm/java/com/android/internal/telecom/IInCallService.aidl index b9563fa7bb18..93d9f282560f 100644 --- a/telecomm/java/com/android/internal/telecom/IInCallService.aidl +++ b/telecomm/java/com/android/internal/telecom/IInCallService.aidl @@ -19,9 +19,12 @@ package com.android.internal.telecom; import android.app.PendingIntent; import android.os.Bundle; import android.telecom.CallAudioState; +import android.telecom.CallEndpoint; import android.telecom.ParcelableCall; import com.android.internal.telecom.IInCallAdapter; +import com.android.internal.telecom.ICallEndpointCallback; +import com.android.internal.telecom.ICallEndpointSession; /** * Internal remote interface for in-call services. @@ -30,9 +33,12 @@ import com.android.internal.telecom.IInCallAdapter; * * {@hide} */ -oneway interface IInCallService { +interface IInCallService { void setInCallAdapter(in IInCallAdapter inCallAdapter); + ICallEndpointCallback requestCallEndpointActivation(in CallEndpoint callEndpoint, + in ICallEndpointSession callEndpointSession); + void addCall(in ParcelableCall call); void updateCall(in ParcelableCall call); @@ -58,4 +64,10 @@ oneway interface IInCallService { void onHandoverFailed(String callId, int error); void onHandoverComplete(String callId); + + void onCallPullFailed(String callId, int reason); + + void onCallPushFailed(String callId, in CallEndpoint endpoint, int reason); + + void onAnswerFailed(String callId, in CallEndpoint endpoint, int reason); } diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl index a75f79caeee0..985f6bc7131b 100644 --- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl +++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl @@ -18,6 +18,7 @@ package com.android.internal.telecom; import android.content.ComponentName; import android.content.Intent; +import android.telecom.CallEndpoint; import android.telecom.TelecomAnalytics; import android.telecom.PhoneAccountHandle; import android.net.Uri; @@ -342,6 +343,8 @@ interface ITelecomService { void cleanupStuckCalls(); + int cleanupOrphanPhoneAccounts(); + void resetCarMode(); void setTestDefaultCallRedirectionApp(String packageName); @@ -366,4 +369,19 @@ interface ITelecomService { * @see TelecomServiceImpl#setTestCallDiagnosticService */ void setTestCallDiagnosticService(in String packageName); + + /** + * @see TelecomServiceImpl#registerCallEndpoints(in List<CallEndpoint>, in String); + */ + void registerCallEndpoints(in List<CallEndpoint> endpoints, in String packageName); + + /** + * @see TelecomServiceImpl#unregisterCallEndpoints(in List<CallEndpoint>, String); + */ + void unregisterCallEndpoints(in List<CallEndpoint> endpoints, in String packageName); + + /** + * @see TelecomServiceImpl#getCallEndpoints(in String packageName); + */ + List<CallEndpoint> getCallEndpoints(in String packageName); } diff --git a/telephony/java/android/telephony/AvailableNetworkInfo.java b/telephony/java/android/telephony/AvailableNetworkInfo.java index 2b355ae216e3..6d673fbc7305 100644 --- a/telephony/java/android/telephony/AvailableNetworkInfo.java +++ b/telephony/java/android/telephony/AvailableNetworkInfo.java @@ -185,9 +185,9 @@ public final class AvailableNetworkInfo implements Parcelable { mMccMncs = new ArrayList<>(); in.readStringList(mMccMncs); mBands = new ArrayList<>(); - in.readList(mBands, Integer.class.getClassLoader()); + in.readList(mBands, Integer.class.getClassLoader(), java.lang.Integer.class); mRadioAccessSpecifiers = new ArrayList<>(); - in.readList(mRadioAccessSpecifiers, RadioAccessSpecifier.class.getClassLoader()); + in.readList(mRadioAccessSpecifiers, RadioAccessSpecifier.class.getClassLoader(), android.telephony.RadioAccessSpecifier.class); } public AvailableNetworkInfo(int subId, int priority, @NonNull List<String> mccMncs, diff --git a/telephony/java/android/telephony/BarringInfo.java b/telephony/java/android/telephony/BarringInfo.java index 0aa4b5805cd6..29152f19d17d 100644 --- a/telephony/java/android/telephony/BarringInfo.java +++ b/telephony/java/android/telephony/BarringInfo.java @@ -294,8 +294,8 @@ public final class BarringInfo implements Parcelable { /** @hide */ public BarringInfo(Parcel p) { - mCellIdentity = p.readParcelable(CellIdentity.class.getClassLoader()); - mBarringServiceInfos = p.readSparseArray(BarringServiceInfo.class.getClassLoader()); + mCellIdentity = p.readParcelable(CellIdentity.class.getClassLoader(), android.telephony.CellIdentity.class); + mBarringServiceInfos = p.readSparseArray(BarringServiceInfo.class.getClassLoader(), android.telephony.BarringInfo.BarringServiceInfo.class); } @Override diff --git a/telephony/java/android/telephony/CallAttributes.java b/telephony/java/android/telephony/CallAttributes.java index 0c258f4b6435..b7bef39aa275 100644 --- a/telephony/java/android/telephony/CallAttributes.java +++ b/telephony/java/android/telephony/CallAttributes.java @@ -53,9 +53,9 @@ public final class CallAttributes implements Parcelable { } private CallAttributes(Parcel in) { - this.mPreciseCallState = in.readParcelable(PreciseCallState.class.getClassLoader()); + this.mPreciseCallState = in.readParcelable(PreciseCallState.class.getClassLoader(), android.telephony.PreciseCallState.class); this.mNetworkType = in.readInt(); - this.mCallQuality = in.readParcelable(CallQuality.class.getClassLoader()); + this.mCallQuality = in.readParcelable(CallQuality.class.getClassLoader(), android.telephony.CallQuality.class); } // getters diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 21967f4f4687..d5c846d5ed81 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -175,7 +175,10 @@ public class CarrierConfigManager { /** * This flag specifies whether VoLTE availability is based on provisioning. By default this is * false. + * Used for UCE to determine if EAB provisioning checks should be based on provisioning. + * @deprecated Use {@link Ims#KEY_MMTEL_REQUIRES_PROVISIONING_BUNDLE} instead. */ + @Deprecated public static final String KEY_CARRIER_VOLTE_PROVISIONED_BOOL = "carrier_volte_provisioned_bool"; @@ -879,7 +882,12 @@ public class CarrierConfigManager { /** * Flag specifying whether provisioning is required for VoLTE, Video Telephony, and WiFi * Calling. + + * Combines VoLTE, VT, VoWiFI calling provisioning into one parameter. + * @deprecated Use {@link Ims#KEY_MMTEL_REQUIRES_PROVISIONING_BUNDLE} instead for + * finer-grained control. */ + @Deprecated public static final String KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL = "carrier_volte_provisioning_required_bool"; @@ -893,7 +901,11 @@ public class CarrierConfigManager { * and enable the UT over IMS capability for the subscription when the subscription is loaded. * * The default value for this key is {@code false}. + * + * @deprecated Use {@link Ims#KEY_MMTEL_REQUIRES_PROVISIONING_BUNDLE} instead for + * determining if UT requires provisioning. */ + @Deprecated public static final String KEY_CARRIER_UT_PROVISIONING_REQUIRED_BOOL = "carrier_ut_provisioning_required_bool"; @@ -5173,6 +5185,95 @@ public class CarrierConfigManager { /** E911 RTP inactivity occurred when call is connected. */ public static final int E911_RTP_INACTIVITY_ON_CONNECTED = 4; + /** + * A bundle which specifies the MMTEL capability and registration technology + * that requires provisioning. If a tuple is not present, the + * framework will not require that the tuple requires provisioning before + * enabling the capability. + * <p> Possible keys in this bundle are + * <ul> + * <li>{@link #KEY_CAPABILITY_TYPE_VOICE_INT_ARRAY}</li> + * <li>{@link #KEY_CAPABILITY_TYPE_VIDEO_INT_ARRAY}</li> + * <li>{@link #KEY_CAPABILITY_TYPE_UT_INT_ARRAY}</li> + * <li>{@link #KEY_CAPABILITY_TYPE_SMS_INT_ARRAY}</li> + * <li>{@link #KEY_CAPABILITY_CALL_COMPOSER_INT_ARRAY}</li> + * </ul> + * <p> The values are defined in + * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationTech} + */ + public static final String KEY_MMTEL_REQUIRES_PROVISIONING_BUNDLE = + KEY_PREFIX + "mmtel_requires_provisioning_bundle"; + + /** + * This MmTelFeature supports Voice calling (IR.92) + * @see MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_VOICE + */ + public static final String KEY_CAPABILITY_TYPE_VOICE_INT_ARRAY = + KEY_PREFIX + "key_capability_type_voice_int_array"; + + /** + * This MmTelFeature supports Video (IR.94) + * @see MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_VIDEO + */ + public static final String KEY_CAPABILITY_TYPE_VIDEO_INT_ARRAY = + KEY_PREFIX + "key_capability_type_video_int_array"; + + /** + * This MmTelFeature supports XCAP over Ut for supplementary services. (IR.92) + * @see MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_UT + */ + public static final String KEY_CAPABILITY_TYPE_UT_INT_ARRAY = + KEY_PREFIX + "key_capability_type_ut_int_array"; + + /** + * This MmTelFeature supports SMS (IR.92) + * @see MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_SMS + */ + public static final String KEY_CAPABILITY_TYPE_SMS_INT_ARRAY = + KEY_PREFIX + "key_capability_type_sms_int_array"; + + /** + * This MmTelFeature supports Call Composer (section 2.4 of RCC.20) + * @see MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_CALL_COMPOSER + */ + public static final String KEY_CAPABILITY_CALL_COMPOSER_INT_ARRAY = + KEY_PREFIX + "key_capability_type_call_composer_int_array"; + + /** + * A bundle which specifies the RCS capability and registration technology + * that requires provisioning. If a tuple is not present, the + * framework will not require that the tuple requires provisioning before + * enabling the capability. + * <p> Possible keys in this bundle are + * <ul> + * <li>{@link #KEY_CAPABILITY_TYPE_OPTIONS_UCE_INT_ARRAY}</li> + * <li>{@link #KEY_CAPABILITY_TYPE_PRESENCE_UCE_INT_ARRAY}</li> + * </ul> + * <p> The values are defined in + * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationTech} + */ + public static final String KEY_RCS_REQUIRES_PROVISIONING_BUNDLE = + KEY_PREFIX + "rcs_requires_provisioning_bundle"; + + /** + * This carrier supports User Capability Exchange using SIP OPTIONS as defined by the + * framework. If set, the RcsFeature should support capability exchange using SIP OPTIONS. + * If not set, this RcsFeature should not service capability requests. + * @see RcsFeature.RcsImsCapabilities#CAPABILITY_TYPE_OPTIONS_UCE + */ + public static final String KEY_CAPABILITY_TYPE_OPTIONS_UCE_INT_ARRAY = + KEY_PREFIX + "key_capability_type_options_uce_int_array"; + + /** + * This carrier supports User Capability Exchange using a presence server as defined by the + * framework. If set, the RcsFeature should support capability exchange using a presence + * server. If not set, this RcsFeature should not publish capabilities or service capability + * requests using presence. + * @see RcsFeature.RcsImsCapabilities#CAPABILITY_TYPE_PRESENCE_UCE + */ + public static final String KEY_CAPABILITY_TYPE_PRESENCE_UCE_INT_ARRAY = + KEY_PREFIX + "key_capability_type_presence_uce_int_array"; + private Ims() {} private static PersistableBundle getDefaults() { @@ -5209,6 +5310,20 @@ public class CarrierConfigManager { "+g.gsma.rcs.botversion=\"#=1,#=2\"", "+g.gsma.rcs.cpimext"}); + /** + * @see #KEY_MMTEL_REQUIRES_PROVISIONING_BUNDLE + */ + PersistableBundle mmtel_requires_provisioning_int_array = new PersistableBundle(); + defaults.putPersistableBundle( + KEY_MMTEL_REQUIRES_PROVISIONING_BUNDLE, mmtel_requires_provisioning_int_array); + + /** + * @see #KEY_RCS_REQUIRES_PROVISIONING_BUNDLE + */ + PersistableBundle rcs_requires_provisioning_int_array = new PersistableBundle(); + defaults.putPersistableBundle( + KEY_RCS_REQUIRES_PROVISIONING_BUNDLE, rcs_requires_provisioning_int_array); + defaults.putBoolean(KEY_GRUU_ENABLED_BOOL, true); defaults.putBoolean(KEY_SIP_OVER_IPSEC_ENABLED_BOOL, true); defaults.putBoolean(KEY_KEEP_PDN_UP_IN_NO_VOPS_BOOL, false); @@ -7573,8 +7688,8 @@ public class CarrierConfigManager { public static final String KEY_EPDG_PCO_ID_IPV4_INT = KEY_PREFIX + "epdg_pco_id_ipv4_int"; /** Controls if the IKE tunnel setup supports EAP-AKA fast reauth */ - public static final String KEY_ENABLE_SUPPORT_FOR_EAP_AKA_FAST_REAUTH_BOOL = - KEY_PREFIX + "enable_support_for_eap_aka_fast_reauth_bool"; + public static final String KEY_SUPPORTS_EAP_AKA_FAST_REAUTH_BOOL = + KEY_PREFIX + "supports_eap_aka_fast_reauth_bool"; /** @hide */ @IntDef({AUTHENTICATION_METHOD_EAP_ONLY, AUTHENTICATION_METHOD_CERT}) @@ -7722,7 +7837,7 @@ public class CarrierConfigManager { defaults.putBoolean(KEY_ADD_KE_TO_CHILD_SESSION_REKEY_BOOL, false); defaults.putInt(KEY_EPDG_PCO_ID_IPV6_INT, 0); defaults.putInt(KEY_EPDG_PCO_ID_IPV4_INT, 0); - defaults.putBoolean(KEY_ENABLE_SUPPORT_FOR_EAP_AKA_FAST_REAUTH_BOOL, false); + defaults.putBoolean(KEY_SUPPORTS_EAP_AKA_FAST_REAUTH_BOOL, false); return defaults; } diff --git a/telephony/java/android/telephony/CellIdentityLte.java b/telephony/java/android/telephony/CellIdentityLte.java index 4db00cf258e5..b4b8aee31b54 100644 --- a/telephony/java/android/telephony/CellIdentityLte.java +++ b/telephony/java/android/telephony/CellIdentityLte.java @@ -379,7 +379,7 @@ public final class CellIdentityLte extends CellIdentity { mBands = in.createIntArray(); mBandwidth = in.readInt(); mAdditionalPlmns = (ArraySet<String>) in.readArraySet(null); - mCsgInfo = in.readParcelable(null); + mCsgInfo = in.readParcelable(null, android.telephony.ClosedSubscriberGroupInfo.class); updateGlobalCellId(); if (DBG) log(toString()); diff --git a/telephony/java/android/telephony/CellIdentityTdscdma.java b/telephony/java/android/telephony/CellIdentityTdscdma.java index 13d93737f751..90e6295abda8 100644 --- a/telephony/java/android/telephony/CellIdentityTdscdma.java +++ b/telephony/java/android/telephony/CellIdentityTdscdma.java @@ -297,7 +297,7 @@ public final class CellIdentityTdscdma extends CellIdentity { mCpid = in.readInt(); mUarfcn = in.readInt(); mAdditionalPlmns = (ArraySet<String>) in.readArraySet(null); - mCsgInfo = in.readParcelable(null); + mCsgInfo = in.readParcelable(null, android.telephony.ClosedSubscriberGroupInfo.class); updateGlobalCellId(); if (DBG) log(toString()); diff --git a/telephony/java/android/telephony/CellIdentityWcdma.java b/telephony/java/android/telephony/CellIdentityWcdma.java index 9b463da14f16..72282cdb344b 100644 --- a/telephony/java/android/telephony/CellIdentityWcdma.java +++ b/telephony/java/android/telephony/CellIdentityWcdma.java @@ -313,7 +313,7 @@ public final class CellIdentityWcdma extends CellIdentity { mPsc = in.readInt(); mUarfcn = in.readInt(); mAdditionalPlmns = (ArraySet<String>) in.readArraySet(null); - mCsgInfo = in.readParcelable(null); + mCsgInfo = in.readParcelable(null, android.telephony.ClosedSubscriberGroupInfo.class); updateGlobalCellId(); if (DBG) log(toString()); diff --git a/telephony/java/android/telephony/CellSignalStrengthNr.java b/telephony/java/android/telephony/CellSignalStrengthNr.java index cd22abddd3a7..f5ba3abf53a5 100644 --- a/telephony/java/android/telephony/CellSignalStrengthNr.java +++ b/telephony/java/android/telephony/CellSignalStrengthNr.java @@ -326,7 +326,7 @@ public final class CellSignalStrengthNr extends CellSignalStrength implements Pa mCsiRsrq = in.readInt(); mCsiSinr = in.readInt(); mCsiCqiTableIndex = in.readInt(); - mCsiCqiReport = in.readArrayList(Integer.class.getClassLoader()); + mCsiCqiReport = in.readArrayList(Integer.class.getClassLoader(), java.lang.Integer.class); mSsRsrp = in.readInt(); mSsRsrq = in.readInt(); mSsSinr = in.readInt(); diff --git a/telephony/java/android/telephony/DataSpecificRegistrationInfo.java b/telephony/java/android/telephony/DataSpecificRegistrationInfo.java index 957f683292f7..837124fe89de 100644 --- a/telephony/java/android/telephony/DataSpecificRegistrationInfo.java +++ b/telephony/java/android/telephony/DataSpecificRegistrationInfo.java @@ -105,7 +105,7 @@ public final class DataSpecificRegistrationInfo implements Parcelable { isDcNrRestricted = source.readBoolean(); isNrAvailable = source.readBoolean(); isEnDcAvailable = source.readBoolean(); - mVopsSupportInfo = source.readParcelable(VopsSupportInfo.class.getClassLoader()); + mVopsSupportInfo = source.readParcelable(VopsSupportInfo.class.getClassLoader(), android.telephony.VopsSupportInfo.class); } @Override diff --git a/telephony/java/android/telephony/ImsManager.java b/telephony/java/android/telephony/ImsManager.java index 2758e1273cec..b0ff9499eac4 100644 --- a/telephony/java/android/telephony/ImsManager.java +++ b/telephony/java/android/telephony/ImsManager.java @@ -163,6 +163,26 @@ public class ImsManager { return new SipDelegateManager(mContext, subscriptionId, sRcsCache, sTelephonyCache); } + + /** + * Create an instance of {@link ProvisioningManager} for the subscription id specified. + * <p> + * Provides a ProvisioningManager instance to carrier apps to update carrier provisioning + * information, as well as provides a callback so that apps can listen for changes + * in MMTEL/RCS provisioning + * @param subscriptionId The ID of the subscription that this ProvisioningManager will use. + * @throws IllegalArgumentException if the subscription is invalid. + * @return a ProvisioningManager instance with the specific subscription ID. + */ + @NonNull + public ProvisioningManager getProvisioningManager(int subscriptionId) { + if (!SubscriptionManager.isValidSubscriptionId(subscriptionId)) { + throw new IllegalArgumentException("Invalid subscription ID: " + subscriptionId); + } + + return new ProvisioningManager(subscriptionId); + } + private static IImsRcsController getIImsRcsControllerInterface() { return IImsRcsController.Stub.asInterface( TelephonyFrameworkInitializer diff --git a/telephony/java/android/telephony/NetworkRegistrationInfo.java b/telephony/java/android/telephony/NetworkRegistrationInfo.java index 6a807665a103..c18443e81aff 100644 --- a/telephony/java/android/telephony/NetworkRegistrationInfo.java +++ b/telephony/java/android/telephony/NetworkRegistrationInfo.java @@ -311,12 +311,12 @@ public final class NetworkRegistrationInfo implements Parcelable { mRejectCause = source.readInt(); mEmergencyOnly = source.readBoolean(); mAvailableServices = new ArrayList<>(); - source.readList(mAvailableServices, Integer.class.getClassLoader()); - mCellIdentity = source.readParcelable(CellIdentity.class.getClassLoader()); + source.readList(mAvailableServices, Integer.class.getClassLoader(), java.lang.Integer.class); + mCellIdentity = source.readParcelable(CellIdentity.class.getClassLoader(), android.telephony.CellIdentity.class); mVoiceSpecificInfo = source.readParcelable( - VoiceSpecificRegistrationInfo.class.getClassLoader()); + VoiceSpecificRegistrationInfo.class.getClassLoader(), android.telephony.VoiceSpecificRegistrationInfo.class); mDataSpecificInfo = source.readParcelable( - DataSpecificRegistrationInfo.class.getClassLoader()); + DataSpecificRegistrationInfo.class.getClassLoader(), android.telephony.DataSpecificRegistrationInfo.class); mNrState = source.readInt(); mRplmn = source.readString(); mIsUsingCarrierAggregation = source.readBoolean(); diff --git a/telephony/java/android/telephony/PcoData.java b/telephony/java/android/telephony/PcoData.java index bcfbcf8bf5fa..39e4f2f799d8 100644 --- a/telephony/java/android/telephony/PcoData.java +++ b/telephony/java/android/telephony/PcoData.java @@ -19,6 +19,9 @@ package android.telephony; import android.os.Parcel; import android.os.Parcelable; +import java.util.Arrays; +import java.util.Objects; + /** * Contains Carrier-specific (and opaque) Protocol configuration Option * Data. In general this is only passed on to carrier-specific applications @@ -84,4 +87,22 @@ public class PcoData implements Parcelable { return "PcoData(" + cid + ", " + bearerProto + ", " + pcoId + ", contents[" + contents.length + "])"; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + PcoData pcoData = (PcoData) o; + return cid == pcoData.cid + && pcoId == pcoData.pcoId + && Objects.equals(bearerProto, pcoData.bearerProto) + && Arrays.equals(contents, pcoData.contents); + } + + @Override + public int hashCode() { + int result = Objects.hash(cid, bearerProto, pcoId); + result = 31 * result + Arrays.hashCode(contents); + return result; + } } diff --git a/telephony/java/android/telephony/PhoneCapability.java b/telephony/java/android/telephony/PhoneCapability.java index a3aaf61a6fec..63e3468ac19b 100644 --- a/telephony/java/android/telephony/PhoneCapability.java +++ b/telephony/java/android/telephony/PhoneCapability.java @@ -150,7 +150,7 @@ public final class PhoneCapability implements Parcelable { mMaxActiveDataSubscriptions = in.readInt(); mNetworkValidationBeforeSwitchSupported = in.readBoolean(); mLogicalModemList = new ArrayList<>(); - in.readList(mLogicalModemList, ModemInfo.class.getClassLoader()); + in.readList(mLogicalModemList, ModemInfo.class.getClassLoader(), android.telephony.ModemInfo.class); mDeviceNrCapabilities = in.createIntArray(); } diff --git a/telephony/java/android/telephony/PreciseDataConnectionState.java b/telephony/java/android/telephony/PreciseDataConnectionState.java index ce2f3f924554..2670b03ca8ac 100644 --- a/telephony/java/android/telephony/PreciseDataConnectionState.java +++ b/telephony/java/android/telephony/PreciseDataConnectionState.java @@ -125,9 +125,9 @@ public final class PreciseDataConnectionState implements Parcelable { mId = in.readInt(); mState = in.readInt(); mNetworkType = in.readInt(); - mLinkProperties = in.readParcelable(LinkProperties.class.getClassLoader()); + mLinkProperties = in.readParcelable(LinkProperties.class.getClassLoader(), android.net.LinkProperties.class); mFailCause = in.readInt(); - mApnSetting = in.readParcelable(ApnSetting.class.getClassLoader()); + mApnSetting = in.readParcelable(ApnSetting.class.getClassLoader(), android.telephony.data.ApnSetting.class); } /** diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java index 5affb62ae5cd..70da9b95410a 100644 --- a/telephony/java/android/telephony/ServiceState.java +++ b/telephony/java/android/telephony/ServiceState.java @@ -479,7 +479,7 @@ public class ServiceState implements Parcelable { mIsEmergencyOnly = in.readInt() != 0; mArfcnRsrpBoost = in.readInt(); synchronized (mNetworkRegistrationInfos) { - in.readList(mNetworkRegistrationInfos, NetworkRegistrationInfo.class.getClassLoader()); + in.readList(mNetworkRegistrationInfos, NetworkRegistrationInfo.class.getClassLoader(), android.telephony.NetworkRegistrationInfo.class); } mChannelNumber = in.readInt(); mCellBandwidths = in.createIntArray(); diff --git a/telephony/java/android/telephony/SignalStrength.java b/telephony/java/android/telephony/SignalStrength.java index b7bc46736e18..f74ef0fe764a 100644 --- a/telephony/java/android/telephony/SignalStrength.java +++ b/telephony/java/android/telephony/SignalStrength.java @@ -275,12 +275,12 @@ public class SignalStrength implements Parcelable { public SignalStrength(Parcel in) { if (DBG) log("Size of signalstrength parcel:" + in.dataSize()); - mCdma = in.readParcelable(CellSignalStrengthCdma.class.getClassLoader()); - mGsm = in.readParcelable(CellSignalStrengthGsm.class.getClassLoader()); - mWcdma = in.readParcelable(CellSignalStrengthWcdma.class.getClassLoader()); - mTdscdma = in.readParcelable(CellSignalStrengthTdscdma.class.getClassLoader()); - mLte = in.readParcelable(CellSignalStrengthLte.class.getClassLoader()); - mNr = in.readParcelable(CellSignalStrengthLte.class.getClassLoader()); + mCdma = in.readParcelable(CellSignalStrengthCdma.class.getClassLoader(), android.telephony.CellSignalStrengthCdma.class); + mGsm = in.readParcelable(CellSignalStrengthGsm.class.getClassLoader(), android.telephony.CellSignalStrengthGsm.class); + mWcdma = in.readParcelable(CellSignalStrengthWcdma.class.getClassLoader(), android.telephony.CellSignalStrengthWcdma.class); + mTdscdma = in.readParcelable(CellSignalStrengthTdscdma.class.getClassLoader(), android.telephony.CellSignalStrengthTdscdma.class); + mLte = in.readParcelable(CellSignalStrengthLte.class.getClassLoader(), android.telephony.CellSignalStrengthLte.class); + mNr = in.readParcelable(CellSignalStrengthLte.class.getClassLoader(), android.telephony.CellSignalStrengthNr.class); mTimestampMillis = in.readLong(); } diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java index 574a356c43f2..250e55cf5014 100644 --- a/telephony/java/android/telephony/SubscriptionManager.java +++ b/telephony/java/android/telephony/SubscriptionManager.java @@ -3892,6 +3892,11 @@ public class SubscriptionManager { * {@link #PHONE_NUMBER_SOURCE_UICC UICC} and decide if the previously set phone number * of source {@link #PHONE_NUMBER_SOURCE_CARRIER carrier} should be updated. * + * <p>The API provides no guarantees of what format the number is in: the format can vary + * depending on the {@code source} and the network etc. Programmatic parsing should be done + * cautiously, for example, after formatting the number to a consistent format with + * {@link android.telephony.PhoneNumberUtils#formatNumberToE164(String, String)}. + * * <p>Note the assumption is that one subscription (which usually means one SIM) has * only one phone number. The multiple sources backup each other so hopefully at least one * is availavle. For example, for a carrier that doesn't typically set phone numbers @@ -3950,6 +3955,11 @@ public class SubscriptionManager { * from available sources in the following order: {@link #PHONE_NUMBER_SOURCE_CARRIER} * > {@link #PHONE_NUMBER_SOURCE_UICC} > {@link #PHONE_NUMBER_SOURCE_IMS}. * + * <p>The API provides no guarantees of what format the number is in: the format can vary + * depending on the underlying source and the network etc. Programmatic parsing should be done + * cautiously, for example, after formatting the number to a consistent format with + * {@link android.telephony.PhoneNumberUtils#formatNumberToE164(String, String)}. + * * @param subscriptionId the subscription ID, or {@link #DEFAULT_SUBSCRIPTION_ID} * for the default one. * @return the phone number, or an empty string if not available. @@ -3988,6 +3998,9 @@ public class SubscriptionManager { * <p>The API is suitable for carrier apps to provide a phone number, for example when * it's not possible to update {@link #PHONE_NUMBER_SOURCE_UICC UICC} directly. * + * <p>It's recommended that the phone number is formatted to well-known formats, + * for example, by {@link PhoneNumberUtils} {@code formatNumber*} methods. + * * @param subscriptionId the subscription ID, or {@link #DEFAULT_SUBSCRIPTION_ID} * for the default one. * @param number the phone number, or an empty string to remove the previously set number. diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 0c56de82eafc..536517c12313 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -15138,6 +15138,15 @@ public class TelephonyManager { public static final int CALL_WAITING_STATUS_NOT_SUPPORTED = 4; /** + * Indicates the call waiting status could not be set or queried because the Fixed Dialing + * Numbers (FDN) feature is enabled. + * + * @hide + */ + @SystemApi + public static final int CALL_WAITING_STATUS_FDN_CHECK_FAILURE = 5; + + /** * @hide */ @IntDef(prefix = { "CALL_WAITING_STATUS_" }, value = { @@ -15145,6 +15154,7 @@ public class TelephonyManager { CALL_WAITING_STATUS_DISABLED, CALL_WAITING_STATUS_UNKNOWN_ERROR, CALL_WAITING_STATUS_NOT_SUPPORTED, + CALL_WAITING_STATUS_FDN_CHECK_FAILURE, }) @Retention(RetentionPolicy.SOURCE) public @interface CallWaitingStatus { @@ -15165,6 +15175,7 @@ public class TelephonyManager { * <li>{@link #CALL_WAITING_STATUS_DISABLED}}</li> * <li>{@link #CALL_WAITING_STATUS_UNKNOWN_ERROR}}</li> * <li>{@link #CALL_WAITING_STATUS_NOT_SUPPORTED}}</li> + * <li>{@link #CALL_WAITING_STATUS_FDN_CHECK_FAILURE}}</li> * </ul> * @hide */ @@ -15214,7 +15225,8 @@ public class TelephonyManager { * {@link #CALL_WAITING_STATUS_ENABLED} or * {@link #CALL_WAITING_STATUS_DISABLED} if the operation succeeded and * {@link #CALL_WAITING_STATUS_NOT_SUPPORTED} or - * {@link #CALL_WAITING_STATUS_UNKNOWN_ERROR} if it failed. + * {@link #CALL_WAITING_STATUS_UNKNOWN_ERROR} or + * {@link #CALL_WAITING_STATUS_FDN_CHECK_FAILURE} if it failed. * @hide */ @SystemApi diff --git a/telephony/java/android/telephony/ThermalMitigationRequest.java b/telephony/java/android/telephony/ThermalMitigationRequest.java index 91ad9c3e1f51..a0676ea63711 100644 --- a/telephony/java/android/telephony/ThermalMitigationRequest.java +++ b/telephony/java/android/telephony/ThermalMitigationRequest.java @@ -100,7 +100,7 @@ public final class ThermalMitigationRequest implements Parcelable { private ThermalMitigationRequest(Parcel in) { mThermalMitigationAction = in.readInt(); - mDataThrottlingRequest = in.readParcelable(DataThrottlingRequest.class.getClassLoader()); + mDataThrottlingRequest = in.readParcelable(DataThrottlingRequest.class.getClassLoader(), android.telephony.DataThrottlingRequest.class); } /** diff --git a/telephony/java/android/telephony/VisualVoicemailSms.java b/telephony/java/android/telephony/VisualVoicemailSms.java index 085f8823b840..bec715e3b81a 100644 --- a/telephony/java/android/telephony/VisualVoicemailSms.java +++ b/telephony/java/android/telephony/VisualVoicemailSms.java @@ -121,7 +121,7 @@ public final class VisualVoicemailSms implements Parcelable { @Override public VisualVoicemailSms createFromParcel(Parcel in) { return new Builder() - .setPhoneAccountHandle((PhoneAccountHandle) in.readParcelable(null)) + .setPhoneAccountHandle((PhoneAccountHandle) in.readParcelable(null, android.telecom.PhoneAccountHandle.class)) .setPrefix(in.readString()) .setFields(in.readBundle()) .setMessageBody(in.readString()) diff --git a/telephony/java/android/telephony/data/ApnSetting.java b/telephony/java/android/telephony/data/ApnSetting.java index cb112cf3b93a..acbd64b57773 100644 --- a/telephony/java/android/telephony/data/ApnSetting.java +++ b/telephony/java/android/telephony/data/ApnSetting.java @@ -1628,7 +1628,7 @@ public class ApnSetting implements Parcelable { .setApnName(in.readString()) .setProxyAddress(in.readString()) .setProxyPort(in.readInt()) - .setMmsc(in.readParcelable(Uri.class.getClassLoader())) + .setMmsc(in.readParcelable(Uri.class.getClassLoader(), android.net.Uri.class)) .setMmsProxyAddress(in.readString()) .setMmsProxyPort(in.readInt()) .setUser(in.readString()) diff --git a/telephony/java/android/telephony/data/DataCallResponse.java b/telephony/java/android/telephony/data/DataCallResponse.java index ef02589abaf8..ae0d4e7e3b4e 100644 --- a/telephony/java/android/telephony/data/DataCallResponse.java +++ b/telephony/java/android/telephony/data/DataCallResponse.java @@ -241,24 +241,24 @@ public final class DataCallResponse implements Parcelable { mProtocolType = source.readInt(); mInterfaceName = source.readString(); mAddresses = new ArrayList<>(); - source.readList(mAddresses, LinkAddress.class.getClassLoader()); + source.readList(mAddresses, LinkAddress.class.getClassLoader(), android.net.LinkAddress.class); mDnsAddresses = new ArrayList<>(); - source.readList(mDnsAddresses, InetAddress.class.getClassLoader()); + source.readList(mDnsAddresses, InetAddress.class.getClassLoader(), java.net.InetAddress.class); mGatewayAddresses = new ArrayList<>(); - source.readList(mGatewayAddresses, InetAddress.class.getClassLoader()); + source.readList(mGatewayAddresses, InetAddress.class.getClassLoader(), java.net.InetAddress.class); mPcscfAddresses = new ArrayList<>(); - source.readList(mPcscfAddresses, InetAddress.class.getClassLoader()); + source.readList(mPcscfAddresses, InetAddress.class.getClassLoader(), java.net.InetAddress.class); mMtu = source.readInt(); mMtuV4 = source.readInt(); mMtuV6 = source.readInt(); mHandoverFailureMode = source.readInt(); mPduSessionId = source.readInt(); - mDefaultQos = source.readParcelable(Qos.class.getClassLoader()); + mDefaultQos = source.readParcelable(Qos.class.getClassLoader(), android.telephony.data.Qos.class); mQosBearerSessions = new ArrayList<>(); - source.readList(mQosBearerSessions, QosBearerSession.class.getClassLoader()); - mSliceInfo = source.readParcelable(NetworkSliceInfo.class.getClassLoader()); + source.readList(mQosBearerSessions, QosBearerSession.class.getClassLoader(), android.telephony.data.QosBearerSession.class); + mSliceInfo = source.readParcelable(NetworkSliceInfo.class.getClassLoader(), android.telephony.data.NetworkSliceInfo.class); mTrafficDescriptors = new ArrayList<>(); - source.readList(mTrafficDescriptors, TrafficDescriptor.class.getClassLoader()); + source.readList(mTrafficDescriptors, TrafficDescriptor.class.getClassLoader(), android.telephony.data.TrafficDescriptor.class); } /** diff --git a/telephony/java/android/telephony/data/DataProfile.java b/telephony/java/android/telephony/data/DataProfile.java index ec04c1ae9522..a166a5d6404c 100644 --- a/telephony/java/android/telephony/data/DataProfile.java +++ b/telephony/java/android/telephony/data/DataProfile.java @@ -107,8 +107,8 @@ public final class DataProfile implements Parcelable { private DataProfile(Parcel source) { mType = source.readInt(); - mApnSetting = source.readParcelable(ApnSetting.class.getClassLoader()); - mTrafficDescriptor = source.readParcelable(TrafficDescriptor.class.getClassLoader()); + mApnSetting = source.readParcelable(ApnSetting.class.getClassLoader(), android.telephony.data.ApnSetting.class); + mTrafficDescriptor = source.readParcelable(TrafficDescriptor.class.getClassLoader(), android.telephony.data.TrafficDescriptor.class); mPreferred = source.readBoolean(); mSetupTimestamp = source.readLong(); } diff --git a/telephony/java/android/telephony/data/DataServiceCallback.java b/telephony/java/android/telephony/data/DataServiceCallback.java index 051d6c5d5ec0..1ff6ec1779cd 100644 --- a/telephony/java/android/telephony/data/DataServiceCallback.java +++ b/telephony/java/android/telephony/data/DataServiceCallback.java @@ -253,8 +253,10 @@ public class DataServiceCallback { return "RESULT_ERROR_BUSY"; case RESULT_ERROR_ILLEGAL_STATE: return "RESULT_ERROR_ILLEGAL_STATE"; + case RESULT_ERROR_TEMPORARILY_UNAVAILABLE: + return "RESULT_ERROR_TEMPORARILY_UNAVAILABLE"; default: - return "Missing case for result code=" + resultCode; + return "Unknown(" + resultCode + ")"; } } diff --git a/telephony/java/android/telephony/data/Qos.java b/telephony/java/android/telephony/data/Qos.java index 8c437c83e196..9c2a3bb1e15c 100644 --- a/telephony/java/android/telephony/data/Qos.java +++ b/telephony/java/android/telephony/data/Qos.java @@ -136,8 +136,8 @@ public abstract class Qos { protected Qos(@NonNull Parcel source) { type = source.readInt(); - downlink = source.readParcelable(QosBandwidth.class.getClassLoader()); - uplink = source.readParcelable(QosBandwidth.class.getClassLoader()); + downlink = source.readParcelable(QosBandwidth.class.getClassLoader(), android.telephony.data.Qos.QosBandwidth.class); + uplink = source.readParcelable(QosBandwidth.class.getClassLoader(), android.telephony.data.Qos.QosBandwidth.class); } /** diff --git a/telephony/java/android/telephony/data/QosBearerFilter.java b/telephony/java/android/telephony/data/QosBearerFilter.java index d6f0cb02f0aa..0ab7b61bd73d 100644 --- a/telephony/java/android/telephony/data/QosBearerFilter.java +++ b/telephony/java/android/telephony/data/QosBearerFilter.java @@ -256,11 +256,11 @@ public final class QosBearerFilter implements Parcelable { private QosBearerFilter(Parcel source) { localAddresses = new ArrayList<>(); - source.readList(localAddresses, LinkAddress.class.getClassLoader()); + source.readList(localAddresses, LinkAddress.class.getClassLoader(), android.net.LinkAddress.class); remoteAddresses = new ArrayList<>(); - source.readList(remoteAddresses, LinkAddress.class.getClassLoader()); - localPort = source.readParcelable(PortRange.class.getClassLoader()); - remotePort = source.readParcelable(PortRange.class.getClassLoader()); + source.readList(remoteAddresses, LinkAddress.class.getClassLoader(), android.net.LinkAddress.class); + localPort = source.readParcelable(PortRange.class.getClassLoader(), android.telephony.data.QosBearerFilter.PortRange.class); + remotePort = source.readParcelable(PortRange.class.getClassLoader(), android.telephony.data.QosBearerFilter.PortRange.class); protocol = source.readInt(); typeOfServiceMask = source.readInt(); flowLabel = source.readLong(); diff --git a/telephony/java/android/telephony/data/QosBearerSession.java b/telephony/java/android/telephony/data/QosBearerSession.java index ffeb08a17584..dd080856d450 100644 --- a/telephony/java/android/telephony/data/QosBearerSession.java +++ b/telephony/java/android/telephony/data/QosBearerSession.java @@ -46,9 +46,9 @@ public final class QosBearerSession implements Parcelable{ private QosBearerSession(Parcel source) { qosBearerSessionId = source.readInt(); - qos = source.readParcelable(Qos.class.getClassLoader()); + qos = source.readParcelable(Qos.class.getClassLoader(), android.telephony.data.Qos.class); qosBearerFilterList = new ArrayList<>(); - source.readList(qosBearerFilterList, QosBearerFilter.class.getClassLoader()); + source.readList(qosBearerFilterList, QosBearerFilter.class.getClassLoader(), android.telephony.data.QosBearerFilter.class); } public int getQosBearerSessionId() { diff --git a/telephony/java/android/telephony/gba/GbaAuthRequest.java b/telephony/java/android/telephony/gba/GbaAuthRequest.java index 5366e9af3147..2c6021a18ea2 100644 --- a/telephony/java/android/telephony/gba/GbaAuthRequest.java +++ b/telephony/java/android/telephony/gba/GbaAuthRequest.java @@ -120,7 +120,7 @@ public final class GbaAuthRequest implements Parcelable { int token = in.readInt(); int subId = in.readInt(); int appType = in.readInt(); - Uri nafUrl = in.readParcelable(GbaAuthRequest.class.getClassLoader()); + Uri nafUrl = in.readParcelable(GbaAuthRequest.class.getClassLoader(), android.net.Uri.class); int len = in.readInt(); byte[] protocol = new byte[len]; in.readByteArray(protocol); diff --git a/telephony/java/android/telephony/ims/DelegateRequest.java b/telephony/java/android/telephony/ims/DelegateRequest.java index c322d924182a..c5c92009ee32 100644 --- a/telephony/java/android/telephony/ims/DelegateRequest.java +++ b/telephony/java/android/telephony/ims/DelegateRequest.java @@ -63,7 +63,7 @@ public final class DelegateRequest implements Parcelable { */ private DelegateRequest(Parcel in) { mFeatureTags = new ArrayList<>(); - in.readList(mFeatureTags, null /*classLoader*/); + in.readList(mFeatureTags, null /*classLoader*/, java.lang.String.class); } @Override diff --git a/telephony/java/android/telephony/ims/ImsCallProfile.java b/telephony/java/android/telephony/ims/ImsCallProfile.java index 8a665dc92421..e6d7df34f755 100644 --- a/telephony/java/android/telephony/ims/ImsCallProfile.java +++ b/telephony/java/android/telephony/ims/ImsCallProfile.java @@ -843,7 +843,7 @@ public final class ImsCallProfile implements Parcelable { mServiceType = in.readInt(); mCallType = in.readInt(); mCallExtras = in.readBundle(); - mMediaProfile = in.readParcelable(ImsStreamMediaProfile.class.getClassLoader()); + mMediaProfile = in.readParcelable(ImsStreamMediaProfile.class.getClassLoader(), android.telephony.ims.ImsStreamMediaProfile.class); mEmergencyServiceCategories = in.readInt(); mEmergencyUrns = in.createStringArrayList(); mEmergencyCallRouting = in.readInt(); diff --git a/telephony/java/android/telephony/ims/ImsCallSession.java b/telephony/java/android/telephony/ims/ImsCallSession.java index 6569de626702..d65286f26447 100755 --- a/telephony/java/android/telephony/ims/ImsCallSession.java +++ b/telephony/java/android/telephony/ims/ImsCallSession.java @@ -1212,50 +1212,56 @@ public class ImsCallSession { */ @Override public void callSessionInitiating(ImsCallProfile profile) { - if (mListener != null) { - TelephonyUtils.runWithCleanCallingIdentity(()-> mListener.callSessionInitiating( - ImsCallSession.this, profile), mListenerExecutor); - } + TelephonyUtils.runWithCleanCallingIdentity(()-> { + if (mListener != null) { + mListener.callSessionInitiating(ImsCallSession.this, profile); + } + }, mListenerExecutor); } @Override public void callSessionProgressing(ImsStreamMediaProfile profile) { - if (mListener != null) { - TelephonyUtils.runWithCleanCallingIdentity(()-> mListener.callSessionProgressing( - ImsCallSession.this, profile), mListenerExecutor); - } + TelephonyUtils.runWithCleanCallingIdentity(()-> { + if (mListener != null) { + mListener.callSessionProgressing(ImsCallSession.this, profile); + } + }, mListenerExecutor); } @Override public void callSessionInitiated(ImsCallProfile profile) { - if (mListener != null) { - TelephonyUtils.runWithCleanCallingIdentity(()-> mListener.callSessionStarted( - ImsCallSession.this, profile), mListenerExecutor); - } + TelephonyUtils.runWithCleanCallingIdentity(()-> { + if (mListener != null) { + mListener.callSessionStarted(ImsCallSession.this, profile); + } + }, mListenerExecutor); } @Override public void callSessionInitiatingFailed(ImsReasonInfo reasonInfo) { - if (mListener != null) { - TelephonyUtils.runWithCleanCallingIdentity(()-> mListener.callSessionStartFailed( - ImsCallSession.this, reasonInfo), mListenerExecutor); - } + TelephonyUtils.runWithCleanCallingIdentity(()-> { + if (mListener != null) { + mListener.callSessionStartFailed(ImsCallSession.this, reasonInfo); + } + }, mListenerExecutor); } @Override public void callSessionInitiatedFailed(ImsReasonInfo reasonInfo) { - if (mListener != null) { - TelephonyUtils.runWithCleanCallingIdentity(()-> mListener.callSessionStartFailed( - ImsCallSession.this, reasonInfo), mListenerExecutor); - } + TelephonyUtils.runWithCleanCallingIdentity(()-> { + if (mListener != null) { + mListener.callSessionStartFailed(ImsCallSession.this, reasonInfo); + } + }, mListenerExecutor); } @Override public void callSessionTerminated(ImsReasonInfo reasonInfo) { - if (mListener != null) { - TelephonyUtils.runWithCleanCallingIdentity(()-> mListener.callSessionTerminated( - ImsCallSession.this, reasonInfo), mListenerExecutor); - } + TelephonyUtils.runWithCleanCallingIdentity(()-> { + if (mListener != null) { + mListener.callSessionTerminated(ImsCallSession.this, reasonInfo); + } + }, mListenerExecutor); } /** @@ -1263,51 +1269,56 @@ public class ImsCallSession { */ @Override public void callSessionHeld(ImsCallProfile profile) { - if (mListener != null) { - TelephonyUtils.runWithCleanCallingIdentity(()-> mListener.callSessionHeld( - ImsCallSession.this, profile), mListenerExecutor); - } + TelephonyUtils.runWithCleanCallingIdentity(()-> { + if (mListener != null) { + mListener.callSessionHeld(ImsCallSession.this, profile); + } + }, mListenerExecutor); } @Override public void callSessionHoldFailed(ImsReasonInfo reasonInfo) { - if (mListener != null) { - TelephonyUtils.runWithCleanCallingIdentity(()-> mListener.callSessionHoldFailed( - ImsCallSession.this, reasonInfo), mListenerExecutor); - } + TelephonyUtils.runWithCleanCallingIdentity(()-> { + if (mListener != null) { + mListener.callSessionHoldFailed(ImsCallSession.this, reasonInfo); + } + }, mListenerExecutor); } @Override public void callSessionHoldReceived(ImsCallProfile profile) { - if (mListener != null) { - TelephonyUtils.runWithCleanCallingIdentity(()-> mListener.callSessionHoldReceived( - ImsCallSession.this, profile), mListenerExecutor); - } + TelephonyUtils.runWithCleanCallingIdentity(()-> { + if (mListener != null) { + mListener.callSessionHoldReceived(ImsCallSession.this, profile); + } + }, mListenerExecutor); } @Override public void callSessionResumed(ImsCallProfile profile) { - if (mListener != null) { - TelephonyUtils.runWithCleanCallingIdentity(()-> mListener.callSessionResumed( - ImsCallSession.this, profile), mListenerExecutor); - } + TelephonyUtils.runWithCleanCallingIdentity(()-> { + if (mListener != null) { + mListener.callSessionResumed(ImsCallSession.this, profile); + } + }, mListenerExecutor); } @Override public void callSessionResumeFailed(ImsReasonInfo reasonInfo) { - if (mListener != null) { - TelephonyUtils.runWithCleanCallingIdentity(()-> mListener.callSessionResumeFailed( - ImsCallSession.this, reasonInfo), mListenerExecutor); - } + TelephonyUtils.runWithCleanCallingIdentity(()-> { + if (mListener != null) { + mListener.callSessionResumeFailed(ImsCallSession.this, reasonInfo); + } + }, mListenerExecutor); } @Override public void callSessionResumeReceived(ImsCallProfile profile) { - if (mListener != null) { - TelephonyUtils.runWithCleanCallingIdentity(()-> - mListener.callSessionResumeReceived(ImsCallSession.this, profile), - mListenerExecutor); - } + TelephonyUtils.runWithCleanCallingIdentity(()-> { + if (mListener != null) { + mListener.callSessionResumeReceived(ImsCallSession.this, profile); + } + }, mListenerExecutor); } /** @@ -1330,8 +1341,8 @@ public class ImsCallSession { */ @Override public void callSessionMergeComplete(IImsCallSession newSession) { - if (mListener != null) { - TelephonyUtils.runWithCleanCallingIdentity(()-> { + TelephonyUtils.runWithCleanCallingIdentity(()-> { + if (mListener != null) { if (newSession != null) { // New session created after conference mListener.callSessionMergeComplete(new ImsCallSession(newSession)); @@ -1339,8 +1350,8 @@ public class ImsCallSession { // Session already exists. Hence no need to pass mListener.callSessionMergeComplete(null); } - }, mListenerExecutor); - } + } + }, mListenerExecutor); } /** @@ -1350,11 +1361,11 @@ public class ImsCallSession { */ @Override public void callSessionMergeFailed(ImsReasonInfo reasonInfo) { - if (mListener != null) { - TelephonyUtils.runWithCleanCallingIdentity(()-> - mListener.callSessionMergeFailed(ImsCallSession.this, reasonInfo), - mListenerExecutor); - } + TelephonyUtils.runWithCleanCallingIdentity(()-> { + if (mListener != null) { + mListener.callSessionMergeFailed(ImsCallSession.this, reasonInfo); + } + }, mListenerExecutor); } /** @@ -1362,29 +1373,29 @@ public class ImsCallSession { */ @Override public void callSessionUpdated(ImsCallProfile profile) { - if (mListener != null) { - TelephonyUtils.runWithCleanCallingIdentity(()-> - mListener.callSessionUpdated(ImsCallSession.this, profile), - mListenerExecutor); - } + TelephonyUtils.runWithCleanCallingIdentity(()-> { + if (mListener != null) { + mListener.callSessionUpdated(ImsCallSession.this, profile); + } + }, mListenerExecutor); } @Override public void callSessionUpdateFailed(ImsReasonInfo reasonInfo) { - if (mListener != null) { - TelephonyUtils.runWithCleanCallingIdentity(()-> - mListener.callSessionUpdateFailed(ImsCallSession.this, reasonInfo), - mListenerExecutor); - } + TelephonyUtils.runWithCleanCallingIdentity(()-> { + if (mListener != null) { + mListener.callSessionUpdateFailed(ImsCallSession.this, reasonInfo); + } + }, mListenerExecutor); } @Override public void callSessionUpdateReceived(ImsCallProfile profile) { - if (mListener != null) { - TelephonyUtils.runWithCleanCallingIdentity(()-> - mListener.callSessionUpdateReceived(ImsCallSession.this, profile), - mListenerExecutor); - } + TelephonyUtils.runWithCleanCallingIdentity(()-> { + if (mListener != null) { + mListener.callSessionUpdateReceived(ImsCallSession.this, profile); + } + }, mListenerExecutor); } /** @@ -1393,30 +1404,33 @@ public class ImsCallSession { @Override public void callSessionConferenceExtended(IImsCallSession newSession, ImsCallProfile profile) { - if (mListener != null) { - TelephonyUtils.runWithCleanCallingIdentity(()-> - mListener.callSessionConferenceExtended(ImsCallSession.this, - new ImsCallSession(newSession), profile), mListenerExecutor); - } + TelephonyUtils.runWithCleanCallingIdentity(()-> { + if (mListener != null) { + mListener.callSessionConferenceExtended(ImsCallSession.this, + new ImsCallSession(newSession), profile); + } + }, mListenerExecutor); } @Override public void callSessionConferenceExtendFailed(ImsReasonInfo reasonInfo) { - if (mListener != null) { - TelephonyUtils.runWithCleanCallingIdentity(()-> - mListener.callSessionConferenceExtendFailed( - ImsCallSession.this, reasonInfo), mListenerExecutor); - } + TelephonyUtils.runWithCleanCallingIdentity(()-> { + if (mListener != null) { + mListener.callSessionConferenceExtendFailed( + ImsCallSession.this, reasonInfo); + } + }, mListenerExecutor); } @Override public void callSessionConferenceExtendReceived(IImsCallSession newSession, ImsCallProfile profile) { - if (mListener != null) { - TelephonyUtils.runWithCleanCallingIdentity(()-> - mListener.callSessionConferenceExtendReceived(ImsCallSession.this, - new ImsCallSession(newSession), profile), mListenerExecutor); - } + TelephonyUtils.runWithCleanCallingIdentity(()-> { + if (mListener != null) { + mListener.callSessionConferenceExtendReceived(ImsCallSession.this, + new ImsCallSession(newSession), profile); + } + }, mListenerExecutor); } /** @@ -1425,38 +1439,41 @@ public class ImsCallSession { */ @Override public void callSessionInviteParticipantsRequestDelivered() { - if (mListener != null) { - TelephonyUtils.runWithCleanCallingIdentity(()-> - mListener.callSessionInviteParticipantsRequestDelivered( - ImsCallSession.this), mListenerExecutor); - } + TelephonyUtils.runWithCleanCallingIdentity(()-> { + if (mListener != null) { + mListener.callSessionInviteParticipantsRequestDelivered( + ImsCallSession.this); + } + }, mListenerExecutor); } @Override public void callSessionInviteParticipantsRequestFailed(ImsReasonInfo reasonInfo) { - if (mListener != null) { - TelephonyUtils.runWithCleanCallingIdentity(()-> - mListener.callSessionInviteParticipantsRequestFailed(ImsCallSession.this, - reasonInfo), mListenerExecutor); - } + TelephonyUtils.runWithCleanCallingIdentity(()-> { + if (mListener != null) { + mListener.callSessionInviteParticipantsRequestFailed(ImsCallSession.this, + reasonInfo); + } + }, mListenerExecutor); } @Override public void callSessionRemoveParticipantsRequestDelivered() { - if (mListener != null) { - TelephonyUtils.runWithCleanCallingIdentity(()-> - mListener.callSessionRemoveParticipantsRequestDelivered( - ImsCallSession.this), mListenerExecutor); - } + TelephonyUtils.runWithCleanCallingIdentity(()-> { + if (mListener != null) { + mListener.callSessionRemoveParticipantsRequestDelivered(ImsCallSession.this); + } + }, mListenerExecutor); } @Override public void callSessionRemoveParticipantsRequestFailed(ImsReasonInfo reasonInfo) { - if (mListener != null) { - TelephonyUtils.runWithCleanCallingIdentity(()-> - mListener.callSessionRemoveParticipantsRequestFailed(ImsCallSession.this, - reasonInfo), mListenerExecutor); - } + TelephonyUtils.runWithCleanCallingIdentity(()-> { + if (mListener != null) { + mListener.callSessionRemoveParticipantsRequestFailed(ImsCallSession.this, + reasonInfo); + } + }, mListenerExecutor); } /** @@ -1464,11 +1481,11 @@ public class ImsCallSession { */ @Override public void callSessionConferenceStateUpdated(ImsConferenceState state) { - if (mListener != null) { - TelephonyUtils.runWithCleanCallingIdentity(()-> - mListener.callSessionConferenceStateUpdated(ImsCallSession.this, state), - mListenerExecutor); - } + TelephonyUtils.runWithCleanCallingIdentity(()-> { + if (mListener != null) { + mListener.callSessionConferenceStateUpdated(ImsCallSession.this, state); + } + }, mListenerExecutor); } /** @@ -1476,11 +1493,12 @@ public class ImsCallSession { */ @Override public void callSessionUssdMessageReceived(int mode, String ussdMessage) { - if (mListener != null) { - TelephonyUtils.runWithCleanCallingIdentity(()-> - mListener.callSessionUssdMessageReceived(ImsCallSession.this, mode, - ussdMessage), mListenerExecutor); - } + TelephonyUtils.runWithCleanCallingIdentity(()-> { + if (mListener != null) { + mListener.callSessionUssdMessageReceived(ImsCallSession.this, mode, + ussdMessage); + } + }, mListenerExecutor); } /** @@ -1496,11 +1514,12 @@ public class ImsCallSession { */ @Override public void callSessionMayHandover(int srcNetworkType, int targetNetworkType) { - if (mListener != null) { - TelephonyUtils.runWithCleanCallingIdentity(()-> - mListener.callSessionMayHandover(ImsCallSession.this, srcNetworkType, - targetNetworkType), mListenerExecutor); - } + TelephonyUtils.runWithCleanCallingIdentity(()-> { + if (mListener != null) { + mListener.callSessionMayHandover(ImsCallSession.this, srcNetworkType, + targetNetworkType); + } + }, mListenerExecutor); } /** @@ -1509,11 +1528,12 @@ public class ImsCallSession { @Override public void callSessionHandover(int srcNetworkType, int targetNetworkType, ImsReasonInfo reasonInfo) { - if (mListener != null) { - TelephonyUtils.runWithCleanCallingIdentity(()-> - mListener.callSessionHandover(ImsCallSession.this, srcNetworkType, - targetNetworkType, reasonInfo), mListenerExecutor); - } + TelephonyUtils.runWithCleanCallingIdentity(()-> { + if (mListener != null) { + mListener.callSessionHandover(ImsCallSession.this, srcNetworkType, + targetNetworkType, reasonInfo); + } + }, mListenerExecutor); } /** @@ -1522,11 +1542,12 @@ public class ImsCallSession { @Override public void callSessionHandoverFailed(int srcNetworkType, int targetNetworkType, ImsReasonInfo reasonInfo) { - if (mListener != null) { - TelephonyUtils.runWithCleanCallingIdentity(()-> - mListener.callSessionHandoverFailed(ImsCallSession.this, srcNetworkType, - targetNetworkType, reasonInfo), mListenerExecutor); - } + TelephonyUtils.runWithCleanCallingIdentity(()-> { + if (mListener != null) { + mListener.callSessionHandoverFailed(ImsCallSession.this, srcNetworkType, + targetNetworkType, reasonInfo); + } + }, mListenerExecutor); } /** @@ -1534,11 +1555,11 @@ public class ImsCallSession { */ @Override public void callSessionTtyModeReceived(int mode) { - if (mListener != null) { - TelephonyUtils.runWithCleanCallingIdentity(()-> - mListener.callSessionTtyModeReceived(ImsCallSession.this, mode), - mListenerExecutor); - } + TelephonyUtils.runWithCleanCallingIdentity(()-> { + if (mListener != null) { + mListener.callSessionTtyModeReceived(ImsCallSession.this, mode); + } + }, mListenerExecutor); } /** @@ -1548,20 +1569,22 @@ public class ImsCallSession { * otherwise. */ public void callSessionMultipartyStateChanged(boolean isMultiParty) { - if (mListener != null) { - TelephonyUtils.runWithCleanCallingIdentity(()-> - mListener.callSessionMultipartyStateChanged(ImsCallSession.this, - isMultiParty), mListenerExecutor); - } + TelephonyUtils.runWithCleanCallingIdentity(()-> { + if (mListener != null) { + mListener.callSessionMultipartyStateChanged(ImsCallSession.this, + isMultiParty); + } + }, mListenerExecutor); } @Override public void callSessionSuppServiceReceived(ImsSuppServiceNotification suppServiceInfo ) { - if (mListener != null) { - TelephonyUtils.runWithCleanCallingIdentity(()-> - mListener.callSessionSuppServiceReceived(ImsCallSession.this, - suppServiceInfo), mListenerExecutor); - } + TelephonyUtils.runWithCleanCallingIdentity(()-> { + if (mListener != null) { + mListener.callSessionSuppServiceReceived(ImsCallSession.this, + suppServiceInfo); + } + }, mListenerExecutor); } /** @@ -1569,11 +1592,12 @@ public class ImsCallSession { */ @Override public void callSessionRttModifyRequestReceived(ImsCallProfile callProfile) { - if (mListener != null) { - TelephonyUtils.runWithCleanCallingIdentity(()-> - mListener.callSessionRttModifyRequestReceived(ImsCallSession.this, - callProfile), mListenerExecutor); - } + TelephonyUtils.runWithCleanCallingIdentity(()-> { + if (mListener != null) { + mListener.callSessionRttModifyRequestReceived(ImsCallSession.this, + callProfile); + } + }, mListenerExecutor); } /** @@ -1581,11 +1605,11 @@ public class ImsCallSession { */ @Override public void callSessionRttModifyResponseReceived(int status) { - if (mListener != null) { - TelephonyUtils.runWithCleanCallingIdentity(()-> - mListener.callSessionRttModifyResponseReceived(status), - mListenerExecutor); - } + TelephonyUtils.runWithCleanCallingIdentity(()-> { + if (mListener != null) { + mListener.callSessionRttModifyResponseReceived(status); + } + }, mListenerExecutor); } /** @@ -1593,10 +1617,11 @@ public class ImsCallSession { */ @Override public void callSessionRttMessageReceived(String rttMessage) { - if (mListener != null) { - TelephonyUtils.runWithCleanCallingIdentity(()-> - mListener.callSessionRttMessageReceived(rttMessage), mListenerExecutor); - } + TelephonyUtils.runWithCleanCallingIdentity(()-> { + if (mListener != null) { + mListener.callSessionRttMessageReceived(rttMessage); + } + }, mListenerExecutor); } /** @@ -1604,28 +1629,29 @@ public class ImsCallSession { */ @Override public void callSessionRttAudioIndicatorChanged(ImsStreamMediaProfile profile) { - if (mListener != null) { - TelephonyUtils.runWithCleanCallingIdentity(()-> - mListener.callSessionRttAudioIndicatorChanged(profile), - mListenerExecutor); - } + TelephonyUtils.runWithCleanCallingIdentity(()-> { + if (mListener != null) { + mListener.callSessionRttAudioIndicatorChanged(profile); + } + }, mListenerExecutor); } @Override public void callSessionTransferred() { - if (mListener != null) { - TelephonyUtils.runWithCleanCallingIdentity(()-> - mListener.callSessionTransferred(ImsCallSession.this), mListenerExecutor); - } + TelephonyUtils.runWithCleanCallingIdentity(()-> { + if (mListener != null) { + mListener.callSessionTransferred(ImsCallSession.this); + } + }, mListenerExecutor); } @Override public void callSessionTransferFailed(@Nullable ImsReasonInfo reasonInfo) { - if (mListener != null) { - TelephonyUtils.runWithCleanCallingIdentity(()-> - mListener.callSessionTransferFailed(ImsCallSession.this, reasonInfo), - mListenerExecutor); - } + TelephonyUtils.runWithCleanCallingIdentity(()-> { + if (mListener != null) { + mListener.callSessionTransferFailed(ImsCallSession.this, reasonInfo); + } + }, mListenerExecutor); } /** @@ -1634,10 +1660,11 @@ public class ImsCallSession { */ @Override public void callSessionDtmfReceived(char dtmf) { - if (mListener != null) { - TelephonyUtils.runWithCleanCallingIdentity(()-> mListener.callSessionDtmfReceived( - dtmf), mListenerExecutor); - } + TelephonyUtils.runWithCleanCallingIdentity(()-> { + if (mListener != null) { + mListener.callSessionDtmfReceived(dtmf); + } + }, mListenerExecutor); } /** @@ -1645,10 +1672,11 @@ public class ImsCallSession { */ @Override public void callQualityChanged(CallQuality callQuality) { - if (mListener != null) { - TelephonyUtils.runWithCleanCallingIdentity(()-> mListener.callQualityChanged( - callQuality), mListenerExecutor); - } + TelephonyUtils.runWithCleanCallingIdentity(()-> { + if (mListener != null) { + mListener.callQualityChanged(callQuality); + } + }, mListenerExecutor); } /** @@ -1658,11 +1686,12 @@ public class ImsCallSession { @Override public void callSessionRtpHeaderExtensionsReceived( @NonNull List<RtpHeaderExtension> extensions) { - if (mListener != null) { - TelephonyUtils.runWithCleanCallingIdentity(()-> - mListener.callSessionRtpHeaderExtensionsReceived( - new ArraySet<RtpHeaderExtension>(extensions)), mListenerExecutor); - } + TelephonyUtils.runWithCleanCallingIdentity(()-> { + if (mListener != null) { + mListener.callSessionRtpHeaderExtensionsReceived( + new ArraySet<RtpHeaderExtension>(extensions)); + } + }, mListenerExecutor); } } diff --git a/telephony/java/android/telephony/ims/ImsConferenceState.java b/telephony/java/android/telephony/ims/ImsConferenceState.java index 1fa5f52968e5..d4d8c44196d5 100644 --- a/telephony/java/android/telephony/ims/ImsConferenceState.java +++ b/telephony/java/android/telephony/ims/ImsConferenceState.java @@ -133,7 +133,7 @@ public final class ImsConferenceState implements Parcelable { for (int i = 0; i < size; ++i) { String user = in.readString(); - Bundle state = in.readParcelable(null); + Bundle state = in.readParcelable(null, android.os.Bundle.class); mParticipants.put(user, state); } } diff --git a/telephony/java/android/telephony/ims/ImsExternalCallState.java b/telephony/java/android/telephony/ims/ImsExternalCallState.java index c663e393fe06..d45110772ce4 100644 --- a/telephony/java/android/telephony/ims/ImsExternalCallState.java +++ b/telephony/java/android/telephony/ims/ImsExternalCallState.java @@ -141,8 +141,8 @@ public final class ImsExternalCallState implements Parcelable { public ImsExternalCallState(Parcel in) { mCallId = in.readInt(); ClassLoader classLoader = ImsExternalCallState.class.getClassLoader(); - mAddress = in.readParcelable(classLoader); - mLocalAddress = in.readParcelable(classLoader); + mAddress = in.readParcelable(classLoader, android.net.Uri.class); + mLocalAddress = in.readParcelable(classLoader, android.net.Uri.class); mIsPullable = (in.readInt() != 0); mCallState = in.readInt(); mCallType = in.readInt(); diff --git a/telephony/java/android/telephony/ims/ImsRegistrationAttributes.java b/telephony/java/android/telephony/ims/ImsRegistrationAttributes.java index ccb3231526dd..b77d3063e2cc 100644 --- a/telephony/java/android/telephony/ims/ImsRegistrationAttributes.java +++ b/telephony/java/android/telephony/ims/ImsRegistrationAttributes.java @@ -153,7 +153,7 @@ public final class ImsRegistrationAttributes implements Parcelable { mTransportType = source.readInt(); mImsAttributeFlags = source.readInt(); mFeatureTags = new ArrayList<>(); - source.readList(mFeatureTags, null /*classloader*/); + source.readList(mFeatureTags, null /*classloader*/, java.lang.String.class); } /** diff --git a/telephony/java/android/telephony/ims/ImsSsData.java b/telephony/java/android/telephony/ims/ImsSsData.java index 868dea6a3121..9f4b77e22dd7 100644 --- a/telephony/java/android/telephony/ims/ImsSsData.java +++ b/telephony/java/android/telephony/ims/ImsSsData.java @@ -365,8 +365,8 @@ public final class ImsSsData implements Parcelable { serviceClass = in.readInt(); result = in.readInt(); mSsInfo = in.createIntArray(); - mCfInfo = in.readParcelableList(new ArrayList<>(), this.getClass().getClassLoader()); - mImsSsInfo = in.readParcelableList(new ArrayList<>(), this.getClass().getClassLoader()); + mCfInfo = in.readParcelableList(new ArrayList<>(), this.getClass().getClassLoader(), android.telephony.ims.ImsCallForwardInfo.class); + mImsSsInfo = in.readParcelableList(new ArrayList<>(), this.getClass().getClassLoader(), android.telephony.ims.ImsSsInfo.class); } public static final @android.annotation.NonNull Creator<ImsSsData> CREATOR = new Creator<ImsSsData>() { diff --git a/telephony/java/android/telephony/ims/ProvisioningManager.java b/telephony/java/android/telephony/ims/ProvisioningManager.java index dbf4c99939de..677c1a9a7d76 100644 --- a/telephony/java/android/telephony/ims/ProvisioningManager.java +++ b/telephony/java/android/telephony/ims/ProvisioningManager.java @@ -34,6 +34,7 @@ import android.telephony.CarrierConfigManager; import android.telephony.SubscriptionManager; import android.telephony.TelephonyFrameworkInitializer; import android.telephony.TelephonyManager; +import android.telephony.ims.aidl.IFeatureProvisioningCallback; import android.telephony.ims.aidl.IImsConfigCallback; import android.telephony.ims.aidl.IRcsConfigCallback; import android.telephony.ims.feature.MmTelFeature; @@ -54,18 +55,12 @@ import java.util.concurrent.Executor; * IMS provisioning keys are defined per carrier or OEM using OMA-DM or other provisioning * applications and may vary. It is up to the carrier and OEM applications to ensure that the * correct provisioning keys are being used when integrating with a vendor's ImsService. - * - * Note: For compatibility purposes, the integer values [0 - 99] used in - * {@link #setProvisioningIntValue(int, int)} have been reserved for existing provisioning keys - * previously defined in the Android framework. Please do not redefine new provisioning keys in this - * range or it may generate collisions with existing keys. Some common constants have also been - * defined in this class to make integrating with other system apps easier. - * @hide */ -@SystemApi @RequiresFeature(PackageManager.FEATURE_TELEPHONY_IMS) public class ProvisioningManager { + private static final String TAG = "ProvisioningManager"; + /**@hide*/ @StringDef(prefix = "STRING_QUERY_RESULT_ERROR_", value = { STRING_QUERY_RESULT_ERROR_GENERIC, @@ -76,14 +71,18 @@ public class ProvisioningManager { /** * The query from {@link #getProvisioningStringValue(int)} has resulted in an unspecified error. + * @hide */ + @SystemApi public static final String STRING_QUERY_RESULT_ERROR_GENERIC = "STRING_QUERY_RESULT_ERROR_GENERIC"; /** * The query from {@link #getProvisioningStringValue(int)} has resulted in an error because the * ImsService implementation was not ready for provisioning queries. + * @hide */ + @SystemApi public static final String STRING_QUERY_RESULT_ERROR_NOT_READY = "STRING_QUERY_RESULT_ERROR_NOT_READY"; @@ -95,12 +94,16 @@ public class ProvisioningManager { /** * The integer result of provisioning for the queried key is disabled. + * @hide */ + @SystemApi public static final int PROVISIONING_VALUE_DISABLED = 0; /** * The integer result of provisioning for the queried key is enabled. + * @hide */ + @SystemApi public static final int PROVISIONING_VALUE_ENABLED = 1; @@ -445,27 +448,31 @@ public class ProvisioningManager { /** * Override the user-defined WiFi Roaming enabled setting for this subscription, defined in - * {@link SubscriptionManager#WFC_ROAMING_ENABLED_CONTENT_URI}, for the purposes of provisioning - * the subscription for WiFi Calling. + * {@link android.telephony.SubscriptionManager#WFC_ROAMING_ENABLED_CONTENT_URI}, + * for the purposes of provisioning the subscription for WiFi Calling. * - * @see #getProvisioningIntValue(int) * @see #setProvisioningIntValue(int, int) + * @see #getProvisioningIntValue(int) + * @hide */ + @SystemApi public static final int KEY_VOICE_OVER_WIFI_ROAMING_ENABLED_OVERRIDE = 26; /** * Override the user-defined WiFi mode for this subscription, defined in - * {@link SubscriptionManager#WFC_MODE_CONTENT_URI}, for the purposes of provisioning - * this subscription for WiFi Calling. + * {@link android.telephony.SubscriptionManager#WFC_MODE_CONTENT_URI}, + * for the purposes of provisioning this subscription for WiFi Calling. * * Valid values for this key are: * {@link ImsMmTelManager#WIFI_MODE_WIFI_ONLY}, * {@link ImsMmTelManager#WIFI_MODE_CELLULAR_PREFERRED}, or * {@link ImsMmTelManager#WIFI_MODE_WIFI_PREFERRED}. * - * @see #getProvisioningIntValue(int) * @see #setProvisioningIntValue(int, int) + * @see #getProvisioningIntValue(int) + * @hide */ + @SystemApi public static final int KEY_VOICE_OVER_WIFI_MODE_OVERRIDE = 27; /** @@ -864,7 +871,9 @@ public class ProvisioningManager { * <p>Value is in String format. * @see #setProvisioningStringValue(int, String) * @see #getProvisioningStringValue(int) + * @hide */ + @SystemApi public static final int KEY_VOICE_OVER_WIFI_ENTITLEMENT_ID = 67; /** @@ -886,7 +895,9 @@ public class ProvisioningManager { /** * Callback for IMS provisioning changes. + * @hide */ + @SystemApi public static class Callback { private static class CallbackBinder extends IImsConfigCallback.Stub { @@ -956,11 +967,105 @@ public class ProvisioningManager { } } + /** + * Callback for IMS provisioning feature changes. + */ + public static class FeatureProvisioningCallback { + + private static class CallbackBinder extends IFeatureProvisioningCallback.Stub { + + private final FeatureProvisioningCallback mFeatureProvisioningCallback; + private Executor mExecutor; + + private CallbackBinder(FeatureProvisioningCallback featureProvisioningCallback) { + mFeatureProvisioningCallback = featureProvisioningCallback; + } + + @Override + public final void onFeatureProvisioningChanged( + int capability, int tech, boolean isProvisioned) { + final long callingIdentity = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> + mFeatureProvisioningCallback.onFeatureProvisioningChanged( + capability, tech, isProvisioned)); + } finally { + restoreCallingIdentity(callingIdentity); + } + } + + @Override + public final void onRcsFeatureProvisioningChanged( + int capability, int tech, boolean isProvisioned) { + final long callingIdentity = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> + mFeatureProvisioningCallback.onRcsFeatureProvisioningChanged( + capability, tech, isProvisioned)); + } finally { + restoreCallingIdentity(callingIdentity); + } + } + + private void setExecutor(Executor executor) { + mExecutor = executor; + } + } + + private final CallbackBinder mBinder = new CallbackBinder(this); + + /** + * The IMS MMTEL provisioning has changed for a specific capability and IMS + * registration technology. + * @param capability The MMTEL capability that provisioning has changed for. + * @param tech The IMS registration technology associated with the MMTEL capability that + * provisioning has changed for. + * @param isProvisioned {@code true} if the capability is provisioned for the technology + * specified, or {@code false} if the capability is not provisioned for the technology + * specified. + */ + public void onFeatureProvisioningChanged( + @MmTelFeature.MmTelCapabilities.MmTelCapability int capability, + @ImsRegistrationImplBase.ImsRegistrationTech int tech, + boolean isProvisioned) { + // Base Implementation + } + + /** + * The IMS RCS provisioning has changed for a specific capability and IMS + * registration technology. + * @param capability The RCS capability that provisioning has changed for. + * @param tech The IMS registration technology associated with the RCS capability that + * provisioning has changed for. + * @param isProvisioned {@code true} if the capability is provisioned for the technology + * specified, or {@code false} if the capability is not provisioned for the technology + * specified. + */ + public void onRcsFeatureProvisioningChanged( + @RcsFeature.RcsImsCapabilities.RcsImsCapabilityFlag int capability, + @ImsRegistrationImplBase.ImsRegistrationTech int tech, + boolean isProvisioned) { + // Base Implementation + } + + /**@hide*/ + public final IFeatureProvisioningCallback getBinder() { + return mBinder; + } + + /**@hide*/ + public void setExecutor(Executor executor) { + mBinder.setExecutor(executor); + } + } + private int mSubId; /** * The callback for RCS provisioning changes. + * @hide */ + @SystemApi public static class RcsProvisioningCallback { private static class CallbackBinder extends IRcsConfigCallback.Stub { @@ -1098,7 +1203,9 @@ public class ProvisioningManager { * @param subId The ID of the subscription that this ProvisioningManager will use. * @see android.telephony.SubscriptionManager#getActiveSubscriptionInfoList() * @throws IllegalArgumentException if the subscription is invalid. + * @hide */ + @SystemApi public static @NonNull ProvisioningManager createForSubscriptionId(int subId) { if (!SubscriptionManager.isValidSubscriptionId(subId)) { throw new IllegalArgumentException("Invalid subscription ID"); @@ -1107,7 +1214,9 @@ public class ProvisioningManager { return new ProvisioningManager(subId); } - private ProvisioningManager(int subId) { + /**@hide*/ + //@SystemApi + public ProvisioningManager(int subId) { mSubId = subId; } @@ -1116,6 +1225,12 @@ public class ProvisioningManager { * * When the subscription associated with this callback is removed (SIM removed, ESIM swap, * etc...), this callback will automatically be removed. + * + * <p> Requires Permission: + * <ul> + * <li>{@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE},</li> + * </ul> + * * @param executor The {@link Executor} to call the callback methods on * @param callback The provisioning callbackto be registered. * @see #unregisterProvisioningChangedCallback(Callback) @@ -1126,7 +1241,9 @@ public class ProvisioningManager { * the {@link ImsService} associated with the subscription is not available. This can happen if * the service crashed, for example. See {@link ImsException#getCode()} for a more detailed * reason. + * @hide */ + @SystemApi @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerProvisioningChangedCallback(@NonNull @CallbackExecutor Executor executor, @NonNull Callback callback) throws ImsException { @@ -1144,12 +1261,20 @@ public class ProvisioningManager { * Unregister an existing {@link Callback}. When the subscription associated with this * callback is removed (SIM removed, ESIM swap, etc...), this callback will automatically be * removed. If this method is called for an inactive subscription, it will result in a no-op. + * + * <p> Requires Permission: + * <ul> + * <li>{@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE},</li> + * </ul> + * * @param callback The existing {@link Callback} to be removed. * @see #registerProvisioningChangedCallback(Executor, Callback) * * @throws IllegalArgumentException if the subscription associated with this callback is * invalid. + * @hide */ + @SystemApi @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void unregisterProvisioningChangedCallback(@NonNull Callback callback) { try { @@ -1160,6 +1285,62 @@ public class ProvisioningManager { } /** + * Register a new {@link FeatureProvisioningCallback}, which is used to listen for + * IMS feature provisioning updates. + * <p> + * When the subscription associated with this callback is removed (SIM removed, + * ESIM swap,etc...), this callback will automatically be removed. + * + * <p> Requires Permission: + * <ul> + * <li> android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE,</li> + * <li>{@link android.Manifest.permission#READ_PRECISE_PHONE_STATE},</li> + * <li>or that the caller has carrier privileges (see + * {@link TelephonyManager#hasCarrierPrivileges()}).</li> + * </ul> + * + * @param executor The executor that the callback methods will be called on. + * @param callback The callback instance being registered. + * @throws ImsException if the subscription associated with this callback is + * valid, but the {@link ImsService the service crashed, for example. See + * {@link ImsException#getCode()} for a more detailed reason. + */ + @RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE) + public void registerFeatureProvisioningChangedCallback( + @NonNull @CallbackExecutor Executor executor, + @NonNull FeatureProvisioningCallback callback) throws ImsException { + callback.setExecutor(executor); + try { + getITelephony().registerFeatureProvisioningChangedCallback(mSubId, + callback.getBinder()); + } catch (ServiceSpecificException e) { + throw new ImsException(e.getMessage(), e.errorCode); + } catch (RemoteException | IllegalStateException e) { + throw new ImsException(e.getMessage(), ImsException.CODE_ERROR_SERVICE_UNAVAILABLE); + } + } + + /** + * Unregisters a previously registered {@link FeatureProvisioningCallback} + * instance. When the subscription associated with this + * callback is removed (SIM removed, ESIM swap, etc...), this callback will + * automatically be removed. If this method is called for an inactive + * subscription, it will result in a no-op. + * + * @param callback The existing {@link FeatureProvisioningCallback} to be removed. + * @see #registerFeatureProvisioningChangedCallback(Executor, FeatureProvisioningCallback) + */ + public void unregisterFeatureProvisioningChangedCallback( + @NonNull FeatureProvisioningCallback callback) { + try { + getITelephony().unregisterFeatureProvisioningChangedCallback(mSubId, + callback.getBinder()); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + /** * Query for the integer value associated with the provided key. * * This operation is blocking and should not be performed on the UI thread. @@ -1168,7 +1349,9 @@ public class ProvisioningManager { * @return an integer value for the provided key, or * {@link ImsConfigImplBase#CONFIG_RESULT_UNKNOWN} if the key doesn't exist. * @throws IllegalArgumentException if the key provided was invalid. + * @hide */ + @SystemApi @WorkerThread @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getProvisioningIntValue(int key) { @@ -1188,7 +1371,9 @@ public class ProvisioningManager { * @return a String value for the provided key, {@code null} if the key doesn't exist, or * {@link StringResultError} if there was an error getting the value for the provided key. * @throws IllegalArgumentException if the key provided was invalid. + * @hide */ + @SystemApi @WorkerThread @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public @Nullable @StringResultError String getProvisioningStringValue(int key) { @@ -1209,7 +1394,15 @@ public class ProvisioningManager { * @param key An integer that represents the provisioning key, which is defined by the OEM. * @param value a integer value for the provided key. * @return the result of setting the configuration value. + * @hide + * + * Note: For compatibility purposes, the integer values [0 - 99] used in + * {@link #setProvisioningIntValue(int, int)} have been reserved for existing provisioning keys + * previously defined in the Android framework. Please do not redefine new provisioning keys + * in this range or it may generate collisions with existing keys. Some common constants have + * also been defined in this class to make integrating with other system apps easier. */ + @SystemApi @WorkerThread @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE) public @ImsConfigImplBase.SetConfigResult int setProvisioningIntValue(int key, int value) { @@ -1229,7 +1422,9 @@ public class ProvisioningManager { * should be appropriately namespaced to avoid collision. * @param value a String value for the provided key. * @return the result of setting the configuration value. + * @hide */ + @SystemApi @WorkerThread @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE) public @ImsConfigImplBase.SetConfigResult int setProvisioningStringValue(int key, @@ -1249,8 +1444,14 @@ public class ProvisioningManager { * does not support the capability/technology combination specified, this operation will be a * no-op. * - * @see CarrierConfigManager#KEY_CARRIER_UT_PROVISIONING_REQUIRED_BOOL - * @see CarrierConfigManager#KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL + * <p>Requires Permission: + * <ul> + * <li>{@link android.Manifest.permission#MODIFY_PHONE_STATE},</li> + * <li>or that the calling app has carrier privileges (see</li> + * <li>{@link TelephonyManager#hasCarrierPrivileges}).</li> + * </ul> + * + * @see CarrierConfigManager.Ims#KEY_MMTEL_REQUIRES_PROVISIONING_BUNDLE * @param isProvisioned true if the device is provisioned for UT over IMS, false otherwise. */ @WorkerThread @@ -1258,9 +1459,10 @@ public class ProvisioningManager { public void setProvisioningStatusForCapability( @MmTelFeature.MmTelCapabilities.MmTelCapability int capability, @ImsRegistrationImplBase.ImsRegistrationTech int tech, boolean isProvisioned) { + try { getITelephony().setImsProvisioningStatusForCapability(mSubId, capability, tech, - isProvisioned); + isProvisioned); } catch (RemoteException e) { throw e.rethrowAsRuntimeException(); } @@ -1274,14 +1476,21 @@ public class ProvisioningManager { * {@link ImsRegistrationImplBase.ImsRegistrationTech} combination specified, this method will * always return {@code true}. * - * @see CarrierConfigManager#KEY_CARRIER_UT_PROVISIONING_REQUIRED_BOOL - * @see CarrierConfigManager#KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL + * <p> Requires Permission: + * <ul> + * <li>android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE,</li> + * <li>{@link android.Manifest.permission#READ_PRECISE_PHONE_STATE},</li> + * <li>or that the caller has carrier privileges (see + * {@link TelephonyManager#hasCarrierPrivileges()}).</li> + * </ul> + * + * @see CarrierConfigManager.Ims#KEY_MMTEL_REQUIRES_PROVISIONING_BUNDLE * @return true if the device is provisioned for the capability or does not require * provisioning, false if the capability does require provisioning and has not been * provisioned yet. */ @WorkerThread - @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + @RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE) public boolean getProvisioningStatusForCapability( @MmTelFeature.MmTelCapabilities.MmTelCapability int capability, @ImsRegistrationImplBase.ImsRegistrationTech int tech) { @@ -1299,17 +1508,55 @@ public class ProvisioningManager { * {@link RcsFeature.RcsImsCapabilities.RcsImsCapabilityFlag} this method will always return * {@code true}. * - * @see CarrierConfigManager#KEY_CARRIER_RCS_PROVISIONING_REQUIRED_BOOL + * @see CarrierConfigManager.Ims#KEY_CARRIER_RCS_PROVISIONING_REQUIRED_BOOL * @return true if the device is provisioned for the capability or does not require * provisioning, false if the capability does require provisioning and has not been * provisioned yet. + * @deprecated Use {@link #getRcsProvisioningStatusForCapability(int, int)} instead, + * as this only retrieves provisioning information for + * {@link ImsRegistrationImplBase#REGISTRATION_TECH_LTE} + * @hide */ + @Deprecated + @SystemApi @WorkerThread @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean getRcsProvisioningStatusForCapability( @RcsFeature.RcsImsCapabilities.RcsImsCapabilityFlag int capability) { try { - return getITelephony().getRcsProvisioningStatusForCapability(mSubId, capability); + return getITelephony().getRcsProvisioningStatusForCapability(mSubId, capability, + ImsRegistrationImplBase.REGISTRATION_TECH_LTE); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + /** + * Get the provisioning status for the IMS RCS capability specified. + * + * If provisioning is not required for the queried + * {@link RcsFeature.RcsImsCapabilities.RcsImsCapabilityFlag} this method + * will always return {@code true}. + * + * <p> Requires Permission: + * <ul> + * <li>{@link android.Manifest.permission#READ_PRECISE_PHONE_STATE},</li> + * <li>or that the caller has carrier privileges (see + * {@link TelephonyManager#hasCarrierPrivileges()}).</li> + * </ul> + * + * @see CarrierConfigManager.Ims#KEY_RCS_REQUIRES_PROVISIONING_BUNDLE + * @return true if the device is provisioned for the capability or does not require + * provisioning, false if the capability does require provisioning and has not been + * provisioned yet. + */ + @WorkerThread + @RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE) + public boolean getRcsProvisioningStatusForCapability( + @RcsFeature.RcsImsCapabilities.RcsImsCapabilityFlag int capability, + @ImsRegistrationImplBase.ImsRegistrationTech int tech) { + try { + return getITelephony().getRcsProvisioningStatusForCapability(mSubId, capability, tech); } catch (RemoteException e) { throw e.rethrowAsRuntimeException(); } @@ -1318,6 +1565,13 @@ public class ProvisioningManager { /** * Set the provisioning status for the IMS RCS capability using the specified subscription. * + * <p> Requires Permission: + * <ul> + * <li>{@link android.Manifest.permission#MODIFY_PHONE_STATE}</li> + * <li>or that the caller has carrier privileges (see + * {@link TelephonyManager#hasCarrierPrivileges()}).</li> + * </ul> + * Provisioning may or may not be required, depending on the carrier configuration. If * provisioning is not required for the carrier associated with this subscription or the device * does not support the capability/technology combination specified, this operation will be a @@ -1326,7 +1580,13 @@ public class ProvisioningManager { * @see CarrierConfigManager#KEY_CARRIER_RCS_PROVISIONING_REQUIRED_BOOL * @param isProvisioned true if the device is provisioned for the RCS capability specified, * false otherwise. + * @deprecated Use {@link #setRcsProvisioningStatusForCapability(int, int, boolean)} instead, + * as this method only sets provisioning information for + * {@link ImsRegistrationImplBase#REGISTRATION_TECH_LTE} + * @hide */ + @Deprecated + @SystemApi @WorkerThread @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE) public void setRcsProvisioningStatusForCapability( @@ -1334,28 +1594,121 @@ public class ProvisioningManager { boolean isProvisioned) { try { getITelephony().setRcsProvisioningStatusForCapability(mSubId, capability, - isProvisioned); + ImsRegistrationImplBase.REGISTRATION_TECH_LTE, isProvisioned); } catch (RemoteException e) { throw e.rethrowAsRuntimeException(); } } /** + * Set the provisioning status for the IMS RCS capability using the specified subscription. + * + * Provisioning may or may not be required, depending on the carrier configuration. If + * provisioning is not required for the carrier associated with this subscription or the device + * does not support the capability/technology combination specified, this operation will be a + * no-op. + * + * <p> Requires Permission: + * <ul> + * <li>{@link android.Manifest.permission#MODIFY_PHONE_STATE},</li> + * <li>or that the caller has carrier privileges (see + * {@link TelephonyManager#hasCarrierPrivileges()}).</li> + * </ul> + * + * @see CarrierConfigManager.Ims#KEY_RCS_REQUIRES_PROVISIONING_BUNDLE + * @param isProvisioned true if the device is provisioned for the RCS capability specified, + * false otherwise. + */ + @WorkerThread + @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE) + public void setRcsProvisioningStatusForCapability( + @RcsFeature.RcsImsCapabilities.RcsImsCapabilityFlag int capability, + @ImsRegistrationImplBase.ImsRegistrationTech int tech, boolean isProvisioned) { + try { + getITelephony().setRcsProvisioningStatusForCapability(mSubId, capability, + tech, isProvisioned); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + /** + * Indicates whether provisioning for the MMTEL capability and IMS registration technology + * specified is required or not + * + * <p> Requires Permission: + * <ul> + * <li> android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE,</li> + * <li>{@link android.Manifest.permission#READ_PRECISE_PHONE_STATE},</li> + * <li> or that the caller has carrier privileges (see + * {@link TelephonyManager#hasCarrierPrivileges()}).</li> + * </ul> + * + * @return true if provisioning is required for the MMTEL capability and IMS + * registration technology specified, false if it is not required. + */ + @RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE) + public boolean isProvisioningRequiredForCapability( + @MmTelFeature.MmTelCapabilities.MmTelCapability int capability, + @ImsRegistrationImplBase.ImsRegistrationTech int tech) { + try { + return getITelephony().isProvisioningRequiredForCapability(mSubId, capability, tech); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + + /** + * Indicates whether provisioning for the RCS capability and IMS registration technology + * specified is required or not + * + * <p> Requires Permission: + * <ul> + * <li> android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE,</li> + * <li>{@link android.Manifest.permission#READ_PRECISE_PHONE_STATE},</li> + * <li> or that the caller has carrier privileges (see + * {@link TelephonyManager#hasCarrierPrivileges()}).</li> + * </ul> + * + * @return true if provisioning is required for the RCS capability and IMS + * registration technology specified, false if it is not required. + */ + @RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE) + public boolean isRcsProvisioningRequiredForCapability( + @RcsFeature.RcsImsCapabilities.RcsImsCapabilityFlag int capability, + @ImsRegistrationImplBase.ImsRegistrationTech int tech) { + try { + return getITelephony().isRcsProvisioningRequiredForCapability(mSubId, capability, tech); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + + /** * Notify the framework that an RCS autoconfiguration XML file has been received for * provisioning. - * <p> - * Requires Permission: Manifest.permission.MODIFY_PHONE_STATE or that the calling app has - * carrier privileges (see {@link TelephonyManager#hasCarrierPrivileges}). + * + * <p>Requires Permission: + * <ul> + * <li>{@link Manifest.permission#MODIFY_PHONE_STATE},</li> + * <li>or that the calling app has carrier privileges (see + * {@link TelephonyManager#hasCarrierPrivileges()}).</li> + * </ul> + * * @param config The XML file to be read. ASCII/UTF8 encoded text if not compressed. * @param isCompressed The XML file is compressed in gzip format and must be decompressed * before being read. - * + * @hide */ + @SystemApi @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE) public void notifyRcsAutoConfigurationReceived(@NonNull byte[] config, boolean isCompressed) { if (config == null) { throw new IllegalArgumentException("Must include a non-null config XML file."); } + try { getITelephony().notifyRcsAutoConfigurationReceived(mSubId, config, isCompressed); } catch (RemoteException e) { @@ -1374,7 +1727,9 @@ public class ProvisioningManager { * <p>Contains {@link #EXTRA_SUBSCRIPTION_ID} to specify the subscription index for which * the intent is valid. and {@link #EXTRA_STATUS} to specify RCS VoLTE single registration * status. + * @hide */ + @SystemApi @RequiresPermission(Manifest.permission.PERFORM_IMS_SINGLE_REGISTRATION) @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_RCS_SINGLE_REGISTRATION_CAPABILITY_UPDATE = @@ -1382,7 +1737,9 @@ public class ProvisioningManager { /** * Integer extra to specify subscription index. + * @hide */ + @SystemApi public static final String EXTRA_SUBSCRIPTION_ID = "android.telephony.ims.extra.SUBSCRIPTION_ID"; @@ -1392,22 +1749,30 @@ public class ProvisioningManager { * <p>The value can be {@link #STATUS_CAPABLE}, {@link #STATUS_DEVICE_NOT_CAPABLE}, * {@link #STATUS_CARRIER_NOT_CAPABLE}, or bitwise OR of * {@link #STATUS_DEVICE_NOT_CAPABLE} and {@link #STATUS_CARRIER_NOT_CAPABLE}. + * @hide */ + @SystemApi public static final String EXTRA_STATUS = "android.telephony.ims.extra.STATUS"; /** * RCS VoLTE single registration is supported by the device and carrier. + * @hide */ + @SystemApi public static final int STATUS_CAPABLE = 0; /** * RCS VoLTE single registration is not supported by the device. + * @hide */ + @SystemApi public static final int STATUS_DEVICE_NOT_CAPABLE = 0x01; /** * RCS VoLTE single registration is not supported by the carrier + * @hide */ + @SystemApi public static final int STATUS_CARRIER_NOT_CAPABLE = 0x01 << 1; /** @@ -1417,11 +1782,14 @@ public class ProvisioningManager { * provisioning is done using autoconfiguration, then these parameters shall be * sent in the HTTP get request to fetch the RCS provisioning. RCS client * configuration must be provided by the application before registering for the - * provisioning status events {@link #registerRcsProvisioningCallback()} + * provisioning status events + * {@link #registerRcsProvisioningCallback(Executor, RcsProvisioningCallback)} * When the IMS/RCS service receives the RCS client configuration, it will detect * the change in the configuration, and trigger the auto-configuration as needed. * @param rcc RCS client configuration {@link RcsClientConfiguration} + * @hide */ + @SystemApi @RequiresPermission(Manifest.permission.PERFORM_IMS_SINGLE_REGISTRATION) public void setRcsClientConfiguration( @NonNull RcsClientConfiguration rcc) throws ImsException { @@ -1442,18 +1810,21 @@ public class ProvisioningManager { * <ul> * <li>{@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE},</li> * <li>{@link android.Manifest.permission#PERFORM_IMS_SINGLE_REGISTRATION},</li> - * <li>or that the caller has carrier privileges (see + * <li>or that the calling app has carrier privileges (see * {@link TelephonyManager#hasCarrierPrivileges()}).</li> * </ul> + * * @return true if IMS single registration is capable at this time, or false otherwise - * @throws ImsException If the remote ImsService is not available for - * any reason or the subscription associated with this instance is no - * longer active. See {@link ImsException#getCode()} for more - * information. + * @throws ImsException If the remote ImsService is not available for any reason or + * the subscription associated with this instance is no longer active. + * See {@link ImsException#getCode()} for more information. * @see PackageManager#FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION for whether or not this * device supports IMS single registration. + * @hide */ - @RequiresPermission(anyOf = {Manifest.permission.READ_PRIVILEGED_PHONE_STATE, + @SystemApi + @RequiresPermission(anyOf = { + Manifest.permission.READ_PRIVILEGED_PHONE_STATE, Manifest.permission.PERFORM_IMS_SINGLE_REGISTRATION}) public boolean isRcsVolteSingleRegistrationCapable() throws ImsException { try { @@ -1480,8 +1851,6 @@ public class ProvisioningManager { * <ul> * <li>{@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE},</li> * <li>{@link android.Manifest.permission#PERFORM_IMS_SINGLE_REGISTRATION},</li> - * <li>or that the caller has carrier privileges (see - * {@link TelephonyManager#hasCarrierPrivileges()}).</li> * </ul> * * @param executor The {@link Executor} to call the callback methods on @@ -1499,8 +1868,11 @@ public class ProvisioningManager { * params (See {@link #setRcsClientConfiguration}) and re register the * callback. * See {@link ImsException#getCode()} for a more detailed reason. + * @hide */ - @RequiresPermission(anyOf = {Manifest.permission.READ_PRIVILEGED_PHONE_STATE, + @SystemApi + @RequiresPermission(anyOf = { + Manifest.permission.READ_PRIVILEGED_PHONE_STATE, Manifest.permission.PERFORM_IMS_SINGLE_REGISTRATION}) public void registerRcsProvisioningCallback( @NonNull @CallbackExecutor Executor executor, @@ -1527,8 +1899,6 @@ public class ProvisioningManager { * <ul> * <li>{@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE},</li> * <li>{@link android.Manifest.permission#PERFORM_IMS_SINGLE_REGISTRATION},</li> - * <li>or that the caller has carrier privileges (see - * {@link TelephonyManager#hasCarrierPrivileges()}).</li> * </ul> * * @param callback The existing {@link RcsProvisioningCallback} to be @@ -1536,8 +1906,11 @@ public class ProvisioningManager { * @see #registerRcsProvisioningCallback(Executor, RcsProvisioningCallback) * @throws IllegalArgumentException if the subscription associated with * this callback is invalid. + * @hide */ - @RequiresPermission(anyOf = {Manifest.permission.READ_PRIVILEGED_PHONE_STATE, + @SystemApi + @RequiresPermission(anyOf = { + Manifest.permission.READ_PRIVILEGED_PHONE_STATE, Manifest.permission.PERFORM_IMS_SINGLE_REGISTRATION}) public void unregisterRcsProvisioningCallback( @NonNull RcsProvisioningCallback callback) { @@ -1558,9 +1931,10 @@ public class ProvisioningManager { * {@link RcsProvisioningCallback} may expect to receive * {@link RcsProvisioningCallback#onConfigurationReset}, then * {@link RcsProvisioningCallback#onConfigurationChanged} when the new - * RCS configuration is received and notified by - * {@link #notifyRcsAutoConfigurationReceived} + * RCS configuration is received and notified by {@link #notifyRcsAutoConfigurationReceived} + * @hide */ + @SystemApi @RequiresPermission(Manifest.permission.PERFORM_IMS_SINGLE_REGISTRATION) public void triggerRcsReconfiguration() { try { diff --git a/telephony/java/android/telephony/ims/RcsContactPresenceTuple.java b/telephony/java/android/telephony/ims/RcsContactPresenceTuple.java index 9c28c36521f5..6a6c3063483e 100644 --- a/telephony/java/android/telephony/ims/RcsContactPresenceTuple.java +++ b/telephony/java/android/telephony/ims/RcsContactPresenceTuple.java @@ -439,13 +439,13 @@ public final class RcsContactPresenceTuple implements Parcelable { } private RcsContactPresenceTuple(Parcel in) { - mContactUri = in.readParcelable(Uri.class.getClassLoader()); + mContactUri = in.readParcelable(Uri.class.getClassLoader(), android.net.Uri.class); mTimestamp = convertStringFormatTimeToInstant(in.readString()); mStatus = in.readString(); mServiceId = in.readString(); mServiceVersion = in.readString(); mServiceDescription = in.readString(); - mServiceCapabilities = in.readParcelable(ServiceCapabilities.class.getClassLoader()); + mServiceCapabilities = in.readParcelable(ServiceCapabilities.class.getClassLoader(), android.telephony.ims.RcsContactPresenceTuple.ServiceCapabilities.class); } @Override diff --git a/telephony/java/android/telephony/ims/RcsContactTerminatedReason.java b/telephony/java/android/telephony/ims/RcsContactTerminatedReason.java index ee02564267c0..ea022de3bc01 100644 --- a/telephony/java/android/telephony/ims/RcsContactTerminatedReason.java +++ b/telephony/java/android/telephony/ims/RcsContactTerminatedReason.java @@ -37,7 +37,7 @@ public final class RcsContactTerminatedReason implements Parcelable { } private RcsContactTerminatedReason(Parcel in) { - mContactUri = in.readParcelable(Uri.class.getClassLoader()); + mContactUri = in.readParcelable(Uri.class.getClassLoader(), android.net.Uri.class); mReason = in.readString(); } diff --git a/telephony/java/android/telephony/ims/RcsContactUceCapability.java b/telephony/java/android/telephony/ims/RcsContactUceCapability.java index 91121187a19a..0f1b3695270b 100644 --- a/telephony/java/android/telephony/ims/RcsContactUceCapability.java +++ b/telephony/java/android/telephony/ims/RcsContactUceCapability.java @@ -244,14 +244,14 @@ public final class RcsContactUceCapability implements Parcelable { } private RcsContactUceCapability(Parcel in) { - mContactUri = in.readParcelable(Uri.class.getClassLoader()); + mContactUri = in.readParcelable(Uri.class.getClassLoader(), android.net.Uri.class); mCapabilityMechanism = in.readInt(); mSourceType = in.readInt(); mRequestResult = in.readInt(); List<String> featureTagList = new ArrayList<>(); in.readStringList(featureTagList); mFeatureTags.addAll(featureTagList); - in.readParcelableList(mPresenceTuples, RcsContactPresenceTuple.class.getClassLoader()); + in.readParcelableList(mPresenceTuples, RcsContactPresenceTuple.class.getClassLoader(), android.telephony.ims.RcsContactPresenceTuple.class); } @Override diff --git a/telephony/java/android/telephony/ims/RtpHeaderExtensionType.java b/telephony/java/android/telephony/ims/RtpHeaderExtensionType.java index af4e23476331..b9ffd247f658 100644 --- a/telephony/java/android/telephony/ims/RtpHeaderExtensionType.java +++ b/telephony/java/android/telephony/ims/RtpHeaderExtensionType.java @@ -63,7 +63,7 @@ public final class RtpHeaderExtensionType implements Parcelable { private RtpHeaderExtensionType(Parcel in) { mLocalIdentifier = in.readInt(); - mUri = in.readParcelable(Uri.class.getClassLoader()); + mUri = in.readParcelable(Uri.class.getClassLoader(), android.net.Uri.class); } public static final @NonNull Creator<RtpHeaderExtensionType> CREATOR = diff --git a/telephony/java/android/telephony/ims/SipDelegateConfiguration.java b/telephony/java/android/telephony/ims/SipDelegateConfiguration.java index 1bf5cad49c53..db0ae033713e 100644 --- a/telephony/java/android/telephony/ims/SipDelegateConfiguration.java +++ b/telephony/java/android/telephony/ims/SipDelegateConfiguration.java @@ -573,7 +573,7 @@ public final class SipDelegateConfiguration implements Parcelable { mPrivateUserIdentifier = source.readString(); mHomeDomain = source.readString(); mImei = source.readString(); - mGruu = source.readParcelable(null); + mGruu = source.readParcelable(null, android.net.Uri.class); mSipAuthHeader = source.readString(); mSipAuthNonce = source.readString(); mServiceRouteHeader = source.readString(); diff --git a/telephony/java/android/telephony/ims/aidl/IFeatureProvisioningCallback.aidl b/telephony/java/android/telephony/ims/aidl/IFeatureProvisioningCallback.aidl new file mode 100644 index 000000000000..63ec4aaf1f48 --- /dev/null +++ b/telephony/java/android/telephony/ims/aidl/IFeatureProvisioningCallback.aidl @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package android.telephony.ims.aidl; + +/** + * Provides callback interface for FeatureProvisioning when a value has changed. + * + * {@hide} + */ +oneway interface IFeatureProvisioningCallback { + void onFeatureProvisioningChanged(int capability, int tech, boolean isProvisioned); + void onRcsFeatureProvisioningChanged(int capability, int tech, boolean isProvisioned); +} diff --git a/telephony/java/android/telephony/ims/feature/MmTelFeature.java b/telephony/java/android/telephony/ims/feature/MmTelFeature.java index 7fdf21b3e5ff..ad2e9e133a11 100644 --- a/telephony/java/android/telephony/ims/feature/MmTelFeature.java +++ b/telephony/java/android/telephony/ims/feature/MmTelFeature.java @@ -394,6 +394,13 @@ public class MmTelFeature extends ImsFeature { public @interface MmTelCapability {} /** + * Undefined capability type for initialization + * This is used to check the upper range of MmTel capability + * {@hide} + */ + public static final int CAPABILITY_TYPE_NONE = 0; + + /** * This MmTelFeature supports Voice calling (IR.92) */ public static final int CAPABILITY_TYPE_VOICE = 1 << 0; @@ -419,6 +426,12 @@ public class MmTelFeature extends ImsFeature { public static final int CAPABILITY_TYPE_CALL_COMPOSER = 1 << 4; /** + * This is used to check the upper range of MmTel capability + * {@hide} + */ + public static final int CAPABILITY_TYPE_MAX = CAPABILITY_TYPE_CALL_COMPOSER + 1; + + /** * @hide */ @Override diff --git a/telephony/java/android/telephony/ims/feature/RcsFeature.java b/telephony/java/android/telephony/ims/feature/RcsFeature.java index 11cf0e3f7855..70e4ef1f1a3a 100644 --- a/telephony/java/android/telephony/ims/feature/RcsFeature.java +++ b/telephony/java/android/telephony/ims/feature/RcsFeature.java @@ -59,9 +59,7 @@ import java.util.function.Supplier; /** * Base implementation of the RcsFeature APIs. Any ImsService wishing to support RCS should extend * this class and provide implementations of the RcsFeature methods that they support. - * @hide */ -@SystemApi public class RcsFeature extends ImsFeature { private static final String LOG_TAG = "RcsFeature"; @@ -186,14 +184,14 @@ public class RcsFeature extends ImsFeature { * Contains the capabilities defined and supported by a {@link RcsFeature} in the * form of a bitmask. The capabilities that are used in the RcsFeature are * defined as: - * {@link RcsUceAdatper.RcsImsCapabilityFlag#CAPABILITY_TYPE_OPTIONS_UCE} - * {@link RceUceAdapter.RcsImsCapabilityFlag#CAPABILITY_TYPE_PRESENCE_UCE} + * {RcsUceAdapter.RcsImsCapabilityFlag#CAPABILITY_TYPE_OPTIONS_UCE} + * {RcsUceAdapter.RcsImsCapabilityFlag#CAPABILITY_TYPE_PRESENCE_UCE} * * The enabled capabilities of this RcsFeature will be set by the framework - * using {@link #changeEnabledCapabilities(CapabilityChangeRequest, CapabilityCallbackProxy)}. + * using {#changeEnabledCapabilities(CapabilityChangeRequest, CapabilityCallbackProxy)}. * After the capabilities have been set, the RcsFeature may then perform the necessary bring up * of the capability and notify the capability status as true using - * {@link #notifyCapabilitiesStatusChanged(RcsImsCapabilities)}. This will signal to the + * {#notifyCapabilitiesStatusChanged(RcsImsCapabilities)}. This will signal to the * framework that the capability is available for usage. */ public static class RcsImsCapabilities extends Capabilities { @@ -227,10 +225,18 @@ public class RcsFeature extends ImsFeature { public static final int CAPABILITY_TYPE_PRESENCE_UCE = 1 << 1; /** + * This is used to check the upper range of RCS capability + * {@hide} + */ + public static final int CAPABILITY_TYPE_MAX = CAPABILITY_TYPE_PRESENCE_UCE + 1; + + /** * Create a new {@link RcsImsCapabilities} instance with the provided capabilities. * @param capabilities The capabilities that are supported for RCS in the form of a * bitfield. + * @hide */ + @SystemApi public RcsImsCapabilities(@RcsUceAdapter.RcsImsCapabilityFlag int capabilities) { super(capabilities); } @@ -243,17 +249,29 @@ public class RcsFeature extends ImsFeature { super(capabilities.getMask()); } + /** + * @hide + */ @Override + @SystemApi public void addCapabilities(@RcsUceAdapter.RcsImsCapabilityFlag int capabilities) { super.addCapabilities(capabilities); } + /** + * @hide + */ @Override + @SystemApi public void removeCapabilities(@RcsUceAdapter.RcsImsCapabilityFlag int capabilities) { super.removeCapabilities(capabilities); } + /** + * @hide + */ @Override + @SystemApi public boolean isCapable(@RcsUceAdapter.RcsImsCapabilityFlag int capabilities) { return super.isCapable(capabilities); } @@ -270,7 +288,9 @@ public class RcsFeature extends ImsFeature { * Method stubs called from the framework will be called asynchronously. To specify the * {@link Executor} that the methods stubs will be called, use * {@link RcsFeature#RcsFeature(Executor)} instead. + * @hide */ + @SystemApi public RcsFeature() { super(); // Run on the Binder threads that call them. @@ -282,7 +302,9 @@ public class RcsFeature extends ImsFeature { * framework. * @param executor The executor for the framework to use when executing the methods overridden * by the implementation of RcsFeature. + * @hide */ + @SystemApi public RcsFeature(@NonNull Executor executor) { super(); if (executor == null) { @@ -301,7 +323,7 @@ public class RcsFeature extends ImsFeature { * @hide */ @Override - public void initialize(Context context, int slotId) { + public void initialize(@NonNull Context context, @NonNull int slotId) { super.initialize(context, slotId); // Notify that the RcsFeature is ready. mExecutor.execute(() -> onFeatureReady()); @@ -313,8 +335,10 @@ public class RcsFeature extends ImsFeature { * requests. To change the status of the capabilities * {@link #notifyCapabilitiesStatusChanged(RcsImsCapabilities)} should be called. * @return A copy of the current RcsFeature capability status. + * @hide */ @Override + @SystemApi public @NonNull final RcsImsCapabilities queryCapabilityStatus() { return new RcsImsCapabilities(super.queryCapabilityStatus()); } @@ -324,7 +348,9 @@ public class RcsFeature extends ImsFeature { * this signals to the framework that the capability has been initialized and is ready. * Call {@link #queryCapabilityStatus()} to return the current capability status. * @param capabilities The current capability status of the RcsFeature. + * @hide */ + @SystemApi public final void notifyCapabilitiesStatusChanged(@NonNull RcsImsCapabilities capabilities) { if (capabilities == null) { throw new IllegalArgumentException("RcsImsCapabilities must be non-null!"); @@ -341,7 +367,9 @@ public class RcsFeature extends ImsFeature { * @param capability The capability that we are querying the configuration for. * @param radioTech The radio technology type that we are querying. * @return true if the capability is enabled, false otherwise. + * @hide */ + @SystemApi public boolean queryCapabilityConfiguration( @RcsUceAdapter.RcsImsCapabilityFlag int capability, @ImsRegistrationImplBase.ImsRegistrationTech int radioTech) { @@ -364,8 +392,10 @@ public class RcsFeature extends ImsFeature { * be called for each capability change that resulted in an error. * @param request The request to change the capability. * @param callback To notify the framework that the result of the capability changes. + * @hide */ @Override + @SystemApi public void changeEnabledCapabilities(@NonNull CapabilityChangeRequest request, @NonNull CapabilityCallbackProxy callback) { // Base Implementation - Override to provide functionality @@ -385,7 +415,9 @@ public class RcsFeature extends ImsFeature { * event to the framework. * @return An instance of {@link RcsCapabilityExchangeImplBase} that implements capability * exchange if it is supported by the device. + * @hide */ + @SystemApi public @NonNull RcsCapabilityExchangeImplBase createCapabilityExchangeImpl( @NonNull CapabilityExchangeEventListener listener) { // Base Implementation, override to implement functionality @@ -395,20 +427,28 @@ public class RcsFeature extends ImsFeature { /** * Remove the given CapabilityExchangeImplBase instance. * @param capExchangeImpl The {@link RcsCapabilityExchangeImplBase} instance to be destroyed. + * @hide */ + @SystemApi public void destroyCapabilityExchangeImpl( @NonNull RcsCapabilityExchangeImplBase capExchangeImpl) { // Override to implement the process of destroying RcsCapabilityExchangeImplBase instance. } - /**{@inheritDoc}*/ + /**{@inheritDoc} + * @hide + */ @Override + @SystemApi public void onFeatureRemoved() { } - /**{@inheritDoc}*/ + /**{@inheritDoc} + * @hide + */ @Override + @SystemApi public void onFeatureReady() { } diff --git a/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java b/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java index 3b151a422b57..ac5565bea810 100644 --- a/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java +++ b/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java @@ -34,7 +34,6 @@ import com.android.internal.telephony.util.RemoteCallbackListExt; import com.android.internal.telephony.util.TelephonyUtils; import com.android.internal.util.ArrayUtils; - import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.concurrent.CancellationException; @@ -51,9 +50,7 @@ import java.util.function.Supplier; * <p> * Note: There is no guarantee on the thread that the calls from the framework will be called on. It * is the implementors responsibility to handle moving the calls to a working thread if required. - * @hide */ -@SystemApi public class ImsRegistrationImplBase { private static final String LOG_TAG = "ImsRegistrationImplBase"; @@ -94,6 +91,12 @@ public class ImsRegistrationImplBase { */ public static final int REGISTRATION_TECH_NR = 3; + /** + * This is used to check the upper range of registration tech + * {@hide} + */ + public static final int REGISTRATION_TECH_MAX = REGISTRATION_TECH_NR + 1; + // Registration states, used to notify new ImsRegistrationImplBase#Callbacks of the current // state. // The unknown state is set as the initialization state. This is so that we do not call back @@ -109,7 +112,9 @@ public class ImsRegistrationImplBase { * Method stubs called from the framework will be called asynchronously. To specify the * {@link Executor} that the methods stubs will be called, use * {@link ImsRegistrationImplBase#ImsRegistrationImplBase(Executor)} instead. + * @hide This API is not part of the Android public SDK API */ + @SystemApi public ImsRegistrationImplBase() { super(); } @@ -119,7 +124,9 @@ public class ImsRegistrationImplBase { * framework. * @param executor The executor for the framework to use when executing the methods overridden * by the implementation of ImsRegistration. + * @hide This API is not part of the Android public SDK API */ + @SystemApi public ImsRegistrationImplBase(@NonNull Executor executor) { super(); mExecutor = executor; @@ -250,7 +257,9 @@ public class ImsRegistrationImplBase { * If the SIP delegate feature tag configuration has changed, then this method will be * called in order to let the ImsService know that it can pick up these changes in the IMS * registration. + * @hide This API is not part of the Android public SDK API */ + @SystemApi public void updateSipDelegateRegistration() { // Stub implementation, ImsService should implement this } @@ -266,7 +275,9 @@ public class ImsRegistrationImplBase { * <p> * This should not affect the registration of features managed by the ImsService itself, such as * feature tags related to MMTEL registration. + * @hide This API is not part of the Android public SDK API */ + @SystemApi public void triggerSipDelegateDeregistration() { // Stub implementation, ImsService should implement this } @@ -284,7 +295,9 @@ public class ImsRegistrationImplBase { * be carrier specific. * @param sipReason The reason associated with the SIP error code. {@code null} if there was no * reason associated with the error. + * @hide This API is not part of the Android public SDK API */ + @SystemApi public void triggerFullNetworkRegistration(@IntRange(from = 100, to = 699) int sipCode, @Nullable String sipReason) { // Stub implementation, ImsService should implement this @@ -295,7 +308,9 @@ public class ImsRegistrationImplBase { * Notify the framework that the device is connected to the IMS network. * * @param imsRadioTech the radio access technology. + * @hide This API is not part of the Android public SDK API */ + @SystemApi public final void onRegistered(@ImsRegistrationTech int imsRadioTech) { onRegistered(new ImsRegistrationAttributes.Builder(imsRadioTech).build()); } @@ -304,7 +319,9 @@ public class ImsRegistrationImplBase { * Notify the framework that the device is connected to the IMS network. * * @param attributes The attributes associated with the IMS registration. + * @hide This API is not part of the Android public SDK API */ + @SystemApi public final void onRegistered(@NonNull ImsRegistrationAttributes attributes) { updateToState(attributes, RegistrationManager.REGISTRATION_STATE_REGISTERED); mCallbacks.broadcastAction((c) -> { @@ -320,7 +337,9 @@ public class ImsRegistrationImplBase { * Notify the framework that the device is trying to connect the IMS network. * * @param imsRadioTech the radio access technology. + * @hide This API is not part of the Android public SDK API */ + @SystemApi public final void onRegistering(@ImsRegistrationTech int imsRadioTech) { onRegistering(new ImsRegistrationAttributes.Builder(imsRadioTech).build()); } @@ -329,7 +348,9 @@ public class ImsRegistrationImplBase { * Notify the framework that the device is trying to connect the IMS network. * * @param attributes The attributes associated with the IMS registration. + * @hide This API is not part of the Android public SDK API */ + @SystemApi public final void onRegistering(@NonNull ImsRegistrationAttributes attributes) { updateToState(attributes, RegistrationManager.REGISTRATION_STATE_REGISTERING); mCallbacks.broadcastAction((c) -> { @@ -356,7 +377,9 @@ public class ImsRegistrationImplBase { * result. * * @param info the {@link ImsReasonInfo} associated with why registration was disconnected. + * @hide This API is not part of the Android public SDK API */ + @SystemApi public final void onDeregistered(ImsReasonInfo info) { updateToDisconnectedState(info); // ImsReasonInfo should never be null. @@ -377,7 +400,9 @@ public class ImsRegistrationImplBase { * {@link #REGISTRATION_TECH_LTE}, {@link #REGISTRATION_TECH_IWLAN} and * {@link #REGISTRATION_TECH_CROSS_SIM}. * @param info The {@link ImsReasonInfo} for the failure to change technology. + * @hide This API is not part of the Android public SDK API */ + @SystemApi public final void onTechnologyChangeFailed(@ImsRegistrationTech int imsRadioTech, ImsReasonInfo info) { final ImsReasonInfo reasonInfo = (info != null) ? info : new ImsReasonInfo(); @@ -396,7 +421,9 @@ public class ImsRegistrationImplBase { * * The {@link Uri}s are not guaranteed to be different between subsequent calls. * @param uris changed uris + * @hide This API is not part of the Android public SDK API */ + @SystemApi public final void onSubscriberAssociatedUriChanged(Uri[] uris) { synchronized (mLock) { mUris = ArrayUtils.cloneOrNull(uris); diff --git a/telephony/java/android/telephony/mbms/DownloadRequest.java b/telephony/java/android/telephony/mbms/DownloadRequest.java index eb59f87a6c02..81d5be8562b6 100644 --- a/telephony/java/android/telephony/mbms/DownloadRequest.java +++ b/telephony/java/android/telephony/mbms/DownloadRequest.java @@ -242,8 +242,8 @@ public final class DownloadRequest implements Parcelable { private DownloadRequest(Parcel in) { fileServiceId = in.readString(); - sourceUri = in.readParcelable(getClass().getClassLoader()); - destinationUri = in.readParcelable(getClass().getClassLoader()); + sourceUri = in.readParcelable(getClass().getClassLoader(), android.net.Uri.class); + destinationUri = in.readParcelable(getClass().getClassLoader(), android.net.Uri.class); subscriptionId = in.readInt(); serializedResultIntentForApp = in.readString(); version = in.readInt(); diff --git a/telephony/java/android/telephony/mbms/FileInfo.java b/telephony/java/android/telephony/mbms/FileInfo.java index e52b2ce0c505..ffd864ebda93 100644 --- a/telephony/java/android/telephony/mbms/FileInfo.java +++ b/telephony/java/android/telephony/mbms/FileInfo.java @@ -55,7 +55,7 @@ public final class FileInfo implements Parcelable { } private FileInfo(Parcel in) { - uri = in.readParcelable(null); + uri = in.readParcelable(null, android.net.Uri.class); mimeType = in.readString(); } diff --git a/telephony/java/android/telephony/mbms/FileServiceInfo.java b/telephony/java/android/telephony/mbms/FileServiceInfo.java index 8777e7f59e3f..0fc3be6de929 100644 --- a/telephony/java/android/telephony/mbms/FileServiceInfo.java +++ b/telephony/java/android/telephony/mbms/FileServiceInfo.java @@ -58,7 +58,7 @@ public final class FileServiceInfo extends ServiceInfo implements Parcelable { FileServiceInfo(Parcel in) { super(in); files = new ArrayList<FileInfo>(); - in.readList(files, FileInfo.class.getClassLoader()); + in.readList(files, FileInfo.class.getClassLoader(), android.telephony.mbms.FileInfo.class); } @Override diff --git a/telephony/java/android/telephony/mbms/ServiceInfo.java b/telephony/java/android/telephony/mbms/ServiceInfo.java index f78e7a6e54c4..02424ff75c82 100644 --- a/telephony/java/android/telephony/mbms/ServiceInfo.java +++ b/telephony/java/android/telephony/mbms/ServiceInfo.java @@ -80,7 +80,7 @@ public class ServiceInfo { } names = new HashMap(mapCount); while (mapCount-- > 0) { - Locale locale = (java.util.Locale) in.readSerializable(); + Locale locale = (java.util.Locale) in.readSerializable(java.util.Locale.class.getClassLoader(), java.util.Locale.class); String name = in.readString(); names.put(locale, name); } @@ -91,12 +91,12 @@ public class ServiceInfo { } locales = new ArrayList<Locale>(localesCount); while (localesCount-- > 0) { - Locale l = (java.util.Locale) in.readSerializable(); + Locale l = (java.util.Locale) in.readSerializable(java.util.Locale.class.getClassLoader(), java.util.Locale.class); locales.add(l); } serviceId = in.readString(); - sessionStartTime = (java.util.Date) in.readSerializable(); - sessionEndTime = (java.util.Date) in.readSerializable(); + sessionStartTime = (java.util.Date) in.readSerializable(java.util.Date.class.getClassLoader(), java.util.Date.class); + sessionEndTime = (java.util.Date) in.readSerializable(java.util.Date.class.getClassLoader(), java.util.Date.class); } /** @hide */ diff --git a/telephony/java/android/telephony/mbms/UriPathPair.java b/telephony/java/android/telephony/mbms/UriPathPair.java index 9258919919b7..54d9d9e5284e 100644 --- a/telephony/java/android/telephony/mbms/UriPathPair.java +++ b/telephony/java/android/telephony/mbms/UriPathPair.java @@ -48,8 +48,8 @@ public final class UriPathPair implements Parcelable { /** @hide */ private UriPathPair(Parcel in) { - mFilePathUri = in.readParcelable(Uri.class.getClassLoader()); - mContentUri = in.readParcelable(Uri.class.getClassLoader()); + mFilePathUri = in.readParcelable(Uri.class.getClassLoader(), android.net.Uri.class); + mContentUri = in.readParcelable(Uri.class.getClassLoader(), android.net.Uri.class); } public static final @android.annotation.NonNull Creator<UriPathPair> CREATOR = new Creator<UriPathPair>() { diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index be54cecbe3ba..bce7a246463d 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -55,6 +55,7 @@ import android.telephony.VisualVoicemailSmsFilterSettings; import android.telephony.emergency.EmergencyNumber; import android.telephony.ims.RcsClientConfiguration; import android.telephony.ims.RcsContactUceCapability; +import android.telephony.ims.aidl.IFeatureProvisioningCallback; import android.telephony.ims.aidl.IImsCapabilityCallback; import android.telephony.ims.aidl.IImsConfig; import android.telephony.ims.aidl.IImsConfigCallback; @@ -1992,6 +1993,18 @@ interface ITelephony { void unregisterImsProvisioningChangedCallback(int subId, IImsConfigCallback callback); /** + * Register an IMS provisioning change callback with Telephony. + */ + void registerFeatureProvisioningChangedCallback(int subId, + IFeatureProvisioningCallback callback); + + /** + * unregister an existing IMS provisioning change callback. + */ + void unregisterFeatureProvisioningChangedCallback(int subId, + IFeatureProvisioningCallback callback); + + /** * Set the provisioning status for the IMS MmTel capability using the specified subscription. */ void setImsProvisioningStatusForCapability(int subId, int capability, int tech, @@ -2005,19 +2018,12 @@ interface ITelephony { /** * Get the provisioning status for the IMS Rcs capability specified. */ - boolean getRcsProvisioningStatusForCapability(int subId, int capability); + boolean getRcsProvisioningStatusForCapability(int subId, int capability, int tech); /** * Set the provisioning status for the IMS Rcs capability using the specified subscription. */ - void setRcsProvisioningStatusForCapability(int subId, int capability, - boolean isProvisioned); - - /** Is the capability and tech flagged as provisioned in the cache */ - boolean isMmTelCapabilityProvisionedInCache(int subId, int capability, int tech); - - /** Set the provisioning for the capability and tech in the cache */ - void cacheMmTelCapabilityProvisioning(int subId, int capability, int tech, + void setRcsProvisioningStatusForCapability(int subId, int capability, int tech, boolean isProvisioned); /** @@ -2523,4 +2529,18 @@ interface ITelephony { * @return the service name of the modem service which bind to. */ String getModemService(); + + /** + * Is Provisioning required for capability + * @return true if provisioning is required for the MMTEL capability and IMS + * registration technology specified, false if it is not required. + */ + boolean isProvisioningRequiredForCapability(int subId, int capability, int tech); + + /** + * Is RCS Provisioning required for capability + * @return true if provisioning is required for the RCS capability and IMS + * registration technology specified, false if it is not required. + */ + boolean isRcsProvisioningRequiredForCapability(int subId, int capability, int tech); } diff --git a/telephony/java/com/android/internal/telephony/NetworkScanResult.java b/telephony/java/com/android/internal/telephony/NetworkScanResult.java index d07d77ca742a..8b49f4b4593c 100644 --- a/telephony/java/com/android/internal/telephony/NetworkScanResult.java +++ b/telephony/java/com/android/internal/telephony/NetworkScanResult.java @@ -83,7 +83,7 @@ public final class NetworkScanResult implements Parcelable { scanStatus = in.readInt(); scanError = in.readInt(); List<CellInfo> ni = new ArrayList<>(); - in.readParcelableList(ni, Object.class.getClassLoader()); + in.readParcelableList(ni, Object.class.getClassLoader(), android.telephony.CellInfo.class); networkInfos = ni; } diff --git a/telephony/java/com/android/internal/telephony/OperatorInfo.java b/telephony/java/com/android/internal/telephony/OperatorInfo.java index a6f0f667d0cd..1820a1dc4d6c 100644 --- a/telephony/java/com/android/internal/telephony/OperatorInfo.java +++ b/telephony/java/com/android/internal/telephony/OperatorInfo.java @@ -189,7 +189,7 @@ public class OperatorInfo implements Parcelable { in.readString(), /*operatorAlphaLong*/ in.readString(), /*operatorAlphaShort*/ in.readString(), /*operatorNumeric*/ - (State) in.readSerializable(), /*state*/ + (State) in.readSerializable(com.android.internal.telephony.OperatorInfo.State.class.getClassLoader(), com.android.internal.telephony.OperatorInfo.State.class), /*state*/ in.readInt()); /*ran*/ return opInfo; } diff --git a/telephony/java/com/android/internal/telephony/uicc/IccUtils.java b/telephony/java/com/android/internal/telephony/uicc/IccUtils.java index 21c3f760771c..f518d53fa517 100644 --- a/telephony/java/com/android/internal/telephony/uicc/IccUtils.java +++ b/telephony/java/com/android/internal/telephony/uicc/IccUtils.java @@ -24,6 +24,7 @@ import android.graphics.Bitmap; import android.graphics.Color; import android.os.Build; import android.telephony.UiccPortInfo; +import android.text.TextUtils; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telephony.GsmAlphabet; @@ -934,6 +935,13 @@ public class IccUtils { } /** + * Strip all the trailing 'F' characters of a string if exists and compare. + */ + public static boolean compareIgnoreTrailingFs(String a, String b) { + return TextUtils.equals(a, b) || TextUtils.equals(stripTrailingFs(a), stripTrailingFs(b)); + } + + /** * Converts a character of [0-9a-fA-F] to its hex value in a byte. If the character is not a * hex number, 0 will be returned. */ diff --git a/tests/DataIdleTest/src/com/android/tests/dataidle/DataIdleTest.java b/tests/DataIdleTest/src/com/android/tests/dataidle/DataIdleTest.java index 4ec86b186285..56848b89be6f 100644 --- a/tests/DataIdleTest/src/com/android/tests/dataidle/DataIdleTest.java +++ b/tests/DataIdleTest/src/com/android/tests/dataidle/DataIdleTest.java @@ -15,20 +15,19 @@ */ package com.android.tests.dataidle; +import static android.net.NetworkStats.METERED_YES; + +import android.app.usage.NetworkStats; +import android.app.usage.NetworkStatsManager; import android.content.Context; -import android.net.INetworkStatsService; -import android.net.INetworkStatsSession; -import android.net.NetworkStats; -import android.net.NetworkStats.Entry; import android.net.NetworkTemplate; -import android.net.TrafficStats; import android.os.Bundle; -import android.os.RemoteException; -import android.os.ServiceManager; import android.telephony.TelephonyManager; import android.test.InstrumentationTestCase; import android.util.Log; +import java.util.Set; + /** * A test that dumps data usage to instrumentation out, used for measuring data usage for idle * devices. @@ -36,7 +35,7 @@ import android.util.Log; public class DataIdleTest extends InstrumentationTestCase { private TelephonyManager mTelephonyManager; - private INetworkStatsService mStatsService; + private NetworkStatsManager mStatsManager; private static final String LOG_TAG = "DataIdleTest"; private final static int INSTRUMENTATION_IN_PROGRESS = 2; @@ -44,8 +43,7 @@ public class DataIdleTest extends InstrumentationTestCase { protected void setUp() throws Exception { super.setUp(); Context c = getInstrumentation().getTargetContext(); - mStatsService = INetworkStatsService.Stub.asInterface( - ServiceManager.getService(Context.NETWORK_STATS_SERVICE)); + mStatsManager = c.getSystemService(NetworkStatsManager.class); mTelephonyManager = (TelephonyManager) c.getSystemService(Context.TELEPHONY_SERVICE); } @@ -53,7 +51,9 @@ public class DataIdleTest extends InstrumentationTestCase { * Test that dumps all the data usage metrics for wifi to instrumentation out. */ public void testWifiIdle() { - NetworkTemplate template = NetworkTemplate.buildTemplateWifiWildcard(); + final NetworkTemplate template = new NetworkTemplate + .Builder(NetworkTemplate.MATCH_WIFI) + .build(); fetchStats(template); } @@ -61,8 +61,11 @@ public class DataIdleTest extends InstrumentationTestCase { * Test that dumps all the data usage metrics for all mobile to instrumentation out. */ public void testMobile() { - String subscriberId = mTelephonyManager.getSubscriberId(); - NetworkTemplate template = NetworkTemplate.buildTemplateMobileAll(subscriberId); + final String subscriberId = mTelephonyManager.getSubscriberId(); + NetworkTemplate template = new NetworkTemplate + .Builder(NetworkTemplate.MATCH_MOBILE) + .setMeteredness(METERED_YES) + .setSubscriberIds(Set.of(subscriberId)).build(); fetchStats(template); } @@ -72,49 +75,26 @@ public class DataIdleTest extends InstrumentationTestCase { * @param template {@link NetworkTemplate} to match. */ private void fetchStats(NetworkTemplate template) { - INetworkStatsSession session = null; try { - mStatsService.forceUpdate(); - session = mStatsService.openSession(); - final NetworkStats stats = session.getSummaryForAllUid( - template, Long.MIN_VALUE, Long.MAX_VALUE, false); - reportStats(stats); - } catch (RemoteException e) { + mStatsManager.forceUpdate(); + final NetworkStats.Bucket bucket = + mStatsManager.querySummaryForDevice(template, Long.MIN_VALUE, Long.MAX_VALUE); + reportStats(bucket); + } catch (RuntimeException e) { Log.w(LOG_TAG, "Failed to fetch network stats."); - } finally { - TrafficStats.closeQuietly(session); } } /** * Print network data usage stats to instrumentation out - * @param stats {@link NetworkorStats} to print + * @param bucket {@link NetworkStats} to print */ - void reportStats(NetworkStats stats) { + void reportStats(NetworkStats.Bucket bucket) { Bundle result = new Bundle(); - long rxBytes = 0; - long txBytes = 0; - long rxPackets = 0; - long txPackets = 0; - for (int i = 0; i < stats.size(); ++i) { - // Label will be iface_uid_tag_set - Entry statsEntry = stats.getValues(i, null); - // Debugging use. - /* - String labelTemplate = String.format("%s_%d_%d_%d", statsEntry.iface, statsEntry.uid, - statsEntry.tag, statsEntry.set) + "_%s"; - result.putLong(String.format(labelTemplate, "rxBytes"), statsEntry.rxBytes); - result.putLong(String.format(labelTemplate, "txBytes"), statsEntry.txBytes); - */ - rxPackets += statsEntry.rxPackets; - rxBytes += statsEntry.rxBytes; - txPackets += statsEntry.txPackets; - txBytes += statsEntry.txBytes; - } - result.putLong("Total rx Bytes", rxBytes); - result.putLong("Total tx Bytes", txBytes); - result.putLong("Total rx Packets", rxPackets); - result.putLong("Total tx Packets", txPackets); + result.putLong("Total rx Bytes", bucket.getRxBytes()); + result.putLong("Total tx Bytes", bucket.getTxBytes()); + result.putLong("Total rx Packets", bucket.getRxPackets()); + result.putLong("Total tx Packets", bucket.getTxPackets()); getInstrumentation().sendStatus(INSTRUMENTATION_IN_PROGRESS, result); } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt index 22acc03af6b6..d960e94bf09d 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt @@ -108,6 +108,15 @@ class CloseAppHomeButtonTest(testSpec: FlickerTestParameter) : CloseAppTransitio super.entireScreenCovered() } + /** {@inheritDoc} */ + @Presubmit + @Test + override fun visibleLayersShownMoreThanOneConsecutiveEntry() { + // This test doesn't work in shell transitions because of b/215885246 + assumeFalse(isShellTransitionsEnabled) + super.visibleLayersShownMoreThanOneConsecutiveEntry() + } + companion object { /** * Creates the test configurations. diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/OWNERS b/tests/FlickerTests/src/com/android/server/wm/flicker/close/OWNERS new file mode 100644 index 000000000000..f7c0a87f5dac --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/OWNERS @@ -0,0 +1,2 @@ +# window manager > animations/transitions +# Bug component: 316275 diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt index 0b1748a6bda4..535612a2bd8b 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt @@ -17,7 +17,9 @@ package com.android.server.wm.flicker.helpers import android.app.Instrumentation +import androidx.test.uiautomator.By import androidx.test.uiautomator.UiDevice +import androidx.test.uiautomator.Until import com.android.server.wm.flicker.testapp.ActivityOptions import com.android.server.wm.traces.common.FlickerComponentName import com.android.server.wm.traces.parser.toFlickerComponent @@ -47,4 +49,28 @@ class ImeAppAutoFocusHelper @JvmOverloads constructor( } launcherStrategy.launch(appName, expectedPackage) } + + fun startDialogThemedActivity(wmHelper: WindowManagerStateHelper) { + val button = uiDevice.wait(Until.findObject(By.res(getPackage(), + "start_dialog_themed_activity_btn")), FIND_TIMEOUT) + + require(button != null) { + "Button not found, this usually happens when the device " + + "was left in an unknown state (e.g. Screen turned off)" + } + button.click() + wmHelper.waitForAppTransitionIdle() + wmHelper.waitForFullScreenApp( + ActivityOptions.DIALOG_THEMED_ACTIVITY_COMPONENT_NAME.toFlickerComponent()) + } + fun dismissDialog(wmHelper: WindowManagerStateHelper) { + val dialog = uiDevice.wait( + Until.findObject(By.text("Dialog for test")), FIND_TIMEOUT) + + // Pressing back key to dismiss the dialog + if (dialog != null) { + uiDevice.pressBack() + wmHelper.waitForAppTransitionIdle() + } + } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt index f2696d8a71ff..815ea778555b 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt @@ -178,7 +178,7 @@ class CloseImeAutoOpenWindowToHomeTest(private val testSpec: FlickerTestParamete @JvmStatic fun getParams(): Collection<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance() - .getConfigNonRotationTests(repetitions = 1, + .getConfigNonRotationTests(repetitions = 5, // b/190352379 (IME doesn't show on app launch in 90 degrees) supportedRotations = listOf(Surface.ROTATION_0), supportedNavigationModes = listOf( diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeAndDialogThemeAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeAndDialogThemeAppTest.kt new file mode 100644 index 000000000000..429541c28d19 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeAndDialogThemeAppTest.kt @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.flicker.ime + +import android.app.Instrumentation +import android.platform.test.annotations.Presubmit +import android.view.Surface +import android.view.WindowManagerPolicyConstants +import androidx.test.filters.FlakyTest +import androidx.test.filters.RequiresDevice +import androidx.test.platform.app.InstrumentationRegistry +import com.android.server.wm.flicker.FlickerBuilderProvider +import com.android.server.wm.flicker.FlickerParametersRunnerFactory +import com.android.server.wm.flicker.FlickerTestParameter +import com.android.server.wm.flicker.FlickerTestParameterFactory +import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper +import com.android.server.wm.traces.common.FlickerComponentName +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Test IME snapshot mechanism won't apply when transitioning from non-IME focused dialog activity. + * To run this test: `atest FlickerTests:LaunchAppShowImeAndDialogThemeAppTest` + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class LaunchAppShowImeAndDialogThemeAppTest(private val testSpec: FlickerTestParameter) { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val testApp = ImeAppAutoFocusHelper(instrumentation, testSpec.startRotation) + + @FlickerBuilderProvider + fun buildFlicker(): FlickerBuilder { + return FlickerBuilder(instrumentation).apply { + setup { + eachRun { + testApp.launchViaIntent(wmHelper) + wmHelper.waitImeShown() + testApp.startDialogThemedActivity(wmHelper) + } + } + teardown { + eachRun { + testApp.exit() + } + } + transitions { + testApp.dismissDialog(wmHelper) + } + } + } + + /** + * Checks that [FlickerComponentName.IME] layer becomes visible during the transition + */ + @FlakyTest(bugId = 215884488) + @Test + fun imeWindowIsAlwaysVisible() = testSpec.imeWindowIsAlwaysVisible() + + /** + * Checks that [FlickerComponentName.IME] layer is visible at the end of the transition + */ + @Presubmit + @Test + fun imeLayerExistsEnd() { + testSpec.assertLayersEnd { + this.isVisible(FlickerComponentName.IME) + } + } + + /** + * Checks that [FlickerComponentName.IME_SNAPSHOT] layer is invisible always. + */ + @Presubmit + @Test + fun imeSnapshotNotVisible() { + testSpec.assertLayers { + this.isInvisible(FlickerComponentName.IME_SNAPSHOT) + } + } + + companion object { + /** + * Creates the test configurations. + * + * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring + * repetitions, screen orientation and navigation modes. + */ + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): Collection<FlickerTestParameter> { + return FlickerTestParameterFactory.getInstance() + .getConfigNonRotationTests( + repetitions = 3, + supportedRotations = listOf(Surface.ROTATION_0), + supportedNavigationModes = listOf( + WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY, + WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY + ) + ) + } + } +}
\ No newline at end of file diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OWNERS b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OWNERS new file mode 100644 index 000000000000..301fafa5309e --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OWNERS @@ -0,0 +1,2 @@ +# ime +# Bug component: 34867 diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt index 0ad0a033a4fe..5e06f11fb6b8 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt @@ -18,8 +18,8 @@ package com.android.server.wm.flicker.ime import android.app.Instrumentation import android.platform.test.annotations.Presubmit +import android.view.Display import android.view.Surface -import android.view.WindowManagerPolicyConstants import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice import androidx.test.platform.app.InstrumentationRegistry @@ -41,7 +41,9 @@ import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled import com.android.server.wm.flicker.statusBarLayerIsVisible import com.android.server.wm.flicker.statusBarLayerRotatesScales import com.android.server.wm.flicker.statusBarWindowIsVisible +import com.android.server.wm.traces.common.ConditionList import com.android.server.wm.traces.common.FlickerComponentName +import com.android.server.wm.traces.common.WindowManagerConditionsFactory import org.junit.Assume.assumeFalse import org.junit.Assume.assumeTrue import org.junit.FixMethodOrder @@ -63,6 +65,12 @@ class ReOpenImeWindowTest(private val testSpec: FlickerTestParameter) { private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() private val testApp = ImeAppAutoFocusHelper(instrumentation, testSpec.startRotation) + private val waitConditionSetup = ConditionList(listOf( + WindowManagerConditionsFactory.isAppTransitionIdle(Display.DEFAULT_DISPLAY), + WindowManagerConditionsFactory.hasLayersAnimating().negate(), + WindowManagerConditionsFactory.isHomeActivityVisible() + )) + @FlickerBuilderProvider fun buildFlicker(): FlickerBuilder { return FlickerBuilder(instrumentation).apply { @@ -73,8 +81,7 @@ class ReOpenImeWindowTest(private val testSpec: FlickerTestParameter) { } eachRun { device.pressRecentApps() - wmHelper.waitImeGone() - wmHelper.waitForAppTransitionIdle() + wmHelper.waitFor(waitConditionSetup) this.setRotation(testSpec.startRotation) } } @@ -231,11 +238,8 @@ class ReOpenImeWindowTest(private val testSpec: FlickerTestParameter) { fun getParams(): Collection<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance() .getConfigNonRotationTests( - repetitions = 1, - supportedRotations = listOf(Surface.ROTATION_0), - supportedNavigationModes = listOf( - WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY - ) + repetitions = 5, + supportedRotations = listOf(Surface.ROTATION_0) ) } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OWNERS b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OWNERS new file mode 100644 index 000000000000..2c414a27cacb --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OWNERS @@ -0,0 +1,4 @@ +# System UI > ... > Overview (recent apps) > UI +# Bug template url: https://b.corp.google.com/issues/new?component=807991&template=1390280 = per-file *Overview* +# window manager > animations/transitions +# Bug template url: https://b.corp.google.com/issues/new?component=316275&template=1018192 diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt index e07a8f94d651..5450610722fa 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt @@ -122,6 +122,11 @@ class OpenAppColdTest(testSpec: FlickerTestParameter) : OpenAppTransition(testSp @Test override fun navBarWindowIsVisible() = super.navBarWindowIsVisible() + /** {@inheritDoc} */ + @FlakyTest(bugId = 213852103) + @Test + override fun entireScreenCovered() = super.entireScreenCovered() + companion object { /** * Creates the test configurations. diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt index 6e5c600eb141..a85dcc57ddb2 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt @@ -127,7 +127,7 @@ class OpenAppNonResizeableTest(testSpec: FlickerTestParameter) : OpenAppTransiti * The `isAppWindowInvisible` step is optional because we log once per frame, upon logging, * the window may be visible or not depending on what was processed until that moment. */ - @Presubmit + @FlakyTest(bugId = 203538234) @Test fun appWindowBecomesVisible() { testSpec.assertWm { @@ -240,7 +240,7 @@ class OpenAppNonResizeableTest(testSpec: FlickerTestParameter) : OpenAppTransiti * it cannot use the regular assertion (check over time), because on lock screen neither * the app not the launcher are visible, and there is no top visible window. */ - @Presubmit + @FlakyTest(bugId = 203538234) @Test override fun appWindowReplacesLauncherAsTopWindow() { testSpec.assertWm { diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/OWNERS b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/OWNERS new file mode 100644 index 000000000000..897fe5dee7fb --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/OWNERS @@ -0,0 +1,2 @@ +# System UI > ... > Launcher > Gesture nav +# Bug component: 565144 diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/OWNERS b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/OWNERS new file mode 100644 index 000000000000..f7c0a87f5dac --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/OWNERS @@ -0,0 +1,2 @@ +# window manager > animations/transitions +# Bug component: 316275 diff --git a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml index 0a88f6bb5908..9e371e5e381e 100644 --- a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml +++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml @@ -97,5 +97,16 @@ <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> + <activity android:name=".DialogThemedActivity" + android:taskAffinity="com.android.server.wm.flicker.testapp.DialogThemedActivity" + android:configChanges="orientation|screenSize" + android:theme="@style/DialogTheme" + android:label="DialogThemedActivity" + android:exported="true"> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + <category android:name="android.intent.category.LAUNCHER"/> + </intent-filter> + </activity> </application> </manifest> diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_ime.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_ime.xml index 2620ff407efc..baaf7073b3a6 100644 --- a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_ime.xml +++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_ime.xml @@ -31,4 +31,9 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Finish activity" /> + <Button + android:id="@+id/start_dialog_themed_activity_btn" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="Start dialog themed activity" /> </LinearLayout> diff --git a/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml b/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml index 87a61a88c094..746b0f4ca7e1 100644 --- a/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml +++ b/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml @@ -27,4 +27,15 @@ <style name="CutoutNever"> <item name="android:windowLayoutInDisplayCutoutMode">never</item> </style> -</resources>
\ No newline at end of file + + <style name="DialogTheme" parent="@android:style/Theme.DeviceDefault"> + <item name="android:windowAnimationStyle">@null</item> + <item name="android:windowIsTranslucent">true</item> + <item name="android:windowBackground">@null</item> + <item name="android:windowContentOverlay">@null</item> + <item name="android:windowNoTitle">true</item> + <item name="android:windowIsFloating">true</item> + <item name="android:backgroundDimEnabled">false</item> + <item name="android:windowSoftInputMode">stateUnchanged</item> + </style> +</resources> diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java index baf36ab0e132..13adb681c30c 100644 --- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java @@ -56,4 +56,8 @@ public class ActivityOptions { public static final ComponentName LAUNCH_NEW_TASK_ACTIVITY_COMPONENT_NAME = new ComponentName(FLICKER_APP_PACKAGE, FLICKER_APP_PACKAGE + ".LaunchNewTaskActivity"); + public static final String DIALOG_THEMED_ACTIVITY = "DialogThemedActivity"; + public static final ComponentName DIALOG_THEMED_ACTIVITY_COMPONENT_NAME = + new ComponentName(FLICKER_APP_PACKAGE, + FLICKER_APP_PACKAGE + ".DialogThemedActivity"); } diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/DialogThemedActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/DialogThemedActivity.java new file mode 100644 index 000000000000..27606d81f9d3 --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/DialogThemedActivity.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.flicker.testapp; + +import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; +import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; +import static android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND; + +import android.app.Activity; +import android.app.AlertDialog; +import android.graphics.Color; +import android.os.Bundle; +import android.view.WindowManager; +import android.widget.LinearLayout; +import android.widget.TextView; + +public class DialogThemedActivity extends Activity { + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_simple); + getWindow().getDecorView().setBackgroundColor(Color.TRANSPARENT); + TextView textView = new TextView(this); + textView.setText("This is a test dialog"); + textView.setTextColor(Color.BLACK); + LinearLayout layout = new LinearLayout(this); + layout.setBackgroundColor(Color.GREEN); + layout.addView(textView); + + // Create a dialog with dialog-themed activity + AlertDialog dialog = new AlertDialog.Builder(this) + .setView(layout) + .setTitle("Dialog for test") + .create(); + final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(MATCH_PARENT, + MATCH_PARENT); + attrs.flags = FLAG_DIM_BEHIND | FLAG_ALT_FOCUSABLE_IM; + dialog.getWindow().getDecorView().setLayoutParams(attrs); + dialog.setCanceledOnTouchOutside(true); + dialog.show(); + dialog.setOnDismissListener((d) -> finish()); + } +} diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeActivityAutoFocus.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeActivityAutoFocus.java index 05da717620aa..bb200f125507 100644 --- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeActivityAutoFocus.java +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeActivityAutoFocus.java @@ -16,6 +16,8 @@ package com.android.server.wm.flicker.testapp; +import android.content.Intent; +import android.widget.Button; import android.widget.EditText; public class ImeActivityAutoFocus extends ImeActivity { @@ -26,5 +28,9 @@ public class ImeActivityAutoFocus extends ImeActivity { EditText editTextField = findViewById(R.id.plain_text_input); editTextField.requestFocus(); + + Button startThemedActivityButton = findViewById(R.id.start_dialog_themed_activity_btn); + startThemedActivityButton.setOnClickListener( + button -> startActivity(new Intent(this, DialogThemedActivity.class))); } } diff --git a/tests/vcn/java/com/android/server/vcn/VcnTest.java b/tests/vcn/java/com/android/server/vcn/VcnTest.java index 5d2f9d748581..6d269686e42f 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnTest.java +++ b/tests/vcn/java/com/android/server/vcn/VcnTest.java @@ -58,6 +58,7 @@ import android.util.ArraySet; import com.android.server.VcnManagementService.VcnCallback; import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; import com.android.server.vcn.Vcn.VcnGatewayStatusCallback; +import com.android.server.vcn.Vcn.VcnUserMobileDataStateListener; import com.android.server.vcn.VcnNetworkProvider.NetworkRequestListener; import org.junit.Before; @@ -208,6 +209,13 @@ public class VcnTest { } @Test + public void testMobileDataStateListenersRegistered() { + // Validate state from setUp() + verify(mTelephonyManager, times(3)) + .registerTelephonyCallback(any(), any(VcnUserMobileDataStateListener.class)); + } + + @Test public void testMobileDataStateCheckedOnInitialization_enabled() { // Validate state from setUp() assertTrue(mVcn.isMobileDataEnabled()); @@ -263,6 +271,24 @@ public class VcnTest { assertFalse(mVcn.isMobileDataEnabled()); } + @Test + public void testSubscriptionSnapshotUpdatesMobileDataStateListeners() { + final TelephonySubscriptionSnapshot updatedSnapshot = + mock(TelephonySubscriptionSnapshot.class); + + doReturn(new ArraySet<>(Arrays.asList(2, 4))) + .when(updatedSnapshot) + .getAllSubIdsInGroup(any()); + + mVcn.updateSubscriptionSnapshot(updatedSnapshot); + mTestLooper.dispatchAll(); + + verify(mTelephonyManager, times(4)) + .registerTelephonyCallback(any(), any(VcnUserMobileDataStateListener.class)); + verify(mTelephonyManager, times(2)) + .unregisterTelephonyCallback(any(VcnUserMobileDataStateListener.class)); + } + private void triggerVcnRequestListeners(NetworkRequestListener requestListener) { for (final int[] caps : TEST_CAPS) { startVcnGatewayWithCapabilities(requestListener, caps); @@ -402,24 +428,17 @@ public class VcnTest { verify(mVcnNetworkProvider).resendAllRequests(requestListener); } - private void verifyMobileDataToggled(boolean startingToggleState, boolean endingToggleState) { - final ArgumentCaptor<ContentObserver> captor = - ArgumentCaptor.forClass(ContentObserver.class); - verify(mContentResolver).registerContentObserver(any(), anyBoolean(), captor.capture()); - final ContentObserver contentObserver = captor.getValue(); - + private void setupForMobileDataTest(boolean startingToggleState) { // Start VcnGatewayConnections final NetworkRequestListener requestListener = verifyAndGetRequestListener(); mVcn.setMobileDataEnabled(startingToggleState); triggerVcnRequestListeners(requestListener); - final Map<VcnGatewayConnectionConfig, VcnGatewayConnection> gateways = - mVcn.getVcnGatewayConnectionConfigMap(); - - // Trigger data toggle change. - doReturn(endingToggleState).when(mTelephonyManager).isDataEnabled(); - contentObserver.onChange(false /* selfChange, ignored */); - mTestLooper.dispatchAll(); + } + private void verifyMobileDataToggledUpdatesGatewayConnections( + boolean startingToggleState, + boolean endingToggleState, + Map<VcnGatewayConnectionConfig, VcnGatewayConnection> gateways) { // Verify that data toggle changes restart ONLY INTERNET or DUN networks, and only if the // toggle state changed. for (Entry<VcnGatewayConnectionConfig, VcnGatewayConnection> entry : gateways.entrySet()) { @@ -433,29 +452,98 @@ public class VcnTest { } } + final NetworkRequestListener requestListener = verifyAndGetRequestListener(); if (startingToggleState != endingToggleState) { verify(mVcnNetworkProvider).resendAllRequests(requestListener); } assertEquals(endingToggleState, mVcn.isMobileDataEnabled()); } + private void verifyGlobalMobileDataToggled( + boolean startingToggleState, boolean endingToggleState) { + setupForMobileDataTest(startingToggleState); + final Map<VcnGatewayConnectionConfig, VcnGatewayConnection> gateways = + mVcn.getVcnGatewayConnectionConfigMap(); + + // Trigger data toggle change + final ArgumentCaptor<ContentObserver> captor = + ArgumentCaptor.forClass(ContentObserver.class); + verify(mContentResolver).registerContentObserver(any(), anyBoolean(), captor.capture()); + final ContentObserver contentObserver = captor.getValue(); + + doReturn(endingToggleState).when(mTelephonyManager).isDataEnabled(); + contentObserver.onChange(false /* selfChange, ignored */); + mTestLooper.dispatchAll(); + + // Verify resultant behavior + verifyMobileDataToggledUpdatesGatewayConnections( + startingToggleState, endingToggleState, gateways); + } + + @Test + public void testGlobalMobileDataEnabled() { + verifyGlobalMobileDataToggled( + false /* startingToggleState */, true /* endingToggleState */); + } + + @Test + public void testGlobalMobileDataDisabled() { + verifyGlobalMobileDataToggled( + true /* startingToggleState */, false /* endingToggleState */); + } + + @Test + public void testGlobalMobileDataObserverFiredWithoutChanges_dataEnabled() { + verifyGlobalMobileDataToggled( + false /* startingToggleState */, false /* endingToggleState */); + } + + @Test + public void testGlobalMobileDataObserverFiredWithoutChanges_dataDisabled() { + verifyGlobalMobileDataToggled(true /* startingToggleState */, true /* endingToggleState */); + } + + private void verifySubscriptionMobileDataToggled( + boolean startingToggleState, boolean endingToggleState) { + setupForMobileDataTest(startingToggleState); + final Map<VcnGatewayConnectionConfig, VcnGatewayConnection> gateways = + mVcn.getVcnGatewayConnectionConfigMap(); + + // Trigger data toggle change. + final ArgumentCaptor<VcnUserMobileDataStateListener> captor = + ArgumentCaptor.forClass(VcnUserMobileDataStateListener.class); + verify(mTelephonyManager, times(3)).registerTelephonyCallback(any(), captor.capture()); + final VcnUserMobileDataStateListener listener = captor.getValue(); + + doReturn(endingToggleState).when(mTelephonyManager).isDataEnabled(); + listener.onUserMobileDataStateChanged(false /* enabled, ignored */); + mTestLooper.dispatchAll(); + + // Verify resultant behavior + verifyMobileDataToggledUpdatesGatewayConnections( + startingToggleState, endingToggleState, gateways); + } + @Test - public void testMobileDataEnabled() { - verifyMobileDataToggled(false /* startingToggleState */, true /* endingToggleState */); + public void testSubscriptionMobileDataEnabled() { + verifyGlobalMobileDataToggled( + false /* startingToggleState */, true /* endingToggleState */); } @Test - public void testMobileDataDisabled() { - verifyMobileDataToggled(true /* startingToggleState */, false /* endingToggleState */); + public void testSubscriptionMobileDataDisabled() { + verifyGlobalMobileDataToggled( + true /* startingToggleState */, false /* endingToggleState */); } @Test - public void testMobileDataObserverFiredWithoutChanges_dataEnabled() { - verifyMobileDataToggled(false /* startingToggleState */, false /* endingToggleState */); + public void testSubscriptionMobileDataListenerFiredWithoutChanges_dataEnabled() { + verifyGlobalMobileDataToggled( + false /* startingToggleState */, false /* endingToggleState */); } @Test - public void testMobileDataObserverFiredWithoutChanges_dataDisabled() { - verifyMobileDataToggled(true /* startingToggleState */, true /* endingToggleState */); + public void testSubscriptionMobileDataListenerFiredWithoutChanges_dataDisabled() { + verifyGlobalMobileDataToggled(true /* startingToggleState */, true /* endingToggleState */); } } diff --git a/tools/fonts/fontchain_linter.py b/tools/fonts/fontchain_linter.py index 3131f5687c6a..99f77fee4698 100755 --- a/tools/fonts/fontchain_linter.py +++ b/tools/fonts/fontchain_linter.py @@ -54,6 +54,7 @@ LANG_TO_SCRIPT = { 'or': 'Orya', 'pa': 'Guru', 'pt': 'Latn', + 'ru': 'Latn', 'sk': 'Latn', 'sl': 'Latn', 'sq': 'Latn', diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt b/tools/lint/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt index 900c2145975a..a6fd9bba6192 100644 --- a/tools/lint/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt +++ b/tools/lint/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt @@ -31,7 +31,9 @@ class AndroidFrameworkIssueRegistry : IssueRegistry() { CallingIdentityTokenDetector.ISSUE_RESTORE_IDENTITY_CALL_NOT_IN_FINALLY_BLOCK, CallingIdentityTokenDetector.ISSUE_USE_OF_CALLER_AWARE_METHODS_WITH_CLEARED_IDENTITY, CallingIdentityTokenDetector.ISSUE_CLEAR_IDENTITY_CALL_NOT_FOLLOWED_BY_TRY_FINALLY, - CallingSettingsNonUserGetterMethodsDetector.ISSUE_NON_USER_GETTER_CALLED + CallingSettingsNonUserGetterMethodsDetector.ISSUE_NON_USER_GETTER_CALLED, + EnforcePermissionDetector.ISSUE_MISSING_ENFORCE_PERMISSION, + EnforcePermissionDetector.ISSUE_MISMATCHING_ENFORCE_PERMISSION ) override val api: Int diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/EnforcePermissionDetector.kt b/tools/lint/checks/src/main/java/com/google/android/lint/EnforcePermissionDetector.kt new file mode 100644 index 000000000000..8011b36c9a8f --- /dev/null +++ b/tools/lint/checks/src/main/java/com/google/android/lint/EnforcePermissionDetector.kt @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.lint + +import com.android.tools.lint.detector.api.AnnotationInfo +import com.android.tools.lint.detector.api.AnnotationOrigin +import com.android.tools.lint.detector.api.AnnotationUsageInfo +import com.android.tools.lint.detector.api.AnnotationUsageType +import com.android.tools.lint.detector.api.ConstantEvaluator +import com.android.tools.lint.detector.api.Category +import com.android.tools.lint.detector.api.Detector +import com.android.tools.lint.detector.api.Implementation +import com.android.tools.lint.detector.api.Issue +import com.android.tools.lint.detector.api.JavaContext +import com.android.tools.lint.detector.api.Scope +import com.android.tools.lint.detector.api.Severity +import com.android.tools.lint.detector.api.SourceCodeScanner +import com.intellij.psi.PsiAnnotation +import com.intellij.psi.PsiClass +import com.intellij.psi.PsiMethod +import org.jetbrains.uast.UElement + +/** + * Lint Detector that ensures that any method overriding a method annotated + * with @EnforcePermission is also annotated with the exact same annotation. + * The intent is to surface the effective permission checks to the service + * implementations. + */ +class EnforcePermissionDetector : Detector(), SourceCodeScanner { + + val ENFORCE_PERMISSION = "android.annotation.EnforcePermission" + + override fun applicableAnnotations(): List<String> { + return listOf(ENFORCE_PERMISSION) + } + + private fun areAnnotationsEquivalent( + context: JavaContext, + anno1: PsiAnnotation, + anno2: PsiAnnotation + ): Boolean { + if (anno1.qualifiedName != anno2.qualifiedName) { + return false + } + val attr1 = anno1.parameterList.attributes + val attr2 = anno2.parameterList.attributes + if (attr1.size != attr2.size) { + return false + } + for (i in attr1.indices) { + if (attr1[i].name != attr2[i].name) { + return false + } + val v1 = ConstantEvaluator.evaluate(context, attr1[i].value) + val v2 = ConstantEvaluator.evaluate(context, attr2[i].value) + if (v1 != v2) { + return false + } + } + return true + } + + override fun visitAnnotationUsage( + context: JavaContext, + element: UElement, + annotationInfo: AnnotationInfo, + usageInfo: AnnotationUsageInfo + ) { + if (usageInfo.type == AnnotationUsageType.EXTENDS) { + val newClass = element.sourcePsi?.parent?.parent as PsiClass + val extendedClass: PsiClass = usageInfo.referenced as PsiClass + val newAnnotation = newClass.getAnnotation(ENFORCE_PERMISSION) + val extendedAnnotation = extendedClass.getAnnotation(ENFORCE_PERMISSION)!! + + val location = context.getLocation(element) + val newClassName = newClass.qualifiedName + val extendedClassName = extendedClass.qualifiedName + if (newAnnotation == null) { + val msg = "The class $newClassName extends the class $extendedClassName which " + + "is annotated with @EnforcePermission. The same annotation must be used " + + "on $newClassName." + context.report(ISSUE_MISSING_ENFORCE_PERMISSION, element, location, msg) + } else if (!areAnnotationsEquivalent(context, newAnnotation, extendedAnnotation)) { + val msg = "The class $newClassName is annotated with ${newAnnotation.text} " + + "which differs from the parent class $extendedClassName: " + + "${extendedAnnotation.text}. The same annotation must be used for " + + "both classes." + context.report(ISSUE_MISMATCHING_ENFORCE_PERMISSION, element, location, msg) + } + } else if (usageInfo.type == AnnotationUsageType.METHOD_OVERRIDE && + annotationInfo.origin == AnnotationOrigin.METHOD) { + val overridingMethod = element.sourcePsi as PsiMethod + val overriddenMethod = usageInfo.referenced as PsiMethod + val overridingAnnotation = overridingMethod.getAnnotation(ENFORCE_PERMISSION) + val overriddenAnnotation = overriddenMethod.getAnnotation(ENFORCE_PERMISSION)!! + + val location = context.getLocation(element) + val overridingClass = overridingMethod.parent as PsiClass + val overriddenClass = overriddenMethod.parent as PsiClass + val overridingName = "${overridingClass.name}.${overridingMethod.name}" + val overriddenName = "${overriddenClass.name}.${overriddenMethod.name}" + if (overridingAnnotation == null) { + val msg = "The method $overridingName overrides the method $overriddenName which " + + "is annotated with @EnforcePermission. The same annotation must be used " + + "on $overridingName" + context.report(ISSUE_MISSING_ENFORCE_PERMISSION, element, location, msg) + } else if (!areAnnotationsEquivalent( + context, overridingAnnotation, overriddenAnnotation)) { + val msg = "The method $overridingName is annotated with " + + "${overridingAnnotation.text} which differs from the overridden " + + "method $overriddenName: ${overriddenAnnotation.text}. The same " + + "annotation must be used for both methods." + context.report(ISSUE_MISMATCHING_ENFORCE_PERMISSION, element, location, msg) + } + } + } + + companion object { + val EXPLANATION = """ + The @EnforcePermission annotation is used to indicate that the underlying binder code + has already verified the caller's permissions before calling the appropriate method. The + verification code is usually generated by the AIDL compiler, which also takes care of + annotating the generated Java code. + + In order to surface that information to platform developers, the same annotation must be + used on the implementation class or methods. + """ + + val ISSUE_MISSING_ENFORCE_PERMISSION: Issue = Issue.create( + id = "MissingEnforcePermissionAnnotation", + briefDescription = "Missing @EnforcePermission annotation on Binder method", + explanation = EXPLANATION, + category = Category.SECURITY, + priority = 6, + severity = Severity.ERROR, + implementation = Implementation( + EnforcePermissionDetector::class.java, + Scope.JAVA_FILE_SCOPE + ) + ) + + val ISSUE_MISMATCHING_ENFORCE_PERMISSION: Issue = Issue.create( + id = "MismatchingEnforcePermissionAnnotation", + briefDescription = "Incorrect @EnforcePermission annotation on Binder method", + explanation = EXPLANATION, + category = Category.SECURITY, + priority = 6, + severity = Severity.ERROR, + implementation = Implementation( + EnforcePermissionDetector::class.java, + Scope.JAVA_FILE_SCOPE + ) + ) + } +} diff --git a/tools/lint/checks/src/test/java/com/google/android/lint/EnforcePermissionDetectorTest.kt b/tools/lint/checks/src/test/java/com/google/android/lint/EnforcePermissionDetectorTest.kt new file mode 100644 index 000000000000..f5f4ebee24e0 --- /dev/null +++ b/tools/lint/checks/src/test/java/com/google/android/lint/EnforcePermissionDetectorTest.kt @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.lint + +import com.android.tools.lint.checks.infrastructure.LintDetectorTest +import com.android.tools.lint.checks.infrastructure.TestFile +import com.android.tools.lint.checks.infrastructure.TestLintTask +import com.android.tools.lint.detector.api.Detector +import com.android.tools.lint.detector.api.Issue + +@Suppress("UnstableApiUsage") +class EnforcePermissionDetectorTest : LintDetectorTest() { + override fun getDetector(): Detector = EnforcePermissionDetector() + + override fun getIssues(): List<Issue> = listOf( + EnforcePermissionDetector.ISSUE_MISSING_ENFORCE_PERMISSION, + EnforcePermissionDetector.ISSUE_MISMATCHING_ENFORCE_PERMISSION + ) + + override fun lint(): TestLintTask = super.lint().allowMissingSdk(true) + + fun testDoesNotDetectIssuesCorrectAnnotationOnClass() { + lint().files(java( + """ + package test.pkg; + @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE) + public class TestClass1 extends IFoo.Stub { + public void testMethod() {} + } + """).indented(), + *stubs + ) + .run() + .expectClean() + } + + fun testDoesNotDetectIssuesCorrectAnnotationOnMethod() { + lint().files(java( + """ + package test.pkg; + import android.annotation.EnforcePermission; + public class TestClass2 extends IFooMethod.Stub { + @Override + @EnforcePermission(android.Manifest.permission.READ_PHONE_STATE) + public void testMethod() {} + } + """).indented(), + *stubs + ) + .run() + .expectClean() + } + + fun testDetectIssuesMismatchingAnnotationOnClass() { + lint().files(java( + """ + package test.pkg; + @android.annotation.EnforcePermission(android.Manifest.permission.INTERNET) + public class TestClass3 extends IFoo.Stub { + public void testMethod() {} + } + """).indented(), + *stubs + ) + .run() + .expect("""src/test/pkg/TestClass3.java:3: Error: The class test.pkg.TestClass3 is \ +annotated with @android.annotation.EnforcePermission(android.Manifest.permission.INTERNET) \ +which differs from the parent class IFoo.Stub: \ +@android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE). The \ +same annotation must be used for both classes. [MismatchingEnforcePermissionAnnotation] +public class TestClass3 extends IFoo.Stub { + ~~~~~~~~~ +1 errors, 0 warnings""".addLineContinuation()) + } + + fun testDetectIssuesMismatchingAnnotationOnMethod() { + lint().files(java( + """ + package test.pkg; + public class TestClass4 extends IFooMethod.Stub { + @android.annotation.EnforcePermission(android.Manifest.permission.INTERNET) + public void testMethod() {} + } + """).indented(), + *stubs + ) + .run() + .expect("""src/test/pkg/TestClass4.java:4: Error: The method TestClass4.testMethod is \ +annotated with @android.annotation.EnforcePermission(android.Manifest.permission.INTERNET) \ +which differs from the overridden method Stub.testMethod: \ +@android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE). The same \ +annotation must be used for both methods. [MismatchingEnforcePermissionAnnotation] + public void testMethod() {} + ~~~~~~~~~~ +1 errors, 0 warnings""".addLineContinuation()) + } + + fun testDetectIssuesMissingAnnotationOnClass() { + lint().files(java( + """ + package test.pkg; + public class TestClass5 extends IFoo.Stub { + public void testMethod() {} + } + """).indented(), + *stubs + ) + .run() + .expect("""src/test/pkg/TestClass5.java:2: Error: The class test.pkg.TestClass5 extends \ +the class IFoo.Stub which is annotated with @EnforcePermission. The same annotation must be \ +used on test.pkg.TestClass5. [MissingEnforcePermissionAnnotation] +public class TestClass5 extends IFoo.Stub { + ~~~~~~~~~ +1 errors, 0 warnings""".addLineContinuation()) + } + + fun testDetectIssuesMissingAnnotationOnMethod() { + lint().files(java( + """ + package test.pkg; + public class TestClass6 extends IFooMethod.Stub { + public void testMethod() {} + } + """).indented(), + *stubs + ) + .run() + .expect("""src/test/pkg/TestClass6.java:3: Error: The method TestClass6.testMethod \ +overrides the method Stub.testMethod which is annotated with @EnforcePermission. The same \ +annotation must be used on TestClass6.testMethod [MissingEnforcePermissionAnnotation] + public void testMethod() {} + ~~~~~~~~~~ +1 errors, 0 warnings""".addLineContinuation()) + } + + /* Stubs */ + + private val interfaceIFooStub: TestFile = java( + """ + @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE) + public interface IFoo { + @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE) + public static abstract class Stub implements IFoo { + @Override + public void testMethod() {} + } + public void testMethod(); + } + """ + ).indented() + + private val interfaceIFooMethodStub: TestFile = java( + """ + public interface IFooMethod { + public static abstract class Stub implements IFooMethod { + @Override + @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE) + public void testMethod() {} + } + @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE) + public void testMethod(); + } + """ + ).indented() + + private val manifestPermissionStub: TestFile = java( + """ + package android.Manifest; + class permission { + public static final String READ_PHONE_STATE = "android.permission.READ_PHONE_STATE"; + public static final String INTERNET = "android.permission.INTERNET"; + } + """ + ).indented() + + private val enforcePermissionAnnotationStub: TestFile = java( + """ + package android.annotation; + public @interface EnforcePermission {} + """ + ).indented() + + private val stubs = arrayOf(interfaceIFooStub, interfaceIFooMethodStub, + manifestPermissionStub, enforcePermissionAnnotationStub) + + // Substitutes "backslash + new line" with an empty string to imitate line continuation + private fun String.addLineContinuation(): String = this.trimIndent().replace("\\\n", "") +} diff --git a/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java b/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java index 3b7566051fae..459696e81ee4 100644 --- a/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java +++ b/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java @@ -1262,6 +1262,26 @@ public class WifiNl80211Manager { } /** + * Notifies the wificond daemon that the WiFi framework has successfully updated the Country + * Code of the driver. The wificond daemon needs this notification if the device does not + * support the NL80211_CMD_REG_CHANGED (otherwise it will find out on its own). The wificond + * updates in internal state in response to this Country Code update. + * + * @return true on success, false otherwise. + */ + public boolean notifyCountryCodeChanged() { + try { + if (mWificond != null) { + mWificond.notifyCountryCodeChanged(); + return true; + } + } catch (RemoteException e1) { + Log.e(TAG, "Failed to notify country code changed due to remote exception"); + } + return false; + } + + /** * Register the provided callback handler for SoftAp events. The interface must first be created * using {@link #setupInterfaceForSoftApMode(String)}. The callback registration is valid until * the interface is deleted using {@link #tearDownSoftApInterface(String)} (no deregistration diff --git a/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java b/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java index 3fb23013bdec..4032a7b0c75c 100644 --- a/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java +++ b/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java @@ -1137,6 +1137,26 @@ public class WifiNl80211ManagerTest { assertEquals(capaExpected, capaActual); } + /** + * Tests notifyCountryCodeChanged + */ + @Test + public void testNotifyCountryCodeChanged() throws Exception { + doNothing().when(mWificond).notifyCountryCodeChanged(); + assertTrue(mWificondControl.notifyCountryCodeChanged()); + verify(mWificond).notifyCountryCodeChanged(); + } + + /** + * Tests notifyCountryCodeChanged with RemoteException + */ + @Test + public void testNotifyCountryCodeChangedRemoteException() throws Exception { + doThrow(new RemoteException()).when(mWificond).notifyCountryCodeChanged(); + assertFalse(mWificondControl.notifyCountryCodeChanged()); + verify(mWificond).notifyCountryCodeChanged(); + } + // Create a ArgumentMatcher which captures a SingleScanSettings parameter and checks if it // matches the provided frequency set and ssid set. private class ScanMatcher implements ArgumentMatcher<SingleScanSettings> { |